def test_delta(): parser = LDLfParser() i_ = {} i_a = {"A": True} i_b = {"B": True} i_ab = {"A": True, "B": True} true = PLTrue() false = PLFalse() tt = PLAtomic(LDLfLogicalTrue()) ff = PLAtomic(LDLfLogicalFalse()) assert parser("<A>tt").delta(i_) == false assert parser("<A>tt").delta(i_a) == tt assert parser("<A>tt").delta(i_b) == false assert parser("<A>tt").delta(i_ab) == tt assert parser("[B]ff").delta(i_) == true assert parser("[B]ff").delta(i_a) == true assert parser("[B]ff").delta(i_b) == ff assert parser("[B]ff").delta(i_ab) == ff f = parser("!(<?(!last)>end)") assert f.delta(i_) == f.to_nnf().delta(i_) assert f.delta(i_ab) == f.to_nnf().delta(i_ab) assert f.delta(i_, epsilon=True) == f.to_nnf().delta(i_, epsilon=True) assert f.delta(i_ab, epsilon=True) == f.to_nnf().delta(i_ab, epsilon=True) # with epsilon=True, the result is either PLTrue or PLFalse assert f.delta(i_, epsilon=True) in [PLTrue(), PLFalse()]
def _delta(self, i: PLInterpretation, epsilon=False): if epsilon: return PLFalse() else: return PLAnd( [PLAtomic(self.f), PLAtomic(LTLfNot(LTLfEnd()).to_nnf())])
def test_nnf(): parser = PLParser() sa, sb = "A", "B" a, b = PLAtomic(sa), PLAtomic(sb) i_ = PLInterpretation(set()) i_a = PLInterpretation({sa}) i_b = PLInterpretation({sb}) i_ab = PLInterpretation({sa, sb}) not_a_and_b = parser("!(A&B)") nnf_not_a_and_b = parser("!A | !B") assert not_a_and_b.to_nnf() == nnf_not_a_and_b assert nnf_not_a_and_b == nnf_not_a_and_b.to_nnf() dup = parser("!(A | A)") nnf_dup = dup.to_nnf() assert nnf_dup == PLNot(a) material_implication = parser("!A | B <-> !(A & !B) <-> A->B") nnf_material_implication = parser( "((!A | B) & (!A | B) & (!A | B)) | ((A & !B) & (A & !B) & (A & !B))") nnf_m = material_implication.to_nnf() assert nnf_m == nnf_material_implication.to_nnf() assert nnf_m.truth(i_) == material_implication.truth( i_) == nnf_material_implication.truth(i_) == True assert nnf_m.truth(i_a) == material_implication.truth( i_a) == nnf_material_implication.truth(i_a) == True assert nnf_m.truth(i_b) == material_implication.truth( i_b) == nnf_material_implication.truth(i_b) == True assert nnf_m.truth(i_ab) == material_implication.truth( i_ab) == nnf_material_implication.truth(i_ab) == True
def test_always(self): parser = self.parser i_, i_a, i_b, i_ab = self.i_, self.i_a, self.i_b, self.i_ab true = self.true false = self.false assert parser("G a").delta(i_a) == PLAnd([ true, PLOr([ false, PLOr([ PLAtomic(LTLfRelease([LTLfFalse(), LTLfAtomic("a")])), PLAtomic(LTLfRelease([LTLfFalse(), LTLfFalse()])), ]), ]), ]) assert parser("G a").delta(i_a) == PLAnd([ true, PLOr([ false, PLOr([ PLAtomic(LTLfRelease([LTLfFalse(), LTLfAtomic("a")])), PLAtomic(LTLfRelease([LTLfFalse(), LTLfFalse()])), ]), ]), ]) assert parser("G a").delta(i_a, epsilon=True) == true assert parser("G a").delta(i_, epsilon=True) == true
def test_eventually(self): parser = self.parser i_, i_a, i_b, i_ab = self.i_, self.i_a, self.i_b, self.i_ab true = self.true false = self.false assert parser("F a").delta(i_a) == PLOr([ true, PLAnd([ true, PLAnd([ PLAtomic(LTLfUntil([LTLfTrue(), LTLfAtomic("a")])), PLAtomic(LTLfUntil([LTLfTrue(), LTLfTrue()])), ]), ]), ]) assert parser("F a").delta(i_) == PLOr([ false, PLAnd([ true, PLAnd([ PLAtomic(LTLfUntil([LTLfTrue(), LTLfAtomic("a")])), PLAtomic(LTLfUntil([LTLfTrue(), LTLfTrue()])), ]), ]), ]) assert parser("F a").delta(i_a, epsilon=True) == false assert parser("F a").delta(i_, epsilon=True) == false
def _delta(self, i: PropositionalInterpretation, epsilon=False): """Apply the delta function.""" if epsilon: return PLFalse() else: return PLAnd( [PLAtomic(self.f), PLAtomic(LTLfNot(LTLfEnd()).to_nnf())])
def setup_class(cls): cls.sa, cls.sb = "a", "b" cls.i_ = PLInterpretation(set()) cls.i_a = PLInterpretation({cls.sa}) cls.i_b = PLInterpretation({cls.sb}) cls.i_ab = PLInterpretation({cls.sa, cls.sb}) cls.a, cls.b = PLAtomic(cls.sa), PLAtomic(cls.sb)
def setup_class(cls): cls.sa, cls.sb = "a", "b" cls.i_ = {} cls.i_a = {cls.sa: True} cls.i_b = {cls.sb: True} cls.i_ab = {cls.sa: True, cls.sb: True} cls.a, cls.b = PLAtomic(cls.sa), PLAtomic(cls.sb)
def test_nnf(): parser = PLParser() sa, sb = "A", "B" a, b = PLAtomic(sa), PLAtomic(sb) i_ = {} i_a = {sa: True} i_b = {sb: True} i_ab = {sa: True, sb: True} not_a_and_b = parser("!(A&B)") nnf_not_a_and_b = parser("!A | !B") assert not_a_and_b.to_nnf() == nnf_not_a_and_b assert nnf_not_a_and_b == nnf_not_a_and_b.to_nnf() dup = parser("!(A | A)") nnf_dup = dup.to_nnf() assert nnf_dup == PLAnd([PLNot(a), PLNot(a)]) material_implication = parser("!A | B <-> !(A & !B) <-> A->B") nnf_material_implication = parser( "((!A | B) & (!A | B) & (!A | B)) | ((A & !B) & (A & !B) & (A & !B))" ) nnf_m = material_implication.to_nnf() assert nnf_m == nnf_material_implication.to_nnf() assert ( nnf_m.truth(i_) == material_implication.truth(i_) == nnf_material_implication.truth(i_) == True ) assert ( nnf_m.truth(i_a) == material_implication.truth(i_a) == nnf_material_implication.truth(i_a) == True ) assert ( nnf_m.truth(i_b) == material_implication.truth(i_b) == nnf_material_implication.truth(i_b) == True ) assert ( nnf_m.truth(i_ab) == material_implication.truth(i_ab) == nnf_material_implication.truth(i_ab) == True )
def test_until(self): parser = self.parser i_, i_a, i_b, i_ab = self.i_, self.i_a, self.i_b, self.i_ab true = self.true false = self.false assert parser("A U B").delta(i_a) == PLOr([ false, PLAnd([ true, PLAtomic(LTLfUntil([LTLfAtomic("A"), LTLfAtomic("B")])), PLAtomic(LTLfEventually(LTLfTrue()).to_nnf()) ]) ]) assert parser("A U B").delta(i_ab, epsilon=True) == false
def test_release(self): parser = self.parser i_, i_a, i_b, i_ab = self.i_, self.i_a, self.i_b, self.i_ab true = self.true false = self.false assert parser("A R B").delta(i_a) == PLAnd([ false, PLOr([ true, PLAtomic(LTLfRelease([LTLfAtomic("A"), LTLfAtomic("B")])), PLAtomic(LTLfAlways(LTLfFalse()).to_nnf()) ]) ]) assert parser("A R B").delta(i_ab, epsilon=True) == true
def p_propositional(self, p): """propositional : propositional EQUIVALENCE propositional | propositional IMPLIES propositional | propositional OR propositional | propositional AND propositional | NOT propositional | FALSE | TRUE | ATOM""" if len(p)==4: if p[2] == Symbols.EQUIVALENCE.value: p[0] = PLEquivalence([p[1], p[3]]) elif p[2] == Symbols.IMPLIES.value: p[0] = PLImplies([p[1], p[3]]) elif p[2] == Symbols.OR.value: p[0] = PLOr([p[1], p[3]]) elif p[2] == Symbols.AND.value: p[0] = PLAnd([p[1], p[3]]) else: raise ValueError # else: # p[0] = p[2] elif len(p)==3: p[0] = PLNot(p[2]) elif len(p)==2: if p[1]==Symbols.TRUE.value: p[0] = PLTrue() elif p[1]==Symbols.FALSE.value: p[0] = PLFalse() else: p[0] = PLAtomic(p[1]) else: raise ValueError
def delta_box(self, f: LDLfFormula, i: PLInterpretation, epsilon=False): if epsilon: return PLTrue() if self.pl_formula.truth(i): return PLAtomic(_expand(f)) else: return PLTrue()
def to_automaton(f) -> SymbolicDFA: # noqa: C901 """Translate to automaton.""" f = f.to_nnf() initial_state = frozenset({frozenset({PLAtomic(f)})}) states = {initial_state} final_states = set() transition_function = {} # type: Dict all_labels = f.find_labels() alphabet = powerset(all_labels) if f.delta({}, epsilon=True) == PLTrue(): final_states.add(initial_state) visited = set() # type: Set to_be_visited = {initial_state} while len(to_be_visited) != 0: for q in list(to_be_visited): to_be_visited.remove(q) for actions_set in alphabet: new_state = _make_transition( q, {label: True for label in actions_set}) if new_state not in states: states.add(new_state) to_be_visited.add(new_state) transition_function.setdefault(q, {})[actions_set] = new_state if new_state not in visited: visited.add(new_state) if _is_true(new_state): final_states.add(new_state) automaton = SymbolicAutomaton() state2idx = {} for state in states: state_idx = automaton.create_state() state2idx[state] = state_idx if state == initial_state: automaton.set_initial_state(state_idx) if state in final_states: automaton.set_accepting_state(state_idx, True) for source in transition_function: for symbol, destination in transition_function[source].items(): source_idx = state2idx[source] dest_idx = state2idx[destination] pos_expr = sympy.And(*map(sympy.Symbol, symbol)) neg_expr = sympy.And(*map(lambda x: sympy.Not(sympy.Symbol(x)), all_labels.difference(symbol))) automaton.add_transition( (source_idx, sympy.And(pos_expr, neg_expr), dest_idx)) determinized = automaton.determinize() minimized = determinized.minimize() return minimized
def find_atomics(formula: Formula) -> Set[PLAtomic]: """Finds all the atomic formulas""" res = set() if isinstance(formula, PLFormula): res = formula.find_atomics() else: res.add(PLAtomic(formula)) return res
def test_1(self): sa, sb = "A", "B" a, b = PLAtomic(sa), PLAtomic(sb) i_ = {} i_a = {"A": True} i_b = {"B": True} i_ab = {"A": True, "B": True} tr_false_a_b_ab = [i_, i_a, i_b, i_ab, i_] tt = LDLfLogicalTrue() ff = LDLfLogicalFalse() assert tt.truth(tr_false_a_b_ab, 0) assert not ff.truth(tr_false_a_b_ab, 0) assert not LDLfNot(tt).truth(tr_false_a_b_ab, 0) assert LDLfNot(ff).truth(tr_false_a_b_ab, 0) # assert LDLfAnd([LDLfPropositional(a), LDLfPropositional(b)]).truth( # tr_false_a_b_ab, 3 # ) assert not LDLfDiamond(RegExpPropositional(PLAnd([a, b])), tt).truth( tr_false_a_b_ab, 0) trace = self.trace parser = self.parser formula = "<true*;A&B>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 0) formula = "[(A+!B)*]<C>tt" parsed_formula = parser(formula) assert not parsed_formula.truth(trace, 1) formula = "<?(<!C>tt)><A>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 1) formula = "<!C+A>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 1) formula = "<!C+A>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 1)
def test_delta(): parser = LDLfParser() sa, sb, sc = "A", "B", "C" a, b, c = PLAtomic(sa), PLAtomic(sb), PLAtomic(sc) i_ = PLFalseInterpretation() i_a = PLInterpretation({sa}) i_b = PLInterpretation({sb}) i_ab = PLInterpretation({sa, sb}) true = PLTrue() false = PLFalse() tt = LDLfLogicalTrue() ff = LDLfLogicalFalse() assert parser("<A>tt").delta(i_) == false assert parser("<A>tt").delta(i_a) == PLAtomic(tt) assert parser("<A>tt").delta(i_b) == false assert parser("<A>tt").delta(i_ab) == PLAtomic(tt) assert parser("[B]ff").delta(i_) == true assert parser("[B]ff").delta(i_a) == true assert parser("[B]ff").delta(i_b) == PLAtomic(ff) assert parser("[B]ff").delta(i_ab) == PLAtomic(ff) f = parser("!(<!(A<->B)+(B;A)*+(!last)?>[(true)*]end)") assert f.delta(i_) == f.to_nnf().delta(i_) assert f.delta(i_ab) == f.to_nnf().delta(i_ab) assert f.delta(i_, epsilon=True) == f.to_nnf().delta(i_, epsilon=True) assert f.delta(i_ab, epsilon=True) == f.to_nnf().delta(i_ab, epsilon=True) # with epsilon=True, the result is either PLTrue or PLFalse assert f.delta(i_, epsilon=True) in [PLTrue(), PLFalse()]
def _make_transition(Q: FrozenSet[FrozenSet[PLAtomic]], i: PLInterpretation): actions_set = i.true_propositions new_macrostate = set() for q in Q: # delta function applied to every formula in the macro state Q delta_formulas = [f.s.delta(actions_set) for f in q] # find the list of atoms, which are "true" atoms (i.e. propositional atoms) or LDLf formulas atomics = [s for subf in delta_formulas for s in find_atomics(subf)] atom2id = {v: k for k, v in enumerate(atomics)} # type: Dict[int, PLAtomic] id2atom = {v: k for k, v in atom2id.items()} # type: Dict[PLAtomic, int] # "freeze" the found atoms as symbols and build a mapping from symbols to formulas symbol2formula = { atom2id[f] for f in atomics if f != PLTrue() and f != PLFalse() } # build a map from formula to a "freezed" propositional Atomic Formula formula2atomic_formulas = { f: PLAtomic(atom2id[f]) if f != PLTrue() and f != PLFalse() # and not isinstance(f, PLAtomic) else f for f in atomics } # the final list of Propositional Atomic Formulas, one for each formula in the original macro state Q transformed_delta_formulas = [ _transform_delta(f, formula2atomic_formulas) for f in delta_formulas ] # the empty conjunction stands for true if len(transformed_delta_formulas) == 0: conjunctions = PLTrue() elif len(transformed_delta_formulas) == 1: conjunctions = transformed_delta_formulas[0] else: conjunctions = PLAnd(transformed_delta_formulas) # the model in this case is the smallest set of symbols s.t. the conjunction of "freezed" atomic formula # is true. alphabet = frozenset(symbol2formula) models = frozenset(conjunctions.minimal_models(alphabet)) for min_model in models: q_prime = frozenset( {id2atom[s] for s in min_model.true_propositions}) new_macrostate.add(q_prime) return frozenset(new_macrostate)
def test_names(): good = [ "A", "b", "Hello", "PropZero", "Prop0", "this_is_fine_2", '"This is also allowed!"', PLParser()("A -> B"), ] bad = ["!", "&", "Invalid:", "", '"', "="] for name in good: PLAtomic(name) for name in bad: with pytest.raises(ValueError): PLAtomic(name)
def p_formula_atom(self, p): """formula : ATOM | TRUE | FALSE""" if p[1] == Symbols.TRUE.value: p[0] = PLTrue() elif p[1] == Symbols.FALSE.value: p[0] = PLFalse() else: p[0] = PLAtomic(p[1])
def test_parser(): parser = PLParser() sa, sb = "A", "B" a, b = PLAtomic(sa), PLAtomic(sb) a_and_b = parser("A & B") true_a_and_b = PLAnd([a, b]) assert a_and_b == true_a_and_b material_implication = parser("!A | B <-> !(A & !B) <-> A->B") true_material_implication = PLEquivalence( [PLOr([PLNot(a), b]), PLNot(PLAnd([a, PLNot(b)])), PLImplies([a, b])] ) assert material_implication == true_material_implication true_a_and_false_and_true = PLAnd([a, PLFalse(), PLTrue()]) a_and_false_and_true = parser("A & false & true") assert a_and_false_and_true == true_a_and_false_and_true
def delta_box(self, f: LDLfFormula, i: PropositionalInterpretation, epsilon=False): """Apply delta function for regular expressions in the box operator.""" if epsilon: return PLTrue() if self.pl_formula.truth(i): return PLAtomic(_expand(f)) else: return PLTrue()
def test_truth(): sa, sb = "a", "b" a, b = PLAtomic(sa), PLAtomic(sb) i_ = PLFalseInterpretation() i_a = PLInterpretation({sa}) i_b = PLInterpretation({sb}) i_ab = PLInterpretation({sa, sb}) tr_false_a_b_ab = FiniteTrace([i_, i_a, i_b, i_ab, i_]) tt = LDLfLogicalTrue() ff = LDLfLogicalFalse() assert tt.truth(tr_false_a_b_ab, 0) assert not ff.truth(tr_false_a_b_ab, 0) assert not LDLfNot(tt).truth(tr_false_a_b_ab, 0) assert LDLfNot(ff).truth(tr_false_a_b_ab, 0) assert LDLfAnd([LDLfPropositional(a), LDLfPropositional(b)]).truth(tr_false_a_b_ab, 3) assert not LDLfDiamond(RegExpPropositional(PLAnd([a, b])), tt).truth( tr_false_a_b_ab, 0) parser = LDLfParser() trace = FiniteTrace.from_symbol_sets([{}, {"A"}, {"A"}, {"A", "B"}, {}]) formula = "<true*;A&B>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 0) formula = "[(A+!B)*]<C>tt" parsed_formula = parser(formula) assert not parsed_formula.truth(trace, 1) formula = "<(<!C>tt)?><A>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 1) formula = "<!C+A>tt" parsed_formula = parser(formula) assert parsed_formula.truth(trace, 1)
def test_next(self): parser = self.parser i_, i_a, i_b, i_ab = self.i_, self.i_a, self.i_b, self.i_ab true = self.true false = self.false assert parser("X A").delta(i_) == PLAnd( [PLAtomic(LTLfAtomic("A")), PLAtomic(LTLfEventually(LTLfTrue()).to_nnf())]) assert parser("X A").delta(i_a) == PLAnd( [PLAtomic(LTLfAtomic("A")), PLAtomic(LTLfEventually(LTLfTrue()).to_nnf())]) assert parser("X A").delta(i_b) == PLAnd( [PLAtomic(LTLfAtomic("A")), PLAtomic(LTLfEventually(LTLfTrue()).to_nnf())]) assert parser("X A").delta(i_ab) == PLAnd( [PLAtomic(LTLfAtomic("A")), PLAtomic(LTLfEventually(LTLfTrue()).to_nnf())]) assert parser("X A").delta(i_, epsilon=True) == false
def to_automaton(f, labels: Optional[Set[Symbol]] = None): initial_state = frozenset({frozenset({PLAtomic(f)})}) states = {initial_state} final_states = set() transition_function = {} # the alphabet is the powerset of the set of fluents alphabet = powerset(labels if labels is not None else f.find_labels()) if f.delta(PLFalseInterpretation(), epsilon=True) == PLTrue(): final_states.add(initial_state) visited = set() to_be_visited = {initial_state} while len(to_be_visited) != 0: for q in list(to_be_visited): to_be_visited.remove(q) for actions_set in alphabet: new_state = _make_transition(q, PLInterpretation(actions_set)) if new_state not in states: states.add(new_state) to_be_visited.add(new_state) transition_function.setdefault( q, {})[PLInterpretation(actions_set)] = new_state if new_state not in visited: visited.add(new_state) if _is_true(new_state): final_states.add(new_state) new_alphabet = {PLInterpretation(set(sym)) for sym in alphabet} dfa = DFA(states, new_alphabet, initial_state, final_states, transition_function) dfa = dfa.minimize() dfa = dfa.trim() return dfa
def _make_transition(marco_q: FrozenSet[FrozenSet[PLAtomic]], i: PropositionalInterpretation): new_macrostate = set() for q in marco_q: # delta function applied to every formula in the macro state Q delta_formulas = [cast(Delta, f.s).delta(i) for f in q] # find atomics -> so also ldlf formulas # replace atomic with custom object # convert to sympy # find the list of atoms, which are "true" atoms # (i.e. propositional atoms) or LDLf formulas atomics = [s for subf in delta_formulas for s in find_atomics(subf)] atom2id = {v: str(k) for k, v in enumerate(atomics)} # type: Dict[PLAtomic, str] id2atom = {v: k for k, v in atom2id.items()} # type: Dict[str, PLAtomic] # build a map from formula to a "freezed" propositional Atomic Formula formula2atomic_formulas = { f: PLAtomic(atom2id[f]) if f != PLTrue() and f != PLFalse() # and not isinstance(f, PLAtomic) else f for f in atomics } # the final list of Propositional Atomic Formulas, # one for each formula in the original macro state Q transformed_delta_formulas = [ _transform_delta(f, formula2atomic_formulas) for f in delta_formulas ] # the empty conjunction stands for true if len(transformed_delta_formulas) == 0: conjunctions = PLTrue() elif len(transformed_delta_formulas) == 1: conjunctions = transformed_delta_formulas[0] else: conjunctions = PLAnd(transformed_delta_formulas) # type: ignore # the model in this case is the smallest set of symbols # s.t. the conjunction of "freezed" atomic formula is true. # alphabet = frozenset(symbol2formula) # models = frozenset(conjunctions.minimal_models(alphabet)) formula = to_sympy(conjunctions, replace=atom2id) # type: ignore all_models = list(sympy.satisfiable(formula, all_models=True)) if len(all_models) == 1 and all_models[0] == BooleanFalse(): models = [] # type: List[Set[str]] elif len(all_models) == 1 and all_models[0] == {True: True}: models = [set()] else: models = list( map(lambda x: {k for k, v in x.items() if v is True}, all_models)) for min_model in models: q_prime = frozenset({id2atom[s] for s in map(str, min_model)}) new_macrostate.add(q_prime) return frozenset(new_macrostate)
def to_LDLf(self): return LDLfPropositional(PLAtomic(self.s)).convert()
def find_labels(self): return PLAtomic(self.s).find_labels()
def truth(self, i: FiniteTrace, pos: int = 0): return PLAtomic(self.s).truth(i.get(pos))
def _delta(self, i: PLInterpretation, epsilon: bool = False): if epsilon: return PLFalse() return PLTrue() if PLAtomic(self.s).truth(i) else PLFalse()