def apply_to_backend_request(self, block: Block, level: Tuple[Factor, Union[SimpleLevel, DerivedLevel]], backend_request: BackendRequest ) -> None: sublists = self._build_variable_sublists(block, level, self.k) implications = [] # Handle the regular cases (1 => 2 ^ ... ^ n ^ ~n+1) trim = len(sublists) if self.k > 1 else len(sublists) - 1 for idx, l in enumerate(sublists[:trim]): if idx > 0: p_list = [Not(sublists[idx-1][0]), l[0]] p = And(p_list) if len(p_list) > 1 else p_list[0] else: p = l[0] if idx < len(sublists) - 1: q_list = cast(List[Any], l[1:]) + [Not(sublists[idx+1][-1])] q = And(q_list) if len(q_list) > 1 else q_list[0] else: q = And(l[1:]) if len(l[1:]) > 1 else l[self.k - 1] implications.append(If(p, q)) # Handle the tail if len(sublists[-1]) > 1: tail = sublists[-1] tail.reverse() for idx in range(len(tail) - 1): implications.append(If(l[idx], l[idx + 1])) (cnf, new_fresh) = block.cnf_fn(And(implications), backend_request.fresh) backend_request.cnfs.append(cnf) backend_request.fresh = new_fresh
def apply(block: MultipleCrossBlock, backend_request: BackendRequest) -> None: # Treat each crossing seperately, and repeat the same process as fullycross for c in block.crossing: fresh = backend_request.fresh # Step 1: Get a list of the trials that are involved in the crossing. crossing_size = max(block.min_trials, block.crossing_size()) crossing_trials = list(filter(lambda t: all(map(lambda f: f.applies_to_trial(t), c)), range(1, block.trials_per_sample() + 1))) crossing_trials = crossing_trials[:crossing_size] # Step 2: For each trial, cross all levels of all factors in the crossing. crossing_factors = list(map(lambda t: (list(product(*[block.factor_variables_for_trial(f, t) for f in c]))), crossing_trials)) # Step 3: For each trial, cross all levels of all design-only factors in the crossing. design_factors = cast(List[List[List[int]]], []) design_factors = list(map(lambda _: [], crossing_trials)) for f in list(filter(lambda f: f not in c and not f.has_complex_window, block.design)): for i, t in enumerate(crossing_trials): design_factors[i].append(block.factor_variables_for_trial(f, t)) design_combinations = cast(List[List[Tuple[int, ...]]], []) design_combinations = list(map(lambda l: list(product(*l)), design_factors)) # Step 4: For each trial, combine each of the crossing factors with all of the design-only factors. crossings = cast(List[List[List[Tuple[int, ...]]]], []) for i, t in enumerate(crossing_trials): crossings.append(list(map(lambda c: [c] + design_combinations[i], crossing_factors[i]))) # Step 5: Remove crossings that are not possible. # From here on ignore all values other than the first in every list. crossings = block.filter_excluded_derived_levels(crossings) # Step 6: Allocate additional variables to represent each crossing. num_state_vars = list(map(lambda c: len(c), crossings)) state_vars = list(range(fresh, fresh + sum(num_state_vars))) fresh += sum(num_state_vars) # Step 7: Associate each state variable with its crossing. flattened_crossings = list(chain.from_iterable(crossings)) iffs = list(map(lambda n: Iff(state_vars[n], And([*flattened_crossings[n][0]])), range(len(state_vars)))) # Step 8: Constrain each crossing to occur in only one trial. states = list(chunk(state_vars, block.crossing_size())) transposed = cast(List[List[int]], list(map(list, zip(*states)))) # We Use n < 2 rather than n = 1 here because they may exclude some levels from the crossing. # This ensures that there won't be duplicates, while still allowing some to be missing. # backend_request.ll_requests += list(map(lambda l: LowLevelRequest("LT", 2, l), transposed)) backend_request.ll_requests += list(map(lambda l: LowLevelRequest("GT", 0, l), transposed)) (cnf, new_fresh) = block.cnf_fn(And(iffs), fresh) backend_request.cnfs.append(cnf) backend_request.fresh = new_fresh
def __apply_derivation(self, block: Block, backend_request: BackendRequest) -> None: trial_size = block.variables_per_trial() cross_size = block.trials_per_sample() iffs = [] for n in range(cross_size): or_clause = Or(list(And(list(map(lambda x: x + (n * trial_size) + 1, l))) for l in self.dependent_idxs)) iffs.append(Iff(self.derived_idx + (n * trial_size) + 1, or_clause)) (cnf, new_fresh) = block.cnf_fn(And(iffs), backend_request.fresh) backend_request.cnfs.append(cnf) backend_request.fresh = new_fresh
def apply_to_backend_request(self, block: Block, level: Tuple[Factor, Union[SimpleLevel, DerivedLevel]], backend_request: BackendRequest) -> None: # Request sublists for k+1 to allow us to determine the transition sublists = self._build_variable_sublists(block, level, self.k + 1) implications = [] if sublists: # Starting corner case implications.append(If(sublists[0][0], And(sublists[0][1:-1]))) for sublist in sublists: implications.append(If(And([Not(sublist[0]), sublist[1]]), And(sublist[2:]))) # Ending corner case implications.append(If(sublists[-1][-1], And(sublists[-1][1:-1]))) (cnf, new_fresh) = block.cnf_fn(And(implications), backend_request.fresh) backend_request.cnfs.append(cnf) backend_request.fresh = new_fresh
def __apply_derivation_with_complex_window(self, block: Block, backend_request: BackendRequest) -> None: trial_size = block.variables_per_trial() trial_count = block.trials_per_sample() iffs = [] f = self.factor window = f.levels[0].window t = 0 for n in range(trial_count): if not f.applies_to_trial(n + 1): continue num_levels = len(f.levels) get_trial_size = lambda x: trial_size if x < block.grid_variables() else len(block.decode_variable(x+1)[0].levels) or_clause = Or(list(And(list(map(lambda x: x + (t * window.stride * get_trial_size(x) + 1), l))) for l in self.dependent_idxs)) iffs.append(Iff(self.derived_idx + (t * num_levels) + 1, or_clause)) t += 1 (cnf, new_fresh) = block.cnf_fn(And(iffs), backend_request.fresh) backend_request.cnfs.append(cnf) backend_request.fresh = new_fresh