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})
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) )
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
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__}." )
def union_from_explanation_allow_contradiction( self, other: ComparableGroup, context: ContextRegister) -> ComparableGroup: result = self + other.new_context(context.reversed()) result = result.drop_implied_factors() return result
def _trigger_addition(self, other: Procedure, context: ContextRegister): """Add two Procedures, given that they have already been found to be addable.""" triggered_procedure = other.new_context(context.reversed()) new_outputs = [*self.outputs, *triggered_procedure.outputs] unique_new_outputs = tuple({key: None for key in new_outputs}) return self.evolve({"outputs": unique_new_outputs})