def merge_some_signals(cube, C, aig, argv): # TODO: there must be a more pythonic way of doing all of this log.LOG_MSG(str(len(C)) + " sub-games originally") cube_deps = aig.get_bdd_deps(cube) dep_map = dict() for c in C: deps = frozenset(cube_deps | aig.get_lit_deps(c)) log.DBG_MSG("Current deps: " + str(deps)) found = False for key in dep_map: if key >= deps: log.DBG_MSG("Some key subsumes deps") dep_map[key] &= aig.lit2bdd(c) found = True break elif key <= deps: log.DBG_MSG("New deps subsumes some key") if deps in dep_map: log.DBG_MSG("AND... the deps existed already") dep_map[deps] &= dep_map[key] & aig.lit2bdd(c) else: dep_map[deps] = dep_map[key] & aig.lit2bdd(c) del dep_map[key] found = True break if not found: log.DBG_MSG("Adding a new dict element") dep_map[deps] = aig.lit2bdd(c) log.LOG_MSG(str(len(dep_map.keys())) + " sub-games after incl. red.") for key in dep_map: yield ~dep_map[key] & cube
def subgame_mapper(games, aig): s = None cum_s = None cnt = 0 pair_list = [] for game in games: assert isinstance(game, BackwardGame) w = backward_safety_synth(game) cnt += 1 # short-circuit a negative response if w is None: log.DBG_MSG("Short-circuit exit 1 after sub-game #" + str(cnt)) return None s = game.cpre(w, get_strat=True) if cum_s is None: cum_s = s else: cum_s &= s # another short-circuit exit if (not cum_s or not game.init() & cum_s): log.DBG_MSG("Short-circuit exit 2 after sub-game #" + str(cnt)) return None pair_list.append((game, s, w)) log.DBG_MSG("Solved " + str(cnt) + " sub games.") # lets simplify transition functions # aig.restrict_latch_next_funs(cum_s) return pair_list
def fixpoint(s, fun, early_exit=never): """ fixpoint of monotone function starting from s """ prev = None cur = s cnt = 0 while prev is None or prev != cur: prev = cur cur = fun(prev) cnt += 1 if early_exit(cur): log.DBG_MSG("Early exit after " + str(cnt) + " steps.") return cur log.DBG_MSG("Fixpoint reached after " + str(cnt) + " steps.") return cur
def upost(self, q): assert isinstance(q, BDD) if q in self.succ_cache: return iter(self.succ_cache[q]) A = BDD.true() M = set() while A != BDD.false(): a = A.get_one_minterm(self.uinputs) trans = BDD.make_cube( imap( lambda x: BDD.make_eq( BDD(self.aig.get_primed_var(x.lit)), self.aig.lit2bdd(x.next).and_abstract( q, self.latch_cube)), self.aig.iterate_latches())) lhs = trans & a rhs = self.aig.prime_all_inputs_in_bdd(trans) simd = BDD.make_impl(lhs, rhs).univ_abstract(self.platch_cube)\ .exist_abstract(self.pcinputs_cube)\ .univ_abstract(self.cinputs_cube) simd = self.aig.unprime_all_inputs_in_bdd(simd) A &= ~simd Mp = set() for m in M: if not (BDD.make_impl(m, simd) == BDD.true()): Mp.add(m) M = Mp M.add(a) log.DBG_MSG("Upost |M| = " + str(len(M))) self.succ_cache[q] = map(lambda x: (q, x), M) return iter(self.succ_cache[q])
def subgame_reducer(games, aig, argv, a=None, b=None, c=None): assert games if a is None: a = 2 if b is None: b = -1 if c is None: c = -1 while len(games) >= 2: triple_list = [] # we first compute an fij function for all pairs for i in range(0, len(games) - 1): for j in range(i + 1, len(games)): li = set(aig.get_bdd_latch_deps(games[i][1])) lj = set(aig.get_bdd_latch_deps(games[j][1])) cij = len(li & lj) nij = len(li | lj) sij = (games[i][1] & games[j][1]).dag_size() triple_list.append((i, j, a * cij + b * nij + c * sij)) # now we get the best pair according to the fij function (i, j, val) = max(triple_list, key=lambda x: x[2]) log.DBG_MSG("We must reduce games " + str(i) + " and " + str(j)) # we must reduce games i and j now game = ConcGame(BDDAIG(aig).short_error(~(games[i][1] & games[j][1])), use_trans=argv.use_trans) w = backward_safety_synth(game) if w is None: return None else: s = game.cpre(w, get_strat=True) games[i] = (game, s, w) games.pop(j) # lets simplify the transition relations # aig.restrict_latch_next_funs(s) return games[0][2]
def add_clause(self, lits, avoid_checks=False): # just a simple check to get rid of False in clauses and clauses that # are trivially true if 1 in lits: log.DBG_MSG("The clause includes lit 1. It is trivially true.") return self if 0 in lits: log.DBG_MSG("The clause includes lit 0. I removed it.") lits = [l for l in lits if l != 0] # if not avoid_checks: # if next((c for c in self.clauses if c <= lits), None): # log.DBG_MSG("The clause is subsumed by an existing one.") # return # self.clauses = set(filter(lambda x: not lits <= x, self.clauses)) self.clauses.add(frozenset(lits)) return self
def extract_output_funcs(strategy, care_set=None): """ Calculate BDDs for output functions given non-deterministic winning strategy. """ if care_set is None: care_set = bdd.true() output_models = dict() all_outputs = get_controllable_inputs_bdds() for c in get_controllable_inputs_bdds(): others = set(set(all_outputs) - set([c])) if others: others_cube = bdd.get_cube(others) c_arena = strategy.exist_abstract(others_cube) else: c_arena = strategy # pairs (x,u) in which c can be true can_be_true = c_arena.cofactor(c) # pairs (x,u) in which c can be false can_be_false = c_arena.cofactor(~c) must_be_true = (~can_be_false) & can_be_true must_be_false = (~can_be_true) & can_be_false local_care_set = care_set & (must_be_true | must_be_false) # Restrict operation: # on care_set: must_be_true.restrict(care_set) <-> must_be_true c_model = min([ must_be_true.safe_restrict(local_care_set), (~must_be_false).safe_restrict(local_care_set) ], key=bdd.dag_size) output_models[c] = c_model log.DBG_MSG("Size of function for " + str(c.get_index()) + " = " + str(c_model.dag_size())) strategy &= bdd.make_eq(c, c_model) return output_models
def compose_abs_transition_bdd(): global cached_abs_transition, procd_blocks # check cache if cached_abs_transition is None: log.DBG_MSG("Rebuilding abstract transition relation") for b in preds.abs_blocks: procd_blocks[b] = False c = bdd.true() else: c = cached_abs_transition latches = [x.lit for x in iterate_latches_and_error()] latches_bdd = bdd.get_cube(get_all_latches_as_bdds()) latch_funs = [ get_bdd_for_aig_lit(x.next) for x in iterate_latches_and_error() ] for b in preds.abs_blocks: if b not in procd_blocks or not procd_blocks[b]: procd_blocks[b] = True temp = bdd.make_eq(bdd.BDD(get_primed_variable(b)), preds.block_to_bdd[b]) c &= temp.compose(latches, latch_funs) # cache c cached_abs_transition = c return c.and_abstract(compose_abs_eq_bdd(), latches_bdd)
def synthesize(): if use_trans: log.register_sum("trans_time", "Time spent building transition relation: ") log.start_clock() compose_transition_bdd() log.stop_clock("trans_time") init_state_bdd = compose_init_state_bdd() error_bdd = bdd.BDD(error_fake_latch.lit) reach_over = [] # use abstraction to minimize state space exploration if ini_reach: initial_abstraction() for j in range(ini_reach): preds.drop_latches() # add some latches make_vis = (aig.num_latches() + 1) // ini_reach_latches log.DBG_MSG("Making visible " + str(make_vis) + " latches") for i in range(make_vis): preds.loc_red() log.DBG_MSG("Computing reachable states over-app") abs_reach_region = fp(compose_abs_init_state_bdd(), fun=lambda x: x | post_bdd_abs(x)) reach_over.append(gamma(abs_reach_region)) # get the winning region for controller def min_pre(s): for r in reach_over: s = s.restrict(r) result = pre_env_bdd(s) for r in reach_over: result = result.restrict(r) return s | result log.DBG_MSG("Computing fixpoint of UPRE") win_region = ~fp(error_bdd, fun=min_pre, early_exit=lambda x: x & init_state_bdd != bdd.false()) if win_region & init_state_bdd == bdd.false(): log.LOG_MSG("The spec is unrealizable.") log.LOG_ACCUM() return (None, None) else: log.LOG_MSG("The spec is realizable.") log.LOG_ACCUM() return (win_region, reach_over)
def introduce_error_latch(self): if self.error_fake_latch is not None: return self.error_fake_latch = new_aiger_symbol() error_symbol = self.get_err_symbol() self.error_fake_latch.lit = self.next_lit() self.error_fake_latch.name = "fake_error_latch" self.error_fake_latch.next = error_symbol.lit log.DBG_MSG("Error fake latch = " + str(self.error_fake_latch.lit))
def __init__(self, aiger_file_name, intro_error_latch=False): self.spec = aiger_init() err = aiger_open_and_read_from_file(self.spec, aiger_file_name) assert not err, err # introduce a fake latch for the error and call the given hook self.error_fake_latch = None if intro_error_latch: self.introduce_error_latch() # initialize caches self._1l_land_cache = dict() self._deps_cache = dict() # dump some info about the spec if not log.debug: return latches = [x.lit for x in self.iterate_latches()] log.DBG_MSG(str(len(latches)) + " Latches: " + str(latches)) uinputs = [x.lit for x in self.iterate_uncontrollable_inputs()] log.DBG_MSG(str(len(uinputs)) + " U. Inputs: " + str(uinputs)) cinputs = [x.lit for x in self.iterate_controllable_inputs()] log.DBG_MSG(str(len(cinputs)) + " C. Inputs: " + str(cinputs))
def short_error(self, b): nu_bddaig = BDDAIG(aig=self) nu_bddaig.set_lit2bdd(self.error_fake_latch.next, b) latch_deps = self.get_bdd_latch_deps(b) if log.debug: not_deps = [ l.lit for l in self.iterate_latches() if l.lit not in latch_deps ] log.DBG_MSG(str(len(not_deps)) + " Latches not needed") nu_bddaig.latch_restr = latch_deps nu_bddaig.restrict_latch_next_funs(~b) return nu_bddaig
def backward_safety_synth(game): assert isinstance(game, BackwardGame) init_state = game.init() error_states = game.error() log.DBG_MSG("Computing fixpoint of UPRE.") win_region = ~fixpoint(error_states, fun=lambda x: x | game.upre(x), early_exit=lambda x: x & init_state) if not (win_region & init_state): return None else: return win_region
def declare_winner(controllable, conc_lose): log.LOG_MSG("Nr. of predicates: " + str(len(preds.abs_blocks))) log.LOG_ACCUM() if controllable: log.LOG_MSG("The spec is realizable.") if compute_win_region: # make sure we reached the fixpoint log.DBG_MSG("Get winning region") return (~fp(bdd.BDD(error_fake_latch.lit) | conc_lose, fun=lambda x: x | pre_env_bdd(x)), []) else: return (~conc_lose, []) log.LOG_MSG("The spec is unrealizable.") return (None, [])
def comp_synth(games): s = BDD.true() cum_w = BDD.true() cnt = 0 for game in games: assert isinstance(game, BackwardGame) w = backward_safety_synth(game) cnt += 1 # short-circuit a negative response if w is None: log.DBG_MSG("Short-circuit exit after sub-game #" + str(cnt)) return (None, None) if s is None: s = game.cpre(w, get_strat=True) cum_w = w else: s &= game.cpre(w, get_strat=True) cum_w &= w # sanity check before moving forward if (not s or not game.init() & s): return (None, None) # we must aggregate everything now log.DBG_MSG("Solved " + str(cnt) + " sub games.") return (cum_w, s)
def initial_abstraction(): global preds introduce_error_latch() all_latches = [x.lit for x in aig.iterate_latches()] all_latches_and_error = list(all_latches + [error_fake_latch.lit]) all_latches_and_error_next = ( [get_bdd_for_aig_lit(x.next) for x in aig.iterate_latches()] + [get_bdd_for_aig_lit(error_fake_latch.next)]) preds = spred.SmartPreds(supp=all_latches_and_error, supp_next=all_latches_and_error_next) preds.add_fixed_pred("unsafe", bdd.BDD(error_fake_latch.lit)) preds.add_fixed_pred("init", bdd.get_clause([bdd.BDD(l) for l in all_latches])) log.DBG_MSG("Initial abstraction of the system computed.") return True
def _read_delays(self, time_file_name): delays = dict() latches = [x.lit for x in self.aig.iterate_latches()] with open(time_file_name, 'r') as fp: for l in range(len(latches)): s = fp.readline() if s <> "": #print "Doing line:<{0}>".format(s) #print s.split(" ") si = map(lambda x: int(x), s.split(" ")) assert (len(si) == 2) delays[latches[l]] = (si[0] * self.factor, si[1] * self.factor) else: delays[latches[l]] = (self.factor, self.factor) log.DBG_MSG("Latch delays: " + str(delays)) return delays
def forward_safety_synth(game): assert isinstance(game, ForwardGame) init_state = game.init() error_states = game.error() tracker = game.visit_tracker() depend = dict() depend[init_state] = set() waiting = [(init_state, game.upost(init_state))] while waiting and not tracker.is_in_attr(init_state): (s, sp_iter) = waiting.pop() try: sp = next(sp_iter) except StopIteration: continue # nothing to do here # push the rest of the iterator back into the stack waiting.append((s, sp_iter)) # process s, sp_iter if not tracker.is_visited(sp): tracker.visit(sp) tracker.mark_in_attr( sp, game.is_env_state(sp) and bool(sp & error_states)) if sp in depend: depend[sp].add((s, iter([sp]))) else: depend[sp] = set([(s, iter([sp]))]) if tracker.is_in_attr(sp): waiting.append((s, iter([sp]))) else: if game.is_env_state(sp): waiting.append((sp, game.upost(sp))) else: waiting.append((sp, game.cpost(sp))) else: local_lose = any(imap(tracker.is_in_attr, game.upost(s)))\ if game.is_env_state(s)\ else all(imap(tracker.is_in_attr, game.cpost(s))) if local_lose: tracker.mark_in_attr(s, True) waiting.extend(depend[s]) if not tracker.is_in_attr(sp): depend[sp].add((s, sp)) log.DBG_MSG("OTFUR, losing[init_state] = " + str(tracker.is_in_attr(init_state))) return None if tracker.is_in_attr(init_state) else True
def compose_abs_eq_bdd(): global cached_abs_eq, abs_eq_procd_blocks # check cache if cached_abs_eq is None: log.DBG_MSG("Rebuilding abs_eq") for b in preds.abs_blocks: abs_eq_procd_blocks[b] = False c = bdd.true() else: c = cached_abs_eq for b in preds.abs_blocks: if b not in procd_blocks or not procd_blocks[b]: c &= bdd.make_eq(bdd.BDD(b), preds.block_to_bdd[b]) # cache c cached_abs_eq = c return c
def update_block_funs(): global cached_block_funs, block_funs latches = [x.lit for x in iterate_latches_and_error()] latch_funs = [ get_bdd_for_aig_lit(x.next) for x in iterate_latches_and_error() ] # check cache if cached_block_funs is None: log.DBG_MSG("Rebuilding block_funs") block_funs = dict() for b in preds.abs_blocks: block_funs[b] = gamma(preds.block_to_bdd[b]).compose( latches, latch_funs) else: for b in preds.abs_blocks: if b not in block_funs: block_funs[b] = gamma(preds.block_to_bdd[b]) block_funs[b] = block_funs[b].compose(latches, latch_funs) # set cache cached_block_funs = bdd.true()
def decompose(aig, argv): if argv.decomp == 1: if lit_is_negated(aig.error_fake_latch.next): log.DBG_MSG("Decomposition opt possible (BIG OR case)") (A, B) = aig.get_1l_land(strip_lit(aig.error_fake_latch.next)) return imap( lambda a: ConcGame(BDDAIG(aig).short_error(a), use_trans=argv.use_trans), merge_some_signals(BDD.true(), A, aig, argv)) else: (A, B) = aig.get_1l_land(aig.error_fake_latch.next) if not B: log.DBG_MSG("No decomposition opt possible") return None else: log.DBG_MSG("Decomposition opt possible (A ^ [C v D] case)") log.DBG_MSG(str(len(A)) + " AND leaves: " + str(A)) # critical heuristic: which OR leaf do we distribute? # here I propose to choose the one with the most children b = B.pop() (C, D) = aig.get_1l_land(b) for bp in B: (Cp, Dp) = aig.get_1l_land(bp) if len(Cp) > len(C): b = bp C = Cp log.DBG_MSG("Chosen OR: " + str(b)) rem_AND_leaves = filter(lambda x: strip_lit(x) != b, A) rdeps = set() for r in rem_AND_leaves: rdeps |= aig.get_lit_deps(strip_lit(r)) log.DBG_MSG("Rem. AND leaves' deps: " + str(rdeps)) cube = BDD.make_cube(map(aig.lit2bdd, rem_AND_leaves)) log.DBG_MSG( str(len(C)) + " OR leaves: " + str(map(aig.get_lit_name, C))) return imap( lambda a: ConcGame(BDDAIG(aig).short_error(a), use_trans=argv.use_trans), merge_some_signals(cube, C, aig, argv)) elif argv.decomp == 2: raise NotImplementedError
def cpost(self, s): assert isinstance(s, tuple) q = s[0] au = s[1] if s in self.succ_cache: L = self.succ_cache[s] else: L = BDD.make_cube( imap(lambda x: BDD.make_eq(BDD(x.lit), self.aig.lit2bdd(x.next) .and_abstract(q & au, self.latch_cube & self.uinputs_cube)), self.aig.iterate_latches()))\ .exist_abstract(self.cinputs_cube) self.succ_cache[s] = L M = set() while L != BDD.false(): l = L.get_one_minterm(self.latches) L &= ~l self.Venv[l] = True M.add(l) log.DBG_MSG("Cpost |M| = " + str(len(M))) return iter(M)
def main(aiger_file_name, out_file): aig.parse_into_spec(aiger_file_name) log.DBG_MSG("AIG spec file parsed") log.LOG_MSG("Nr. of latches: " + str(aig.num_latches())) log.DBG_MSG("Latches: " + str([x.lit for x in iterate_latches_and_error()])) log.DBG_MSG("U. Inputs: " + str([x.lit for x in aig.iterate_uncontrollable_inputs()])) log.DBG_MSG("C. Inputs: " + str([x.lit for x in aig.iterate_controllable_inputs()])) # realizability and preliminary synthesis if use_abs: (win_region, reach_over) = abs_synthesis(out_file is not None) else: (win_region, reach_over) = synthesize() if out_file and win_region: log.LOG_MSG("Win region bdd node count = " + str(win_region.dag_size())) strategy = single_pre_sys_bdd(win_region, get_strat=True) log.LOG_MSG("Strategy bdd node count = " + str(strategy.dag_size())) func_by_var = extract_output_funcs(strategy, win_region) # attempt to minimize the winning region for r in reach_over: for (c_bdd, func_bdd) in func_by_var.items(): func_by_var[c_bdd] = func_bdd.safe_restrict(r) log.DBG_MSG("Min'd version size " + str(func_by_var[c_bdd].dag_size())) # attempt to minimize the winning region if min_win: bdd.disable_reorder() strategy = bdd.true() for (c_bdd, func_bdd) in func_by_var.items(): strategy &= bdd.make_eq(c_bdd, func_bdd) win_region = fp(compose_init_state_bdd(), fun=lambda x: x | post_bdd(x, strategy)) for (c_bdd, func_bdd) in func_by_var.items(): func_by_var[c_bdd] = func_bdd.safe_restrict(win_region) log.DBG_MSG("Min'd version size " + str(func_by_var[c_bdd].dag_size())) # model check? if model_check: strategy = bdd.true() for (c_bdd, func_bdd) in func_by_var.items(): strategy &= bdd.make_eq(c_bdd, func_bdd) assert (fp(bdd.BDD(error_fake_latch.lit), fun=lambda x: x | single_pre_bdd(x, strategy)) & compose_init_state_bdd()) == bdd.false() # print out the strategy total_dag = 0 for (c_bdd, func_bdd) in func_by_var.items(): total_dag += func_bdd.dag_size() model_to_aiger(c_bdd, func_bdd) log.LOG_MSG("Sum of func dag sizes = " + str(total_dag)) log.LOG_MSG("# of added gates = " + str(len(bdd_gate_cache))) aig.write_spec(out_file) return True elif win_region: return True else: return False
def restrict_latch_next_funs(self, b): log.DBG_MSG("Restricting next funs") for l in self.iterate_latches(): if l != self.error_fake_latch: self.set_lit2bdd(l.next, self.lit2bdd(l.next).safe_restrict(b))
def synth_from_spec(aig, argv): # Explicit approach if argv.use_symb: assert argv.out_file is None symgame = SymblicitGame(aig) w = forward_safety_synth(symgame) # Symbolic approach with compositional opts elif argv.decomp is not None: game_it = decompose(aig, argv) # if there was no decomposition possible then call simple # solver if game_it is None: argv.decomp = None return synth_from_spec(aig, argv) if argv.comp_algo == 1: # solve and aggregate sub-games (w, strat) = comp_synth(game_it) # back to the general game if w is None: return False log.DBG_MSG("Interm. win region bdd node count = " + str(w.dag_size())) game = ConcGame(BDDAIG(aig).short_error(~strat), use_trans=argv.use_trans) w = backward_safety_synth(game) elif argv.comp_algo == 2: games_mapped = subgame_mapper(game_it, aig) # local aggregation yields None if short-circ'd if games_mapped is None: return False w = subgame_reducer(games_mapped, aig, argv) elif argv.comp_algo == 3: # solve games by up-down algo gen_game = ConcGame(aig, use_trans=argv.use_trans) w = comp_synth3(game_it, gen_game) elif argv.comp_algo == 4: # solve games by up-down algo gen_game = ConcGame(aig, use_trans=argv.use_trans) w = comp_synth4(game_it, gen_game) else: raise NotImplementedError() # Symbolic approach (avoiding compositional opts) else: game = ConcGame(aig, use_trans=argv.use_trans) w = backward_safety_synth(game) # final check if w is None: return False log.DBG_MSG("Win region bdd node count = " + str(w.dag_size())) # synthesis from the realizability analysis if w is not None: if argv.out_file is not None: log.DBG_MSG("Win region bdd node count = " + str(w.dag_size())) c_input_info = [] n_strategy = aig.cpre_bdd(w, get_strat=True) func_per_output = aig.extract_output_funs(n_strategy, care_set=w) if argv.only_transducer: for c in aig.iterate_controllable_inputs(): c_input_info.append((c.lit, c.name)) for (c, func_bdd) in func_per_output.items(): aig.input2and(c, aig.bdd2aig(func_bdd)) if argv.only_transducer: aig.remove_outputs() for (l, n) in c_input_info: aig.add_output(l, n) aig.write_spec(argv.out_file) return True else: return False
def abs_synthesis(compute_win_region=False): global use_abs # declare winner def declare_winner(controllable, conc_lose): log.LOG_MSG("Nr. of predicates: " + str(len(preds.abs_blocks))) log.LOG_ACCUM() if controllable: log.LOG_MSG("The spec is realizable.") if compute_win_region: # make sure we reached the fixpoint log.DBG_MSG("Get winning region") return (~fp(bdd.BDD(error_fake_latch.lit) | conc_lose, fun=lambda x: x | pre_env_bdd(x)), []) else: return (~conc_lose, []) log.LOG_MSG("The spec is unrealizable.") return (None, []) # make sure that we have something to abstract if aig.num_latches() == 0: log.WRN_MSG("No latches in spec. Defaulting to regular synthesis.") use_abs = False return synthesize() # update loss steps local_loss_steps = (aig.num_latches() + 1) // loss_steps log.DBG_MSG("Loss steps = " + str(local_loss_steps)) # registered quants steps = 0 log.register_sum("ref_cnt", "Nr. of refinements: ") log.register_sum("abs_time", "Time spent abstracting: ") log.register_sum("uabs_time", "Time spent computing under-app of fp: ") log.register_sum("oabs_time", "Time spent exhausting info of over-app: ") log.register_average("unsafe_bdd_size", "Average unsafe iterate bdd size: ") # create the abstract game initial_abstraction() error_bdd = alpha_under(bdd.BDD(error_fake_latch.lit)) # add some latches if ini_latch: make_vis = (aig.num_latches() + 1) // ini_latch log.DBG_MSG("Making visible " + str(make_vis) + " latches") for i in range(make_vis): preds.loc_red() # first over-approx of the reachable region reachable_bdd = bdd.true() # The REAL algo while True: log.start_clock() if use_trans: transition_bdd = compose_abs_transition_bdd() log.BDD_DMP(transition_bdd, "transition relation") init_state_bdd = compose_abs_init_state_bdd() log.BDD_DMP(init_state_bdd, "initial state set") log.BDD_DMP(error_bdd, "unsafe state set") log.stop_clock("abs_time") # STEP 1: check if under-approx is losing log.DBG_MSG("Computing over approx of FP") log.start_clock() under_fp = fp(error_bdd, fun=lambda x: (reachable_bdd & (x | pre_env_bdd_uabs(x)))) log.stop_clock("uabs_time") if (init_state_bdd & under_fp) != bdd.false(): return declare_winner(False) # STEP 2: exhaust information from the abstract game, i.e. # update the reachability information we have log.start_clock() prev_reach = bdd.false() reach = reachable_bdd while prev_reach != reach: prev_reach = reach # STEP 2.1: check if the over-approx is winning log.DBG_MSG("Computing over approx of FP") over_fp = fp(under_fp, fun=lambda x: (reach & (x | pre_env_bdd_abs(x)))) if (over_fp & init_state_bdd) == bdd.false(): log.DBG_MSG("FP of the over-approx losing region not initial") return declare_winner(True, gamma(under_fp)) # if there is no early exit we compute a strategy for Env env_strats = pre_env_bdd_abs(over_fp, get_strat=True) log.DBG_MSG("Computing over approx of Reach") reach = fp(init_state_bdd, fun=lambda x: (reach & (x | post_bdd_abs(x, env_strats)))) log.stop_clock("oabs_time") # STEP 3: refine or declare controllable log.DBG_MSG("Concretizing the strategy of Env") conc_env_strats = gamma(env_strats) conc_reach = gamma(reach) conc_under_fp = gamma(under_fp) log.DBG_MSG("Taking one step of UPRE in the concrete game") conc_step = single_pre_env_bdd(conc_under_fp, env_strat=conc_env_strats) conc_step &= conc_reach if bdd.make_impl(conc_step, conc_under_fp) == bdd.true(): log.DBG_MSG("The concrete step revealed we are at the FP") return declare_winner(True, conc_under_fp) else: # drop latches every number of steps reset = False if (steps != 0 and steps % local_loss_steps == 0): log.DBG_MSG("Dropping all visible latches!") reset = preds.drop_latches() # add new predicates and reset caches if necessary nu_losing_region = conc_step | conc_under_fp reset |= preds.add_fixed_pred("reach", conc_reach) reset |= preds.add_fixed_pred("unsafe", nu_losing_region) # find interesting set of latches log.DBG_MSG("Localization reduction step.") reset |= preds.loc_red(not_imply=nu_losing_region) log.DBG_MSG("# of predicates = " + str(len(preds.abs_blocks))) if reset: reset_caches() # update error bdd log.push_accumulated("unsafe_bdd_size", nu_losing_region.dag_size()) error_bdd = alpha_under(nu_losing_region) # update reachable area reachable_bdd = alpha_over(conc_reach) steps += 1 log.push_accumulated("ref_cnt", 1)
def comp_synth4(games, gen_game): s = None cum_s = None cum_w = None cnt = 0 triple_list = [] for game in games: assert isinstance(game, BackwardGame) w = backward_safety_synth(game) cnt += 1 # short-circuit a negative response if w is None: log.DBG_MSG("Short-circuit exit 1 after sub-game #" + str(cnt)) return None s = game.cpre(w, get_strat=True) if cum_s is None: cum_s = s cum_w = w else: cum_s &= s cum_w &= w # another short-circuit exit if (not cum_s or not game.init() & cum_s): log.DBG_MSG("Short-circuit exit 2 after sub-game #" + str(cnt)) return None triple_list.append((game, s, w)) log.DBG_MSG("Solved " + str(cnt) + " sub games.") # lets simplify transition functions gen_game.aig.restrict_latch_next_funs(cum_s) # what comes next is a fixpoint computation using a UPRE # step at a time in the global game and using it to get more # information from the local sub-games lose = BDD.true() lose_next = ~cum_w | gen_game.error() while lose_next != lose: lose = lose_next log.DBG_MSG("Doing global UPRE") lose_next = lose | gen_game.upre(lose) for i in range(len(triple_list)): wt = triple_list[i][2] gamet = triple_list[i][0] local_deps = set([x.lit for x in gamet.aig.iterate_latches()]) rem_lats = gen_game.aig.get_bdd_latch_deps(lose_next) - local_deps pt = lose_next if rem_lats: pt = lose_next.univ_abstract(BDD.make_cube(map(BDD, rem_lats))) # log.BDD_DMP(lose_next, "global losing area iterate") # log.BDD_DMP(pt, "new losing area") assert BDD.make_impl(~wt, pt) == BDD.true() if BDD.make_impl(pt, ~wt) != BDD.true(): gamet.short_error = pt wt = backward_safety_synth(gamet) if (wt is None or not gamet.init() & wt): log.DBG_MSG("Short-circuit exit 3") return None st = gamet.cpre(wt, get_strat=True) gen_game.aig.restrict_latch_next_funs(wt) triple_list[i] = (gamet, st, wt) for t in triple_list: lose_next |= ~t[2] # after the fixpoint has been reached we can compute the error win = ~lose if (not win or not gen_game.init() & win): return None else: return win