.. currentmodule:: edzed
**--- INFORMATION FOR DEVELOPERS ---**
=============================
Creating combinational blocks
=============================
Feel free to skip this chapter. The :class:`FuncBlock` is an universal
combinational block and there is very little reason to write a new one.
Directions
==========
Directions for creating a new CBlock:
#. subclass from :class:`CBlock`
#. define :meth:`CBlock.calc_output`
#. optional: define :meth:`CBlock.start` and :meth:`CBlock.stop`
----
.. method:: CBlock.calc_output() -> Any
:abstractmethod:
Compute and return the output value. This is supposed to be a pure function
with inputs retrieved from the :attr:`CBlock._in` described below.
.. attribute:: CBlock._in
An object providing access to input values.
An input value can be retrieved using the input name as a key ``self._in['myinput']``
or as an attribute ``self._in.myinput``.
The result is a single value or a tuple of values if the input is a group.
.. method:: CBlock.start() -> None
Pre-simulation hook.
``start()`` is called when the circuit simulation is about to start.
By definition CBlocks do not require any preparations.
``start()`` typically just checks the :ref:`input signature `.
A signature check is optional, but recommended, because it catches
possible errors early and gives clear problem descriptions.
.. important::
When using ``start()``, always call the ``super().start()``.
.. method:: CBlock.stop() -> None
Post-simulation hook.
``stop()`` is called when the circuit simulation has finished,
but only if :meth:`start` was successfully called.
By definition CBlocks do not require cleanup, so ``stop()``
is rarely used. A possible use-case might be processing
of some gathered statistics data.
An exception in ``stop()`` will be logged, but otherwise ignored.
.. important::
When using ``stop()``, always call the ``super().stop()``
Input signatures
================
An input signature is a dict with the following key:value structure:
- key = the input name (string)
The reserved group name ``'_'`` represents the group of unnamed inputs, if any.
- value = ``None`` or integer:
- ``None`` - if the input is a single input
- the number of inputs in a group - if the input is a group
.. method:: CBlock.input_signature() -> dict[str, None|int]
Return the :ref:`input signature `. The data is available after
connecting the inputs with :meth:`CBlock.connect`.
An :exc:`EdzedInvalidState` is raised when called before
connecting the inputs.
.. method:: CBlock.check_signature(esig: Mapping[str, None|int|Sequence[int]]) -> dict
Compare the expected :ref:`signature ` *esig* with the actual one.
For a successful result items in the *esig* and
items from :meth:`CBlock.input_signature` must match.
If no problems are detected, the input signature data is returned
for eventual further analysis.
If any mismatches are found, a :exc:`ValueError` with a description
of all differences (missing items, etc.) is raised. :meth:`check_signature`
tries to be really helpful in this respect, e.g. it provides suggestions
for probably mistyped names.
In order to support variable input group sizes, the expected
size may be given also as a range of valid values using
a sequence of two values ``[min, max]`` where ``max`` may be ``None``
for no maximum. ``min`` may be also ``None`` for no minimum, but
zero - the lowest possible input count - has the same effect.
Examples of *esig* items::
# single vs group
'input1': None # a single input (not a group)
'input2': 1 # a group with one input (not a single input)
# number of inputs in a group
'group1': 4 # exactly 4 inputs
'group2': [2, None] # 2 or more inputs
'group3': [0, 4] # 4 or less
'group4': [None, None] # input count doesn't matter
Example (Invert)
================
:class:`Invert` source::
class Invert(edzed.CBlock):
def calc_output(self):
return not self._in['_'][0]
def start(self):
super().start()
self.check_signature({'_': 1})