示例#1
0
    def __count_exclusions(self, num):
        from sweetpea.constraints import Exclude

        excluded_crossings = set()
        excluded_external_names = set()

        # Get the exclude constraints.
        exclusions = list(
            filter(lambda c: isinstance(c, Exclude), self.constraints))
        if not exclusions:
            return 0

        # If there are any, generate the full crossing as a list of tuples.
        levels_lists = [list(f.levels) for f in self.crossing[0]]
        all_crossings = list(product(*levels_lists))

        for constraint in exclusions:
            if constraint.factor.has_complex_window():
                # If the excluded factor has a complex window, then we don't need
                # to reduce the sequence length. What if the transition being excluded
                # is in the crossing? If it is, then they shouldn't be excluding it.
                # We should give an error if we detect that.
                continue

            # Retrieve the derivation function that defines this exclusion.
            excluded_level = constraint.level

            if type(excluded_level) is SimpleLevel:
                for c in all_crossings:
                    if excluded_level in c:
                        excluded_crossings.add(
                            get_internal_level_name(c[0]) + ", " +
                            get_internal_level_name(c[1]))
            else:
                # For each crossing, ensure that atleast one combination is possible with the disgn-only factors keeping in mind the exclude contraints.
                for c in all_crossings:
                    if all(
                            list(
                                map(
                                    lambda d: self.__excluded_derived(
                                        excluded_level, c + d),
                                    list(
                                        product(*[
                                            list(f.levels) for f in filter(
                                                lambda f: f not in self.
                                                crossing[0], self.design)
                                        ]))))):
                        excluded_crossings.add(
                            get_internal_level_name(c[0]) + ", " +
                            get_internal_level_name(c[1]))
                        excluded_external_names.add(
                            get_external_level_name(c[0]) + ", " +
                            get_external_level_name(c[1]))
        if self.require_complete_crossing and len(excluded_crossings) != 0:
            er = "Complete crossing is not possible beacuse the following combinations have been excluded:"
            for names in excluded_external_names:
                er += "\n" + names
            self.errors.add(er)
        return len(excluded_crossings)
示例#2
0
    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
示例#3
0
def print_experiments(block: Block, experiments: List[dict]):
    """Displays the generated experiments in a human-friendly form.

    :param block:
        An experimental description as a :class:`.Block`.

    :param experiments:
        A list of experiments as :class:`dicts <dict>`. These are produced by
        calls to any of the synthesis functions (:func:`.synthesize_trials`,
        :func:`.synthesize_trials_non_uniform`, or
        :func:`.synthesize_trials_uniform`).
    """
    nested_assignment_strs = [
        list(
            map(lambda l: f.factor_name + " " + get_external_level_name(l),
                f.levels)) for f in block.design
    ]
    column_widths = list(
        map(lambda l: max(list(map(len, l))), nested_assignment_strs))

    format_str = reduce(lambda a, b: a + '{{:<{}}} | '.format(b),
                        column_widths, '')[:-3] + '\n'

    print('{} trial sequences found.'.format(len(experiments)))
    for idx, e in enumerate(experiments):
        print('Experiment {}:'.format(idx))
        strs = [
            list(map(lambda v: name + " " + v, values))
            for (name, values) in e.items()
        ]
        transposed = list(map(list, zip(*strs)))
        print(reduce(lambda a, b: a + format_str.format(*b), transposed, ''))
    def __count_solutions(self):
        self._segment_lengths = []
        ##############################################################
        # Permutations of crossing instances
        n = self._block.crossing_size()
        n_factorial = factorial(n)
        self._segment_lengths.append(n_factorial)

        ##############################################################
        # Uncrossed Dependent Factors
        level_combinations = self.__generate_source_combinations()

        # Keep only allowed combos for each permutation
        for ci in self._crossing_instances:
            sc_indices = list(range(len(self._source_combinations)))
            for sc_idx, sc in enumerate(self._source_combinations):
                # Apply the derivation fn for each DF in the crossing for this instance and make sure it returns
                # true for this level combination. If it doesn't, then remove this combination.
                merged_levels = {**ci, **sc}
                for df in self._partitions.get_crossed_factors_derived():
                    w = merged_levels[df].window
                    if not w.fn(*[get_external_level_name(merged_levels[f]) for f in w.args]):
                        sc_indices.remove(sc_idx)

            self._segment_lengths.append(len(sc_indices))
            self._valid_source_combinations_indices.append(sc_indices)

        ##############################################################
        # Uncrossed Independent Factors
        u_b_i_counts = []
        u_b_i = self._partitions.get_uncrossed_basic_independent_factors()
        for f in u_b_i:
            self._segment_lengths.append(pow(len(f.levels), n))

        return reduce(op.mul, self._segment_lengths, 1)
示例#5
0
 def extract_simplelevel(self, block: Block, level: DerivedLevel) -> List[Dict[Factor, SimpleLevel]]:
     excluded_levels = []
     excluded = list(filter(lambda c: level.window.fn(*list(map(lambda f: get_external_level_name(f[1]), c))), level.get_dependent_cross_product()))
     for i in excluded:
         combos = cast(List[Dict[Factor, SimpleLevel]], [dict()])
         for j in i:
             if type(j[1]) is DerivedLevel:
                 result = self.extract_simplelevel(block, j[1])
                 newcombos = []
                 valid = True
                 for r in result:
                     for c in combos:
                         for f in c:
                             if f in r:
                                 if c[f] != r[f]:
                                     valid = False
                     if valid:
                         newcombos.append({**r, **c})
                 combos = newcombos
             else:
                 for c in combos:
                     if block.factor_in_crossing(j[0]) and block.require_complete_crossing:
                         block.errors.add("WARNING: Some combinations have been excluded, this crossing may not be complete!")
                     c[j[0]] = j[1] 
         excluded_levels.extend(combos)
     return excluded_levels
示例#6
0
 def generate_argument_list(level: DerivedLevel, tup: Tuple) -> List:
     # User-supplied string level names are the arguments for the user-supplied derivation functions
     level_strings = list(map(lambda t: get_external_level_name(t[1]), tup))
     # For windows with a width of 1, we just pass the arguments directly, rather than putting them in lists.
     if level.window.width == 1:
         return level_strings
     else:
         return list(chunk_list(level_strings, level.window.width))
    def generate_sample(self, sequence_number: int) -> dict:
        trial_values = self.generate_trail_values(sequence_number)

        experiment = cast(dict, {})
        for trial_number, trial_value in enumerate(trial_values):
            for factor, level in trial_value.items():
                if factor.factor_name not in experiment:
                    experiment[factor.factor_name] = []
                experiment[factor.factor_name].append(get_external_level_name(level))
        return experiment
示例#8
0
def __assert_atmostkinarow_factor(k: int, f: factor,
                                  experiments: List[dict]) -> None:
    factor_name = f.factor_name
    for level in f.levels:
        level_name = get_external_level_name(level)
        sublist = list(repeat(level_name, k + 1))
        for e in experiments:
            assert sublist not in [
                e[factor_name][i:i + k + 1]
                for i in range(len(e[factor_name]) - (k + 1))
            ]
示例#9
0
def get_all_external_level_names(
        design: List[Factor]) -> List[Tuple[str, str]]:
    """Usage

    ::

        >>> color = Factor("color", ["red", "blue"])
        >>> text  = Factor("text",  ["red", "blue"])
        >>> get_all_internal_level_names([color, text])
        [('color', 'red'), ('color', 'blue'), ('text', 'red'), ('text', 'blue')]
    """
    return [(factor.factor_name, get_external_level_name(level))
            for factor in design for level in factor.levels]
示例#10
0
    def __excluded_derived(self, excluded_level, c):
        """Given the complete crossing and an exclude constraint returns true
        if that combination results in the exclude level.
        """
        ret = []

        for f in filter(lambda f: f.is_derived(), excluded_level.window.args):
            ret.append(self.__excluded_derived(list(filter(lambda l: l.factor == f, c))[0], c))

        # Invoking the fn this way is only ok because we only do this for WithinTrial windows.
        # With complex windows, it wouldn't work due to the list aspect for each argument.
        ret.append(excluded_level.window.fn(*list(map(lambda l: get_external_level_name(l), filter(lambda l: l.factor in excluded_level.window.args, c)))))

        return all(ret)
    def generate_trail_values(self, sequence_number: int) -> List[dict]:
        # 1. Extract the component pieces (permutation, each combination setting, etc)
        #    The 0th component is always the permutation index.
        #    The 1st-nth components are always the source combination indices for each trial in the sequence
        #    Any following components are the combination indices for independent basic factors.
        components = extract_components(self._segment_lengths, sequence_number)

        # 2. Generate the inversion sequence for the selected permutation number.
        #    Use the inversion sequence to construct the permutation.
        l = self._block.crossing_size()
        inversion_sequence = compute_jth_inversion_sequence(l, components[0])
        permutation_indices = construct_permutation(inversion_sequence)
        permutation = list(map(lambda i: self._crossing_instances[i], permutation_indices))

        # 3. Generate the source combinations for the selected sequence.
        source_combinations = cast(List[dict], [])
        for i, p in enumerate(permutation_indices):
            component_for_p = components[p + 1]
            source_combination_index_for_component = self._valid_source_combinations_indices[p][component_for_p]
            source_combinations.append(self._source_combinations[source_combination_index_for_component])

        # 4. Generate the combinations for independent basic factors
        independent_factor_combinations = cast(List[dict], [{}] *l)
        u_b_i = self._partitions.get_uncrossed_basic_independent_factors()
        if u_b_i:
            independent_combination_idx = components[l+1]
            for f in u_b_i:
                combo = compute_jth_combination(l, len(f.levels), independent_combination_idx)
                for i in range(l):
                    if not independent_factor_combinations[i]:
                        independent_factor_combinations[i] = {f : f.levels[combo[i]]}
                        continue
                    independent_factor_combinations[i][f] = f.levels[combo[i]]

        # 5. Merge the selected levels gathered so far to facilitate computing the uncrossed derived factor levels.
        trial_values = cast(List[dict], [{}] * l)
        for t in range(l):
            trial_values[t] = {**permutation[t], **source_combinations[t], **independent_factor_combinations[t]}

        # 6. Generate uncrossed derived level values
        u_d = self._partitions.get_uncrossed_derived_factors()
        for f in u_d:
            for t in range(l):
                # For each level in the factor, see if the derivation function is true.
                for level in f.levels:
                    if level.window.fn(*[get_external_level_name(trial_values[t][f]) for f in level.window.args]):
                        trial_values[t][f] = level

        return trial_values
def test_generate_crossing_instances():
    enumerator = UCSolutionEnumerator(block)
    crossing_instances = enumerator._UCSolutionEnumerator__generate_crossing_instances()
    simplified_names = []
    for d in crossing_instances:
        d_simple = {}
        for (f, l) in d.items():
            d_simple[f.factor_name] = get_external_level_name(l)
        simplified_names.append(d_simple)

    assert simplified_names == [
        {'color': 'red', 'text': 'red'},
        {'color': 'red', 'text': 'blue'},
        {'color': 'blue', 'text': 'red'},
        {'color': 'blue', 'text': 'blue'}
    ]
def test_generate_source_combinations():
    block = fully_cross_block(design, [congruency], [])
    enumerator = UCSolutionEnumerator(block)
    crossing_source_combos = enumerator._UCSolutionEnumerator__generate_source_combinations()
    simplified_names = []
    for d in crossing_source_combos:
        d_simple = {}
        for (f, l) in d.items():
            d_simple[f.factor_name] = get_external_level_name(l)
        simplified_names.append(d_simple)

    assert simplified_names == [
        {'color': 'red', 'text': 'red'},
        {'color': 'red', 'text': 'blue'},
        {'color': 'blue', 'text': 'red'},
        {'color': 'blue', 'text': 'blue'}
    ]
示例#14
0
def tabulate_experiments(experiments: List[Dict],
                         factors: Optional[List[Factor]] = None,
                         trials: Optional[List[int]] = None):
    """Tabulates and prints the given experiments in a human-friendly form.
    Outputs a table that shows the absolute and relative frequencies of
    combinations of factor levels.

    :param experiments:
        A list of experiments as :class:`dicts <dict>`. These are produced by
        calls to any of the synthesis functions (:func:`.synthesize_trials`,
        :func:`.synthesize_trials_non_uniform`, or
        :func:`.synthesize_trials_uniform`).

    :param factors:
        An optional :class:`list` of :class:`Factors <.Factor>`...

        .. todo::

            Finish specification of this parameter.

    :param trials:
        An optional :class:`list` of :class:`ints <int>`...

        .. todo::

            Finish specification of this parameter.
    """
    if factors is None:
        factors = []

    for exp_idx, e in enumerate(experiments):
        tabulation: Dict[str, List[str]] = dict()
        frequency_list = list()
        proportion_list = list()
        levels: List[List[str]] = list()

        if trials is None:
            trials = list(range(0, len(e[list(e.keys())[0]])))

        num_trials = len(trials)

        # initialize table
        for f in factors:
            tabulation[f.factor_name] = list()
            factor_levels: List[str] = list()
            for l in f.levels:
                factor_levels.append(l.external_name)
            levels.append(factor_levels)

        max_combinations = 0
        # Each `element` is an n-tuple (s1, s2, ..., sn) where n is the number
        # of levels and each element is a level name.
        for element in itertools.product(*levels):
            max_combinations += 1

            # add factor combination
            for idx, factor_name in enumerate(tabulation.keys()):
                tabulation[factor_name].append(element[idx])

            # compute frequency
            frequency = 0
            for trial in trials:
                valid_condition = True
                for idx, factor in enumerate(tabulation.keys()):
                    if e[factor][trial] != element[idx]:
                        valid_condition = False
                        break
                if valid_condition:
                    frequency += 1

            proportion = frequency / num_trials

            frequency_list.append(str(frequency))
            proportion_list.append(str(proportion * 100) + '%')

        tabulation["frequency"] = frequency_list
        tabulation["proportion"] = proportion_list

        frequency_factor = Factor("frequency", list(set(frequency_list)))
        proportion_factor = Factor("proportion", list(set(proportion_list)))

        design = list()
        for f in factors:
            design.append(f)
        design.append(frequency_factor)
        design.append(proportion_factor)

        # print tabulation
        nested_assignment_strs = [
            list(
                map(lambda l: f.factor_name + " " + get_external_level_name(l),
                    f.levels)) for f in design
        ]
        column_widths = list(
            map(lambda l: max(list(map(len, l))), nested_assignment_strs))

        format_str = reduce(lambda a, b: a + '{{:<{}}} | '.format(b),
                            column_widths, '')[:-3] + '\n'

        print('Experiment {}:'.format(exp_idx))
        strs = [
            list(map(lambda v: name + " " + v, values))
            for (name, values) in tabulation.items()
        ]
        transposed = list(map(list, zip(*strs)))
        print(reduce(lambda a, b: a + format_str.format(*b), transposed, ''))
示例#15
0
def get_all_external_level_names(
        design: List[Factor]) -> List[Tuple[str, str]]:
    return [(factor.factor_name, get_external_level_name(level))
            for factor in design for level in factor.levels]