def test_bdd_transform(circ): circ = circ.unroll(3) out = list(circ.outputs)[0] f, manager, relabel = to_bdd(circ, output=out) expr = from_bdd(f, manager=manager) circ2 = expr.aig['i', relabel.inv]['o', {expr.output: out}] assert not circ2.latches assert circ2.inputs <= circ.inputs assert out in circ2.outputs f2, _, relabel = to_bdd(expr.aig, manager=manager, output=expr.output, renamer=lambda *x: x[1]) assert f == f2
def toggle(self, actions: Actions): """Toggles a sequence of (sys, env) actions.""" assert len(actions) == self.horizon aps = fn.lpluck(0, self.dyn.simulate(actions)) expr = preimage(aps=aps, mdp=self.dyn) bexpr, *_ = aiger_bdd.to_bdd( expr, manager=self.manager, renamer=lambda _, x: x ) return attr.evolve(self, bexpr=xor(self.bexpr, bexpr))
def test_set_levels(): a1, c1, a2, c2 = aiger.atoms('a1', 'c1', 'a2', 'c2') expr = (a1 == c1) & (a2 == c2) levels = {'c1': 0, 'a1': 1, 'c2': 2, 'a2': 3} bexpr, manager, relabels = to_bdd(expr, renamer=lambda _, x: x, levels=levels) assert all(k == v for k, v in relabels.items()) assert bexpr.low.negated assert not bexpr.high.negated
def test_preimage(): spec, mdp = scenario_reactive() sys1 = mdp.aigbv >> BV.sink(1, ['c_next', '##valid']) sys2 = sys1 >> BV.aig2aigbv(spec.aig) def act(action, coin): return {'a': (action, ), 'c': (coin, )} actions = [act(True, True), act(True, False), act(True, True)] observations = fn.lpluck(0, sys1.simulate(actions)) expr = preimage(observations, sys1) assert expr.inputs == { 'c##time_0', 'c##time_1', 'c##time_2', 'a##time_0', 'a##time_1', 'a##time_2', } bexpr1, manager, order = to_bdd2(sys2, horizon=3) def accepts(bexpr, actions): """Check if bexpr accepts action sequence.""" timed_actions = {} for t, action in enumerate(actions): c, a = action['c'], action['a'] timed_actions.update({ f'c##time_{t}[0]': c[0], f'a##time_{t}[0]': a[0] }) assert timed_actions.keys() == manager.vars.keys() tmp = manager.let(timed_actions, bexpr) assert tmp in (manager.true, manager.false) return tmp == manager.true assert not accepts(bexpr1, actions) bexpr2, _, input2var = aiger_bdd.to_bdd(expr, manager=manager, renamer=lambda _, x: x) assert accepts(bexpr2, actions) assert not accepts(~bexpr2, actions) bexpr3 = xor(bexpr1, bexpr2) assert accepts(bexpr3, actions)
def sample(self): guard, *_ = random.choices(*zip(*self.data), k=1) bexpr, manager, *_ = aiger_bdd.to_bdd(guard) if bexpr == manager.false: raise ValueError('None zero probability assigned to empty set.') # Uniformly sample bits and use BDD to error-correct to a model. nbits = len(manager.vars) bits: int = random.getrandbits(nbits) # Packed random bits. for i in range(nbits): var = bexpr.bdd.var_at_level(i) decision = bool((bits >> i) & 1) # Look up ith random bit. bexpr2 = bexpr.let(**{var: decision}) if bexpr2 == manager.false: bexpr2 = bexpr.let(**{var: not decision}) bits ^= 1 << i # Error correction. bexpr = bexpr2 return self.decode(bits)
def to_bdd2(mdp, horizon, output=None, manager=None): """ Compute the BDD for `output`'s value after unrolling the dynamics (`mdp`) `horizon` steps. Returns a triplet of: 1. The BDD. 2. The BDD Manager. 3. The order object determining if a given bit is a decision or chance bit. """ if output is None: assert len(mdp.outputs) == 1 output = fn.first(mdp.outputs) circ = cone(mdp.aigbv, output) inputs, env_inputs = mdp.inputs, circ.inputs - mdp.inputs imap = circ.imap def flattened(t): def fmt(k): idxs = range(imap[k].size) return [f"{k}##time_{t}[{i}]" for i in idxs] actions = fn.lmapcat(fmt, inputs) coin_flips = fn.lmapcat(fmt, env_inputs) return actions + coin_flips unrolled_inputs = fn.lmapcat(flattened, range(horizon)) levels = {k: i for i, k in enumerate(unrolled_inputs)} circ2 = BV.AIGBV(circ.aig.lazy_aig).unroll(horizon, only_last_outputs=True) bexpr, *_ = aiger_bdd.to_bdd(circ2, levels=levels, renamer=lambda _, x: x) def count_bits(inputs): return sum(imap[i].size for i in inputs) order = BitOrder(count_bits(inputs), count_bits(env_inputs), horizon) return bexpr, bexpr.bdd, order
def test_bdd_transform_smoke(): to_bdd(atom(3, 'x', signed=False) < 4)