Source code for circuitgenome.synthesizer.compatibility.polarity
"""
Cross-slot compatibility filter for :func:`~circuitgenome.synthesizer.synthesizer.enumerate_circuits`.
Some module variants only form a circuit with a real DC current path when
paired with variants of a matching electrical *polarity*. For example, an
NMOS ``input_pair`` (``differential_pair_nmos``) draws current out of
``out1``/``out2`` into the tail, so it needs a ``load`` that *sources*
current into ``out1``/``out2`` from vdd, and a ``tail_current`` that *sinks*
the tail node to gnd. Pairing it with ``active_load_nmos`` (which also sinks
to gnd) or ``current_mirror_tail_pmos`` (which also sources from vdd) leaves
the shared node with no current path -- a non-functional circuit.
Each variant declares its required polarity via the ``polarity`` field in
``opamp_modules.yaml``: ``"pmos_input"``, ``"nmos_input"``, or omitted/``None``
for variants that work with either polarity (e.g. ``inverter_based_input``,
and -- for now -- every ``bias_generation`` variant, since a single bias_gen
variant feeds all four bias rails regardless of the load/input-pair polarity
that consumes each one).
``input_pair`` is the reference: a combination is valid as long as every other
polarity-tagged variant in it matches ``input_pair.polarity`` (or has no tag
at all). ``input_pair`` variants with no tag of their own
(``inverter_based_input``) impose no constraint, so they're compatible with
every ``load``/``tail_current``/etc. regardless of those variants' tags.
To extend: tag new or edited variants with ``polarity: pmos_input`` /
``polarity: nmos_input`` in ``opamp_modules.yaml`` -- no code changes needed
here.
"""
from __future__ import annotations
from ..models import ModuleVariant
[docs]
def is_combination_valid(variant_map: dict[str, ModuleVariant]) -> bool:
"""Return ``False`` if *variant_map* pairs ``input_pair`` with a
polarity-tagged variant of the opposite polarity.
``input_pair`` is the reference: every other slot's ``polarity`` tag (if
any) must match ``input_pair.polarity``. If ``input_pair`` itself has no
polarity tag (``inverter_based_input``), it has no current-direction
requirement, so every combination involving it is valid.
"""
input_pair = variant_map.get("input_pair")
if input_pair is None or input_pair.polarity is None:
return True
return all(
variant.polarity is None or variant.polarity == input_pair.polarity
for slot_name, variant in variant_map.items()
if slot_name != "input_pair"
)