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
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
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
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
|