Exemple #1
0
def bmc_equiv(circ1, circ2, horizon, assume=None) -> Iterator[bool]:
    """
    Perform bounded model checking up to horizon to see if circ1 and
    circ2 are equivilent.
    """
    # Create distinguishing predicate.
    expr = BV.uatom(1, val=0)
    for o1 in circ1.outputs:
        o2 = f'{o1}##copy'
        size = circ1.omap[o1].size
        expr |= BV.uatom(size, o1) != BV.uatom(size, o2)
    expr.with_output('distinguished')

    monitor = ((circ1 | circ2) >> expr.aigbv).aig
    assert len(monitor.outputs) == 1

    # Make underlying aig lazy.
    monitor = monitor.lazy_aig

    # BMC loop
    for t in range(horizon):
        delta = horizon - t
        unrolled = monitor.unroll(delta, only_last_outputs=True)
        assert len(unrolled.outputs) == 1
        unrolled = aiger.BoolExpr(unrolled)

        if assume is not None:
            unrolled |= assume

        yield aiger_sat.is_sat(unrolled)
Exemple #2
0
def test_loopback_unroll():
    x = BV.uatom(3, 'x')
    y = BV.uatom(3, 'y')
    adder = (x + y).with_output('z')

    pcirc = C.PCirc(adder, dist_map={'y': lambda _: 1/3}) \
             .assume((y > 0) & (y < 4))

    pcirc2 = pcirc.loopback({
        'input': 'x',
        'output': 'z',
        'init': 4,
        'keep_output': True,
    })

    assert pcirc2.inputs == set()
    assert pcirc2.outputs == {'z'}
    assert len(pcirc2.latches) == 1
    assert 4 < pcirc2({})[0]['z'] < 8

    pcirc3 = pcirc2.unroll(3)
    assert pcirc3.inputs == set()
    assert pcirc3.outputs == {'z##time_1', 'z##time_2', 'z##time_3'}

    pcirc4 = pcirc2.unroll(3, only_last_outputs=True)
    assert pcirc4.outputs == {'z##time_3'}
    pcirc4({})  # Could technically be any value due to roll back.
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)
Exemple #4
0
def test_par_compose():
    x = BV.uatom(3, 'x').with_output('x')
    y = BV.uatom(3, 'y').with_output('y')

    pcirc_x = C.PCirc(circ=x)
    pcirc_y = C.PCirc(circ=y, dist_map={'y': lambda _: 1 / 3})

    pcirc_xy = pcirc_x | pcirc_y
    assert pcirc_xy.inputs == {'x'}
    assert pcirc_xy.outputs == {'x', 'y'}
    assert pcirc_xy.dist_map['y'](0) == 1 / 3
Exemple #5
0
    def selectors():
        indices = range(len(edge_circuits))
        for v in ctx.scope.variables:
            size = ctx.scope.get_aig_variable(v.name).size
            outputs = [BV.uatom(size, f"{v.name}-{idx}") for idx in indices]
            yield mux(outputs, key_name='edge').with_output(v.name).aigbv

        for v in ctx.scope.variables:
            if not v.is_local:
                mname = f"{v.name}-mod"
                size = 1
                outputs = [BV.uatom(size, f"{mname}-{idx}") for idx in indices]
                yield mux(outputs, key_name='edge').with_output(mname).aigbv
Exemple #6
0
def test_pcirc_smoke():
    x = BV.uatom(3, 'x')
    y = BV.uatom(3, 'y')
    z = (x + y).with_output('z')

    pcirc = C.PCirc(circ=z, dist_map={'y': lambda _: 1/3}) \
             .assume(y <= 2)

    rvar = C.RandomVarCirc(pcirc)

    # Warning. May be flaky.
    for i in range(3):
        assert 3 <= rvar({'x': 3}) <= 5
Exemple #7
0
    def preimage(expr):
        assert expr.inputs <= unrolled_d.outputs

        circ = expr.aigbv
        for name in unrolled_d.outputs - expr.inputs:
            circ >>= BV.sink(omap[name].size, [name])

        valid = BV.uatom(1, unrolled_d.valid_id)
        sat = BV.uatom(1, expr.output)

        preimg = (unrolled_d >> circ).aigbv >> (sat & valid).aigbv
        assert preimg.inputs == unrolled_d.inputs

        return BV.UnsignedBVExpr(preimg)
Exemple #8
0
def test_seq_compose():
    x = BV.uatom(3, 'x').with_output('y')
    y = BV.uatom(3, 'y').with_output('y')
    pcirc = C.PCirc(circ=y)
    pcirc2 = C.PCirc(circ=x, dist_map={'x': lambda _: 1 / 3}) >> pcirc
    pcirc3 = pcirc << C.PCirc(circ=x, dist_map={'x': lambda _: 1 / 3})

    assert pcirc2.outputs == pcirc3.outputs == {'y'}
    assert pcirc2.inputs == pcirc3.inputs == set()
    assert pcirc2.dist_map['x'](0) == pcirc3.dist_map['x'](0) == 1 / 3

    assert 0 <= pcirc2({})[0]['y'] <= 7

    pcirc4 = C.PCirc(circ=(x + 1).with_output('y')) >> pcirc
    assert pcirc4({'x': 0})[0] == {'y': 1}
Exemple #9
0
def onehot_gadget(output: str):
    sat = BV.uatom(1, output)
    false, true = BV.uatom(2, 0b01), BV.uatom(2, 0b10)
    expr = BV.ite(sat, true, false) \
             .with_output('sat')

    encoder = D.Encoding(
        encode=lambda x: 1 << int(x),
        decode=lambda x: bool((x >> 1) & 1),
    )

    return D.from_aigbv(
        expr.aigbv,
        output_encodings={'sat': encoder},
    )
Exemple #10
0
def test_pcirc_relabel():
    x = BV.uatom(3, 'x')
    pcirc = C.PCirc(circ=x, dist_map={'x': lambda _: 1 / 3})
    pcirc2 = pcirc['i', {'x': 'y'}]
    assert pcirc2.inputs == set()
    assert pcirc2.circ.inputs == {'y'}
    assert pcirc2.dist_map['y'](0) == 1 / 3
def test_parallel_composition():
    x = BV.uatom(3, 'x')
    circ1 = (x + 1).with_output('y').aigbv \
        | (x < 5).with_output('##valid').aigbv
    func1 = from_aigbv(
        circ1,
        input_encodings={'x': INT_ENC},
        output_encodings={'y': INT_ENC},
    )

    circ2 = x.with_output('z').aigbv \
        | (x > 2).with_output('##valid').aigbv
    func2 = from_aigbv(
        circ2,
        input_encodings={'x': INT_ENC},
        output_encodings={'z': INT_ENC},
    )

    func12 = func1 | func2
    assert func12({'x': 3})[0] == {'y': 4, 'z': 3}

    with pytest.raises(ValueError):
        func12({'x': 0})

    with pytest.raises(ValueError):
        func12({'x': 7})
def test_discrete_wrapper_smoke():
    x = BV.uatom(3, 'x')
    circ = (x + 1).with_output('z') \
                  .aigbv

    func = from_aigbv(circ)
    assert func({'x': 6})[0] == {'z': 7}

    func2 = from_aigbv(
        circ,
        input_encodings={'x': NEG_ENC},
        output_encodings={'z': NEG_ENC},
    )
    assert func2({'x': -2})[0] == {'z': -3}

    valid = (x <= 2).with_output('##valid') \
                    .aigbv

    func3 = from_aigbv(
        circ | valid,
        input_encodings={'x': NEG_ENC},
        output_encodings={'z': NEG_ENC},
    )
    assert func3({'x': -2})[0] == {'z': -3}

    with pytest.raises(ValueError):
        func3({'x': -3})

    assert func3.inputs == {'x'}
    assert func3.outputs == {'z'}
    assert func3.latches == set()
    assert func3.latch2init == {}
def test_readme():
    # Will assume inputs are in 'A', 'B', 'C', 'D', or 'E'.
    ascii_encoder = Encoding(
        decode=lambda x: chr(x + ord('A')),  # Make 'A' map to 0.
        encode=lambda x: ord(x) - ord('A'),
    )

    # Create function which maps: A -> B, B -> C, C -> D, D -> E.
    x = BV.uatom(3, 'x')  # Need 3 bits to capture 5 input types.
    update_expr = (x < 4).repeat(3) & (x + 1)  # 0 if x < 4 else x + 1.
    circ = update_expr.with_output('y').aigbv

    # Need to assert that the inputs are less than 4.
    circ |= (x < 5).with_output('##valid').aigbv

    # Wrap using aiger_discrete.
    func = from_aigbv(
        circ,
        input_encodings={'x': ascii_encoder},
        output_encodings={'y': ascii_encoder},
        valid_id='##valid',
    )

    assert func({'x': 'A'})[0] == {'y': 'B'}
    assert func({'x': 'B'})[0] == {'y': 'C'}
    assert func({'x': 'C'})[0] == {'y': 'D'}
    assert func({'x': 'D'})[0] == {'y': 'E'}
    assert func({'x': 'E'})[0] == {'y': 'A'}
Exemple #14
0
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
Exemple #15
0
def masked_outputs(outputs, key_name: str):
    size = min_bits(len(outputs))
    key = BV.uatom(size, key_name)

    for idx, output in enumerate(outputs):
        mask = (key == idx).repeat(output.size)
        yield output & mask
def from_aigbv(circ: BV.AIGBV,
               input_encodings: Encodings = None,
               output_encodings: Encodings = None,
               valid_id="##valid") -> FiniteFunc:
    """Lift an bit-vector into a function over finite sets.

    Note: if `valid_id` is not present as an output of `circ`, then it
      will be added, and will always output True.

    Args:
     - input_encodings: Maps an input to an encoder. Default is identity.
     - output_encodings: Maps an output to an encoder. Default is identity.
     - valid_id: Denotes which output monitors if inputs are "valid".
    """
    if input_encodings is None:
        input_encodings = {}
    if output_encodings is None:
        output_encodings = {}
    if valid_id not in circ.outputs:
        circ |= BV.uatom(1, 1).with_output(valid_id).aigbv

    input_encodings = project(input_encodings, circ.inputs)
    output_encodings = project(output_encodings, circ.outputs - {valid_id})

    return FiniteFunc(
        circ=circ,
        input_encodings=input_encodings,
        output_encodings=output_encodings,
        valid_id=valid_id,
    )
def test_relabel():
    x = BV.uatom(3, 'x')
    circ1 = (x + 1).with_output('y').aigbv \
        | (x < 5).with_output('##valid').aigbv
    func1 = from_aigbv(circ1,)
    assert func1['i', {'x': 'z'}].inputs == {'z'}
    assert func1['o', {'y': 'z'}].outputs == {'z'}
    assert func1['i', {'x': 'z'}].valid_id == func1.valid_id
Exemple #18
0
def onehot_output(expr):
    """Creates circuit that depends only on 1-hot active bit."""
    bits = BV.uatom(expr.size, expr.output)

    def ite(test, idx):
        return BV.ite(expr[idx], bits[idx], test)

    # Create chained if then else testing 1-hot bit.
    return reduce(ite, range(1, expr.size), bits[0])
Exemple #19
0
def to_var(bdl: Bundle, encoding: Optional[Encoding]) -> mdd.Variable:
    if encoding is None:
        encoding = Encoding()

    return mdd.Variable(
        encode=encoding.encode,
        decode=encoding.decode,
        valid=BV.uatom(bdl.size, bdl.name)[0] | 1,  # const 1.
    )
Exemple #20
0
def _translate_expression(data: dict, scope: JaniScope):
    """
    Takes an expression in JANI json, returns a circuit.
    :param data:  the expression AST
    :param scope: The scope with the variable definitions.
    :return: An expression in py-aiger-bv
    """
    if isinstance(data, bool):
        return BV.uatom(1, 1 if data else 0)
    if isinstance(data, int):
        if data == 0:
            return BV.uatom(1, data)
        nr_bits = min_bits(data)
        return BV.uatom(nr_bits, data)
    if isinstance(data, str):
        return scope.get_aig_variable(data)

    if "op" not in data:
        raise ValueError(f"{data} is expected to have an operator")
    if "right" in data:
        try:
            op = BINARY_OPS[data["op"]]
        except KeyError:
            raise NotImplementedError(f"Operator {data['op']} not supported")
        left_subexpr = _translate_expression(data["left"], scope)
        right_subexpr = _translate_expression(data["right"], scope)

        # Match size
        if left_subexpr.size < right_subexpr.size:
            left_subexpr = left_subexpr.resize(right_subexpr.size)
        elif right_subexpr.size < left_subexpr.size:
            right_subexpr = right_subexpr.resize(left_subexpr.size)

        return op(left_subexpr, right_subexpr)
    else:
        try:
            op = UNARY_OPS[data["op"]]
        except KeyError:
            raise NotImplementedError(f"Operator {data['op']} not supported")

        subexpr = _translate_expression(data["exp"], scope)
        return op(subexpr)
Exemple #21
0
    def aigbv(self):
        assert "##valid" not in self.outputs

        circ = self._aigbv
        is_valid = uatom(1, 1)
        for dist in self.input2dist.values():
            circ <<= dist.expr.aigbv
            is_valid &= dist.valid

        circ |= is_valid.with_output("##valid").aigbv
        return circ
def test_loopback_and_unroll():
    x = BV.uatom(3, 'x')
    y = BV.uatom(3, 'y')
    circ1 = (x + y).with_output('y').aigbv \
        | (x < 7).with_output('##valid').aigbv
    func1 = from_aigbv(circ1,)
    func2 = func1.loopback({
        'input': 'x', 'output': 'y',
        'keep_output': True,
        'init': 0,
    })
    assert func2.simulate([{'y': 1}, {'y': 1}])[-1][0] == {'y': 2}
    with pytest.raises(ValueError):
        assert func2.simulate([{'y': 1}]*10)

    func3 = func2.unroll(2, only_last_outputs=True)
    assert func3({'y##time_0': 0, 'y##time_1': 1})[0] == {'y##time_2': 1}

    with pytest.raises(ValueError):
        assert func3({'y##time_0': 7, 'y##time_1': 0})
Exemple #23
0
def gridworld(n, start=(None, None), compressed_inputs=False):
    # Gridworld is 2 synchronized chains.
    circ = chain(n, 'x', 'ax', start[1]) | chain(n, 'y', 'ay', start[0])
    circ <<= split_gate('a', 2, 'ay', 2, 'ax')  # Combine inputs.
    x = BV.uatom(circ.omap['x'].size, 'x')
    y = BV.uatom(circ.omap['y'].size, 'y')
    circ >>= y.concat(x).with_output('state').aigbv  # Combine outputs.

    if compressed_inputs:
        uncompress = lookup(2,
                            4,
                            COMPRESSION_MAPPING,
                            'a',
                            'a',
                            in_signed=False,
                            out_signed=False)
        circ <<= uncompress

    # Wrap using aiger discrete add encoding + valid inputs.
    actions_map = ACTIONS_C if compressed_inputs else ACTIONS
    action_encoding = aiger_discrete.Encoding(
        encode=actions_map.get,
        decode=actions_map.inv.get,
    )
    state_encoding = aiger_discrete.Encoding(
        encode=lambda s: s.yx,
        decode=lambda yx: G.GridState(yx, n),
    )

    func = aiger_discrete.from_aigbv(
        circ,
        input_encodings={'a': action_encoding},
        output_encodings={'state': state_encoding},
    )
    if not compressed_inputs:
        action = BV.uatom(4, 'a')
        is_1hot = (action != 0) & ((action & (action - 1)) == 0)
        func = func.assume(is_1hot)
    return func
Exemple #24
0
def test_minimdp():
    x, y = BV.uatom(2, 'main-x'), BV.uatom(2, 'main-y')
    circ = translate_file("tests/minimdp.jani")
    assert circ.outputs == {'main-x', 'main-y'}

    # Fix edge and check probability of ending on x=3 given valid run.
    # TODO this currently only works with one.
    query = circ << BV.source(2, 0, 'edge', False)
    query >>= BV.sink(2, ['main-y'])
    query >>= (BV.uatom(2, 'main-x') == 3).aigbv

    assert infer.prob(query.unroll(1, only_last_outputs=True)) == approx(0)
    assert infer.prob(query.unroll(2, only_last_outputs=True)) == approx(1 / 4)
    assert infer.prob(query.unroll(3, only_last_outputs=True)) == approx(1 / 3)

    # Randomize edge and check probability of ending on x=y given valid run.
    query = circ.randomize({'edge': {0: 0.5, 1: 0.5}})
    query >>= (x == y).aigbv

    assert infer.prob(query.unroll(1, only_last_outputs=True)) == approx(1 / 4)
    assert infer.prob(query.unroll(2, only_last_outputs=True)) == approx(1 / 4)
    assert infer.prob(query.unroll(3,
                                   only_last_outputs=True)) == approx(13 / 38)
Exemple #25
0
def test_readme_mdd():
    # Will assume inputs are in 'A', 'B', 'C', 'D', or 'E'.
    ascii_encoder = Encoding(
        decode=lambda x: chr(x + ord('A')),  # Make 'A' map to 0.
        encode=lambda x: ord(x) - ord('A'),
    )

    one_hot_ascii_encoder = Encoding(
        decode=lambda x: ascii_encoder.decode(ONE_HOT.inv[x]),
        encode=lambda x: ONE_HOT[ascii_encoder.encode(x)],
    )

    # Create function which maps: A -> B, B -> C, C -> D, D -> E.
    x = BV.uatom(3, 'x')  # Need 3 bits to capture 5 input types.
    update_expr = (x < 4).repeat(3) & (x + 1)  # 0 if x < 4 else x + 1.
    circ = update_expr.with_output('y').aigbv
    circ |= (x < 5).with_output('##valid').aigbv
    one_hot_converter = BV.lookup(3,
                                  5,
                                  ONE_HOT,
                                  'y',
                                  'y',
                                  in_signed=False,
                                  out_signed=False)
    circ >>= one_hot_converter

    func_circ = from_aigbv(
        circ,
        input_encodings={'x': ascii_encoder},
        output_encodings={'y': one_hot_ascii_encoder},
        valid_id='##valid',
    )

    assert func_circ({'x': 'A'})[0] == {'y': 'B'}
    assert func_circ({'x': 'B'})[0] == {'y': 'C'}
    assert func_circ({'x': 'C'})[0] == {'y': 'D'}
    assert func_circ({'x': 'D'})[0] == {'y': 'E'}
    assert func_circ({'x': 'E'})[0] == {'y': 'A'}

    func_mdd = to_mdd(func_circ)

    assert func_mdd({'x': 'A'})[0] == 'B'
    assert func_mdd({'x': 'B'})[0] == 'C'
    assert func_mdd({'x': 'C'})[0] == 'D'
    assert func_mdd({'x': 'D'})[0] == 'E'
    assert func_mdd({'x': 'E'})[0] == 'A'
Exemple #26
0
    def _encode(self, prev_latch, action, state):
        (step, lmap), circ1 = self._cutlatches()
        curr_step = step << aiger.source(prev_latch)

        for a, v in action.items():
            size = circ1.imap[a].size
            const = aiger_bv.source(size,
                                    aiger_bv.decode_int(v, signed=False),
                                    name=a,
                                    signed=False)
            curr_step <<= const.aig

        expr = uatom(1, "##valid") == 1
        for k, v in fn.chain(state.items()):
            expr &= _constraint(k, v)

        curr_step >>= expr.aig

        query = curr_step >> aiger.sink(prev_latch.keys())
        assert len(query.outputs) == 1

        model = solve(query)
        assert model is not None

        # Fill in any model don't cares.
        model = fn.merge({i: False for i in circ1.aig.inputs}, model)

        # HACK. Put model back into bitvector.
        coins = circ1.imap.omit(self.inputs).unblast(model)

        if len(prev_latch) > 0:
            next_latch_circ = curr_step >> aiger.sink(expr.aig.outputs)
            next_latch = next_latch_circ(model)[0]
            assert next_latch.keys() == prev_latch.keys()
            prev_latch = next_latch

        return coins, prev_latch
Exemple #27
0
    def _transition_coin(self, start, action, end):
        # 1. Init latches to start.
        circ = self.aigbv.reinit(start)

        # 2. Omit observations. `end` specifies latches.
        for out in self.outputs:
            circ >>= BV.sink(circ.omap[out].size, [out])
        assert circ.outputs == {'##valid'}

        # 3. Create circuit to check valid coin flips.
        assert circ.omap['##valid'].size == 1
        is_valid = BV.UnsignedBVExpr(circ.unroll(1))

        circ >>= BV.sink(1, {'##valid'})  # Assume circ has no outputs now.

        # 4. Expose latchouts via unrolling.
        circ = circ.unroll(1, omit_latches=False)
        end = {f'{k}##time_1': v for k, v in end.items()}
        action = {f'{k}##time_0': v for k, v in action.items()}
        assert set(end.keys()) == circ.outputs
        assert set(action.keys()) <= circ.inputs

        # 5. Create circuit to check if inputs lead to end.
        test_equals = uatom(1, 1)
        for k, v in end.items():
            size = circ.omap[k].size
            test_equals &= uatom(size, k) == uatom(size, v)
        match_end = BV.UnsignedBVExpr(circ >> test_equals.aigbv)

        # 6. Create circuit to assert inputs match action.
        match_action = uatom(1, 1)
        for k, v in action.items():
            size = circ.imap[k].size
            match_action &= uatom(size, k) == uatom(size, v)

        return aigc.Coin(
            expr=match_end & match_action,
            valid=is_valid & match_action,
        )
Exemple #28
0
def _constraint(k, v):
    var = uatom(len(v), k)
    return var == aiger_bv.decode_int(v, signed=False)
Exemple #29
0
 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
def test_rename_valid():
    func = from_aigbv(BV.uatom(3, 'x').aigbv).rename_valid('foo')
    assert 'foo' in func.circ.outputs
    assert 'foo' == func.valid_id