Esempio n. 1
0
    def wrapper(
        factor: Factor,
        changes: Optional[Union[Sequence[Factor], ContextRegister]],
        context_opinion: Optional[Opinion] = None,
    ) -> Factor:

        if changes is None:
            return factor
        if not isinstance(changes, Iterable):
            changes = (changes, )
        if not isinstance(changes, dict):
            generic_factors = factor.generic_factors
            if len(generic_factors) < len(changes):
                raise ValueError(
                    f"The iterable {changes} is too long to be interpreted " +
                    f"as a list of replacements for the " +
                    f"{len(generic_factors)} items of generic_factors.")
            changes = ContextRegister(dict(zip(generic_factors, changes)))

        expanded_changes = ContextRegister({
            seek_factor_by_name(old, factor, context_opinion):
            seek_factor_by_name(new, factor, context_opinion)
            for old, new in changes.items()
        })
        for old, new in expanded_changes.items():
            if factor.means(old) and factor.name == old.name:
                return new

        return func(factor, expanded_changes)
Esempio n. 2
0
 def _update_context_from_factors(
         self, other: Comparable,
         context: ContextRegister) -> Optional[ContextRegister]:
     incoming = ContextRegister(
         dict(zip(self.generic_factors, other.generic_factors)))
     updated_context = context.merged_with(incoming)
     return updated_context
 def test_import_to_mapping_no_change(self, make_entity):
     old_mapping = ContextRegister(
         {make_entity["motel"]: make_entity["trees"]})
     assert dict(
         old_mapping.merged_with(
             {make_entity["motel"]: make_entity["trees"]})) == {
                  make_entity["motel"]: make_entity["trees"],
              }
 def test_import_to_context_register(self, make_entity, watt_factor):
     f = watt_factor["f7"]
     left = ContextRegister({
         watt_factor["f7"]:
         watt_factor["f7_swap_entities"],
         make_entity["motel"]:
         make_entity["trees"],
     })
     right = ContextRegister({make_entity["trees"]: make_entity["motel"]})
     assert len(left.merged_with(right)) == 3
Esempio n. 5
0
    def explanations_contradiction(
        self, other: Factor, context: ContextRegister = None
    ) -> Iterator[Explanation]:
        r"""
        Find context matches that would result in a contradiction with other.

        Works by testing whether ``self`` would imply ``other`` if
        ``other`` had an opposite value for ``rule_valid``.

        This method takes three main paths depending on
        whether the holdings ``self`` and ``other`` assert that
        rules are decided or undecided.

        A ``decided`` :class:`Rule` can never contradict
        a previous statement that any :class:`Rule` was undecided.

        If rule A implies rule B, then a holding that B is undecided
        contradicts a prior :class:`Rule` deciding that
        rule A is valid or invalid.

        :param other:
            The :class:`.Factor` to be compared to self. Unlike with
            :meth:`~Holding.contradicts`\, this method cannot be called
            with an :class:`.Opinion` for `other`.

        :returns:
            a generator yielding :class:`.ContextRegister`\s that cause a
            contradiction.
        """

        if context is None:
            context = ContextRegister()
        if isinstance(other, Procedure):
            other = Rule(procedure=other)
        if isinstance(other, Rule):
            other = Holding(rule=other)
        if isinstance(other, self.__class__):
            yield from self._explanations_contradiction_of_holding(other, context)
        elif isinstance(other, Factor):
            yield from []  # no possible contradiction
        elif hasattr(other, "explanations_contradiction"):
            if context:
                context = context.reversed()
            yield from other.explanations_contradiction(self, context=context)
        else:
            raise TypeError(
                f"'Contradicts' test not implemented for types "
                f"{self.__class__} and {other.__class__}."
            )
Esempio n. 6
0
    def triggers_next_procedure_if_universal(
        self,
        other: Procedure,
        context: Optional[ContextRegister] = None
    ) -> Iterator[ContextRegister]:
        r"""
        Test if Factors from firing `self` trigger `other` if both are universal.

        The difference from :func:`triggers_next_procedure` is that this
        function doesn't require the "despite" :class:`.Factor`\s to
        be addressed. If both calling :class:`.Rules`\s apply in "ALL"
        cases where their inputs are present, then it doesn't matter
        what Factors they apply "despite".

        :param other:
            another :class:`Procedure` to test to see whether it can
            be triggered by triggering ``self``

        :returns:
            whether the set of :class:`Factor`\s that exist after ``self``
            is fired could trigger ``other``
        """
        context = context or ContextRegister()
        self_output_or_input = FactorGroup((*self.outputs, *self.inputs))
        yield from self_output_or_input.comparison(
            operation=operator.ge,
            still_need_matches=list(other.inputs),
            matches=context,
        )
Esempio n. 7
0
    def explanations_implication(self,
                                 other: Comparable,
                                 context: Optional[ContextRegister] = None
                                 ) -> Iterator[ContextRegister]:
        r"""
        Generate :class:`.ContextRegister`\s that cause `self` to imply `other`.

        If self is `absent`, then generate a ContextRegister from other's point
        of view and then swap the keys and values.
        """
        if context is None:
            context = ContextRegister()
        if not isinstance(other, Factor):
            raise TypeError(
                f"{self.__class__} objects may only be compared for " +
                "implication with other Factor objects or None.")
        if isinstance(other, self.__class__):
            if not self.__dict__.get("absent"):
                if not other.__dict__.get("absent"):
                    yield from self._implies_if_present(other, context)
                else:
                    yield from self._contradicts_if_present(other, context)

            else:
                if other.__dict__.get("absent"):
                    test = other._implies_if_present(self, context.reversed())
                else:
                    test = other._contradicts_if_present(
                        self, context.reversed())
                yield from (register.reversed() for register in test)
Esempio n. 8
0
    def explanations_contradiction(self,
                                   other: Comparable,
                                   context: Optional[ContextRegister] = None
                                   ) -> Iterator[ContextRegister]:
        """
        Test whether ``self`` :meth:`implies` the absence of ``other``.

        This should only be called after confirming that ``other``
        is not ``None``.

        :returns:
            ``True`` if self and other can't both be true at
            the same time. Otherwise returns ``False``.
        """
        if context is None:
            context = ContextRegister()
        if not isinstance(other, Factor):
            raise TypeError(
                f"{self.__class__} objects may only be compared for " +
                "contradiction with other Factor objects or None.")
        if isinstance(other, self.__class__):
            if not self.__dict__.get("absent"):
                if not other.__dict__.get("absent"):
                    yield from self._contradicts_if_present(other, context)
                else:
                    yield from self._implies_if_present(other, context)
            elif self.__dict__.get("absent"):
                if not other.__dict__.get("absent"):
                    test = other._implies_if_present(self, context.reversed())
                else:
                    test = other._contradicts_if_present(
                        self, context.reversed())
                yield from (register.reversed() for register in test)
Esempio n. 9
0
    def implies(
        self, other: Optional[Comparable], context: ContextRegister = None
    ) -> bool:
        r"""
        Test for implication.

        See :meth:`.Procedure.implies_all_to_all`
        and :meth:`.Procedure.implies_all_to_some` for
        explanations of how ``inputs``, ``outputs``,
        and ``despite`` :class:`.Factor`\s affect implication.

        :param other:
            A :class:`Holding` to compare to self, or a :class:`.Rule` to
            convert into such a :class:`Holding` and then compare

        :returns:
            whether ``self`` implies ``other``
        """
        if other is None:
            return True
        if isinstance(other, (Rule, Procedure)):
            other = Holding(rule=other)
        if not isinstance(other, self.__class__):
            if hasattr(other, "implied_by"):
                if context:
                    context = context.reversed()
                return other.implied_by(self, context=context)
            return False
        return any(
            explanation is not None
            for explanation in self.explanations_implication(other, context)
        )
Esempio n. 10
0
    def _union_if_not_exclusive(
        self, other: Holding, context: ContextRegister
    ) -> Optional[Holding]:
        if self.decided is other.decided is False:
            if self.rule.implies(other.rule, context=context):
                return other
            if other.rule.implies(self.rule, context=context.reversed()):
                return self
            return None

        if not self.decided or not other.decided:
            return None
        if self.rule_valid != other.rule_valid:
            return None

        if self.rule_valid is False:
            # If a Rule with input A present is not valid
            # and a Rule with input A absent is also not valid
            # then a version of the Rule with input A
            # omitted is also not valid.
            raise NotImplementedError(
                "The union operation is not yet implemented for Holdings "
                "that assert a Rule is not valid."
            )

        new_rule = self.rule.union(other.rule, context=context)
        if not new_rule:
            return None
        return self.evolve({"rule": new_rule, "exclusive": False})
Esempio n. 11
0
    def ordered_comparison(
        self,
        other: FactorSequence,
        operation: Callable,
        context: Optional[ContextRegister] = None,
    ) -> Iterator[ContextRegister]:
        r"""
        Find ways for a series of pairs of :class:`.Factor`\s to satisfy a comparison.

        :param context:
            keys representing :class:`.Factor`\s in ``self`` and
            values representing :class:`.Factor`\s in ``other``. The
            keys and values have been found in corresponding positions
            in ``self`` and ``other``.

        :yields:
            every way that ``matches`` can be updated to be consistent
            with each element of ``self.need_matches`` having the relationship
            ``self.comparison`` with the item at the corresponding index of
            ``self.available``.
        """
        def update_register(
            register: ContextRegister,
            factor_pairs: List[Tuple[Optional[Comparable],
                                     Optional[Comparable]]],
            i: int = 0,
        ):
            """
            Recursively search through :class:`Factor` pairs trying out context assignments.

            This has the potential to take a long time to fail if the problem is
            unsatisfiable. It will reduce risk to check that every :class:`Factor` pair
            is satisfiable before checking that they're all satisfiable together.
            """
            if i == len(factor_pairs):
                yield register
            else:
                left, right = factor_pairs[i]
                if left is not None or right is None:
                    if left is None:
                        yield from update_register(register,
                                                   factor_pairs=factor_pairs,
                                                   i=i + 1)
                    else:
                        new_mapping_choices: List[ContextRegister] = []
                        for incoming_register in left.update_context_register(
                                right, register, operation):
                            if incoming_register not in new_mapping_choices:
                                new_mapping_choices.append(incoming_register)
                                yield from update_register(
                                    incoming_register,
                                    factor_pairs=factor_pairs,
                                    i=i + 1,
                                )

        if context is None:
            context = ContextRegister()
        ordered_pairs = list(zip_longest(self, other))
        yield from update_register(register=context,
                                   factor_pairs=ordered_pairs)
Esempio n. 12
0
    def contradicts(
        self,
        other: ComparableGroup,
        context: Optional[ContextRegister] = None,
    ) -> bool:
        r"""
        Find whether two sets of :class:`.Factor`\s can be contradictory.

        :param other:
            a second set of :class:`Factor`\s with context factors that
            are internally consistent, but may not be consistent with ``self_factors``.

        :param context:
            correspondences between :class:`Factor`s in self and other
            that can't be changed in seeking a contradiction

        :returns:
            whether any :class:`.Factor` assignment can be found that
            makes a :class:`.Factor` in the output of ``other`` contradict
            a :class:`.Factor` in the output of ``self``.
        """
        if context is None:
            context = ContextRegister()
        for other_factor in other:
            for self_factor in self:
                if self_factor.contradicts(other_factor, context):
                    return True
        return False
Esempio n. 13
0
 def explanations_same_meaning(self,
                               other: Comparable,
                               context: Optional[ContextRegister] = None
                               ) -> Iterator[ContextRegister]:
     """Yield contexts that could cause self to have the same meaning as other."""
     context = context or ContextRegister()
     if isinstance(other, self.__class__):
         yield from self._explanations_same_meaning_as_procedure(
             other, context)
Esempio n. 14
0
 def likely_contexts(self,
                     other: Comparable,
                     context: Optional[ContextRegister] = None
                     ) -> Iterator[ContextRegister]:
     context = context or ContextRegister()
     if isinstance(other, Factor):
         yield from self._likely_contexts_for_factor(other, context)
     else:
         yield from self._likely_contexts_for_factorgroup(other, context)
Esempio n. 15
0
 def _likely_context_from_implication(
         self, other: Comparable,
         context: ContextRegister) -> Optional[ContextRegister]:
     new_context = None
     if self.implies(other, context=context) or other.implies(
             self, context=context.reversed()):
         new_context = self._update_context_from_factors(other, context)
     if new_context and new_context != context:
         return new_context
     return None
Esempio n. 16
0
 def explain_implication_all_to_some(
     self,
     other: Factor,
     context: Optional[ContextRegister] = None
 ) -> Iterator[ContextRegister]:
     """Yield contexts establishing that if self is always valid, other is sometimes valid."""
     context = context or ContextRegister()
     if isinstance(other, self.__class__):
         yield from self._explain_implication_of_procedure_all_to_some(
             other=other, context=context)
Esempio n. 17
0
 def union(
     self, other: Union[Rule, Holding], context: Optional[ContextRegister] = None
 ) -> Optional[Holding]:
     """Infer a Holding from all inputs and outputs of self and other, in context."""
     context = context or ContextRegister()
     if isinstance(other, Rule):
         other = Holding(rule=other)
     if not isinstance(other, Holding):
         raise TypeError
     return self._union_with_holding(other, context=context)
Esempio n. 18
0
    def comparison(
        self,
        operation: Callable,
        still_need_matches: Sequence[Factor],
        matches: ContextRegister = None,
    ) -> Iterator[ContextRegister]:
        r"""
        Find ways for two unordered sets of :class:`.Factor`\s to satisfy a comparison.

        All of the elements of `other` need to fit the comparison. The elements of
        `self` don't all need to be used.

        :param context:
            a mapping of :class:`.Factor`\s that have already been matched
            to each other in the recursive search for a complete group of
            matches. Usually starts empty when the method is first called.

        :param still_need_matches:
            :class:`.Factor`\s that need to satisfy the comparison
            :attr:`comparison` with some :class:`.Factor` of :attr:`available`
            for the relation to hold, and have not yet been matched.

        :param matches:
            a :class:`.ContextRegister` matching generic :class:`.Factor`\s

        :yields:
            context registers showing how each :class:`.Factor` in
            ``need_matches`` can have the relation ``comparison``
            with some :class:`.Factor` in ``available_for_matching``,
            with matching context.
        """
        still_need_matches = list(still_need_matches)

        if matches is None:
            matches = ContextRegister()

        if not still_need_matches:
            yield matches
        else:
            other_factor = still_need_matches.pop()
            for self_factor in self:
                if operation(self_factor, other_factor):
                    updated_mappings = iter(
                        self_factor.update_context_register(
                            other=other_factor,
                            register=matches,
                            comparison=operation))
                    for new_matches in updated_mappings:
                        if new_matches is not None:
                            yield from iter(
                                self.comparison(
                                    still_need_matches=still_need_matches,
                                    operation=operation,
                                    matches=new_matches,
                                ))
 def test_registers_for_interchangeable_context(self, make_entity,
                                                watt_factor):
     """
     Test that _registers_for_interchangeable_context swaps the first two
     items in the ContextRegister
     """
     matches = ContextRegister({
         make_entity["motel"]: make_entity["trees"],
         make_entity["trees"]: make_entity["motel"],
         make_entity["watt"]: make_entity["watt"],
     })
     new_matches = [
         match for match in
         watt_factor["f7"]._registers_for_interchangeable_context(matches)
     ]
     assert (ContextRegister({
         make_entity["trees"]: make_entity["trees"],
         make_entity["motel"]: make_entity["motel"],
         make_entity["watt"]: make_entity["watt"],
     }) in new_matches)
Esempio n. 20
0
 def explanations_same_meaning(self,
                               other: Comparable,
                               context: Optional[ContextRegister] = None
                               ) -> Iterator[ContextRegister]:
     """Generate ways to match contexts of self and other so they mean the same."""
     if (isinstance(other, Factor) and self.__class__ == other.__class__
             and self.absent == other.absent
             and self.generic == other.generic):
         if self.generic:
             yield ContextRegister({self: other})
         yield from self._means_if_concrete(other, context)
Esempio n. 21
0
    def _implies_if_present(self,
                            other: Factor,
                            context: Optional[ContextRegister] = None
                            ) -> Iterator[ContextRegister]:
        """
        Find if ``self`` would imply ``other`` if they aren't absent.

        :returns:
            bool indicating whether ``self`` would imply ``other``,
            under the assumption that neither self nor other has
            the attribute ``absent == True``.
        """
        if context is None:
            context = ContextRegister()
        if isinstance(other, self.__class__):
            if other.generic:
                if context.get(self) is None or (context.get(self) == other):
                    yield ContextRegister({self: other})
            if not self.generic:
                yield from self._implies_if_concrete(other, context)
 def test_likely_context_from_factor_meaning(self,
                                             make_opinion_with_holding):
     lotus = make_opinion_with_holding["lotus_majority"]
     oracle = make_opinion_with_holding["oracle_majority"]
     left = lotus.holdings[2].outputs[0]
     right = oracle.holdings[2].outputs[0]
     likely = left._likely_context_from_meaning(right,
                                                context=ContextRegister())
     lotus_menu = lotus.holdings[2].generic_factors[0]
     java_api = oracle.generic_factors[0]
     assert likely[lotus_menu] == java_api
Esempio n. 23
0
 def explanations_shares_all_factors_with(
     self,
     other: ComparableGroup,
     context: Optional[ContextRegister] = None
 ) -> Iterator[ContextRegister]:
     context = context or ContextRegister()
     context_for_other = context.reversed()
     yield from (context.reversed() for context in other.comparison(
         operation=means,
         still_need_matches=list(self),
         matches=context_for_other,
     ))
Esempio n. 24
0
 def explanations_implication(
         self,
         other: ComparableGroup,
         context: Optional[ContextRegister] = None
 ) -> Iterator[Explanation]:
     explanation = Explanation(matches=[],
                               context=context or ContextRegister())
     yield from self.verbose_comparison(
         operation=operator.ge,
         still_need_matches=list(other),
         explanation=explanation,
     )
Esempio n. 25
0
 def _contradicts_if_not_exclusive(
     self, other: Holding, context: ContextRegister = None
 ) -> Iterator[ContextRegister]:
     if context is None:
         context = ContextRegister()
     if isinstance(other, Holding) and other.decided:
         if self.decided:
             yield from self._explanations_implies_if_not_exclusive(
                 other.negated(), context=context
             )
         else:
             yield from chain(
                 other._implies_if_decided(self),
                 other._implies_if_decided(self.negated()),
             )
Esempio n. 26
0
 def likely_contexts(self,
                     other: Comparable,
                     context: Optional[ContextRegister] = None
                     ) -> Iterator[ContextRegister]:
     context = context or ContextRegister()
     same_meaning = self._likely_context_from_meaning(other, context)
     if same_meaning:
         implied = self._likely_context_from_implication(
             other, same_meaning)
     else:
         implied = self._likely_context_from_implication(other, context)
     if implied:
         yield implied
     if same_meaning:
         yield same_meaning
     yield context
Esempio n. 27
0
    def internally_consistent(self,
                              context: Optional[ContextRegister] = None
                              ) -> bool:
        """
        Check for contradictions among the Factors in self.

        :returns: bool indicating whether self is internally consistent
        """
        context = context or ContextRegister()
        unchecked = list(self)
        while unchecked:
            current = unchecked.pop()
            for item in unchecked:
                if not current.consistent_with(item, context):
                    return False
        return True
Esempio n. 28
0
    def consistent_with(
        self,
        other: ComparableGroup,
        context: Optional[ContextRegister] = None,
    ) -> bool:
        r"""
        Find whether two sets of :class:`.Factor`\s can be consistent.

        Works by first determining whether one :class:`.Factor`
        potentially :meth:`~.Factor.contradicts` another,
        and then determining whether it's possible to make
        context assignments match between the contradictory
        :class:`.Factor`\s.

        .. Note::
            Does ``Factor: None`` in matches always mean that
            the :class:`.Factor` can avoid being matched in a
            contradictory way?

        :param context:
            correspondences between :class:`Factor`s in self and other
            that can't be changed in seeking a way to interpret the groups
            as consistent

        :returns:
            whether unassigned context factors can be assigned in such
            a way that there's no contradiction between any factor in
            ``self_factors`` and ``other_factors``, given that some
            :class:`.Factor`\s have already been assigned as
            described by ``matches``.
        """
        if context is None:
            context = ContextRegister()
        for self_factor in self:
            for other_factor in other:
                if self_factor.contradicts(other_factor):
                    if all(
                            all(
                                context.get(key) == context_register[key]
                                or context.get(context_register[key]) == key
                                for key in self_factor.generic_factors)
                            for context_register in self_factor.
                            _context_registers(other_factor, means)):
                        return False
        return True
Esempio n. 29
0
    def explanations_consistent_with_factor(
        self,
        other: Factor,
        context: Optional[ContextRegister] = None
    ) -> Iterator[ContextRegister]:
        """
        Test whether ``self`` does not contradict ``other``.

        This should only be called after confirming that ``other``
        is not ``None``.

        :returns:
            ``True`` if self and other can't both be true at
            the same time. Otherwise returns ``False``.
        """
        if context is None:
            context = ContextRegister()
        for possible in self.possible_contexts(other, context):
            if not self.contradicts(other, context=possible):
                yield possible
Esempio n. 30
0
    def _context_registers(
        self,
        other: Optional[ComparableGroup],
        comparison: Callable,
        context: Optional[ContextRegister] = None,
    ) -> Iterator[ContextRegister]:
        r"""
        Search for ways to match :attr:`context_factors` of ``self`` and ``other``.

        :yields:
            all valid ways to make matches between
            corresponding :class:`Factor`\s.
        """
        if context is None:
            context = ContextRegister()
        if other is None:
            yield context
        else:
            yield from self.comparison(operation=comparison,
                                       still_need_matches=list(other),
                                       matches=context)