def test_expand_formula_derived_formulas(self): fol = self.fol john = ConstantTerm.fromString("john") x = Variable.fromString("x") right_equal = Equal(x, john) true_ = TrueFormula() false_ = FalseFormula() or_ = Or(true_, Not(right_equal)) implies_ = Implies(or_, false_) equivalence_ = Equivalence(implies_, false_) forall_ = ForAll(x, equivalence_) expanded_true = Equal(DUMMY_TERM, DUMMY_TERM) expanded_false = Not(Equal(DUMMY_TERM, DUMMY_TERM)) expanded_or_ = Not(And(Not(expanded_true), Not(Not(right_equal)))) expanded_implies_ = Not(And(expanded_or_, Not(expanded_false))) positive_equivalence = And(expanded_implies_, expanded_false) negative_equivalence = And(Not(expanded_implies_), Not(expanded_false)) expanded_equivalence_ = Not( And(Not(positive_equivalence), Not(negative_equivalence))) self.assertEqual(fol.expand_formula(true_), expanded_true) self.assertEqual(fol.expand_formula(false_), expanded_false) self.assertEqual(fol.expand_formula(or_), expanded_or_) self.assertEqual(fol.expand_formula(implies_), expanded_implies_) self.assertEqual(fol.expand_formula(equivalence_), expanded_equivalence_) self.assertEqual(fol.expand_formula(forall_), Not(Exists(x, Not(expanded_equivalence_))))
def _build_automata(self): rows = self.row_symbols atoms = [AtomicFormula(r) for r in rows] alphabet = Alphabet(set(rows)) ldlf = LDLf_EmptyTraces(alphabet) f = PathExpressionEventually( PathExpressionSequence.chain([ PathExpressionStar( And.chain([Not(atoms[0]), Not(atoms[1]), Not(atoms[2])])), PathExpressionStar( And.chain([atoms[0], Not(atoms[1]), Not(atoms[2])])), # Not(atoms[3]), Not(atoms[4]), Not(atoms[5])]), PathExpressionStar( And.chain([atoms[0], atoms[1], Not(atoms[2])])), # Not(atoms[3]), Not(atoms[4]), Not(atoms[5])]), # And.chain([atoms[0], atoms[1], atoms[2]]), # Not(atoms[3]), Not(atoms[4]), Not(atoms[5])]), # And.chain([atoms[0], atoms[1], atoms[2], atoms[3], Not(atoms[4]), Not(atoms[5])]), # And.chain([atoms[0], atoms[1], atoms[2], atoms[3], atoms[4], Not(atoms[5])]), # And.chain([atoms[0], atoms[1], atoms[2], atoms[3], atoms[4], atoms[5] ]) ]), And.chain([atoms[0], atoms[1], atoms[2]])) nfa = ldlf.to_nfa(f) dfa = _to_pythomata_dfa(nfa) return dfa
def test_to_nnf_derived_formulas(self): fol = self.fol john = ConstantTerm.fromString("john") x = Variable.fromString("x") right_equal = Equal(x, john) true_ = TrueFormula() false_ = FalseFormula() or_ = Or(true_, Not(right_equal)) implies_ = Implies(or_, false_) equivalence_ = Equivalence(implies_, false_) forall_true_ = ForAll(x, true_) forall_not_or_ = ForAll(x, Not(or_)) forall_equivalence_ = ForAll(x, equivalence_) to_nnf_true_ = Equal(DUMMY_TERM, DUMMY_TERM) to_nnf_false_ = Not(Equal(DUMMY_TERM, DUMMY_TERM)) to_nnf_or_ = Or(to_nnf_true_, Not(right_equal)) to_nnf_not_or_ = And(Not(to_nnf_true_), right_equal) to_nnf_implies_ = Or(to_nnf_not_or_, to_nnf_false_) not_to_nnf_implies_ = And(to_nnf_or_, to_nnf_true_) positive_equivalence = And(to_nnf_implies_, to_nnf_false_) negative_equivalence = And(not_to_nnf_implies_, to_nnf_true_) to_nnf_equivalence_ = Or(positive_equivalence, negative_equivalence) self.assertEqual(fol.to_nnf(true_), to_nnf_true_) self.assertEqual(fol.to_nnf(false_), to_nnf_false_) self.assertEqual(fol.to_nnf(or_), to_nnf_or_) self.assertEqual(fol.to_nnf(implies_), to_nnf_implies_) self.assertEqual(fol.to_nnf(equivalence_), to_nnf_equivalence_) self.assertEqual(fol.to_nnf(forall_true_), ForAll(x, to_nnf_true_)) self.assertEqual(fol.to_nnf(forall_not_or_), ForAll(x, to_nnf_not_or_)) self.assertEqual(fol.to_nnf(forall_equivalence_), ForAll(x, to_nnf_equivalence_))
def test_is_formula_and(self): fol = self.fol john = ConstantTerm.fromString("john") paul = ConstantTerm.fromString("paul") not_a_term = ConstantTerm.fromString("NotATerm") right_equal = Equal(john, paul) wrong_equal = Equal(john, not_a_term) self.assertTrue(fol.is_formula(And(right_equal, right_equal))) self.assertFalse(fol.is_formula(And(right_equal, Not(wrong_equal)))) self.assertTrue( fol.is_formula( And(right_equal, PredicateFormula(PredicateSymbol("Person", 2), john, paul)))) self.assertFalse( fol.is_formula( And( right_equal, PredicateFormula(PredicateSymbol("Person", 3), john, paul, john)))) self.assertFalse( fol.is_formula( And( right_equal, PredicateFormula(PredicateSymbol("Person_fake", 2), john, paul))))
def test_to_nnf_derived_formula(self): a_sym = Symbol("a") b_sym = Symbol("b") alphabet = Alphabet({a_sym, b_sym}) a = AtomicFormula(a_sym) b = AtomicFormula(b_sym) pl = PL(alphabet) self.assertEqual(pl.to_nnf(Not(Or(b, Not(a)))), And(Not(b), a)) self.assertEqual(pl.to_nnf(Not(Implies(b, Not(a)))), And(b, a))
def test_expand_formula_allowed_formulas(self): a_sym = Symbol("a") b_sym = Symbol("b") alphabet = Alphabet({a_sym, b_sym}) a = AtomicFormula(a_sym) b = AtomicFormula(b_sym) pl = PL(alphabet) self.assertEqual(pl.expand_formula(a), a) self.assertEqual(pl.expand_formula(Not(b)), Not(b)) self.assertEqual(pl.expand_formula(And(a, b)), And(a, b))
def test_is_formula_composed(self): a_sym = Symbol("a") alphabet = Alphabet({a_sym}) a = AtomicFormula(a_sym) pl = PL(alphabet) self.assertTrue( pl.is_formula( Implies(Not(a), And(TrueFormula(), Not(FalseFormula()))))) self.assertFalse( pl.is_formula( Implies(Not(a), And(TrueFormula(), Next(FalseFormula())))))
def test_truth_propositional(self): ref = self.ref a = self.a b = self.b c = self.c self.assertTrue(ref.truth(a, self.trace_1, 0, 1)) self.assertTrue(ref.truth(And(Not(b), And(a, c)), self.trace_1, 1, 2)) self.assertFalse(ref.truth(And(b, And(a, c)), self.trace_1, 1, 2)) self.assertTrue(ref.truth(TrueFormula(), self.trace_1, 0, 1)) self.assertFalse(ref.truth(FalseFormula(), self.trace_1, 0, 1)) self.assertFalse(ref.truth(TrueFormula(), self.trace_1, 0, 5)) self.assertFalse(ref.truth(TrueFormula(), self.trace_1, 0, 0))
def to_equivalent_formula(self, derived_formula: Formula): if isinstance(derived_formula, Or): return Not(And(Not(derived_formula.f1), Not(derived_formula.f2))) elif isinstance(derived_formula, PathExpressionAlways): return Not(PathExpressionEventually(derived_formula.p, Not(derived_formula.f))) elif isinstance(derived_formula, FalseFormula): return And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC) elif isinstance(derived_formula, TrueFormula): return Not(FalseFormula()) elif isinstance(derived_formula, LDLfLast): return PathExpressionAlways(TrueFormula(), FalseFormula()) else: raise ValueError("Derived formula not recognized")
def test_truth_sequence(self): ref = self.ref a = self.a b = self.b c = self.c self.assertTrue( ref.truth(PathExpressionSequence(And(a, b), And(a, c)), self.trace_1, 0, 2)) self.assertTrue( ref.truth( PathExpressionSequence( And(a, b), PathExpressionSequence(And(a, c), And(b, c))), self.trace_1, 2, 5))
def to_equivalent_formula(self, derived_formula: Formula): if isinstance(derived_formula, Or): return Not(And(Not(derived_formula.f1), Not(derived_formula.f2))) elif isinstance(derived_formula, Always): return Not(Eventually(Not(derived_formula.f))) elif isinstance(derived_formula, Eventually): return Until(TrueFormula(), derived_formula.f) elif isinstance(derived_formula, FalseFormula): return And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC) elif isinstance(derived_formula, TrueFormula): return Not(FalseFormula()) elif isinstance(derived_formula, LDLfLast): return Next(TrueFormula(), FalseFormula()) else: raise ValueError("Derived formula not recognized")
def test_find_atomics(self): a = Symbol("a") b = Symbol("b") c = Symbol("c") atomic_a = AtomicFormula(a) atomic_b = AtomicFormula(b) atomic_c = AtomicFormula(c) self.assertEqual( PL.find_atomics(And(atomic_a, And(atomic_b, atomic_c))), {atomic_a, atomic_b, atomic_c}) self.assertEqual(PL.find_atomics(Or(atomic_a, Or(atomic_b, atomic_c))), {atomic_a, atomic_b, atomic_c}) self.assertEqual( PL.find_atomics(Equivalence(atomic_a, Or(atomic_b, atomic_c))), {atomic_a, atomic_b, atomic_c})
def setUp(self): """Set up test fixtures, if any.""" self.a_sym = Symbol("a") self.b_sym = Symbol("b") self.c_sym = Symbol("c") self.alphabet = Alphabet({self.a_sym, self.b_sym, self.c_sym}) # Propositions self.a = AtomicFormula(self.a_sym) self.b = AtomicFormula(self.b_sym) self.c = AtomicFormula(self.c_sym) self.not_a = Not(self.a) self.not_a_and_b = And(self.not_a, self.b) self.not_a_or_c = Or(self.not_a, self.c) self.true = TrueFormula() self.false = FalseFormula() self.symbol2truth = { self.a_sym: True, self.b_sym: False, self.c_sym: True } self.I = PLInterpretation(self.alphabet, self.symbol2truth) self.PL = PL(self.alphabet)
def to_nnf(self, f: Formula): assert self.is_formula(f) formula = self.expand_formula(f) if isinstance(formula, PredicateFormula) or isinstance(formula, Equal): return formula elif isinstance(formula, And): return And(self.to_nnf(formula.f1), self.to_nnf(formula.f2)) if isinstance(formula, Exists): return Exists(formula.v, self.to_nnf(formula.f)) if isinstance(formula, Not): subformula = formula.f if isinstance(subformula, Not): return self.to_nnf(subformula.f) elif isinstance(subformula, And): return Or(self.to_nnf(Not(subformula.f1)), self.to_nnf((Not(subformula.f2)))) elif isinstance(subformula, Exists): return ForAll(subformula.v, self.to_nnf(Not(subformula.f))) elif isinstance(subformula, PredicateFormula) or isinstance( subformula, Equal): return formula else: raise ValueError else: raise ValueError
def to_nnf(self, f: Formula): # assert self.is_formula(f) # formula = self.expand_formula(f) formula = f if isinstance(formula, AtomicFormula) or isinstance( formula, TrueFormula) or isinstance(formula, FalseFormula): return formula elif isinstance(formula, And) or isinstance(formula, Or): return type(formula)(self.to_nnf(formula.f1), self.to_nnf(formula.f2)) elif type(formula) in self.derived_formulas: return self.to_nnf(self.derived_formulas[type(formula)](formula)) elif isinstance(formula, Not): subformula = formula.f if isinstance(subformula, Not): return self.to_nnf(subformula.f) elif isinstance(subformula, And): return Or(self.to_nnf(Not(subformula.f1)), self.to_nnf((Not(subformula.f2)))) elif isinstance(subformula, Or): return And(self.to_nnf(Not(subformula.f1)), self.to_nnf((Not(subformula.f2)))) elif isinstance(subformula, AtomicFormula) or isinstance( formula, TrueFormula) or isinstance(formula, FalseFormula): return formula elif type(subformula) in self.derived_formulas: return self.to_nnf( Not(self.derived_formulas[type(subformula)](subformula))) else: raise ValueError else: raise ValueError
def _expand_formula(self, f: Formula): if isinstance(f, AtomicFormula): return f elif isinstance(f, And): return And(self.expand_formula(f.f1), self.expand_formula(f.f2)) elif isinstance(f, Not): return Not(self.expand_formula(f.f)) else: raise ValueError("Formula to expand not recognized")
def test_to_nnf_allowed_formulas_not_normalized(self): a_sym = Symbol("a") b_sym = Symbol("b") alphabet = Alphabet({a_sym, b_sym}) a = AtomicFormula(a_sym) b = AtomicFormula(b_sym) pl = PL(alphabet) self.assertEqual(pl.to_nnf(Not(Not(b))), b) self.assertEqual(pl.to_nnf(Not(And(a, Not(b)))), Or(Not(a), b))
def _expand_formula(self, f: Formula): if isinstance(f, AtomicFormula): return f elif isinstance(f, And): return And(self.expand_formula(f.f1), self.expand_formula(f.f2)) elif isinstance(f, Not): return Not(self.expand_formula(f.f)) elif isinstance(f, PathExpressionEventually): return PathExpressionEventually(f.p, self.expand_formula(f.f)) else: raise ValueError("Not valid Formula to expand")
def _expand_formula(self, f: Formula): if isinstance(f, PredicateFormula) or isinstance(f, Equal): return f elif isinstance(f, And): return And(self.expand_formula(f.f1), self.expand_formula(f.f2)) elif isinstance(f, Not): return Not(self.expand_formula(f.f)) elif isinstance(f, Exists): return Exists(f.v, self.expand_formula(f.f)) else: raise ValueError("Not valid Formula to expand.")
def to_equivalent_formula(self, derived_formula: Formula): if isinstance(derived_formula, Or): return Not(And(Not(derived_formula.f1), Not(derived_formula.f2))) elif isinstance(derived_formula, Implies): return Not(And(derived_formula.f1, Not(derived_formula.f2))) elif isinstance(derived_formula, Equivalence): positive_equivalence = And(derived_formula.f1, derived_formula.f2) negative_equivalence = And(Not(derived_formula.f1), Not(derived_formula.f2)) return Not( And(Not(positive_equivalence), Not(negative_equivalence))) elif isinstance(derived_formula, FalseFormula): return Not(Equal(DUMMY_TERM, DUMMY_TERM)) elif isinstance(derived_formula, TrueFormula): return Equal(DUMMY_TERM, DUMMY_TERM) elif isinstance(derived_formula, ForAll): return Not(Exists(derived_formula.v, Not(derived_formula.f))) elif derived_formula in self.allowed_formulas: return derived_formula else: raise ValueError("Derived formula not recognized")
def to_nnf(self, formula: Formula) -> Formula: if isinstance(formula, AtomicFormula): return formula elif isinstance(formula, And): return And(self.to_nnf(formula.f1), self.to_nnf(formula.f2)) elif isinstance(formula, Or): return Or(self.to_nnf(formula.f1), self.to_nnf(formula.f2)) elif isinstance(formula, PathExpressionFormula): return type(formula)(self.to_nnf_path(formula.p), self.to_nnf(formula.f)) elif isinstance(formula, Not): return self._not_to_nnf(formula) else: raise ValueError
def test_to_nnf_allowed_formulas(self): fol = self.fol john = ConstantTerm.fromString("john") x = Variable.fromString("x") right_equal = Equal(x, john) not_ = Not(right_equal) and_ = And(right_equal, not_) exists_ = Exists(x, right_equal) self.assertEqual(fol.to_nnf(right_equal), right_equal) self.assertEqual(fol.to_nnf(not_), not_) self.assertEqual(fol.to_nnf(and_), and_) self.assertEqual(fol.to_nnf(exists_), exists_)
def _expand_formula(self, f: Formula): if isinstance(f, AtomicFormula): return f elif isinstance(f, And): return And(self.expand_formula(f.f1), self.expand_formula(f.f2)) elif isinstance(f, Not): return Not(self.expand_formula(f.f)) elif isinstance(f, Until): return Until(self.expand_formula(f.f1), self.expand_formula(f.f2)) elif isinstance(f, Next): return Next(self.expand_formula(f.f)) else: raise ValueError("Not valid Formula to expand")
def to_equivalent_formula(self, derived_formula: Formula): # make lines shorter ef = self.to_equivalent_formula if isinstance(derived_formula, AtomicFormula): return PathExpressionEventually(derived_formula, LogicalTrue()) elif isinstance(derived_formula, LogicalFalse): return Not(LogicalTrue()) elif isinstance(derived_formula, Or): return Not(And(Not(derived_formula.f1), Not(derived_formula.f2))) elif isinstance(derived_formula, PathExpressionAlways): return Not( PathExpressionEventually(derived_formula.p, Not(derived_formula.f))) elif isinstance(derived_formula, Next): return PathExpressionEventually( TrueFormula(), And(derived_formula.f, Not(ef(End())))) elif isinstance(derived_formula, End): return ef(PathExpressionAlways(TrueFormula(), ef(LogicalFalse()))) elif isinstance(derived_formula, Until): return PathExpressionEventually( PathExpressionStar( PathExpressionSequence( PathExpressionTest(derived_formula.f1), ef(TrueFormula()))), And(derived_formula.f2, Not(ef(End())))) elif isinstance(derived_formula, FalseFormula): return FalseFormula() elif isinstance(derived_formula, TrueFormula): return TrueFormula() elif isinstance(derived_formula, LDLfLast): return PathExpressionEventually(ef(TrueFormula()), ef(End())) # propositional elif isinstance(derived_formula, Formula): pl = PL(self.alphabet) assert pl.is_formula(derived_formula) f = pl.to_nnf(derived_formula) return PathExpressionEventually(f, LogicalTrue()) else: raise ValueError("Derived formula not recognized")
def to_nnf(self, f: Formula) -> Formula: formula = self.expand_formula(f) pl = PL(self.alphabet) if pl.is_formula(formula): return pl.to_nnf(formula) elif isinstance(formula, LogicalTrue): return formula elif isinstance(formula, And): return And(self.to_nnf(formula.f1), self.to_nnf(formula.f2)) elif isinstance(formula, PathExpressionFormula): return type(formula)(self.to_nnf_path(formula.p), self.to_nnf(formula.f)) elif isinstance(formula, Not): return self._not_to_nnf(formula) else: raise ValueError
def _not_to_nnf(self, not_formula: Not): subformula = not_formula.f if isinstance(subformula, AtomicFormula): return not_formula if isinstance(subformula, Not): # skip two consecutive Not new_formula = subformula.f return self.to_nnf(new_formula) elif isinstance(subformula, And): return Or(self.to_nnf(Not(subformula.f1)), self.to_nnf(Not(subformula.f2))) elif isinstance(subformula, Or): return And(self.to_nnf(Not(subformula.f1)), self.to_nnf(Not(subformula.f2))) elif isinstance(subformula, PathExpressionEventually): return PathExpressionAlways(self.to_nnf_path(subformula.p), self.to_nnf(Not(subformula.f))) elif isinstance(subformula, PathExpressionAlways): return PathExpressionEventually(self.to_nnf_path(subformula.p), self.to_nnf(Not(subformula.f))) else: raise ValueError
def test_truth(self): self.assertFalse(self.ldlf.truth(self.not_a, self.trace_1, 0)) self.assertTrue(self.ldlf.truth(self.not_a, self.trace_1, 4)) self.assertTrue(self.ldlf.truth(self.a_and_b, self.trace_1, 0)) self.assertFalse(self.ldlf.truth(self.a_and_b, self.trace_1, 1)) self.assertTrue(self.ldlf.truth(self.a_or_b, self.trace_1, 1)) self.assertTrue( self.ldlf.truth(Not(And(self.b, self.c)), self.trace_1, 0)) self.assertTrue( self.ldlf.truth(self.eventually_seq_a_and_b__a_and_c__not_c, self.trace_1, 0)) self.assertFalse( self.ldlf.truth(self.eventually_seq_a_and_b__a_and_c__not_c, self.trace_1, 1)) self.assertTrue( self.ldlf.truth(self.eventually_propositional_a_and_b__a_and_c, self.trace_1, 0)) self.assertFalse( self.ldlf.truth(self.eventually_test_a__c, self.trace_1, 0)) self.assertTrue( self.ldlf.truth(self.eventually_test_a__b, self.trace_1, 0)) self.assertTrue( self.ldlf.truth(self.eventually_seq_a_and_b__a_and_c__not_c, self.trace_1, 0)) self.assertFalse( self.ldlf.truth(self.eventually_seq_a_and_b__a_and_c__c, self.trace_1, 0)) self.assertTrue(self.ldlf.truth(self.next_a_and_c, self.trace_1, 0)) self.assertTrue(self.ldlf.truth(self.liveness_b_and_c, self.trace_1, 0)) self.assertFalse(self.ldlf.truth(self.liveness_abc, self.trace_1, 0)) self.assertFalse(self.ldlf.truth(self.always_true__a, self.trace_1, 0)) self.assertTrue( self.ldlf.truth(self.always_true__a, self.trace_1.segment(0, self.trace_1.length() - 1), 0)) self.assertTrue( self.ldlf.truth(self.always_true__b_or_c, self.trace_1, 0))
def test_expand_formula_composed(self): a_sym = Symbol("a") alphabet = Alphabet({a_sym}) a = AtomicFormula(a_sym) # T = Not(And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC)) # F = And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC) T = TrueFormula() F = FalseFormula() pl = PL(alphabet) self.assertEqual(pl.expand_formula(And(TrueFormula(), FalseFormula())), And(T, F)) self.assertEqual(pl.expand_formula(Or(TrueFormula(), FalseFormula())), Not(And(Not(T), Not(F)))) self.assertEqual( pl.expand_formula(Implies(TrueFormula(), FalseFormula())), Not(And(Not(Not(T)), Not(F)))) self.assertEqual( pl.expand_formula(Equivalence(TrueFormula(), FalseFormula())), Not(And(Not(And(T, F)), Not(And(Not(T), Not(F))))))
def test_expand_formula_derived_formulas(self): a_sym = Symbol("a") b_sym = Symbol("b") alphabet = Alphabet({a_sym, b_sym}) a = AtomicFormula(a_sym) b = AtomicFormula(b_sym) # T = Not(And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC)) # F = And(Not(DUMMY_ATOMIC), DUMMY_ATOMIC) T = TrueFormula() F = FalseFormula() pl = PL(alphabet) self.assertEqual(pl.expand_formula(TrueFormula()), T) self.assertEqual(pl.expand_formula(FalseFormula()), F) self.assertEqual(pl.expand_formula(Or(a, b)), Not(And(Not(a), Not(b)))) self.assertEqual(pl.expand_formula(Implies(a, b)), Not(And(Not(Not(a)), Not(b)))) self.assertEqual(pl.expand_formula(Implies(b, a)), Not(And(Not(Not(b)), Not(a)))) # A === B = (A AND B) OR (NOT A AND NOT B) = NOT( NOT(A AND B) AND NOT(NOT A AND NOT B) ) self.assertEqual(pl.expand_formula(Equivalence(a, b)), Not(And(Not(And(a, b)), Not(And(Not(a), Not(b))))))
def test_minimal_models(self): a = Symbol("a") b = Symbol("b") c = Symbol("c") alphabet = Alphabet({a, b, c}) pl = PL(alphabet) atomic_a = AtomicFormula(a) atomic_b = AtomicFormula(b) atomic_c = AtomicFormula(c) self.assertEqual( pl.minimal_models(TrueFormula()), {PLInterpretation(alphabet, { a: False, b: False, c: False })}) self.assertEqual(pl.minimal_models(FalseFormula()), set()) self.assertEqual( pl.minimal_models(atomic_a), {PLInterpretation(alphabet, { a: True, b: False, c: False })}) self.assertEqual( pl.minimal_models(Not(atomic_a)), {PLInterpretation(alphabet, { a: False, b: False, c: False })}) self.assertEqual( pl.minimal_models(And(atomic_a, atomic_b)), {PLInterpretation(alphabet, { a: True, b: True, c: False })}) self.assertEqual(pl.minimal_models(And(atomic_a, Not(atomic_a))), set()) self.assertEqual( pl.minimal_models(Or(atomic_a, atomic_b)), { PLInterpretation(alphabet, { a: False, b: True, c: False }), PLInterpretation(alphabet, { a: True, b: False, c: False }) }) self.assertEqual( pl.minimal_models(And.chain([atomic_a, atomic_b, atomic_c])), {PLInterpretation(alphabet, { a: True, b: True, c: True })})