def test_generate_derivations_when_derived_factor_precedes_dependencies(): block = fully_cross_block([congruency, motion, color, task], [color, motion, task], []) derivations = DerivationProcessor.generate_derivations(block) assert Derivation(0, [[4, 2], [5, 3]], congruency) in derivations assert Derivation(1, [[4, 3], [5, 2]], congruency) in derivations
def test_generate_derivations_with_window(): block = fully_cross_block([color, text, congruent_bookend], [color, text], []) assert DerivationProcessor.generate_derivations(block) == [ Derivation(16, [[0, 2], [1, 3]], congruent_bookend), Derivation(17, [[0, 3], [1, 2]], congruent_bookend) ]
def test_generate_derivations_transition(design): block = fully_cross_block(design, [color, text], []) assert DerivationProcessor.generate_derivations(block) == [ Derivation(16, [[0, 4], [1, 5]], color_repeats_factor), Derivation(17, [[0, 5], [1, 4]], color_repeats_factor) ]
def test_generate_derivations_within_trial(): assert DerivationProcessor.generate_derivations(blk) == [ Derivation(4, [[0, 2], [1, 3]], con_factor), Derivation(5, [[0, 3], [1, 2]], con_factor) ] integer = Factor("integer", ["1", "2"]) numeral = Factor("numeral", ["I", "II"]) text = Factor("text", ["one", "two"]) twoConLevel = DerivedLevel("twoCon", WithinTrial(two_con, [integer, numeral, text])) twoNotConLevel = DerivedLevel( "twoNotCon", WithinTrial(two_not_con, [integer, numeral, text])) two_con_factor = Factor("twoCon?", [twoConLevel, twoNotConLevel]) one_two_design = [integer, numeral, text, two_con_factor] one_two_crossing = [integer, numeral, text] assert DerivationProcessor.generate_derivations( fully_cross_block(one_two_design, one_two_crossing, [])) == [ Derivation(6, [[0, 2, 5], [0, 3, 4], [0, 3, 5], [1, 2, 4], [1, 2, 5], [1, 3, 4]], two_con_factor), Derivation(7, [[0, 2, 4], [1, 3, 5]], two_con_factor) ]
def test_generate_derivations_with_transition_that_depends_on_derived_levels(): block = fully_cross_block( [color, motion, task, response, response_transition], [color, motion, task], []) derivations = DerivationProcessor.generate_derivations(block) assert Derivation(64, [[6, 14], [7, 15]], response_transition) in derivations assert Derivation(65, [[6, 15], [7, 14]], response_transition) in derivations
def test_generate_derivations_with_multiple_transitions(design): block = fully_cross_block([color, text, color_repeats_factor, text_repeats_factor], [color, text], []) assert DerivationProcessor.generate_derivations(block) == [ Derivation(16, [[0, 4], [1, 5]], color_repeats_factor), Derivation(17, [[0, 5], [1, 4]], color_repeats_factor), Derivation(22, [[2, 6], [3, 7]], text_repeats_factor), Derivation(23, [[2, 7], [3, 6]], text_repeats_factor) ]
def test_generate_derivations(): assert DerivationProcessor.generate_derivations(block) == [ Derivation(16, [[0, 4, 2, 7], [0, 4, 3, 6], [0, 5, 2, 6], [0, 5, 3, 7], [1, 4, 2, 6], [1, 4, 3, 7], [1, 5, 2, 7], [1, 5, 3, 6]], change), Derivation(17, [[0, 4, 2, 6], [0, 4, 3, 7], [0, 5, 2, 7], [0, 5, 3, 6], [1, 4, 2, 7], [1, 4, 3, 6], [1, 5, 2, 6], [1, 5, 3, 7]], change) ]
def test_derivation_with_general_window(): block = fully_cross_block([color, text, congruent_bookend], [color, text], []) # congruent bookend - yes d = Derivation(16, [[0, 2], [1, 3]], congruent_bookend) backend_request = BackendRequest(19) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(17, Or([And([1, 3]), And([2, 4])])), Iff(19, Or([And([13, 15]), And([14, 16])])) ]), 19) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf] # congruent bookend - no d = Derivation(17, [[0, 3], [1, 2]], congruent_bookend) backend_request = BackendRequest(19) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(18, Or([And([1, 4]), And([2, 3])])), Iff(20, Or([And([13, 16]), And([14, 15])])) ]), 19) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
def test_derivation_with_multiple_transitions(): block = fully_cross_block( [color, text, color_repeats_factor, text_repeats_factor], [color, text], []) # Text repeats derivation d = Derivation(22, [[2, 6], [3, 7]], text_repeats_factor) backend_request = BackendRequest(29) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(23, Or([And([3, 7]), And([4, 8])])), Iff(25, Or([And([7, 11]), And([8, 12])])), Iff(27, Or([And([11, 15]), And([12, 16])])) ]), 29) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf] # Text does not repeat derivation d = Derivation(23, [[2, 7], [3, 6]], text_repeats_factor) backend_request = BackendRequest(29) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(24, Or([And([3, 8]), And([4, 7])])), Iff(26, Or([And([7, 12]), And([8, 11])])), Iff(28, Or([And([11, 16]), And([12, 15])])) ]), 29) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
def test_derivation_with_transition(): block = fully_cross_block([color, text, color_repeats_factor], [color, text], []) # Color repeats derivation d = Derivation(16, [[0, 4], [1, 5]], color_repeats_factor) backend_request = BackendRequest(23) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(17, Or([And([1, 5]), And([2, 6])])), Iff(19, Or([And([5, 9]), And([6, 10])])), Iff(21, Or([And([9, 13]), And([10, 14])])) ]), 23) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf] # Color does not repeat derivation d = Derivation(17, [[0, 5], [1, 4]], color_repeats_factor) backend_request = BackendRequest(23) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(18, Or([And([1, 6]), And([2, 5])])), Iff(20, Or([And([5, 10]), And([6, 9])])), Iff(22, Or([And([9, 14]), And([10, 13])])) ]), 23) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
def test_derivation(): # Congruent derivation d = Derivation(4, [[0, 2], [1, 3]], con_factor) backend_request = BackendRequest(24) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(5, Or([And([1, 3]), And([2, 4])])), Iff(11, Or([And([7, 9]), And([8, 10])])), Iff(17, Or([And([13, 15]), And([14, 16])])), Iff(23, Or([And([19, 21]), And([20, 22])])) ]), 24) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf] # Incongruent derivation d = Derivation(5, [[0, 3], [1, 2]], con_factor) backend_request = BackendRequest(24) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(6, Or([And([1, 4]), And([2, 3])])), Iff(12, Or([And([7, 10]), And([8, 9])])), Iff(18, Or([And([13, 16]), And([14, 15])])), Iff(24, Or([And([19, 22]), And([20, 21])])) ]), 24) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
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 test_derivation_with_unusual_order(): d = Derivation(0, [[4, 2], [5, 3]], congruency) backend_request = BackendRequest(64) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([ Iff(1, Or([And([5, 3]), And([6, 4])])), Iff(9, Or([And([13, 11]), And([14, 12])])), Iff(17, Or([And([21, 19]), And([22, 20])])), Iff(25, Or([And([29, 27]), And([30, 28])])), Iff(33, Or([And([37, 35]), And([38, 36])])), Iff(41, Or([And([45, 43]), And([46, 44])])), Iff(49, Or([And([53, 51]), And([54, 52])])), Iff(57, Or([And([61, 59]), And([62, 60])])), ]), 64) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
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 test_derivation_with_three_level_transition(): f = Factor("f", ["a", "b", "c"]) f_transition = Factor("transition", [ DerivedLevel("aa", Transition(lambda c: c[0] == "a" and c[1] == "a", [f])), DerivedLevel("ab", Transition(lambda c: c[0] == "a" and c[1] == "b", [f])), DerivedLevel("ac", Transition(lambda c: c[0] == "a" and c[1] == "c", [f])), DerivedLevel("ba", Transition(lambda c: c[0] == "b" and c[1] == "a", [f])), DerivedLevel("bb", Transition(lambda c: c[0] == "b" and c[1] == "b", [f])), DerivedLevel("bc", Transition(lambda c: c[0] == "b" and c[1] == "c", [f])), DerivedLevel("ca", Transition(lambda c: c[0] == "c" and c[1] == "a", [f])), DerivedLevel("cb", Transition(lambda c: c[0] == "c" and c[1] == "b", [f])), DerivedLevel("cc", Transition(lambda c: c[0] == "c" and c[1] == "c", [f])), ]) block = fully_cross_block([f, f_transition], [f], []) # a-a derivation d = Derivation(9, [[0, 3]], f_transition) backend_request = BackendRequest(28) d.apply(block, backend_request) (expected_cnf, expected_fresh) = to_cnf_tseitin( And([Iff(10, Or([And([1, 4])])), Iff(19, Or([And([4, 7])]))]), 28) assert backend_request.fresh == expected_fresh assert backend_request.cnfs == [expected_cnf]
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