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 test_expand_formula_error(self): a_sym = Symbol("a") alphabet = Alphabet({a_sym}) a = Next(AtomicFormula(a_sym)) pl = PL(alphabet) with self.assertRaises(ValueError) as ve: pl.expand_formula(a)
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_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): """Manage the case when we have a propositional formula.""" # Check first if it is a propositional pl = PL(self.alphabet) if pl.is_formula(f): return super().expand_formula( PathExpressionEventually(f, LogicalTrue())) else: return super().expand_formula(f)
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 _is_formula(self, f: Formula): """Check if a formula is legal in the current formal system""" if isinstance(f, PathExpressionUnion): return self.is_formula(f.p1) and self.is_formula(f.p2) elif isinstance(f, PathExpressionSequence): return self._is_formula(f.p1) and self._is_formula(f.p2) elif isinstance(f, PathExpressionStar): return self._is_formula(f.p) else: pl = PL(self.alphabet) return pl.is_formula(f)
def _expand_path(self, p: PathExpression) -> PathExpression: if isinstance(p, PathExpressionUnion) or isinstance( p, PathExpressionSequence): return type(p)(self._expand_path(p.p1), self._expand_path(p.p2)) elif isinstance(p, PathExpressionTest): return PathExpressionTest(self.expand_formula(p.f)) elif isinstance(p, PathExpressionStar): return PathExpressionStar(self._expand_path(p.p)) elif isinstance(p, Formula): pl = PL(self.alphabet) return pl.expand_formula(p) else: raise ValueError
def _is_path(self, p: PathExpression): # PathExpression if isinstance(p, PathExpressionUnion) or isinstance(p, PathExpressionSequence): return self._is_path(p.p1) and self._is_path(p.p2) elif isinstance(p, PathExpressionTest): return self.is_formula(p.f) elif isinstance(p, PathExpressionStar): return self._is_path(p.p) elif isinstance(p, Formula): pl = PL(self.alphabet) return pl.is_formula(p) else: raise ValueError("Argument not a valid Path")
def to_nnf(self, f: Formula): if isinstance(f, PathExpressionUnion): return PathExpressionUnion(self.to_nnf(f.p1), self.to_nnf(f.p2)) elif isinstance(f, PathExpressionSequence): return PathExpressionSequence(self.to_nnf(f.p1), self.to_nnf(f.p2)) elif isinstance(f, PathExpressionStar): return PathExpressionStar(self.to_nnf(f.p)) elif isinstance(f, Formula): pl = PL(self.alphabet) assert pl.is_formula(f) return f else: raise ValueError
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 to_nnf_path(self, path: PathExpression): if isinstance(path, PathExpressionTest): return PathExpressionTest(self.to_nnf(path.f)) elif isinstance(path, PathExpressionUnion): return PathExpressionUnion(self.to_nnf_path(path.p1), self.to_nnf_path(path.p2)) elif isinstance(path, PathExpressionSequence): return PathExpressionSequence(self.to_nnf_path(path.p1), self.to_nnf_path(path.p2)) elif isinstance(path, PathExpressionStar): return PathExpressionStar(self.to_nnf_path(path.p)) elif isinstance(path, Formula): pl = PL(self.alphabet) assert pl.is_formula(path) return pl.to_nnf(path) else: raise ValueError
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 _truth(self, f: Formula, trace: FiniteTrace, position: int): assert trace.alphabet == self.alphabet truth = self._truth # LDLfFormulas if isinstance(f, AtomicFormula): return f.symbol in trace.get(position) elif isinstance(f, Not): return not self.truth(f.f, trace, position) elif isinstance(f, And): return self.truth(f.f1, trace, position) and self.truth(f.f2, trace, position) elif isinstance(f, PathExpressionEventually): path = f.p assert self._is_path(path) if isinstance(path, PathExpressionTest): return truth(path.f, trace, position) and truth(f.f, trace, position) elif isinstance(path, PathExpressionUnion): return truth(PathExpressionEventually(path.p1, f.f), trace, position) or truth( PathExpressionEventually(path.p2, f.f), trace, position) elif isinstance(path, PathExpressionSequence): return truth(PathExpressionEventually(path.p1, PathExpressionEventually(path.p2, f.f)), trace, position) elif isinstance(path, PathExpressionStar): return truth(f.f, trace, position) or ( position < trace.last() and truth(PathExpressionEventually(path.p, PathExpressionEventually(path, f.f)), trace, position) and not self._is_testonly(path) ) # Should be a Propositional Formula else: pl, I = PL._from_set_of_propositionals(trace.get(position), trace.alphabet) return position < trace.last() and pl.truth(path, I) and truth(f.f, trace, position + 1) else: raise ValueError("Argument not a valid Formula")
def _is_formula(self, f: Formula): """Check if a formula is legal in the current formal system""" # Check first if it is a propositional pl = PL(self.alphabet) if pl.is_formula(f): return self.is_formula(PathExpressionEventually(f, LogicalTrue())) elif isinstance(f, LogicalTrue): return True elif isinstance(f, Not): return self.is_formula(f.f) elif isinstance(f, And): return self.is_formula(f.f1) and self.is_formula(f.f2) elif isinstance(f, PathExpressionEventually): return self._is_path(f.p) and self.is_formula(f.f) else: return False
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")
class TestPL(unittest.TestCase): """Tests for `pythogic.ltlf` package.""" 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 tearDown(self): """Tear down test fixtures, if any.""" def test_truth(self): self.assertTrue(self.PL.truth(self.a, self.I)) self.assertFalse(self.PL.truth(self.b, self.I)) self.assertTrue(self.PL.truth(self.c, self.I)) self.assertFalse(self.PL.truth(self.not_a, self.I)) self.assertFalse(self.PL.truth(self.not_a_and_b, self.I)) self.assertTrue(self.PL.truth(self.not_a_or_c, self.I)) self.assertTrue(self.PL.truth(self.true, self.I)) self.assertFalse(self.PL.truth(self.false, self.I))
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 _truth(self, f: Formula, trace: FiniteTrace, start: int, end: int): assert self._is_formula(f) assert trace.alphabet == self.alphabet truth = self.truth if isinstance(f, PathExpressionUnion): return truth(f.p1, trace, start, end) or truth( f.p2, trace, start, end) if isinstance(f, PathExpressionSequence): return any( truth(f.p1, trace, start, k) and truth(f.p2, trace, k, end) for k in range(start, end + 1)) if isinstance(f, PathExpressionStar): return end == start or any( truth(f.p, trace, start, k) and truth(f, trace, k, end) for k in range(start, end + 1)) else: pl, I = PL._from_set_of_propositionals(trace.get(start), self.alphabet) assert pl.is_formula(f) return end == start + 1 and end <= trace.length() and pl.truth( f, I)
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))))))
class REf(FormalSystem): def __init__(self, alphabet: Alphabet): super().__init__(alphabet) self.pl = PL(self.alphabet) allowed_formulas = { PathExpressionUnion, PathExpressionSequence, PathExpressionStar, AtomicFormula, And, Not } derived_formulas = {Or, Implies, Equivalence, TrueFormula, FalseFormula} def _is_formula(self, f: Formula): """Check if a formula is legal in the current formal system""" if isinstance(f, PathExpressionUnion): return self.is_formula(f.p1) and self.is_formula(f.p2) elif isinstance(f, PathExpressionSequence): return self._is_formula(f.p1) and self._is_formula(f.p2) elif isinstance(f, PathExpressionStar): return self._is_formula(f.p) else: pl = PL(self.alphabet) return pl.is_formula(f) def _truth(self, f: Formula, trace: FiniteTrace, start: int, end: int): assert self._is_formula(f) assert trace.alphabet == self.alphabet truth = self.truth if isinstance(f, PathExpressionUnion): return truth(f.p1, trace, start, end) or truth( f.p2, trace, start, end) if isinstance(f, PathExpressionSequence): return any( truth(f.p1, trace, start, k) and truth(f.p2, trace, k, end) for k in range(start, end + 1)) if isinstance(f, PathExpressionStar): return end == start or any( truth(f.p, trace, start, k) and truth(f, trace, k, end) for k in range(start, end + 1)) else: pl, I = PL._from_set_of_propositionals(trace.get(start), self.alphabet) assert pl.is_formula(f) return end == start + 1 and end <= trace.length() and pl.truth( f, I) def to_nnf(self, f: Formula): if isinstance(f, PathExpressionUnion): return PathExpressionUnion(self.to_nnf(f.p1), self.to_nnf(f.p2)) elif isinstance(f, PathExpressionSequence): return PathExpressionSequence(self.to_nnf(f.p1), self.to_nnf(f.p2)) elif isinstance(f, PathExpressionStar): return PathExpressionStar(self.to_nnf(f.p)) elif isinstance(f, Formula): pl = PL(self.alphabet) assert pl.is_formula(f) return f else: raise ValueError def to_equivalent_formula(self, derived_formula: Formula): return self.pl.to_equivalent_formula(derived_formula) def _expand_formula(self, f: Formula): if self.pl.is_formula(f): return self.pl.expand_formula(f) else: return f
def test_is_formula_error(self): a_sym = Symbol("a") alphabet = Alphabet({a_sym}) a = Next(AtomicFormula(a_sym)) pl = PL(alphabet) self.assertFalse(pl.is_formula(a))
def test_is_formula_atomic(self): a_sym = Symbol("a") alphabet = Alphabet({a_sym}) a = AtomicFormula(a_sym) pl = PL(alphabet) self.assertTrue(pl.is_formula(a))
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 })})
def to_nfa(self, f: Formula): # TODO: optimize!!! assert self.is_formula(f) nnf_f = self.to_nnf(f) alphabet = powerset(self.alphabet.symbols) initial_states = {frozenset([nnf_f])} final_states = {frozenset()} delta = set() pl, I = PL._from_set_of_propositionals(set(), Alphabet(set())) d = self.delta(nnf_f, frozenset(), epsilon=True) if pl.truth(d, I): final_states.add(frozenset([nnf_f])) states = {frozenset(), frozenset([nnf_f])} states_changed, delta_changed = True, True while states_changed or delta_changed: states_changed, delta_changed = False, False for actions_set in alphabet: states_list = list(states) for q in states_list: delta_formulas = [ self.delta(subf, actions_set) for subf in q ] atomics = [ s for subf in delta_formulas for s in PL.find_atomics(subf) ] symbol2formula = { Symbol(str(f)): f for f in atomics if f != TrueFormula() and f != FalseFormula() } formula2atomic_formulas = { f: AtomicFormula.fromName(str(f)) if f != TrueFormula() and f != FalseFormula() else f for f in atomics } transformed_delta_formulas = [ self._tranform_delta(f, formula2atomic_formulas) for f in delta_formulas ] conjunctions = And.chain(transformed_delta_formulas) models = frozenset( PL(Alphabet( set(symbol2formula))).minimal_models(conjunctions)) if len(models) == 0: continue for min_model in models: q_prime = frozenset({ symbol2formula[s] for s in min_model.symbol2truth if min_model.symbol2truth[s] }) len_before = len(states) states.add(q_prime) if len(states) == len_before + 1: states_list.append(q_prime) states_changed = True len_before = len(delta) delta.add((q, actions_set, q_prime)) if len(delta) == len_before + 1: delta_changed = True # check if q_prime should be added as final state if len(q_prime) == 0: final_states.add(q_prime) else: q_prime_delta_conjunction = And.chain([ self.delta(subf, frozenset(), epsilon=True) for subf in q_prime ]) pl, I = PL._from_set_of_propositionals( set(), Alphabet(set())) if pl.truth(q_prime_delta_conjunction, I): final_states.add(q_prime) return { "alphabet": alphabet, "states": frozenset(states), "initial_states": frozenset(initial_states), "transitions": delta, "accepting_states": frozenset(final_states) }
class LDLf_EmptyTraces(FormalSystem): def __init__(self, alphabet: Alphabet): super().__init__(alphabet) self.pl = PL(self.alphabet) allowed_formulas = { LogicalTrue, Not, And, PathExpressionEventually, TrueFormula, FalseFormula } derived_formulas = { LogicalFalse, Or, Next, Until, End, PathExpressionAlways, LDLfLast, AtomicFormula } def _is_formula(self, f: Formula): """Check if a formula is legal in the current formal system""" # Check first if it is a propositional pl = PL(self.alphabet) if pl.is_formula(f): return self.is_formula(PathExpressionEventually(f, LogicalTrue())) elif isinstance(f, LogicalTrue): return True elif isinstance(f, Not): return self.is_formula(f.f) elif isinstance(f, And): return self.is_formula(f.f1) and self.is_formula(f.f2) elif isinstance(f, PathExpressionEventually): return self._is_path(f.p) and self.is_formula(f.f) else: return False def _is_path(self, p: PathExpression): # PathExpression if isinstance(p, PathExpressionUnion) or isinstance( p, PathExpressionSequence): return self._is_path(p.p1) and self._is_path(p.p2) elif isinstance(p, PathExpressionTest): if self.pl.is_formula(p.f): return True else: return self.is_formula(p.f) elif isinstance(p, PathExpressionStar): return self._is_path(p.p) elif isinstance(p, Formula): pl = PL(self.alphabet) return pl.is_formula(p) else: raise ValueError("Argument not a valid Path") def expand_formula(self, f: Formula): """Manage the case when we have a propositional formula.""" # Check first if it is a propositional pl = PL(self.alphabet) if pl.is_formula(f): return super().expand_formula( PathExpressionEventually(f, LogicalTrue())) else: return super().expand_formula(f) def _expand_formula(self, f: Formula): if isinstance(f, LogicalTrue): 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(self._expand_path(f.p), self.expand_formula(f.f)) else: raise ValueError("Not valid Formula to expand") def _expand_path(self, p: PathExpression) -> PathExpression: if isinstance(p, PathExpressionUnion) or isinstance( p, PathExpressionSequence): return type(p)(self._expand_path(p.p1), self._expand_path(p.p2)) elif isinstance(p, PathExpressionTest): return PathExpressionTest(self.expand_formula(p.f)) elif isinstance(p, PathExpressionStar): return PathExpressionStar(self._expand_path(p.p)) elif isinstance(p, Formula): pl = PL(self.alphabet) return pl.expand_formula(p) else: raise ValueError @staticmethod def _is_testonly(p: PathExpression): if isinstance(p, PathExpressionUnion) or isinstance( p, PathExpressionSequence): return LDLf_EmptyTraces._is_testonly( p.p1) and LDLf_EmptyTraces._is_testonly(p.p2) elif isinstance(p, PathExpressionStar): return LDLf_EmptyTraces._is_testonly(p.p) else: return isinstance(p, PathExpressionTest) def _truth(self, f: Formula, trace: FiniteTrace, position: int): assert trace.alphabet == self.alphabet truth = self._truth pl = PL(self.alphabet) if pl.is_formula(f): return self.truth(PathExpressionEventually(f, LogicalTrue()), trace, position) elif isinstance(f, LogicalTrue): return True elif isinstance(f, Not): return not self.truth(f.f, trace, position) elif isinstance(f, And): return self.truth(f.f1, trace, position) and self.truth( f.f2, trace, position) elif isinstance(f, PathExpressionEventually): path = f.p assert self._is_path(path) if isinstance(path, PathExpressionTest): return truth(path.f, trace, position) and truth( f.f, trace, position) elif isinstance(path, PathExpressionUnion): return truth(PathExpressionEventually(path.p1, f.f), trace, position) or truth( PathExpressionEventually(path.p2, f.f), trace, position) elif isinstance(path, PathExpressionSequence): return truth( PathExpressionEventually( path.p1, PathExpressionEventually(path.p2, f.f)), trace, position) elif isinstance(path, PathExpressionStar): return truth(f.f, trace, position) or (position < trace.last() and truth( PathExpressionEventually( path.p, PathExpressionEventually( path, f.f)), trace, position) and not self._is_testonly(path)) # path should be a Propositional Formula else: pl, I = PL._from_set_of_propositionals(trace.get(position), trace.alphabet) return position < trace.length() and pl.truth( path, I) and truth(f.f, trace, position + 1) else: raise ValueError("Argument not a valid Formula") 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, Not): # skip two consecutive Not new_formula = subformula.f return self.to_nnf(new_formula) elif isinstance(subformula, LogicalTrue): return LogicalFalse() 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 to_nnf_path(self, path: PathExpression): if isinstance(path, PathExpressionTest): return PathExpressionTest(self.to_nnf(path.f)) elif isinstance(path, PathExpressionUnion): return PathExpressionUnion(self.to_nnf_path(path.p1), self.to_nnf_path(path.p2)) elif isinstance(path, PathExpressionSequence): return PathExpressionSequence(self.to_nnf_path(path.p1), self.to_nnf_path(path.p2)) elif isinstance(path, PathExpressionStar): return PathExpressionStar(self.to_nnf_path(path.p)) elif isinstance(path, Formula): pl = PL(self.alphabet) if pl.is_formula(path): return pl.to_nnf(path) else: raise ValueError else: raise ValueError def to_nfa(self, f: Formula): # TODO: optimize!!! assert self.is_formula(f) nnf_f = self.to_nnf(f) alphabet = powerset(self.alphabet.symbols) initial_states = {frozenset([nnf_f])} final_states = {frozenset()} delta = set() pl, I = PL._from_set_of_propositionals(set(), Alphabet(set())) d = self.delta(nnf_f, frozenset(), epsilon=True) if pl.truth(d, I): final_states.add(frozenset([nnf_f])) states = {frozenset(), frozenset([nnf_f])} states_changed, delta_changed = True, True while states_changed or delta_changed: states_changed, delta_changed = False, False for actions_set in alphabet: states_list = list(states) for q in states_list: delta_formulas = [ self.delta(subf, actions_set) for subf in q ] atomics = [ s for subf in delta_formulas for s in PL.find_atomics(subf) ] symbol2formula = { Symbol(str(f)): f for f in atomics if f != TrueFormula() and f != FalseFormula() } formula2atomic_formulas = { f: AtomicFormula.fromName(str(f)) if f != TrueFormula() and f != FalseFormula() else f for f in atomics } transformed_delta_formulas = [ self._tranform_delta(f, formula2atomic_formulas) for f in delta_formulas ] conjunctions = And.chain(transformed_delta_formulas) models = frozenset( PL(Alphabet( set(symbol2formula))).minimal_models(conjunctions)) if len(models) == 0: continue for min_model in models: q_prime = frozenset({ symbol2formula[s] for s in min_model.symbol2truth if min_model.symbol2truth[s] }) len_before = len(states) states.add(q_prime) if len(states) == len_before + 1: states_list.append(q_prime) states_changed = True len_before = len(delta) delta.add((q, actions_set, q_prime)) if len(delta) == len_before + 1: delta_changed = True # check if q_prime should be added as final state if len(q_prime) == 0: final_states.add(q_prime) else: q_prime_delta_conjunction = And.chain([ self.delta(subf, frozenset(), epsilon=True) for subf in q_prime ]) pl, I = PL._from_set_of_propositionals( set(), Alphabet(set())) if pl.truth(q_prime_delta_conjunction, I): final_states.add(q_prime) return { "alphabet": alphabet, "states": frozenset(states), "initial_states": frozenset(initial_states), "transitions": delta, "accepting_states": frozenset(final_states) } def _tranform_delta(self, f: Formula, formula2AtomicFormula): if isinstance(f, Not): return Not(self._tranform_delta(f, formula2AtomicFormula)) elif isinstance(f, And) or isinstance(f, Or): return type(f)(self._tranform_delta(f.f1, formula2AtomicFormula), self._tranform_delta(f.f2, formula2AtomicFormula)) elif isinstance(f, TrueFormula) or isinstance(f, FalseFormula): return f else: return formula2AtomicFormula[f] def delta(self, f: Formula, action: FrozenSet[Symbol], epsilon=False): # TODO: should return [True|False]Formula or simply True/False? pl, I = PL._from_set_of_propositionals(action, self.alphabet) if pl.is_formula(f): return self.delta(PathExpressionEventually(f, LogicalTrue()), action, epsilon) elif isinstance(f, LogicalTrue): return TrueFormula() elif isinstance(f, LogicalFalse): return FalseFormula() elif isinstance(f, And): return And(self.delta(f.f1, action), self.delta(f.f2, action, epsilon)) elif isinstance(f, Or): return Or(self.delta(f.f1, action), self.delta(f.f2, action, epsilon)) elif isinstance(f, PathExpressionEventually): if pl.is_formula(f.p): if not epsilon and pl.truth(f.p, I): return self._expand(f.f) else: return FalseFormula() elif isinstance(f.p, PathExpressionTest): return And(self.delta(f.p.f, action, epsilon), self.delta(f.f, action, epsilon)) elif isinstance(f.p, PathExpressionUnion): return Or( self.delta(PathExpressionEventually(f.p.p1, f.f), action, epsilon), self.delta(PathExpressionEventually(f.p.p2, f.f), action, epsilon)) elif isinstance(f.p, PathExpressionSequence): e2 = PathExpressionEventually(f.p.p2, f.f) e1 = PathExpressionEventually(f.p.p1, e2) return self.delta(e1, action, epsilon) elif isinstance(f.p, PathExpressionStar): o1 = self.delta(f.f, action, epsilon) o2 = self.delta(PathExpressionEventually(f.p.p, F(f)), action, epsilon) return Or(o1, o2) elif isinstance(f, PathExpressionAlways): if pl.is_formula(f.p): if not epsilon and pl.truth(f.p, I): return self._expand(f.f) else: return TrueFormula() elif isinstance(f.p, PathExpressionTest): o1 = self.delta(self.to_nnf(Not(f.p.f)), action, epsilon) o2 = self.delta(f.f, action, epsilon) return Or(o1, o2) elif isinstance(f.p, PathExpressionUnion): return And( self.delta(PathExpressionAlways(f.p.p1, f.f), action, epsilon), self.delta(PathExpressionAlways(f.p.p2, f.f), action, epsilon)) elif isinstance(f.p, PathExpressionSequence): return self.delta( PathExpressionAlways(f.p.p1, PathExpressionAlways(f.p.p2, f.f)), action, epsilon) elif isinstance(f.p, PathExpressionStar): a1 = self.delta(f.f, action, epsilon) a2 = self.delta(PathExpressionAlways(f.p.p, T(f)), action, epsilon) return And(a1, a2) elif isinstance(f, F): return FalseFormula() elif isinstance(f, T): return TrueFormula() else: raise ValueError def _expand(self, f: Formula): if isinstance(f, F) or isinstance(f, T): return self._expand(f.f) elif isinstance(f, PathExpressionEventually) or isinstance( f, PathExpressionAlways): return type(f)(f.p, self._expand(f.f)) else: return f
def __init__(self, alphabet: Alphabet): super().__init__(alphabet) self.pl = PL(self.alphabet)
def delta(self, f: Formula, action: FrozenSet[Symbol], epsilon=False): # TODO: should return [True|False]Formula or simply True/False? pl, I = PL._from_set_of_propositionals(action, self.alphabet) if pl.is_formula(f): return self.delta(PathExpressionEventually(f, LogicalTrue()), action, epsilon) elif isinstance(f, LogicalTrue): return TrueFormula() elif isinstance(f, LogicalFalse): return FalseFormula() elif isinstance(f, And): return And(self.delta(f.f1, action), self.delta(f.f2, action, epsilon)) elif isinstance(f, Or): return Or(self.delta(f.f1, action), self.delta(f.f2, action, epsilon)) elif isinstance(f, PathExpressionEventually): if pl.is_formula(f.p): if not epsilon and pl.truth(f.p, I): return self._expand(f.f) else: return FalseFormula() elif isinstance(f.p, PathExpressionTest): return And(self.delta(f.p.f, action, epsilon), self.delta(f.f, action, epsilon)) elif isinstance(f.p, PathExpressionUnion): return Or( self.delta(PathExpressionEventually(f.p.p1, f.f), action, epsilon), self.delta(PathExpressionEventually(f.p.p2, f.f), action, epsilon)) elif isinstance(f.p, PathExpressionSequence): e2 = PathExpressionEventually(f.p.p2, f.f) e1 = PathExpressionEventually(f.p.p1, e2) return self.delta(e1, action, epsilon) elif isinstance(f.p, PathExpressionStar): o1 = self.delta(f.f, action, epsilon) o2 = self.delta(PathExpressionEventually(f.p.p, F(f)), action, epsilon) return Or(o1, o2) elif isinstance(f, PathExpressionAlways): if pl.is_formula(f.p): if not epsilon and pl.truth(f.p, I): return self._expand(f.f) else: return TrueFormula() elif isinstance(f.p, PathExpressionTest): o1 = self.delta(self.to_nnf(Not(f.p.f)), action, epsilon) o2 = self.delta(f.f, action, epsilon) return Or(o1, o2) elif isinstance(f.p, PathExpressionUnion): return And( self.delta(PathExpressionAlways(f.p.p1, f.f), action, epsilon), self.delta(PathExpressionAlways(f.p.p2, f.f), action, epsilon)) elif isinstance(f.p, PathExpressionSequence): return self.delta( PathExpressionAlways(f.p.p1, PathExpressionAlways(f.p.p2, f.f)), action, epsilon) elif isinstance(f.p, PathExpressionStar): a1 = self.delta(f.f, action, epsilon) a2 = self.delta(PathExpressionAlways(f.p.p, T(f)), action, epsilon) return And(a1, a2) elif isinstance(f, F): return FalseFormula() elif isinstance(f, T): return TrueFormula() else: raise ValueError