def generate_derivations(block: Block) -> List[Derivation]: derived_factors = list(filter(lambda f: f.is_derived(), block.design)) accum = [] for fact in derived_factors: for level in fact.levels: level_index = block.first_variable_for_level( fact.name, level.name) x_product = level.get_dependent_cross_product() # filter to valid tuples, and get their idxs valid_tuples = [ tup for tup in x_product if level.window.fn( *DerivationProcessor.generate_argument_list( level, tup)) ] valid_idxs = [[ block.first_variable_for_level(pair[0], pair[1]) for pair in tup_list ] for tup_list in valid_tuples] shifted_idxs = DerivationProcessor.shift_window( valid_idxs, level.window, block.variables_per_trial()) accum.append(Derivation(level_index, shifted_idxs, fact)) return accum
def generate_derivations(block: Block) -> List[Derivation]: derived_factors = list(filter(lambda f: f.is_derived(), block.design)) accum = [] for fact in derived_factors: according_level: Dict[Tuple[Any, ...], DerivedLevel] = {} # according_level = {} for level in fact.levels: level_index = block.first_variable_for_level(fact, level) x_product = level.get_dependent_cross_product() # filter to valid tuples, and get their idxs valid_tuples = [] for tup in x_product: args = DerivationProcessor.generate_argument_list( level, tup) fn_result = level.window.fn(*args) # Make sure the fn returned a boolean if not isinstance(fn_result, bool): raise ValueError( 'Derivation function did not return a boolean! factor={} level={} fn={} return={} args={} ' .format(fact.factor_name, get_external_level_name(level), level.window.fn, fn_result, args)) # If the result was true, add the tuple to the list if fn_result: valid_tuples.append(tup) if tup in according_level.keys(): raise ValueError( 'Factor={} matches both level={} and level={} with assignment={}' .format(fact.factor_name, according_level[tup], get_external_level_name(level), args)) else: according_level[tup] = get_external_level_name( level) if not valid_tuples: print( 'WARNING: There is no assignment that matches factor={} level={}' .format(fact.factor_name, get_external_level_name(level))) valid_idxs = [[ block.first_variable_for_level(pair[0], pair[1]) for pair in tup_list ] for tup_list in valid_tuples] shifted_idxs = DerivationProcessor.shift_window( valid_idxs, level.window, block.variables_per_trial()) accum.append(Derivation(level_index, shifted_idxs, fact)) return accum
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_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
def generate_derivations(block: Block) -> List[Derivation]: """Usage:: >>> import operator as op >>> color = Factor("color", ["red", "blue"]) >>> text = Factor("text", ["red", "blue"]) >>> conLevel = DerivedLevel("con", WithinTrial(op.eq, [color, text])) >>> incLevel = DerivedLevel("inc", WithinTrial(op.ne, [color, text])) >>> conFactor = Factor("congruent?", [conLevel, incLevel]) >>> design = [color, text, conFactor] >>> crossing = [color, text] >>> block = fully_cross_block(design, crossing, []) >>> DerivationProcessor.generate_derivations(block) [Derivation(derivedIdx=4, dependentIdxs=[[0, 2], [1, 3]]), Derivation(derivedIdx=5, dependentIdxs=[[0, 3], [1, 2]])] In the example above, the indicies of the design are: === ============= idx level === ============= 0 color:red 1 color:blue 2 text:red 3 text:blue 4 conFactor:con 5 conFactor:inc === ============= So the tuple ``(4, [[0,2], [1,3]])`` represents the information that the derivedLevel con is true iff ``(color:red && text:red) || (color:blue && text:blue)`` by pairing the relevant indices together. :rtype: returns a list of tuples. Each tuple is structured as: ``(index of the derived level, list of dependent levels)`` """ derived_factors: List[DerivedFactor] = [factor for factor in block.design if isinstance(factor, DerivedFactor)] accum = [] for factor in derived_factors: according_level: Dict[Tuple[Any, ...], DerivedLevel] = {} for level in factor.levels: cross_product: List[Tuple[Level, ...]] = level.get_dependent_cross_product() valid_tuples: List[Tuple[Level, ...]] = [] for level_tuple in cross_product: names = [level.name for level in level_tuple] if level.window.width != 1: # NOTE: mypy doesn't like this, but I'm not rewriting # it right now. Need to replace `chunk_list` with # a better version. names = list(chunk_list(names, level.window.width)) # type: ignore result = level.window.predicate(*names) if not isinstance(result, bool): raise ValueError(f"Expected derivation predicate to return bool; got {type(result)}.") if level.window.predicate(*names): valid_tuples.append(level_tuple) if level_tuple in according_level: raise ValueError(f"Factor {factor.name} matches {according_level[level_tuple].name} and " f"{level.name} with assignment {names}.") according_level[level_tuple] = level if not valid_tuples: print(f"WARNING: There is no assignment that matches factor {factor.name} with level {level.name}.") valid_indices = [[block.first_variable_for_level(level.factor, level) for level in valid_tuple] for valid_tuple in valid_tuples] shifted_indices = DerivationProcessor.shift_window(valid_indices, level.window, block.variables_per_trial()) level_index = block.first_variable_for_level(factor, level) accum.append(Derivation(level_index, shifted_indices, factor)) return accum
def __generate_encoding_diagram(blk: Block) -> str: diagram_str = "" design_size = blk.variables_per_trial() num_trials = blk.trials_per_sample() num_vars = blk.variables_per_sample() largest_number_len = len(str(num_vars)) header_widths = [] row_format_str = '| {:>7} |' for f in blk.design: # length of all levels concatenated for this factor level_names = list(map(get_external_level_name, f.levels)) level_name_widths = [ max(largest_number_len, l) for l in list(map(len, level_names)) ] level_names_width = sum(level_name_widths) + len( level_names) - 1 # Extra length for spaces in between names. factor_header_width = max(len(f.factor_name), level_names_width) header_widths.append(factor_header_width) # If the header is longer than the level widths combined, then they need to be lengthened. diff = factor_header_width - level_names_width if diff > 0: idx = 0 while diff > 0: level_name_widths[idx] += 1 idx += 1 diff -= 1 if idx >= len(level_name_widths): idx = 0 # While we're here, build up the row format str. row_format_str = reduce(lambda a, b: a + ' {{:^{}}}'.format(b), level_name_widths, row_format_str) row_format_str += ' |' header_format_str = reduce(lambda a, b: a + ' {{:^{}}} |'.format(b), header_widths, '| {:>7} |') factor_names = list(map(lambda f: f.factor_name, blk.design)) header_str = header_format_str.format(*["Trial"] + factor_names) row_width = len(header_str) # First line diagram_str += ('-' * row_width) + '\n' # Header diagram_str += header_str + '\n' # Level names all_level_names = [ ln for (fn, ln) in get_all_external_level_names(blk.design) ] diagram_str += row_format_str.format(*['#'] + all_level_names) + '\n' # Separator diagram_str += ('-' * row_width) + '\n' # Variables for t in range(num_trials): args = [str(t + 1)] for f in blk.design: if f.applies_to_trial(t + 1): variables = [ blk.first_variable_for_level(f, l) + 1 for l in f.levels ] if f.has_complex_window(): def acc_width(w) -> int: return w.width + ( acc_width(w.args[0].levels[0].window) - 1 if w.args[0].has_complex_window() else 0) width = acc_width(f.levels[0].window) stride = f.levels[0].window.stride stride_offset = (stride - 1) * int(t / stride) offset = t - width + 1 - stride_offset variables = list( map(lambda n: n + len(variables) * offset, variables)) else: variables = list( map(lambda n: n + design_size * t, variables)) args += list(map(str, variables)) else: args += list(repeat('', len(f.levels))) diagram_str += row_format_str.format(*args) + '\n' # Footer diagram_str += ('-' * row_width) + '\n' return diagram_str