Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
147
docs/model-ir.md
Normal file
147
docs/model-ir.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Entropyk Model IR (unified schema)
|
||||
|
||||
Entropyk uses **one declarative Model IR** — a schema-versioned JSON document —
|
||||
as the single source of truth shared by the CLI, the web UI, and (once present)
|
||||
the Python and WASM bindings. There is no divergent per-frontend schema: the web
|
||||
graph editor serializes to the same document the CLI loads.
|
||||
|
||||
The IR is defined by the Rust types in [`crates/cli/src/config.rs`](../crates/cli/src/config.rs).
|
||||
The machine-readable JSON Schema is **generated from those types** (via `schemars`)
|
||||
so it can never drift from what the loader accepts.
|
||||
|
||||
## Getting the schema
|
||||
|
||||
```bash
|
||||
# Print the canonical JSON Schema (draft-07) to stdout
|
||||
cargo run -p entropyk-cli -- schema
|
||||
|
||||
# …or write it to a file (the committed copy lives at docs/model-ir.schema.json)
|
||||
cargo run -p entropyk-cli -- schema --output docs/model-ir.schema.json
|
||||
```
|
||||
|
||||
External tools and the UI can validate documents against
|
||||
[`model-ir.schema.json`](./model-ir.schema.json).
|
||||
|
||||
## Versioning
|
||||
|
||||
Every document carries a `schema_version`. This lets the schema — and any embedded
|
||||
standard/norm references (SEER/SCOP/IPLV/NPLV) — evolve without silently
|
||||
misreading older files.
|
||||
|
||||
| `schema_version` | Meaning |
|
||||
| ---------------- | ------- |
|
||||
| absent | Treated as `"1"` (legacy). |
|
||||
| `"1"` | Flat `circuits` / `components` / `edges` graph. |
|
||||
| `"2"` | Adds `controls`, `subsystems`, `instances`, `connections`. |
|
||||
|
||||
The loader accepts every version listed in `SUPPORTED_SCHEMA_VERSIONS`
|
||||
(currently `1`, `2`) and rejects anything else with a clear error. `v1` documents
|
||||
load unchanged — the new fields are all optional and default to empty.
|
||||
|
||||
## Top-level fields
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"schema_version": "2",
|
||||
"fluid": "R134a",
|
||||
"fluid_backend": "CoolProp",
|
||||
|
||||
"circuits": [ /* flat refrigerant loops (v1) */ ],
|
||||
"thermal_couplings": [ /* inter-circuit UA links (v1) */ ],
|
||||
|
||||
"subsystems": { /* reusable parameterized templates (v2) */ },
|
||||
"instances": [ /* template instantiations (v2) */ ],
|
||||
"connections": [ /* external edges between instance ports (v2) */ ],
|
||||
|
||||
"controls": [ /* co-solved steady-state control loops (v2) */ ],
|
||||
|
||||
"solver": { "strategy": "fallback", "max_iterations": 300, "tolerance": 1e-6 }
|
||||
}
|
||||
```
|
||||
|
||||
### `subsystems` + `instances` (hierarchical templates)
|
||||
|
||||
A `SubsystemTemplate` is a parameterized assembly of components and internal
|
||||
edges, exposing a reduced set of external `ports`. It is declared once and
|
||||
instantiated any number of times. At **load time** each instance is *flattened*
|
||||
into the flat `circuits/components/edges` graph — the solver never sees the
|
||||
hierarchy — with component names prefixed `"{instance}."`.
|
||||
|
||||
Parameter substitution: any component parameter whose value is a string `"$name"`
|
||||
is replaced by the resolved parameter (instance override → template default).
|
||||
Because `ua`, `secondary_*`, `isentropic_efficiency`, `t_cond_k`, … all flow
|
||||
through the component parameter catch-all, essentially every physical knob is
|
||||
parameterizable.
|
||||
|
||||
```jsonc
|
||||
"subsystems": {
|
||||
"EmergentCircuit": {
|
||||
"params": { "ua_cond": 766.0, "ua_evap": 1468.0 },
|
||||
"components": [
|
||||
{ "type": "IsentropicCompressor", "name": "comp", "isentropic_efficiency": 0.7 },
|
||||
{ "type": "Condenser", "name": "cond", "ua": "$ua_cond" },
|
||||
{ "type": "IsenthalpicExpansionValve", "name": "exv" },
|
||||
{ "type": "Evaporator", "name": "evap", "ua": "$ua_evap" }
|
||||
],
|
||||
"edges": [
|
||||
{ "from": "comp:outlet", "to": "cond:inlet" },
|
||||
{ "from": "cond:outlet", "to": "exv:inlet" },
|
||||
{ "from": "exv:outlet", "to": "evap:inlet" },
|
||||
{ "from": "evap:outlet", "to": "comp:inlet" }
|
||||
],
|
||||
"ports": { "suction": "evap:outlet", "discharge": "comp:outlet" }
|
||||
}
|
||||
},
|
||||
"instances": [
|
||||
{ "of": "EmergentCircuit", "name": "A", "circuit": 0, "params": { "ua_cond": 766.0 } },
|
||||
{ "of": "EmergentCircuit", "name": "B", "circuit": 1, "params": { "ua_cond": 900.0 } }
|
||||
]
|
||||
```
|
||||
|
||||
This unlocks multi-circuit machines (e.g. 61XW `System_2C`, multi-module 61AQ):
|
||||
define the circuit once, instantiate `A`/`B` with different parameters. See
|
||||
[`crates/cli/examples/chiller_2circuit_subsystems.json`](../crates/cli/examples/chiller_2circuit_subsystems.json).
|
||||
|
||||
`connections` wire external ports together (or literal `component:port` endpoints):
|
||||
|
||||
```jsonc
|
||||
"connections": [ { "from": "A.discharge", "to": "B.suction" } ]
|
||||
```
|
||||
|
||||
### `controls` (co-solved steady-state control loops)
|
||||
|
||||
Each control loop is solved **inside the same Newton system** as the physics — no
|
||||
time integration. A `SaturatedController` is a saturated-PI loop with exact
|
||||
anti-windup that drives a measured plant output (`measure`) to `target` by
|
||||
manipulating an actuator factor (`actuator`) within `[min, max]`.
|
||||
|
||||
```jsonc
|
||||
"controls": [
|
||||
{
|
||||
"type": "SaturatedController",
|
||||
"id": "evap_capacity",
|
||||
"measure": { "component": "evap", "output": "capacity" },
|
||||
"actuator": { "component": "comp", "factor": "z_flow", "initial": 1.0, "min": 0.5, "max": 1.5 },
|
||||
"target": 7000.0,
|
||||
"gain": 0.01,
|
||||
"band": 1.0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Measurable outputs: `capacity`, `heatTransferRate`, `superheat`, `subcooling`,
|
||||
`saturationTemperature`, `massFlowRate`, `pressure`, `temperature`.
|
||||
Actuator Z-factors (canonical): `z_flow`, `z_dp`, `z_ua`, `z_power`, `z_etav`
|
||||
(BOLT equivalents: `Z_flow_suc`, `Z_dpc`, `Z_UA`, `Z_power`; legacy `f_*` names still accepted).
|
||||
|
||||
When an instance is involved, reference the prefixed name, e.g.
|
||||
`"component": "A.evap"`. See
|
||||
[`crates/cli/examples/chiller_r134a_capacity_control.json`](../crates/cli/examples/chiller_r134a_capacity_control.json).
|
||||
|
||||
## One IR, every frontend
|
||||
|
||||
- **CLI** — `entropyk-cli run|validate|rate|scop|seer …` load this IR directly.
|
||||
- **Web UI** (`apps/web`) — the React-Flow graph serializes to this exact IR via
|
||||
`buildScenarioConfig` (`apps/web/src/lib/configBuilder.ts`); it emits
|
||||
`schema_version` from the shared `SCHEMA_VERSION` constant.
|
||||
- **Python / WASM bindings** — consume the same JSON document, no separate schema.
|
||||
Reference in New Issue
Block a user