Skip to content

model ¤

Reusable model-library registrations for schematic instance elaboration.

Classes:

Name Description
LibraryModel

Register one library wrapper, binding settings separately per instance.

NetlistDefinition

Canonical topology and shared leaf models, before solver compilation.

LibraryModel ¤

LibraryModel(
    library: Library | None,
    subcircuit: str,
    *,
    defaults: Mapping[str, Any] | None = None,
    params_map: Mapping[str, str] | None = None,
    port_map: Mapping[str, str] | None = None,
    parameter_units: Mapping[str, tuple[str, str]] | None = None,
    parameter_values: Mapping[str, Mapping[Any, float]] | None = None,
    expressions: Mapping[str, str] | None = None,
    module_paths: tuple[Path, ...] = (),
    osdi_modules: tuple[Path, ...] = (),
    compiler: str | None = None,
    cache_dir: Path | None = None,
    analysis: str = "dc",
    state_policy: str = "reject",
    simparams: Mapping[str, float] | None = None
)

Register one library wrapper, binding settings separately per instance.

This object is deliberately non-callable. compile_circuit elaborates wrapper instances to kfnetlist and lets kfnetlist flatten their topology. OSDI descriptor definitions are shared; card and instance values populate individual leaf settings and therefore rows in the native device batch.

params_map maps schematic setting names to library parameter names. parameter_units explicitly declares (source, target) units for those schematic settings. Defaults use the same source units. expressions maps library parameter names to safe expressions over converted settings. Unspecified defaults remain owned by the original library cards.

A temperature_c setting (degrees Celsius) selects the card temperature. It stays available to expressions and parameter_units/parameter_values, but is forwarded to the library as a parameter only when params_map names it.

@tags circulax-simulation

Methods:

Name Description
from_file

Register lazily; parse the selected corner only when instantiated.

inspection_identity

Return deterministic read-only provenance and resolved topology.

instantiate

Build the resolved topology and retain reusable native models.

resolve

Evaluate settings and cards without building or provisioning models.

Source code in circulax/netlist_io/model.py
def __init__(
    self,
    library: Library | None,
    subcircuit: str,
    *,
    defaults: Mapping[str, Any] | None = None,
    params_map: Mapping[str, str] | None = None,
    port_map: Mapping[str, str] | None = None,
    parameter_units: Mapping[str, tuple[str, str]] | None = None,
    parameter_values: Mapping[str, Mapping[Any, float]] | None = None,
    expressions: Mapping[str, str] | None = None,
    module_paths: tuple[Path, ...] = (),
    osdi_modules: tuple[Path, ...] = (),
    compiler: str | None = None,
    cache_dir: Path | None = None,
    analysis: str = "dc",
    state_policy: str = "reject",
    simparams: Mapping[str, float] | None = None,
) -> None:
    """Declare schematic-to-library mappings and native provisioning.

    @tags circulax-simulation
    """
    self.library = library
    self.subcircuit = subcircuit
    self.defaults = dict(defaults or {})
    self.params_map = dict(params_map or {})
    self.port_map = dict(port_map or {})
    self.parameter_units = dict(parameter_units or {})
    self.parameter_values = {name: dict(mapping) for name, mapping in (parameter_values or {}).items()}
    self.expressions = dict(expressions or {})
    self.options = {
        "module_paths": module_paths,
        "osdi_modules": osdi_modules,
        "compiler": compiler,
        "cache_dir": cache_dir,
        "analysis": analysis,
        "state_policy": state_policy,
        "simparams": simparams,
    }
    # Provisioned modules per Library.revision, with the artifact signature they were built from.
    self._modules = {}
    self._module_signatures = {}
    self._digests = FileDigests()
    self._source = None
    self._libraries = {}

from_file classmethod ¤

from_file(
    path: str | Path,
    subcircuit: str,
    *,
    section: str | None = None,
    sections: tuple[str, ...] = (),
    dialect: str = "ngspice",
    temperature_c: float = 27.0,
    include_paths: tuple[Path, ...] = (),
    **options: Any
) -> LibraryModel

Register lazily; parse the selected corner only when instantiated.

@tags circulax-simulation

Source code in circulax/netlist_io/model.py
@classmethod
def from_file(
    cls,
    path: str | Path,
    subcircuit: str,
    *,
    section: str | None = None,
    sections: tuple[str, ...] = (),
    dialect: str = "ngspice",
    temperature_c: float = 27.0,
    include_paths: tuple[Path, ...] = (),
    **options: Any,
) -> LibraryModel:
    """Register lazily; parse the selected corner only when instantiated.

    @tags circulax-simulation
    """
    model = cls(None, subcircuit, **options)
    model._source = (Path(path), section, tuple(sections), dialect, temperature_c, include_paths)
    return model

inspection_identity ¤

inspection_identity(settings: Mapping[str, Any] | None = None) -> dict[str, Any]

Return deterministic read-only provenance and resolved topology.

Source/include contents and available native artifact bytes contribute to the fingerprint. Native artifacts are not loaded or ABI-validated; this identity does not establish backend readiness.

Source code in circulax/netlist_io/model.py
def inspection_identity(self, settings: Mapping[str, Any] | None = None) -> dict[str, Any]:
    """Return deterministic read-only provenance and resolved topology.

    Source/include contents and available native artifact bytes contribute
    to the fingerprint. Native artifacts are not loaded or ABI-validated;
    this identity does not establish backend readiness.
    """
    resolved = self.resolve(settings)
    library = self._select_library({**self.defaults, **(settings or {})})
    artifacts = {Path(path).resolve() for path in self.options["osdi_modules"]}
    artifacts.update(Path(path).resolve() for _, path in resolved.loads)
    artifacts.update(Path(path).resolve() for path, _ in self._modules.get(library.revision, {}).values())
    native: list[dict[str, Any]] = []
    for path in sorted(artifacts, key=str):
        record: dict[str, Any] = {"path": str(path)}
        if path.is_file():
            record["sha256"] = hashlib.sha256(path.read_bytes()).hexdigest()
        else:
            record["missing"] = True
        native.append(record)
    identity = {
        "sources": library.inspection_sources(),
        "native_artifacts": native,
        "resolved": {
            "ports": dict(resolved.ports),
            "temperature_c": resolved.temperature_c,
            "corner": {**self.defaults, **(settings or {})}.get(
                "corner", self._source[1] if self._source is not None else None
            ),
            "instances": [
                {
                    "name": instance.name,
                    "module": instance.module,
                    "nodes": list(instance.nodes),
                    "parameters": dict(instance.parameters),
                }
                for instance in resolved.instances
            ],
            "loads": [{"name": name, "path": str(path)} for name, path in resolved.loads],
        },
    }
    identity["fingerprint"] = hashlib.sha256(json.dumps(identity, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
    return identity

instantiate ¤

instantiate(settings: Mapping[str, Any] | None = None) -> NetlistDefinition

Build the resolved topology and retain reusable native models.

Source code in circulax/netlist_io/model.py
def instantiate(self, settings: Mapping[str, Any] | None = None) -> NetlistDefinition:
    """Build the resolved topology and retain reusable native models."""
    resolved = self.resolve(settings)
    library = self._select_library({**self.defaults, **(settings or {})})
    signature = self._artifact_signature(resolved)
    cached = self._modules.get(library.revision) if self._module_signatures.get(library.revision) == signature else None
    netlist, models, modules = build_resolved(resolved, **self.options, modules=cached, expose_internal_nodes=False)
    self._modules[library.revision] = modules
    self._module_signatures[library.revision] = signature
    return NetlistDefinition(netlist, models)

resolve ¤

resolve(settings: Mapping[str, Any] | None = None) -> ResolvedCircuit

Evaluate settings and cards without building or provisioning models.

Returns ordered electrical leaf topology and public terminal aliases using the same corner, unit and expression bindings as execution. Source files may be parsed and cached; no native model, compiler or simulator is loaded or executed by this operation.

@tags circulax-simulation

Source code in circulax/netlist_io/model.py
def resolve(self, settings: Mapping[str, Any] | None = None) -> ResolvedCircuit:
    """Evaluate settings and cards without building or provisioning models.

    Returns ordered electrical leaf topology and public terminal aliases
    using the same corner, unit and expression bindings as execution.
    Source files may be parsed and cached; no native model, compiler or
    simulator is loaded or executed by this operation.

    @tags circulax-simulation
    """
    values = {**self.defaults, **(settings or {})}
    library = self._select_library(values)
    for name, translation in self.parameter_values.items():
        if name in values:
            if values[name] not in translation:
                msg = f"unsupported value {values[name]!r} for {name!r}; expected one of {tuple(translation)!r}"
                raise ValueError(msg)
            values[name] = translation[values[name]]
    for name, (source, target) in self.parameter_units.items():
        if name in values:
            values[name] = convert_unit(values[name], source, target)
    mapped = {
        self.params_map.get(name, name): value
        for name, value in values.items()
        if name in self.params_map or (not self.expressions and name != "temperature_c")
    }
    mapped.update({name: evaluate_source(expression, values) for name, expression in self.expressions.items()})
    resolved = library.instantiate(self.subcircuit, settings=mapped)
    if self.port_map:
        unknown = set(self.port_map.values()) - resolved.ports.keys()
        if unknown:
            msg = f"unknown library terminals {sorted(unknown)}"
            raise ValueError(msg)
        ports = {name: node for name, node in resolved.ports.items() if name not in self.port_map.values()}
        ports.update({alias: resolved.ports[terminal] for alias, terminal in self.port_map.items()})
        resolved.ports = ports
    return resolved

NetlistDefinition dataclass ¤

NetlistDefinition(source_netlist: Netlist, source_models: dict[str, Any])

Canonical topology and shared leaf models, before solver compilation.