rocket-engine 0.0.2b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""RocketEDA shared tooling engine.
|
|
2
|
+
|
|
3
|
+
No logic lives here yet. This package only declares the module boundaries
|
|
4
|
+
so the locked environment and the bundled build can be proven before any
|
|
5
|
+
engine behavior lands. See README.md for the module map and coding rules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""`python -m rocket_engine`: the `rocket-engine` command surface.
|
|
2
|
+
|
|
3
|
+
Thin entry over the CLI inbound adapter (`adapters.cli`); a
|
|
4
|
+
`[project.scripts]` binary alias can point here once packaging
|
|
5
|
+
wires it. Exits: 0 execution-ok (even FAIL verdicts), 1 engine
|
|
6
|
+
error, 2 usage error.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .adapters.cli import main
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# adapters — transport edges and solver integrations
|
|
2
|
+
|
|
3
|
+
This package contains the client transports and wrappers around existing EDA tools. composition.Engine selects a registered solver for each operation. Adapters return normalized results; project state and verdict rules stay in engine services.
|
|
4
|
+
|
|
5
|
+
## Client transports
|
|
6
|
+
|
|
7
|
+
| Adapter | Current surface |
|
|
8
|
+
|---|---|
|
|
9
|
+
| cli/ | Interactive command loop, one-operation mode, sequential --batch, and JSON output; entry point is python -m rocket_engine. |
|
|
10
|
+
| mcp/ | Project-scoped MCP stdio server with one tool per catalog operation/query; descriptions come from handler docstrings. Most input schemas remain generic. |
|
|
11
|
+
| http/ | HttpTransport.handle routes one JSON command/query envelope; serve_http adds a FastAPI/uvicorn POST listener on loopback only (no auth, ADR-006). SSE is not implemented here. |
|
|
12
|
+
| ipc/ | IpcTransport.handle routes one JSON command/query envelope. Length-prefix framing and Electron child-process lifecycle are not implemented here. |
|
|
13
|
+
|
|
14
|
+
Events are read through the engine event-query surface; HTTP SSE and pushed IPC events are not currently provided by these transport classes.
|
|
15
|
+
|
|
16
|
+
## Registered solver adapters
|
|
17
|
+
|
|
18
|
+
| Operation | Registered tool(s) |
|
|
19
|
+
|---|---|
|
|
20
|
+
| run_drc | Magic (default), KLayout |
|
|
21
|
+
| run_hdl_test, run_syntax, run_formal, run_synth, run_timing | Icarus, Slang, SBY, Yosys, OpenSTA |
|
|
22
|
+
| run_spice, run_post_layout, run_cosim | ngspice; run_cosim also uses Icarus |
|
|
23
|
+
| run_optical, run_em | SAX/Circulax, MEOW |
|
|
24
|
+
| run_extraction, run_lvs | Magic, Netgen |
|
|
25
|
+
| run_connectivity, run_erc, run_view_sync | connectgraph |
|
|
26
|
+
| generate_views | GDSFactory |
|
|
27
|
+
| run_digital_flow | LibreLane |
|
|
28
|
+
|
|
29
|
+
The exact registration order and default tool are in [composition.py](../composition.py). A registered adapter can still be blocked by a missing executable, incompatible PDK/deck, or an unqualified flow. Use get_capabilities for machine-specific readiness; see the [DIGITAL-FLOW report](../../docs/work/DIGITAL-FLOW/report.md) for the current live-flow limitation.
|
|
30
|
+
|
|
31
|
+
## Gateway rule: api contract + port adapter
|
|
32
|
+
|
|
33
|
+
`api.py` is the one inbound port: it builds the single submit
|
|
34
|
+
envelope (project_id, operation, arguments, minted-or-caller
|
|
35
|
+
request_id) and calls the engine. Every gateway is that contract
|
|
36
|
+
plus one framing adapter, composed — never inherited:
|
|
37
|
+
|
|
38
|
+
- http/mcp/ipc `handle`: parse the wire frame, enforce scope, call
|
|
39
|
+
`api.submit`, serialize the result or error envelope.
|
|
40
|
+
- cli/console `OpDispatcher.execute`: parse op-call text, stamp the
|
|
41
|
+
head revision on operations, call `api.submit`.
|
|
42
|
+
|
|
43
|
+
Gateways own only I/O framing and wire-appropriate errors
|
|
44
|
+
(UsageError on text wires, AdapterError on framed wires); the
|
|
45
|
+
envelope shape, error payloads (`errors.error_payload`), and status
|
|
46
|
+
mapping (`errors.status_for_code`) exist exactly once and are
|
|
47
|
+
shared. A new transport adds one framing module over `api.submit`
|
|
48
|
+
— never a second envelope builder, never a transport subclass.
|
|
49
|
+
|
|
50
|
+
## Boundaries
|
|
51
|
+
|
|
52
|
+
Transport adapters validate the envelope and project scope, then call the api contract (`api.submit` → Engine.submit). Solver adapters implement the ToolAdapter port and own tool invocation and output parsing. The intended module boundary forbids solver adapters from importing one another. Current exception: CosimAdapter calls HdlSimAdapter directly for its digital half; treat this as existing boundary debt, not a pattern to extend.
|
|
53
|
+
|
|
54
|
+
For endpoint documentation rules, see [AGENTS.md](../../AGENTS.md). For operation names, see [interfaces](../interfaces/README.md) and the [generated API reference](../../docs/engine-api.md).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""API contract: the one inbound port every gateway calls.
|
|
2
|
+
|
|
3
|
+
Gateways (http, mcp, ipc, cli/dispatcher, console) own only I/O
|
|
4
|
+
framing — parsing their wire format and serializing results. They
|
|
5
|
+
never build submit envelopes themselves and never subclass each
|
|
6
|
+
other: every gateway composes over `submit` here (composition over
|
|
7
|
+
inheritance), so the envelope shape, request-id minting, and error
|
|
8
|
+
propagation exist exactly once.
|
|
9
|
+
|
|
10
|
+
Validation stays at the boundaries: gateways reject unknown names
|
|
11
|
+
with their wire-appropriate error (UsageError on text wires,
|
|
12
|
+
AdapterError on framed wires), and Engine.submit backstops with
|
|
13
|
+
unsupported_operation. Engine errors propagate untouched — mapping
|
|
14
|
+
them to wire envelopes is framing, owned by each gateway with the
|
|
15
|
+
shared shapes from `adapters.errors`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import uuid
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def submit(engine, project_id, operation, arguments, request_id=None):
|
|
22
|
+
"""Submit one call through the engine port.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
engine: Engine (or stand-in) exposing `submit`.
|
|
26
|
+
project_id: Project the call runs against.
|
|
27
|
+
operation: Cataloged operation or query name.
|
|
28
|
+
arguments: Operation arguments mapping.
|
|
29
|
+
request_id: Envelope id; minted when omitted.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Engine result for the call (ack, completion, or data).
|
|
33
|
+
"""
|
|
34
|
+
return engine.submit({
|
|
35
|
+
"project_id": project_id,
|
|
36
|
+
"operation": operation,
|
|
37
|
+
"arguments": dict(arguments),
|
|
38
|
+
"request_id": request_id or uuid.uuid4().hex,
|
|
39
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Shared transport helpers: JSON parsing and project-scope enforcement."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from .errors import AdapterError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_json(text):
|
|
9
|
+
"""Parse one JSON object request; reject anything else loudly.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
text: Raw request text, expected to be one JSON object.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Parsed request mapping.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
AdapterError: invalid_command when the text is not JSON or
|
|
19
|
+
not an object.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
data = json.loads(text)
|
|
23
|
+
except Exception as exc:
|
|
24
|
+
raise AdapterError("invalid_command", f"Cannot parse request: {exc}") from exc
|
|
25
|
+
if not isinstance(data, dict):
|
|
26
|
+
raise AdapterError("invalid_command", "Request must be a JSON object.")
|
|
27
|
+
return data
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def enforce_scope(envelope_project, bound_project):
|
|
31
|
+
"""Deny envelopes outside the bound project; allow unbound use.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
envelope_project: Project id carried by the envelope.
|
|
35
|
+
bound_project: Project this transport is bound to, or None
|
|
36
|
+
for unbound (any envelope passes).
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
AdapterError: cross_project_denied for an envelope outside
|
|
40
|
+
the bound scope — denied, never routed.
|
|
41
|
+
"""
|
|
42
|
+
if bound_project is not None and envelope_project != bound_project:
|
|
43
|
+
raise AdapterError(
|
|
44
|
+
"cross_project_denied",
|
|
45
|
+
f"Bound to {bound_project}, got {envelope_project}.",
|
|
46
|
+
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""CACE characterization adapter: run_characterize port."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ...execution.errors import ExecutionError
|
|
9
|
+
from ...interfaces import ToolAdapter
|
|
10
|
+
from ..errors import AdapterError
|
|
11
|
+
|
|
12
|
+
OPERATION = "run_characterize"
|
|
13
|
+
_TOOL = "cace"
|
|
14
|
+
_TIMEOUT_S = 900
|
|
15
|
+
_STATUS_RE = re.compile(r"\|\s*(Pass|Fail|Error|Unknown|Skip|Cancel)\s",
|
|
16
|
+
re.IGNORECASE)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CaceAdapter(ToolAdapter):
|
|
20
|
+
"""run_characterize via cace: CLI run plus summary.md verdicts.
|
|
21
|
+
|
|
22
|
+
Serves the run_characterize operation with tool `cace`. Runs
|
|
23
|
+
`cace <datasheet>` as a subprocess and gates on the exit code
|
|
24
|
+
(0 pass, 2 spec miss, 1/3/4 error/unknown/canceled); values
|
|
25
|
+
come from summary.md rows plus simulation_summary.csv, never
|
|
26
|
+
flow.log scraping. In-process reads reuse pure helpers only.
|
|
27
|
+
|
|
28
|
+
Job keys:
|
|
29
|
+
datasheet (required): Input datasheet YAML path.
|
|
30
|
+
run_path: Run dir parent (RUNDIR lands beneath it).
|
|
31
|
+
workdir: Run directory (default ".").
|
|
32
|
+
timeout_s: Budget in seconds (default 900).
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, runner=None):
|
|
39
|
+
self._runner = runner
|
|
40
|
+
|
|
41
|
+
def capabilities(self, target):
|
|
42
|
+
"""Report the served operation and tool.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
target: Target the capabilities are reported for.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Capability mapping with operations and tool.
|
|
49
|
+
"""
|
|
50
|
+
return {"operations": [OPERATION], "tool": _TOOL}
|
|
51
|
+
|
|
52
|
+
def preflight(self, job):
|
|
53
|
+
"""Check operation, datasheet, and cace presence.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
job: Solver job with operation run_characterize.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Checked mapping with datasheet, binary, timeout.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
AdapterError: unsupported_operation, invalid_reference
|
|
63
|
+
for a missing datasheet, missing_dependency with
|
|
64
|
+
BLOCKED-needs-host when cace is absent (2.13.0 pin).
|
|
65
|
+
"""
|
|
66
|
+
if job.get("operation") != OPERATION:
|
|
67
|
+
raise AdapterError(
|
|
68
|
+
"unsupported_operation",
|
|
69
|
+
f"Cace handles {OPERATION}, got {job.get('operation')}.")
|
|
70
|
+
datasheet = job.get("datasheet")
|
|
71
|
+
if not datasheet or not Path(datasheet).is_file():
|
|
72
|
+
raise AdapterError(
|
|
73
|
+
"invalid_reference", f"Datasheet not found: {datasheet}.")
|
|
74
|
+
binary = shutil.which(_TOOL)
|
|
75
|
+
if binary is None:
|
|
76
|
+
raise AdapterError(
|
|
77
|
+
"missing_dependency",
|
|
78
|
+
"BLOCKED-needs-host: cace absent (need cace==2.13.0).")
|
|
79
|
+
try:
|
|
80
|
+
__import__("cace")
|
|
81
|
+
except ImportError as exc:
|
|
82
|
+
raise AdapterError(
|
|
83
|
+
"missing_dependency",
|
|
84
|
+
"BLOCKED-needs-host: cace python package absent.",
|
|
85
|
+
) from exc
|
|
86
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
87
|
+
if (not isinstance(timeout, (int, float))
|
|
88
|
+
or isinstance(timeout, bool) or not timeout > 0):
|
|
89
|
+
raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
|
|
90
|
+
checked = {"datasheet": str(datasheet), "binary": binary,
|
|
91
|
+
"timeout_s": timeout,
|
|
92
|
+
"run_path": job.get("run_path")}
|
|
93
|
+
if self._runner is not None:
|
|
94
|
+
try:
|
|
95
|
+
self._runner.preflight()
|
|
96
|
+
except ExecutionError as exc:
|
|
97
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
98
|
+
return checked
|
|
99
|
+
|
|
100
|
+
def execute(self, job):
|
|
101
|
+
"""Run cace on the datasheet under a timeout.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
job: Solver job with operation run_characterize.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Native output with exit_code, stdout/stderr, summary
|
|
108
|
+
markdown, and run dir.
|
|
109
|
+
"""
|
|
110
|
+
checked = self.preflight(job)
|
|
111
|
+
workdir = job.get("workdir", ".")
|
|
112
|
+
if not Path(workdir).is_dir():
|
|
113
|
+
raise AdapterError(
|
|
114
|
+
"invalid_reference", f"Workdir not found: {workdir}.")
|
|
115
|
+
argv = [checked["binary"], checked["datasheet"],
|
|
116
|
+
"--no-progress-bar"]
|
|
117
|
+
if checked["run_path"]:
|
|
118
|
+
argv.extend(["--run-path", str(checked["run_path"])])
|
|
119
|
+
try:
|
|
120
|
+
proc = subprocess.run(argv, capture_output=True, text=True,
|
|
121
|
+
timeout=checked["timeout_s"], cwd=workdir)
|
|
122
|
+
except subprocess.TimeoutExpired as exc:
|
|
123
|
+
raise AdapterError(
|
|
124
|
+
"runner_unavailable", "Cace run timed out.") from exc
|
|
125
|
+
summary = ""
|
|
126
|
+
run_dir = checked["run_path"] or workdir
|
|
127
|
+
for cand in sorted(Path(run_dir).glob("RUN_*/summary.md")):
|
|
128
|
+
summary = cand.read_text(errors="replace")
|
|
129
|
+
run_dir = str(cand.parent)
|
|
130
|
+
break
|
|
131
|
+
return {"exit_code": proc.returncode, "stdout": proc.stdout,
|
|
132
|
+
"stderr": proc.stderr, "summary_md": summary,
|
|
133
|
+
"run_dir": run_dir}
|
|
134
|
+
|
|
135
|
+
def parse(self, native_output):
|
|
136
|
+
"""Parse summary.md status cells to pass/fail verdict.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
native_output: Native output with summary markdown.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
{total, violations[], source}.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
AdapterError: runner_unavailable with no summary.
|
|
146
|
+
"""
|
|
147
|
+
summary = native_output.get("summary_md")
|
|
148
|
+
if not summary or not _STATUS_RE.search(summary):
|
|
149
|
+
raise AdapterError(
|
|
150
|
+
"runner_unavailable", "Cace run left no summary.md.")
|
|
151
|
+
bad = [m for m in ("Fail", "Error") if m.lower() in summary.lower()]
|
|
152
|
+
if native_output.get("exit_code", 0) not in (0,) or bad:
|
|
153
|
+
fails = [line.strip() for line in summary.splitlines()
|
|
154
|
+
if "Fail" in line or "Error" in line]
|
|
155
|
+
violations = [{"rule": "characterize_fail", "severity": "error",
|
|
156
|
+
"message": line[:300] or "Cace spec miss."}
|
|
157
|
+
for line in fails] or [{
|
|
158
|
+
"rule": "characterize_fail",
|
|
159
|
+
"severity": "error",
|
|
160
|
+
"message": "Cace run failed."}]
|
|
161
|
+
return {"total": len(violations), "violations": violations,
|
|
162
|
+
"source": "summary_md"}
|
|
163
|
+
return {"total": 0, "violations": [], "source": "summary_md"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Circulax time-domain adapter
|
|
2
|
+
|
|
3
|
+
The Circulax package has two roles:
|
|
4
|
+
|
|
5
|
+
- Its RC step smoke qualifies the installed library with a small, known-good and known-bad circuit. It returns solver version, time samples, capacitor voltage, and final voltage.
|
|
6
|
+
- CirculaxAdapter is registered for the run_optical operation as the circulax deck tool. Engine jobs provide the simulation inputs and receive normalized results.
|
|
7
|
+
|
|
8
|
+
The smoke uses a voltage step, resistor, and capacitor compiled through kfnetlist. With Circulax 0.2.5, the 1 kΩ / 1 nF case reaches 0.9933 V at 5 μs; the 100 kΩ / 1 nF case reaches 0.0488 V and misses the threshold. A missing Circulax dependency reports missing_dependency rather than a fabricated verdict.
|
|
9
|
+
|
|
10
|
+
See [adapter.py](adapter.py) and the [solver map](../README.md).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Circulax time-domain smoke: smallest honest RC transient.
|
|
2
|
+
|
|
3
|
+
A V step driving R + C to GND, compiled via kfnetlist and run with
|
|
4
|
+
`Circuit.transient` from a discharged (`y0=zeros`) start. Verified
|
|
5
|
+
against circulax 0.2.5: good RC (1k/1nF, tau=1us) charges
|
|
6
|
+
Vc(1us)=0.6321, Vc(5us)=0.9933; slow RC (100k/1nF) sits at 0.0488.
|
|
7
|
+
The same 0.9 V @5us threshold passes the good run and fails the slow
|
|
8
|
+
run — the good-vs-bad signal. Solver imports stay lazy so bare envs
|
|
9
|
+
fail loud (`missing_dependency`), never fake-green. A full
|
|
10
|
+
`deck.tool: circulax` Engine adapter is follow-up, out of scope.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ..errors import AdapterError
|
|
14
|
+
|
|
15
|
+
CIRCULAX_PIN = "0.2.5"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def circulax_version():
|
|
19
|
+
"""Proven solver version, observable per run (uv.lock pin).
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Installed circulax version, or "unknown" when the package
|
|
23
|
+
carries no version attribute.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
AdapterError: missing_dependency when circulax is not
|
|
27
|
+
importable.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
import circulax
|
|
31
|
+
except ImportError as exc:
|
|
32
|
+
raise AdapterError(
|
|
33
|
+
"missing_dependency",
|
|
34
|
+
f"circulax is not importable: {exc} "
|
|
35
|
+
"(uv sync).",
|
|
36
|
+
) from exc
|
|
37
|
+
return getattr(circulax, "__version__", "unknown")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def run_rc_step(*, R=1e3, C=1e-9, V=1.0, t1=5e-6, points=11, dt0=1e-8):
|
|
41
|
+
"""Run the RC step transient live; return the cap voltage trace.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
R: Resistance in ohms (positive).
|
|
45
|
+
C: Capacitance in farads (positive).
|
|
46
|
+
V: Step voltage in volts.
|
|
47
|
+
t1: End time in seconds (positive).
|
|
48
|
+
points: Samples on the grid (at least 2).
|
|
49
|
+
dt0: Initial solver step in seconds (positive).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
`{circulax_version, R, C, V, t_s[], vc_V[], final_V}` —
|
|
53
|
+
plain floats, JSON-friendly.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
AdapterError: invalid_command on bad parameters,
|
|
57
|
+
missing_dependency where circulax is uninstalled.
|
|
58
|
+
"""
|
|
59
|
+
if not R > 0 or not C > 0 or not t1 > 0 or not dt0 > 0:
|
|
60
|
+
raise AdapterError(
|
|
61
|
+
"invalid_command",
|
|
62
|
+
f"RC step needs positive R/C/t1/dt0, got R={R!r} C={C!r} "
|
|
63
|
+
f"t1={t1!r} dt0={dt0!r}.",
|
|
64
|
+
)
|
|
65
|
+
if not isinstance(points, int) or points < 2:
|
|
66
|
+
raise AdapterError(
|
|
67
|
+
"invalid_command", f"RC step needs points >= 2, got {points!r}."
|
|
68
|
+
)
|
|
69
|
+
try:
|
|
70
|
+
import jax.numpy as jnp
|
|
71
|
+
import kfnetlist as kfnl
|
|
72
|
+
from circulax import compile_circuit
|
|
73
|
+
from circulax.components.electronic import (
|
|
74
|
+
Capacitor,
|
|
75
|
+
Resistor,
|
|
76
|
+
VoltageSource,
|
|
77
|
+
)
|
|
78
|
+
except ImportError as exc:
|
|
79
|
+
raise AdapterError(
|
|
80
|
+
"missing_dependency",
|
|
81
|
+
f"circulax is not importable: {exc} "
|
|
82
|
+
"(uv sync).",
|
|
83
|
+
) from exc
|
|
84
|
+
netlist = kfnl.Netlist()
|
|
85
|
+
netlist.create_inst(name="V1", kcl="", component="vsource", settings={"V": V})
|
|
86
|
+
netlist.create_inst(name="R1", kcl="", component="resistor", settings={"R": R})
|
|
87
|
+
netlist.create_inst(name="C1", kcl="", component="capacitor", settings={"C": C})
|
|
88
|
+
netlist.create_inst(name="GND", kcl="", component="ground")
|
|
89
|
+
netlist.create_net(kfnl.PortRef("V1", "p1"), kfnl.PortRef("R1", "p1"))
|
|
90
|
+
netlist.create_net(kfnl.PortRef("V1", "p2"), kfnl.PortRef("GND", "p1"))
|
|
91
|
+
netlist.create_net(kfnl.PortRef("R1", "p2"), kfnl.PortRef("C1", "p1"))
|
|
92
|
+
netlist.create_net(kfnl.PortRef("C1", "p2"), kfnl.PortRef("GND", "p1"))
|
|
93
|
+
circuit = compile_circuit(
|
|
94
|
+
netlist,
|
|
95
|
+
{"vsource": VoltageSource, "resistor": Resistor, "capacitor": Capacitor},
|
|
96
|
+
)
|
|
97
|
+
grid = jnp.linspace(0, t1, points)
|
|
98
|
+
solution = circuit.transient(
|
|
99
|
+
t0=0.0, t1=t1, dt0=dt0, saveat=grid, y0=jnp.zeros(circuit.sys_size)
|
|
100
|
+
)
|
|
101
|
+
cap = circuit.port_map["C1,p1"]
|
|
102
|
+
times = [float(t) for t in solution.ts]
|
|
103
|
+
voltages = [float(row[cap]) for row in solution.ys]
|
|
104
|
+
return {
|
|
105
|
+
"circulax_version": circulax_version(),
|
|
106
|
+
"R": R,
|
|
107
|
+
"C": C,
|
|
108
|
+
"V": V,
|
|
109
|
+
"t_s": times,
|
|
110
|
+
"vc_V": voltages,
|
|
111
|
+
"final_V": voltages[-1],
|
|
112
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Staged circulax sim driver: job JSON in, result envelope JSON on stdout.
|
|
2
|
+
|
|
3
|
+
The adapter copies this file into the run workdir next to
|
|
4
|
+
`circulax_job.json` and executes `python circulax_driver.py
|
|
5
|
+
circulax_job.json`. One marker-delimited JSON envelope is printed to stdout
|
|
6
|
+
(ROCKETEDA_ENVELOPE_BEGIN/END); chatter outside the markers is
|
|
7
|
+
tolerated and stays in logs.
|
|
8
|
+
|
|
9
|
+
The netlist is built from the project spec (`doc` instances +
|
|
10
|
+
connections + transient): nothing is hardcoded — R/C/V, the
|
|
11
|
+
topology, the time grid, and the observed port all ride the job.
|
|
12
|
+
Only the proven transient path runs (`Circuit.transient` from a
|
|
13
|
+
discharged start); DC/AC/harmonic-balance stay refused in the
|
|
14
|
+
adapter until a proof lands.
|
|
15
|
+
|
|
16
|
+
Envelope: `{"ok": true, "circulax_version, "result": {t_s[],
|
|
17
|
+
v_V[], final_V, observe, t1_s, points}}` or `{"ok": false, "error":
|
|
18
|
+
{code, message, token, line}}`. Exit 0 on sim success, 2 on a
|
|
19
|
+
malformed circuit, 3 when circulax is not importable. Anything else
|
|
20
|
+
explodes with a traceback and exit 1, which parse() reports as
|
|
21
|
+
runner_unavailable rather than mistaking a tool crash for physics.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
_ENVELOPE_BEGIN = "ROCKETEDA_ENVELOPE_BEGIN"
|
|
29
|
+
_ENVELOPE_END = "ROCKETEDA_ENVELOPE_END"
|
|
30
|
+
|
|
31
|
+
_QUOTED_RE = re.compile(r"""['\"]([^'\"]+)['\"]""")
|
|
32
|
+
|
|
33
|
+
_COMPONENTS = ("vsource", "resistor", "capacitor", "ground")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _token(doc, message):
|
|
37
|
+
instances = doc.get("instances") or {}
|
|
38
|
+
names = list(instances)
|
|
39
|
+
names.extend(list(_COMPONENTS))
|
|
40
|
+
for name in names:
|
|
41
|
+
if name and re.search(r"\b" + re.escape(name) + r"\b", message):
|
|
42
|
+
return name
|
|
43
|
+
for match in _QUOTED_RE.finditer(message):
|
|
44
|
+
return match.group(1)
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _find_line(text, token):
|
|
49
|
+
if not token:
|
|
50
|
+
return None
|
|
51
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
52
|
+
if token in line:
|
|
53
|
+
return number
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _fail(code, message, token, line):
|
|
58
|
+
print(_ENVELOPE_BEGIN)
|
|
59
|
+
print(
|
|
60
|
+
json.dumps(
|
|
61
|
+
{
|
|
62
|
+
"ok": False,
|
|
63
|
+
"error": {
|
|
64
|
+
"code": code,
|
|
65
|
+
"message": message,
|
|
66
|
+
"token": token,
|
|
67
|
+
"line": line,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
print(_ENVELOPE_END)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main(argv):
|
|
76
|
+
# TODO: why is the output stdout??!!!! reserve the json, pass the python object as is back to the adapter level, and it will fanout from there to any active adapter who is responsible for formatting said outputs!!!!
|
|
77
|
+
"""Run one staged circulax transient: job JSON in, envelope on stdout.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
argv: Argument vector; argv[1] is the staged job JSON path.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Process exit code: 0 on sim success, 2 on a malformed
|
|
84
|
+
circuit, 3 when circulax is not importable.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: Unknown component or bad observe port (raised
|
|
88
|
+
and caught inside main into the invalid_circuit
|
|
89
|
+
envelope — never escapes).
|
|
90
|
+
"""
|
|
91
|
+
with open(argv[1], encoding="utf-8") as handle:
|
|
92
|
+
job = json.load(handle)
|
|
93
|
+
text = job.get("text", "")
|
|
94
|
+
doc = job["doc"]
|
|
95
|
+
try:
|
|
96
|
+
import jax.numpy as jnp
|
|
97
|
+
import kfnetlist as kfnl
|
|
98
|
+
from circulax import compile_circuit
|
|
99
|
+
from circulax.components.electronic import (
|
|
100
|
+
Capacitor,
|
|
101
|
+
Resistor,
|
|
102
|
+
VoltageSource,
|
|
103
|
+
)
|
|
104
|
+
except ImportError as exc:
|
|
105
|
+
_fail(
|
|
106
|
+
"missing_dependency",
|
|
107
|
+
f"circulax is not importable: {exc} "
|
|
108
|
+
"(uv sync).",
|
|
109
|
+
None,
|
|
110
|
+
None,
|
|
111
|
+
)
|
|
112
|
+
return 3
|
|
113
|
+
try:
|
|
114
|
+
import circulax
|
|
115
|
+
|
|
116
|
+
classes = {
|
|
117
|
+
"vsource": VoltageSource,
|
|
118
|
+
"resistor": Resistor,
|
|
119
|
+
"capacitor": Capacitor,
|
|
120
|
+
}
|
|
121
|
+
netlist = kfnl.Netlist()
|
|
122
|
+
for name, inst in doc["instances"].items():
|
|
123
|
+
component = inst["component"]
|
|
124
|
+
if component not in _COMPONENTS:
|
|
125
|
+
raise ValueError(f"Unknown component: {component!r}.")
|
|
126
|
+
netlist.create_inst(
|
|
127
|
+
name=name,
|
|
128
|
+
kcl="",
|
|
129
|
+
component=component,
|
|
130
|
+
settings=dict(inst.get("settings") or {}),
|
|
131
|
+
)
|
|
132
|
+
for left, right in (doc.get("connections") or {}).items():
|
|
133
|
+
left_inst, _, left_port = left.partition(",")
|
|
134
|
+
right_inst, _, right_port = right.partition(",")
|
|
135
|
+
netlist.create_net(
|
|
136
|
+
kfnl.PortRef(left_inst, left_port),
|
|
137
|
+
kfnl.PortRef(right_inst, right_port),
|
|
138
|
+
)
|
|
139
|
+
circuit = compile_circuit(
|
|
140
|
+
netlist,
|
|
141
|
+
{key: classes[key] for key in classes},
|
|
142
|
+
)
|
|
143
|
+
transient = doc["transient"]
|
|
144
|
+
t1 = float(transient["t1_s"])
|
|
145
|
+
points = int(transient["points"])
|
|
146
|
+
dt0 = float(transient.get("dt0_s", 1e-8))
|
|
147
|
+
observe = transient["observe"]
|
|
148
|
+
if observe not in circuit.port_map:
|
|
149
|
+
raise ValueError(f"Unknown observe port: {observe!r}.")
|
|
150
|
+
grid = jnp.linspace(0, t1, points)
|
|
151
|
+
solution = circuit.transient(
|
|
152
|
+
t0=0.0,
|
|
153
|
+
t1=t1,
|
|
154
|
+
dt0=dt0,
|
|
155
|
+
saveat=grid,
|
|
156
|
+
y0=jnp.zeros(circuit.sys_size),
|
|
157
|
+
)
|
|
158
|
+
index = circuit.port_map[observe]
|
|
159
|
+
times = [float(t) for t in solution.ts]
|
|
160
|
+
volts = [float(row[index]) for row in solution.ys]
|
|
161
|
+
except (ValueError, KeyError, TypeError) as exc:
|
|
162
|
+
message = f"{type(exc).__name__}: {exc}"
|
|
163
|
+
token = _token(doc, message)
|
|
164
|
+
_fail("invalid_circuit", message, token, _find_line(text, token))
|
|
165
|
+
return 2
|
|
166
|
+
print(_ENVELOPE_BEGIN)
|
|
167
|
+
print(
|
|
168
|
+
json.dumps(
|
|
169
|
+
{
|
|
170
|
+
"ok": True,
|
|
171
|
+
"circulax_version": getattr(
|
|
172
|
+
circulax, "__version__", "unknown"
|
|
173
|
+
),
|
|
174
|
+
"result": {
|
|
175
|
+
"t_s": times,
|
|
176
|
+
"v_V": volts,
|
|
177
|
+
"final_V": volts[-1],
|
|
178
|
+
"observe": observe,
|
|
179
|
+
"t1_s": t1,
|
|
180
|
+
"points": points,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
print(_ENVELOPE_END)
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
sys.exit(main(sys.argv))
|