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
Beispiel #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
Beispiel #3
0
    def decode(block: Block, solution: List[int]) -> dict:
        # Sort the list and remove any negative (false) variables
        solution.sort()
        solution = list(filter(lambda v: v > 0, solution))

        # Separate into simple/complex variables.
        simple_variables = list(
            filter(lambda v: v <= block.grid_variables(), solution))
        complex_variables = list(
            filter(lambda v: v > block.grid_variables(), solution))

        experiment = cast(dict, {})

        # Simple factors
        tuples = list(map(lambda v: block.decode_variable(v),
                          simple_variables))
        string_tuples = list(
            map(lambda t: (t[0].factor_name, t[1].external_name), tuples))
        for (factor_name, level_name) in string_tuples:
            if factor_name not in experiment:
                experiment[factor_name] = []
            experiment[factor_name].append(level_name)

        # Complex factors - The challenge here is knowing when to insert '', rather than using the variables.
        # Start after 'width' trials, and shift 'stride' trials for each variable.
        complex_factors = list(
            filter(lambda f: f.has_complex_window, block.design))
        for f in complex_factors:
            # Get variables for this factor
            start = block.first_variable_for_level(f, f.levels[0]) + 1
            end = start + block.variables_for_factor(f)
            variables = list(
                filter(lambda n: n in range(start, end), complex_variables))

            # Get the level names for the variables in the solution.
            level_tuples = list(
                map(lambda v: block.decode_variable(v), variables))
            level_names = list(
                map(lambda t: (t[1].external_name), level_tuples))

            # Intersperse empty strings for the trials to which this factor does not apply.
            #level_names = list(intersperse('', level_names, f.levels[0].window.stride - 1))
            #level_names = list(repeat('', f.levels[0].window.width - 1)) + level_names
            level_names_fill = []
            for n in range(block.trials_per_sample()):
                level_names_fill.append(
                    level_names.pop(0) if f.applies_to_trial(n + 1) else '')
            experiment[f.factor_name] = level_names_fill

        return experiment
Beispiel #4
0
def __decode(block: Block, solution: List[int]) -> dict:
    gt0 = lambda n: n > 0
    simple_variables = list(filter(gt0, solution[:block.grid_variables()]))
    complex_variables = list(
        filter(gt0,
               solution[block.grid_variables():block.variables_per_sample()]))

    experiment = cast(dict, {})

    # Simple factors
    tuples = list(map(lambda v: block.decode_variable(v), simple_variables))
    for (factor_name, level_name) in tuples:
        if factor_name not in experiment:
            experiment[factor_name] = []
        experiment[factor_name].append(level_name)

    # Complex factors - The challenge here is knowing when to insert '', rather than using the variables.
    # Start after 'width' trials, and shift 'stride' trials for each variable.
    complex_factors = list(
        filter(lambda f: f.has_complex_window(), block.design))
    for f in complex_factors:
        # Get variables for this factor
        start = block.first_variable_for_level(f.name, f.levels[0].name) + 1
        end = start + block.variables_for_factor(f)
        variables = list(
            filter(lambda n: n in range(start, end), complex_variables))

        # Get the level names for the variables in the solution.
        level_names = list(
            map(lambda v: block.decode_variable(v)[1], variables))

        # Intersperse empty strings for the trials to which this factor does not apply.
        level_names = list(
            intersperse('', level_names, f.levels[0].window.stride - 1))
        level_names = list(repeat('',
                                  f.levels[0].window.width - 1)) + level_names

        experiment[f.name] = level_names

    return experiment
    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
Beispiel #6
0
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