Ejemplo n.º 1
0
    def autodetect_transitive(self):
        import logic as lg
        from ivy_logic_utils import Clauses
        from ivy_solver import clauses_imply
        from concept import Concept

        #        self.edge_display_checkboxes['=']['transitive'].value = True
        #        self.edge_display_checkboxes['=']['all_to_all'].value = True

        self.transitive_relations = []
        self.transitive_relation_concepts = []

        axioms = im.module.background_theory()
        for c in il.all_symbols():
            if (type(c.sort) is lg.FunctionSort and c.sort.arity == 2
                    and c.sort.domain[0] == c.sort.domain[1]
                    and c.sort.range == lg.Boolean):
                X = lg.Var('X', c.sort.domain[0])
                Y = lg.Var('Y', c.sort.domain[0])
                Z = lg.Var('Z', c.sort.domain[0])
                transitive = lg.ForAll([X, Y, Z],
                                       lg.Or(lg.Not(c(X, Y)), lg.Not(c(Y, Z)),
                                             c(X, Z)))
                defined_symmetry = lg.ForAll([X, Y],
                                             lg.Or(c(X, X), lg.Not(c(Y, Y))))
                t = Clauses([transitive, defined_symmetry])
                if clauses_imply(axioms, t):
                    self.transitive_relations.append(c.name)
                    concept = self.current_concept_graph.g.formula_to_concept(
                        c(X, Y))
                    self.transitive_relation_concepts.append(concept)
                    self.current_concept_graph.show_relation(concept, 'T')
        if self.transitive_relations:
            self.current_concept_graph.update()
Ejemplo n.º 2
0
    def prop_events(gprops):
        res = []
        for gprop in gprops:
            vs, t, env = gprop.variables, gprop.body, gprop.environ
            res.append(
                AssignAction(
                    old_l2s_g(vs, t, env)(*vs),
                    l2s_g(vs, t, env)(*vs)).set_lineno(lineno))
            res.append(HavocAction(l2s_g(vs, t, env)(*vs)).set_lineno(lineno))
        for gprop in gprops:
            vs, t, env = gprop.variables, gprop.body, gprop.environ
            res.append(
                AssumeAction(
                    forall(
                        vs,
                        lg.Implies(
                            old_l2s_g(vs, t, env)(*vs),
                            l2s_g(vs, t, env)(*vs)))).set_lineno(lineno))
            res.append(
                AssumeAction(
                    forall(
                        vs,
                        lg.Implies(
                            lg.And(lg.Not(old_l2s_g(vs, t, env)(*vs)), t),
                            lg.Not(l2s_g(vs, t,
                                         env)(*vs))))).set_lineno(lineno))
            res.append(
                AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t, env)(*vs),
                                                   t))).set_lineno(lineno))

        return res
Ejemplo n.º 3
0
def testfunc(mod):
    # for testing/ debugging
    ns = logic.UninterpretedSort("node")
    ids = logic.UninterpretedSort('id')
    n1 = logic.Var('Node0', ns)
    n2 = logic.Var('Node1', ns)
    ineq = logic.Not(logic.Eq(n2, n1))
    leadsorts = [ns, logic.BooleanSort()]
    leadfunc = logic.Const("leader", logic.FunctionSort(*leadsorts))
    idsorts = [ns, logic.UninterpretedSort("id")]
    idfunc = logic.Const("idn", logic.FunctionSort(*idsorts))
    lesorts = [ids, ids, logic.BooleanSort()]
    lefunc = logic.Const('le', logic.FunctionSort(*lesorts))
    leadterm = logic.Apply(leadfunc, *[n1])
    leterm = logic.Apply(
        lefunc, *[logic.Apply(idfunc, *[n1]),
                  logic.Apply(idfunc, *[n2])])
    fmla = logic.Not(logic.And(*[ineq, leadterm, leterm]))
    candInv, coincide = Clauses([fmla]), false_clauses()
    print "<plearn> CandInv", candInv
    for actname in sorted(mod.public_actions):
        spos, sneg = samplePos(mod, candInv, coincide,
                               actname), sampleNeg(mod, candInv, actname)
        spos, sneg = Sample(spos, '1'), Sample(sneg, '0')
        if hasattr(spos, 'interp'):
            print "<plearn> + interp: ", spos.interp
            spos.displaySample()
        if hasattr(sneg, 'interp'):
            print "<plearn> - interp: ", sneg.interp
            sneg.displaySample()
    exit(0)
Ejemplo n.º 4
0
def drop_universals(term):
    if isinstance(term, lg.ForAll):
        return drop_universals(term.body)
    if isinstance(term, lg.Not):
        return lg.Not(drop_existentials(term.args[0]))
    if isinstance(term, lg.And) and len(term.args) == 1:
        return drop_universals(term.args[0])
    return term
Ejemplo n.º 5
0
Archivo: ivy_l2s.py Proyecto: odedp/ivy
    def wait_events(waits):
        res = []
        for wait in waits:
            vs = wait.variables
            t = wait.body

        # (l2s_w V. phi)(V) := (l2s_w V. phi)(V) & ~phi & ~(l2s_g V. ~phi)(V)

            res.append(
                AssignAction(
                    wait(*vs),
                    lg.And(wait(*vs),
                           lg.Not(t),
                           replace_temporals_by_l2s_g(lg.Not(lg.Globally(proof_label,ilu.negate(t)))))
                    # TODO check this and make sure its correct
                    # note this adds to l2s_gs
                ).set_lineno(lineno))
        return res
Ejemplo n.º 6
0
        def tofmla(node):
            ''' encodes the subtree with root represented by node to fmla
				node is of type int
			'''
            global featureset
            if bintree.children_right[node] != bintree.children_left[
                    node]:  # not a leaf
                assert bintree.feature[
                    node] != _tree.TREE_UNDEFINED, "parent node uses undefined feature"
                assert isinstance(bintree.feature[node],
                                  int), "feature returned is not int"
                threshold = bintree.threshold[node]
                assert not (threshold == 0 or threshold == 1
                            ), "threshold=({}, {}) adds no information".format(
                                type(threshold), threshold)
                feat = featureset[
                    bintree.feature[node]]  # object of class Predicate
                ivyFeat = predToivyFmla(feat)
                fmlaleft = tofmla(
                    bintree.children_left[node]
                )  # left branch means case when ivyFeat is false
                fmlaright = tofmla(bintree.children_right[node])
                if fmlaright == logic.And():  # fmlaright == True
                    return simplifyOr(ivyFeat, fmlaleft)
                if fmlaleft == logic.And():
                    return simplifyOr(logic.Not(ivyFeat), fmlaright)
                f1 = simplifyAnd(logic.Not(ivyFeat), fmlaleft)
                f2 = simplifyAnd(ivyFeat, fmlaright)
                return simplifyOr(f1, f2)
            else:  # is leaf
                numdata = bintree.value[node][
                    0]  # gives number of data points for each class, 0 here because its a unioutput clf
                if numdata[0] != 0:
                    assert len(numdata) == 1 or numdata[
                        1] == 0, "leaf node has mixed data points"
                    ntype = self.clf.classes_[0]
                else:
                    assert len(numdata) == 2 and numdata[
                        1] != 0, "clf is not a biclass clf"
                    ntype = self.clf.classes_[1]
                return logic.And() if ntype == '1' else logic.Or(
                )  # and with no argument is true, or with no args is false
Ejemplo n.º 7
0
 def ite(self, dp):
     global featureset
     andArgs = []
     for i in range(len(dp)):
         featval = dp[i]
         feat = predToivyFmla(featureset[i])
         if featval == '1':
             andArgs.append(feat)
         else:
             andArgs.append(logic.Not(feat))
     return logic.And(*andArgs)
Ejemplo n.º 8
0
    def get_selected_conjecture(self):
        """
        Return a positive universal conjecture based on the selected facts.

        The result is a Clauses object
        """
        from logic_util import used_constants, free_variables, substitute
        from ivy_logic_utils import negate, Clauses, simplify_clauses

        facts = self.get_active_facts()
        assert len(free_variables(
            *
            facts)) == 0, "conjecture would contain existential quantifiers..."
        sig_symbols = frozenset(il.sig.symbols.values())
        facts_consts = used_constants(*facts)
        subs = {}
        rn = iu.VariableGenerator()
        for c in sorted(facts_consts, key=lambda c: c.name):
            if c.is_numeral() and il.is_uninterpreted_sort(c.sort):
                #                prefix = str(c.sort)[:2].upper() + c.name
                subs[c] = lg.Var(rn(c.sort.name), c.sort)

        literals = [negate(substitute(f, subs)) for f in facts]
        result = Clauses([lg.Or(*literals)])
        result = simplify_clauses(result)

        # now rename again to get a pretty clause, since some
        # variables have been eliminated by simplify_clauses
        # assert len(result.fmlas) == 1
        # clause = result.fmlas[0]
        # subs = {}
        # count = defaultdict(int)
        # for c in free_variables(clause):
        #     prefix = str(c.sort)[0].upper()
        #     count[prefix] += 1
        #     subs[c] = lg.Var(prefix + str(count[prefix]), c.sort)
        # result = Clauses([substitute(clause, subs)])

        # change to negation of conjunction rather than disjunction
        assert len(result.fmlas) == 1
        if type(result.fmlas[0]) is lg.Or:
            result = Clauses(
                [lg.Not(lg.And(*(negate(lit) for lit in result.fmlas[0])))])

        return result
Ejemplo n.º 9
0
    def process_sig(self):
        for name, sort in ivy_logic.sig.sorts.iteritems():
            if name == 'bool':
                continue
            if isinstance(sort, lg.EnumeratedSort):
                #                print >> sys.stderr, 'enumerated sort', sort, type(sort)
                n = len(sort.extension)
                #                self.instance[name] = n
                for i in range(n):
                    for j in range(i):
                        if sort.extension[
                                i] in ivy_logic.sig.symbols and sort.extension[
                                    j] in ivy_logic.sig.symbols:
                            self.axioms.append(
                                lg.Not(
                                    lg.Eq(
                                        ivy_logic.sig.symbols[
                                            sort.extension[i]],
                                        ivy_logic.sig.symbols[
                                            sort.extension[j]])))
            elif not isinstance(sort, UninterpretedSort):
                assert False, "todo"
            res = ''
            res += '(declare-sort {} 0)'.format(name)
            self.sorts[sort] = 0
            self.str[str(sort)] = res

        for name, sym in ivy_logic.sig.symbols.iteritems():
            if isinstance(sym.sort, ivy_logic.UnionSort):
                assert len(
                    sym.sort.sorts) <= 1, 'Unkonwn Union Sort: %s %s' % (
                        name, sym.sort)
                if len(sym.sort.sorts) == 0:
                    continue
                sym = lg.Const(sym.name, sym.sort.sorts[0])

            psym = sym.prefix('__')
            nsym = sym
            self.pre.add(psym)
            self.nex.add(nsym)
            self.pre2nex[psym] = nsym
            self.nex2pre[nsym] = psym
            self.allvars.add(psym)
            self.allvars.add(nsym)
            self.add_constant(sym, True)
Ejemplo n.º 10
0
def relation_size_constraint(relation, size):
    assert type(relation) is lg.Const
    assert type(relation.sort) is lg.FunctionSort

    consts = [[
        lg.Const('__${}${}${}'.format(relation.name, i, j), s)
        for j, s in enumerate(relation.sort.domain)
    ] for i in range(size)]

    vs = [
        lg.Var('X${}${}'.format(relation.name, j), s)
        for j, s in enumerate(relation.sort.domain)
    ]
    result = lg.Or(
        lg.Not(relation(*vs)),
        *(lg.And(*(lg.Eq(c, v) for c, v in zip(cs, vs))) for cs in consts))
    print "relation_size_constraint: {}".format(result)
    return result
Ejemplo n.º 11
0
def l2s(mod, lf):

    # modify mod in place

    # module pass helper funciton
    def mod_pass(transform):
        mod.labeled_conjs = [transform(x) for x in mod.labeled_conjs]
        # TODO: what about axioms and properties?
        for a in mod.public_actions:
            action = mod.actions[a]
            new_action = transform(action)
            new_action.lineno = action.lineno
            new_action.formal_params = action.formal_params
            new_action.formal_returns = action.formal_returns
            mod.actions[a] = new_action
        mod.initializers = [(x, transform(y)) for x, y in mod.initializers]

    l2s_waiting = lg.Const('l2s_waiting', lg.Boolean)
    l2s_frozen = lg.Const('l2s_frozen', lg.Boolean)
    l2s_saved = lg.Const('l2s_saved', lg.Boolean)
    l2s_d = lambda sort: lg.Const('l2s_d', lg.FunctionSort(sort, lg.Boolean))
    l2s_a = lambda sort: lg.Const('l2s_a', lg.FunctionSort(sort, lg.Boolean))
    l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, t)
    l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, t)
    l2s_g = lambda vs, t: lg.NamedBinder('l2s_g', vs, t)
    old_l2s_g = lambda vs, t: lg.NamedBinder('old_l2s_g', vs, t)

    #print ilu.used_symbols_asts(mod.labeled_conjs)
    #print '='*40
    #print list(ilu.named_binders_asts(mod.labeled_conjs))

    # some normalization

    # We first convert all temporal operators to named binders, so
    # it's possible to normalize them. Otherwise we won't have the
    # connection betweel (globally p(X)) and (globally p(Y)). Note
    # that we replace them even inside named binders.
    l2s_gs = set()

    def _l2s_g(vs, t):
        vs = tuple(vs)
        res = l2s_g(vs, t)
        l2s_gs.add((vs, t))
        return res

    replace_temporals_by_l2s_g = lambda ast: ilu.replace_temporals_by_named_binder_g_ast(
        ast, _l2s_g)
    mod_pass(replace_temporals_by_l2s_g)
    not_lf = replace_temporals_by_l2s_g(lg.Not(lf.formula))
    if debug.get():
        print "=" * 80 + "\nafter replace_temporals_by_named_binder_g_ast" + "\n" * 3
        print "=" * 80 + "\nl2s_gs:"
        for vs, t in sorted(l2s_gs):
            print vs, t
        print "=" * 80 + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # now we normalize all named binders
    mod_pass(ilu.normalize_named_binders)
    if debug.get():
        print "=" * 80 + "\nafter normalize_named_binders" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # TODO: what about normalizing lf?

    # construct the monitor related building blocks

    uninterpreted_sorts = [
        s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort
    ]
    reset_a = [
        AssignAction(l2s_a(s)(v),
                     l2s_d(s)(v)) for s in uninterpreted_sorts
        for v in [lg.Var('X', s)]
    ]
    add_consts_to_d = [
        AssignAction(l2s_d(s)(c), lg.true) for s in uninterpreted_sorts
        for c in mod.sig.symbols.values() if c.sort == s
    ]
    # TODO: maybe add all ground terms, not just consts (if stratified)
    # TODO: add conjectures that constants are in d and a

    # figure out which l2s_w and l2s_s are used in conjectures
    named_binders_conjs = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(mod.labeled_conjs):
        named_binders_conjs[b.name].append((b.variables, b.body))
    named_binders_conjs = defaultdict(
        list, ((k, list(set(v))) for k, v in named_binders_conjs.iteritems()))
    to_wait = [
    ]  # list of (variables, term) corresponding to l2s_w in conjectures
    to_wait += named_binders_conjs['l2s_w']
    to_save = [
    ]  # list of (variables, term) corresponding to l2s_s in conjectures
    to_save += named_binders_conjs['l2s_s']

    if debug.get():
        print "=" * 40 + "\nto_wait:\n"
        for vs, t in to_wait:
            print vs, t
            print list(ilu.variables_ast(t)) == list(vs)
            print
        print "=" * 40

    save_state = [AssignAction(l2s_s(vs, t)(*vs), t) for vs, t in to_save]
    done_waiting = [forall(vs, lg.Not(l2s_w(vs, t)(*vs))) for vs, t in to_wait]
    reset_w = [
        AssignAction(
            l2s_w(vs, t)(*vs), lg.And(*(l2s_d(v.sort)(v) for v in vs)))
        for vs, t in to_wait
    ]
    update_w = [
        AssignAction(
            l2s_w(vs, t)(*vs),
            lg.And(
                l2s_w(vs, t)(*vs), lg.Not(t),
                replace_temporals_by_l2s_g(lg.Not(lg.Globally(lg.Not(t)))))
            # TODO check this and make sure its correct
            # note this adds to l2s_gs
        ) for vs, t in to_wait
    ]
    if debug.get():
        print "=" * 40 + "\nupdate_w:\n"
        for x in update_w:
            print x
            print
        print "=" * 40

    fair_cycle = [l2s_saved]
    fair_cycle += done_waiting
    # projection of relations
    fair_cycle += [
        lg.ForAll(
            vs,
            lg.Implies(lg.And(*(l2s_a(v.sort)(v)
                                for v in vs)), lg.Iff(l2s_s(vs, t)(*vs), t)))
        if len(vs) > 0 else lg.Iff(l2s_s(vs, t), t) for vs, t in to_save
        if (t.sort == lg.Boolean or isinstance(t.sort, lg.FunctionSort)
            and t.sort.range == lg.Boolean)
    ]
    # projection of functions and constants
    fair_cycle += [
        forall(
            vs,
            lg.Implies(
                lg.And(*([l2s_a(v.sort)(v) for v in vs] + [
                    lg.Or(l2s_a(t.sort)(l2s_s(vs, t)(*vs)),
                          l2s_a(t.sort)(t))
                ])), lg.Eq(l2s_s(vs, t)(*vs), t))) for vs, t in to_save
        if (isinstance(t.sort, lg.UninterpretedSort)
            or isinstance(t.sort, lg.FunctionSort)
            and isinstance(t.sort.range, lg.UninterpretedSort))
    ]
    assert_no_fair_cycle = AssertAction(lg.Not(lg.And(*fair_cycle)))
    assert_no_fair_cycle.lineno = lf.lineno

    monitor_edge = lambda s1, s2: [
        AssumeAction(s1),
        AssignAction(s1, lg.false),
        AssignAction(s2, lg.true),
    ]
    change_monitor_state = [
        ChoiceAction(
            # waiting -> frozen
            Sequence(*(monitor_edge(l2s_waiting, l2s_frozen) +
                       [AssumeAction(x) for x in done_waiting] + reset_a)),
            # # frozen -> saved
            # Sequence(*(
            #     monitor_edge(l2s_frozen, l2s_saved) +
            #     save_state +
            #     reset_w
            # )),
            # stay in same state (self edge)
            Sequence(),
        )
    ]

    # tableau construction (sort of)

    # Note that we first transformed globally and eventually to named
    # binders, in order to normalize. Without this, we would get
    # multiple redundant axioms like:
    # forall X. (globally phi(X)) -> phi(X)
    # forall Y. (globally phi(Y)) -> phi(Y)
    # and the same redundancy will happen for transition updates.

    # temporals = []
    # temporals += list(ilu.temporals_asts(
    #     # TODO: these should be handled by mod_pass instead (and come via l2s_gs):
    #     # mod.labeled_axioms +
    #     # mod.labeled_props +
    #     [lf]
    # ))
    # temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait]
    # temporals += [lg.Globally(t) for vs, t in l2s_gs]
    # # TODO get from temporal axioms and temporal properties as well
    # print '='*40 + "\ntemporals:"
    # for t in temporals:
    #     print t, '\n'
    # print '='*40
    # to_g = [ # list of (variables, formula)
    #     (tuple(sorted(ilu.variables_ast(tt))), tt) # TODO what about variable normalization??
    #     for t in temporals
    #     for tt in [t.body if type(t) is lg.Globally else
    #                lg.Not(t.body) if type(t) is lg.Eventually else 1/0]
    # ]
    # TODO: get rid of the above, after properly combining it
    to_g = []  # list of (variables, formula)
    to_g += list(l2s_gs)
    to_g = list(set(to_g))
    if debug.get():
        print '=' * 40 + "\nto_g:\n"
        for vs, t in sorted(to_g):
            print vs, t, '\n'
        print '=' * 40

    assume_g_axioms = [
        AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t)))
        for vs, t in to_g
    ]
    update_g = [
        a for vs, t in to_g for a in [
            HavocAction(l2s_g(vs, t)(*vs)),
            AssumeAction(
                forall(vs, lg.Implies(
                    old_l2s_g(vs, t)(*vs),
                    l2s_g(vs, t)(*vs)))),
            AssumeAction(
                forall(
                    vs,
                    lg.Implies(lg.And(lg.Not(old_l2s_g(vs, t)(
                        *vs)), t), lg.Not(l2s_g(vs, t)(*vs))))),
        ]
    ]

    # now patch the module actions with monitor and tableau

    if debug.get():
        print "public_actions:", mod.public_actions
    # TODO: this includes the succ action (for the ticket example of
    # test/test_liveness.ivy). seems to be a bug, and this causes
    # wrong behavior for the monitor, since a call to succ from within
    # another action lets it take a step

    for a in mod.public_actions:
        action = mod.actions[a]
        add_params_to_d = [
            AssignAction(l2s_d(p.sort)(p), lg.true)
            for p in action.formal_params
        ]
        new_action = concat_actions(*(change_monitor_state + add_params_to_d +
                                      update_g + [action] + assume_g_axioms +
                                      add_consts_to_d + update_w +
                                      [assert_no_fair_cycle]))
        new_action.lineno = action.lineno
        new_action.formal_params = action.formal_params
        new_action.formal_returns = action.formal_returns
        mod.actions[a] = new_action

    l2s_init = [
        AssignAction(l2s_waiting, lg.true),
        AssignAction(l2s_frozen, lg.false),
        AssignAction(l2s_saved, lg.false),
    ]
    l2s_init += add_consts_to_d
    l2s_init += reset_w
    l2s_init += assume_g_axioms
    l2s_init += [AssumeAction(not_lf)]
    mod.initializers.append(('l2s_init', Sequence(*l2s_init)))

    if debug.get():
        print "=" * 80 + "\nafter patching actions" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # now replace all named binders by fresh relations

    named_binders = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(
            chain(
                mod.labeled_conjs,
                mod.actions.values(),
                (y for x, y in mod.initializers),
            )):
        named_binders[b.name].append(b)
    named_binders = defaultdict(list, ((k, list(sorted(set(v))))
                                       for k, v in named_binders.iteritems()))
    # make sure old_l2s_g is consistent with l2s_g
    assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g'])
    assert named_binders['old_l2s_g'] == [
        lg.NamedBinder('old_l2s_g', b.variables, b.body)
        for b in named_binders['l2s_g']
    ]
    subs = dict((b, lg.Const('{}_{}'.format(k, i), b.sort))
                for k, v in named_binders.iteritems() for i, b in enumerate(v))
    if debug.get():
        print "=" * 80 + "\nsubs:" + "\n" * 3
        for k, v in subs.items():
            print k, ' : ', v, '\n'
        print "=" * 80 + "\n" * 3
    mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs))

    if debug.get():
        print "=" * 80 + "\nafter replace_named_binders" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3
Ejemplo n.º 12
0
def drop_existentials(term):
    if isinstance(term, lg.Exists):
        return drop_existentials(term.body)
    if isinstance(term, lg.Not):
        return lg.Not(drop_universals(term.args[0]))
    return term
Ejemplo n.º 13
0
def l2s_tactic(prover, goals, proof):
    mod = im.module
    goal = goals[0]  # pick up the first proof goal
    lineno = goal.lineno
    conc = ipr.goal_conc(goal)  # get its conclusion
    if not isinstance(conc, itm.TemporalModels):
        raise iu.IvyError(proof, 'proof goal is not temporal')
    model = conc.model.clone([])
    fmla = conc.fmla

    # Get all the temporal properties from the prover environment as assumptions

    # Add all the assumed invariants to the model

    assumed_gprops = [
        x for x in prover.axioms if not x.explicit and x.temporal
    ]
    model.asms.extend(
        [p.clone([p.label, p.formula.args[0]]) for p in assumed_gprops])

    # TRICKY: We postpone compiling formulas in the tactic until now, so
    # that tactics can introduce their own symbols. But, this means that the
    # tactic has to be given an appropriate environment label for any temporal
    # operators. Here, we compile the invariants in the tactic, using the given
    # label.

    assert hasattr(proof, 'labels') and len(proof.labels) == 1
    proof_label = proof.labels[0]
    #    print 'proof label: {}'.format(proof_label)
    invars = [
        ilg.label_temporal(inv.compile(), proof_label)
        for inv in proof.tactic_decls
    ]

    l2s_waiting = lg.Const('l2s_waiting', lg.Boolean)
    l2s_frozen = lg.Const('l2s_frozen', lg.Boolean)
    l2s_saved = lg.Const('l2s_saved', lg.Boolean)
    l2s_d = lambda sort: lg.Const('l2s_d', lg.FunctionSort(sort, lg.Boolean))
    l2s_a = lambda sort: lg.Const('l2s_a', lg.FunctionSort(sort, lg.Boolean))
    l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, proof_label, t)
    l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, proof_label, t)
    l2s_g = lambda vs, t, environ: lg.NamedBinder('l2s_g', vs, environ, t)
    old_l2s_g = lambda vs, t, environ: lg.NamedBinder('_old_l2s_g', vs,
                                                      environ, t)

    # Desugar the invariants.
    #
    # $was. phi(V)  -->   l2s_saved & ($l2s_s V.phi(V))(V)
    # $happened. phi --> l2s_saved & ~($l2s_w V.phi(V))(V)
    #
    # We push $l2s_s inside propositional connectives, so that the saved
    # values correspond to atoms. Otherwise, we would have redundant
    # saved values, for example p(X) and ~p(X).

    def desugar(expr):
        def apply_was(expr):
            if isinstance(expr, (lg.And, lg.Or, lg.Not, lg.Implies, lg.Iff)):
                return expr.clone([apply_was(a) for a in expr.args])
            vs = list(iu.unique(ilu.variables_ast(expr)))
            return l2s_s(vs, expr)(*vs)

        def apply_happened(expr):
            vs = list(iu.unique(ilu.variables_ast(expr)))
            return lg.Not(l2s_w(vs, expr)(*vs))

        if ilg.is_named_binder(expr):
            if expr.name == 'was':
                if len(expr.variables) > 0:
                    raise iu.IvyError(
                        expr, "operator 'was' does not take parameters")
                return lg.And(l2s_saved, apply_was(expr.body))
            elif expr.name == 'happened':
                if len(expr.variables) > 0:
                    raise iu.IvyError(
                        expr, "operator 'happened' does not take parameters")
                return lg.And(l2s_saved, apply_happened(expr.body))
        return expr.clone([desugar(a) for a in expr.args])

    invars = map(desugar, invars)

    # Add the invariant phi to the list. TODO: maybe, if it is a G prop
    # invars.append(ipr.clone_goal(goal,[],invar))

    # Add the invariant list to the model
    model.invars = model.invars + invars

    # for inv in invars:
    #     print inv
    #     for b in ilu.named_binders_ast(inv):
    #         print 'orig binder: {} {} {}'.format(b.name,b.environ,b.body)

    # model pass helper funciton
    def mod_pass(transform):
        model.invars = [transform(x) for x in model.invars]
        model.asms = [transform(x) for x in model.asms]
        # TODO: what about axioms and properties?
        newb = []
        model.bindings = [
            b.clone([transform(b.action)]) for b in model.bindings
        ]
        model.init = transform(model.init)

    # We first convert all temporal operators to named binders, so
    # it's possible to normalize them. Otherwise we won't have the
    # connection betweel (globally p(X)) and (globally p(Y)). Note
    # that we replace them even inside named binders.
    l2s_gs = set()

    def _l2s_g(vs, t, env):
        vs = tuple(vs)
        res = l2s_g(vs, t, env)
        #        print 'l2s_gs: {} {} {}'.format(vs,t,env)
        l2s_gs.add((vs, t, env))
        return res

    replace_temporals_by_l2s_g = lambda ast: ilu.replace_temporals_by_named_binder_g_ast(
        ast, _l2s_g)
    mod_pass(replace_temporals_by_l2s_g)

    not_lf = replace_temporals_by_l2s_g(lg.Not(fmla))
    if debug.get():
        print "=" * 80 + "\nafter replace_temporals_by_named_binder_g_ast" + "\n" * 3
        print "=" * 80 + "\nl2s_gs:"
        for vs, t, env in sorted(l2s_gs):
            print vs, t, env
        print "=" * 80 + "\n" * 3
        print model
        print "=" * 80 + "\n" * 3

    # now we normalize all named binders
    mod_pass(ilu.normalize_named_binders)
    if debug.get():
        print "=" * 80 + "\nafter normalize_named_binders" + "\n" * 3
        print model
        print "=" * 80 + "\n" * 3

    # construct the monitor related building blocks

    uninterpreted_sorts = [
        s for s in ilg.sig.sorts.values()
        if type(s) is lg.UninterpretedSort and s.name not in mod.finite_sorts
    ]
    reset_a = [
        AssignAction(l2s_a(s)(v),
                     l2s_d(s)(v)).set_lineno(lineno)
        for s in uninterpreted_sorts for v in [lg.Var('X', s)]
    ]
    add_consts_to_d = [
        AssignAction(l2s_d(s)(c), lg.true).set_lineno(lineno)
        for s in uninterpreted_sorts for c in ilg.sig.symbols.values()
        if c.sort == s
    ]
    # TODO: maybe add all ground terms, not just consts (if stratified)
    # TODO: add conjectures that constants are in d and a

    # figure out which l2s_w and l2s_s are used in conjectures
    named_binders_conjs = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(model.invars):
        #        print 'binder: {} {} {}'.format(b.name,b.environ,b.body)
        named_binders_conjs[b.name].append((b.variables, b.body))
    named_binders_conjs = defaultdict(
        list, ((k, list(set(v))) for k, v in named_binders_conjs.iteritems()))
    to_wait = [
    ]  # list of (variables, term) corresponding to l2s_w in conjectures
    to_wait += named_binders_conjs['l2s_w']
    to_save = [
    ]  # list of (variables, term) corresponding to l2s_s in conjectures
    to_save += named_binders_conjs['l2s_s']

    if debug.get():
        print "=" * 40 + "\nto_wait:\n"
        for vs, t in to_wait:
            print vs, t
            print list(ilu.variables_ast(t)) == list(vs)
            print
        print "=" * 40

    save_state = [
        AssignAction(l2s_s(vs, t)(*vs), t).set_lineno(lineno)
        for vs, t in to_save
    ]
    done_waiting = [forall(vs, lg.Not(l2s_w(vs, t)(*vs))) for vs, t in to_wait]
    reset_w = [
        AssignAction(
            l2s_w(vs, t)(*vs),
            lg.And(*([
                l2s_d(v.sort)(v)
                for v in vs if v.sort.name not in mod.finite_sorts
            ] + [
                lg.Not(t),
                replace_temporals_by_l2s_g(
                    lg.Not(lg.Globally(proof_label, ilu.negate(t))))
            ]))).set_lineno(lineno) for vs, t in to_wait
    ]

    fair_cycle = [l2s_saved]
    fair_cycle += done_waiting
    # projection of relations
    fair_cycle += [
        lg.ForAll(
            vs,
            lg.Implies(
                lg.And(*(l2s_a(v.sort)(v) for v in vs
                         if v.sort.name not in mod.finite_sorts)),
                lg.Iff(l2s_s(vs, t)(
                    *vs), t))) if len(vs) > 0 else lg.Iff(l2s_s(vs, t), t)
        for vs, t in to_save
        if (t.sort == lg.Boolean or isinstance(t.sort, lg.FunctionSort)
            and t.sort.range == lg.Boolean)
    ]
    # projection of functions and constants
    fair_cycle += [
        forall(
            vs,
            lg.Implies(
                lg.And(*([
                    l2s_a(v.sort)(v)
                    for v in vs if v.sort.name not in mod.finite_sorts
                ] + ([
                    lg.Or(l2s_a(t.sort)(l2s_s(vs, t)(*vs)),
                          l2s_a(t.sort)(t))
                ] if t.sort.name not in mod.finite_sorts else []))),
                lg.Eq(l2s_s(vs, t)(*vs), t))) for vs, t in to_save
        if (isinstance(t.sort, lg.UninterpretedSort)
            or isinstance(t.sort, lg.FunctionSort)
            and isinstance(t.sort.range, lg.UninterpretedSort))
    ]
    assert_no_fair_cycle = AssertAction(lg.Not(
        lg.And(*fair_cycle))).set_lineno(lineno)
    assert_no_fair_cycle.lineno = goal.lineno

    monitor_edge = lambda s1, s2: [
        AssumeAction(s1).set_lineno(lineno),
        AssignAction(s1, lg.false).set_lineno(lineno),
        AssignAction(s2, lg.true).set_lineno(lineno),
    ]
    change_monitor_state = [
        ChoiceAction(
            # waiting -> frozen
            Sequence(
                *(monitor_edge(l2s_waiting, l2s_frozen) +
                  [AssumeAction(x).set_lineno(lineno)
                   for x in done_waiting] + reset_a)).set_lineno(lineno),
            # frozen -> saved
            Sequence(*(monitor_edge(l2s_frozen, l2s_saved) + save_state +
                       reset_w)),
            # stay in same state (self edge)
            Sequence().set_lineno(lineno),
        ).set_lineno(lineno)
    ]

    # tableau construction (sort of)

    # Note that we first transformed globally and eventually to named
    # binders, in order to normalize. Without this, we would get
    # multiple redundant axioms like:
    # forall X. (globally phi(X)) -> phi(X)
    # forall Y. (globally phi(Y)) -> phi(Y)
    # and the same redundancy will happen for transition updates.

    # temporals = []
    # temporals += list(ilu.temporals_asts(
    #     # TODO: these should be handled by mod_pass instead (and come via l2s_gs):
    #     # mod.labeled_axioms +
    #     # mod.labeled_props +
    #     [lf]
    # ))
    # temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait]
    # temporals += [lg.Globally(t) for vs, t in l2s_gs]
    # # TODO get from temporal axioms and temporal properties as well
    # print '='*40 + "\ntemporals:"
    # for t in temporals:
    #     print t, '\n'
    # print '='*40
    # to_g = [ # list of (variables, formula)
    #     (tuple(sorted(ilu.variables_ast(tt))), tt) # TODO what about variable normalization??
    #     for t in temporals
    #     for tt in [t.body if type(t) is lg.Globally else
    #                lg.Not(t.body) if type(t) is lg.Eventually else 1/0]
    # ]
    # TODO: get rid of the above, after properly combining it
    to_g = []  # list of (variables, formula)
    to_g += list(l2s_gs)
    to_g = list(set(to_g))
    if debug.get():
        print '=' * 40 + "\nto_g:\n"
        for vs, t, env in sorted(to_g):
            print vs, t, '\n'
        print '=' * 40

    assume_g_axioms = [
        AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t, env)(*vs),
                                           t))).set_lineno(lineno)
        for vs, t, env in to_g
    ]

    # now patch the module actions with monitor and tableau

    if debug.get():
        print "public_actions:", model.calls

    # Tableau construction
    #
    # Each temporal operator has an 'environment'. The operator
    # applies to states *not* in actions labeled with this
    # environment. This has several consequences:
    #
    # 1) The operator's semantic constraint is an assumed invariant (i.e.,
    # it holds outside of any action)
    #
    # 2) An 'event' for the temporal operator occurs when (a) we return
    # from an execution context inside its environment to one outside,
    # or (b) we are outside the environment of the operator and some symbol
    # occurring in it's body is mutated.
    #
    # 3) At any event for the operator, we update its truth value and
    # and re-establish its semantic constraint.
    #

    # This procedure generates code for an event corresponding to a
    # list of operators. The tableau state is updated and the
    # semantics applied.

    def prop_events(gprops):
        res = []
        for gprop in gprops:
            vs, t, env = gprop.variables, gprop.body, gprop.environ
            res.append(
                AssignAction(
                    old_l2s_g(vs, t, env)(*vs),
                    l2s_g(vs, t, env)(*vs)).set_lineno(lineno))
            res.append(HavocAction(l2s_g(vs, t, env)(*vs)).set_lineno(lineno))
        for gprop in gprops:
            vs, t, env = gprop.variables, gprop.body, gprop.environ
            res.append(
                AssumeAction(
                    forall(
                        vs,
                        lg.Implies(
                            old_l2s_g(vs, t, env)(*vs),
                            l2s_g(vs, t, env)(*vs)))).set_lineno(lineno))
            res.append(
                AssumeAction(
                    forall(
                        vs,
                        lg.Implies(
                            lg.And(lg.Not(old_l2s_g(vs, t, env)(*vs)), t),
                            lg.Not(l2s_g(vs, t,
                                         env)(*vs))))).set_lineno(lineno))
            res.append(
                AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t, env)(*vs),
                                                   t))).set_lineno(lineno))

        return res

    # This procedure generates code for an event corresponding to a
    # list of eventualites to be waited on. The tableau state is updated and the
    # semantics applied.

    def wait_events(waits):
        res = []
        for wait in waits:
            vs = wait.variables
            t = wait.body

            # (l2s_w V. phi)(V) := (l2s_w V. phi)(V) & ~phi & ~(l2s_g V. ~phi)(V)

            res.append(
                AssignAction(
                    wait(*vs),
                    lg.And(
                        wait(*vs), lg.Not(t),
                        replace_temporals_by_l2s_g(
                            lg.Not(lg.Globally(proof_label, ilu.negate(t)))))
                    # TODO check this and make sure its correct
                    # note this adds to l2s_gs
                ).set_lineno(lineno))
        return res

    # The following procedure instruments a statement with operator
    # events for all of the temporal operators.  This depends on the
    # statement's environment, that is, current set of environment
    # labels.
    #
    # Currently, the environment labels of a statement have to be
    # statically determined, but this could change, i.e., the labels
    # could be represented by boolean variables.
    #

    # First, make some memo tables

    envprops = defaultdict(list)
    symprops = defaultdict(list)
    symwaits = defaultdict(list)
    for vs, t, env in l2s_gs:
        prop = l2s_g(vs, t, env)
        envprops[env].append(prop)
        for sym in ilu.symbols_ast(t):
            symprops[sym].append(prop)
    for vs, t in to_wait:
        wait = l2s_w(vs, t)
        for sym in ilu.symbols_ast(t):
            symwaits[sym].append(wait)
    actions = dict((b.name, b.action) for b in model.bindings)

    # lines = dict(zip(gprops,gproplines))

    def instr_stmt(stmt, labels):

        # first, recur on the sub-statements
        args = [
            instr_stmt(a, labels) if isinstance(a, Action) else a
            for a in stmt.args
        ]
        res = stmt.clone(args)

        # now add any needed temporal events after this statement
        event_props = set()
        event_waits = set()

        # first, if it is a call, we must consider any events associated with
        # the return

        # if isinstance(stmt,CallAction):
        #     callee = actions[stmt.callee()]  # get the called action
        #     exiting = [l for l in callee.labels if l not in labels] # environments we exit on return
        #     for label in exiting:
        #         for prop in envprops[label]:
        #             event_props.add(prop)

        # Second, if a symbol is modified, we must add events for every property that
        # depends on the symbol, but only if we are not in the environment of that property.

        for sym in stmt.modifies():
            for prop in symprops[sym]:
                #                if prop.environ not in labels:
                event_props.add(prop)
            for wait in symwaits[sym]:
                event_waits.add(wait)

        # Now, for every property event, we update the property state (none in this case)
        # and also assert the property semantic constraint.

        events = prop_events(event_props)
        events += wait_events(event_waits)
        res = iact.postfix_action(res, events)
        stmt.copy_formals(res)  # HACK: This shouldn't be needed
        return res

    # Instrument all the actions

    model.bindings = [
        b.clone([b.action.clone([instr_stmt(b.action.stmt, b.action.labels)])])
        for b in model.bindings
    ]

    # Now, for every exported action, we add the l2s construction. On
    # exit of each external procedure, we add a tableau event for all
    # the operators whose scope is being exited.
    #
    # TODO: This is wrong in the case of an exported procedure that is
    # also internally called.  We do *not* want to update the tableau
    # in the case of an internal call, since the scope of the
    # operators os not exited. One solution to this is to create to
    # duplicate the actions so there is one version for internal
    # callers and one for external callers. It is possible that this
    # is already done by ivy_isolate, but this needs to be verified.

    calls = set(model.calls)  # the exports
    for b in model.bindings:
        if b.name in calls:
            add_params_to_d = [
                AssignAction(l2s_d(p.sort)(p), lg.true)
                for p in b.action.inputs if p.sort.name not in mod.finite_sorts
            ]
            # tableau updates for exit to environment
            # event_props = set()
            # for label in b.action.labels:
            #     for prop in envprops[label]:
            #         event_props.add(prop)
            # events = prop_events(event_props)
            stmt = concat_actions(*(
                add_params_to_d +
                assume_g_axioms +  # could be added to model.asms
                [b.action.stmt] + add_consts_to_d)).set_lineno(lineno)
            b.action.stmt.copy_formals(stmt)  # HACK: This shouldn't be needed
            b.action = b.action.clone([stmt])

    # The idle action handles automaton state update and cycle checking

    idle_action = concat_actions(*(
        change_monitor_state +
        assume_g_axioms +  # could be added to model.asms
        add_consts_to_d + [assert_no_fair_cycle])).set_lineno(lineno)
    idle_action.formal_params = []
    idle_action.formal_returns = []
    model.bindings.append(
        itm.ActionTermBinding('idle', itm.ActionTerm([], [], [], idle_action)))
    model.calls.append('idle')

    l2s_init = [
        AssignAction(l2s_waiting, lg.true).set_lineno(lineno),
        AssignAction(l2s_frozen, lg.false).set_lineno(lineno),
        AssignAction(l2s_saved, lg.false).set_lineno(lineno),
    ]
    l2s_init += add_consts_to_d
    l2s_init += reset_w
    l2s_init += assume_g_axioms
    l2s_init += [AssumeAction(not_lf).set_lineno(lineno)]
    if not hasattr(model.init, 'lineno'):
        model.init.lineno = None  # Hack: fix this
    model.init = iact.postfix_action(model.init, l2s_init)

    if debug.get():
        print "=" * 80 + "\nafter patching actions" + "\n" * 3
        print model
        print "=" * 80 + "\n" * 3

    # now replace all named binders by fresh relations

    named_binders = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(
            chain(
                model.invars,
                model.asms,
                [model.init],
                [b.action for b in model.bindings],
            )):
        named_binders[b.name].append(b)
    named_binders = defaultdict(list, ((k, list(sorted(set(v))))
                                       for k, v in named_binders.iteritems()))
    # make sure old_l2s_g is consistent with l2s_g
    #    assert len(named_binders['l2s_g']) == len(named_binders['_old_l2s_g'])
    named_binders['_old_l2s_g'] = [
        lg.NamedBinder('_old_l2s_g', b.variables, b.environ, b.body)
        for b in named_binders['l2s_g']
    ]
    subs = dict((b, lg.Const('{}_{}'.format(k, i), b.sort))
                for k, v in named_binders.iteritems() for i, b in enumerate(v))
    if debug.get():
        print "=" * 80 + "\nsubs:" + "\n" * 3
        for k, v in subs.items():
            print k, ' : ', v, '\n'
        print "=" * 80 + "\n" * 3
    mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs))

    if debug.get():
        print "=" * 80 + "\nafter replace_named_binders" + "\n" * 3
        print model
        print "=" * 80 + "\n" * 3

    # if len(gprops) > 0:
    #     assumes = [gprop_to_assume(x) for x in gprops]
    #     model.bindings = [b.clone([prefix_action(b.action,assumes)]) for b in model.bindings]

    # HACK: reestablish invariant that shouldn't be needed

    for b in model.bindings:
        b.action.stmt.formal_params = b.action.inputs
        b.action.stmt.formal_returns = b.action.outputs

    # Change the conclusion formula to M |= true
    conc = itm.TemporalModels(model, lg.And())

    # Build the new goal
    goal = ipr.clone_goal(goal, ipr.goal_prems(goal), conc)

    # Return the new goal stack

    goals = [goal] + goals[1:]
    return goals
Ejemplo n.º 14
0
 def apply_happened(expr):
     vs = list(iu.unique(ilu.variables_ast(expr)))
     return lg.Not(l2s_w(vs, expr)(*vs))
Ejemplo n.º 15
0
def l2s(mod, temporal_goal):
    # modify mod in place

    # module pass helper funciton
    def mod_pass(transform):
        mod.labeled_conjs = [transform(x) for x in mod.labeled_conjs]
        # TODO: what about axioms and properties?
        for a in mod.public_actions:
            action = mod.actions[a]
            new_action = transform(action)
            new_action.lineno = action.lineno
            new_action.formal_params = action.formal_params
            new_action.formal_returns = action.formal_returns
            mod.actions[a] = new_action
        mod.initializers = [(x, transform(y)) for x, y in mod.initializers]

    l2s_waiting = lg.Const('l2s_waiting', lg.Boolean)
    l2s_frozen = lg.Const('l2s_frozen', lg.Boolean)
    l2s_saved = lg.Const('l2s_saved', lg.Boolean)
    l2s_error = lg.Const('l2s_error', lg.Boolean)
    l2s_d = lambda sort: lg.Const('l2s_d', lg.FunctionSort(sort, lg.Boolean))
    l2s_a = lambda sort: lg.Const('l2s_a', lg.FunctionSort(sort, lg.Boolean))
    l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, t)
    l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, t)
    l2s_g = lambda vs, t: lg.NamedBinder('l2s_g', vs, t)
    old_l2s_g = lambda vs, t: lg.NamedBinder('old_l2s_g', vs, t)

    # add conjectures about monitor state
    conjs = [
        lg.Or(l2s_waiting, l2s_frozen, l2s_saved),
        lg.Or(lg.Not(l2s_waiting), lg.Not(l2s_frozen)),
        lg.Or(lg.Not(l2s_waiting), lg.Not(l2s_saved)),
        lg.Or(lg.Not(l2s_frozen), lg.Not(l2s_saved)),
    ]
    for f in conjs:
        c = ast.LabeledFormula(ast.Atom('l2s_internal'), f)
        c.lineno = temporal_goal.lineno
        mod.labeled_conjs.append(c)

    # add conjecture that we are not in the error state (this is
    # instead of using an assertion. see below)
    c = ast.LabeledFormula(ast.Atom('not_l2s_error'), lg.Not(l2s_error))
    c.lineno = temporal_goal.lineno
    mod.labeled_conjs.append(c)

    #print ilu.used_symbols_asts(mod.labeled_conjs)
    #print '='*40
    #print list(ilu.named_binders_asts(mod.labeled_conjs))

    # some normalization

    # We first convert all temporal operators to named binders, so
    # it's possible to normalize them. Otherwise we won't have the
    # connection betweel (globally p(X)) and (globally p(Y)). Note
    # that we replace them even inside named binders.
    l2s_gs = set()

    def _l2s_g(vs, t):
        vs = tuple(vs)
        res = l2s_g(vs, t)
        l2s_gs.add((vs, t))
        return res

    replace_temporals_by_l2s_g = lambda ast: ilu.replace_temporals_by_named_binder_g_ast(
        ast, _l2s_g)
    mod_pass(replace_temporals_by_l2s_g)
    not_temporal_goal = replace_temporals_by_l2s_g(
        lg.Not(temporal_goal.formula))
    if debug.get():
        print "=" * 80 + "\nafter replace_temporals_by_named_binder_g_ast" + "\n" * 3
        print "=" * 80 + "\nl2s_gs:"
        for vs, t in sorted(l2s_gs):
            print vs, t
        print "=" * 80 + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # now we normalize all named binders
    mod_pass(ilu.normalize_named_binders)
    if debug.get():
        print "=" * 80 + "\nafter normalize_named_binders" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # TODO: what about normalizing temporal_goal? - temporal_goal
    # should not contain any named binders except for temporal
    # properties, so it is normalized by construction

    # construct the monitor related building blocks

    uninterpreted_sorts = [
        s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort
    ]
    reset_a = [
        AssignAction(l2s_a(s)(v),
                     l2s_d(s)(v)) for s in uninterpreted_sorts
        for v in [lg.Var('X', s)]
    ]
    add_consts_to_d = [
        AssignAction(l2s_d(s)(c), lg.true) for s in uninterpreted_sorts
        for c in mod.sig.symbols.values() if c.sort == s
    ]
    # TODO: maybe add all ground terms, not just consts (if stratified)
    # TODO: add conjectures that constants are in d and a

    # figure out which l2s_w and l2s_s are used in conjectures
    named_binders_conjs = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(mod.labeled_conjs):
        named_binders_conjs[b.name].append((b.variables, b.body))
    named_binders_conjs = defaultdict(
        list, ((k, sorted(list(set(v))))
               for k, v in named_binders_conjs.iteritems()))
    to_wait = [
    ]  # list of (variables, term) corresponding to l2s_w in conjectures
    to_wait += named_binders_conjs['l2s_w']
    to_save = [
    ]  # list of (variables, term) corresponding to l2s_s in conjectures
    to_save += named_binders_conjs['l2s_s']

    if debug.get():
        print "=" * 40 + "\nto_wait:\n"
        for vs, t in to_wait:
            print vs, t
            # print list(ilu.variables_ast(t)) == list(vs)
            # print
        print "=" * 40

    save_state = [AssignAction(l2s_s(vs, t)(*vs), t) for vs, t in to_save]
    done_waiting = [forall(vs, lg.Not(l2s_w(vs, t)(*vs))) for vs, t in to_wait]
    reset_w = [
        AssignAction(
            l2s_w(vs, t)(*vs), lg.And(*(l2s_d(v.sort)(v) for v in vs)))
        for vs, t in to_wait
    ]
    update_w = [
        AssignAction(
            l2s_w(vs, t)(*vs),
            lg.And(
                l2s_w(vs, t)(*vs), lg.Not(t),
                replace_temporals_by_l2s_g(lg.Not(lg.Globally(ilu.negate(t)))))
            # ($l2s_w.  phi) waits until ( phi | globally ~phi), but
            # ($l2s_w. ~phi) waits until (~phi | globally  phi) (i.e., we avoid "globally ~~phi" here)
            # note this adds to l2s_gs
        ) for vs, t in to_wait
    ]
    if debug.get():
        print "=" * 40 + "\nupdate_w:\n"
        for x in update_w:
            print x
            print
        print "=" * 40

    fair_cycle = [l2s_saved]
    fair_cycle += done_waiting
    # projection of relations
    fair_cycle += [
        lg.ForAll(
            vs,
            lg.Implies(lg.And(*(l2s_a(v.sort)(v)
                                for v in vs)), lg.Iff(l2s_s(vs, t)(*vs), t)))
        if len(vs) > 0 else lg.Iff(l2s_s(vs, t), t) for vs, t in to_save
        if (t.sort == lg.Boolean or isinstance(t.sort, lg.FunctionSort)
            and t.sort.range == lg.Boolean)
    ]
    # projection of functions and constants
    fair_cycle += [
        forall(
            vs,
            lg.Implies(
                lg.And(*([l2s_a(v.sort)(v) for v in vs] + [
                    lg.Or(l2s_a(t.sort)(l2s_s(vs, t)(*vs)),
                          l2s_a(t.sort)(t))
                ])), lg.Eq(l2s_s(vs, t)(*vs), t))) for vs, t in to_save
        if (isinstance(t.sort, lg.UninterpretedSort)
            or isinstance(t.sort, lg.FunctionSort)
            and isinstance(t.sort.range, lg.UninterpretedSort))
    ]
    if debug.get():
        print "=" * 40 + "\nfair_cycle:\n"
        for x in fair_cycle:
            print x
            print
        print "=" * 40
    # TODO: figure out why AssertAction doesn't work properly
    def assert_no_fair_cycle(a):
        # comment and uncomment the following lines to debug:
        # res = AssertAction(lg.Not(lg.And(*fair_cycle)))
        # res = AssertAction(lg.false)
        # res.lineno = temporal_goal.lineno
        # res.lineno = a.lineno
        res = AssignAction(l2s_error, lg.And(*fair_cycle))
        return res

    monitor_edge = lambda s1, s2: [
        AssumeAction(s1),
        AssignAction(s1, lg.false),
        AssignAction(s2, lg.true),
    ]
    change_monitor_state = [
        ChoiceAction(
            # waiting -> frozen
            Sequence(*(monitor_edge(l2s_waiting, l2s_frozen) +
                       [AssumeAction(x) for x in done_waiting] + reset_a)),
            # frozen -> saved
            Sequence(*(monitor_edge(l2s_frozen, l2s_saved) + save_state +
                       reset_w)),
            # stay in same state (self edge)
            Sequence(),
        )
    ]

    # tableau construction
    to_g = []  # list of (variables, formula)
    to_g += sorted(list(l2s_gs))
    if debug.get():
        print '=' * 40 + "\nto_g:\n"
        for vs, t in to_g:
            print vs, t, '\n'
        print '=' * 40
    assume_g_axioms = [
        AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t)))
        for vs, t in to_g
    ]
    update_g = [
        a for vs, t in to_g for a in [
            HavocAction(l2s_g(vs, t)(*vs)),
            AssumeAction(
                forall(vs, lg.Implies(
                    old_l2s_g(vs, t)(*vs),
                    l2s_g(vs, t)(*vs)))),
            AssumeAction(
                forall(
                    vs,
                    lg.Implies(lg.And(lg.Not(old_l2s_g(vs, t)(
                        *vs)), t), lg.Not(l2s_g(vs, t)(*vs))))),
        ]
    ]

    # now patch the module actions with monitor and tableau

    if debug.get():
        print "public_actions:", mod.public_actions
    for a in mod.public_actions:
        action = mod.actions[a]
        add_params_to_d = [
            AssignAction(l2s_d(p.sort)(p), lg.true)
            for p in action.formal_params
        ]
        new_action = concat_actions(*(
            # TODO: check this with Sharon
            assume_g_axioms + change_monitor_state + add_params_to_d +
            update_g + [action] + assume_g_axioms + add_consts_to_d +
            update_w + [assert_no_fair_cycle(action)]))
        new_action.lineno = action.lineno
        new_action.formal_params = action.formal_params
        new_action.formal_returns = action.formal_returns
        mod.actions[a] = new_action

    l2s_init = [
        AssignAction(l2s_waiting, lg.true),
        AssignAction(l2s_frozen, lg.false),
        AssignAction(l2s_saved, lg.false),
        AssignAction(l2s_error, lg.false),
    ]
    l2s_init += add_consts_to_d
    l2s_init += reset_w
    l2s_init += assume_g_axioms
    l2s_init += [AssumeAction(not_temporal_goal)]
    mod.initializers.append(('l2s_init', Sequence(*l2s_init)))

    if debug.get():
        print "=" * 80 + "\nafter patching actions" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3

    # now replace all named binders by fresh relations

    named_binders = defaultdict(
        list)  # dict mapping names to lists of (vars, body)
    for b in ilu.named_binders_asts(
            chain(
                mod.labeled_conjs,
                mod.actions.values(),
                (y for x, y in mod.initializers),
            )):
        named_binders[b.name].append(b)
    # sort named binders according to a consistent order
    named_binders = defaultdict(
        list, ((k,
                list(
                    sorted(
                        set(v),
                        key=lambda x:
                        (len(x.variables), str(x.variables), str(x.body)),
                    ))) for k, v in named_binders.iteritems()))
    # make sure old_l2s_g is consistent with l2s_g, so that
    # old_l2s_g_X is really old l2s_g_X after the substitution
    assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g'])
    assert named_binders['old_l2s_g'] == [
        lg.NamedBinder('old_l2s_g', b.variables, b.body)
        for b in named_binders['l2s_g']
    ]
    subs = dict((b, lg.Const('{}_{}'.format(k, i), b.sort))
                for k, v in named_binders.iteritems() for i, b in enumerate(v))
    if debug.get():
        print "=" * 80 + "\nsubs:" + "\n" * 3
        for k in sorted(named_binders.keys()):
            v = named_binders[k]
            for i, b in enumerate(v):
                print '{}_{}'.format(k, i), ' : ', b
        print "=" * 80 + "\n" * 3
    mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs))

    if debug.get():
        print "=" * 80 + "\nafter replace_named_binders" + "\n" * 3
        print_module(mod)
        print "=" * 80 + "\n" * 3