def if_tactic(self,decls,proof): cond = proof.args[0] true_goal = ia.LabeledFormula(decls[0].label,il.Implies(cond,decls[0].formula)) true_goal.lineno = decls[0].lineno false_goal = ia.LabeledFormula(decls[0].label,il.Implies(il.Not(cond),decls[0].formula)) false_goal.lineno = decls[0].lineno return (attrib_goals(proof.args[1],self.apply_proof([true_goal],proof.args[1])) + attrib_goals(proof.args[2],self.apply_proof([false_goal],proof.args[2])) + decls[1:])
def create_strat_map(assumes, asserts, macros): """ Given a list of assumptions, assertions and macros, compute the stratification graph. The difference between assumes and asserts is that the free variables in assumes are treated as universally quantified, while the asserts are treated as negated. Each argument is a list of pairs `(fmla,ast)` where `fmla` is the formula and `ast` an ast giving the origin of the formula. """ global universally_quantified_variables global strat_map global arcs # Gather all the formulas in the VC. all_fmlas = [(il.close_formula(x), y) for x, y in assumes] all_fmlas.extend((il.Not(x), y) for x, y in asserts) all_fmlas.extend(macros) # Get the universally quantified variables. The free variables of # asserts and macros won't count as universal. We keep track of the # line numbers of these variables for error messages. universally_quantified_variables = dict() for fmla, lf in all_fmlas: for v in il.universal_variables([fmla]): if (il.is_uninterpreted_sort(v.sort) or il.has_infinite_interpretation(v.sort)): universally_quantified_variables[v] = lf # print 'universally_quantified_variables : {}'.format([str(v) for v in universally_quantified_variables]) # Create an empty graph. strat_map = defaultdict(UFNode) arcs = [] # Handle macros, as described above. create_macro_maps(assumes, asserts, macros) # Simulate the Skolem functions that would be generated by AE alternations. for fmla, ast in all_fmlas: make_skolems(fmla, ast, True, []) # Construct the stratification graph by calling `map_fmla` on all # of the formulas in the VC. We don't include the macro definitions # here, because these are 'inlined' by `map_fmla`. for pair in assumes + asserts: map_fmla(pair[1].lineno, pair[0], 0)
def create_strat_map(assumes,asserts,macros): global symbols_over_universals global universally_quantified_variables all_fmlas = [il.close_formula(pair[0]) for pair in assumes] all_fmlas.extend(il.Not(pair[0]) for pair in asserts) all_fmlas.extend(pair[0] for pair in macros) # for f in all_fmlas: # print f symbols_over_universals = il.symbols_over_universals(all_fmlas) universally_quantified_variables = il.universal_variables(all_fmlas) strat_map = defaultdict(UFNode) for pair in assumes+asserts+macros: map_fmla(pair[0],strat_map) # show_strat_map(strat_map) # print 'universally_quantified_variables:{}'.format(universally_quantified_variables) return strat_map
def check_can_assert(logic, fmla, ast): check_can_assume(logic, fmla, ast) if not il.is_in_logic(il.Not(fmla), logic): report_error(logic, " when negated", ast)
if __name__ == "__main__": ag = ivy.ivy_init() ac = AC(ag.assertions, ag.actions, ag.domain) with utl.ErrorPrinter(): for ass in ag.assertions: lhs = ass.args[0].rep rhs = ass.args[1] rhs_state = eval_assert_rhs(rhs, ag.domain) try: if lhs in ag.predicates: with ac: lhs_state = itp.eval_state(ag.predicates[lhs], ac) elif lhs in ag.actions: with ac: lhs_state = itp.eval_state( act.entry(lg.Not(rhs_state.precond)), ac) lhs_state = itp.apply_action(ass, lhs, ag.actions[lhs], lhs_state) else: raise utl.IvyError(ass, "{} undefined".format(lhs)) print "lhs_state: %s" % lhs_state.clauses print "rhs_fmla: %s" % rhs_state.clauses cex = ag.domain.order(lhs_state, rhs_state) if not cex: print repr(utl.IvyError(ass, "assertion does not hold")) analyze_state(ag, lhs_state, cex.clauses) except itp.IvyActionFailedError as err: print repr(err) print err.error_state.clauses analyze_state(ag, err.error_state, [])
def to_aiger(mod,ext_act): erf = il.Symbol('err_flag',il.find_sort('bool')) errconds = [] add_err_flag_mod(mod,erf,errconds) # we use a special state variable __init to indicate the initial state ext_acts = [mod.actions[x] for x in sorted(mod.public_actions)] ext_act = ia.EnvAction(*ext_acts) init_var = il.Symbol('__init',il.find_sort('bool')) init = add_err_flag(ia.Sequence(*([a for n,a in mod.initializers]+[ia.AssignAction(init_var,il.And())])),erf,errconds) action = ia.Sequence(ia.AssignAction(erf,il.Or()),ia.IfAction(init_var,ext_act,init)) # get the invariant to be proved, replacing free variables with # skolems. First, we apply any proof tactics. pc = ivy_proof.ProofChecker(mod.axioms,mod.definitions,mod.schemata) pmap = dict((lf.id,p) for lf,p in mod.proofs) conjs = [] for lf in mod.labeled_conjs: if lf.id in pmap: proof = pmap[lf.id] subgoals = pc.admit_proposition(lf,proof) conjs.extend(subgoals) else: conjs.append(lf) invariant = il.And(*[il.drop_universals(lf.formula) for lf in conjs]) # iu.dbg('invariant') skolemizer = lambda v: ilu.var_to_skolem('__',il.Variable(v.rep,v.sort)) vs = ilu.used_variables_in_order_ast(invariant) sksubs = dict((v.rep,skolemizer(v)) for v in vs) invariant = ilu.substitute_ast(invariant,sksubs) invar_syms = ilu.used_symbols_ast(invariant) # compute the transition relation stvars,trans,error = action.update(mod,None) # print 'action : {}'.format(action) # print 'annotation: {}'.format(trans.annot) annot = trans.annot # match_annotation(action,annot,MatchHandler()) indhyps = [il.close_formula(il.Implies(init_var,lf.formula)) for lf in mod.labeled_conjs] # trans = ilu.and_clauses(trans,indhyps) # save the original symbols for trace orig_syms = ilu.used_symbols_clauses(trans) orig_syms.update(ilu.used_symbols_ast(invariant)) # TODO: get the axioms (or maybe only the ground ones?) # axioms = mod.background_theory() # rn = dict((sym,tr.new(sym)) for sym in stvars) # next_axioms = ilu.rename_clauses(axioms,rn) # return ilu.and_clauses(axioms,next_axioms) funs = set() for df in trans.defs: funs.update(ilu.used_symbols_ast(df.args[1])) for fmla in trans.fmlas: funs.update(ilu.used_symbols_ast(fmla)) # funs = ilu.used_symbols_clauses(trans) funs.update(ilu.used_symbols_ast(invariant)) funs = set(sym for sym in funs if il.is_function_sort(sym.sort)) iu.dbg('[str(fun) for fun in funs]') # Propositionally abstract # step 1: get rid of definitions of non-finite symbols by turning # them into constraints new_defs = [] new_fmlas = [] for df in trans.defs: if len(df.args[0].args) == 0 and is_finite_sort(df.args[0].sort): new_defs.append(df) else: fmla = df.to_constraint() new_fmlas.append(fmla) trans = ilu.Clauses(new_fmlas+trans.fmlas,new_defs) # step 2: get rid of ite's over non-finite sorts, by introducing constraints cnsts = [] new_defs = [elim_ite(df,cnsts) for df in trans.defs] new_fmlas = [elim_ite(fmla,cnsts) for fmla in trans.fmlas] trans = ilu.Clauses(new_fmlas+cnsts,new_defs) # step 3: eliminate quantfiers using finite instantiations from_asserts = il.And(*[il.Equals(x,x) for x in ilu.used_symbols_ast(il.And(*errconds)) if tr.is_skolem(x) and not il.is_function_sort(x.sort)]) iu.dbg('from_asserts') invar_syms.update(ilu.used_symbols_ast(from_asserts)) sort_constants = mine_constants(mod,trans,il.And(invariant,from_asserts)) sort_constants2 = mine_constants2(mod,trans,invariant) print '\ninstantiations:' trans,invariant = Qelim(sort_constants,sort_constants2)(trans,invariant,indhyps) # print 'after qe:' # print 'trans: {}'.format(trans) # print 'invariant: {}'.format(invariant) # step 4: instantiate the axioms using patterns # We have to condition both the transition relation and the # invariant on the axioms, so we define a boolean symbol '__axioms' # to represent the axioms. axs = instantiate_axioms(mod,stvars,trans,invariant,sort_constants,funs) ax_conj = il.And(*axs) ax_var = il.Symbol('__axioms',ax_conj.sort) ax_def = il.Definition(ax_var,ax_conj) invariant = il.Implies(ax_var,invariant) trans = ilu.Clauses(trans.fmlas+[ax_var],trans.defs+[ax_def]) # step 5: eliminate all non-propositional atoms by replacing with fresh booleans # An atom with next-state symbols is converted to a next-state symbol if possible stvarset = set(stvars) prop_abs = dict() # map from atoms to proposition variables global prop_abs_ctr # sigh -- python lameness prop_abs_ctr = 0 # counter for fresh symbols new_stvars = [] # list of fresh symbols # get the propositional abstraction of an atom def new_prop(expr): res = prop_abs.get(expr,None) if res is None: prev = prev_expr(stvarset,expr,sort_constants) if prev is not None: # print 'stvar: old: {} new: {}'.format(prev,expr) pva = new_prop(prev) res = tr.new(pva) new_stvars.append(pva) prop_abs[expr] = res # prevent adding this again to new_stvars else: global prop_abs_ctr res = il.Symbol('__abs[{}]'.format(prop_abs_ctr),expr.sort) # print '{} = {}'.format(res,expr) prop_abs[expr] = res prop_abs_ctr += 1 return res # propositionally abstract an expression global mk_prop_fmlas mk_prop_fmlas = [] def mk_prop_abs(expr): if il.is_quantifier(expr) or len(expr.args) > 0 and any(not is_finite_sort(a.sort) for a in expr.args): return new_prop(expr) return expr.clone(map(mk_prop_abs,expr.args)) # apply propositional abstraction to the transition relation new_defs = map(mk_prop_abs,trans.defs) new_fmlas = [mk_prop_abs(il.close_formula(fmla)) for fmla in trans.fmlas] # find any immutable abstract variables, and give them a next definition def my_is_skolem(x): res = tr.is_skolem(x) and x not in invar_syms return res def is_immutable_expr(expr): res = not any(my_is_skolem(sym) or tr.is_new(sym) or sym in stvarset for sym in ilu.used_symbols_ast(expr)) return res for expr,v in prop_abs.iteritems(): if is_immutable_expr(expr): new_stvars.append(v) print 'new state: {}'.format(expr) new_defs.append(il.Definition(tr.new(v),v)) trans = ilu.Clauses(new_fmlas+mk_prop_fmlas,new_defs) # apply propositional abstraction to the invariant invariant = mk_prop_abs(invariant) # create next-state symbols for atoms in the invariant (is this needed?) rn = dict((sym,tr.new(sym)) for sym in stvars) mk_prop_abs(ilu.rename_ast(invariant,rn)) # this is to pick up state variables from invariant # update the state variables by removing the non-finite ones and adding the fresh state booleans stvars = [sym for sym in stvars if is_finite_sort(sym.sort)] + new_stvars # iu.dbg('trans') # iu.dbg('stvars') # iu.dbg('invariant') # exit(0) # For each state var, create a variable that corresponds to the input of its latch # Also, havoc all the state bits except the init flag at the initial time. This # is needed because in aiger, all latches start at 0! def fix(v): return v.prefix('nondet') def curval(v): return v.prefix('curval') def initchoice(v): return v.prefix('initchoice') stvars_fix_map = dict((tr.new(v),fix(v)) for v in stvars) stvars_fix_map.update((v,curval(v)) for v in stvars if v != init_var) trans = ilu.rename_clauses(trans,stvars_fix_map) # iu.dbg('trans') new_defs = trans.defs + [il.Definition(ilu.sym_inst(tr.new(v)),ilu.sym_inst(fix(v))) for v in stvars] new_defs.extend(il.Definition(curval(v),il.Ite(init_var,v,initchoice(v))) for v in stvars if v != init_var) trans = ilu.Clauses(trans.fmlas,new_defs) # Turn the transition constraint into a definition cnst_var = il.Symbol('__cnst',il.find_sort('bool')) new_defs = list(trans.defs) new_defs.append(il.Definition(tr.new(cnst_var),fix(cnst_var))) new_defs.append(il.Definition(fix(cnst_var),il.Or(cnst_var,il.Not(il.And(*trans.fmlas))))) stvars.append(cnst_var) trans = ilu.Clauses([],new_defs) # Input are all the non-defined symbols. Output indicates invariant is false. # iu.dbg('trans') def_set = set(df.defines() for df in trans.defs) def_set.update(stvars) # iu.dbg('def_set') used = ilu.used_symbols_clauses(trans) used.update(ilu.symbols_ast(invariant)) inputs = [sym for sym in used if sym not in def_set and not il.is_interpreted_symbol(sym)] fail = il.Symbol('__fail',il.find_sort('bool')) outputs = [fail] # iu.dbg('trans') # make an aiger aiger = Encoder(inputs,stvars,outputs) comb_defs = [df for df in trans.defs if not tr.is_new(df.defines())] invar_fail = il.Symbol('invar__fail',il.find_sort('bool')) # make a name for invariant fail cond comb_defs.append(il.Definition(invar_fail,il.Not(invariant))) aiger.deflist(comb_defs) for df in trans.defs: if tr.is_new(df.defines()): aiger.set(tr.new_of(df.defines()),aiger.eval(df.args[1])) miter = il.And(init_var,il.Not(cnst_var),il.Or(invar_fail,il.And(fix(erf),il.Not(fix(cnst_var))))) aiger.set(fail,aiger.eval(miter)) # aiger.sub.debug() # make a decoder for the abstract propositions decoder = dict((y,x) for x,y in prop_abs.iteritems()) for sym in aiger.inputs + aiger.latches: if sym not in decoder and sym in orig_syms: decoder[sym] = sym cnsts = set(sym for syms in sort_constants.values() for sym in syms) return aiger,decoder,annot,cnsts,action,stvarset
def invariance_tactic(prover,goals,proof): goal = goals[0] # pick up the first proof goal conc = ipr.goal_conc(goal) # get its conclusion if not isinstance(conc,TemporalModels): raise iu.IvyError(proof,'proof goal is not temporal') model = conc.model fmla = conc.fmla if not isinstance(fmla,il.Globally): raise iu.IvyError(proof,'invariance tactic applies only to globally formulas') invar = fmla.args[0] if il.is_temporal(invar): raise iu.IvyError(proof,'invariance tactic applies only formulas "globally p"' + ' where p is non-temporal') # Collect the auxiliary invariants invars = [inv.compile() for inv in proof.tactic_decls] # Add the invariant phi to the list invars.append(ipr.clone_goal(goal,[],invar)) # Add the invariant list to the model model = model.clone([]) model.invars = model.invars + invars # Get all the implicit globally properties from the proof # environment. 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 senatic constraint. # # # The following procedure instruments a statement with operator events for # both the property to be proved and the invariant assumptions (all G properties here). # 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. # # TODO: there is something a bit inelegant here, because when we return from # an exported action to the external environment, we need to update the operator # states, however, we do *not* want to do this when returning to in internal caller. # The best solution currently for this is to duplication the actions so there # is one version for internal callers and one for external callers. This issue doesn't # affect this simple invariance tactic, because it doesn't need to update the truth # values of the operators. # Get all the G properties from the prover environment as assumptions assumed_gprops = [x for x in prover.axioms if not x.explicit and x.temporal and il.is_gprop(x.formula)] gprops = [x.formula for x in assumed_gprops] gproplines = [x.lineno for x in assumed_gprops] # We represent the property G phi to be proved by its negation F ~phi. gprops.append(il.Eventually(fmla.environ,il.Not(invar))) gproplines.append(goal.lineno) # Make some memo tables envprops = defaultdict(list) symprops = defaultdict(list) for prop in gprops: envprops[prop.environ].append(prop) for sym in ilu.symbols_ast(prop): symprops[sym].append(prop) 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,iact.Action) else a for a in stmt.args] res = stmt.clone(args) # now add any needed temporal events after this statement event_props = set() # first, if it is a call, we must consider any events associated with # the return if isinstance(stmt,iact.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) # Now, for every property event, we update the property state (none in this case) # and also assert the property semantic constraint. events = [prop_event(prop,lines[prop]) for prop in event_props] res = iact.postfix_action(res,events) stmt.copy_formals(res) # HACK: This shouldn't be needed return res # Add property events to all of the actions: model.bindings = [b.clone([b.action.clone([instr_stmt(b.action.stmt,b.action.labels)])]) for b in model.bindings] # Add all the assumed invariants to the model model.asms.extend([p.clone([p.label,p.formula.args[0]]) for p in assumed_gprops]) # 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] # Change the conclusion formula to M |= true conc = TemporalModels(model,il.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
def vc_to_goal(lineno,name,vc,action): return pr.make_goal(lineno,name,[],lg.Not(lu.clauses_to_formula(vc)), annot=(action,vc.annot))