def test_consistency_with_transition_first_and_uneven_level_lengths(): color3 = Factor("color3", ["red", "blue", "green"]) yes_fn = lambda colors: colors[0] == colors[1] == colors[2] no_fn = lambda colors: not yes_fn(colors) color3_repeats_factor = Factor("color3 repeats?", [ DerivedLevel("yes", Window(yes_fn, [color3], 3, 1)), DerivedLevel("no", Window(no_fn, [color3], 3, 1)) ]) block = fully_cross_block([color3_repeats_factor, color3, text], [color3, text], []) backend_request = BackendRequest(0) Consistency.apply(block, backend_request) assert backend_request.ll_requests == [ LowLevelRequest("EQ", 1, [1, 2, 3]), LowLevelRequest("EQ", 1, [4, 5]), LowLevelRequest("EQ", 1, [6, 7, 8]), LowLevelRequest("EQ", 1, [9, 10]), LowLevelRequest("EQ", 1, [11, 12, 13]), LowLevelRequest("EQ", 1, [14, 15]), LowLevelRequest("EQ", 1, [16, 17, 18]), LowLevelRequest("EQ", 1, [19, 20]), LowLevelRequest("EQ", 1, [21, 22, 23]), LowLevelRequest("EQ", 1, [24, 25]), LowLevelRequest("EQ", 1, [26, 27, 28]), LowLevelRequest("EQ", 1, [29, 30]), LowLevelRequest("EQ", 1, [31, 32]), LowLevelRequest("EQ", 1, [33, 34]), LowLevelRequest("EQ", 1, [35, 36]), LowLevelRequest("EQ", 1, [37, 38]) ]
def test_consistency_with_multiple_transitions(design): block = fully_cross_block(design, [color, text], []) backend_request = BackendRequest(0) Consistency.apply(block, backend_request) assert backend_request.ll_requests == \ list(map(lambda x: LowLevelRequest("EQ", 1, [x, x+1]), range(1, 28, 2)))
def test_consistency_with_transition(design): block = fully_cross_block(design, [color, text], []) backend_request = BackendRequest(0) Consistency.apply(block, backend_request) # Because the color_repeats_factor doesn't apply to the first trial, (there isn't a previous trial # to compare to) the variables only go up to 22. assert backend_request.ll_requests == \ list(map(lambda x: LowLevelRequest("EQ", 1, [x, x+1]), range(1, 22, 2)))
def fully_cross_block(design: List[Factor], crossing: List[Factor], constraints: List[Constraint], require_complete_crossing=True, cnf_fn=to_cnf_tseitin) -> Block: """Returns a fully crossed :class:`.Block` meant to be used in experiment synthesis. This is the preferred mechanism for describing an experiment. :param design: A :class:`list` of all the :class:`Factors <.Factor>` in the design. When a sequence of trials is generated, each trial will have one level from each factor in ``design``. :param crossing: A :class:`list` of :class:`Factors <.Factor>` used to produce crossings. The number of trials in each run of the experiment is determined as the product of the number of levels of factors in ``crossing``. If ``require_complete_crossing`` is ``False``, the ``constraints`` can reduce the total number of trials. Different trial sequences of the experiment will have different combinations of levels in different orders. The factors in ``crossing`` supply an implicit constraint that every combination of levels in the cross should appear once. Derived factors impose additional constraints: only combinations of levels that are consistent with derivations can appear as a trial. Additional constraints can be manually imposed via the ``constraints`` parameter. :param constraints: A :class:`list` of :class:`Constraints <.Constraint>` that restrict the generated trials. :param require_complete_crossing: Whether every combination in ``crossing`` must appear in a block of trials. ``True`` by default. A ``False`` value is appropriate if combinations are excluded through an :class:`.Exclude` :class:`.Constraint`. :param cnf_fn: A CNF conversion function. Default is :func:`.to_cnf_tseitin`. """ all_constraints = cast(List[Constraint], [FullyCross(), Consistency()]) + constraints all_constraints = __desugar_constraints( all_constraints) #expand the constraints into a form we can process. block = FullyCrossBlock(design, [crossing], all_constraints, require_complete_crossing, cnf_fn) block.constraints += DerivationProcessor.generate_derivations(block) if not constraints and not list(filter( lambda f: f.is_derived(), crossing)) and not list( filter(lambda f: f.has_complex_window, design)): block.complex_factors_or_constraints = False return block
def test_consistency_with_general_window(): design = [color, text, congruent_bookend] crossing = [color, text] block = fully_cross_block(design, crossing, []) backend_request = BackendRequest(0) Consistency.apply(block, backend_request) assert backend_request.ll_requests == [ LowLevelRequest("EQ", 1, [1, 2]), LowLevelRequest("EQ", 1, [3, 4]), LowLevelRequest("EQ", 1, [5, 6]), LowLevelRequest("EQ", 1, [7, 8]), LowLevelRequest("EQ", 1, [9, 10]), LowLevelRequest("EQ", 1, [11, 12]), LowLevelRequest("EQ", 1, [13, 14]), LowLevelRequest("EQ", 1, [15, 16]), LowLevelRequest("EQ", 1, [17, 18]), LowLevelRequest("EQ", 1, [19, 20]) ]
def test_consistency(): # From standard example # [ LowLevelRequest("EQ", 1, [1, 2]), LowLevelRequest("EQ", 1, [3, 4]), ...] backend_request = BackendRequest(0) Consistency.apply(block, backend_request) assert backend_request.ll_requests == \ list(map(lambda x: LowLevelRequest("EQ", 1, [x, x+1]), range(1, 24, 2))) # Different case backend_request = BackendRequest(0) f = Factor("a", ["b", "c", "d", "e"]) f_block = fully_cross_block([f], [f], []) Consistency.apply(f_block, backend_request) assert backend_request.ll_requests == \ list(map(lambda x: LowLevelRequest("EQ", 1, [x, x+1, x+2, x+3]), range(1, 16, 4))) # Varied level lengths backend_request = BackendRequest(0) f1 = Factor("a", ["b", "c", "d"]) f2 = Factor("e", ["f"]) f_block = fully_cross_block([f1, f2], [f1, f2], []) Consistency.apply(f_block, backend_request) assert backend_request.ll_requests == [ LowLevelRequest("EQ", 1, [1, 2, 3]), LowLevelRequest("EQ", 1, [4]), LowLevelRequest("EQ", 1, [5, 6, 7]), LowLevelRequest("EQ", 1, [8]), LowLevelRequest("EQ", 1, [9, 10, 11]), LowLevelRequest("EQ", 1, [12]) ]
def multiple_cross_block(design: List[Factor], crossings: List[List[Factor]], constraints: List[Constraint], require_complete_crossing=True, cnf_fn=to_cnf_tseitin) -> Block: """Returns a :class:`.Block` with multiple crossings, meant to be used in experiment synthesis. Similar to :func:`fully_cross_block`, except it can be configured with multiple crossings. :param design: A :class:`list` of all the :class:`Factors <.Factor>` in the design. When a sequence of trials is generated, each trial will have one level from each factor in ``design``. :param crossings: A :class:`list` of :class:`lists <list>` of :class:`Factors <.Factor>` representing crossings. The number of trials in each run of the experiment is determined by the *maximum* product among the number of levels in the crossings. Every combination of levels in each individual crossing in ``crossings`` appears at least once. Different crossings can refer to the same factors, which constrains how factor levels are chosen across crossings. :param constraints: A :class:`list` of :class:`Constraints <.Constraint>` that restrict the generated trials. :param require_complete_crossing: Whether every combination in ``crossings`` must appear in a block of trials. ``True`` by default. A ``False`` value is appropriate if combinations are excluded through an :class:`.Exclude` :class:`.Constraint.` :param cnf_fn: A CNF conversion function. Default is :func:`.to_cnf_tseitin`. """ all_constraints = cast(List[Constraint], [MultipleCross(), Consistency()]) + constraints all_constraints = __desugar_constraints( all_constraints) #expand the constraints into a form we can process. block = MultipleCrossBlock(design, crossings, all_constraints, require_complete_crossing, cnf_fn) block.constraints += DerivationProcessor.generate_derivations(block) return block