def upre_bdd(self, dst_states_bdd, env_strat=None, get_strat=False, use_trans=False): """ UPRE = EXu.AXc.EL' : T(L,Xu,Xc,L') ^ dst(L') [^St(L,Xu)] """ # take a transition step backwards # TECH NOTE: the restrict_fun=~dst... works ONLY because I will use the # result and take the union with dst_states afterwards... p_bdd = self.substitute_latches_next(dst_states_bdd, restrict_fun=~dst_states_bdd, use_trans=use_trans) # use the given strategy if env_strat is not None: p_bdd &= env_strat # there is an uncontrollable action such that for all contro... temp_bdd = p_bdd.univ_abstract( BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.iterate_controllable_inputs()))) p_bdd = temp_bdd.exist_abstract( BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.iterate_uncontrollable_inputs()))) # prepare the output if get_strat: return temp_bdd else: return p_bdd
def upre_bdd(self, dst_states_bdd, env_strat=None, get_strat=False, use_trans=False): """ UPRE = EXu.AXc.EL' : T(L,Xu,Xc,L') ^ dst(L') [^St(L,Xu)] """ # take a transition step backwards # TECH NOTE: the restrict_fun=~dst... works ONLY because I will use the # result and take the union with dst_states afterwards... p_bdd = self.substitute_latches_next( dst_states_bdd, restrict_fun=~dst_states_bdd, use_trans=use_trans) # use the given strategy if env_strat is not None: p_bdd &= env_strat # there is an uncontrollable action such that for all contro... temp_bdd = p_bdd.univ_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), self.iterate_controllable_inputs()))) p_bdd = temp_bdd.exist_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), self.iterate_uncontrollable_inputs()))) # prepare the output if get_strat: return temp_bdd else: return p_bdd
def __init__(self, aig): self.aig = aig self.uinputs = [x.lit for x in self.aig.iterate_uncontrollable_inputs()] self.latches = [x.lit for x in self.aig.iterate_latches()] self.latch_cube = BDD.make_cube(imap(funcomp(BDD, symbol_lit), self.aig.iterate_latches())) self.platch_cube = BDD.make_cube(imap(funcomp(BDD, self.aig.get_primed_var, symbol_lit), self.aig.iterate_latches())) self.cinputs_cube = BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.aig.iterate_controllable_inputs())) self.pcinputs_cube = self.aig.prime_all_inputs_in_bdd( self.cinputs_cube) self.uinputs_cube = BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.aig.iterate_uncontrollable_inputs())) self.init_state_bdd = self.aig.init_state_bdd() self.error_bdd = self.aig.lit2bdd(self.aig.error_fake_latch.lit) self.Venv = dict() self.Venv[self.init_state_bdd] = True self.succ_cache = dict()
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 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 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 post_bdd(self, src_states_bdd, sys_strat=None, use_trans=False, over_approx=False): """ POST = EL.EXu.EXc : src(L) ^ T(L,Xu,Xc,L') [^St(L,Xu,Xc)] optional argument fixes possible actions for the environment """ if not use_trans or over_approx: return self.over_post_bdd(src_states_bdd, sys_strat) transition_bdd = self.trans_rel_bdd() trans = transition_bdd if sys_strat is not None: trans &= sys_strat trans = trans.restrict(src_states_bdd) suc_bdd = trans.and_abstract( src_states_bdd, BDD.make_cube( imap( funcomp(BDD, symbol_lit), chain(self.iterate_controllable_inputs(), self.iterate_uncontrollable_inputs(), self.iterate_latches())))) return self.unprime_latches_in_bdd(suc_bdd)
def cpre_bdd(self, dst_states_bdd, get_strat=False, use_trans=False): """ CPRE = AXu.EXc.EL' : T(L,Xu,Xc,L') ^ dst(L') """ # take a transition step backwards p_bdd = self.substitute_latches_next(dst_states_bdd, use_trans=use_trans) # for all uncontrollable action there is a contro... # note: if argument get_strat == True then we leave the "good" # controllable actions in the bdd if not get_strat: p_bdd = p_bdd.exist_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), self.iterate_controllable_inputs()))) p_bdd = p_bdd.univ_abstract( BDD.make_cube(imap(funcomp(BDD, symbol_lit), self.iterate_uncontrollable_inputs()))) return p_bdd
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 cpre_bdd(self, dst_states_bdd, get_strat=False, use_trans=False): """ CPRE = AXu.EXc.EL' : T(L,Xu,Xc,L') ^ dst(L') """ # take a transition step backwards p_bdd = self.substitute_latches_next(dst_states_bdd, use_trans=use_trans) # for all uncontrollable action there is a contro... # note: if argument get_strat == True then we leave the "good" # controllable actions in the bdd if not get_strat: p_bdd = p_bdd.exist_abstract( BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.iterate_controllable_inputs()))) p_bdd = p_bdd.univ_abstract( BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.iterate_uncontrollable_inputs()))) return p_bdd
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 __init__(self, aig): self.aig = aig self.uinputs = [ x.lit for x in self.aig.iterate_uncontrollable_inputs() ] self.latches = [x.lit for x in self.aig.iterate_latches()] self.latch_cube = BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.aig.iterate_latches())) self.platch_cube = BDD.make_cube( imap(funcomp(BDD, self.aig.get_primed_var, symbol_lit), self.aig.iterate_latches())) self.cinputs_cube = BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.aig.iterate_controllable_inputs())) self.pcinputs_cube = self.aig.prime_all_inputs_in_bdd( self.cinputs_cube) self.uinputs_cube = BDD.make_cube( imap(funcomp(BDD, symbol_lit), self.aig.iterate_uncontrollable_inputs())) self.init_state_bdd = self.aig.init_state_bdd() self.error_bdd = self.aig.lit2bdd(self.aig.error_fake_latch.lit) self.Venv = dict() self.Venv[self.init_state_bdd] = True self.succ_cache = dict()
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 substitute_latches_next(self, b, use_trans=False, restrict_fun=None): if use_trans: transition_bdd = self.trans_rel_bdd() trans = transition_bdd if restrict_fun is not None: trans = trans.restrict(restrict_fun) primed_bdd = self.prime_latches_in_bdd(b) primed_latches = BDD.make_cube( imap(funcomp(BDD, self.get_primed_var, symbol_lit), self.iterate_latches())) return trans.and_abstract(primed_bdd, primed_latches) else: latches = [x.lit for x in self.iterate_latches()] latch_funs = [self.lit2bdd(x.next) for x in self.iterate_latches()] if restrict_fun is not None: latch_funs = [x.restrict(restrict_fun) for x in latch_funs] # take a transition step backwards return b.compose(latches, latch_funs)
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 post_bdd(self, src_states_bdd, sys_strat=None, use_trans=False, over_approx=False): """ POST = EL.EXu.EXc : src(L) ^ T(L,Xu,Xc,L') [^St(L,Xu,Xc)] optional argument fixes possible actions for the environment """ if not use_trans or over_approx: return self.over_post_bdd(src_states_bdd, sys_strat) transition_bdd = self.trans_rel_bdd() trans = transition_bdd if sys_strat is not None: trans &= sys_strat trans = trans.restrict(src_states_bdd) suc_bdd = trans.and_abstract( src_states_bdd, BDD.make_cube(imap(funcomp(BDD, symbol_lit), chain( self.iterate_controllable_inputs(), self.iterate_uncontrollable_inputs(), self.iterate_latches()) ))) return self.unprime_latches_in_bdd(suc_bdd)
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 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 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