def bmc(s: Solver) -> None: safety = syntax.And(*get_safety()) n = utils.args.depth utils.logger.always_print( 'bmc checking the following property up to depth %d' % n) utils.logger.always_print(' ' + str(safety)) if not utils.args.relax: def bmc_normal(bound: int) -> Optional[Trace]: return logic.check_bmc(s, safety, bound) bmcer = bmc_normal else: def bmc_relaxed(bound: int) -> Optional[Trace]: return relaxed_traces.check_relaxed_bmc(safety, bound) bmcer = bmc_relaxed for k in range(0, n + 1): if (m := bmcer(k)) is not None: if utils.args.print_counterexample: print('found violation') print(str(m)) break
def as_onestate_formula(self, index: Optional[int] = None) -> Expr: # TODO: move to class State, this shouldn't be here assert self.num_states == 1 or index is not None, \ 'to generate a onestate formula from a multi-state model, ' + \ 'you must specify which state you want' assert index is None or (0 <= index and index < self.num_states) if index is None: index = 0 if index not in self.onestate_formula_cache: prog = syntax.the_program mut_rel_interps = self.rel_interps[index] mut_const_interps = self.const_interps[index] mut_func_interps = self.func_interps[index] vs: List[syntax.SortedVar] = [] ineqs: Dict[SortDecl, List[Expr]] = {} rels: Dict[RelationDecl, List[Expr]] = {} consts: Dict[ConstantDecl, Expr] = {} funcs: Dict[FunctionDecl, List[Expr]] = {} for sort in self.univs: vs.extend(syntax.SortedVar(v, syntax.UninterpretedSort(sort.name)) for v in self.univs[sort]) u = [syntax.Id(v) for v in self.univs[sort]] ineqs[sort] = [syntax.Neq(a, b) for a, b in combinations(u, 2)] for R, l in chain(mut_rel_interps.items(), self.immut_rel_interps.items()): rels[R] = [] for tup, ans in l.items(): e: Expr = ( syntax.AppExpr(R.name, tuple(syntax.Id(col) for col in tup)) if tup else syntax.Id(R.name) ) rels[R].append(e if ans else syntax.Not(e)) for C, c in chain(mut_const_interps.items(), self.immut_const_interps.items()): consts[C] = syntax.Eq(syntax.Id(C.name), syntax.Id(c)) for F, fl in chain(mut_func_interps.items(), self.immut_func_interps.items()): funcs[F] = [ syntax.Eq(syntax.AppExpr(F.name, tuple(syntax.Id(col) for col in tup)), syntax.Id(res)) for tup, res in fl.items() ] # get a fresh variable, avoiding names of universe elements in vs fresh = prog.scope.fresh('x', [v.name for v in vs]) e = syntax.Exists(tuple(vs), syntax.And( *chain(*ineqs.values(), *rels.values(), consts.values(), *funcs.values(), ( syntax.Forall((syntax.SortedVar(fresh, syntax.UninterpretedSort(sort.name)),), syntax.Or(*(syntax.Eq(syntax.Id(fresh), syntax.Id(v)) for v in self.univs[sort]))) for sort in self.univs )))) assert prog.scope is not None with prog.scope.n_states(1): typechecker.typecheck_expr(prog.scope, e, None) self.onestate_formula_cache[index] = e return self.onestate_formula_cache[index]
def assert_any_transition(s: Solver, t: Z3Translator, state_index: int, allow_stutter: bool = False) -> None: prog = syntax.the_program uid = str(state_index) tids = [] for transition in prog.transitions(): tid = z3.Bool(get_transition_indicator(uid, transition.name)) tids.append(tid) s.add( z3.Implies( tid, t.translate_expr( New(transition.as_twostate_formula(prog.scope), state_index)))) if allow_stutter: tid = z3.Bool(get_transition_indicator(uid, '$stutter')) tids.append(tid) frame = syntax.And(*DefinitionDecl._frame(prog.scope, mods=())) s.add(z3.Implies(tid, t.translate_expr(New(frame, state_index)))) s.add(z3.Or(*tids))
def relaxed_program(prog: syntax.Program) -> syntax.Program: new_decls: List[syntax.Decl] = [d for d in prog.sorts()] actives: Dict[syntax.SortDecl, syntax.RelationDecl] = {} for sort in prog.sorts(): name = prog.scope.fresh('active_' + sort.name) r = syntax.RelationDecl(name, arity=[syntax.UninterpretedSort(sort.name)], mutable=True, derived=None, annotations=[]) actives[sort] = r new_decls.append(r) # active relations initial conditions: always true for sort in prog.sorts(): name = prog.scope.fresh(sort.name[0].upper()) expr = syntax.Forall([syntax.SortedVar(name, None)], syntax.Apply(actives[sort].name, [syntax.Id(name)])) new_decls.append(syntax.InitDecl(name=None, expr=expr)) for d in prog.decls: if isinstance(d, syntax.SortDecl): pass # already included above elif isinstance(d, syntax.RelationDecl): if d.derived_axiom is not None: expr = syntax.relativize_quantifiers(actives, d.derived_axiom) new_decls.append(syntax.RelationDecl(d.name, d.arity, d.mutable, expr, d.annotations)) else: new_decls.append(d) elif isinstance(d, syntax.ConstantDecl): new_decls.append(d) elif isinstance(d, syntax.FunctionDecl): new_decls.append(d) elif isinstance(d, syntax.AxiomDecl): new_decls.append(d) elif isinstance(d, syntax.InitDecl): new_decls.append(d) elif isinstance(d, syntax.DefinitionDecl): assert not isinstance(d.body, syntax.BlockStatement), \ "relax does not support transitions written in imperative syntax" mods, expr = d.body expr = syntax.relativize_quantifiers(actives, expr) if d.is_public_transition: guard = syntax.relativization_guard_for_binder(actives, d.binder) expr = syntax.And(guard, expr) new_decls.append(syntax.DefinitionDecl(d.is_public_transition, d.num_states, d.name, params=d.binder.vs, body=(mods, expr))) elif isinstance(d, syntax.InvariantDecl): expr = syntax.relativize_quantifiers(actives, d.expr) new_decls.append(syntax.InvariantDecl(d.name, expr=expr, is_safety=d.is_safety, is_sketch=d.is_sketch)) else: assert False, d new_decls.append(relaxation_action_def(prog, actives=actives, fresh=True)) res = syntax.Program(new_decls) res.resolve() # #sorrynotsorry return res
def verify_inductive_invariant(s: Solver, inv: List[Expr]) -> None: prog = syntax.the_program inits = [init.expr for init in prog.inits()] safeties = [inv.expr for inv in prog.invs() if inv.is_safety] assert logic.check_implication(s, inits, inv) is None assert logic.check_implication(s, inv, safeties) is None assert logic.check_two_state_implication_all_transitions( s, inv, syntax.And(*inv), minimize=False) is None
def is_rel_blocking_relax(trns: Trace, idx: int, derived_rel: Tuple[List[Tuple[syntax.SortedVar, str]], Expr]) -> bool: # TODO: probably can obtain the sort from the sortedvar when not using scapy free_vars, derived_relation_formula = derived_rel free_vars_active_clause = syntax.And(*(active_var(v.name, sort_name) for (v, sort_name) in free_vars)) diffing_formula = syntax.Exists([v for (v, _) in free_vars], syntax.And(syntax.Old(syntax.And(free_vars_active_clause, derived_relation_formula)), syntax.And(free_vars_active_clause, syntax.Not(derived_relation_formula)))) with syntax.the_program.scope.two_state(twostate=True): # TODO: what is this doing? probably misusing diffing_formula.resolve(syntax.the_program.scope, syntax.BoolSort) res = trns.eval_double_vocab(diffing_formula, idx) assert isinstance(res, bool) return cast(bool, res)
def is_rel_blocking_relax_step( trns: Trace, idx: int, derived_rel: Tuple[List[Tuple[syntax.SortedVar, str]], Expr] ) -> bool: # TODO: probably can obtain the sort from the sortedvar when not using pickle free_vars, derived_relation_formula = derived_rel free_vars_active_clause = syntax.And(*(active_var(v.name, sort_name) for (v, sort_name) in free_vars)) diffing_formula = syntax.Exists([v for (v, _) in free_vars], syntax.And(syntax.And(free_vars_active_clause, derived_relation_formula), syntax.New(syntax.And(free_vars_active_clause, syntax.Not(derived_relation_formula))))) with syntax.the_program.scope.fresh_stack(): with syntax.the_program.scope.n_states(2): diffing_formula.resolve(syntax.the_program.scope, syntax.BoolSort) res = trns.eval(diffing_formula, idx) assert isinstance(res, bool) return cast(bool, res)
def itp_gen(s: Solver) -> None: k = 4 prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) candidate = [safety] while True: cti = get_cti(s, syntax.And(*candidate)) if cti is None: break pre_diag = cti[0] with logic.BoundedReachabilityCheck(s, syntax.the_program, k) as bmc_checker: core = bmc_checker.check_and_core(pre_diag) pre_diag.minimize_from_core(core) pre_diag.generalize( s, lambda diag: generalize_cex_omission_checker(s, diag, k)) e = syntax.Not(pre_diag.to_ast()) utils.logger.info('adding new clause to the invariant: %s' % str(e)) candidate.append(e) res = logic.check_implication(s, inits, candidate) if res is not None: utils.logger.always_print( "Failure: candidate %s excludes initial states" % ' & '.join(str(clause) for clause in candidate)) else: utils.logger.always_print("Success! Inductive invariant:") for clause in candidate: utils.logger.always_print(str(clause))
def relativize_decl(d: syntax.DefinitionDecl, actives: Dict[syntax.SortDecl, syntax.RelationDecl], scope: syntax.Scope, inline_relax_actives: bool) -> syntax.DefinitionDecl: mods, expr = d.mods, d.expr expr = syntax.relativize_quantifiers(actives, expr) if d.is_public_transition: guard = syntax.relativization_guard_for_binder(actives, d.binder) expr = syntax.And(guard, expr) if inline_relax_actives: new_mods, new_conjs = relax_actives_action_chunk(scope, actives) mods += new_mods expr = syntax.And(expr, *new_conjs) relativized_def = syntax.DefinitionDecl(d.is_public_transition, d.num_states, d.name, params=d.binder.vs, mods=mods, expr=expr) return relativized_def
def itp_gen(s: Solver) -> None: k = utils.args.forward_depth prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) candidate = [safety] while True: cti = get_cti(s, syntax.And(*candidate)) if cti is None: break pre_diag = cti[0] pre_diag.generalize(s, lambda diag: bmc_upto_bound( s, syntax.Not(diag.to_ast()), k) is None, order=utils.args.generalization_order) e = syntax.Not(pre_diag.to_ast()) utils.logger.info('adding new clause to the invariant: %s' % str(e)) candidate.append(e) if logic.check_implication(s, inits, candidate) is not None: utils.logger.always_print( "Failure: candidate %s excludes initial states" % ' & '.join(str(clause) for clause in candidate)) else: utils.logger.always_print("Success! Inductive invariant:") for clause in candidate: utils.logger.always_print(str(clause))
def oneshot(s: Solver) -> None: prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) candidate = oneshot_compute_inv(s, utils.args.depth, inits, safety, minimize=utils.args.minimize_models) utils.logger.always_print("Got candidate:") for clause in candidate: utils.logger.always_print(str(clause)) verify_inductive_invariant(s, candidate)
def bmc(s: Solver) -> None: safety = syntax.And(*get_safety()) n = utils.args.depth utils.logger.always_print('bmc checking the following property up to depth %d' % n) utils.logger.always_print(' ' + str(safety)) for k in range(0, n + 1): m = logic.check_bmc(s, safety, k) if m is not None: if utils.args.print_counterexample: print('found violation') print(str(m)) break else: print('no violation found.')
def diagram_trace_to_explicitly_relaxed_trace(trace: RelaxedTrace, safety: Sequence[syntax.InvariantDecl]) -> None: relaxed_prog = relaxed_program(syntax.the_program) with syntax.prog_context(relaxed_prog): s = Solver() end_expr = syntax.Not(syntax.And(*(invd.expr for invd in safety))) with syntax.the_program.scope.n_states(1): end_expr.resolve(syntax.the_program.scope, syntax.BoolSort) trace_decl = diagram_trace_to_explicitly_relaxed_trace_decl(trace, end_expr) with syntax.the_program.scope.n_states(1): trace_decl.resolve(syntax.the_program.scope) print(trace_decl) res = bmc_trace(relaxed_prog, trace_decl, s, lambda slvr, ks: logic.check_solver(slvr, ks, minimize=True)) print(res) assert False
def find_something_to_block(self) -> Optional[BackwardReachableState]: utils.logger.info('looking for something to block') while True: # for bstate in self.backwards_reachable_states: # utils.logger.info(f'#{bstate.id} valid_up_to={bstate.known_absent_until_frame} ' # f'steps_to_bad={bstate.num_steps_to_bad}') bstate_min = min(self.backwards_reachable_states, key=lambda b: (b.known_absent_until_frame, b.num_steps_to_bad), default=None) if bstate_min is None or (min_frame_no := bstate_min.known_absent_until_frame) == len(self) - 1: break if isinstance(state := bstate_min.state_or_expr, State): eval_res = state.eval(syntax.And(*(self[min_frame_no + 1].summary()))) assert isinstance(eval_res, bool) if eval_res: return bstate_min
def brat(s: Solver) -> None: k = utils.args.depth prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) current_frame = inits prev_frame: List[Expr] = [syntax.FalseExpr] bad_cache: Set[Diagram] = set() idx = 0 while logic.check_implication(s, current_frame, prev_frame, minimize=False) is not None: idx += 1 prev_frame = current_frame if not utils.args.decrease_depth: current_depth = k else: current_depth = k - idx + 1 if current_depth <= 0: assert False, "exhaused bmc depth: becoming non-positive" current_frame = brat_next_frame(s, prev_frame, current_depth, inits, safety, bad_cache, utils.args.minimize_models) utils.logger.info("Frame: %d" % idx) for c in current_frame: utils.logger.info(str(c)) utils.logger.always_print("Success! Inductive invariant:") for clause in current_frame: utils.logger.always_print(str(clause)) verify_inductive_invariant(s, current_frame)
def to_ast(self) -> Expr: e = syntax.And(*(c for _, _, c in self.conjuncts())) vs = self.binder.vs return syntax.Exists(vs, e)
def load_relaxed_trace_from_updr_cex(prog: Program, s: Solver) -> logic.Trace: import xml.dom.minidom # type: ignore collection = xml.dom.minidom.parse( "paxos_derived_trace.xml").documentElement components: List[syntax.TraceComponent] = [] xml_decls = reversed(collection.childNodes) seen_first = False for elm in xml_decls: if isinstance(elm, xml.dom.minidom.Text): # type: ignore continue if elm.tagName == 'state': diagram = parser.parse_expr(elm.childNodes[0].data) typechecker.typecheck_expr(prog.scope, diagram, syntax.BoolSort) assert isinstance( diagram, syntax.QuantifierExpr) and diagram.quant == 'EXISTS' active_clauses = [ relaxed_traces.active_var(v.name, str(v.sort)) for v in diagram.get_vs() ] if not seen_first: # restrict the domain to be subdomain of the diagram's existentials seen_first = True import itertools # type: ignore for sort, vars in itertools.groupby( diagram.get_vs(), lambda v: v.sort): # TODO; need to sort first free_var = syntax.SortedVar( syntax.the_program.scope.fresh("v_%s" % str(sort)), None) # TODO: diagram simplification omits them from the exists somewhere consts = list( filter(lambda c: c.sort == sort, prog.constants())) els: Sequence[Union[syntax.SortedVar, syntax.ConstantDecl]] els = list(vars) els += consts restrict_domain = syntax.Forall( (free_var, ), syntax.Or(*(syntax.Eq(syntax.Id(free_var.name), syntax.Id(v.name)) for v in els))) active_clauses += [restrict_domain] diagram_active = syntax.Exists( diagram.get_vs(), syntax.And(diagram.body, *active_clauses)) typechecker.typecheck_expr(prog.scope, diagram_active, syntax.BoolSort) components.append(syntax.AssertDecl(expr=diagram_active)) elif elm.tagName == 'action': action_name = elm.childNodes[0].data.split()[0] tcall = syntax.TransitionCalls( calls=[syntax.TransitionCall(target=action_name, args=None)]) components.append(syntax.TraceTransitionDecl(transition=tcall)) else: assert False, "unknown xml tagName" trace_decl = syntax.TraceDecl(components=components, sat=True) migrated_trace = bmc_trace( prog, trace_decl, s, lambda s, ks: logic.check_solver(s, ks, minimize=True), log=False) assert migrated_trace is not None import pickle pickle.dump(migrated_trace, open("migrated_trace.p", "wb")) return migrated_trace
def relaxation_action_def(prog: syntax.Program, actives: Optional[Dict[syntax.SortDecl, syntax.RelationDecl]] = None, fresh: bool = True) -> syntax.DefinitionDecl: decrease_name = (prog.scope.fresh('decrease_domain') if fresh else 'decrease_domain') mods: Tuple[syntax.ModifiesClause, ...] = () conjs: List[Expr] = [] if actives is None: actives = active_rel_by_sort(prog) # a conjunct allowing each domain to decrease new_mods, new_conjs = relax_actives_action_chunk(prog.scope, actives) mods += new_mods conjs += new_conjs # constants are active for const in prog.constants(): conjs.append( syntax.New( syntax.Apply( actives[syntax.get_decl_from_sort(const.sort)].name, (syntax.Id(const.name), )))) # functions map active to active for func in prog.functions(): names: List[str] = [] func_conjs = [] for arg_sort in func.arity: arg_sort_decl = syntax.get_decl_from_sort(arg_sort) name = prog.scope.fresh(arg_sort_decl.name[0].upper(), also_avoid=names) names.append(name) func_conjs.append( syntax.New( syntax.Apply(actives[arg_sort_decl].name, (syntax.Id(name), )))) ap_func = syntax.Apply(func.name, tuple(syntax.Id(name) for name in names)) name = prog.scope.fresh('y', also_avoid=names) active_func = syntax.Let( syntax.SortedVar(name, func.sort), ap_func, syntax.New( syntax.Apply( actives[syntax.get_decl_from_sort(func.sort)].name, (syntax.Id(name), )))) conjs.append( syntax.Forall( tuple(syntax.SortedVar(name, None) for name in names), syntax.Implies(syntax.And(*func_conjs), active_func))) # (relativized) axioms hold after relaxation for axiom in prog.axioms(): if not syntax.is_universal(axiom.expr): conjs.append(syntax.relativize_quantifiers(actives, axiom.expr)) # derived relations have the same interpretation on the active domain for rel in prog.derived_relations(): names = [] rel_conjs = [] for arg_sort in rel.arity: arg_sort_decl = syntax.get_decl_from_sort(arg_sort) name = prog.scope.fresh(arg_sort_decl.name[0].upper(), also_avoid=names) names.append(name) rel_conjs.append( syntax.Apply(actives[arg_sort_decl].name, (syntax.Id(name), ))) ap_rel = syntax.Apply(rel.name, tuple(syntax.Id(name) for name in names)) conjs.append( syntax.Forall( tuple(syntax.SortedVar(name, None) for name in names), syntax.Implies(syntax.And(*rel_conjs), syntax.Iff(syntax.New(ap_rel), ap_rel)))) return syntax.DefinitionDecl(is_public_transition=True, num_states=2, name=decrease_name, params=(), mods=mods, expr=syntax.And(*conjs))
def derived_rels_candidates_from_trace(trns: Trace, more_traces: List[Trace], max_conj_size: int, max_free_vars: int) -> List[Tuple[List[syntax.SortedVar],Expr]]: first_relax_idx = first_relax_step_idx(trns) pre_relax_state = trns.as_state(first_relax_idx) post_relax_state = trns.as_state(first_relax_idx + 1) assert pre_relax_state.univs == post_relax_state.univs # relaxed elements relaxed_elements = [] for sort, univ in pre_relax_state.univs.items(): active_rel_name = 'active_' + sort.name # TODO: de-duplicate pre_active_interp = dict_val_from_rel_name(active_rel_name, pre_relax_state.rel_interp) post_active_interp = dict_val_from_rel_name(active_rel_name, post_relax_state.rel_interp) pre_active_elements = [tup[0] for (tup, b) in pre_active_interp if b] post_active_elements = [tup[0] for (tup, b) in post_active_interp if b] assert set(post_active_elements).issubset(set(pre_active_elements)) for relaxed_elem in utils.OrderedSet(pre_active_elements) - set(post_active_elements): relaxed_elements.append((sort, relaxed_elem)) # pre-relaxation step facts concerning at least one relaxed element (other to be found by UPDR) relevant_facts: List[Union[RelationFact,FunctionFact,InequalityFact]] = [] for rel, rintp in pre_relax_state.rel_interp.items(): for rfact in rintp: (elms, polarity) = rfact relation_fact = RelationFact(rel, elms, polarity) if set(relation_fact.involved_elms()) & set(ename for (_, ename) in relaxed_elements): relevant_facts.append(relation_fact) for func, fintp in pre_relax_state.func_interp.items(): for ffact in fintp: (els_params, els_res) = ffact function_fact = FunctionFact(func, els_params, els_res) if set(function_fact.involved_elms()) & set(ename for (_, ename) in relaxed_elements): relevant_facts.append(function_fact) for sort, elm in relaxed_elements: # other inequalities presumably handled by UPDR for other_elm in pre_relax_state.univs[sort]: if other_elm == elm: continue relevant_facts.append(InequalityFact(elm, other_elm)) # facts blocking this specific relaxation step diff_conjunctions = [] candidates_cache: Set[str] = set() for fact_lst in itertools.combinations(relevant_facts, max_conj_size): elements = utils.OrderedSet(itertools.chain.from_iterable(fact.involved_elms() for fact in fact_lst)) relaxed_elements_relevant = [elm for (_, elm) in relaxed_elements if elm in elements] vars_from_elm = dict((elm, syntax.SortedVar(None, syntax.the_program.scope.fresh("v%d" % i), None)) for (i, elm) in enumerate(elements)) parameter_elements = elements - set(relaxed_elements_relevant) if len(parameter_elements) > max_free_vars: continue conjuncts = [fact.as_expr(lambda elm: vars_from_elm[elm].name) for fact in fact_lst] # for elm, var in vars_from_elm.items(): # TODO: make the two loops similar for elm in relaxed_elements_relevant: var = vars_from_elm[elm] sort = pre_relax_state.element_sort(elm) active_element_conj = syntax.Apply('active_%s' % sort.name, [syntax.Id(None, var.name)]) conjuncts.append(active_element_conj) derived_relation_formula = syntax.Exists([vars_from_elm[elm] for (_, elm) in relaxed_elements if elm in vars_from_elm], syntax.And(*conjuncts)) if str(derived_relation_formula) in candidates_cache: continue candidates_cache.add(str(derived_relation_formula)) if closing_qa_cycle(syntax.the_program, [pre_relax_state.element_sort(elm) for elm in parameter_elements], [pre_relax_state.element_sort(elm) for elm in relaxed_elements_relevant]): # adding the derived relation would close a quantifier alternation cycle, discard the candidate continue # if trns.eval_double_vocab(diffing_formula, first_relax_idx): if is_rel_blocking_relax(trns, first_relax_idx, ([(vars_from_elm[elm], pre_relax_state.element_sort(elm).name) for elm in parameter_elements], derived_relation_formula)): # if all(trs.eval_double_vocab(diffing_formula, first_relax_step_idx(trs)) for trs in more_traces): diff_conjunctions.append(([vars_from_elm[elm] for elm in parameter_elements], derived_relation_formula)) return diff_conjunctions
def relaxation_action_def(prog: syntax.Program, actives: Optional[Dict[syntax.SortDecl, syntax.RelationDecl]]=None, fresh: bool=True) \ -> syntax.DefinitionDecl: decrease_name = (prog.scope.fresh('decrease_domain') if fresh else 'decrease_domain') mods = [] conjs: List[Expr] = [] if actives is None: actives = active_rel_by_sort(prog) # a conjunct allowing each domain to decrease for sort in prog.sorts(): name = prog.scope.fresh(sort.name[0].upper()) ap = syntax.Apply(actives[sort].name, [syntax.Id(None, name)]) expr = syntax.Forall([syntax.SortedVar(None, name, None)], syntax.Implies(ap, syntax.Old(ap))) conjs.append(expr) mods.append(syntax.ModifiesClause(None, actives[sort].name)) # constants are active for const in prog.constants(): conjs.append(syntax.Apply(actives[syntax.get_decl_from_sort(const.sort)].name, [syntax.Id(None, const.name)])) # functions map active to active for func in prog.functions(): names: List[str] = [] func_conjs = [] for arg_sort in func.arity: arg_sort_decl = syntax.get_decl_from_sort(arg_sort) name = prog.scope.fresh(arg_sort_decl.name[0].upper(), also_avoid=names) names.append(name) func_conjs.append(syntax.Apply(actives[arg_sort_decl].name, [syntax.Id(None, name)])) ap_func = syntax.Old(syntax.Apply(func.name, [syntax.Id(None, name) for name in names])) active_func = syntax.Apply(actives[syntax.get_decl_from_sort(func.sort)].name, [ap_func]) conjs.append(syntax.Forall([syntax.SortedVar(None, name, None) for name in names], syntax.Implies(syntax.And(*func_conjs), active_func))) # (relativized) axioms hold after relaxation for axiom in prog.axioms(): if not syntax.is_universal(axiom.expr): conjs.append(syntax.relativize_quantifiers(actives, axiom.expr)) # derived relations have the same interpretation on the active domain for rel in prog.derived_relations(): names = [] rel_conjs = [] for arg_sort in rel.arity: arg_sort_decl = syntax.get_decl_from_sort(arg_sort) name = prog.scope.fresh(arg_sort_decl.name[0].upper(), also_avoid=names) names.append(name) rel_conjs.append(syntax.Apply(actives[arg_sort_decl].name, [syntax.Id(None, name)])) ap_rel = syntax.Apply(rel.name, [syntax.Id(None, name) for name in names]) conjs.append(syntax.Forall([syntax.SortedVar(None, name, None) for name in names], syntax.Implies(syntax.And(*rel_conjs), syntax.Iff(ap_rel, syntax.Old(ap_rel))))) return syntax.DefinitionDecl(None, public=True, twostate=True, name=decrease_name, params=[], body=(mods, syntax.And(*conjs)))