def test_releases(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x V y)", str(x_.releases(y_)))
def test_magicmethod_or(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x | y)", str(x_ | y_))
def test_triggered(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x T y)", str(x_.triggered(y_)))
def test_iff(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x <-> y)", str(x_.iff(y_)))
def test_depth(self): # raw symbol has no depth x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) # propositional connectives do not increase the depth self.assertEqual(0, (x_ & y_).depth) self.assertEqual(0, (x_ | y_).depth) self.assertEqual(0, (- x_).depth) self.assertEqual(0, (x_.implies(y_)).to_negation_normal_form().depth) self.assertEqual(0, (x_.iff(y_)).to_negation_normal_form().depth) # temporal operators do increase the depth self.assertEqual(42, x_.next_times(42).depth) # 42 times X ( .. X(x)) self.assertEqual( 1, x_.opnext().depth) # X x self.assertEqual( 1, x_.opprec().depth) # Y x self.assertEqual( 1, x_.opnotprecnot().depth) # Z x self.assertEqual( 1, x_.globally().depth) # G x self.assertEqual( 1, x_.historically().depth) # H x self.assertEqual( 1, x_.eventually().depth) # F x self.assertEqual( 1, x_.once().depth) # O x self.assertEqual( 1, x_.until(y_).depth) # x U y self.assertEqual( 1, x_.since(y_).depth) # x S y self.assertEqual( 1, x_.releases(y_).depth) # x V y self.assertEqual( 1, x_.triggered(y_).depth) # x T y
def test_and_(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x & y)", str(x_.and_(y_)))
def test_until_with_loop(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("(a U b)") # bound 1 offset = 0 bound = 1 loop = 0 # remember: the NuSMV std apis incorporate the loop condition ! ref_expr= ltlspec.bounded_semantics_single_loop(fsm, formula, bound, loop) expr = ltlspec.bounded_semantics_with_loop_at_offset(fsm, formula, 0, bound, loop, offset) \ & bmcutils.loop_condition(self.enc, bound, loop) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # ---- other offset ---- # bound 2 offset = 1 bound = 2 loop = 0 car = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) cdr = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) # because of the way the loop condition is encoded ! ref_expr= self.enc.shift_to_time(cdr, offset) | (self.enc.shift_to_time(car, offset) \ & self.enc.shift_to_time(cdr, offset+1)) expr = ltlspec.bounded_semantics_with_loop_at_offset(fsm, formula, 0, bound, loop, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # because loop < i, the condition must be the same as before expr = ltlspec.bounded_semantics_with_loop_at_offset(fsm, formula, 1, bound, loop, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_constraint_context_sigma(self): fsm = master_be_fsm() _true = Node.from_ptr(parse_ltl_spec("TRUE")) _true = bmcutils.make_nnf_boolean_wff(_true) _truen= _true.to_node() cond = Wff(parse_ltl_spec("G !(mouse = hover)"))\ .to_boolean_wff()\ .to_negation_normal_form() off_1 = 0 off_2 = 2 length= 1 # sigma1 problem = diagnosability.generate_sat_problem([], (_truen, _truen), length, _true, cond.to_node(), _truen) tm_cond = ltlspec.bounded_semantics_at_offset(fsm, cond.to_node(), length, off_1) canonical_p = tests.canonical_cnf(problem) canonical_f = tests.canonical_cnf(tm_cond) self.assertTrue(all(clause in canonical_p for clause in canonical_f)) # sigma2 problem = diagnosability.generate_sat_problem([], (_truen, _truen), length, _true, _truen, cond.to_node()) tm_cond = ltlspec.bounded_semantics_at_offset(fsm, cond.to_node(), length, off_2) canonical_p = tests.canonical_cnf(problem) canonical_f = tests.canonical_cnf(tm_cond) self.assertTrue(all(clause in canonical_p for clause in canonical_f))
def test_since(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x S y)", str(x_.since(y_)))
def test_until(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x U y)", str(x_.until(y_)))
def test_constraint_context_theta(self): enc = master_be_fsm().encoding cond = Wff(parse_simple_expression("mouse = down")).to_boolean_wff() theta = diagnosability.constraint_context_theta_initial(cond, 0, 1) manual= enc.shift_to_time(cond.to_be(enc), 0) \ & enc.shift_to_time(cond.to_be(enc), 1) \ self.assertEqual(theta, manual)
def test_to_negation_normal_form(self): x = self.enc.by_name["x"] y = self.enc.by_name["y"] x_ = Wff.decorate(x.name) y_ = Wff.decorate(y.name) self.assertEqual("(x -> y)", str(x_.implies(y_))) self.assertEqual("(!x | y)", str(x_.implies(y_).to_negation_normal_form())) self.assertEqual("(x <-> y)", str(x_.iff(y_))) self.assertEqual("((!x | y) & (x | !y))", str(x_.iff(y_).to_negation_normal_form()))
def test_decorate(self): for prop in glob.prop_database(): # can decorate a property dec = Wff.decorate(prop.exprcore) self.assertEqual(str(dec), str(prop.exprcore)) # can decorate a plain node x = self.enc.by_name["x"] _x = Wff.decorate(x.name) self.assertEqual("x", str(_x)) # combination possible between plain nodes and specs self.assertEqual(str(dec | _x), '('+str(prop.exprcore)+" | x)")
def test_next_no_loop(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("X (a <-> !b)") # bound 0 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 0) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 0, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 1) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # ---- other offset ---- # bound 0 offset = 1 ref_expr= Be.false(fsm.encoding.manager) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 0, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 1 ref_expr= Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) ref_expr= fsm.encoding.shift_to_time(ref_expr, offset+1) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_bounded_semantics_with_loop_optimized_depth1(self): spec = Node.from_ptr(parse_ltl_spec("G ( y <= 7 )")) # depth == 1 # it must raise exception when the bound is not feasible with self.assertRaises(ValueError): ltlspec.bounded_semantics_all_loops_optimisation_depth1( self.fsm, spec, -1) # should yield the same result (w/ opt) as regular all loops when depth is one optimized = ltlspec.bounded_semantics_all_loops_optimisation_depth1( self.fsm, spec, 5) regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0) self.assertEqual(regular, optimized) # but not when the optim is turned off on 'all loops' regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0, optimized=False) self.assertNotEqual(regular, optimized) # and it should only be applied when the depth is equal to one spec = Node.from_ptr(parse_ltl_spec("F G ( y <= 7 )")) # depth == 2 self.assertEqual(2, Wff.decorate(spec).depth) optimized = ltlspec.bounded_semantics_all_loops_optimisation_depth1( self.fsm, spec, 5) regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0) self.assertNotEqual(regular, optimized)
def make_nnf_boolean_wff(prop_node): """ Decorates the property identified by `prop_node` to become a boolean WFF, and converts the resulting formula to negation normal form. (negation sign on literals only). """ return Wff.decorate(prop_node).\ to_boolean_wff().\ to_negation_normal_form()
def nnf(self, text): """ Utility function to convert text into an equivalent Node form in NNF :return: an NNF node version of the text """ return Wff(parse_ltl_spec(text)).to_boolean_wff()\ .to_negation_normal_form()\ .to_node()
def test_until(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("(a U !b)") # bound 0 offset = 0 bound = 0 ref_expr= ltlspec.bounded_semantics(fsm, formula, bound) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 0 bound = 1 ref_expr= ltlspec.bounded_semantics(fsm, formula, bound) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # ---- other offset ---- # bound 0 offset = 2 bound = 0 cdr = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) ref_expr= self.enc.shift_to_time(cdr, 0+offset) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 2 bound = 1 car = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) cdr = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) ref_expr= self.enc.shift_to_time(cdr, 0+offset) \ | (self.enc.shift_to_time(car, 0+offset) & self.enc.shift_to_time(cdr, 1+offset)) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_releases_no_loop(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("(a V b)") # bound 0 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 0) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 0, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 1) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 2 -- verification must be done by hand because the different # cnf literals mess the comparison ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 2) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 2, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # ---- other offset ---- # bound 0 offset = 1 bound = 0 left = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) right = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) ref_expr= self.enc.shift_to_time(right, offset) & self.enc.shift_to_time(left, offset) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 1 bound = 1 left = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) right = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) ref_expr= self.enc.shift_to_time(right, offset) & ( self.enc.shift_to_time(left, offset) \ | (self.enc.shift_to_time(right, 1+offset) & self.enc.shift_to_time(left, 1+offset))) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_until_no_loop(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("(a U b)") # bound 0 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 0) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 0, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 1) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, 0) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 2 -- verification must be done by hand because the different # cnf literals mess the comparison # ref_expr= ltlspec.bounded_semantics_without_loop(fsm, formula, 2) # expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 2, 0) # self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # ---- other offset ---- # bound 0 offset = 1 ref_expr= Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) ref_expr= fsm.encoding.shift_to_time(ref_expr, offset) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 0, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 1 cdr = Wff.decorate(ltlspec.cdr(formula)).to_be(fsm.encoding) car = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) ref_expr= fsm.encoding.shift_to_time(cdr, offset) \ | ( fsm.encoding.shift_to_time(car, offset) \ & fsm.encoding.shift_to_time(cdr, offset+1)) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_bounded_semantics_with_loop_optimized_depth1(self): spec = Node.from_ptr(parse_ltl_spec("G ( y <= 7 )")) # depth == 1 # it must raise exception when the bound is not feasible with self.assertRaises(ValueError): ltlspec.bounded_semantics_all_loops_optimisation_depth1(self.fsm, spec, -1) # should yield the same result (w/ opt) as regular all loops when depth is one optimized = ltlspec.bounded_semantics_all_loops_optimisation_depth1(self.fsm, spec, 5) regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0) self.assertEqual(regular, optimized) # but not when the optim is turned off on 'all loops' regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0, optimized=False) self.assertNotEqual(regular, optimized) # and it should only be applied when the depth is equal to one spec = Node.from_ptr(parse_ltl_spec("F G ( y <= 7 )")) # depth == 2 self.assertEqual(2, Wff.decorate(spec).depth) optimized = ltlspec.bounded_semantics_all_loops_optimisation_depth1(self.fsm, spec, 5) regular = ltlspec.bounded_semantics_all_loops(self.fsm, spec, bound=5, loop=0) self.assertNotEqual(regular, optimized)
def _booleanize(self): """ Returns a boolean expression (Be) corresponding to this simple boolean expression (text). .. note:: Albeit feasible, working directly with variables as offered by the encoding is a little bit limiting as it de facto rejects any symbol which is not a variable. As a consequence, the DEFINES, or arithmetic expressions are not usable. The use of this function palliates that limitation and makes the use of any simple boolean expression possible. :return: a be expression (Be) corresponding to `self` """ if not self._booleanized: befsm = master_be_fsm() node = parse_simple_expression(self.id) self._booleanized = Wff(node).to_boolean_wff().to_be( befsm.encoding) return self._booleanized
def test_next(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("X (a <-> !b)") # bound 0 offset = 0 bound = 0 ref_expr= ltlspec.bounded_semantics(fsm, formula, bound) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr)) # bound 1 offset = 0 bound = 1 # done this way to avoid the depth 1 optimisation car = Wff.decorate(ltlspec.car(formula)).to_be(fsm.encoding) ref_expr= self.enc.shift_to_time(car, offset+1) \ | (self.enc.shift_to_time(car, offset) & bmcutils.loop_condition(self.enc, bound, 0)) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset)
def test_bounded_semantics_without_loop(self): # parse the ltl property spec = Node.from_ptr(parse_ltl_spec("G ( y <= 7 )")) # it must raise exception when the bound is not feasible with self.assertRaises(ValueError): ltlspec.bounded_semantics_without_loop(self.fsm, spec, bound=-1) # verify that the generated expression corresponds to what is announced no_loop = ltlspec.bounded_semantics_without_loop(self.fsm, spec, 10) # globally w/o loop is false (this is just a test) self.assertEqual(no_loop, Be.false(self.fsm.encoding.manager)) # an other more complex generation spec = Node.from_ptr(parse_ltl_spec("F (y <= 7)")) no_loop = ltlspec.bounded_semantics_without_loop(self.fsm, spec, 10) # # The generated expression is [[f]]^{0}_{k} so (! L_{k}) is not taken # care of. And actually, NuSMV does not generate that part of the # formula: it only enforce the loop condition when the semantics with # loop is used # handcrafted = Be.false(self.fsm.encoding.manager) y_le_seven = Wff(parse_ltl_spec("y <= 7")).to_boolean_wff().to_be( self.fsm.encoding) for time_x in reversed( range(11)): # 11 because range 'eats' up the last step handcrafted |= self.fsm.encoding.shift_to_time(y_le_seven, time_x) #### debuging info ##### #print("noloop = {}".format(no_loop.to_cnf())) #print("hancraft= {}".format(handcrafted.to_cnf())) #print(self.fsm.encoding) self.assertEqual(no_loop, handcrafted)
def test_constraint_context_sigma(self): fsm = master_be_fsm() _true = Node.from_ptr(parse_ltl_spec("TRUE")) _true = bmcutils.make_nnf_boolean_wff(_true) _truen = _true.to_node() cond = Wff(parse_ltl_spec("G !(mouse = hover)"))\ .to_boolean_wff()\ .to_negation_normal_form() off_1 = 0 off_2 = 2 length = 1 # sigma1 problem = diagnosability.generate_sat_problem([], (_truen, _truen), length, _true, cond.to_node(), _truen) tm_cond = ltlspec.bounded_semantics_at_offset(fsm, cond.to_node(), length, off_1) canonical_p = tests.canonical_cnf(problem) canonical_f = tests.canonical_cnf(tm_cond) self.assertTrue(all(clause in canonical_p for clause in canonical_f)) # sigma2 problem = diagnosability.generate_sat_problem([], (_truen, _truen), length, _true, _truen, cond.to_node()) tm_cond = ltlspec.bounded_semantics_at_offset(fsm, cond.to_node(), length, off_2) canonical_p = tests.canonical_cnf(problem) canonical_f = tests.canonical_cnf(tm_cond) self.assertTrue(all(clause in canonical_p for clause in canonical_f))
def test_once(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(" O x", str(x_.once()))
def test_eventually(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(" F x", str(x_.eventually()))
def test_to_node(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(x.name, x_.to_node())
def test_to_boolean_wff(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name).to_boolean_wff(glob.bdd_encoding()) # TODO: find something better to validate this self.assertIsNotNone(x_)
def test_globally(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(" G x", str(x_.globally()))
def test_historically(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(" H x", str(x_.historically()))
def test_next_times(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual("x", str(x_.next_times(0))) self.assertEqual(" X x", str(x_.next_times(1))) self.assertEqual(" X ( X ( X x))", str(x_.next_times(3)))
def test_false(self): self.assertEqual("FALSE", str(Wff.false())) self.assertEqual(0, Wff.false().depth)
def test_true(self): self.assertEqual("TRUE", str(Wff.true())) self.assertEqual(0, Wff.true().depth)
def test_magicmethod_invert(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual("!x", str(~x_))
def test_opnotprecnot(self): x = self.enc.by_name["x"] x_ = Wff.decorate(x.name) self.assertEqual(" Z x", str(x_.opnotprecnot()))