Foundation Closed Loop

This page shows the smallest project-level Monata path that exercises the framework without adding simulator backends:

Project -> Library -> Cell -> generated views -> SimTask -> SimResult -> Spec

It uses the existing ngspice-subprocess backend. Xyce, VACASK, binary rawfile parsing, and richer model-deck projection remain deferred capabilities.

Create a Project and Library

from monata.workspace.project import Project

project = Project("work/closed_loop")
lib = project.create_library("analog", tech_model_paths=[])

assert project.list_libraries() == ["analog"]
assert Project("work/closed_loop").get_library("analog").name == "analog"

Project is the workspace container. It records libraries and experiments; it does not introduce a separate workspace service or database.

Add a Cell and Generated Views

cell = lib.create_cell("rc_probe", description="foundation closed-loop cell")

(cell.path / "schematic.py").write_text(
    "from monata.netlist import SubCircuit\n"
    "\n"
    "class RcProbe(SubCircuit):\n"
    "    NAME = 'rc_probe'\n"
    "    NODES = ('inp', 'out', 'gnd')\n"
    "\n"
    "    def build(self):\n"
    "        self.resistor('load', 'inp', 'out', '1k')\n"
    "        self.capacitor('hold', 'out', 'gnd', '1n')\n"
)

cell.create_view("schematic", entry="schematic.py", cls_name="RcProbe")
symbol_path = cell.generate_symbol()
netlist_path = cell.generate_netlist()

Generated symbol and netlist views are marked as generated. Monata refuses to overwrite user-modified generated views unless force=True is explicit.

Register Model Assets Explicitly

Existing .osdi assets can be registered and converted into paths suitable for SimTask.osdi_paths:

from monata.models import ModelRegistry

models = ModelRegistry(auto_discover=False)
models.register("mos", "models/bsimcmg.osdi", module_name="bsimcmg")
osdi_paths = models.osdi_paths("mos")

This is an explicit preparation step. Simulation does not implicitly compile, discover, or load models beyond the paths passed into SimTask.

Run the Existing ngspice Backend

from monata.measure.spec import Spec
from monata.netlist import Circuit
from monata.sim.core import DCSpec, LocalExecutor, SimTask

circuit = Circuit("foundation dc smoke")
circuit.subckt(cell["schematic"].load()())
circuit.voltage("1", "in", "0", "0")
circuit.instance("probe", ("in", "out", "0"), "rc_probe")
circuit.resistor("sense", "out", "0", "1g")

task = SimTask(
    circuit=circuit,
    analysis_spec=DCSpec(source="V1", start=0, stop=1, step=0.5),
    output_names=["out"],
    metadata={"project": project.name, "library": lib.name, "cell": cell.name},
)

result = LocalExecutor(max_workers=1).submit(task).result()
if result.status != "ok":
    raise RuntimeError(result.error_message)

final_vout = Spec("final_vout", lambda sim: float(sim.waveforms["out"][-1]), min=0.99, max=1.01)
assert final_vout.evaluate(result).passed

Always check result.status before evaluating measurements.

Save the Result in an Experiment

experiment = project.new_experiment("dc_smoke")
experiment.save_results("nominal", result)
loaded = experiment.load_results("nominal")

assert loaded.status == "ok"

This completes the foundation loop: project metadata can be reloaded, the schematic view supplies the subcircuit used by simulation, generated artifacts are present on disk, model assets are explicit references, simulation uses the existing backend, and measurements consume the returned SimResult.