Skip to content

Composing models

A model can be imported into another model and instantiated several times, so you build a reusable sub-model once and wire copies of it into a larger one. The copies keep their nets — and their state — apart.

A reusable model is a Circuit subclass. The parent builds each copy with mk_naked_circuit(inputs, namespace=True): it passes the nets to wire into the sub-model's inputs, gets back the sub-model's outputs, and namespace=True prefixes every net the copy creates with the copy's name, so two copies that both call a latch z become left.z and right.z — no clash.

The sub-model A

# submodel.py
from intrepyd.circuit import Circuit

class StickyAnd(Circuit):
    """z latches true the first cycle x and y both hold, and stays true."""

    def _mk_inputs(self):                       # only for standalone use
        bool_t = self.context.mk_boolean_type()
        self.inputs['x'] = self.context.mk_input('x', bool_t)
        self.inputs['y'] = self.context.mk_input('y', bool_t)
        self.nets.update(self.inputs)

    def _mk_naked_circuit_impl(self, inputs):   # used when composed
        ctx = self.context
        both = ctx.mk_and(inputs['x'], inputs['y'])
        z = ctx.mk_latch('z', ctx.mk_boolean_type())
        ctx.set_latch_init_next(z, ctx.mk_false(), ctx.mk_or(z, both))
        self.nets['z'] = z
        return {'z': z}

The model B, importing two copies of A

# top.py
from intrepyd.circuit import Circuit
from submodel import StickyAnd

class Top(Circuit):
    def _mk_inputs(self):
        bool_t = self.context.mk_boolean_type()
        for name in ('a', 'b', 'c', 'd'):
            self.inputs[name] = self.context.mk_input(name, bool_t)
        self.nets.update(self.inputs)

    def _mk_naked_circuit_impl(self, inputs):
        ctx = self.context
        left = StickyAnd(ctx, 'left')
        left_out = left.mk_naked_circuit({'x': inputs['a'], 'y': inputs['b']}, namespace=True)
        right = StickyAnd(ctx, 'right')
        right_out = right.mk_naked_circuit({'x': inputs['c'], 'y': inputs['d']}, namespace=True)
        result = ctx.mk_or(left_out['z'], right_out['z'])
        self.nets['result'] = result
        return {'result': result}

left and right each build their own latch; because of the namespaces the two are independent, so left sticking true does not affect right.

Where to put the files

A model is ordinary python, so composition is just an import. The simplest layout is both files in the same directory, and run from there:

cd your/models            # the directory holding submodel.py and top.py
python top.py             # from here, "from submodel import StickyAnd" resolves
python -m intrepyd.export top.py   # the exporter finds it too

The import works because python puts the script's own directory on the import path. Run python path/to/top.py from another directory and the import still resolves (the script's directory is used), but a bare python -c "import top" from elsewhere does not — the directory is not on the path. If you prefer a package, add an __init__.py next to the models and import from mypkg.submodel import StickyAnd, with the package's parent on PYTHONPATH. Avoid naming a model file after a standard or installed module (e.g. types.py, queue.py), which would shadow it.

Runnable example

A complete, tested version of this example is in the repository under intrepyd/examples/composition/ (submodel.py and top.py), exercised by tests/test_composition_example.py.