def over_post_bdd(self, src_states_bdd, sys_strat=None): """ Over-approximated version of concrete post which can be done even without the transition relation """ strat = BDD.true() if sys_strat is not None: strat &= sys_strat # to do this, we use an over-simplified transition relation, EXu,Xc b = BDD.true() for x in self.iterate_latches(): temp = BDD.make_eq(BDD(self.get_primed_var(x.lit)), self.lit2bdd(x.next)) b &= temp.and_abstract( strat, BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.iterate_controllable_inputs()))) b = b.restrict(src_states_bdd) b &= src_states_bdd b = b.exist_abstract( BDD.make_cube( imap( funcomp(BDD, symbol_lit), chain(self.iterate_latches(), self.iterate_uncontrollable_inputs())))) return self.unprime_latches_in_bdd(b)
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 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 strat_is_inductive(self, strat, use_trans=False): strat_dom = strat.exist_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), chain(self.iterate_controllable_inputs(), self.iterate_uncontrollable_inputs())))) p_bdd = self.substitute_latches_next(strat_dom, use_trans=use_trans) return BDD.make_impl(strat, p_bdd) == BDD.true()
def extract_output_funs(self, 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 = [BDD(x.lit) for x in self.iterate_controllable_inputs()] for c_symb in self.iterate_controllable_inputs(): c = BDD(c_symb.lit) others = set(set(all_outputs) - set([c])) if others: others_cube = BDD.make_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=lambda x: x.dag_size()) output_models[c_symb.lit] = 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 strat_is_inductive(self, strat, use_trans=False): strat_dom = strat.exist_abstract( BDD.make_cube( imap( funcomp(BDD, symbol_lit), chain(self.iterate_controllable_inputs(), self.iterate_uncontrollable_inputs())))) p_bdd = self.substitute_latches_next(strat_dom, use_trans=use_trans) return BDD.make_impl(strat, p_bdd) == BDD.true()
def trans_rel_bdd(self): # check cache if self._cached_transition is not None: return self._cached_transition b = BDD.true() for x in self.iterate_latches(): b &= BDD.make_eq(BDD(self.get_primed_var(x.lit)), self.lit2bdd(x.next)) self._cached_transition = b log.BDD_DMP(b, "Composed and cached the concrete transition relation.") return b
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 over_post_bdd(self, src_states_bdd, sys_strat=None): """ Over-approximated version of concrete post which can be done even without the transition relation """ strat = BDD.true() if sys_strat is not None: strat &= sys_strat # to do this, we use an over-simplified transition relation, EXu,Xc b = BDD.true() for x in self.iterate_latches(): temp = BDD.make_eq(BDD(self.get_primed_var(x.lit)), self.lit2bdd(x.next)) b &= temp.and_abstract( strat, BDD.make_cube(imap( funcomp(BDD, symbol_lit), self.iterate_controllable_inputs() ))) b = b.restrict(src_states_bdd) b &= src_states_bdd b = b.exist_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), chain(self.iterate_latches(), self.iterate_uncontrollable_inputs())))) return self.unprime_latches_in_bdd(b)
def bdd2aig(self, a_bdd): """ Walk given BDD node (recursively). If given input BDD requires intermediate AND gates for its representation, the function adds them. Literal representing given input BDD is `not` added to the spec. """ if a_bdd in self.bdd_gate_cache: return self.bdd_gate_cache[a_bdd] if a_bdd.is_constant(): res = int(a_bdd == BDD.true()) # in aiger 0/1 = False/True return res # get an index of variable, # all variables used in bdds also introduced in aiger, # except fake error latch literal, # but fake error latch will not be used in output functions (at least # we don't need this..) a_lit = a_bdd.get_index() assert (a_lit != self.error_fake_latch.lit),\ ("using error latch in the " + "definition of output " + "function is not allowed") t_bdd = a_bdd.then_child() e_bdd = a_bdd.else_child() t_lit = self.bdd2aig(t_bdd) e_lit = self.bdd2aig(e_bdd) # ite(a_bdd, then_bdd, else_bdd) # = a*then + !a*else # = !(!(a*then) * !(!a*else)) # -> in general case we need 3 more ANDs a_t_lit = self.get_optimized_and_lit(a_lit, t_lit) na_e_lit = self.get_optimized_and_lit(negate_lit(a_lit), e_lit) n_a_t_lit = negate_lit(a_t_lit) n_na_e_lit = negate_lit(na_e_lit) ite_lit = self.get_optimized_and_lit(n_a_t_lit, n_na_e_lit) res = negate_lit(ite_lit) if a_bdd.is_complement(): res = negate_lit(res) # cache result self.bdd_gate_cache[a_bdd] = res return res
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 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 extract_output_funs(self, 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 = [BDD(x.lit) for x in self.iterate_controllable_inputs()] for c_symb in self.iterate_controllable_inputs(): c = BDD(c_symb.lit) others = set(set(all_outputs) - set([c])) if others: others_cube = BDD.make_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=lambda x: x.dag_size()) output_models[c_symb.lit] = 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 init_state_bdd(self): b = BDD.true() for x in self.iterate_latches(): b &= ~BDD(x.lit) return b
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
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