def register_distribution(self, probs): """ Register a distribution :param probs: The distribution as a list of probabilities :return: A pcirc that generates a binary-encoded output sel that corresponds to sampling from the distribution. """ dist = tuple(probs) if dist not in self._distributions: name = f"{self._aut_name}_c{len(self._distributions)}" sel = atom(len(probs), name).with_output("sel") lookup = bidict({idx: f'sel-{idx}' for idx in range(len(probs))}) encoder = D.Encoding(decode=lookup.get, encode=lookup.inv.get) func = D.from_aigbv(sel.aigbv, input_encodings={name: encoder}) name2prob = { name: {f"sel-{index}": prob for index, prob in enumerate(probs)} } coins_id = f"{self._aut_name}_c{len(self._distributions)}" self._distributions[dist] = C.pcirc(func) \ .randomize(name2prob) \ .with_coins_id(coins_id) return self._distributions[dist]
def test_never_false_redemption(): spec = LTL.atom('x').historically() monitor = BV.aig2aigbv(spec.aig) assert len(monitor.latches) == 1 monitor = monitor['l', {fn.first(monitor.latches): 'z'}] # Environment can save you. x, y = BV.uatom(1, 'x'), BV.uatom(1, 'y') xy = (x | y).with_output('x') # env y can override x. dyn = C.pcirc(xy.aigbv) \ .randomize({'y': {0: 0.75, 1: 0.25}}) horizon = 3 model = from_pcirc(dyn, monitor, steps=horizon) coeff = np.log(2) # Special coeff to make LSE visit powers of 2. actor = improviser(model, coeff) v8 = coeff v7 = 0 v6 = coeff / 4 v5 = np.logaddexp(v6, coeff) v4 = (np.log(8) + v5) / 4 v3 = np.logaddexp(v4, v5) v2 = (3 * np.log(4) + v3) / 4 v1 = np.logaddexp(v2, v3) expected = sorted([v8, v7, v6, v5, v4, v3, v2, v1]) expected = fn.lmap(pytest.approx, expected) vals = sorted(list(actor.node2val.values())) assert all(x == y for x, y in zip(vals, expected)) def lprob(elems): return actor.prob(elems, log=True) assert lprob([]) == 0 assert lprob([1]) == pytest.approx(v3 - v1) for prefix in [[1], [1, 0, 1], [1, 1, 1], [1, 1, 1, 1, 1]]: for bit in [0, 1]: expected = pytest.approx(-np.log(4) + bit * np.log(3)) assert lprob(prefix + [bit]) - lprob(prefix) == expected ctrl = actor.policy() example = [] for env in [None, 0, 0]: example.append(ctrl.send(env)) assert -float('inf') < lprob(example) ctrl = actor.policy(observe_states=True) example = [] for env in [None, ({'x': 1}, None), ({'x': 1}, None)]: example.append(ctrl.send(env)) assert -float('inf') < lprob(example)
def test_never_false(): spec = LTL.atom('x').historically() monitor = BV.aig2aigbv(spec.aig) dyn = C.pcirc(BV.identity_gate(1, 'x')) horizon = 3 model = from_pcirc(dyn, monitor, steps=horizon) graph = model.graph() assert len(graph.nodes) == horizon + 2 assert len(graph.edges) == 2 * horizon
def test_never_false_redemption(): spec = LTL.atom('x').historically() monitor = BV.aig2aigbv(spec.aig) # Environment can save you. x, y = BV.uatom(1, 'x'), BV.uatom(1, 'y') xy = (x | y).with_output('x') # env y can override x. dyn = C.pcirc(xy.aigbv) \ .randomize({'y': {0: 0.4, 1: 0.6}}) horizon = 3 model = from_pcirc(dyn, monitor, steps=horizon) graph = model.graph() assert len(graph.nodes) == 2 * horizon + 2 assert len(graph.edges) == 4 * horizon
def test_never_false1(): spec = LTL.atom('x').historically() monitor = BV.aig2aigbv(spec.aig) dyn = C.pcirc(BV.identity_gate(1, 'x')) horizon = 4 model = from_pcirc(dyn, monitor, steps=horizon) coeff = np.log(2) actor = model.improviser(rationality=coeff) expected = [ 0, coeff, np.log(1 + 2), np.log(3 + 2), np.log(7 + 2), np.log(15 + 2), ] # LSE visits powers of 2 for the special coeff. expected = fn.lmap(pytest.approx, expected) vals = sorted(list(actor.node2val.values())) assert all(x == y for x, y in zip(expected, vals)) expected = sorted([ np.log(9) - np.log(17), np.log(8) - np.log(17), ]) expected = fn.lmap(pytest.approx, expected) def lprob(elems): return actor.prob(elems, log=True) assert lprob([]) == 0 assert lprob([1]) == pytest.approx(np.log(9) - np.log(17)) assert lprob([1, 1]) == pytest.approx(np.log(5) - np.log(17)) assert lprob([1, 1, 1]) == pytest.approx(np.log(3) - np.log(17)) assert lprob([1, 1, 1, 1]) == pytest.approx(coeff - np.log(17)) # ----------- Fail on first state --------------------- base = np.log(8) - np.log(17) assert lprob([0]) == pytest.approx(base) # Uniform after failing. assert lprob([0, 1]) == pytest.approx(base - np.log(2)) assert lprob([0, 0]) == pytest.approx(base - np.log(2)) assert lprob([0, 0, 0]) == pytest.approx(base - np.log(4)) assert lprob([0, 0, 1]) == pytest.approx(base - np.log(4)) # ----------- Fail on second state --------------------- base = np.log(4) - np.log(17) assert lprob([1, 0]) == pytest.approx(base) assert lprob([1, 0, 0]) == pytest.approx(base - np.log(2)) assert lprob([1, 0, 1]) == pytest.approx(base - np.log(2)) assert lprob([1, 0, 0, 0]) == pytest.approx(base - np.log(4)) assert lprob([1, 1, 0, 1]) == pytest.approx(base - np.log(4)) with pytest.raises(ValueError): lprob([1, 1, 1, 1, 1, 1]) example = list(actor.sample()) assert -float('inf') < lprob(example) ctrl = actor.policy() example = [] for env in [None, 0, 0, 0]: example.append(ctrl.send(env)) assert -float('inf') < lprob(example) actor = model.improviser(psat=0.7) assert actor.sat_prob() == pytest.approx(0.7)
def _translate_destinations(data: dict, ctx: AutomatonContext) -> set[str]: """ :param data: Describes the destinations. :param ctx: :return: """ # Create coin flipping part. if len(data) == 1: pass else: probs = [] for d in data: if "probability" not in d: probs.append(1) else: probability = d["probability"]["exp"] probs.append(_parse_prob(probability)) prob_input = ctx.register_distribution(probs) # TODO consider what to do with the additional output of prob_input vars_written_to = set() for d in data: for a in d["assignments"]: vars_written_to.add(a["ref"]) destinations = [] for index, d in enumerate(data): assert d["location"] == "l" updates = {} # TODO add location handling for assignment in d["assignments"]: var_name = assignment["ref"] var_primed = var_name if len(data) > 1: # TODO: why make this case special? var_primed += f"-{index}" val = assignment["value"] updates[var_primed] = _translate_expression(val, ctx.scope) \ .resize(ctx.scope.get_aig_variable(var_name).size) \ .with_output(var_primed) \ .aigbv for var in vars_written_to: var_primed = var if len(data) > 1: var_primed += f"-{index}" # TODO: why make this case special. if var_primed not in updates: updates[var_primed] = ctx.scope \ .get_aig_variable(var) \ .with_output(var_primed) \ .aigbv update = par_compose(updates.values()) destinations.append(update) edge_circuit = par_compose(destinations) vars_written_to = list(vars_written_to) if len(destinations) > 1: indices = range(len(destinations)) def selectors(): for var in vars_written_to: size = ctx.scope.get_aig_variable(var).size outputs = [BV.uatom(size, f"{var}-{idx}") for idx in indices] yield mux(outputs, key_name='sel').with_output(var).aigbv edge_circuit >>= par_compose(selectors()) edge_circuit <<= prob_input else: edge_circuit = C.pcirc(edge_circuit) # Deterministic pcirc. return edge_circuit, vars_written_to
def test_from_pcirc_smoke(): sys = BV.uatom(3, 'sys') env = BV.uatom(3, 'env') out = BV.uatom(3, 'out') monitor = out < 4 dyn = (sys + env).with_output('out') dyn = C.pcirc(dyn) \ .randomize({'env': {0: 1/3, 1: 1/3, 5: 1/3}}) \ .with_coins_id('coins') model = from_pcirc(dyn, monitor, steps=2) assert model.coin_biases('sys##time_0') is None coins0 = f'coins##time_0' assert model.coin_biases(coins0) == dyn.coin_biases assert model.mdd({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 0, 'coins##time_1': 0b11, # <- env == 0 }) == 1 assert model.mdd({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 5, 'coins##time_1': 0b11, # <- env == 0 }) == 0 sum_zero0 = BV.uatom(3, f'out##time_1') == 0b000 expr2 = model.preimage(sum_zero0) assert expr2({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 0, 'coins##time_1': 0b11, # <- env == 0 })[0] assert expr2({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 0, 'coins##time_1': 0b00, # <- env != 0 })[0] assert not expr2({ 'sys##time_0': 0, 'coins##time_0': 0b00, # <- env != 0 'sys##time_1': 0, 'coins##time_1': 0b00, # <- env != 0 })[0] model2 = model.override(sum_zero0, False) assert model2.mdd({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 0, 'coins##time_1': 0b11, # <- env == 0 }) == 0 assert model2.mdd({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 3, 'coins##time_1': 0b11, # <- env == 0 }) == 0 assert model2.mdd({ 'sys##time_0': 0, 'coins##time_0': 0b11, # <- env == 0 'sys##time_1': 5, 'coins##time_1': 0b11, # <- env == 0 }) == 0 graph = model.graph() graph2 = model2.graph() assert len(graph2) >= len(graph) # Now need to case split at 0. assert not model.is_random('sys##time_0') assert model.is_random('coins##time_0') assert model.time_step('foo##time_0') == 0 assert model.time_step('foo##time_1') == 1 assert model.time_step(False) == 2 guard = fn.first(graph.edges(data=True))[-1]['label'] assert model.size(guard) > 0 expr = model.mdd.io.inputs[1].expr() == 0 assert model.prob(expr) == pytest.approx(1 / 6)