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_false(self): with self.assertRaises(Exception): Be.false(None) expr = Be.false(self._manager) self.assertFalse(expr.is_true()) self.assertTrue(expr.is_false()) self.assertTrue(expr.is_constant())
def test_false(self): with self.assertRaises(Exception): Be.false(None) expr = Be.false(self._manager) self.assertFalse(expr.is_true()) self.assertTrue(expr.is_false()) self.assertTrue(expr.is_constant())
def test_globally_no_loop(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("G (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= Be.false(fsm.encoding.manager) expr = ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, 1, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
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 bounded_semantics(self, fsm, k, fairness=True): """ Returns a boolean expression corresponding to the bounded semantics of the formula denoted by `self` on a path of length k. This combines both the semantics in case of a loopy path and the case of a non-loopy path. .. note:: This function takes the same approach as NuSMV and does not enforce the absence of loop when using the more restrictive semantic_no_loop. :param fsm: the FSM representing the model. It is used to gain access to the encoder (-> shift variables) and to obtain the list of fairness constraints. :param k: the last time that exists in the universe of this expression :param fairness: a flag indicating whether or not the fairness constraints should be taken into account while generating the formula. :return: a boolean expression translating the bounded semantics of this formula. """ enc = fsm.encoding noloop = self.semantic_no_loop(enc, 0, k) w_loop = Be.false(enc.manager) for l in range(k): # [0; k-1] fairness_cond = fairness_constraint(fsm, k, l) \ if fairness \ else Be.true(enc.manager) w_loop |= (loop_condition(enc, k, l) \ & fairness_cond \ & self.semantic_with_loop(enc, 0, k, l)) return noloop | w_loop
def test_constant_with_loop(self): with tests.Configure(self, __file__, "/example.smv"): expr = ast.Constant("TRUE") self.assertEqual(Be.true(self.mgr), expr.semantic_with_loop(self.enc, 0, 5, 2)) expr = ast.Constant("FALSE") self.assertEqual(Be.false(self.mgr), expr.semantic_with_loop(self.enc, 0, 5, 2))
def test_bounded_semantics_with_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_single_loop(self.fsm, spec, -1, -2) # it must raise exception when the bound and loop are not consistent with self.assertRaises(ValueError): ltlspec.bounded_semantics_single_loop(self.fsm, spec, 5, 6) # verify that the generated problem corresponds to what is announced # without optimisation, the all loops is built as the conjunction of all # the possible 'single_loops' all_loops = ltlspec.bounded_semantics_all_loops(self.fsm, spec, 10, 0, optimized=False) acc_loops = Be.false(self.fsm.encoding.manager) for time_t in range(10): acc_loops |= ltlspec.bounded_semantics_single_loop( self.fsm, spec, 10, time_t) self.assertEqual(acc_loops, all_loops) # with optimisation, it's different all_loops = ltlspec.bounded_semantics_all_loops(self.fsm, spec, 10, 0, optimized=True) self.assertNotEqual(acc_loops, all_loops)
def bounded_semantics(self, fsm, k, fairness=True): """ Returns a boolean expression corresponding to the bounded semantics of the formula denoted by `self` on a path of length k. This combines both the semantics in case of a loopy path and the case of a non-loopy path. .. note:: This function takes the same approach as NuSMV and does not enforce the absence of loop when using the more restrictive semantic_no_loop. :param fsm: the FSM representing the model. It is used to gain access to the encoder (-> shift variables) and to obtain the list of fairness constraints. :param k: the last time that exists in the universe of this expression :param fairness: a flag indicating whether or not the fairness constraints should be taken into account while generating the formula. :return: a boolean expression translating the bounded semantics of this formula. """ enc = fsm.encoding noloop = self.semantic_no_loop(enc, 0, k) w_loop = Be.false(enc.manager) for l in range(k): # [0; k-1] fairness_cond = fairness_constraint(fsm, k, l) if fairness else Be.true(enc.manager) w_loop |= loop_condition(enc, k, l) & fairness_cond & self.semantic_with_loop(enc, 0, k, l) return noloop | w_loop
def test_and(self): # using the function true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse(true.and_(false).is_true()) self.assertTrue(true.and_(false).is_false()) self.assertTrue(true.and_(false).is_constant())
def _semantic(time, cnt): """auxiliary function to stop recursing after k steps""" # at infinity, it is false: psi MUST happen at some time if cnt == k: return Be.false(enc.manager) psi = self.rhs.semantic_with_loop(enc, time, k, l) phi = self.lhs.semantic_with_loop(enc, time, k, l) return psi | (phi & _semantic(successor(time, k, l), cnt + 1))
def test_sub_(self): # and not true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true - false).is_true()) self.assertFalse((true - false).is_false()) self.assertTrue((true - false).is_constant())
def test_add_(self): # using the algebraic notation true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true + false).is_true()) self.assertFalse((true + false).is_false()) self.assertTrue((true + false).is_constant())
def test__and_(self): # using the keyword true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse((true and false).is_true()) self.assertTrue((true and false).is_false()) self.assertTrue((true and false).is_constant())
def test_sub_(self): # and not true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true - false).is_true()) self.assertFalse((true - false).is_false()) self.assertTrue((true - false).is_constant())
def test_and(self): # using the function true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse(true.and_(false).is_true()) self.assertTrue(true.and_(false).is_false()) self.assertTrue(true.and_(false).is_constant())
def _semantic(time, cnt): """auxiliary function to stop recursing after k steps""" # at infinity, it is false: psi MUST happen at some time if cnt == k: return Be.false(enc.manager) psi = self.rhs.semantic_with_loop(enc, time, k, l) phi = self.lhs.semantic_with_loop(enc, time, k, l) return psi | (phi & _semantic(successor(time, k, l), cnt + 1))
def test__and_(self): # using the keyword true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse((true and false).is_true()) self.assertTrue((true and false).is_false()) self.assertTrue((true and false).is_constant())
def test_add_(self): # using the algebraic notation true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true + false).is_true()) self.assertFalse((true + false).is_false()) self.assertTrue((true + false).is_constant())
def semantic_no_loop(self, enc, i, k): """The semantics when there is no loop:: [[lhs W rhs]]_{bound}^{time}""" # k is not infinity when there is no loop if i > k: return Be.false(enc.manager) psi = self.rhs.semantic_no_loop(enc, i, k) phi = self.lhs.semantic_no_loop(enc, i, k) return psi | (phi & self.semantic_no_loop(enc, i + 1, k))
def semantic_no_loop(self, enc, i, k): """The semantics when there is no loop:: [[lhs W rhs]]_{bound}^{time}""" # k is not infinity when there is no loop if i > k: return Be.false(enc.manager) psi = self.rhs.semantic_no_loop(enc, i, k) phi = self.lhs.semantic_no_loop(enc, i, k) return psi | (phi & self.semantic_no_loop(enc, i + 1, k))
def test_constant_with_loop(self): with tests.Configure(self, __file__, "/example.smv"): expr = ast.Constant("TRUE") self.assertEqual(Be.true(self.mgr), expr.semantic_with_loop(self.enc, 0, 5, 2)) expr = ast.Constant("FALSE") self.assertEqual(Be.false(self.mgr), expr.semantic_with_loop(self.enc, 0, 5, 2))
def test_globally_no_loop(self): with tests.Configure(self, __file__, "/example.smv"): i,k = 0,2 enc = self.enc mgr = enc.manager a = ast.Proposition("a") formula = ast.Globally(a) tool = formula.semantic_no_loop(enc, i, k) self.assertEqual(Be.false(mgr), tool)
def test_globally_no_loop(self): with tests.Configure(self, __file__, "/example.smv"): i, k = 0, 2 enc = self.enc mgr = enc.manager a = ast.Proposition("a") formula = ast.Globally(a) tool = formula.semantic_no_loop(enc, i, k) self.assertEqual(Be.false(mgr), tool)
def test_next_noloop(self): with tests.Configure(self, __file__, "/example.smv"): i, k = 0, 2 enc = self.enc # One step a = ast.Proposition("a") formula = ast.Next(a) tool = formula.semantic_no_loop(enc, i, k) manual = a.semantic_no_loop(enc, 1, k) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr( parse_ltl_spec("X a")), bound=k, loop=bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual) # two steps formula = ast.Next(ast.Next(a)) tool = formula.semantic_no_loop(enc, i, k) manual = a.semantic_no_loop(enc, 2, k) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr( parse_ltl_spec("X X a")), bound=k, loop=bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual) # Three steps (getting over k) formula = ast.Next(ast.Next(ast.Next(a))) tool = formula.semantic_no_loop(enc, i, k) manual = Be.false(enc.manager) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr( parse_ltl_spec("X X X a")), bound=k, loop=bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual)
def bounded_semantics_at_offset(fsm, formula, bound, offset, fairness=True): """ Generates the Be :math:`[[formula]]_{bound}` corresponding to the bounded semantic of `formula` but encodes it with an `offset` long shift in the timeline of the encoder. .. note:: This function plays the same role as `bounded_semantics_all_loops` but allows to position the time blocks at some place we like in the encoder timeline. This is mostly helpful if you want to devise verification methods that need to have multiple parallel verifications. (ie. diagnosability). Note however, that the two implementations are different. .. warning:: So far, the only supported temporal operators are F, G, U, R, X :param fsm: the BeFsm for which the property will be verified. Actually, it is only used to provide the encoder used to assign the variables to some time blocks. The api was kept this ways to keep uniformity with its non-offsetted counterpart. :param formula: the property for which to generate a verification problem represented in a 'node' format (subclass of :class:`pynusmv.node.Node`) which corresponds to the format obtained from the ast. (remark: if you need to manipulate [ie negate] the formula before passing it, it is perfectly valid to pass a node decorated by `Wff.decorate`). :param bound: the logical time bound to the problem. (Leave out the offset for this param: if you intend to have a problem with at most 10 steps, say bound=10) :param offset: the time offset in the encoding block where the sem of this formula will be generated. :param fairness: a flag indicating whether or not to take the fairness constraint into account. :return: a Be corresponding to the semantics of `formula` for a problem with a maximum of `bound` steps encoded to start at time `offset` in the `fsm` encoding timeline. """ if bound < 0: raise ValueError("Bound must be a positive integer") if offset < 0: raise ValueError("The offset must be a positive integer") enc = fsm.encoding straight = bounded_semantics_without_loop_at_offset( fsm, formula, 0, bound, offset) k_loop = Be.false(enc.manager) for i in range(bound): fairness_cond = utils.fairness_constraint(fsm, offset+bound, offset+i) \ if fairness \ else Be.true(enc.manager) k_loop |= ( utils.loop_condition(enc, offset+bound, offset+i) \ & fairness_cond \ & bounded_semantics_with_loop_at_offset(fsm, formula, 0, bound, i, offset)) # this is just the sem of the formula return straight | k_loop
def bounded_semantics_at_offset(fsm, formula, bound, offset, fairness=True): """ Generates the Be [[formula]]_{bound} corresponding to the bounded semantic of `formula` but encodes it with an `offset` long shift in the timeline of the encoder. .. note:: This function plays the same role as `bounded_semantics_all_loops` but allows to position the time blocks at some place we like in the encoder timeline. This is mostly helpful if you want to devise verification methods that need to have multiple parallel verifications. (ie. diagnosability). Note however, that the two implementations are different. .. warning:: So far, the only supported temporal operators are F, G, U, R, X :param fsm: the BeFsm for which the property will be verified. Actually, it is only used to provide the encoder used to assign the variables to some time blocks. The api was kept this ways to keep uniformity with its non-offsetted counterpart. :param formula: the property for which to generate a verification problem represented in a 'node' format (subclass of :see::class:`pynusmv.node.Node`) which corresponds to the format obtained from the ast. (remark: if you need to manipulate [ie negate] the formula before passing it, it is perfectly valid to pass a node decorated by `Wff.decorate`). :param bound: the logical time bound to the problem. (Leave out the offset for this param: if you intend to have a problem with at most 10 steps, say bound=10) :param offset: the time offset in the encoding block where the sem of this formula will be generated. :param fairness: a flag indicating whether or not to take the fairness constraint into account. :return: a Be corresponding to the semantics of `formula` for a problem with a maximum of `bound` steps encoded to start at time `offset` in the `fsm` encoding timeline. """ if bound< 0: raise ValueError("Bound must be a positive integer") if offset<0: raise ValueError("The offset must be a positive integer") enc = fsm.encoding straight = bounded_semantics_without_loop_at_offset(fsm, formula, 0, bound, offset) k_loop = Be.false(enc.manager) for i in range(bound): fairness_cond = utils.fairness_constraint(fsm, offset+bound, offset+i) \ if fairness \ else Be.true(enc.manager) k_loop |= ( utils.loop_condition(enc, offset+bound, offset+i) \ & fairness_cond \ & bounded_semantics_with_loop_at_offset(fsm, formula, 0, bound, i, offset)) # this is just the sem of the formula return straight | k_loop
def test__mul_(self): # using algebraic notation true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse((true * false).is_true()) self.assertTrue((true * false).is_false()) self.assertTrue((true * false).is_constant()) self.assertFalse((true & false).is_true()) self.assertTrue((true & false).is_false()) self.assertTrue((true & false).is_constant())
def semantic_with_loop(self, enc, i, k, l): # without moving at least one step, it is impossible to go through a loop if k == 0: return Be.false(enc.manager) def _semantic(time, cnt): if cnt == k: return Be.false(enc.manager) now = self.prop.semantic_with_loop(enc, time, k, l) return now | _semantic(successor(time, k, l), cnt + 1) return _semantic(i, 0)
def test__mul_(self): # using algebraic notation true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse((true * false).is_true()) self.assertTrue((true * false).is_false()) self.assertTrue((true * false).is_constant()) self.assertFalse((true & false).is_true()) self.assertTrue((true & false).is_false()) self.assertTrue((true & false).is_constant())
def semantic_with_loop(self, enc, i, k, l): # without moving at least one step, it is impossible to go through a loop if k == 0: return Be.false(enc.manager) def _semantic(time, cnt): if cnt == k: return Be.false(enc.manager) now = self.prop.semantic_with_loop(enc, time, k, l) return now | _semantic(successor(time, k, l), cnt + 1) return _semantic(i, 0)
def test_next_noloop(self): with tests.Configure(self, __file__, "/example.smv"): i,k = 0,2 enc = self.enc # One step a = ast.Proposition("a") formula = ast.Next(a) tool = formula.semantic_no_loop(enc, i, k) manual = a.semantic_no_loop(enc, 1, k) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr(parse_ltl_spec("X a")), bound = k, loop = bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual) # two steps formula = ast.Next(ast.Next(a)) tool = formula.semantic_no_loop(enc, i, k) manual = a.semantic_no_loop(enc, 2, k) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr(parse_ltl_spec("X X a")), bound = k, loop = bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual) # Three steps (getting over k) formula = ast.Next(ast.Next(ast.Next(a))) tool = formula.semantic_no_loop(enc, i, k) manual = Be.false(enc.manager) nusmv = ltlspec.bounded_semantics(self.befsm, Node.from_ptr(parse_ltl_spec("X X X a")), bound = k, loop = bmcutils.no_loopback()) s_tool = tests.canonical_cnf(tool) s_manual = tests.canonical_cnf(manual) s_nusmv = tests.canonical_cnf(nusmv) self.assertEqual(s_tool, s_nusmv) self.assertEqual(s_tool, s_manual)
def test_imply(self): true = Be.true(self._manager) false = Be.false(self._manager) # antecedent always true self.assertFalse(true.imply(false).is_true()) self.assertTrue( true.imply(false).is_false()) self.assertTrue( true.imply(false).is_constant()) # antecedent always false self.assertTrue( false.imply(true).is_true()) self.assertFalse(false.imply(true).is_false()) self.assertTrue( false.imply(true).is_constant())
def test_imply(self): true = Be.true(self._manager) false = Be.false(self._manager) # antecedent always true self.assertFalse(true.imply(false).is_true()) self.assertTrue(true.imply(false).is_false()) self.assertTrue(true.imply(false).is_constant()) # antecedent always false self.assertTrue(false.imply(true).is_true()) self.assertFalse(false.imply(true).is_false()) self.assertTrue(false.imply(true).is_constant())
def test_clauses_number(self): # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false self.assertEqual(0, TF.to_cnf(Polarity.NOT_SET).clauses_number) self.assertEqual(0, TF.to_cnf(Polarity.POSITIVE).clauses_number) self.assertEqual(0, TF.to_cnf(Polarity.NEGATIVE).clauses_number) # with variables v = self._fsm.encoding.by_name['v'].boolean_expression self.assertEqual(2, v.to_cnf(Polarity.NOT_SET).clauses_number) self.assertEqual(1, v.to_cnf(Polarity.POSITIVE).clauses_number) self.assertEqual(1, v.to_cnf(Polarity.NEGATIVE).clauses_number)
def test_original_problem(self): # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false self.assertEqual(TF, TF.to_cnf(Polarity.NOT_SET).original_problem) self.assertEqual(TF, TF.to_cnf(Polarity.POSITIVE).original_problem) self.assertEqual(TF, TF.to_cnf(Polarity.NEGATIVE).original_problem) # with variables v = self._fsm.encoding.by_name['v'].boolean_expression self.assertEqual(v, v.to_cnf(Polarity.NOT_SET).original_problem) self.assertEqual(v, v.to_cnf(Polarity.POSITIVE).original_problem) self.assertEqual(v, v.to_cnf(Polarity.NEGATIVE).original_problem)
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_reset(self): """ Verifies that resetting the cache provokes no error -> verifies only the absence of runtime errors since it is who RBC does the heavy lifting, not the manager """ mgr = BeRbcManager.with_capacity(10) # test it doesn't hurt to call this method even though nothing is to # be done. mgr.reset() # conversion to CNF populates the hashes which are being reset so # using this manager to perform cnf conversion makes sense if we want # to test the reset works when it actually does something. (Be.true(mgr) and Be.false(mgr)).to_cnf(Polarity.POSITIVE) mgr.reset()
def test_reset(self): """ Verifies that resetting the cache provokes no error -> verifies only the absence of runtime errors since it is who RBC does the heavy lifting, not the manager """ mgr = BeRbcManager.with_capacity(10) # test it doesn't hurt to call this method even though nothing is to # be done. mgr.reset() # conversion to CNF populates the hashes which are being reset so # using this manager to perform cnf conversion makes sense if we want # to test the reset works when it actually does something. (Be.true(mgr) and Be.false(mgr)).to_cnf(Polarity.POSITIVE) mgr.reset()
def semantic_with_loop(self, enc, i, k, l): """The semantics when there is a loop:: _{l}[[lhs W rhs]]_{bound}^{time}""" # without moving at least one step, it is impossible to go through a loop if k == 0: return Be.false(enc.manager) def _semantic(time, cnt): """auxiliary function to stop recursing after k steps""" # at infinity, it is true: psi is not forced if []phi if cnt == k: return Be.true(enc.manager) psi = self.rhs.semantic_with_loop(enc, time, k, l) phi = self.lhs.semantic_with_loop(enc, time, k, l) return psi | (phi & _semantic(successor(time, k, l), cnt + 1)) return _semantic(i, 0)
def semantic_with_loop(self, enc, i, k, l): """The semantics when there is a loop:: _{l}[[lhs W rhs]]_{bound}^{time}""" # without moving at least one step, it is impossible to go through a loop if k == 0: return Be.false(enc.manager) def _semantic(time, cnt): """auxiliary function to stop recursing after k steps""" # at infinity, it is true: psi is not forced if []phi if cnt == k: return Be.true(enc.manager) psi = self.rhs.semantic_with_loop(enc, time, k, l) phi = self.lhs.semantic_with_loop(enc, time, k, l) return psi | (phi & _semantic(successor(time, k, l), cnt + 1)) return _semantic(i, 0)
def test_if_then_else(self): true = Be.true(self._manager) false = Be.false(self._manager) with self.assertRaises(Exception): Be.if_then_else(None, true, true, false) # tautology expr = Be.if_then_else(self._manager, true, true, false) self.assertTrue(expr.is_true()) self.assertFalse(expr.is_false()) self.assertTrue(expr.is_constant()) # antilogy expr = Be.if_then_else(self._manager, false, true, false) self.assertFalse(expr.is_true()) self.assertTrue(expr.is_false()) self.assertTrue(expr.is_constant())
def test_max_var_index(self): # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false TF_cnf = TF.to_cnf(Polarity.POSITIVE) v = self._fsm.encoding.by_name['v'].boolean_expression v_cnf = v.to_cnf(Polarity.POSITIVE) self.assertEqual(0, TF_cnf.max_var_index) self.assertEqual(5, v_cnf.max_var_index) TF_cnf.max_var_index = 5 v_cnf.max_var_index = 6 self.assertEqual(5, TF_cnf.max_var_index) self.assertEqual(6, v_cnf.max_var_index)
def test_if_then_else(self): true = Be.true(self._manager) false = Be.false(self._manager) with self.assertRaises(Exception): Be.if_then_else(None, true, true, false) # tautology expr = Be.if_then_else(self._manager, true, true, false) self.assertTrue(expr.is_true()) self.assertFalse(expr.is_false()) self.assertTrue(expr.is_constant()) # antilogy expr = Be.if_then_else(self._manager, false, true, false) self.assertFalse(expr.is_true()) self.assertTrue(expr.is_false()) self.assertTrue(expr.is_constant())
def test_iff(self): true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse(true.iff(false).is_true()) self.assertTrue(true.iff(false).is_false()) self.assertTrue(true.iff(false).is_constant()) self.assertFalse(false.iff(true).is_true()) self.assertTrue(false.iff(true).is_false()) self.assertTrue(false.iff(true).is_constant()) self.assertTrue(true.iff(true).is_true()) self.assertFalse(true.iff(true).is_false()) self.assertTrue(true.iff(true).is_constant()) self.assertTrue(false.iff(false).is_true()) self.assertFalse(false.iff(false).is_false()) self.assertTrue(false.iff(false).is_constant())
def test_vars_list(self): # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false self.assertEqual("Slist[]", str(TF.to_cnf(Polarity.NOT_SET).vars_list)) self.assertEqual("Slist[]", str(TF.to_cnf(Polarity.POSITIVE).vars_list)) self.assertEqual("Slist[]", str(TF.to_cnf(Polarity.NEGATIVE).vars_list)) # with variables v = self._fsm.encoding.by_name['v'].boolean_expression self.assertEqual("Slist[1]", str(v.to_cnf(Polarity.NOT_SET).vars_list)) self.assertEqual("Slist[1]", str(v.to_cnf(Polarity.POSITIVE).vars_list)) self.assertEqual("Slist[1]", str(v.to_cnf(Polarity.NEGATIVE).vars_list)) self.assertEqual(self._fsm.encoding.by_name['v'].index, 1)
def test_print_stats(self): # TODO: use another output file and verify the output automatically with StdioFile.stdout() as out: # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false TF.to_cnf(Polarity.NOT_SET).print_stats(out, "T/F ? => ") TF.to_cnf(Polarity.POSITIVE).print_stats(out, "T/F + => ") TF.to_cnf(Polarity.NEGATIVE).print_stats(out, "T/F - => ") # with variables v = self._fsm.encoding.by_name['v'].boolean_expression # when polarity is not set, 2 clauses are present v.to_cnf(Polarity.NOT_SET).print_stats(out, "Vee ? => ") # when polarity is positive there is one single clause v.to_cnf(Polarity.POSITIVE).print_stats(out, "Vee + => ") # when polarity is negative there is one single clause v.to_cnf(Polarity.NEGATIVE).print_stats(out, "Vee - => ")
def test_formula_literal(self): # constant expr always have zero clauses zero vars true = Be.true(self._manager) false = Be.false(self._manager) TF = true or false TF_cnf = TF.to_cnf(Polarity.POSITIVE) v = self._fsm.encoding.by_name['v'].boolean_expression v_cnf = v.to_cnf(Polarity.POSITIVE) self.assertEqual((2**31 - 1), TF_cnf.formula_literal) self.assertEqual(5, v_cnf.formula_literal) # fiddling with the clause literal directly seems pretty risky TF_cnf.formula_literal = 42 v_cnf.formula_literal = 42 self.assertEqual(42, TF_cnf.formula_literal) self.assertEqual(42, v_cnf.formula_literal)
def test_xor_(self): # using the operator true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true ^ false).is_true()) self.assertFalse((true ^ false).is_false()) self.assertTrue((true ^ false).is_constant()) self.assertTrue((false ^ true).is_true()) self.assertFalse((false^ true).is_false()) self.assertTrue((false ^ true).is_constant()) self.assertFalse((true ^ true).is_true()) self.assertTrue((true ^ true).is_false()) self.assertTrue((true ^ true).is_constant()) self.assertFalse((false^ false).is_true()) self.assertTrue((false ^ false).is_false()) self.assertTrue((false ^ false).is_constant())
def test_iff(self): true = Be.true(self._manager) false = Be.false(self._manager) self.assertFalse(true.iff(false).is_true()) self.assertTrue( true.iff(false).is_false()) self.assertTrue( true.iff(false).is_constant()) self.assertFalse(false.iff(true).is_true()) self.assertTrue( false.iff(true).is_false()) self.assertTrue( false.iff(true).is_constant()) self.assertTrue( true.iff(true).is_true()) self.assertFalse(true.iff(true).is_false()) self.assertTrue( true.iff(true).is_constant()) self.assertTrue( false.iff(false).is_true()) self.assertFalse(false.iff(false).is_false()) self.assertTrue( false.iff(false).is_constant())
def constraint_eventually_critical_pair(formula_nodes, offset_path1, offset_path2, length): """ Generates a boolean expression representing the critical pair condition. That is to say, it generates a condition that verifies if it is possible that the two belief states are inconsistent wrt `formula`. :param formula_nodes: the formula whose diagnosability is verified. :param offset_path1: the offset at which path 1 is supposed to start (should be 0) :param offset_path2: the offset at which path 2 is supposed to start (must not intersect with path1) :param length: the length of the path :return: an expression describing the 'critical pair' condition. """ enc = master_be_fsm().encoding c1 = make_nnf_boolean_wff(formula_nodes[0]).to_be(enc) c2 = make_nnf_boolean_wff(formula_nodes[1]).to_be(enc) constraint = Be.false(enc.manager) for time_ in range(length + 1): constraint |= enc.shift_to_time(c1, time_ + offset_path1) & enc.shift_to_time(c2, time_ + offset_path2) return constraint
def test_xor_(self): # using the operator true = Be.true(self._manager) false = Be.false(self._manager) self.assertTrue((true ^ false).is_true()) self.assertFalse((true ^ false).is_false()) self.assertTrue((true ^ false).is_constant()) self.assertTrue((false ^ true).is_true()) self.assertFalse((false ^ true).is_false()) self.assertTrue((false ^ true).is_constant()) self.assertFalse((true ^ true).is_true()) self.assertTrue((true ^ true).is_false()) self.assertTrue((true ^ true).is_constant()) self.assertFalse((false ^ false).is_true()) self.assertTrue((false ^ false).is_false()) self.assertTrue((false ^ false).is_constant())
def test_globally(self): with Configure(self, __file__, "/models/flipflops.smv"): fsm = self.befsm formula = self.nnf("G (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 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 ref_expr= Be.false(self.enc.manager) 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 ref_expr= ltlspec.bounded_semantics_without_loop_at_offset(fsm, formula, 0, bound, offset) \ |( ltlspec.bounded_semantics_with_loop_at_offset(fsm, formula, 0, bound, 0, offset) \ & bmcutils.loop_condition(self.enc, bound+offset, offset)) expr = ltlspec.bounded_semantics_at_offset(fsm, formula, bound, offset) self.assertEqual(canonical_cnf(expr), canonical_cnf(ref_expr))
def test_eventually_critical_pair(self): enc= master_be_fsm().encoding f1 = Node.from_ptr(parse_simple_expression("status = active")) f2 = Node.from_ptr(parse_simple_expression("status = highlight")) constraint = diagnosability.constraint_eventually_critical_pair((f1, f2), 0, 5, 5) nnf1 = bmcutils.make_nnf_boolean_wff(f1).to_be(enc) nnf2 = bmcutils.make_nnf_boolean_wff(f2).to_be(enc) manual = Be.false(enc.manager) for i in range(6): # again, from 0 to 5 manual |= ( enc.shift_to_time(nnf1 , i) & enc.shift_to_time(nnf2 , 5+i)) # observing the clauses generated in both cases, one observes that # the generated clauses are the same except that the number of the cnf # literals do not match, example: # [-59, -24, 58] # [-65, -24, 64] # This is due to the fact that some 'fresh' cnf literals are used in the # generation of the epxression. Therefore, a comparison (even on the # canonical form of the CNF) is not feasible. # # Satisfiability is just an indication but at least that is .. something solver_c = SatSolverFactory.create() cnf = constraint.to_cnf() solver_c+= cnf solver_c.polarity(cnf, Polarity.POSITIVE) result_c = solver_c.solve() solver_m = SatSolverFactory.create() cnf = manual.to_cnf() solver_m+= cnf solver_m.polarity(cnf, Polarity.POSITIVE) result_m = solver_m.solve() self.assertEqual(result_c, result_m)
def test_bounded_semantics_with_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_single_loop(self.fsm, spec, -1, -2) # it must raise exception when the bound and loop are not consistent with self.assertRaises(ValueError): ltlspec.bounded_semantics_single_loop(self.fsm, spec, 5, 6) # verify that the generated problem corresponds to what is announced # without optimisation, the all loops is built as the conjunction of all # the possible 'single_loops' all_loops = ltlspec.bounded_semantics_all_loops(self.fsm, spec, 10, 0, optimized=False) acc_loops = Be.false(self.fsm.encoding.manager) for time_t in range(10): acc_loops |= ltlspec.bounded_semantics_single_loop(self.fsm, spec, 10, time_t) self.assertEqual(acc_loops, all_loops) # with optimisation, it's different all_loops = ltlspec.bounded_semantics_all_loops(self.fsm, spec, 10, 0, optimized=True) self.assertNotEqual(acc_loops, all_loops)
def _semantic(time, cnt): if cnt == k: return Be.false(enc.manager) now = self.prop.semantic_with_loop(enc, time, k, l) return now | _semantic(successor(time, k, l), cnt + 1)
def semantic_with_loop(self, enc, i, k, l): # without moving at least one step, it is impossible to go through a loop if k == 0: return Be.false(enc.manager) return self.prop.semantic_with_loop(enc, successor(i, k, l), k, l)
def semantic_no_loop(self, enc, i, k): if i >= k: return Be.false(enc.manager) else: return self.prop.semantic_no_loop(enc, i + 1, k)