Source code for circuitgenome.designer.models

"""Data models for the Designer module (Layer 4).

Plain dataclasses — they carry no logic and serialize straight into the
``report.json`` the designer writes next to the exported netlists.
"""
from __future__ import annotations

from dataclasses import dataclass, field


[docs] @dataclass class DesignSolution: """One accepted design: a synthesized circuit whose sized, SPICE-measured performance meets every measurable spec. :param name: enumeration name (``circuit_0007``), matching the index the synthesizer CLI would assign, so solutions cross-reference ``cg synthesize`` output. :param topology: the :class:`~circuitgenome.synthesizer.models.TopologyTemplate` name. :param variants: slot name → chosen module-variant name. :param metrics: ngspice-measured metrics (``gain_db``, ``gbw_hz``, ``phase_margin_deg``, ``slew_rate_vps``, ``power_w``, ``cmrr_db``, ``psrr_db``, ``output_swing_max_v``, ``output_swing_min_v``); ``None`` when a measurement could not be extracted. :param margins: normalized margin per constrained+measured spec (``(meas − min)/min`` for min-specs, ``(max − meas)/max`` for max-specs); every value is ≥ 0 by construction. :param worst_margin: the smallest margin (``inf`` when no measured spec constrains this design) — the robustness ranking key. :param netlist_path: the sized flat SPICE netlist written for this design. :param notes: advisory lines from the measurement rig (e.g. why an unconstrained metric could not be extracted). """ name: str topology: str variants: dict[str, str] metrics: dict[str, float | None] margins: dict[str, float] worst_margin: float netlist_path: str notes: list[str] = field(default_factory=list)
[docs] @dataclass class TemplateStats: """Counts for one template's enumerate→size→verify run. ``enumerated`` counts the candidates actually evaluated (after ``limit``); the rejection counters partition ``enumerated − accepted`` by the pipeline stage that rejected the circuit. ``rejection_reasons`` histograms the per-candidate rejection details (numerals collapsed to ``#`` so identical failure classes aggregate), ordered most-common first. """ template: str enumerated: int = 0 sizing_failed: int = 0 # solver found no sizing bias_infeasible: int = 0 # analytical or SPICE DC bias check rejected spec_failed: int = 0 # simulated, but missed ≥ 1 measured spec unverified: int = 0 # a constrained spec could not be SPICE-measured errors: int = 0 # unexpected per-circuit exceptions accepted: int = 0 runtime_s: float = 0.0 rejection_reasons: dict[str, int] = field(default_factory=dict)
[docs] @dataclass class DesignReport: """Output of :func:`~circuitgenome.designer.design`. :param spec: echo of the target :class:`~circuitgenome.sizer.SizingSpec` fields. :param tech: technology name the run sized and simulated against. :param solutions: every accepted design, in enumeration order. :param stats: per-template counters, keyed by topology name. :param best_points: criterion → the winning solution among ``solutions`` (``highest_gain``, ``highest_gbw``, ``highest_phase_margin``, ``lowest_power``, ``most_robust``). :param unverified_specs: constrained spec fields whose measurement came back ``None`` for at least one candidate (e.g. swing/slew on fully-differential topologies) — such candidates are **rejected** as ``unverified``; acceptance requires every constrained spec to be SPICE-measured and met. :param runtime_s: wall-clock time for the whole run. """ spec: dict tech: str solutions: list[DesignSolution] = field(default_factory=list) stats: dict[str, TemplateStats] = field(default_factory=dict) best_points: dict[str, DesignSolution] = field(default_factory=dict) unverified_specs: list[str] = field(default_factory=list) runtime_s: float = 0.0