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:
@@ -16,6 +16,8 @@ Ce tutoriel décrit **pas à pas**, sans raccourci, comment utiliser l’outil E
|
||||
6. [Créer notre fichier sample pas à pas](#6-créer-notre-fichier-sample-pas-à-pas)
|
||||
7. [Exporter les résultats (option -o)](#7-exporter-les-résultats-option--o)
|
||||
8. [Mode batch (optionnel)](#8-mode-batch-optionnel)
|
||||
9. [Qualifier une machine à charge partielle (rate — IPLV / ESEER)](#9-qualifier-une-machine-à-charge-partielle-rate--iplv--eseer)
|
||||
10. [Performances saisonnières (scop / seer — méthode par bins EN 14825)](#10-performances-saisonnières-scop--seer--méthode-par-bins-en-14825)
|
||||
|
||||
---
|
||||
|
||||
@@ -161,3 +163,122 @@ Tu dois obtenir un message d’erreur (par exemple « Configuration error » ou
|
||||
---
|
||||
|
||||
*[Sections 4 à 8 à compléter au fur et à mesure]*
|
||||
|
||||
---
|
||||
|
||||
## 9. Qualifier une machine à charge partielle (rate — IPLV / ESEER)
|
||||
|
||||
La commande **rate** calcule un indice de performance à charge partielle
|
||||
(**IPLV**, **NPLV**, **ESEER**…) en re-résolvant le cycle à plusieurs points de
|
||||
charge, puis en les pondérant selon la norme choisie. Chaque point est une
|
||||
**vraie** résolution couplée (pas d'approximation) : on fait varier les
|
||||
températures secondaires et la vitesse du compresseur, et on lit l'EER émergent.
|
||||
|
||||
**Étape 9.1 — Lancer l'exemple fourni**
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli rate --config crates/cli/examples/rate_chiller_iplv_ahri.json
|
||||
```
|
||||
|
||||
Tu obtiens un tableau des points (100/75/50/25 %) avec leur EER, puis la valeur
|
||||
intégrée, par exemple `IPLV = 8.114`. Comme la portée (« lift ») de pression
|
||||
diminue à charge partielle, l'EER augmente à mesure que la charge baisse.
|
||||
|
||||
**Étape 9.2 — Choisir / changer la norme**
|
||||
|
||||
La norme est **modulable** (voir `docs/rating-and-seasonal-metrics.md`). Dans le
|
||||
fichier de config `rate`, l'ordre de priorité est :
|
||||
|
||||
1. `standard` — objet norme inline (poids + fractions de charge) ;
|
||||
2. `standard_file` — chemin vers un JSON de norme (résolu relativement au config) ;
|
||||
3. `standard_name` — identifiant intégré (`iplv`, `eseer`, `nplv`…) ;
|
||||
4. `metric` — enum historique.
|
||||
|
||||
Exemple avec une norme externe personnalisée :
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli rate --config crates/cli/examples/rate_chiller_custom_standard.json
|
||||
```
|
||||
|
||||
Quand une norme évolue, il suffit d'éditer/remplacer le fichier de poids — **aucune
|
||||
recompilation** n'est nécessaire.
|
||||
|
||||
**Étape 9.3 — Exporter le rapport**
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli rate --config <config> --output rapport_iplv.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Performances saisonnières (scop / seer — méthode par bins EN 14825)
|
||||
|
||||
Les commandes **scop** (chauffage saisonnier) et **seer** (refroidissement
|
||||
saisonnier) appliquent la **méthode par bins** d'EN 14825 : la machine est
|
||||
re-résolue à **chaque température extérieure** d'un climat, la demande du bâtiment
|
||||
est déduite d'une droite de charge, puis la saison est agrégée par un ratio
|
||||
d'énergie utile / énergie électrique.
|
||||
|
||||
**Étape 10.1 — Lancer un SCOP (pompe à chaleur air/eau)**
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli scop --config crates/cli/examples/scop_heatpump_r134a.json
|
||||
```
|
||||
|
||||
Résultat attendu (extrait) :
|
||||
|
||||
```text
|
||||
T[°C] h Demand[kW] Cap[kW] COP_full backup COP_eff
|
||||
-10.0 1 6.000 4.967 3.649 17% 2.506
|
||||
2.0 320 3.231 6.918 4.169 0% 3.613
|
||||
15.0 74 0.231 9.445 4.751 0% 3.592
|
||||
|
||||
Seasonal useful: 12393.7 kWh Seasonal electric: 3441.1 kWh
|
||||
SCOP = 3.602
|
||||
```
|
||||
|
||||
Le COP pleine charge augmente quand l'air extérieur se réchauffe (moins de lift) ;
|
||||
aux bins les plus froids, la capacité ne suffit plus à la demande et l'appoint
|
||||
électrique intervient (colonne `backup`).
|
||||
|
||||
**Étape 10.2 — Comprendre le fichier de config**
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"base_config": "heatpump_r134a_air_source.json", // cycle de base (chemin relatif)
|
||||
"climate_name": "en_14825_average", // climat intégré (bins EN 14825)
|
||||
"outdoor_side": "evaporator", // côté piloté par T extérieure
|
||||
"design_load_w": 6000.0, // demande [W] à design_outdoor_c
|
||||
"design_outdoor_c": -10.0, // T extérieure où demande = design_load_w
|
||||
"threshold_outdoor_c": 16.0, // T extérieure où la demande s'annule
|
||||
"cd": 0.25, // dégradation de cyclage (PLF = 1 - Cd*(1-CR))
|
||||
"backup_cop": 1.0 // COP de l'appoint électrique (chauffage)
|
||||
}
|
||||
```
|
||||
|
||||
La température du bin est appliquée au `secondary_inlet_temp_c` de l'échangeur
|
||||
extérieur ; le **débit** et le **cp** du secondaire (ex. air, cp 1006 J/kg·K)
|
||||
restent ceux du `base_config`.
|
||||
|
||||
**Étape 10.3 — Lancer un SEER (refroidissement)**
|
||||
|
||||
Le mode `seer` pilote par défaut le **condenseur** (côté extérieur) et exige un
|
||||
climat de refroidissement explicite (`climate_name`, `climate_file` ou `climate`) :
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli seer --config <mon_config_seer>.json --output seer.json
|
||||
```
|
||||
|
||||
**Étape 10.4 — Sortie JSON**
|
||||
|
||||
Ajoute `--output rapport.json`, ou utilise `--quiet` pour n'émettre que le JSON
|
||||
(pratique pour l'intégration outillée) :
|
||||
|
||||
```bash
|
||||
./target/release/entropyk-cli --quiet scop --config crates/cli/examples/scop_heatpump_r134a.json
|
||||
```
|
||||
|
||||
> **Note.** Comme le climat est une donnée (`BinClimateStandard`), ajouter une
|
||||
> nouvelle saison (climat SEER, révision future d'EN 14825…) revient à fournir un
|
||||
> tableau de bins — **sans changer le code**. Détails complets dans
|
||||
> `docs/rating-and-seasonal-metrics.md`.
|
||||
|
||||
148
docs/adr/ADR-0001-multi-circuit-heat-exchanger-architecture.md
Normal file
148
docs/adr/ADR-0001-multi-circuit-heat-exchanger-architecture.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# ADR-0001: Multi-Circuit Heat-Exchanger Architecture (DRAFT — Phase A)
|
||||
|
||||
**Status:** Proposed (draft produced during Phase A audit)
|
||||
**Date:** 2026-07-16
|
||||
**Decider:** Sepehr (architect review pending)
|
||||
**Supersedes:** none
|
||||
**Related:** `docs/audits/heat_exchanger_architecture_audit.md` (2026-07-16),
|
||||
`docs/audits/montluel_machine_coverage.md` (2026-07-16)
|
||||
|
||||
## Context
|
||||
|
||||
The Phase A audit established that Entropyk has **no shared-vessel / multi-circuit
|
||||
heat-exchanger primitive**:
|
||||
|
||||
- `FloodedEvaporator` is single-circuit (one inlet edge, one outlet edge;
|
||||
`flooded_evaporator.rs:57`).
|
||||
- The solver **rejects cross-circuit edges** (`TopologyError::CrossCircuitConnection`;
|
||||
`multi_circuit.rs:111`), so two refrigerant circuits cannot share a fluid volume.
|
||||
- The only inter-circuit heat mechanism is `ThermalCoupling` (`coupling.rs:52`), which
|
||||
transfers **duty via an external Q unknown**, not a shared volume.
|
||||
- `tests/multi_circuit.rs` validates only topology bookkeeping with zero-returning mocks —
|
||||
it does **not** test shared-vessel thermodynamics.
|
||||
|
||||
Carrier reference (mission §5.1) describes a shared-vessel flooded evaporator
|
||||
(`61XWHVZE`-class): one vessel, two independent refrigerant circuits separated by an
|
||||
intermediate tube sheet, one common water circuit, standard 2-pass water with 1-/3-pass
|
||||
options. This topology cannot be represented today. Product geometry was **not provided**;
|
||||
this ADR fixes the architecture, not the fixture data.
|
||||
|
||||
The audit also confirmed four reusable assets that constrain the design:
|
||||
`correlation_registry.rs` (evidence-aware registry), `bphx_geometry.rs` (typed geometry
|
||||
pattern), `bphx_correlation.rs` (formula↔registry binding), `two_phase_dp.rs` (analytic DP),
|
||||
and `entropyk_core::smoothing` (C¹/C² regularization with derivatives).
|
||||
|
||||
## Decision (proposed)
|
||||
|
||||
Adopt a **dedicated multi-circuit exchanger component** with a shared secondary path,
|
||||
built on the existing registry/geometry/correlation pattern, rather than overloading the
|
||||
single-circuit component or faking a shared vessel with `ThermalCoupling`.
|
||||
|
||||
### 1. New component: `MultiCircuitFloodedEvaporator`
|
||||
|
||||
```rust
|
||||
pub struct MultiCircuitFloodedEvaporator {
|
||||
geometry: ShellAndTubeGeometry, // new typed geometry (Phase B)
|
||||
circuits: Vec<FloodedCircuit>, // N >= 1
|
||||
secondary: SharedSecondaryPath,
|
||||
sizing: HeatExchangerSizing, // NominalUa | GeometryRated | CalibratedGeometry
|
||||
control: FloodedEvaporatorControl, // replaces target_quality=0.7 default
|
||||
calibration: MultiCircuitCalibration,
|
||||
}
|
||||
```
|
||||
|
||||
- Refrigerant mass/energy conserved **independently per circuit** (no A↔B mixing).
|
||||
- Common secondary energy balance = `Σ Q_circuit`.
|
||||
- An OFF circuit has zero refrigerant flow and zero duty but does **not** make the active
|
||||
circuit singular (zero-flow-safe residuals, mission §7).
|
||||
- Allocated heat-transfer area follows physical partitioning.
|
||||
|
||||
### 2. New domain types (Phase B prerequisites, in `entropyk-core`)
|
||||
|
||||
`Length`, `Mass`, `Area`, `TemperatureDifference`, `ThermalConductivity`, `Volume` (currently
|
||||
absent — audit §1.5). Plus `ShellAndTubeGeometry`, `RefrigerantPartition`,
|
||||
`TubeEnhancement`, `WaterBoxGeometry`, `FoulingResistances` (in `components`), validated per
|
||||
mission §6.1/§12.1.
|
||||
|
||||
### 3. Secondary layout is explicit and configurable
|
||||
|
||||
```rust
|
||||
pub enum SharedSecondaryLayout { CommonMixed, SequentialPartitions{..}, ParallelPartitions{..} }
|
||||
```
|
||||
|
||||
The water path through refrigerant partitions is **not** chosen for Carrier equipment
|
||||
without evidence (audit; mission §6.4). Default to configurable; flag required product input.
|
||||
|
||||
### 4. Control semantics corrected (mission §3)
|
||||
|
||||
Replace the `target_quality = 0.7` default with an explicit enum:
|
||||
`FloodedEvaporatorControl::{FixedLevel, Pinch, OutletSuperheat, FixedRefrigerantCharge,
|
||||
External}`. The outlet port's physical meaning (suction vs. recirculation vs. separator
|
||||
inlet) is documented per variant. A legacy quality-control mode is retained only behind a
|
||||
documented migration flag (audit §3.1).
|
||||
|
||||
### 5. Zero-flow handling via smooth blending (mission §7.2)
|
||||
|
||||
Build on `entropyk_core::smoothing` (`smooth_abs`, `cubic_blend`, `smooth_max`). No hard
|
||||
`if |m| < ε { Q = 0 }` branches. Analytic derivatives verified at `m = 0`, near-zero, and
|
||||
normal flow. Pressure drop uses a regularized odd function of flow.
|
||||
|
||||
### 6. Jacobian
|
||||
|
||||
Analytic throughout, including the zero-flow blending and `dT_sat/dP`. Because the backend
|
||||
exposes no `dT_sat/dP` (audit §7), add a single documented central-FD backend method
|
||||
`saturation_temperature_derivative_dp` (cacheable) as the single source of truth, with an
|
||||
optional CoolProp analytic-derivative upgrade later.
|
||||
|
||||
### 7. Correlations via the existing registry
|
||||
|
||||
Extend `correlation_registry` + a new `shell_tube_correlation` module (pool boiling
|
||||
Cooper/Gorenflo baseline; tube-bundle condensation; Gnielinski water-side; Darcy-Weisbach +
|
||||
water-box minor losses). **Do not** apply internal-flow boiling correlations to shell-side
|
||||
flooded boiling without documented justification (mission §6.2).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **A. Overload single-circuit `FloodedEvaporator` with two refrigerant port pairs.**
|
||||
Rejected: breaks the edge model (solver rejects cross-circuit edges), conflates suction
|
||||
outlet with shared-volume representation, and cannot guarantee independent A/B mass
|
||||
conservation.
|
||||
- **B. Compose two `FloodedEvaporator`s + a `ThermalCoupling`.** Rejected as the *primary*
|
||||
model: `ThermalCoupling` transfers duty, not a shared water inventory; the common water
|
||||
energy balance and shared geometry/partitioning would be lost. This composition remains
|
||||
valid for **dual-module** machines (mission §5.2, separate modules) — handled in Phase E.
|
||||
- **C. Defer to a generic N-port exchanger.** Rejected: the shared-vessel physics (common
|
||||
secondary, partitioned area, flooded boiling) is specific enough to deserve a typed
|
||||
component; a generic N-port wrapper would re-introduce the current ambiguities.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** unblocks Montluel archetypes M2, M4-dual, M12; enables circuit-specific
|
||||
calibration and one-circuit-off operation; aligns flooded exchangers with the
|
||||
registry/geometry pattern already proven for BPHX.
|
||||
- **Negative:** new public API surface (migration path required — `from_nominal_ua` helper);
|
||||
larger equation count per component (residual scaling needed — mission §9.5); requires the
|
||||
Phase B geometry/correlation foundations first.
|
||||
- **Risk:** zero-flow blending Jacobian correctness — mitigated by Jacobian-vs-FD parity
|
||||
tests across all modes (mission §9.4) as a hard gate.
|
||||
|
||||
## Open questions (block finalization)
|
||||
|
||||
1. Carrier water-path layout through refrigerant partitions (drives `SharedSecondaryLayout`
|
||||
default) — **data not provided**.
|
||||
2. Whether a legacy `target_quality` control mode has any real supported use case (mission
|
||||
§3.6) — needs Sepehr's confirmation before deprecation.
|
||||
3. CoolProp analytic-derivative wiring priority vs. central-FD fallback sufficiency.
|
||||
|
||||
## Phasing
|
||||
|
||||
- Phase B: domain types + geometry + correlation extension + `dT_sat/dP` method +
|
||||
zero-flow regularization utility.
|
||||
- Phase C: single-circuit `FloodedEvaporator` rewrite (rating vs coupled, geometry UA,
|
||||
control semantics, zero-flow-safe analytic Jacobian, migration).
|
||||
- Phase D: `MultiCircuitFloodedEvaporator` + shared secondary + one-circuit-off.
|
||||
- Phase E: dual-module composition (alternative B, for separate modules) + dual-circuit BPHX.
|
||||
- Phase F: CLI/bindings/examples/fixtures + validation.
|
||||
|
||||
This ADR is **draft** until architect review and until the Phase A blockers (B1–B5 in the
|
||||
audit) are resolved enough to validate the implementation.
|
||||
267
docs/architecture/dof_balance_and_real_machine_plan.md
Normal file
267
docs/architecture/dof_balance_and_real_machine_plan.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# DoF Balance & Real-Machine Simulation Plan
|
||||
|
||||
**Date:** 2026-07-17
|
||||
**Status:** Active implementation plan
|
||||
**Audience:** architects + implementers
|
||||
**Related:** `docs/audits/heat_exchanger_architecture_audit.md`, ADR-0001, mission HX
|
||||
|
||||
---
|
||||
|
||||
## 0. Non-negotiable principles
|
||||
|
||||
1. **A thermodynamic system is a square algebraic problem:**
|
||||
\[
|
||||
n_{\text{equations}} = n_{\text{unknowns}}
|
||||
\]
|
||||
2. **Fixing a quantity consumes one degree of freedom.** Somewhere else an unknown
|
||||
must be freed (or an equation dropped). There is no free lunch.
|
||||
3. **Parameters are not states.** If a value is not in the Newton state vector, it is
|
||||
either (a) a true physical constant/input boundary, or (b) a **cheat** that must be
|
||||
labeled and forbidden in system mode.
|
||||
4. **No bricolage.** Silent clamping, fake residuals, scalar secondary streams that
|
||||
pretend to be a water loop, and design-point pressure pins are not “real machine”
|
||||
simulation.
|
||||
5. **Hard gate before solve.** Imbalance is an error, not a warning.
|
||||
|
||||
---
|
||||
|
||||
## 1. Diagnosis — what the codebase does today
|
||||
|
||||
### 1.1 State vector (unknowns)
|
||||
|
||||
Post CM1.4 layout (`System::finalize`):
|
||||
|
||||
```text
|
||||
[ ṁ_branch_0 … ṁ_branch_{B-1} | P_e0, h_e0, … P_e{|E|-1}, h_e{|E|-1}
|
||||
| inverse_controls | coupling_Q | saturated_(u,x) pairs | free_actuators ]
|
||||
```
|
||||
|
||||
- Series edges share one `ṁ` unknown per branch (`topology.rs`).
|
||||
- Full length: `full_state_vector_len()`.
|
||||
|
||||
### 1.2 Equations
|
||||
|
||||
```text
|
||||
n_eq = Σ component.n_equations()
|
||||
+ n_constraints
|
||||
+ n_thermal_couplings
|
||||
+ 2 × n_saturated_controllers
|
||||
```
|
||||
|
||||
Free actuators contribute **unknowns only**; the owning component supplies the
|
||||
closing residual inside `n_equations()`.
|
||||
|
||||
### 1.3 Existing Fix/Free mechanisms (good)
|
||||
|
||||
| Mechanism | Fix (equation) | Free (unknown) |
|
||||
|-----------|----------------|----------------|
|
||||
| `BrineSource` P,h,(ṁ) | Dirichlet on edge | — (boundary input) |
|
||||
| `BrineSink` P_back | Dirichlet on P | h free (emergent outlet) |
|
||||
| `Anchor` + constraint | +1 thermo spec | must free elsewhere |
|
||||
| `add_constraint` + `BoundedVariable` | +1 measure residual | +1 control |
|
||||
| `add_free_actuator` | component residual | +1 state slot |
|
||||
| `superheat_regulated` | **drops** SH residual | EXV opening closes it |
|
||||
| Emergent HX | outlet closure pins P via balance | design-point P removed from compressor/EXV |
|
||||
|
||||
### 1.4 Cheating patterns (forbidden for real-machine system solve)
|
||||
|
||||
| Cheat | Where | Why it is wrong |
|
||||
|-------|-------|-----------------|
|
||||
| Secondary as scalars `T_sec`, `C_sec` without edges | `FloodedEvaporator` coupled path | Water loop has no First-Law residual; h_sec,out never emerges |
|
||||
| `target_quality = 0.7` + quality residual | Flooded default | +1 equation without free actuator / level unknown |
|
||||
| Fake residual `-target_quality` when quality fails | Flooded | Hides config error |
|
||||
| `unwrap_or(inlet_p_idx)` for mass index | Cond/Evap/Flooded | Jacobian writes energy ∂/∂ṁ into **pressure column** |
|
||||
| Design-point P pins (non-emergent) | compressor/EXV legacy | Pressures not free to respond to secondary |
|
||||
| `p_in > 10_000` hard model switch | several HX | Discontinuous Jacobian near seed |
|
||||
| UI historically “hiding” secondary edges | obsolete HANDOFF claim | Must keep live edges in graph |
|
||||
| Rating `rate(p)` used as if system solve | qualification APIs | OK **only** when labeled rating mode |
|
||||
|
||||
### 1.5 Honest water-cooled chiller budget (reference)
|
||||
|
||||
```text
|
||||
Ref loop: Comp ⇄ Cond ⇄ EXV ⇄ Evap → 1 ṁ + 4×(P,h) = 9
|
||||
CW loop: BrineSrc → Cond.sec → Sink → 1 ṁ + 2×(P,h) = 5
|
||||
CHW loop: BrineSrc → Evap.sec → Sink → 1 ṁ + 2×(P,h) = 5
|
||||
Total unknowns = 19
|
||||
|
||||
Eqs (emergent, same-branch):
|
||||
Comp 2 + Cond (3 thermo + 1 sec energy) 4 + EXV 1
|
||||
+ Evap (3 + 1 sec) 4 + 2×(BrineSrc 3 + BrineSink 1) = 19
|
||||
```
|
||||
|
||||
**19 = 19.** This is the template. Flooded + dual circuit must keep the same discipline.
|
||||
|
||||
---
|
||||
|
||||
## 2. Fix / Free ledger (the core abstraction)
|
||||
|
||||
### 2.1 Rules
|
||||
|
||||
```text
|
||||
USER FIX (non-boundary) ⇒ must FREE something OR DROP a closing residual
|
||||
BOUNDARY FIX (source) ⇒ normal: physical input of the machine
|
||||
OUTLET CLOSURE (SH/SC/x) ⇒ either free actuator elsewhere OR mode drop of that residual
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
| User intent | Fix | Free / drop |
|
||||
|-------------|-----|-------------|
|
||||
| Fixed chilled-water supply T | load-side T_set or capacity constraint | free compressor speed or free EXV / free ṁ_ref |
|
||||
| Fixed superheat 5 K | SH residual OR anchor | free EXV opening (`superheat_regulated` + orifice) |
|
||||
| Fixed flooded level | level residual | free EXV / charge related |
|
||||
| Fixed circuit capacity | capacity constraint | free speed or free slide valve |
|
||||
| Quality target without actuator | **ILLEGAL** | refuse at validate |
|
||||
|
||||
### 2.2 Equation roles (semantic)
|
||||
|
||||
```rust
|
||||
pub enum EquationRole {
|
||||
MassConservation { stream: StreamId },
|
||||
MomentumOrPressureDrop { stream: StreamId },
|
||||
EnergyBalance { stream: StreamId },
|
||||
OutletClosure { kind: OutletClosureKind }, // SH | SC | Quality | Level
|
||||
BoundaryDirichlet { quantity: StateQuantity }, // P | H | M
|
||||
ActuatorClosure { name: String },
|
||||
CouplingDuty,
|
||||
ControlTracking { name: String },
|
||||
Unspecified, // legacy only; must disappear over time
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Unknown kinds
|
||||
|
||||
```rust
|
||||
pub enum UnknownKind {
|
||||
BranchMassFlow { branch_id: usize },
|
||||
EdgePressure { edge: usize },
|
||||
EdgeEnthalpy { edge: usize },
|
||||
InverseControl { id: String },
|
||||
CouplingHeat { index: usize },
|
||||
SaturatedActuator { index: usize },
|
||||
SaturatedIntegrator { index: usize },
|
||||
FreeActuator { id: String },
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 System-level report
|
||||
|
||||
```text
|
||||
DofReport {
|
||||
n_unknowns, n_equations, balance: Balanced | Over(k) | Under(k),
|
||||
per_component: [(name, n_eq, roles)],
|
||||
free_actuators, constraints, couplings,
|
||||
diagnostics: ["FloodedEvaporator quality_control +1 eq without free actuator", ...]
|
||||
}
|
||||
```
|
||||
|
||||
Hard fail when `balance != Balanced` before Newton.
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation phases (ordered, each leaves workspace green)
|
||||
|
||||
### Phase D0 — Ledger + hard gate ✅ done (2026-07-17)
|
||||
|
||||
- [x] Study + this plan document
|
||||
- [x] `crates/solver/src/dof.rs` — `DofReport`, `SystemDofBalance`, errors
|
||||
- [x] `crates/components/src/dof.rs` — `EquationRole` + `Component::equation_roles()`
|
||||
- [x] `System::dof_report()` / `System::validate_system_dof()` / `total_equation_count()`
|
||||
- [x] Hard fail in `finalize()` on **over-constrained** (`enforce_dof_gate`, default on)
|
||||
- [x] Full square check via `validate_system_dof()` (production path)
|
||||
- [x] Roles on FloodedEvaporator, Condenser, BrineSource/Sink, Anchor
|
||||
- [x] Unit tests: `crates/solver/tests/dof_balance.rs` (balanced / over / under)
|
||||
|
||||
### Phase D1 — FloodedEvaporator honesty ✅ core done (2026-07-17)
|
||||
|
||||
- [x] 4-port live secondary via `set_port_context` (ports 2/3), `flow_paths=[(0,1),(2,3)]`
|
||||
- [x] Secondary energy residual `ṁ_sec(h_out−h_in) + Q = 0`
|
||||
- [x] `coupled_ready` requires live secondary edges in **system mode**
|
||||
- [x] Scalar `with_secondary_stream` documented as **rating-mode only**
|
||||
- [x] Remove mass→pressure index fallback; error if ṁ index missing
|
||||
- [x] Quality residual: return `Err` if quality uncomputable (no fake residual)
|
||||
- [x] `with_quality_control` documents DoF cost
|
||||
- [x] Equation roles complete for flooded
|
||||
- [ ] CLI example: flooded + BrineSrc/Sink secondary loop
|
||||
- [ ] Replace quality target with `FloodedEvaporatorControl` enum
|
||||
|
||||
### Phase D2 — Shared DoF diagnostics in CLI + UI ✅ done (2026-07-17)
|
||||
|
||||
- [x] CLI `run` hard-fails on non-square `validate_system_dof` after finalize
|
||||
- [x] `SimulationResult.dof` summary (n_eq, n_unk, balance, ledger text)
|
||||
- [x] CLI `validate` builds system and reports DoF
|
||||
- [x] Example `chiller_flooded_4port_watercooled.json`
|
||||
- [x] Web UI status bar: live eqs vs unk estimate
|
||||
- [x] Web UI Fix/Free badges on parameters + secondary wiring warnings
|
||||
- [x] `validateConfig` requires live secondary ports for 4-port HX
|
||||
|
||||
### Phase D3 — Zero-flow safe residuals (linked to HX mission §7)
|
||||
|
||||
- [ ] Reuse `entropyk_core::smoothing`
|
||||
- [ ] Activity blend with documented scales; Jacobian parity tests at m=0
|
||||
|
||||
### Phase D4 — Multi-circuit shared vessel (ADR-0001)
|
||||
|
||||
- [ ] Solver contract for multi-circuit ports on one component
|
||||
- [ ] `MultiCircuitFloodedEvaporator` with N independent ref balances + shared secondary energy
|
||||
- [ ] OFF circuit: zero duty, fixed eq count, no singularity
|
||||
|
||||
### Phase D5 — Geometry / correlations / product fixtures
|
||||
|
||||
- [ ] As HX mission Phases B–F, always under DoF ledger gate
|
||||
- [ ] No geometry mode ships without equation/unknown inventory table
|
||||
|
||||
---
|
||||
|
||||
## 4. Component checklist (every new residual)
|
||||
|
||||
Before adding a residual equation:
|
||||
|
||||
1. Name its `EquationRole`.
|
||||
2. State which unknown it closes (or which free it requires).
|
||||
3. Update `n_equations()` and Jacobian.
|
||||
4. Add a DoF unit test (balance before/after).
|
||||
5. Never add a residual “to make it converge”.
|
||||
|
||||
Before adding an unknown (actuator, coupling Q, …):
|
||||
|
||||
1. Name its `UnknownKind`.
|
||||
2. Provide the closing residual (component or system).
|
||||
3. Bounds if physical actuator.
|
||||
|
||||
---
|
||||
|
||||
## 5. Acceptance criteria (robust machine simulation)
|
||||
|
||||
A configuration is accepted as **real-machine system mode** only if:
|
||||
|
||||
- [ ] `validate_system_dof()` passes (hard)
|
||||
- [ ] Secondary fluids of every HX are live edges (or explicitly marked rating-only)
|
||||
- [ ] Pressures of interest are emergent (no design-point pin unless documented fixed-P study)
|
||||
- [ ] First Law closes at cycle level (`energy_transfers` sum ≈ 0 within tolerance)
|
||||
- [ ] No fake residual on missing properties
|
||||
- [ ] Jacobian analytic except documented property derivatives
|
||||
- [ ] Staging / zero flow keeps constant equation count
|
||||
|
||||
---
|
||||
|
||||
## 6. Relation to inverse control
|
||||
|
||||
Inverse (Eurovent / calibration) already has a partial DoF check. It is incomplete
|
||||
(misses free actuators, couplings, saturated controllers). The new
|
||||
`validate_system_dof` supersedes it as the **single** square-system gate; inverse
|
||||
mapping remains the Fix/Free pairing API for user constraints.
|
||||
|
||||
---
|
||||
|
||||
## 7. Immediate deliverables (this implementation slice)
|
||||
|
||||
1. This plan document.
|
||||
2. `solver::dof` module + public API.
|
||||
3. Hard DoF validation wired to `System`.
|
||||
4. Flooded evaporator path toward live secondary (D1 start).
|
||||
5. Tests proving overconstrained quality control is detected.
|
||||
|
||||
---
|
||||
|
||||
**Last updated:** 2026-07-17
|
||||
387
docs/audits/heat_exchanger_architecture_audit.md
Normal file
387
docs/audits/heat_exchanger_architecture_audit.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# Heat-Exchanger Architecture Audit (Phase A)
|
||||
|
||||
**Date:** 2026-07-16
|
||||
**Scope:** Full Entropyk workspace, focused on the heat-exchanger family and the
|
||||
`FloodedEvaporator` upgrade target.
|
||||
**Method:** Code reading + live reproduction of `cargo fmt/check/clippy/test` on
|
||||
`C:\Users\serameza\impact\dev\Entropyk-main\entropyk`. All claims cite `file:line`.
|
||||
No code was modified during this audit.
|
||||
|
||||
Legend for evidence: **[code]** = confirmed by repository source; **[build]** = confirmed
|
||||
by running a command this session; **[carrier]** = confirmed by authorized Carrier source
|
||||
(none used in this audit — no Carrier data was provided); **[assumption]** = engineering
|
||||
assumption requiring validation.
|
||||
|
||||
---
|
||||
|
||||
## 1. Workspace and architecture inventory [code]
|
||||
|
||||
### 1.1 Workspace members (`Cargo.toml`)
|
||||
|
||||
12 workspace members: `crates/{core, components, entropyk, fluids, solver, cli, vendors}`,
|
||||
`demo`, `bindings/{python, c, wasm}`, `tests/fluids`. Version `0.1.0`, edition 2021,
|
||||
workspace deps `thiserror`, `serde`, `serde_json`.
|
||||
|
||||
### 1.2 Crate roles and dependencies (relevant subset)
|
||||
|
||||
| Crate | Role | CoolProp coupling |
|
||||
|-------|------|-------------------|
|
||||
| `entropyk-core` | Physical types, smoothing, calibration. **Does NOT own the `Component` trait** (see 1.4). | none |
|
||||
| `entropyk-fluids` | `FluidBackend` trait + backends (CoolProp, tabular, incompressible, damped, cached, dll, test). | optional features `coolprop`, `dll`; `default = []` |
|
||||
| `entropyk-components` | `Component` trait + all thermodynamic components + HX family. | depends on `entropyk-fluids` (default, no coolprop) |
|
||||
| `entropyk-solver` | `System` (petgraph topology, stride-3 state `[m,P,h]` per edge), Newton/Picard/Fallback, `ThermalCoupling`. | `coolprop` feature (default off) |
|
||||
| `entropyk` (facade) | Re-export library. | **forces `entropyk-fluids/coolprop` unconditionally** (`crates/entropyk/Cargo.toml:16`) |
|
||||
| `entropyk-cli` | CLI factory `create_component`, config IR v1/v2, subsystem templates. | **forces `entropyk-fluids/coolprop` unconditionally** (`crates/cli/Cargo.toml:23`) |
|
||||
| `entropyk-python` | PyO3 bindings. | optional; **broken on Python 3.14** (see §4.4-B1) |
|
||||
| `entropyk-vendors` | Compressor data (Bitzer/Copeland/Danfoss) + SWEP BPHX data. | none |
|
||||
|
||||
### 1.3 State vector and topology [code]
|
||||
|
||||
- `System` holds `petgraph::Graph<Box<dyn Component>, FlowEdge, Directed>`
|
||||
(`crates/solver/src/system.rs:198`); nodes = components, edges = flow connections.
|
||||
- State vector is **stride-3**: `[m₀,P₀,h₀, m₁,P₁,h₁, …]` per edge
|
||||
(`system.rs:192`). `FlowEdge` carries `state_index_{m,p,h}`, `source_port`,
|
||||
`target_port`, `mass_flow_branch_id` (`system.rs:134`).
|
||||
- Series branches share one `state_index_m` via `presolve_mass_flow_topology`
|
||||
using `Component::flow_paths()` (default empty).
|
||||
- `MAX_CIRCUIT_ID = 4` → **at most 5 refrigerant circuits (IDs 0–4)** (`system.rs:30`).
|
||||
- Cross-circuit **edges are hard-rejected** (`TopologyError::CrossCircuitConnection`)
|
||||
— verified by test (`crates/solver/tests/multi_circuit.rs:111`).
|
||||
|
||||
### 1.4 `Component` trait — lives in `components`, not `core` [code]
|
||||
|
||||
Defined at `crates/components/src/lib.rs:478`. Required methods:
|
||||
|
||||
```rust
|
||||
fn compute_residuals(&self, state: &StateSlice, residuals: &mut ResidualVector) -> Result<(), ComponentError>;
|
||||
fn jacobian_entries(&self, state: &StateSlice, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError>;
|
||||
fn n_equations(&self) -> usize;
|
||||
fn get_ports(&self) -> &[ConnectedPort];
|
||||
```
|
||||
|
||||
Supporting types: `type StateSlice = [f64];` (`lib.rs:221`),
|
||||
`type ResidualVector = Vec<f64>;` (`lib.rs:227`),
|
||||
`JacobianBuilder` = **flat COO triplet accumulator** `add_entry(row,col,value)`
|
||||
(`lib.rs:235,271`) — not a blocked/sparse matrix. Relevant provided methods:
|
||||
`flow_paths()`, `set_port_context(&mut self, &[(m,p,h)])`, `set_system_context`,
|
||||
`energy_transfers`, `measure_output`, `signature`.
|
||||
|
||||
### 1.5 Strong physical types available [code]
|
||||
|
||||
In `crates/core/src/types.rs` (re-exported `lib.rs:61`): `Pressure`, `Temperature`,
|
||||
`Enthalpy` (**specific, J/kg — no separate `SpecificEnthalpy`**), `MassFlow`
|
||||
(with `regularized()` clamp at `1e-12`, `types.rs:334,382`), `Power`, `Concentration`,
|
||||
`VolumeFlow`, `RelativeHumidity`, `VaporQuality`, `Entropy`, `ThermalConductance` (W/K),
|
||||
`CircuitId(pub u16)` (hashable).
|
||||
|
||||
**Missing types needed by the mission (§6.1):** `Length`, `Mass`, `Area`,
|
||||
`TemperatureDifference`, `ThermalConductivity`, `Volume`. Geometry is currently raw `f64`
|
||||
inside BPHX/FinCoil components. (`TemperatureDelta` exists only in
|
||||
`crates/fluids/src/types.rs:13`, not in core.)
|
||||
|
||||
### 1.6 Smoothing / regularization toolkit already present [code]
|
||||
|
||||
`crates/core/src/smoothing.rs`: `smoothstep`/deriv (C¹), `smootherstep` (C²),
|
||||
`cubic_blend`/`quintic_blend`, `smooth_max`/`smooth_min` (C^∞), `smooth_abs`/deriv,
|
||||
`smooth_clamp`. **All carry verified analytic derivatives.** This is the foundation the
|
||||
zero-flow regularization (mission §7.2) should reuse, not reimplement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Heat-exchanger inventory [code]
|
||||
|
||||
`crates/components/src/heat_exchanger/` — 23 files. Grouped below; line counts in
|
||||
parentheses. Detailed per-file fields (purpose/API/ports/equations/jacobian/geometry/
|
||||
zero-flow/multi-circuit/dp/tests/defects) were captured and are summarized; the most
|
||||
decision-relevant items are inlined.
|
||||
|
||||
### 2.1 Framework / strategy layer
|
||||
|
||||
| File (lines) | Role | Key facts |
|
||||
|--------------|------|-----------|
|
||||
| `model.rs` (206) | Object-safe `HeatTransferModel` trait + `FluidState`. | `n_equations()` per impl; UA is a single `f64`. Jacobian is the wrapper's job. |
|
||||
| `eps_ntu.rs` (349) | ε-NTU model. | **`ExchangerType::ShellAndTube{passes}`: `passes` is bound to `_` and unused** (`eps_ntu.rs:168-174`). The closed form used is actually the *cross-flow both-unmixed* formula, mislabelled. Zero-capacity guard: `c_min<1e-10 → Q=0` (`:204`). |
|
||||
| `lmtd.rs` (403) | LMTD model. | `ShellAndTube1_2` correction factor hard-coded `0.9` (`:53`). |
|
||||
| `exchanger.rs` (1431) | Generic 4-port `HeatExchanger<M>`. | **Jacobian is finite-difference** by default (`:816-865`), re-evaluates backend property calls per column. Doc says 3 eqns, returns 2 (`:73-79` vs `:867`). `flow_paths=[(0,1),(2,3)]` only. |
|
||||
| `mod.rs` (108) | Barrel re-export. | — |
|
||||
|
||||
### 2.2 Refrigerant-side UA/ε-NTU components
|
||||
|
||||
| File (lines) | Geometry | Jacobian | DP | Zero-flow | Multi-circuit | Notable defects |
|
||||
|--------------|----------|----------|----|-----------|---------------|-----------------|
|
||||
| `condenser.rs` (2169) | UA only | analytic (+ FD for `dT_cond/dP`, `:1265`) | optional lumped `quadratic_drop` | `c_sec<=1e-10→ε=0` | same-branch only | `p_in>10_000` switch (×4); **`unwrap_or(inlet_p_idx)` mass→pressure column fallback** (`:1057,1231`); `assert!` in `with_flooded_head_pressure` (`:572`) |
|
||||
| `evaporator.rs` (1897) | UA only | analytic (+ FD `dT_evap/dP`) | optional `quadratic_drop` | `c_sec` guard | same-branch only | same `p_in` switch + `unwrap_or(inlet_p_idx)` (`:851,1030`); heavy duplication with condenser (`SecondaryJacCtx`) |
|
||||
| `flooded_evaporator.rs` (939) | **UA only** | analytic + **FD `dT_sat/dP`** (`:568-572`) | **none** | `c_sec<=1e-10→ε=0` | **none** | see §3 (target component) |
|
||||
| `flooded_condenser.rs` (704) | UA only | delegates to inner (FD) | none | inner | none | **`n_equations=3` but only 2 non-trivial residuals** (`:247-266`); **interior mutability via `&self`** (`Cell`, `:45-46,269,280,283`); no `set_system_context` override; ~20 undocumented public items |
|
||||
|
||||
### 2.3 Air-side / coil components
|
||||
|
||||
| File (lines) | Geometry | Notes |
|
||||
|--------------|----------|-------|
|
||||
| `condenser_coil.rs` (278) | inherits | Thin wrapper enforcing Air cold side; `name()` hard-coded. |
|
||||
| `evaporator_coil.rs` (292) | inherits | Mirror of above (Air hot side). |
|
||||
| `air_cooled_condenser.rs` (302) | approach-based | `T_cond = OAT + approach` (`:78`); UA unused in solver. |
|
||||
| `fin_coil_condenser.rs` (886) | **`CoilGeometry` (11 fields)** | **Richest geometry model** — Chang & Wang louvered-fin, fin efficiency, UA from geometry (`:517-597`); UA clamped `[1e2,1e6]` (`:596`); refrigerant resistance lumped `×0.92`. |
|
||||
| `mchx_condenser_coil.rs` (498) | variable UA | `UA_eff=UA_nom·(ρ/ρ_ref)·fan_speed_ratio^n` (`:234`); `coil_index` metadata only (no bank partition). |
|
||||
|
||||
### 2.4 Brazed-plate (BPHX) family — **the pattern to reuse**
|
||||
|
||||
| File (lines) | Role |
|
||||
|--------------|------|
|
||||
| `bphx_geometry.rs` (504) | **Typed geometry**: `BphxGeometry` (plates, dimensions, chevron, dh, area), `mass_flux(m)`, `n_channels=n_plates−1`. |
|
||||
| `correlation_registry.rs` (1290) | **Evidence-aware registry** (no formulas): `CorrelationId`, `CorrelationMetadata{domain,evidence}`, `SelectionContext`, `assess_candidate`→`CandidateAssessment{InDomain/NearBoundary/Extrapolated}`, `select_correlation`. Every correlation is `EvidenceIncomplete` (enforced by test `:1267`). |
|
||||
| `bphx_correlation.rs` (1597) | Formula enum mirroring registry ids; `HeatTransferCorrelation` trait; `CorrelationSelector::select_and_compute` checked evaluation; Gnielinski with C¹ laminar→turbulent blend using `entropyk_core::smoothing`. |
|
||||
| `bphx_exchanger.rs` (846) | UA/DP from geometry+correlation; **interior mutability via `&self`** (`Cell` fields `:61-64,323-328`); DP helper **not wired into residual**. |
|
||||
| `bphx_evaporator.rs` (865) / `bphx_condenser.rs` (912) | DX variants; read outlet in `compute_residuals(&self)` writing `Cell` state; superheat/subcooling **not enforced as residual equations**. |
|
||||
|
||||
### 2.5 Other
|
||||
|
||||
| File (lines) | Role |
|
||||
|--------------|------|
|
||||
| `moving_boundary_hx.rs` (851) | Zone discretization (SH/TP/SC). **Per-zone HTC hard-coded** (5000/500/1500, `:436-441`); `correlation_selector` stored but **unused** (`:97`); **Jacobian delegates to inner ignoring the UA scale it just computed** (`:219-225`) — real correctness gap. |
|
||||
| `two_phase_dp.rs` (380) | Friedel DP + lumped `quadratic_drop` (antisymmetric C¹). **Fully analytic, well-tested.** Rich Friedel model **never wired into any residual** — only `quadratic_drop` is. |
|
||||
| `economizer.rs` (280) | Suction/liquid IHX, delegates to `HeatExchanger<LmtdModel>`, ON/OFF/BYPASS. |
|
||||
|
||||
---
|
||||
|
||||
## 3. `FloodedEvaporator` — current-state verification (every claim) [code]
|
||||
|
||||
Target file: `crates/components/src/heat_exchanger/flooded_evaporator.rs`.
|
||||
|
||||
| Mission claim (§2) | Verdict | Evidence |
|
||||
|---|---|---|
|
||||
| Wraps `HeatExchanger<EpsNtuModel>` | ✅ | inner field; UA from `EpsNtuModel` fixed at `new(ua)` |
|
||||
| Nominal constant UA | ✅ | `self.ua()` returns fixed value (`:106,168-170,275`) |
|
||||
| `Q = ε·C_sec·(T_sec,in − T_sat)` | ✅ | `coupled_duty` (`:285-291`); `T_evap` from `evap_temperature(P)` at quality 0.5 (`:267`) |
|
||||
| `m_ref·(h_out−h_in) − Q = 0` | ✅ | `residuals[1]` (`:488`) |
|
||||
| No refrigerant pressure drop | ✅ | `residuals[0] = P_out − P_in` (`:486`); no `pressure_drop_coeff` field |
|
||||
| Optional outlet quality target | ✅ | `quality_control_enabled` adds `r2 = q_actual − q_target` (`:445-449,490-496`) |
|
||||
| Secondary stored as boundary values, not live ports | ✅ | `secondary_inlet_temp_k`, `secondary_capacity_rate` are `Option<f64>` (`:77-78`); never edges |
|
||||
| Zero secondary capacity → zero effectiveness | ✅ | `effectiveness()` returns 0 if `c_sec<=1e-10 \|\| ua<=0` (`:276-280`) |
|
||||
| Finite differences for `dT_sat/dP` | ✅ | central FD `dp=p_in·1e-4+100` (`:568-572`) |
|
||||
| No shell-and-tube geometry / tube count / passes / circuit partition | ✅ | struct carries only UA, fluid ids, indices, secondary scalars (`:57-79`) |
|
||||
|
||||
### 3.1 Defects in the current `FloodedEvaporator`
|
||||
|
||||
1. **`target_quality = 0.7` hard-coded default** (`:112`, doc `:148`) — the smell the mission §3 flags.
|
||||
2. **Quality clamped to `[0,1]`** in `compute_quality` (`:375`) and setters (`:150,189`) — hides subcooled/superheated extrapolation from the solver.
|
||||
3. **`p_in > 10_000.0` magic switch** (`:471,541`) — duplicated across condenser/evaporator/flooded.
|
||||
4. **`unwrap_or(inlet_p_idx)`** mass-flow → pressure column fallback (`:478,548`) — if no mass index resolves, the energy residual is written against the *pressure* column (mission §16.3).
|
||||
5. **Quality-control silent degradation**: `residuals[2] = -target_quality` when backend cannot compute quality (`:494,520,526`) — hides config error.
|
||||
6. **`last_heat_transfer_w`/`last_outlet_quality` are dead state** — declared but never written on the coupled path; `heat_transfer()` always returns the initial `0.0`.
|
||||
7. **`assert!(ua >= MIN_UA)` in `new`** (`:105`) — zero-panic policy violation (FloodedCondenser offers `try_new`; this one does not).
|
||||
|
||||
---
|
||||
|
||||
## 4. Current-state verification (reproduction) [build]
|
||||
|
||||
Environment: Rust 1.95.0, cargo 1.95.0, Windows MSVC (VS 2022 BuildTools), CMake present,
|
||||
**Python 3.14** on PATH. Commands run in the working repo on 2026-07-16.
|
||||
|
||||
### 4.1 Commands and exact results
|
||||
|
||||
| Command | Result | Exit |
|
||||
|---------|--------|------|
|
||||
| `cargo fmt --all -- --check` | **PASS** — no diffs | 0 |
|
||||
| `cargo check --workspace --all-targets --all-features` | **FAIL** | 101 |
|
||||
| `cargo check --workspace --all-targets --exclude entropyk-python` (default feats) | **FAIL** | 101 |
|
||||
| `cargo clippy --workspace --all-targets --all-features -- -D warnings` | **NOT RUN to completion** — clippy was not installed (installed during audit); after install, fails on fluids clippy errors + coolprop/pyo3 blockers | — |
|
||||
| `cargo test --workspace --all-features` | **NOT RUN** — blocked by build failures above | — |
|
||||
| `cargo test --doc --workspace` | **NOT RUN** — blocked by build failures above | — |
|
||||
|
||||
**Per-crate reproducible baseline (crates that do not force CoolProp):**
|
||||
|
||||
| Crate | `cargo test -p <crate>` result |
|
||||
|-------|--------------------------------|
|
||||
| `entropyk-core` | **PASS** 160 + 38 = 198 tests |
|
||||
| `entropyk-fluids` (default, no coolprop) | **PASS** 102 + 4 = 106 tests |
|
||||
| `entropyk-components` | **PASS** 856 + 70 = 926 tests (18 ignored) |
|
||||
| `entropyk-solver` | **FAIL** — `convergence_criteria`: `test_newton_with_criteria_single_circuit` panics at `.expect("Solver should converge")` (`tests/convergence_criteria.rs:292`); other suites pass |
|
||||
| `entropyk-vendors` | **PASS** 66 + 4 = 70 tests |
|
||||
|
||||
### 4.2 Root causes of the workspace-wide failures (all pre-existing)
|
||||
|
||||
**B1 — Python bindings (env).** `entropyk-python` pins `pyo3 = "0.23"` (`bindings/python/Cargo.toml:24`), which resolves to 0.23.5 (max Python 3.13). The installed interpreter is **Python 3.14**, so `pyo3-ffi` build-script exits 1: `configured Python interpreter version (3.14) is newer than PyO3's maximum supported version (3.13)`.
|
||||
Fixes: install Python 3.13; or `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`; or upgrade PyO3.
|
||||
|
||||
**B2 — CoolProp from-source build (build).** `crates/entropyk` and `crates/cli` force
|
||||
`entropyk-fluids/coolprop` *unconditionally* (`crates/entropyk/Cargo.toml:16`,
|
||||
`crates/cli/Cargo.toml:23`). `entropyk-coolprop-sys/build.rs` **always recompiles CoolProp
|
||||
from source via CMake** because `vendor/coolprop/CMakeLists.txt` exists (`build.rs:32`),
|
||||
and **ignores the prebuilt static lib** at
|
||||
`vendor/coolprop/install_root/static_library/Windows/64bit_MSVC_19.44.35227.0/CoolProp.lib`.
|
||||
The CMake `generate_headers` custom step fails: `error MSB8066: la build personnalisée de
|
||||
'…generate_headers.rule' s'est arrêtée. Code 1` (MSBuild). The build **appeared to work
|
||||
once** this session (`cargo check -p entropyk-fluids --features coolprop` finished in
|
||||
30.5 s and 115 tests passed) only because cached artifacts from a prior session were
|
||||
present; once that cache was invalidated the from-source header regeneration fails.
|
||||
**Consequence:** a clean checkout (`cargo clean && cargo build`) does NOT build; the
|
||||
facade, CLI, and any CoolProp code path are unbuildable once the cache is gone.
|
||||
|
||||
**B3 — Clippy (lint).** Two deny-by-default clippy errors in
|
||||
`crates/fluids/src/tabular/interpolate.rs:124` and `:131`
|
||||
(`unnecessary '>= y + 1'`) block clippy on every fluids-dependent crate.
|
||||
|
||||
**B4 — Components `http` feature + integration test crate (code).** Under `--all-features`:
|
||||
`entropyk-components` `http` feature → E0308 at `external_model.rs:675`; `tests/fluids`
|
||||
(`fluids-integration-tests`) → 89 errors (E0433) under its `coolprop` feature.
|
||||
|
||||
**B5 — One red solver test (code).** `test_newton_with_criteria_single_circuit` fails
|
||||
(convergence expectation), independent of CoolProp.
|
||||
|
||||
> None of B1–B5 was introduced by this audit. They confirm the warning in mission §4.4
|
||||
> that the "historical coherence report is stale". **They must be resolved before the
|
||||
> Phase B–F implementation can be validated workspace-wide.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Equation / unknown audit (per operating mode) [code]
|
||||
|
||||
### 5.1 `FloodedEvaporator` (single circuit, current)
|
||||
|
||||
Unknowns on its 2 refrigerant edges: inlet `(m,P,h)` + outlet `(P,h)` — outlet mass flow is
|
||||
the same branch as inlet (series) so `m_out = m_in`. Effectively unknown: **m_in, P_in, h_in,
|
||||
P_out, h_out** (5), with secondary `T_sec,in` and `C_sec` imposed as boundary scalars.
|
||||
|
||||
Equations: `n_equations = 2 (+1 if quality_control)` (`:443-450`):
|
||||
- r0: `P_out − P_in` (no DP)
|
||||
- r1: `m_ref·(h_out − h_in) − Q`, `Q = ε·C_sec·(T_sec,in − T_sat(P_in))`
|
||||
- r2 (optional): `q_actual − q_target`
|
||||
|
||||
### 5.2 Mode-by-mode DoF assessment for the *current* model
|
||||
|
||||
| Mode | Behavior | Under/over-determined? |
|
||||
|------|----------|------------------------|
|
||||
| Both sides flowing | 2 eqns (or 3), 5 unknowns → completed by upstream/downstream component equations + boundary `C_sec,T_sec` | Determined within a closed cycle |
|
||||
| `C_sec = 0` (secondary zero) | `effectiveness→0`, `Q→0`; r1 becomes `m·(h_out−h_in)=0`. **No equation constrains `h_out` independently** — outlet enthalpy floats unless an upstream/downstream component closes it | **At risk of underdetermination** if nothing else fixes `h_out` |
|
||||
| Quality control ON | r2 adds an `h_out` constraint via `q_actual(h_out) = q_target` | Restores DoF |
|
||||
| Component OFF / BYPASS | not implemented for FloodedEvaporator (no `OperationalState` handling visible) | Gap |
|
||||
| Flow reversal during Newton | `m` may go negative; model uses `m` directly in r1 (sign carried) but `T_evap`/`effectiveness` are flow-magnitude independent → finite, but **no antisymmetric DP** since DP is absent | Finite but physically thin |
|
||||
|
||||
### 5.3 Multi-circuit modes — **not representable today**
|
||||
|
||||
The current `FloodedEvaporator` has exactly one inlet edge and one outlet edge. The solver
|
||||
**rejects cross-circuit edges**, and the only inter-circuit heat mechanism is
|
||||
`ThermalCoupling` (`crates/solver/src/coupling.rs:52`) which transfers **duty via an external
|
||||
Q unknown**, never a shared fluid volume. Therefore none of these modes are representable:
|
||||
|
||||
- shared-vessel two refrigerant circuits + one water circuit (mission §5.1);
|
||||
- one circuit ON while the other is OFF in the same vessel;
|
||||
- independent refrigerant mass/energy per circuit with a common water energy balance.
|
||||
|
||||
The existing `Drum` (`components/src/drum.rs`) is a 4-port **single-circuit** separator
|
||||
(`n_equations` 8 legacy / 4 edge-coupled, `:333-338`); it does **not** override
|
||||
`flow_paths`/`set_port_context` and cannot model a shared multi-circuit vessel.
|
||||
|
||||
### 5.4 `tests/multi_circuit.rs` does NOT validate shared-vessel thermodynamics [code]
|
||||
|
||||
Read in full (233 lines). It uses a `RefrigerantMock` whose `compute_residuals` **always
|
||||
returns 0.0** (`:23-26`). Its 4 tests assert only: circuit counting, cross-circuit edge
|
||||
rejection, max-5-circuits rejection, and one `ThermalCoupling` UA·ΔT arithmetic check
|
||||
(`coupling_residuals([(350,300)]) ≈ 50000 W`). **No real dual-circuit HX residuals, no shared
|
||||
vessel, no converged coupled solve.** Any claim of multi-circuit HX coverage from this file
|
||||
is false (mission §16.13).
|
||||
|
||||
---
|
||||
|
||||
## 6. Cross-cutting defect catalogue [code]
|
||||
|
||||
| Pattern | Locations | Severity |
|
||||
|---|---|---|
|
||||
| `assert!` in non-test constructors (zero-panic violation) | `lmtd.rs:101`, `eps_ntu.rs:88`, `flooded_evaporator.rs:105`, `flooded_condenser.rs:70`, `condenser.rs:572`, `bphx_condenser.rs:141`, `mchx_condenser_coil.rs:116-117` | Medium |
|
||||
| Hard-coded `target_quality=0.7` | `flooded_evaporator.rs:112` | Low |
|
||||
| `p_in > 10_000.0` magic mode switch | `condenser.rs:1050,1164,1224,1401`; `evaporator.rs:844,947,1023,1149`; `flooded_evaporator.rs:471,541` | Medium |
|
||||
| `m_idx = … .unwrap_or(inlet_p_idx)` (mass→pressure column) | `condenser.rs:1057,1231`; `evaporator.rs:851,1030`; `flooded_evaporator.rs:478,548` | **High** |
|
||||
| Quality clamped to `[0,1]` | `flooded_evaporator.rs:150,189,375`; `moving_boundary_hx.rs:474`; `two_phase_dp.rs:108,132,151` | Low–Medium |
|
||||
| Interior mutability via `&self` in residual methods | `flooded_condenser.rs:45-46,269,280,283`; `bphx_exchanger.rs:61-64,323-328`; `bphx_evaporator.rs:94,348`; `bphx_condenser.rs:98-99` | Medium |
|
||||
| `n_equations` vs actual residual mismatch | `flooded_condenser.rs:247-266`; `moving_boundary_hx.rs:176`; `exchanger.rs:73-79` vs `:867` | Medium |
|
||||
| Jacobian ignores model state-dependence | `moving_boundary_hx.rs:219-225` | High (that component) |
|
||||
| `passes` unused in `ShellAndTube` ε-NTU | `eps_ntu.rs:168-174` | Medium |
|
||||
| Rich Friedel DP never wired into residuals | `two_phase_dp.rs` vs `condenser.rs:668-676` | Medium |
|
||||
| Duplicated `SecondaryJacCtx`/helpers | `condenser.rs` ↔ `evaporator.rs`; `compute_quality` ×3 | Medium |
|
||||
| `ShellAndTube1_2` F=0.9 hard-coded | `lmtd.rs:53` | Low |
|
||||
| `UA` clamping `[1e2,1e6]` | `fin_coil_condenser.rs:596` | Low |
|
||||
| `http` feature E0308 | `external_model.rs:675` | Medium (gatekeeps `--all-features`) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Fluid backend — derivative capability (mission §9.2) [code]
|
||||
|
||||
The `FluidBackend` trait (`crates/fluids/src/backend.rs:41`) exposes **point queries only**:
|
||||
`property`, `full_state`, `bubble_point`, `dew_point`, `temperature_glide` (= `T_dew−T_bubble`,
|
||||
two point queries), `saturation_pressure_t`, `saturation_enthalpy_t`.
|
||||
|
||||
**`dT_sat/dP` is NOT exposed by any backend.** The `CoolPropBackend`
|
||||
(`crates/fluids/src/coolprop.rs:28`) uses only the single-output `PropsSI` FFI shims
|
||||
(`props_si_pt/ph/ps/px/tq`); it **does not call CoolProp's `AbstractState` derivative API**
|
||||
(no `first_partial_deriv`). `DampedBackend` smooths property *values* near the critical
|
||||
point but adds no derivative method.
|
||||
|
||||
**Consequence:** every analytic Jacobian needing `dT_sat/dP` must use a central
|
||||
finite-difference fallback, exactly as `FloodedEvaporator` already does
|
||||
(`:568-572`, 3 CoolProp calls per Jacobian eval). Phase B should: (a) add a thin, documented
|
||||
backend method `saturation_temperature_derivative_dp` implemented as central FD over the
|
||||
backend (single source of truth, cacheable), and (b) optionally wire CoolProp's analytic
|
||||
derivative later. This satisfies mission §9.3 (explicit, documented fallback).
|
||||
|
||||
---
|
||||
|
||||
## 8. Bindings & examples coverage [code]
|
||||
|
||||
- **Python:** `FloodedEvaporator` is **not exposed**; pyo3 0.23 broken on Python 3.14 (B1).
|
||||
- **C:** `FloodedEvaporator` **not exposed**; C bindings are largely `SimpleAdapter` stubs
|
||||
(e.g. `entropyk_compressor_create` ignores AHRI coeffs, `bindings/c/src/components.rs:107-115`).
|
||||
- **WASM:** `FloodedEvaporator` **not exposed**.
|
||||
- **CLI factory** (`crates/cli/src/run.rs:2432`): `FloodedEvaporator` arm exists (`:2590`)
|
||||
but reads only `ua`, `target_quality`, `refrigerant`, `secondary_fluid`, secondary stream —
|
||||
**no geometry, no circuit count, no shared-vessel config**. Not wired into the factory at
|
||||
all: `FloodedCondenser`, `Economizer`, `MchxCondenserCoil`, `ReversingValve`,
|
||||
`PumpController`.
|
||||
- **Examples (18):** `FloodedEvaporator` is used in **none**. `chiller_r134a_flooded_headpressure.json`
|
||||
is misnamed (uses DX `Evaporator` + a condenser parameter). `chiller_r134a_dual_circuit_staging.json`
|
||||
is two **independent** circuits with separate water loops — **no shared vessel**.
|
||||
- **Vendor data:** Copeland/Danfoss/Bitzer compressors + SWEP BPHX only. **No Carrier data,
|
||||
no shell-and-tube / flooded-HX selection data.**
|
||||
|
||||
---
|
||||
|
||||
## 9. Findings directly driving the implementation phases
|
||||
|
||||
1. **No shared-vessel / multi-circuit HX primitive exists** — must be built (mission §6.4, Phase D).
|
||||
Cross-circuit edges are rejected; only duty-transfer `ThermalCoupling` exists.
|
||||
2. **Geometry domain types are missing from core** (`Length`, `Mass`, `Area`,
|
||||
`TemperatureDifference`, `ThermalConductivity`, `Volume`) — Phase B prerequisite.
|
||||
3. **The BPHX registry/geometry/correlation pattern is the reuse target**, not a rewrite:
|
||||
`correlation_registry.rs` + `bphx_correlation.rs` + `bphx_geometry.rs` + `two_phase_dp.rs`
|
||||
form a clean, evidence-aware, single-source-of-truth pattern to extend to shell-and-tube
|
||||
flooded boiling/condensation (mission §6.2).
|
||||
4. **Zero-flow regularization should build on `entropyk_core::smoothing`** (`smooth_abs`,
|
||||
`cubic_blend`, `smooth_max`) rather than hard `if m<ε` branches (mission §7.2).
|
||||
5. **Backend `dT_sat/dP` is absent** — add a documented central-FD method in Phase B (§7).
|
||||
6. **Baseline is red workspace-wide** (B1–B5). These must be unblocked before Phase B–F can
|
||||
be validated. Recommended pre-Phase-B fixes:
|
||||
- B2 (highest leverage): make `coolprop-sys` prefer the prebuilt `install_root` lib, or
|
||||
fix the `generate_headers` step, so the facade/CLI build reproducibly.
|
||||
- B3: fix the two clippy lints in `tabular/interpolate.rs`.
|
||||
- B5: fix or `#[ignore]` the `convergence_criteria` red test with a tracked issue.
|
||||
- B1: standardize on Python 3.13 for the bindings (Phase F, but unblock early).
|
||||
7. **`ShellAndTube` ε-NTU `passes` is decorative** — Phase B/C must implement a real
|
||||
P-NTU/multipass effectiveness or prevent its use for unsupported arrangements (mission §8.4).
|
||||
8. **Flooded evaporator outlet/control semantics** (mission §3) are wrong by default
|
||||
(`target_quality=0.7`) — Phase C replaces with an explicit `FloodedEvaporatorControl` enum.
|
||||
|
||||
---
|
||||
|
||||
## 10. Open questions requiring Carrier/product input (not answered here)
|
||||
|
||||
No Carrier source documents were provided for this audit, so the following remain
|
||||
**Unknown** and must NOT be guessed (mission §1.4, §13):
|
||||
|
||||
- 61XWHVZE flooded-evaporator geometry (shell ID, tube count per pass, active length per
|
||||
refrigerant circuit, tube type/enhancement, water-box layout, refrigerants).
|
||||
- 30XF-Z module/pass arrangement details.
|
||||
- Actual water-path layout through the shared-vessel refrigerant partitions
|
||||
(mission §6.4 `SharedSecondaryLayout`) — configurable until evidenced.
|
||||
- Validation datasets for any product.
|
||||
|
||||
These are captured in the companion `montluel_machine_coverage.md` with `Unknown` status.
|
||||
60
docs/audits/jacobian-health-report.json
Normal file
60
docs/audits/jacobian-health-report.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"story": "0-5-domain-sweep-jacobian-health-gate",
|
||||
"date": "2026-07-18",
|
||||
"gate_command": "cargo test -p entropyk-components --test jacobian_health_sweep",
|
||||
"allow_list": [],
|
||||
"surfaces": [
|
||||
{
|
||||
"surface_id": "valve_flow",
|
||||
"regions": ["opening_grid_x_dp_regimes"],
|
||||
"models": ["IsenthalpicOrifice", "ExvCdA"],
|
||||
"config": "helper",
|
||||
"status": "clean"
|
||||
},
|
||||
{
|
||||
"surface_id": "exv_orifice",
|
||||
"regions": ["dp_positive_opening_*", "dp_le_0_opening_*"],
|
||||
"openings": [0.0, 0.5, 1.0],
|
||||
"config": "EDGE_CFG",
|
||||
"h_floor": 1e-6,
|
||||
"rel_tol": 1e-4,
|
||||
"status": "clean"
|
||||
},
|
||||
{
|
||||
"surface_id": "condenser_fan",
|
||||
"regions": ["fan_phi_0.005", "fan_phi_0.75", "fan_phi_1.495"],
|
||||
"config": "EDGE_CFG",
|
||||
"status": "clean"
|
||||
},
|
||||
{
|
||||
"surface_id": "condenser_flood",
|
||||
"regions": ["flood_lambda_0.005", "flood_lambda_0.5", "flood_lambda_0.975"],
|
||||
"config": "EDGE_CFG",
|
||||
"status": "clean"
|
||||
},
|
||||
{
|
||||
"surface_id": "two_phase_quality",
|
||||
"regions": ["interior", "near_band", "exterior"],
|
||||
"config": "helper",
|
||||
"status": "clean"
|
||||
},
|
||||
{
|
||||
"surface_id": "msh_dome_edge",
|
||||
"regions": ["quality_grid_X_BLEND_to_1"],
|
||||
"config": "central_fd_msh_gradient",
|
||||
"status": "clean"
|
||||
}
|
||||
],
|
||||
"deferred_not_probed": [
|
||||
"exchanger_4port_nfr9",
|
||||
"python_components_stubs",
|
||||
"screw_economizer_incomplete_j",
|
||||
"sat_domain_p1",
|
||||
"bphx_correlation_p2"
|
||||
],
|
||||
"nfr11_attribution": {
|
||||
"status": "pending_story_2_6",
|
||||
"before_after_delta": null,
|
||||
"note": "Epic 0 P0 C1/phantom regularization landed; measure first-try % with Story 2.6"
|
||||
}
|
||||
}
|
||||
65
docs/audits/jacobian-health-report.md
Normal file
65
docs/audits/jacobian-health-report.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Jacobian Health Report (Epic 0 / Story 0.5)
|
||||
|
||||
**Date:** 2026-07-18
|
||||
**Gate:** `cargo test -p entropyk-components --test jacobian_health_sweep`
|
||||
**Harness:** `entropyk_components::jacobian_fd`
|
||||
**Allow-list:** empty for Epic-0 P0 surfaces (`EPIC0_ALLOW_LIST` in the test binary)
|
||||
|
||||
## How to regenerate
|
||||
|
||||
1. Run:
|
||||
```bash
|
||||
cargo test -p entropyk-components --test jacobian_health_sweep -- --nocapture
|
||||
```
|
||||
2. Confirm all gated tests pass.
|
||||
3. Manually sync this markdown if grids, configs, or statuses change.
|
||||
Default CI tests do **not** rewrite this file (avoids dirty working trees).
|
||||
|
||||
Optional machine-readable twin: `docs/audits/jacobian-health-report.json`.
|
||||
|
||||
---
|
||||
|
||||
## Config presets
|
||||
|
||||
| Preset | `rel_epsilon` | `h_floor` | `rel_tol` | Notes |
|
||||
|--------|---------------|-----------|-----------|-------|
|
||||
| `DEFAULT_CFG` | `1e-6` | `1e-3` | `1e-4` | Documented default |
|
||||
| `EDGE_CFG` | `1e-6` | `1e-6` | `1e-4` | C¹ `smooth_clamp` neighborhoods / actuator edges |
|
||||
|
||||
---
|
||||
|
||||
## Status matrix (gated surfaces)
|
||||
|
||||
| Surface | Region / probes | Config | Status | Notes |
|
||||
|---------|-----------------|--------|--------|-------|
|
||||
| `valve_flow` / `IsenthalpicOrifice` | openings `{0,0.5,1}` × `{ΔP>0, ΔP≤0}` | helper FD / phantom checks | `clean` | Informative `dp_up` at ΔP≤0; `dp_down` FD at interior ΔP>0 |
|
||||
| `valve_flow` / `ExvCdA` | same grid | helper FD / phantom checks | `clean` | Same as orifice model |
|
||||
| `exv_orifice` | openings `{0,0.5,1}` × `{dp_positive, dp_le_0}` | `EDGE_CFG` | `clean` | `assert_jacobian_healthy` via allow-list (empty) |
|
||||
| `condenser_fan` | `φ ∈ {0.005, 0.75, 1.495}` | `EDGE_CFG` | `clean` | Inside C¹ ramps (not exact kink) |
|
||||
| `condenser_flood` | `λ ∈ {0.005, 0.5, 0.975}` | `EDGE_CFG` | `clean` | Inside C¹ ramps |
|
||||
| `two_phase_quality` | interior `0.5`, near-band `0.005`, exterior `-0.2` / `1.2` | helper checks | `clean` | Soft≠hard in band; exterior saturates |
|
||||
| `msh_dome_edge` | quality `{0.5,0.8,0.90,0.95,0.99,0.999,1−ε}` | central FD on `msh_gradient` | `clean` | Bounded `∂g/∂x` across Hermite blend |
|
||||
|
||||
---
|
||||
|
||||
## Deferred debt (not probed — do not allow-list P0)
|
||||
|
||||
These remain documented debt and are **out of the Epic-0 gate**. Adding probes later requires either a fix or an explicit allow-list entry with rationale in this file and the audit catalogue.
|
||||
|
||||
| Item | Reason | Status |
|
||||
|------|--------|--------|
|
||||
| `heat_exchanger/exchanger.rs` 4-port production FD Jacobian | NFR9 — analytic path is already FD | not probed |
|
||||
| `python_components*` empty / stub Jacobians | Binding stubs | not probed |
|
||||
| `screw_economizer_compressor` incomplete analytic rows / P1 clamps | Separate remediation | not probed |
|
||||
| `sat_domain.rs` P1 pressure clamp | Deferred P1 | not probed |
|
||||
| `bphx_correlation` P2 quality clamps | Correlation-local P2 | not probed |
|
||||
|
||||
---
|
||||
|
||||
## NFR11 attribution (pending Story 2.6)
|
||||
|
||||
Epic 0 regularized P0 Jacobian killers (valve ΔP≤0 phantom gradients, EXV opening C¹ clamps, two-phase quality / condenser fan-flood C¹, MSH Hermite near x→1).
|
||||
|
||||
First-try convergence % across the operating envelope (`T_source` / `T_sink` / load / fluid) will be measured by **Story 2.6** robustness-sweep infrastructure.
|
||||
|
||||
**Before/after delta attributable to Epic 0 regularization: TBD** (fill when Story 2.6 lands). Do not invent placeholder percentages.
|
||||
@@ -26,20 +26,16 @@
|
||||
|
||||
| Site | Failure mode | Severity | Fix owner |
|
||||
|------|--------------|----------|-----------|
|
||||
| `crates/components/src/valve_flow.rs:105-107` — `valve_mass_flow_dp_up` returns `Ok(0.0)` when `dp <= 0 \|\| m <= 0` | `zero-gradient` | **P0** | Story 0.3 |
|
||||
| `crates/components/src/isenthalpic_expansion_valve.rs:233` — `state[i].clamp(0.0, 1.0)` on orifice opening (residual path) | `discontinuity` | **P0** | Story 0.3 |
|
||||
| `crates/components/src/isenthalpic_expansion_valve.rs:449` — `_state[i].clamp(0.0, 1.0)` on orifice opening (Jacobian path) | `discontinuity` | **P0** | Story 0.3 |
|
||||
| `crates/components/src/heat_exchanger/two_phase_dp.rs:107` — `quality.clamp(0.0, 1.0)` in `homogeneous_density` | `discontinuity` | **P0** | Story 0.4 |
|
||||
| `crates/components/src/heat_exchanger/two_phase_dp.rs:150` — `quality.clamp(0.0, 1.0)` in `friedel_multiplier` | `discontinuity` | **P0** | Story 0.4 |
|
||||
| `crates/components/src/heat_exchanger/condenser.rs:492` — `state[idx].clamp(0.0, 1.5)` fan actuator φ | `discontinuity` | **P0** | Story 0.4 |
|
||||
| `crates/components/src/heat_exchanger/condenser.rs:752` — `state[idx].clamp(0.0, 0.98)` flooded level λ | `discontinuity` | **P0** | Story 0.4 |
|
||||
| `crates/components/src/valve_flow.rs` — ΔP≤0 / opening clamps / `dp_up` hard zero | `already-regularized` | info | Story **0.3 landed** — `smooth_max` ΔP + `smooth_clamp` opening + `valve_mass_flow_dp_down` |
|
||||
| `crates/components/src/isenthalpic_expansion_valve.rs` — orifice opening clamps + ΔP early-out | `already-regularized` | info | Story **0.3 landed** — shared `smooth_clamp` / `smooth_max` in residual **and** Jacobian |
|
||||
| `crates/components/src/heat_exchanger/two_phase_dp.rs` — quality in `homogeneous_density` / `friedel_multiplier` | `already-regularized` | info | Story **0.4 landed** — `smooth_clamp` quality (`QUALITY_WIDTH=1e-2`) |
|
||||
| `crates/components/src/heat_exchanger/condenser.rs` — fan φ / flood λ actuators | `already-regularized` | info | Story **0.4 landed** — `smooth_clamp` + Jacobian `*_derivative` chain rule |
|
||||
| `crates/components/src/heat_exchanger/two_phase_dp.rs` — MSH Hermite blend near x→1 | `already-regularized` | info | Story **0.4 verified** — FD-bounded `∂g/∂x` across `X_BLEND=0.90` |
|
||||
|
||||
### Evidence notes
|
||||
|
||||
- **Valve ΔP≤0:** `valve_mass_flow` already forces `dp = ΔP.max(0)`, so ṁ=0 and `valve_mass_flow_dp_up` returns a hard zero — Newton gets no direction to restore positive ΔP.
|
||||
- **EXV opening clamps:** same hard clamp appears in both residual helper and Jacobian branch; Story 0.2 `smooth_clamp` is the intended replacement (`entropyk_core::smoothing` already exists).
|
||||
- **EXV phantom ΔP:** `DP_FLOOR` (~L396–445) is a **partial** `already-regularized` mitigation for ΔP≤0 on the Jacobian only; residual still early-outs. Opening clamps remain P0.
|
||||
- **Missing API:** Epic Story 0.3 text mentions `valve_mass_flow_dp_down` — **only `valve_mass_flow_dp_up` exists** today (`lib.rs` re-export). Catalogue gap severity `info`; inventing `dp_down` is out of scope for Story 0.1.
|
||||
- **Valve / EXV (Story 0.3):** `dp_eff = smooth_max(ΔP, 0, k)` with `k = 1e3` Pa; opening via `smooth_clamp(..., width=1e-2)`; `valve_mass_flow_dp_up` / `dp_down` are chain-rule consistent; EXV residual and Jacobian share the same expression (no Jacobian-only `DP_FLOOR` split).
|
||||
- **HX (Story 0.4):** quality helpers use `smooth_clamp`; condenser fan/flood residual+Jacobian share `smooth_clamp` / `smooth_clamp_derivative`; MSH singularity blend kept and FD-verified. Evaporator has no fan/flood actuator clamps (consumes regularized `two_phase_dp`).
|
||||
|
||||
---
|
||||
|
||||
@@ -51,17 +47,15 @@ Classified from a ripgrep sweep of `.clamp(` / `.max(` / `.min(` under `crates/c
|
||||
|
||||
| Location | Pattern | Mode | Sev | Notes / owner |
|
||||
|----------|---------|------|-----|---------------|
|
||||
| `valve_flow.rs:71,75,78,99` | `dp.max(0)`, `opening.clamp`, `m.max(0)` | `zero-gradient` / `discontinuity` | P0 | Feeds EXV/orifice; Story 0.3 |
|
||||
| `valve_flow.rs:105-107` | hard `Ok(0.0)` on ∂ṁ/∂P_up | `zero-gradient` | P0 | FR19 |
|
||||
| `isenthalpic_expansion_valve.rs:233,449` | opening clamp on state | `discontinuity` | P0 | FR19 → 0.3 |
|
||||
| `isenthalpic_expansion_valve.rs:396-445` | `DP_FLOOR` phantom | `already-regularized` | info | Keep; unify with toolkit in 0.2/0.3 |
|
||||
| `two_phase_dp.rs:107,150` | quality clamp | `discontinuity` | P0 | FR19 → 0.4 |
|
||||
| `two_phase_dp.rs:118-131` | hard `x<=0` / `x>=1` branches in `zivi_void_fraction` | `discontinuity` | P1 | Story 0.4 (MSH/dome) |
|
||||
| `two_phase_dp.rs:255-294` | Hermite blend near x→1 (`msh_gradient`) | `already-regularized` | info | Partial MSH fix; 0.4 completes |
|
||||
| `condenser.rs:492,752` | fan / flood actuator clamps | `discontinuity` | P0 | FR19 → 0.4 |
|
||||
| `evaporator.rs` | analogous secondary / property floors | `benign` / P2 | P2 | Re-check when evaporator actuators mirror condenser |
|
||||
| `valve_flow.rs` | `smooth_max` ΔP + `smooth_clamp` opening + `dp_up`/`dp_down` | `already-regularized` | info | Story 0.3 landed |
|
||||
| `isenthalpic_expansion_valve.rs` orifice | shared `smooth_clamp` / `smooth_max` residual+Jacobian | `already-regularized` | info | Story 0.3 landed |
|
||||
| `two_phase_dp.rs` quality helpers | `smooth_clamp` quality | `already-regularized` | info | Story 0.4 landed |
|
||||
| `two_phase_dp.rs` `zivi_void_fraction` | hard `x<=0` / `x>=1` branches | `benign` | info | Not on live Newton path (export/tests only) — left as-is in 0.4 |
|
||||
| `two_phase_dp.rs` `msh_gradient` | Hermite blend near x→1 | `already-regularized` | info | Story 0.4 verified (FD bounded) |
|
||||
| `condenser.rs` fan / flood | `smooth_clamp` + chain-rule J | `already-regularized` | info | Story 0.4 landed |
|
||||
| `evaporator.rs` | no fan/flood actuator clamps | `benign` | info | N/A — consumes regularized `two_phase_dp` |
|
||||
| `heat_exchanger/flow_regularization.rs` | smooth mass / activity | `already-regularized` | info | Reference for Story 0.2 unification |
|
||||
| `heat_exchanger/sat_domain.rs:81` | `p_pa.clamp(p_min, p_max)` | `discontinuity` | P1 | Intermediate iterate domain; Story 0.4 / sat path |
|
||||
| `heat_exchanger/sat_domain.rs:81` | `p_pa.clamp(p_min, p_max)` | `already-regularized` | info | Tube-ΔP path now uses a C¹ `smooth_clamp` with exposed derivative (`heat_exchanger::tube_dp`, 2026-07-19); the shared helper keeps the hard clamp for its other consumers |
|
||||
| `screw_economizer_compressor.rs:486` | eco fraction `.clamp(0.0, 0.4)` | `discontinuity` | P1 | If eco fraction is a Newton unknown |
|
||||
| `screw_economizer_compressor.rs:291,343` | slide valve clamp | `discontinuity` | P1 | Config vs state — verify call site |
|
||||
| `bphx_correlation.rs:588+` | quality / p_reduced clamps | `discontinuity` | P2 | Correlation internals on quality |
|
||||
@@ -84,6 +78,7 @@ Classified from a ripgrep sweep of `.clamp(` / `.max(` / `.min(` under `crates/c
|
||||
| `screw_economizer_compressor.rs` | Incomplete analytic Jacobian rows | info |
|
||||
| `python_components.rs` | Empty `jacobian_entries` stubs | info |
|
||||
| Category-B solver tests (`jacobian_freezing`, inverse calibration) | Deferred per `plans/audit-2026-07-remediation-plan.md` | info |
|
||||
| ~~`condenser.rs` / `evaporator.rs` tube-ΔP momentum partials~~ | **Resolved (2026-07-19):** was whole-ΔP central FD through the backend (~72 CoolProp calls/assembly, straddling clamps/blends); now exact analytic composition in `heat_exchanger::tube_dp` with narrow domain-safe FDs only on individual saturation properties (thin FFI exposes no saturation derivatives). Friedel correlation partials use narrow FD of the pure correlation | info (NFR9) |
|
||||
|
||||
---
|
||||
|
||||
@@ -97,12 +92,9 @@ use entropyk_components::jacobian_fd::{
|
||||
};
|
||||
|
||||
let report = check_jacobian_health(&component, &state, JacobianFdConfig::default())?;
|
||||
assert!(
|
||||
report.has_zero_gradient_killers(),
|
||||
"expected clamp/zero-gradient signature, got clean report"
|
||||
);
|
||||
// After Stories 0.2–0.4 fixes, Story 0.5 flips the gate to:
|
||||
// assert!(report.is_clean());
|
||||
// Story 0.5 gate (Epic-0 P0 surfaces):
|
||||
assert!(report.is_clean());
|
||||
// Or: assert_jacobian_healthy(&component, &state, cfg, surface, region, &[]);
|
||||
```
|
||||
|
||||
### Defaults (aligned with condenser FD tests)
|
||||
@@ -117,15 +109,16 @@ assert!(
|
||||
|
||||
**Do not** call `JacobianMatrix::numerical` (solver) from this harness — that API is **forward** difference for Newton fallback.
|
||||
|
||||
Integration smoke tests: `crates/components/tests/jacobian_health_sweep.rs`.
|
||||
Integration gate tests: `crates/components/tests/jacobian_health_sweep.rs`.
|
||||
|
||||
---
|
||||
|
||||
## Story 0.5 CI gate (future)
|
||||
## Story 0.5 CI gate (landed)
|
||||
|
||||
1. Reuse `check_jacobian_health` over per-component physical-domain grids (ΔP≤0, quality 0/1, opening 0/1, dome edges, actuator clamps).
|
||||
2. Fail CI on new `zero_gradient_killers` or mismatches outside an allow-list that shrinks to empty after 0.2–0.4.
|
||||
3. Commit a per-component / per-region health report artifact (extend this file or generate JSON beside it).
|
||||
1. Reuse `check_jacobian_health` / `assert_jacobian_healthy` over Epic-0 physical-domain grids (ΔP≤0, quality edges, opening 0/1, dome edges, actuator clamps).
|
||||
2. Fail CI on new `zero_gradient_killers` or mismatches outside an allow-list — Epic-0 P0 allow-list is **empty**.
|
||||
3. Committed per-surface / per-region report: [`jacobian-health-report.md`](./jacobian-health-report.md) (+ optional JSON twin).
|
||||
4. Explicit CI step: `cargo test -p entropyk-components --test jacobian_health_sweep` in `.github/workflows/ci.yml`.
|
||||
|
||||
---
|
||||
|
||||
@@ -134,15 +127,15 @@ Integration smoke tests: `crates/components/tests/jacobian_health_sweep.rs`.
|
||||
| Work | Owner |
|
||||
|------|-------|
|
||||
| Standardize / document `smooth_*` ε guidance; declare `flow_regularization` as HX reference | Story **0.2** — **landed** (`docs/components/phantom-gradient-regularization.md`; derivatives in `crates/core/src/smoothing.rs`) |
|
||||
| Valve ΔP≤0 phantom + EXV opening `smooth_clamp` | Story **0.3** |
|
||||
| Quality / condenser–evaporator state clamps + MSH singularity | Story **0.4** |
|
||||
| Full-envelope CI Jacobian health gate | Story **0.5** |
|
||||
| Valve ΔP≤0 phantom + EXV opening `smooth_clamp` | Story **0.3** — **landed** |
|
||||
| Quality / condenser–evaporator state clamps + MSH singularity | Story **0.4** — **landed** |
|
||||
| Full-envelope CI Jacobian health gate | Story **0.5** — **landed** (see [`jacobian-health-report.md`](./jacobian-health-report.md)) |
|
||||
| Replace production FD Jacobian in generic 4-port HX | Separate NFR9 / remediation track |
|
||||
|
||||
---
|
||||
|
||||
## Severity ranking summary
|
||||
|
||||
1. **P0 (fix in 0.3–0.4):** valve ΔP zero-gradient; EXV opening clamps; two-phase quality clamps; condenser fan/flood clamps.
|
||||
2. **P1:** sat-domain pressure clamp; screw eco/slide clamps when unknowns; zivi hard edges.
|
||||
3. **P2 / info:** correlation floors, config clamps, NFR9 FD-in-production debt.
|
||||
1. **P0 (fixed):** valve/EXV (0.3); two-phase quality + condenser fan/flood (0.4).
|
||||
2. **P1:** sat-domain pressure clamp; screw eco/slide clamps when unknowns.
|
||||
3. **P2 / info:** correlation floors, config clamps, unused `zivi` hard edges, NFR9 FD-in-production debt.
|
||||
|
||||
306
docs/audits/montluel_machine_coverage.md
Normal file
306
docs/audits/montluel_machine_coverage.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Montluel Machine Coverage Matrix (Phase A — initial)
|
||||
|
||||
**Date:** 2026-07-16
|
||||
**Status:** Phase A initial assessment. **No Carrier source documents were provided for
|
||||
this audit.** Product-specific fields are therefore `Unknown` unless confirmed by repository
|
||||
code. Per mission §13 rules, missing evidence is never converted into an assumption.
|
||||
|
||||
Status legend:
|
||||
- **Supported** = a convergent end-to-end system test exists AND required
|
||||
components/correlations are present.
|
||||
- **Partial** = architecture exists but physical model, configuration, or validation is
|
||||
incomplete.
|
||||
- **Not supported** = required primitive/topology is absent.
|
||||
- **Unknown** = source information insufficient to classify.
|
||||
|
||||
Source codes: **[code]** = repository evidence; **[carrier]** = authorized Carrier source
|
||||
(none used here); **?** = Unknown.
|
||||
|
||||
Evidence base: `docs/audits/heat_exchanger_architecture_audit.md` (same date).
|
||||
|
||||
---
|
||||
|
||||
## A. Archetypes under investigation
|
||||
|
||||
| ID | Archetype | Simulation status | Rationale (one line) |
|
||||
|----|-----------|-------------------|----------------------|
|
||||
| M1 | Air-cooled screw chiller (single circuit) | **Partial** | Components exist; `FloodedEvaporator`+`Drum` smoke-tested but not converged; air coil UA from geometry (FinCoil/MCHX) is geometry-capable. |
|
||||
| M2 | Air-cooled screw chiller, dual-circuit / dual-module | **Not supported** | No shared-vessel primitive; cross-circuit edges rejected; example uses independent circuits only. |
|
||||
| M3 | Water-cooled screw chiller / heat pump | **Partial** | `Condenser`/`Evaporator`/`FloodedEvaporator` exist; flooded model UA-only, no geometry, no `dT_sat/dP` analytic. |
|
||||
| M4 | Flooded shell-and-tube evaporator/condenser machine | **Partial** | `FloodedEvaporator`/`FloodedCondenser` exist but UA-only, single-circuit, no shell-and-tube geometry; `FloodedCondenser` has an eqn-count defect. |
|
||||
| M5 | BPHX evaporator/economizer machine | **Partial** | `BphxEvaporator`/`Economizer` exist with geometry+correlation; superheat/subcooling not enforced as residuals; DP not residual-coupled. |
|
||||
| M6 | Scroll machine with BPHX or air-side coils | **Partial** | Compressors (Copeland/Danfoss) + BPHX/coils exist; no scroll-specific compressor model in core. |
|
||||
| M7 | Reversible air-to-water heat pump | **Partial** | `ReversingValve` component exists; not wired into CLI factory; reversing logic + 4-way cycle not system-tested. |
|
||||
| M8 | Centrifugal machine | **Not supported** | No centrifugal compressor component; no flooded centrifugal cycle test. |
|
||||
| M9 | Dry-cooler / free-cooling | **Partial** | `FreeCoolingExchanger` exists (CLI arm `run.rs:4092`); no converged free-cooling system test verified. |
|
||||
| M10 | Heat recovery / desuperheater | **Not supported** | No desuperheater component; no heat-recovery topology test. |
|
||||
| M11 | One-, two-, three-pass water exchangers | **Not supported (physically)** | `ShellAndTube{passes}` ε-NTU is decorative (`passes` unused); pass count does not affect HTC/DP/UA. |
|
||||
| M12 | One circuit running while another stopped (shared vessel) | **Not supported** | No shared-vessel primitive; staging example uses independent circuits. |
|
||||
| M13 | Multiple compressors per circuit | **Unknown** | Topology allows multiple components per circuit, but no system test with ≥2 compressors on one circuit was verified. |
|
||||
| M14 | Economizer / vapor injection | **Partial** | `ScrewEconomizerCompressor` + `Economizer` IHX exist; economized cycle has integration test (smoke, not converged-physics). |
|
||||
| M15 | Confirmed current-product refrigerants | **Unknown** | CoolProp supports R134a/R410A/R454B/etc. in tests; which refrigerants ship in confirmed Montluel products is Unknown (no Carrier data). |
|
||||
|
||||
> **Note on "Partial":** the codebase can *draw the topology* for several archetypes, but
|
||||
> mission §13 requires separating "can draw the topology" from "can predict performance
|
||||
> accurately." None reach **Supported** because no convergent end-to-end physics solve with
|
||||
> geometry-based UA was verified in Phase A.
|
||||
|
||||
---
|
||||
|
||||
## B. Detailed matrix (required columns)
|
||||
|
||||
Columns per mission §13. `?` = Unknown (no evidence). Condensed where product-specific.
|
||||
|
||||
### M1 — Air-cooled screw chiller (single circuit)
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? (no Carrier data) |
|
||||
| Evidence/source | [code] components exist; no product doc |
|
||||
| Cycle topology | single-circuit vapor-compression with economizer [code] |
|
||||
| Compressor type | `ScrewEconomizerCompressor` [code] |
|
||||
| Number of refrigerant circuits | 1 [code] |
|
||||
| Number of modules | ? |
|
||||
| Evaporator technology | `FloodedEvaporator` + `Drum` (per-circuit) [code] |
|
||||
| Condenser technology | `MchxCondenserCoil` / `FinCoilCondenser` + fan bank [code] |
|
||||
| Water/air path arrangement | air-cooled [code] |
|
||||
| Pass count/options | N/A (air side) |
|
||||
| Economizer | yes (IHX `Economizer`) [code] |
|
||||
| Heat recovery | no |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing Entropyk components | screw econ compressor, flooded evap, drum, mchx/fin coil, fan, EXV |
|
||||
| Missing components | converged-physics solve; geometry-based flooded UA |
|
||||
| Missing topology capability | none (single circuit) |
|
||||
| Missing correlations/data | flooded boiling correlation not in registry; air coil UA inline (not registry) |
|
||||
| Zero-flow/staging support | single-circuit only |
|
||||
| Validation dataset available? | **No** |
|
||||
| Simulation status | **Partial** |
|
||||
| Blocking issues | B2 (CoolProp build), B5 (red test) |
|
||||
| Required implementation/test | converged air-cooled chiller system test |
|
||||
|
||||
### M2 — Air-cooled screw chiller, dual-circuit / dual-module
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? |
|
||||
| Evidence/source | [code] no shared-vessel primitive; `multi_circuit.rs` uses mocks |
|
||||
| Cycle topology | two refrigerant circuits [code: rejected cross-circuit edges] |
|
||||
| Compressor type | screw [code] |
|
||||
| Number of refrigerant circuits | 2 (target) |
|
||||
| Number of modules | 1 (shared vessel) or 2 (dual module) — both **Not supported** |
|
||||
| Evaporator technology | needs shared-vessel flooded OR two modules |
|
||||
| Condenser technology | air coil bank [code] |
|
||||
| Water/air path arrangement | one common air stream (interlaced/face-split) — **not modeled** |
|
||||
| Pass count/options | ? |
|
||||
| Economizer | ? |
|
||||
| Heat recovery | ? |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing Entropyk components | per-circuit flooded evap + drum only |
|
||||
| Missing components | `MultiCircuitFloodedEvaporator`; shared-secondary wrapper; air-side multi-circuit topology |
|
||||
| Missing topology capability | **cross-circuit shared vessel** (Phase D); air-side circuit interlace (Phase E) |
|
||||
| Missing correlations/data | circuit-specific UA calibration |
|
||||
| Zero-flow/staging support | **Not supported** (no primitive) |
|
||||
| Validation dataset available? | **No** |
|
||||
| Simulation status | **Not supported** |
|
||||
| Blocking issues | Phase D + E prerequisites |
|
||||
| Required implementation/test | shared-vessel dual-circuit converged test; one-circuit-off test |
|
||||
|
||||
### M3 — Water-cooled screw chiller / heat pump
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? |
|
||||
| Evidence/source | [code] condenser/evaporator/flooded exist |
|
||||
| Cycle topology | vapor-compression, water-coupled [code] |
|
||||
| Compressor type | screw [code] |
|
||||
| Number of refrigerant circuits | 1 [code] |
|
||||
| Number of modules | ? |
|
||||
| Evaporator technology | DX `Evaporator` or `FloodedEvaporator` (UA-only) [code] |
|
||||
| Condenser technology | `Condenser` (UA-only) / `FloodedCondenser` (defective eqn count) [code] |
|
||||
| Water/air path arrangement | water-cooled, secondary as boundary or 4-port [code] |
|
||||
| Pass count/options | **decorative** (`passes` unused) |
|
||||
| Economizer | optional [code] |
|
||||
| Heat recovery | no |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing Entropyk components | condenser, evaporator, flooded evap/cond, pump, brine boundary |
|
||||
| Missing components | shell-and-tube geometry; water-side DP by pass; pass-aware effectiveness |
|
||||
| Missing topology capability | fully-coupled live secondary ports (boundary mode is default) |
|
||||
| Missing correlations/data | Gnielinski water-side; bundle boiling; tube-bundle condensation |
|
||||
| Zero-flow/staging support | boundary regularization only |
|
||||
| Validation dataset available? | **No** |
|
||||
| Simulation status | **Partial** |
|
||||
| Blocking issues | Phase B/C geometry + correlations |
|
||||
| Required implementation/test | geometry-rated water chiller converged test |
|
||||
|
||||
### M4 — Flooded shell-and-tube evaporator/condenser machine
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? (61XWHVZE referenced in mission — **no data provided**) |
|
||||
| Evidence/source | [code] flooded evap/cond exist; mission §5.1 [carrier: not provided] |
|
||||
| Cycle topology | flooded evap + flooded condenser [code] |
|
||||
| Compressor type | ? |
|
||||
| Number of refrigerant circuits | 1 [code]; 2 shared-vessel **Not supported** |
|
||||
| Number of modules | ? |
|
||||
| Evaporator technology | `FloodedEvaporator` (UA-only, single circuit, no geometry) [code] |
|
||||
| Condenser technology | `FloodedCondenser` (UA-only, eqn-count defect) [code] |
|
||||
| Water/air path arrangement | one common water circuit [carrier: mission §5.1, geometry not provided] |
|
||||
| Pass count/options | standard 2-pass + 1/3 options [carrier: mission §5.1]; **passes unused in code** |
|
||||
| Economizer | economizer BPHX [carrier: mission §5.1] |
|
||||
| Heat recovery | ? |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing Entropyk components | flooded evap/cond (UA-only) |
|
||||
| Missing components | `ShellAndTubeGeometry`; flooded boiling correlation; tube-bundle condensation; `MultiCircuitFloodedEvaporator` |
|
||||
| Missing topology capability | shared-vessel two refrigerant circuits + intermediate tube sheet (Phase D) |
|
||||
| Missing correlations/data | Cooper/Gorenflo pool boiling baseline; enhanced-tube correction; water-box losses; **all product geometry Unknown** |
|
||||
| Zero-flow/staging support | not implemented |
|
||||
| Validation dataset available? | **No** |
|
||||
| Simulation status | **Partial** (single circuit) / **Not supported** (shared-vessel dual circuit) |
|
||||
| Blocking issues | Carrier geometry data required for fixtures; Phases B/C/D |
|
||||
| Required implementation/test | 1- and 2-circuit flooded chiller converged tests with geometry UA |
|
||||
|
||||
### M5 — BPHX evaporator/economizer machine
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? |
|
||||
| Evidence/source | [code] BPHX family + registry + geometry |
|
||||
| Cycle topology | DX BPHX evap + BPHX economizer [code] |
|
||||
| Compressor type | screw/scroll [code] |
|
||||
| Number of refrigerant circuits | 1 [code]; dual-circuit BPHX **Partial** (Phase E) |
|
||||
| Evaporator technology | `BphxEvaporator` (geometry+correlation) [code] |
|
||||
| Condenser technology | `BphxCondenser` [code] |
|
||||
| Water/air path arrangement | 4-port [code] |
|
||||
| Pass count/options | N/A (plate) |
|
||||
| Economizer | `Economizer` IHX [code] |
|
||||
| Heat recovery | no |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing Entropyk components | full BPHX family + correlation registry |
|
||||
| Missing components | superheat/subcooling as residual equations; DP residual coupling; dual-circuit BPHX |
|
||||
| Missing topology capability | circuit-specific UA calibration (Phase E) |
|
||||
| Missing correlations/data | SWEP BPHX data present (B5THx20, B8THx30); product-specific calibration Unknown |
|
||||
| Zero-flow/staging support | correlation guards only |
|
||||
| Validation dataset available? | **No** |
|
||||
| Simulation status | **Partial** |
|
||||
| Blocking issues | residual-coupled DP; Phase E dual-circuit |
|
||||
| Required implementation/test | BPHX chiller converged test with residual DP |
|
||||
|
||||
### M6 — Scroll machine (BPHX / air coils)
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Product family | ? |
|
||||
| Evidence/source | [code] vendor scroll data (Copeland/Danfoss) |
|
||||
| Cycle topology | vapor-compression [code] |
|
||||
| Compressor type | scroll (vendor data) [code]; no core scroll component model |
|
||||
| Number of refrigerant circuits | 1 [code] |
|
||||
| Evaporator/condenser technology | BPHX or air coil [code] |
|
||||
| Pass count/options | N/A |
|
||||
| Economizer | ? |
|
||||
| Heat recovery | no |
|
||||
| Refrigerant(s) | ? |
|
||||
| Existing components | vendor compressor data, BPHX, coils |
|
||||
| Missing components | scroll compressor component wired to vendor backend |
|
||||
| Simulation status | **Partial** |
|
||||
|
||||
### M7 — Reversible air-to-water heat pump
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] `ReversingValve` exists; not in CLI factory |
|
||||
| Cycle topology | reversible [code] |
|
||||
| Compressor type | ? |
|
||||
| Circuits | 1 [code] |
|
||||
| Evaporator/condenser | role-swap via reversing valve [code] |
|
||||
| Reversing capability | component exists, **not system-tested** |
|
||||
| Simulation status | **Partial** |
|
||||
| Blocking issues | reversing valve CLI wiring; reversing system test |
|
||||
|
||||
### M8 — Centrifugal machine
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] no centrifugal compressor component |
|
||||
| Simulation status | **Not supported** |
|
||||
| Blocking issues | centrifugal compressor model entirely absent |
|
||||
|
||||
### M9 — Dry-cooler / free-cooling
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] `FreeCoolingExchanger` (CLI arm `run.rs:4092`) |
|
||||
| Simulation status | **Partial** |
|
||||
| Blocking issues | no converged free-cooling system test verified |
|
||||
|
||||
### M10 — Heat recovery / desuperheater
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] no desuperheater component; no HR topology test |
|
||||
| Simulation status | **Not supported** |
|
||||
|
||||
### M11 — Water exchanger pass count (1/2/3 pass)
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] `eps_ntu.rs:168-174` `passes` unused; `lmtd.rs:53` F=0.9 hard-coded |
|
||||
| Simulation status | **Not supported (physically)** — pass count does not affect UA/HTC/DP/effectiveness |
|
||||
| Blocking issues | Phase B/C: real P-NTU multipass effectiveness + per-pass velocity/DP |
|
||||
|
||||
### M12 — One circuit ON, other OFF (shared vessel)
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] no shared-vessel primitive |
|
||||
| Simulation status | **Not supported** |
|
||||
| Blocking issues | Phase D (zero-flow-safe multi-circuit) |
|
||||
|
||||
### M13 — Multiple compressors per circuit
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | topology allows multiple nodes per circuit [code]; no verified test |
|
||||
| Simulation status | **Unknown** |
|
||||
|
||||
### M14 — Economizer / vapor injection
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | [code] `ScrewEconomizerCompressor` + `Economizer` IHX; integration test is smoke-only |
|
||||
| Simulation status | **Partial** |
|
||||
|
||||
### M15 — Confirmed current-product refrigerants
|
||||
|
||||
| Field | Value / Evidence |
|
||||
|-------|------------------|
|
||||
| Evidence/source | CoolProp tests cover R134a/R410A/R454B [code]; which refrigerants ship in Montluel products |
|
||||
| Simulation status | **Unknown** (no Carrier data) |
|
||||
|
||||
---
|
||||
|
||||
## C. Coverage summary
|
||||
|
||||
| Status | Count | Archetypes |
|
||||
|--------|-------|------------|
|
||||
| Supported | **0** | — |
|
||||
| Partial | 9 | M1, M3, M4(single), M5, M6, M7, M9, M14 (+M4 single) |
|
||||
| Not supported | 5 | M2, M8, M10, M11(physical), M12 |
|
||||
| Unknown | 2 | M13, M15 |
|
||||
|
||||
**Zero archetypes are Supported.** The blockers are, in priority order:
|
||||
1. Shared-vessel multi-circuit primitive (Phase D) → unblocks M2, M4-dual, M12.
|
||||
2. Shell-and-tube geometry + correlations (Phases B/C) → unblocks M3, M4, M11.
|
||||
3. Residual-coupled DP + control semantics (Phase C) → unblocks M5.
|
||||
4. Carrier product geometry/validation data → required to move any archetype to Supported.
|
||||
|
||||
## D. Data required from Carrier (to advance beyond Partial/Unknown)
|
||||
|
||||
The following are needed and **must not be guessed** (mission §1.4, §14):
|
||||
- 61XWHVZE: shell ID, tube OD/ID, tube count per pass, active length per refrigerant
|
||||
circuit, tube enhancement type, water-box geometry, fouling factors, refrigerant(s),
|
||||
intermediate tube-sheet partition details, water-path layout through partitions.
|
||||
- 30XF-Z: module/pass arrangement, one- vs two-pass evaporator options, pump/DP implications.
|
||||
- BPHX dual-circuit: independent circuit UA calibration factors.
|
||||
- Validation datasets (rated capacity/COP at AHRI/equivalent points) for each fixture.
|
||||
|
||||
Until provided, all product-specific fixture rows remain `Unknown`.
|
||||
119
docs/benchmarks/phase-0-baseline.md
Normal file
119
docs/benchmarks/phase-0-baseline.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Phase-0 Performance Baseline
|
||||
|
||||
This document records the Phase-0 benchmark results for `entropyk-solver`. All
|
||||
later Epic-1 speedup claims (NFR1 ≥2× speedup, NFR2 100% parity) are measured
|
||||
against these numbers.
|
||||
|
||||
## Environment
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Date | 2026-07-18 |
|
||||
| Host OS | Windows (x86_64) |
|
||||
| Rust toolchain | 1.89.0 (stable, pinned via `rust-toolchain.toml`) |
|
||||
| Cargo profile | bench (release) |
|
||||
| Linear algebra path | nalgebra 0.33 dense LU |
|
||||
|
||||
## How to Reproduce
|
||||
|
||||
```bash
|
||||
# Ensure the pinned toolchain is active
|
||||
cargo --version # should report 1.89.0
|
||||
|
||||
# Run the full benchmark suite
|
||||
cargo bench -p entropyk-solver
|
||||
```
|
||||
|
||||
## Benchmark Results
|
||||
|
||||
Results were produced by `cargo bench -p entropyk-solver` on the commit captured
|
||||
in the story frontmatter (`baseline_commit`).
|
||||
|
||||
### LU Solve (Dense)
|
||||
|
||||
| Benchmark | Mean time |
|
||||
|---|---|
|
||||
| `lu_solve_mock_9x9` | **425.54 ns** |
|
||||
|
||||
Measures one Ruiz-equilibrated dense LU solve on the 9×9 Jacobian assembled
|
||||
from the deterministic mock refrigeration cycle.
|
||||
|
||||
### Residual / Jacobian Assembly
|
||||
|
||||
| Benchmark | Mean time |
|
||||
|---|---|
|
||||
| `residual_assembly_mock` | **420.95 ns** |
|
||||
| `jacobian_assembly_mock` | **157.47 ns** |
|
||||
|
||||
Measures a single full-system pass of `compute_residuals` and
|
||||
`jacobian_entries` on the mock cycle.
|
||||
|
||||
### Full Solve — Reference Cycles
|
||||
|
||||
Each benchmark builds a real CoolProp-backed emergent-pressure R134a cycle
|
||||
(4 components, 9 unknowns) and solves it end-to-end with the fallback Newton
|
||||
solver.
|
||||
|
||||
| Benchmark | Mean time |
|
||||
|---|---|
|
||||
| `full_solve_reference_cycle_a` | **48.64 ms** |
|
||||
| `full_solve_reference_cycle_b` | **67.06 ms** |
|
||||
| `full_solve_reference_cycle_c` | **66.78 ms** |
|
||||
|
||||
### Sequential Batch Solve Scaling
|
||||
|
||||
| N | Mean time |
|
||||
|---|---|
|
||||
| 1 | **56.03 ms** |
|
||||
| 2 | **118.01 ms** |
|
||||
| 4 | **240.03 ms** |
|
||||
|
||||
This is the Phase-0 *sequential* baseline. Parallel batch solving is out of
|
||||
scope and belongs to Epic 4.
|
||||
|
||||
## Golden Snapshots
|
||||
|
||||
The golden regression test solves the three reference cycles and compares the
|
||||
converged state against committed canonical JSON snapshots. Run with
|
||||
`ENTROPYK_BLESS=1` to regenerate snapshots after an intentional change.
|
||||
|
||||
| Cycle | SHA-256 (canonical JSON) |
|
||||
|---|---|
|
||||
| `golden_cycle_a.json` | `073408dede4ad4622272e9c9375d9f4823e7984ec9e54910a9790229d52af899` |
|
||||
| `golden_cycle_b.json` | `c01e7cb892ed5829926c38ad5ee1423fdfaa7d262c473a8eca46ebc4632b9142` |
|
||||
| `golden_cycle_c.json` | `6e1a77958cce1d1cf5e730505912a887c8bf4fc7dcaed820c7da482aa0ad9f98` |
|
||||
|
||||
## Integration Corpus
|
||||
|
||||
The NFR2 parity corpus consists of **31** pre-existing integration test files in
|
||||
`crates/solver/tests/` (including `_scratch_debug.rs` and `_tmp_analytic.rs`).
|
||||
This story added one new file (`golden_snapshot.rs`), bringing the solver test
|
||||
count to **32**. The 31 pre-existing files must continue to pass unmodified as
|
||||
later Epic-1 stories land; `golden_snapshot.rs` is part of the Phase-0 safety
|
||||
net and must also remain green.
|
||||
|
||||
## Reproduction Commands
|
||||
|
||||
```bash
|
||||
# Ensure the pinned toolchain is active
|
||||
cargo --version # should report 1.89.0
|
||||
|
||||
# Full workspace test suite
|
||||
cargo test --workspace
|
||||
|
||||
# Solver integration corpus only (includes golden snapshot)
|
||||
cargo test -p entropyk-solver --tests
|
||||
|
||||
# Golden snapshot regression test specifically
|
||||
cargo test -p entropyk-solver --test golden_snapshot --features coolprop
|
||||
|
||||
# Regenerate snapshots after an intentional physics/solver change
|
||||
ENTROPYK_BLESS=1 cargo test -p entropyk-solver --test golden_snapshot --features coolprop
|
||||
|
||||
# Benchmark suite
|
||||
cargo bench -p entropyk-solver
|
||||
|
||||
# Lint/format gates
|
||||
cargo fmt --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
```
|
||||
137
docs/components/README.md
Normal file
137
docs/components/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Entropyk Component Documentation / Documentation des composants Entropyk
|
||||
|
||||
> Bilingual reference (EN + FR) for every component usable from the CLI JSON config and the web UI.
|
||||
> Référence bilingue (EN + FR) pour chaque composant CLI / UI.
|
||||
>
|
||||
> Each page documents: physical model, correlations (if any), residual equations, `n_equations()`,
|
||||
> ports, system vs rating secondary, calibration Z-factors, and JSON parameters with defaults.
|
||||
>
|
||||
> Chaque fiche documente : modèle, corrélations, résidus, ports, modes système/rating, facteurs Z, paramètres JSON.
|
||||
|
||||
**Last major doc refresh:** 2026-07-17 — dual-mode HX Newton, compressor maps (AHRI / SST–SDT), correlation inventory, UI Fixed/Free, fallback solver.
|
||||
|
||||
### Full correlation & map inventory
|
||||
|
||||
→ **[correlations-and-maps.md](./correlations-and-maps.md)** — AHRI 540, screw bilinear presets, Longo/Shah/…, ε-NTU, pumps/fans, ΔP.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
### State / État
|
||||
|
||||
- **State per edge:** `(ṁ, P, h)`. Series branches share one ṁ unknown (`same_branch_m`, CM1.4).
|
||||
- **État par arête :** `(ṁ, P, h)`. Branches en série → un seul ṁ.
|
||||
|
||||
### DoF (degrees of freedom)
|
||||
|
||||
A real-machine solve requires **`n_equations = n_unknowns`**.
|
||||
|
||||
- **FIX** = impose a quantity (boundary T/P/ṁ, outlet SH/SC, quality residual, measured calib target…) → +equation or pin.
|
||||
- **FREE** = solver unknown (emergent pressure, free opening, free `z_ua`, …).
|
||||
|
||||
CLI hard-fails on imbalance (`validate_system_dof`). Web UI: Fixed checkboxes + balance bar.
|
||||
|
||||
### System vs rating secondary (HX)
|
||||
|
||||
| Mode | Secondary definition | Secondary unknowns | Newton duty |
|
||||
|------|----------------------|--------------------|-------------|
|
||||
| **System** | Live ports `secondary_inlet` / `secondary_outlet` + Source/Sink | yes | ε-NTU Q from edge state |
|
||||
| **Rating** | Scalars `secondary_inlet_temp_*` + ṁ·cp or `C_sec` | no | ε-NTU Q from scalars **in residuals** |
|
||||
|
||||
Both modes are first-class for Condenser / Evaporator / FloodedEvaporator.
|
||||
Scalars are **not** limited to offline `rate()` only.
|
||||
|
||||
### Zero flow
|
||||
|
||||
Valid state. HX use `flow_regularization` (smooth `|ṁ|`, activity, Δh hold). See [flow-regularization.md](./flow-regularization.md).
|
||||
|
||||
### Emergent pressure
|
||||
|
||||
`emergent_pressure: true` lets condensing/evaporating pressure float from HX ↔ secondary energy balance instead of a fixed design pin.
|
||||
|
||||
### Calibration Z-factors (BOLT)
|
||||
|
||||
Default **1.0** = no correction. Typical range ~0.2–3 for inverse calib; production often ~0.8–1.2.
|
||||
|
||||
| Entropyk | BOLT | Effect |
|
||||
|----------|------|--------|
|
||||
| `z_flow` | `Z_flow_suc`, … | ṁ_eff = z_flow × ṁ_nom |
|
||||
| `z_dp` | `Z_dpc`, … | ΔP_eff = z_dp × ΔP_nom |
|
||||
| `z_ua` | `Z_UA`, `Z_Uev`, `Z_Ucd` | UA_eff = z_ua × UA_nom |
|
||||
| `z_power` | `Z_power` | Ẇ_eff = z_power × Ẇ_nom |
|
||||
| `z_etav` | — | η_v scale |
|
||||
|
||||
Legacy `f_*` and BOLT `Z_*` spellings accepted in JSON.
|
||||
Recommended order: `z_flow → z_dp → z_ua → z_power → z_etav`.
|
||||
|
||||
**UI calibration pattern:** Fixed on measure (SST/SDT) + Free on `z_ua` (do **not** require the Advanced “Regulation loop” node for simple Z_UA calib).
|
||||
|
||||
### Solver strategies (CLI)
|
||||
|
||||
| `solver.strategy` | Behaviour |
|
||||
|-------------------|-----------|
|
||||
| `newton` | Newton–Raphson |
|
||||
| `picard` | Sequential substitution |
|
||||
| `fallback` | Intelligent Newton → Picard (`FallbackSolver`) |
|
||||
|
||||
---
|
||||
|
||||
## Compressors / Compresseurs
|
||||
|
||||
- [IsentropicCompressor](./isentropic-compressor.md)
|
||||
- [ScrewEconomizerCompressor / ScrewCompressor](./screw-economizer-compressor.md)
|
||||
- [Compressor (AHRI 540)](./compressor-ahri540.md)
|
||||
|
||||
## Heat exchangers / Échangeurs
|
||||
|
||||
| Component | Model / correlations | Notes |
|
||||
|-----------|----------------------|--------|
|
||||
| [Condenser](./condenser.md) | ε-NTU phase-change | dual secondary modes |
|
||||
| [Evaporator](./evaporator.md) | ε-NTU DX + SH | dual secondary modes |
|
||||
| [FloodedEvaporator](./flooded-evaporator.md) | ε-NTU + sat-vapor / quality | dual secondary modes |
|
||||
| [FloodedCondenser](./flooded-condenser.md) | inner ε-NTU + SC control | prefer Condenser in production |
|
||||
| [BphxEvaporator / BphxCondenser](./bphx.md) | **Longo / Shah** → UA + ε-NTU | geometry + correlation |
|
||||
| [AirCooledCondenser](./air-cooled-condenser.md) | air-side coil | |
|
||||
| [FinCoilCondenser](./fin-coil-condenser.md) | finned coil | |
|
||||
| [MchxCondenserCoil](./mchx-condenser-coil.md) | microchannel | |
|
||||
| [HeatExchanger (generic)](./heat-exchanger-generic.md) | generic ε-NTU / LMTD | |
|
||||
| [FreeCoolingExchanger](./free-cooling-exchanger.md) | free cooling | |
|
||||
| [Economizer](./economizer.md) | internal LMTD HX | not always a CLI leaf |
|
||||
| [MovingBoundaryHX](./moving-boundary-hx.md) | multi-zone UA ID | research path |
|
||||
| [Flow regularization](./flow-regularization.md) | zero-flow helpers | shared |
|
||||
|
||||
## Valves & expansion
|
||||
|
||||
- [IsenthalpicExpansionValve / EXV](./isenthalpic-expansion-valve.md)
|
||||
- [ExpansionValve](./expansion-valve.md)
|
||||
- [ReversingValve](./reversing-valve.md)
|
||||
- [BypassValve](./bypass-valve.md)
|
||||
|
||||
## Flow network
|
||||
|
||||
- [FlowSplitter](./flow-splitter.md)
|
||||
- [FlowMerger](./flow-merger.md)
|
||||
- [Pipe](./pipe.md)
|
||||
- [Drum](./drum.md)
|
||||
|
||||
## Rotating machines
|
||||
|
||||
- [Fan](./fan.md)
|
||||
- [Pump](./pump.md)
|
||||
|
||||
## Boundaries
|
||||
|
||||
- [Refrigerant / Brine / Air Sources & Sinks](./boundaries.md)
|
||||
|
||||
## Inline nodes
|
||||
|
||||
- [Anchor & HeatSource](./anchor-heat-source.md)
|
||||
|
||||
## Inter-circuit coupling
|
||||
|
||||
- [ThermalLoad](./thermal-load.md)
|
||||
|
||||
---
|
||||
|
||||
See also: system capability notes under `docs/` and example machines in `crates/cli/examples/`.
|
||||
50
docs/components/air-cooled-condenser.md
Normal file
50
docs/components/air-cooled-condenser.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# AirCooledCondenser
|
||||
|
||||
Config type: `"AirCooledCondenser"`
|
||||
Source: air-cooled condenser / coil stack in components
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Air-cooled condenser: refrigerant condensation against outdoor air stream. Combines refrigerant-side phase-change energy balance with air-side capacity (fan flow × cp_air × effectiveness or coil model).
|
||||
|
||||
```
|
||||
Q = ε · C_air · (T_cond − T_air,in) # schematic ε-NTU air-side form
|
||||
```
|
||||
|
||||
May wrap or specialize `Condenser` with air secondary defaults.
|
||||
|
||||
### Residuals
|
||||
|
||||
Similar to Condenser coupled path: refrigerant energy/momentum + air secondary when live ports or rating air stream set.
|
||||
|
||||
### Ports
|
||||
|
||||
Refrigerant inlet/outlet + air secondary_in/out when 4-port.
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua` default **1.0**; fan speed may be free under head-pressure control.
|
||||
|
||||
### JSON
|
||||
|
||||
UA / coil geometry / OAT / face velocity / design capacity depending on arm — see CLI `create_component` and example air-cooled chillers.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Condenseur à air** : rejet de chaleur vers l’air extérieur.
|
||||
|
||||
### Calibration
|
||||
|
||||
Z_UA = 1 par défaut ; vitesse ventilateur possible en régulation.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir exemples CLI air-cooled.
|
||||
60
docs/components/anchor-heat-source.md
Normal file
60
docs/components/anchor-heat-source.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Anchor & HeatSource (inline BOLT-style nodes)
|
||||
|
||||
Config types: `"Anchor"` / `"RefrigerantNode"`, `"HeatSource"`
|
||||
Source: anchor / heat source modules in components
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Anchor (Refrigerant.Node)
|
||||
|
||||
Inline **probe or spec** on a refrigerant edge:
|
||||
|
||||
| Mode | Behaviour | DoF |
|
||||
|------|-----------|-----|
|
||||
| Probe (no key) | measures P/T/SH/SC — **DoF-neutral** | 0 equations |
|
||||
| Spec | imposes **one** of `superheat_k`, `quality`, `t_c`/`t_k`, `p_bar` | **+1 equation** |
|
||||
|
||||
When imposing, free something elsewhere (emergent pressure, free actuator, free boundary).
|
||||
|
||||
### HeatSource (Heat.Source)
|
||||
|
||||
Injects `q_w` (or `q_kw`) into the stream energy balance:
|
||||
|
||||
```
|
||||
ṁ · (h_out − h_in) = Q_heat
|
||||
```
|
||||
|
||||
Negative Q extracts heat. Can be linked as `cold_component` of a thermal coupling (motor cooling pattern).
|
||||
|
||||
### Ports
|
||||
|
||||
Inline on a single branch (inlet/outlet pass-through).
|
||||
|
||||
### Calibration
|
||||
|
||||
None required for probe mode.
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Component | Meaning |
|
||||
|-----|-----------|---------|
|
||||
| `superheat_k` / `quality` / `t_c` / `p_bar` | Anchor | one optional FIX |
|
||||
| `q_w` / `q_kw` | HeatSource | heat injection |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### Anchor
|
||||
|
||||
Sonde (0 DoF) ou **une** spécification (+1 équation) : SH, x, T ou P.
|
||||
|
||||
### HeatSource
|
||||
|
||||
Injection de chaleur `Q` dans le bilan d’énergie du fluide.
|
||||
|
||||
### DoF
|
||||
|
||||
Imposer une spec Anchor ⇒ libérer ailleurs.
|
||||
94
docs/components/boundaries.md
Normal file
94
docs/components/boundaries.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Boundaries — Sources & Sinks / Frontières
|
||||
|
||||
Config types: `RefrigerantSource`, `RefrigerantSink`, `BrineSource`, `BrineSink`, `AirSource`, `AirSink`
|
||||
Sources: `refrigerant_boundary.rs`, `brine_boundary.rs`, `air_boundary.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
Boundary components fix **Dirichlet** conditions on one edge.
|
||||
**Source** = one outlet; **Sink** = one inlet. These are the natural place to **FIX** machine inputs (T, P, ṁ).
|
||||
|
||||
### RefrigerantSource / RefrigerantSink
|
||||
|
||||
```
|
||||
Source: P = P_set ; h = h(P, x) n ≈ 2
|
||||
Sink: P = P_back ; optional h if x set n ≈ 1–2
|
||||
```
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `fluid` | refrigerant | primary |
|
||||
| `p_set_bar` / `p_back_bar` | pressure | ~10 bar typical |
|
||||
| `quality` | vapor quality | 1.0 source |
|
||||
|
||||
### BrineSource / BrineSink (water / glycol)
|
||||
|
||||
```
|
||||
Source: P, h(T), optional ṁ_set n = 2 or 3
|
||||
Sink: P_back, optional T/h, ṁ n = 1–3
|
||||
```
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `fluid` | Water / MEG / … | Water |
|
||||
| `p_set_bar` / `p_back_bar` | pressure | 2 bar |
|
||||
| `t_set_c` | temperature | 12 °C (source) |
|
||||
| `concentration` | glycol mass % | 0 |
|
||||
| `m_flow_kg_s` | imposed loop flow (BOLT `Vd_fixed`) | optional |
|
||||
|
||||
**Do not** combine `m_flow_kg_s` with another flow imposition on the same branch (pump curve + fixed ṁ → over-constrained).
|
||||
|
||||
### AirSource / AirSink
|
||||
|
||||
Psychrometric state (Magnus–Tetens style humidity + moist air enthalpy):
|
||||
|
||||
```
|
||||
h ≈ 1006·T_c + W·(2.501e6 + 1860·T_c)
|
||||
Source: fix P, h(T, RH) n = 2
|
||||
```
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `t_dry_c` / `t_set_c` | dry-bulb | – |
|
||||
| `rh` | relative humidity | – |
|
||||
| `p_set_bar` | pressure | ~1 bar |
|
||||
| `m_flow_kg_s` | optional mass flow | – |
|
||||
|
||||
### System wiring for HX secondary
|
||||
|
||||
```
|
||||
BrineSource.outlet → HX.secondary_inlet
|
||||
HX.secondary_outlet → BrineSink.inlet
|
||||
```
|
||||
|
||||
Without live wiring, HX may still run in **rating** mode with scalar secondary_* on the HX itself.
|
||||
|
||||
### Calibration
|
||||
|
||||
Boundaries generally have **no Z-factors**. They are pure Fixed inputs / back-pressure.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### Rôle
|
||||
|
||||
Imposent les **conditions aux limites** (P, T, ṁ). C’est là qu’on **fixe** les entrées machine.
|
||||
|
||||
### Eau (Brine)
|
||||
|
||||
Source : P, T, ṁ optionnel. Sink : contre-pression (T sortie souvent **libre** = émergente).
|
||||
|
||||
### Air
|
||||
|
||||
État psychrométrique (T sèche, HR → h).
|
||||
|
||||
### Câblage HX
|
||||
|
||||
Source → secondary_in → secondary_out → Sink pour le mode système.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir tableaux EN.
|
||||
119
docs/components/bphx.md
Normal file
119
docs/components/bphx.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# BphxEvaporator / BphxCondenser (Brazed Plate HX)
|
||||
|
||||
Config types: `"BphxEvaporator"`, `"BphxCondenser"`
|
||||
Source: `crates/components/src/heat_exchanger/bphx_evaporator.rs`, `bphx_condenser.rs`, shared geometry/correlation helpers
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Brazed-plate HX with **geometry + two-phase correlation → h → UA estimate**, then runtime solve on an **inner ε-NTU** residual model.
|
||||
|
||||
#### Correlations (selectable)
|
||||
|
||||
Default **Longo 2004**. Also **Shah 1979**, **Shah 2021**.
|
||||
|
||||
Full registry (also Kandlikar, Gungor–Winterton, Gnielinski, Dittus–Boelter, Ko 2021, Friedel ΔP): see [correlations-and-maps.md](./correlations-and-maps.md).
|
||||
|
||||
Equivalent Reynolds construction (schematic):
|
||||
|
||||
```
|
||||
Re_l = G · d_h / μ_l
|
||||
Re_eq = Re_l · (1 − x + x · √(ρ_l / ρ_v))
|
||||
```
|
||||
|
||||
Longo-style Nu (illustrative forms used in the implementation path):
|
||||
|
||||
```
|
||||
Evaporation: Nu ~ f(Re_eq, Pr_l) (e.g. 0.05 · Re_eq^0.8 · Pr_l^0.33)
|
||||
Condensation: Nu ~ f(Re_eq, Pr_l, ρ*) (e.g. 1.875 · Re_eq^0.35 · Pr_l^0.33 · …)
|
||||
h = Nu · k_l / d_h
|
||||
UA_est = h · A · z_ua
|
||||
```
|
||||
|
||||
Pressure drop (schematic):
|
||||
|
||||
```
|
||||
ΔP = z_dp · 2 · f · L · G² / (ρ · d_h)
|
||||
```
|
||||
|
||||
**Important:** the **Newton system residuals** for the component are the **inner ε-NTU** residual set (`n_equations` of the inner model, typically 2 for the base HX path). The correlation updates **UA** (when `update_ua_from_htc` / geometry path is engaged); it is **not** a full multi-zone moving-boundary residual stack.
|
||||
|
||||
### Modes / targets
|
||||
|
||||
| Type | Mode | Notes |
|
||||
|------|------|--------|
|
||||
| `BphxEvaporator` | **DX only** | Outlet is superheated vapor. `target_superheat_k` (default 5 K) is diagnostic/target storage — not a flooded shell model. For flooded shell-and-tube use `FloodedEvaporator`. |
|
||||
| `BphxCondenser` | Subcooling target | `target_subcooling_k` (default 3 K) |
|
||||
|
||||
### Ports
|
||||
|
||||
4-port Modelica-style naming in the system graph when wired:
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| `inlet` / `outlet` | Refrigerant |
|
||||
| `secondary_inlet` / `secondary_outlet` | Secondary fluid |
|
||||
|
||||
Geometry fields: plate length/width, thickness, chevron, channel spacing, optional `dh_m` / `area_m2` overrides.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `z_ua` / `Z_UA` | UA scale | **1.0** |
|
||||
| `z_dp` / `Z_dpc` | ΔP scale | **1.0** |
|
||||
| `ua` explicit | sets `z_ua = ua / UA_nom` | – |
|
||||
|
||||
Legacy `f_ua` / `f_dp` accepted in JSON.
|
||||
|
||||
### JSON parameters (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `n_plates` | plate count | 20 |
|
||||
| `plate_length_m` / `plate_width_m` | geometry | – |
|
||||
| `chevron_angle_deg` | chevron | 60 |
|
||||
| `correlation` | Longo2004 / Shah1979 / Shah2021 | Longo2004 |
|
||||
| `target_superheat_k` | DX target (evap) | 5 K |
|
||||
| `target_subcooling_k` | SC target (cond) | 3 K |
|
||||
| `refrigerant` / `secondary_fluid` | fluids | – |
|
||||
| `z_ua`, `z_dp` | calib | 1.0 |
|
||||
|
||||
### DoF / system usage
|
||||
|
||||
Prefer live secondary wiring for closed loops. Pair `z_ua` free + measured SST/SDT for inverse calibration (same Fixed/Free discipline as other HX).
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
Échangeurs **à plaques brasées** : géométrie + **corrélation biphasique** (Longo 2004 / Shah) → coefficient h → UA, puis solveur sur modèle **ε-NTU interne**.
|
||||
|
||||
Formes types :
|
||||
|
||||
```
|
||||
Re_eq = Re_l · (1 − x + x · √(ρ_l/ρ_v))
|
||||
Nu = f(Re_eq, Pr, …) # Longo / Shah selon `correlation`
|
||||
h = Nu · k / d_h
|
||||
UA = h · A · z_ua
|
||||
```
|
||||
|
||||
Le **Newton** ne résout pas la corrélation plaque par plaque : il résout le **HX ε-NTU** ; la corrélation **calibre/estime UA**.
|
||||
|
||||
### Modes
|
||||
|
||||
- **BphxEvaporator** : DX uniquement (pas un flooded shell).
|
||||
- **BphxCondenser** : cible de sous-refroidissement.
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua = 1`, `z_dp = 1` par défaut. Alias BOLT `Z_UA`, `Z_dpc`.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Voir tableaux EN.
|
||||
53
docs/components/bypass-valve.md
Normal file
53
docs/components/bypass-valve.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# BypassValve
|
||||
|
||||
Config type: `"BypassValve"`
|
||||
Source: `crates/components/src/bypass_valve.rs` (or valve module)
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Bypass leg valve with opening characteristic (linear / equal-percentage / custom). Parallel path around a component (compressor, HX, etc.).
|
||||
|
||||
```
|
||||
ṁ = f(opening, ΔP, kv, characteristic)
|
||||
h_out ≈ h_in
|
||||
```
|
||||
|
||||
### Residuals
|
||||
|
||||
Flow residual + energy (isenthalpic or low Δh).
|
||||
|
||||
### Ports
|
||||
|
||||
`inlet` / `outlet`.
|
||||
|
||||
### Actuator
|
||||
|
||||
`opening` ∈ [0, 1] — free when under control.
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `opening` | 0–1 | 0–1 |
|
||||
| `kv` | capacity | – |
|
||||
| characteristic | linear / … | linear |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Vanne de by-pass** sur une branche parallèle.
|
||||
|
||||
### Actionneur
|
||||
|
||||
Ouverture 0–1.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir EN.
|
||||
131
docs/components/compressor-ahri540.md
Normal file
131
docs/components/compressor-ahri540.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Compressor (AHRI 540 + maps)
|
||||
|
||||
Config type: `"Compressor"`
|
||||
Source: `crates/components/src/compressor.rs`
|
||||
Related: `polynomials.rs` (Polynomial2D), registry `SstSdt` model variant
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose
|
||||
|
||||
Positive-displacement compressor performance from **published coefficient maps**:
|
||||
|
||||
1. **AHRI 540** (CLI default for `"Compressor"`) — 10 coefficients M1–M10
|
||||
2. **SST/SDT polynomial** (API / registry) — 2D polynomials ṁ(SST,SDT), Ẇ(SST,SDT)
|
||||
|
||||
### Model A — AHRI 540
|
||||
|
||||
**Mass flow [kg/s]:**
|
||||
|
||||
```
|
||||
ṁ = M1 · (1 − (P_suc / P_dis)^(1/M2)) · ρ_suc · V_disp · N/60
|
||||
```
|
||||
|
||||
**Power cooling [W]:**
|
||||
|
||||
```
|
||||
Ẇ = M3 + M4 · (P_dis/P_suc) + M5 · T_suc + M6 · T_dis
|
||||
```
|
||||
|
||||
**Power heating [W]:**
|
||||
|
||||
```
|
||||
Ẇ = M7 + M8 · (P_dis/P_suc) + M9 · T_suc + M10 · T_dis
|
||||
```
|
||||
|
||||
| Coeff | Role | Typical CLI default |
|
||||
|-------|------|---------------------|
|
||||
| M1 | flow scale | 0.85 |
|
||||
| M2 | PR exponent (>0) | 2.5 |
|
||||
| M3–M6 | cooling power poly | 500, 1500, −2.5, 1.8 |
|
||||
| M7–M10 | heating power poly | 600, 1600, −3.0, 2.0 |
|
||||
|
||||
Also required: `speed_rpm`, `displacement_m3`, `efficiency` (isentropic / overall as used by the arm).
|
||||
|
||||
### Model B — SST/SDT polynomial (same `Compressor` type)
|
||||
|
||||
Select with JSON / UI: `"model_type": "SstSdt"` (aliases: `SstSdtPolynomial`, `sst_sdt`).
|
||||
|
||||
```
|
||||
ṁ = Σ a_ij · SST^i · SDT^j [kg/s] (SST, SDT in Kelvin)
|
||||
Ẇ = Σ b_ij · SST^i · SDT^j [W]
|
||||
```
|
||||
|
||||
Bilinear form (CLI / UI coefficients):
|
||||
|
||||
```
|
||||
ṁ = a00 + a10·SST + a01·SDT + a11·SST·SDT
|
||||
Ẇ = b00 + b10·SST + b01·SDT + b11·SST·SDT
|
||||
```
|
||||
|
||||
| JSON key | Role | Default (example) |
|
||||
|----------|------|-------------------|
|
||||
| `mf_a00` … `mf_a11` | mass-flow bilinear | 0.05, 0.001, 0.0005, 1e−5 |
|
||||
| `pw_b00` … `pw_b11` | power bilinear | 1000, 50, 30, 0.5 |
|
||||
|
||||
Also used by **ScrewEconomizerCompressor** (same bilinear form + eco fraction + presets Bitzer/Grasso).
|
||||
|
||||
### Residuals / ports
|
||||
|
||||
Two-port suction/discharge. Residual count depends on same-branch mass and model wiring (typically flow + energy).
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Default | Effect |
|
||||
|--------|---------|--------|
|
||||
| `z_flow` | **1.0** | scales ṁ |
|
||||
| `z_power` | **1.0** | scales Ẇ |
|
||||
|
||||
### JSON (CLI `"Compressor"`)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `model_type` | `Ahri540` \| `SstSdt` | `Ahri540` |
|
||||
| `speed_rpm` | speed | **required** |
|
||||
| `displacement_m3` | displacement | **required** |
|
||||
| `efficiency` | efficiency | 0.85 |
|
||||
| `fluid` | refrigerant | required |
|
||||
| `m1` … `m10` | AHRI coeffs (if Ahri540) | see table |
|
||||
| `mf_a00`…`mf_a11`, `pw_b00`…`pw_b11` | SST/SDT bilinear (if SstSdt) | see table |
|
||||
| `p_suction_bar` / `h_suction_kj_kg` | init ports | 3.5 / 400 |
|
||||
| `p_discharge_bar` / `h_discharge_kj_kg` | init ports | 12 / 440 |
|
||||
|
||||
### UI guidance
|
||||
|
||||
- **Modèle de carte** : bascule Ahri540 ↔ SstSdt
|
||||
- Sections **AHRI 540** ou **Map SST/SDT** selon le choix
|
||||
- Section **Machine** : speed, displacement, efficiency
|
||||
- L’**IsentropicCompressor** est un modèle physique différent (η_is + cylindrée)
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Compresseur à **cartes de performance** constructeur.
|
||||
|
||||
### AHRI 540
|
||||
|
||||
```
|
||||
ṁ = M1 · (1 − (P_s/P_d)^{1/M2}) · ρ · V · N/60
|
||||
Ẇ = M3 + M4·PR + M5·T_s + M6·T_d (froid)
|
||||
```
|
||||
|
||||
### Polynôme SST/SDT
|
||||
|
||||
```
|
||||
ṁ, Ẇ = polynôme 2D en SST et SDT
|
||||
```
|
||||
|
||||
(Utilisé surtout sur le **vis** ; presets Bitzer/Grasso.)
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_flow`, `z_power` = **1.0** par défaut.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir tableau EN (`m1`…`m10`, `speed_rpm`, `displacement_m3`).
|
||||
124
docs/components/condenser.md
Normal file
124
docs/components/condenser.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Condenser / CondenserCoil
|
||||
|
||||
Config types: `"Condenser"`, `"CondenserCoil"`
|
||||
Source: `crates/components/src/heat_exchanger/condenser.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
Refrigerant **condenser** rejecting heat to a secondary stream (water/glycol or air). Coupled duty is **phase-change ε-NTU** (isothermal refrigerant side at `T_cond(P)`):
|
||||
|
||||
```
|
||||
ε = 1 − exp(−UA_eff / C_sec)
|
||||
Q = ε · C_sec · (T_cond(P_in) − T_sec,in) # heat rejected by refrigerant
|
||||
```
|
||||
|
||||
- Optional lumped refrigerant ΔP: `ΔP = k · ṁ · |ṁ|`
|
||||
- `CondenserCoil` locks secondary side to **Air** conventions
|
||||
- **No plate correlation** here (see BPHX for Longo/Shah geometry UA)
|
||||
|
||||
`UA_eff` can be reduced by flooded-level actuator; `C_sec` can be scaled by fan speed φ when fan head-pressure is active.
|
||||
|
||||
### Dual secondary modes (Newton)
|
||||
|
||||
| Mode | Secondary source | `n_secondary` |
|
||||
|------|------------------|---------------|
|
||||
| **System** | Live edges ports 2/3 (`secondary_inlet` / `secondary_outlet`) | 1 or 2 |
|
||||
| **Rating** | Scalars `secondary_inlet_temp_*` + capacity rate / ṁ·cp | 0 |
|
||||
|
||||
`coupled_ready` requires refrigerant indices **and** (live edges **or** rating scalars).
|
||||
`live_secondary_stream` prefers edges; falls back to rating scalars (with fan φ scaling of `C_sec` when applicable).
|
||||
|
||||
### Residuals & `n_equations()` (coupled)
|
||||
|
||||
| Row | Equation |
|
||||
|-----|----------|
|
||||
| r0 | `P_out − (P_in − ΔP)` (skippable) |
|
||||
| r1 | `ṁ · (h_in − h_out) − Q` |
|
||||
| r2 (emergent) | `h_out − h(P, T_cond − SC)` subcooling closure |
|
||||
| r_mass | `ṁ_out − ṁ_in` if not same-branch |
|
||||
| r_head (optional) | `T_cond − T_target` (fan **or** flooded head-pressure) |
|
||||
| r_sec | live secondary mass/energy only if edges present |
|
||||
|
||||
```
|
||||
n_equations = n_thermo + (mass?) + (head?) + n_secondary
|
||||
n_thermo = 2 normally, 3 with emergent_pressure (+ subcooling residual)
|
||||
```
|
||||
|
||||
### Emergent pressure & actuators
|
||||
|
||||
- `emergent_pressure: true` + `subcooling_k` → condensing pressure is **solved**, not fixed by design T
|
||||
- **Fan head-pressure:** free φ scales `C_sec = φ · C_nominal`; residual pins `T_cond`
|
||||
- **Flooded head-pressure:** free level λ scales `UA_eff`; mutually exclusive with fan
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Index |
|
||||
|------|-------|
|
||||
| `inlet` / `outlet` | 0 / 1 refrigerant |
|
||||
| `secondary_inlet` / `secondary_outlet` | 2 / 3 secondary |
|
||||
|
||||
System wiring: Source → secondary_in → secondary_out → Sink.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Meaning | Default |
|
||||
|--------|---------|---------|
|
||||
| `z_ua` | UA scale | **1.0** |
|
||||
| `z_dp` | ΔP scale | 1.0 |
|
||||
| `z_flow` / `z_power` / `z_etav` | via shared Calib API | 1.0 |
|
||||
|
||||
UI: Fixed on SDT target + free `z_ua` for inverse calibration.
|
||||
|
||||
### JSON parameters (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `ua` | UA [W/K] | required |
|
||||
| `emergent_pressure` | free P_cond | false |
|
||||
| `subcooling_k` | outlet SC [K] | 5 |
|
||||
| `secondary_fluid` | Water / Air / … | – |
|
||||
| `secondary_inlet_temp_c` / mass_flow / cp | rating stream | – |
|
||||
| `pressure_drop_coeff` | k for ΔP | – |
|
||||
| `fan_head_pressure_target_c` | fan control | – |
|
||||
| `flooded_head_pressure_target_c` | level control | – |
|
||||
| `skip_pressure_eq` | drop r0 | false |
|
||||
|
||||
### Zero flow
|
||||
|
||||
Live `C_sec` uses `smooth_mass_magnitude(|ṁ|)`. Mass-flow index never remapped to a pressure column.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
Condenseur frigo → secondaire (eau/air). Duty **ε-NTU** :
|
||||
|
||||
```
|
||||
Q = ε · C_sec · (T_cond(P) − T_sec,in)
|
||||
```
|
||||
|
||||
Pas de corrélation plaques (voir BPHX). UA global ± actionneurs fan/niveau.
|
||||
|
||||
### Modes secondaire
|
||||
|
||||
- **Système :** ports live Source/Sink
|
||||
- **Rating :** scalaires T + ṁ·cp **dans le Newton** (pas seulement `rate()`)
|
||||
|
||||
### Pression émergente
|
||||
|
||||
`emergent_pressure` + sous-refroidissement : `P_cond` est **calculée**.
|
||||
Fan ou flooded head-pressure = +1 actionneur libre.
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua = 1` par défaut. Imposer SDT + libérer Z_UA pour caler le condenseur.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Voir tableaux EN.
|
||||
143
docs/components/correlations-and-maps.md
Normal file
143
docs/components/correlations-and-maps.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Correlations & performance maps / Corrélations & cartes
|
||||
|
||||
Master inventory of **every performance map and heat-transfer / pressure-drop correlation** wired in Entropyk (as of 2026-07-17).
|
||||
Inventaire de **toutes** les cartes et corrélations du code.
|
||||
|
||||
Sources principales :
|
||||
|
||||
- `crates/components/src/compressor.rs` — AHRI 540 + SST/SDT
|
||||
- `crates/components/src/screw_economizer_compressor.rs` — polynômes 2D + presets
|
||||
- `crates/components/src/isentropic_compressor.rs` — η_is + volumétrique
|
||||
- `crates/components/src/polynomials.rs` — Polynomial1D / Polynomial2D
|
||||
- `crates/components/src/heat_exchanger/bphx_correlation.rs` — formules h
|
||||
- `crates/components/src/heat_exchanger/correlation_registry.rs` — catalogue + domaines
|
||||
- `crates/components/src/heat_exchanger/eps_ntu.rs` / `lmtd.rs` — HX génériques
|
||||
- `crates/components/src/heat_exchanger/two_phase_dp.rs` — ΔP biphasique
|
||||
- `crates/components/src/fan.rs` / `pump.rs` — courbes 1D
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### 1. Compressors — performance maps
|
||||
|
||||
| Component | Model ID | Formula (summary) | Inputs | Outputs | UI / JSON |
|
||||
|-----------|----------|-------------------|--------|---------|-----------|
|
||||
| **IsentropicCompressor** | Physics + η_is | `h_dis = h_suc + (h_is−h_suc)/η_is` ; emergent: `ṁ = ρ·V_d·N·η_vol·z_flow` | η_is, T guesses, V_d, N | ṁ, h_dis, W | η, emergent, displacement, speed |
|
||||
| **Compressor** | **AHRI 540** (`model_type=Ahri540`) | `ṁ = M1·(1−(P_s/P_d)^{1/M2})·ρ·V·N/60` ; `Ẇ_cool = M3+M4·PR+M5·T_s+M6·T_d` (heating M7–M10) | M1…M10, V, N | ṁ, Ẇ | `m1`…`m10`, `speed_rpm`, `displacement_m3` |
|
||||
| **Compressor** | **SST/SDT poly** (`model_type=SstSdt`) | `ṁ = a00+a10·SST+a01·SDT+a11·SST·SDT` ; same for Ẇ with `pw_b**` | bilinear coeffs | ṁ, Ẇ | `mf_a**`, `pw_b**` (CLI + UI) |
|
||||
| **ScrewEconomizerCompressor** | **Bilinear SST/SDT** | `ṁ_suc = z_flow·(a00+a10·SST+a01·SDT+a11·SST·SDT)` ; same for Ẇ with b_ij ; eco fraction poly | presets + overrides | ṁ_suc, ṁ_eco, Ẇ | `preset`, `mf_a**`, `pw_b**` |
|
||||
|
||||
#### Screw presets (CLI)
|
||||
|
||||
| Preset | ṁ (a00,a10,a01,a11) | Power (b00,b10,b01,b11) | eco frac |
|
||||
|--------|---------------------|-------------------------|----------|
|
||||
| `bitzer_generic_200kw` | 1.35, 0.004, −0.0025, 1.2e−5 | 58000, 180, −280, 0.4 | 0.13 |
|
||||
| `grasso_generic_200kw` | 1.30, 0.0035, −0.0022, 1e−5 | 60000, 190, −310, 0.45 | 0.11 |
|
||||
| (none) | 1.2, 0.003, −0.002, 1e−5 | 55000, 200, −300, 0.5 | 0.12 |
|
||||
|
||||
Temps in polynomials: **SST / SDT** as used by the curve implementation (see source; typically °C in manufacturer fits — verify against `Polynomial2D` evaluation units in code).
|
||||
|
||||
#### Calibration Z on compressors
|
||||
|
||||
| Factor | Effect |
|
||||
|--------|--------|
|
||||
| `z_flow` | scales ṁ |
|
||||
| `z_flow_eco` | scales economizer ṁ (screw) |
|
||||
| `z_power` | scales shaft power |
|
||||
| `z_etav` | volumetric efficiency correction |
|
||||
|
||||
Default all **1.0**.
|
||||
|
||||
---
|
||||
|
||||
### 2. Heat exchangers — heat transfer correlations
|
||||
|
||||
| Correlation ID | Year | Purpose | Geometry | Wired in BPHX UI? |
|
||||
|----------------|------|---------|----------|-------------------|
|
||||
| **Longo2004** | 2004 | Evap / cond HTC (plates) | Brazed plate | **Yes** (default) |
|
||||
| **Shah1979** | 1979 | Condensation HTC | Tubes (also selectable) | **Yes** |
|
||||
| **Shah2021** | 2021 | Plate condensation | Plates | **Yes** |
|
||||
| Kandlikar1990 | 1990 | Evaporation HTC | Tubes | Registry / BPHX enum |
|
||||
| GungorWinterton1986 | 1986 | Evaporation HTC | Tubes | Registry |
|
||||
| Gnielinski1976 | 1976 | Single-phase turbulent Nu | Tubes | Registry |
|
||||
| DittusBoelter1930 | 1930 | Single-phase Nu (simple) | Tubes | Registry |
|
||||
| Ko2021 | 2021 | Low-GWP plates | Plates | Registry |
|
||||
| Friedel1979 | 1979 | Two-phase ΔP | Tubes/plates | Registry (ΔP) |
|
||||
|
||||
**BPHX runtime path:** correlation → h → `UA ≈ h·A·z_ua` → **ε-NTU residuals** (not a full multi-zone MB model).
|
||||
|
||||
**Condenser / Evaporator / FloodedEvaporator:** **no** plate correlation — **lumped UA** + phase-change ε-NTU:
|
||||
|
||||
```
|
||||
ε = 1 − exp(−UA/C_sec)
|
||||
Q = ε · C_sec · ΔT_driving
|
||||
```
|
||||
|
||||
**Generic HeatExchanger:** ε-NTU or LMTD (arrangement-dependent).
|
||||
**Economizer (internal):** LMTD-style two-stream.
|
||||
**MovingBoundaryHX:** multi-zone research path (not default production).
|
||||
|
||||
---
|
||||
|
||||
### 3. Pressure drop
|
||||
|
||||
| Model | Formula / role | Components |
|
||||
|-------|----------------|------------|
|
||||
| Quadratic refrigerant | `ΔP = k · ṁ · \|ṁ\|` | Condenser, Evaporator (optional) |
|
||||
| BPHX friction | `ΔP = z_dp · 2·f·L·G²/(ρ·d_h)` (implementation path) | BPHX |
|
||||
| Friedel 1979 | two-phase ΔP (registry) | selection stack |
|
||||
| Pipe Darcy-style | f(L,D,ε,Re,ṁ) | Pipe |
|
||||
| Valve orifice | `ṁ = Kv·opening·√(2·ρ·ΔP)` | EXV orifice, BypassValve |
|
||||
|
||||
---
|
||||
|
||||
### 4. Pumps & fans — 1D polynomials
|
||||
|
||||
```
|
||||
y = c0 + c1·x + c2·x² + … (Polynomial1D)
|
||||
```
|
||||
|
||||
- **Pump:** head H(Q), efficiency η(Q); affinity laws for speed.
|
||||
- **Fan:** static pressure / power vs flow and speed.
|
||||
|
||||
---
|
||||
|
||||
### 5. Flow regularization (not a HTC correlation)
|
||||
|
||||
Smooth `|ṁ|`, activity α, duty blend — keeps Newton finite at zero flow. See [flow-regularization.md](./flow-regularization.md).
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### Compresseurs
|
||||
|
||||
| Composant | Modèle | Formule clé |
|
||||
|-----------|--------|-------------|
|
||||
| Isentropic | Physique + η_is | h_dis isentropique corrigé ; ṁ = ρ V N η_vol |
|
||||
| Compressor | **AHRI 540** M1–M10 | ṁ(P,ρ,V,N) ; Ẇ(PR, T) |
|
||||
| Screw | **Polynôme bilinéaire SST/SDT** | ṁ, W = a00+a10·SST+a01·SDT+a11·SST·SDT |
|
||||
|
||||
Presets vis : Bitzer / Grasso génériques 200 kW (coeffs dans CLI).
|
||||
|
||||
### Échangeurs — corrélations h
|
||||
|
||||
| Corrélation | Usage |
|
||||
|-------------|--------|
|
||||
| Longo 2004 | BPHX défaut évap/cond plaques |
|
||||
| Shah 1979 / 2021 | condensation (tubes / plaques) |
|
||||
| Kandlikar, Gungor–Winterton | évaporation tubes (registre) |
|
||||
| Gnielinski, Dittus–Boelter | monophasique |
|
||||
| Ko 2021 | plaques low-GWP |
|
||||
| Friedel 1979 | ΔP biphasique |
|
||||
|
||||
**Condenser / Evaporator / Flooded :** **UA global + ε-NTU** (pas Longo).
|
||||
|
||||
### Pompes / ventilateurs
|
||||
|
||||
Polynômes 1D Q–H / Q–η + lois d’affinité.
|
||||
|
||||
### Calibration
|
||||
|
||||
Tous les Z par défaut **1.0** (pas de correction).
|
||||
62
docs/components/drum.md
Normal file
62
docs/components/drum.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Drum (separator / recirculation drum)
|
||||
|
||||
Config type: `"Drum"`
|
||||
Source: `crates/components/src/drum.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Liquid/vapor **separator** used in flooded recirculation architectures. Splits a two-phase feed into liquid and vapor outlets; may accept an evaporator return.
|
||||
|
||||
Thermodynamics: equilibrium separation at drum pressure (quality split toward x≈0 liquid / x≈1 vapor legs), mass and energy balances across ports.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Multi-port balance residuals (mass + energy + pressure consistency). Exact count depends on active ports and edge wiring; treat as a multi-equation node — see unit tests and `n_equations()` in source.
|
||||
|
||||
### Ports (4-port naming)
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| `feed_inlet` | two-phase feed |
|
||||
| `evaporator_return` | return from flooded evaporator |
|
||||
| `liquid_outlet` | liquid to pump / recirculation |
|
||||
| `vapor_outlet` | vapor to compressor suction |
|
||||
|
||||
### Calibration
|
||||
|
||||
No primary Z-factor set; geometry/level control may be added in specialized builds.
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `fluid` / refrigerant | working fluid | primary |
|
||||
| level / volume options | if exposed | – |
|
||||
|
||||
### System note
|
||||
|
||||
A **flooded plate** topology is often Drum + recirculation + DX exchanger — not a mode of BPHX alone. Shell-and-tube flooded use `FloodedEvaporator`.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
**Ballon séparateur** liquide/vapeur pour architectures noyées / recirculation.
|
||||
|
||||
### Ports
|
||||
|
||||
Alimentation, retour évap, sortie liquide, sortie vapeur.
|
||||
|
||||
### DoF
|
||||
|
||||
Nœud multi-équations ; équilibrer avec le reste du circuit.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir EN.
|
||||
37
docs/components/economizer.md
Normal file
37
docs/components/economizer.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Economizer (internal)
|
||||
|
||||
Source: economizer HX used inside screw-economizer circuits (`HeatExchanger<LmtdModel>` patterns)
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Internal heat exchanger that subcools liquid / evaporates injection gas for economized screw cycles. Often **not** a standalone CLI leaf — instantiated inside compressor economizer plumbing or macro components.
|
||||
|
||||
Model: LMTD or ε-NTU between liquid line and eco vapor.
|
||||
|
||||
### Residuals
|
||||
|
||||
Standard two-stream HX residuals of the inner model.
|
||||
|
||||
### Ports
|
||||
|
||||
Hot/cold legs as wired by the parent circuit.
|
||||
|
||||
### Note
|
||||
|
||||
For user-facing machines, configure economizer via **ScrewEconomizerCompressor** + circuit topology rather than a free-floating Economizer node unless the CLI arm exposes it.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Échangeur **économiseur** interne (sous-refroidissement / injection).
|
||||
|
||||
### Note
|
||||
|
||||
Souvent intégré au circuit vis, pas un composant CLI autonome.
|
||||
123
docs/components/evaporator.md
Normal file
123
docs/components/evaporator.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Evaporator / EvaporatorCoil
|
||||
|
||||
Config types: `"Evaporator"`, `"EvaporatorCoil"`
|
||||
Source: `crates/components/src/heat_exchanger/evaporator.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
**DX (direct-expansion)** evaporator: refrigerant outlet is **superheated vapor** (not flooded two-phase). Phase-change ε-NTU against a hot secondary stream:
|
||||
|
||||
```
|
||||
ε = 1 − exp(−UA / C_sec)
|
||||
Q = ε · C_sec · (T_sec,in − T_evap(P)) # heat absorbed by refrigerant
|
||||
```
|
||||
|
||||
- Optional refrigerant ΔP = k·ṁ·|ṁ|
|
||||
- `EvaporatorCoil` locks secondary side to **Air**
|
||||
- **No plate correlation** (see BPHX / Longo–Shah for geometry-based UA)
|
||||
|
||||
Difference vs `FloodedEvaporator`: DX uses **superheat closure** (or regulated SH); flooded uses **saturated vapor** (or quality) by default.
|
||||
|
||||
### Dual secondary modes (Newton)
|
||||
|
||||
| Mode | Secondary | `n_secondary` |
|
||||
|------|-----------|---------------|
|
||||
| **System** | Live `secondary_inlet` / `secondary_outlet` | 1 or 2 |
|
||||
| **Rating** | Scalars T_sec + C_sec (ṁ·cp) | 0 |
|
||||
|
||||
`coupled_ready` = refrigerant ready **and** (live edges **or** rating scalars).
|
||||
`live_secondary_stream` = edges first, else rating scalars.
|
||||
|
||||
### Residuals & `n_equations()` (coupled emergent)
|
||||
|
||||
| Row | Equation |
|
||||
|-----|----------|
|
||||
| r0 | `P_out − (P_in − ΔP)` (optional skip) |
|
||||
| r1 | `ṁ · (h_out − h_in) − Q` |
|
||||
| r2 | `h_out − h(P, T_evap+SH)` if superheat is imposed |
|
||||
| r_mass | dropped if same-branch |
|
||||
| r_sec | live secondary mass/energy if edges |
|
||||
|
||||
```
|
||||
n_thermo = base (1 or 2) + 1 if imposes_superheat()
|
||||
n_equations = n_thermo + mass? + n_secondary
|
||||
```
|
||||
|
||||
### Superheat regulation (DoF)
|
||||
|
||||
| Setting | Effect |
|
||||
|---------|--------|
|
||||
| Default | SH residual active (`superheat_k` target) when emergent |
|
||||
| `superheat_regulated: true` | **Drops** SH residual (−1 eq) |
|
||||
|
||||
If SH residual is dropped, pair with a **free** EXV opening (and usually a control loop) so the system stays square. CLI DoF gate enforces balance.
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Index |
|
||||
|------|-------|
|
||||
| `inlet` / `outlet` | 0 / 1 refrigerant |
|
||||
| `secondary_inlet` / `secondary_outlet` | 2 / 3 secondary |
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Default | Notes |
|
||||
|--------|---------|-------|
|
||||
| `z_ua` | **1.0** | UA scale |
|
||||
| `z_dp` | 1.0 | ΔP scale |
|
||||
|
||||
UI Fixed: SST (`saturationTemperature`) + free `z_ua` for inverse calib.
|
||||
|
||||
### JSON parameters (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `ua` | UA [W/K] | required |
|
||||
| `emergent_pressure` | free P_evap | false |
|
||||
| `superheat_k` | SH target [K] | 5 |
|
||||
| `superheat_regulated` | drop SH residual | false |
|
||||
| `secondary_fluid` / `secondary_*` | system edges or rating | – |
|
||||
| `skip_pressure_eq` | drop ΔP residual | false |
|
||||
|
||||
### energy_transfers
|
||||
|
||||
Coupled: `Q = ṁ·(h_out − h_in)` as positive heat (cooling capacity).
|
||||
|
||||
### Zero flow
|
||||
|
||||
Smooth `|ṁ|` for live `C_sec`; no silent mass-index→pressure fallback.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
Évaporateur **DX** (sortie **surchauffée**). Duty ε-NTU :
|
||||
|
||||
```
|
||||
Q = ε · C_sec · (T_sec,in − T_evap(P))
|
||||
```
|
||||
|
||||
Différence avec **FloodedEvaporator** : clôture **superheat**, pas vapeur saturée noyée.
|
||||
|
||||
### Modes secondaire
|
||||
|
||||
- **Système :** ports live
|
||||
- **Rating :** scalaires T + ṁ·cp **dans le Newton**
|
||||
|
||||
### Régulation de surchauffe
|
||||
|
||||
`superheat_regulated: true` enlève le résidu SH → **libérer** l’ouverture EXV (contrôle).
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua = 1` par défaut. Fixed SST + Z_UA libre pour calage.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Voir EN.
|
||||
158
docs/components/expansion-valve.md
Normal file
158
docs/components/expansion-valve.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# ExpansionValve (legacy, port-based)
|
||||
|
||||
Config type: `"ExpansionValve"`
|
||||
Source: `crates/components/src/expansion_valve.rs`
|
||||
|
||||
> Distinct from `IsenthalpicExpansionValve` / `EXV`. Port-object based, with On/Off/Bypass operational states.
|
||||
> Distinct de `IsenthalpicExpansionValve` / `EXV`. Basé sur objets Port, avec états On/Off/Bypass.
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
2-port isenthalpic throttling valve for refrigeration systems:
|
||||
|
||||
```
|
||||
h_out = h_in (isenthalpic)
|
||||
ṁ_out = ṁ_in (mass continuity, with z_flow scale)
|
||||
P_out < P_in (throttling — pressure not closed by a flow law here)
|
||||
Q = 0, W = 0 (adiabatic, no work)
|
||||
```
|
||||
|
||||
Operational states:
|
||||
|
||||
| State | Behaviour |
|
||||
|-------|-----------|
|
||||
| **On** | isenthalpy + mass continuity |
|
||||
| **Off** | zero mass flow (`opening` < 0.01 also forces off) |
|
||||
| **Bypass** | adiabatic pipe: `P_out = P_in`, `h_out = h_in` |
|
||||
|
||||
`opening` does **not** enter the On residual set as a continuous flow coefficient; it only gates `is_effectively_off` below a 1 % threshold. For a free continuous opening + orifice law, use `IsenthalpicExpansionValve` with `orifice_kv`.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
```
|
||||
n_equations = 2 (always)
|
||||
local state: state[0]=ṁ_in, state[1]=ṁ_out
|
||||
```
|
||||
|
||||
| Row | On | Off | Bypass |
|
||||
|-----|----|-----|--------|
|
||||
| r0 | `h_out − h_in` | `ṁ_in = 0` | `P_out − P_in` (with isenthalpy pairing) |
|
||||
| r1 | `ṁ_out − z_flow·ṁ_in` | 0 | `h_out − h_in` |
|
||||
|
||||
### Ports
|
||||
|
||||
| Role | Description |
|
||||
|------|-------------|
|
||||
| inlet | high pressure, typically subcooled liquid |
|
||||
| outlet | low pressure, typically two-phase |
|
||||
|
||||
Type-state: `ExpansionValve<Disconnected>` → `.connect()` → `ExpansionValve<Connected>`.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Effect | Default |
|
||||
|--------|--------|---------|
|
||||
| `z_flow` | `ṁ_eff = z_flow · ṁ_in` | **1.0** |
|
||||
|
||||
`set_calib_indices` supports a dynamic `z_flow` state index.
|
||||
|
||||
### Emergent pressure / orifice
|
||||
|
||||
**Not available** on this component. Prefer `"IsenthalpicExpansionValve"` / `"EXV"`.
|
||||
|
||||
### energy_transfers
|
||||
|
||||
`(Q, W) = (0, 0)` always.
|
||||
|
||||
### JSON parameters
|
||||
|
||||
| Key | Meaning | Unit | Default |
|
||||
|-----|---------|------|---------|
|
||||
| `fluid` | refrigerant | – | **required** |
|
||||
| `opening` | valve position (off if < 0.01) | – | 1.0 |
|
||||
| `p_inlet_bar` / `h_inlet_kj_kg` | inlet IC | bar / kJ/kg | 12.0 / 260.0 |
|
||||
| `p_outlet_bar` / `h_outlet_kj_kg` | outlet IC | bar / kJ/kg | 3.5 / 260.0 |
|
||||
|
||||
### Known limitations
|
||||
|
||||
- Legacy port-based residual path; less integrated with CM1.4 edge ṁ sharing than EXV.
|
||||
- No emergent-pressure or orifice actuator.
|
||||
- Production cycles should use `IsenthalpicExpansionValve`.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle physique
|
||||
|
||||
Vanne de laminage isenthalpique 2-port :
|
||||
|
||||
```
|
||||
h_out = h_in
|
||||
ṁ_out = ṁ_in (avec échelle z_flow)
|
||||
Q = 0, W = 0
|
||||
```
|
||||
|
||||
États opérationnels :
|
||||
|
||||
| État | Comportement |
|
||||
|------|--------------|
|
||||
| **On** | isenthalpie + continuité de masse |
|
||||
| **Off** | débit nul (`opening` < 0.01 force aussi l'arrêt) |
|
||||
| **Bypass** | tube adiabatique : `P_out = P_in`, `h_out = h_in` |
|
||||
|
||||
`opening` ne rentre **pas** dans les résidus On comme coefficient de débit continu ; il ne sert qu'au seuil d'arrêt. Pour une ouverture libre + orifice, utiliser `IsenthalpicExpansionValve` avec `orifice_kv`.
|
||||
|
||||
### Résiduels & `n_equations()`
|
||||
|
||||
```
|
||||
n_equations = 2 (toujours)
|
||||
état local : state[0]=ṁ_in, state[1]=ṁ_out
|
||||
```
|
||||
|
||||
| Ligne | On | Off | Bypass |
|
||||
|-------|----|-----|--------|
|
||||
| r0 | `h_out − h_in` | `ṁ_in = 0` | `P_out − P_in` |
|
||||
| r1 | `ṁ_out − z_flow·ṁ_in` | 0 | `h_out − h_in` |
|
||||
|
||||
### Ports
|
||||
|
||||
| Rôle | Description |
|
||||
|------|-------------|
|
||||
| entrée | haute pression, liquide sous-refroidi typique |
|
||||
| sortie | basse pression, biphasique typique |
|
||||
|
||||
Typestate : `Disconnected` → `.connect()` → `Connected`.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Facteur | Effet | Défaut |
|
||||
|---------|-------|--------|
|
||||
| `z_flow` | `ṁ_eff = z_flow · ṁ_in` | **1.0** |
|
||||
|
||||
### Pression émergente / orifice
|
||||
|
||||
**Non disponibles.** Préférer `"IsenthalpicExpansionValve"` / `"EXV"`.
|
||||
|
||||
### energy_transfers
|
||||
|
||||
`(Q, W) = (0, 0)` toujours.
|
||||
|
||||
### Paramètres JSON
|
||||
|
||||
| Clé | Signification | Unité | Défaut |
|
||||
|-----|---------------|-------|--------|
|
||||
| `fluid` | frigorigène | – | **requis** |
|
||||
| `opening` | position (off si < 0.01) | – | 1.0 |
|
||||
| `p_inlet_bar` / `h_inlet_kj_kg` | CI entrée | bar / kJ/kg | 12.0 / 260.0 |
|
||||
| `p_outlet_bar` / `h_outlet_kj_kg` | CI sortie | bar / kJ/kg | 3.5 / 260.0 |
|
||||
|
||||
### Limites connues
|
||||
|
||||
- Chemin legacy port-object, moins intégré au partage ṁ CM1.4 que l'EXV.
|
||||
- Pas de pression émergente ni d'actionneur orifice.
|
||||
- Les cycles de production doivent utiliser `IsenthalpicExpansionValve`.
|
||||
49
docs/components/fan.md
Normal file
49
docs/components/fan.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Fan
|
||||
|
||||
Config type: `"Fan"`
|
||||
Source: `crates/components/src/fan.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Air-moving machine with **performance curves** (pressure rise / power vs flow and speed). Typestate ports: disconnected → connected.
|
||||
|
||||
Affinity laws may scale curves with rotational speed.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Curve residuals linking ΔP, ṁ (or volume flow), and speed; energy/power residual when power is modeled. See `n_equations()` in source (typically small fixed count for the fan node).
|
||||
|
||||
### Ports
|
||||
|
||||
`inlet` / `outlet` on the air branch.
|
||||
|
||||
### Calibration
|
||||
|
||||
Curve multipliers / Z-style factors when exposed via calib API (default unity).
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| curve data / preset | performance map | required |
|
||||
| `speed` / ratio | operating speed | 1.0 full |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
**Ventilateur** sur courbes ΔP / débit / vitesse.
|
||||
|
||||
### Ports
|
||||
|
||||
Entrée / sortie air.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir EN.
|
||||
46
docs/components/fin-coil-condenser.md
Normal file
46
docs/components/fin-coil-condenser.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# FinCoilCondenser
|
||||
|
||||
Config type: `"FinCoilCondenser"`
|
||||
Source: finned-coil condenser geometry module
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Finned-tube outdoor coil. Geometry (tubes, rows, fin pitch, face velocity) feeds air-side heat transfer estimates; refrigerant side condenses with subcooling target options.
|
||||
|
||||
Correlations: coil/fin air-side Nu–Re style relations as implemented in the geometry stack (see source for exact correlation names).
|
||||
|
||||
### Residuals
|
||||
|
||||
HX residual set analogous to Condenser + coil geometry parameters for UA construction.
|
||||
|
||||
### Ports
|
||||
|
||||
Refrigerant + air secondary ports.
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua` / geometry scales — default unity.
|
||||
|
||||
### JSON (main)
|
||||
|
||||
Tube OD, rows, fin density, face velocity, OAT, design capacity — see componentMeta FinCoilCondenser params.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Batterie ailetée** de condensation air.
|
||||
|
||||
### Corrélations
|
||||
|
||||
Côté air basées géométrie (détail dans le code).
|
||||
|
||||
### JSON
|
||||
|
||||
Voir meta UI / CLI.
|
||||
47
docs/components/flooded-condenser.md
Normal file
47
docs/components/flooded-condenser.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# FloodedCondenser
|
||||
|
||||
Rust / SystemBuilder type: `FloodedCondenser`
|
||||
Source: `crates/components/src/heat_exchanger/flooded_condenser.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Flooded condenser on an inner `HeatExchanger<EpsNtuModel>` with optional **subcooling control** residual:
|
||||
|
||||
```
|
||||
SC = (h_f(P) − h_out) / cp_l # when subcooled
|
||||
r_SC = SC − SC_target
|
||||
```
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Base ≈ 3 (inner HX path); **+1** with subcooling control (default target ~5 K).
|
||||
|
||||
> **Status:** Prefer production **`Condenser` + `emergent_pressure`** for water-cooled machines. FloodedCondenser may lag the dual-mode / DoF discipline of FloodedEvaporator — treat as partial until fully aligned.
|
||||
|
||||
### Ports
|
||||
|
||||
Refrigerant + secondary via inner exchanger / 4-port names when wired.
|
||||
|
||||
### Calibration
|
||||
|
||||
Inner calib `z_ua` (default 1.0) when exposed.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Condenseur **noyé** avec option de contrôle de sous-refroidissement.
|
||||
|
||||
### Statut
|
||||
|
||||
Préférer **`Condenser` + pression émergente** en production. Fiche partielle tant que le DoF n’est pas aligné sur FloodedEvaporator.
|
||||
|
||||
### Calibration
|
||||
|
||||
Z_UA = 1 si exposé.
|
||||
149
docs/components/flooded-evaporator.md
Normal file
149
docs/components/flooded-evaporator.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# FloodedEvaporator
|
||||
|
||||
Config type: `"FloodedEvaporator"`
|
||||
Source: `crates/components/src/heat_exchanger/flooded_evaporator.rs`
|
||||
Example: `crates/cli/examples/chiller_flooded_4port_watercooled.json` (DoF 19=19, COP ≈ 6.45)
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
Shell-and-tube **flooded** evaporator. Refrigerant boils on the shell side; secondary (water/brine) flows in the tubes. Heat duty uses **phase-change ε-NTU** (`C_min = C_sec`, `C_r → 0`):
|
||||
|
||||
```
|
||||
ε = 1 − exp(−UA / C_sec)
|
||||
Q = ε · C_sec · (T_sec,in − T_evap(P))
|
||||
```
|
||||
|
||||
- `T_evap(P)` = saturation temperature of the refrigerant at edge pressure
|
||||
- `C_sec` = secondary heat-capacity rate [W/K]
|
||||
|
||||
There is **no plate-geometry correlation** inside this component (unlike BPHX). UA is a **lumped parameter** (possibly scaled by calibration `z_ua` via the inner `HeatExchanger` calib API).
|
||||
|
||||
### Dual operating modes (both enter Newton residuals)
|
||||
|
||||
| Mode | How secondary is defined | Secondary Newton unknowns | When to use |
|
||||
|------|--------------------------|---------------------------|-------------|
|
||||
| **System (4-port)** | Live edges `secondary_inlet` / `secondary_outlet` (e.g. BrineSource → HX → BrineSink) | Yes (`n_secondary` = 1 or 2) | Closed water loop, real machine |
|
||||
| **Rating** | Scalars `secondary_inlet_temp_*` + `C_sec` (`secondary_mass_flow_kg_s` × `cp` or `secondary_capacity_rate_w_per_k`) | No (`n_secondary` = 0) | Qualification / open-loop duty; still **coupled ε-NTU in residuals** |
|
||||
|
||||
`coupled_ready` = refrigerant indices ready **and** (live secondary edges **or** rating scalars).
|
||||
Never falls through to generic four-port `HeatExchanger::inner` residuals for normal operation (seed path is local and finite).
|
||||
|
||||
**Rating residual energy:** uses full `Q` (not `α(ṁ)·Q`) so `ṁ = 0` is not a trivial root when `C_sec > 0`.
|
||||
**System residual energy:** uses `effective_duty(Q, α_ref, α_sec)` from `flow_regularization` for zero-flow safety.
|
||||
|
||||
Also exposed: `rate(p_in)` open-loop rating API for sweeps (same ε-NTU formulas).
|
||||
|
||||
### Outlet closure (DoF-critical)
|
||||
|
||||
| Setting | Residual r2 | Typical use |
|
||||
|---------|-------------|-------------|
|
||||
| Default (`quality_control: false`) | `h_out − h_g(P)` saturated vapor | Compressor suction after disengagement |
|
||||
| `quality_control: true` | `x_out − target_quality` | Legacy recirculation / two-phase outlet |
|
||||
|
||||
Both keep **the same** `n_equations` (quality replaces sat-vapor; it does not add an extra free residual by itself).
|
||||
`quality_control: true` on a closed cycle often needs a **free actuator** elsewhere or the DoF gate rejects the graph.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
| Row | Equation |
|
||||
|-----|----------|
|
||||
| r0 | `P_out − P_in` (no refrigerant ΔP by default) |
|
||||
| r1 | `ṁ_ref · (h_out − h_in) − Q_eff` |
|
||||
| r2 | sat-vapor **or** quality target (see above) |
|
||||
| r_sec mass | `ṁ_sec,out − ṁ_sec,in` only if live edges and **not** same-branch |
|
||||
| r_sec energy | live secondary energy + duty (blended at low ṁ) |
|
||||
|
||||
```
|
||||
n_equations = 3 + n_secondary
|
||||
n_secondary = 0 # rating (no live edges)
|
||||
| 1 # live edges, same-branch ṁ
|
||||
| 2 # live edges, independent ṁ in/out
|
||||
```
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Index | Role |
|
||||
|------|-------|------|
|
||||
| `inlet` | 0 | Refrigerant from EXV |
|
||||
| `outlet` | 1 | Refrigerant to compressor suction |
|
||||
| `secondary_inlet` | 2 | Water/brine in |
|
||||
| `secondary_outlet` | 3 | Water/brine out |
|
||||
|
||||
CLI aliases: `water_in` / `brine_in` → secondary_inlet, etc. (`resolve_port_index`).
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Effect | Default |
|
||||
|--------|--------|---------|
|
||||
| `z_ua` (BOLT `Z_UA`) | `UA_eff = z_ua · UA` via inner calib | **1.0** |
|
||||
| `z_dp` | pressure-drop scale if ΔP model used | 1.0 |
|
||||
|
||||
Inverse calibration (CLI `controls[]` / UI Fixed checkboxes): impose a measure (e.g. SST = `saturationTemperature`) and free `z_ua`.
|
||||
|
||||
### JSON parameters
|
||||
|
||||
| Key | Meaning | Unit | Default |
|
||||
|-----|---------|------|---------|
|
||||
| `ua` | UA | W/K | **required** |
|
||||
| `refrigerant` | refrigerant id | – | primary fluid |
|
||||
| `secondary_fluid` | secondary fluid | – | Water / MEG |
|
||||
| `quality_control` | quality residual instead of sat-vapor | bool | `false` |
|
||||
| `target_quality` | x target if quality_control | – | 0.7 |
|
||||
| `secondary_inlet_temp_c` / `_k` | rating T_sec,in | °C / K | – |
|
||||
| `secondary_mass_flow_kg_s` | rating ṁ_sec | kg/s | – |
|
||||
| `secondary_cp_j_per_kgk` | rating cp | J/(kg·K) | 4186 |
|
||||
| `secondary_capacity_rate_w_per_k` | rating C_sec direct | W/K | – |
|
||||
| calib `z_ua` / `z_dp` | Z-factors | – | 1.0 |
|
||||
|
||||
### energy_transfers / mass
|
||||
|
||||
When coupled: cooling `Q ≈ ṁ·(h_out − h_in)` (positive heat absorbed by refrigerant).
|
||||
`port_mass_flows` reports 4-port signs without calling generic inner four-port.
|
||||
|
||||
### Zero flow
|
||||
|
||||
`flow_regularization` on system path: smooth `|ṁ|` for live `C_sec`, activity factors, secondary Δh hold. Seed residuals stay finite if P is non-physical.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
Évaporateur **noyé** tubes-calandre. Duty **ε-NTU** à changement de phase :
|
||||
|
||||
```
|
||||
ε = 1 − exp(−UA / C_sec)
|
||||
Q = ε · C_sec · (T_sec,in − T_evap(P))
|
||||
```
|
||||
|
||||
Pas de corrélation géométrique type Longo/Shah (voir BPHX pour ça). UA est un **paramètre global**, modulable par `z_ua` (défaut **1**).
|
||||
|
||||
### Deux modes (tous deux dans le Newton)
|
||||
|
||||
| Mode | Secondaire | Inconnues eau | Usage |
|
||||
|------|------------|---------------|--------|
|
||||
| **Système** | Ports live Source → HX → Sink | oui | Machine fermée |
|
||||
| **Rating** | Scalaires T + ṁ·cp (ou C_sec) | non | Qualification ; Q ε-NTU **dans** les résidus |
|
||||
|
||||
### Clôture de sortie
|
||||
|
||||
- Défaut : **vapeur saturée** `h_out = h_g(P)` (aspiration compresseur).
|
||||
- `quality_control: true` : `x_out − x_cible` (même nombre d’équations).
|
||||
|
||||
### Résiduels
|
||||
|
||||
`n_equations = 3 + n_secondary` (0 / 1 / 2). Voir tableau EN.
|
||||
|
||||
### Calibration
|
||||
|
||||
Imposer une mesure (SST) et libérer `z_ua` (UI case Fixed, ou `controls[]`).
|
||||
`z_ua = 1` = pas de correction.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Identiques aux tableaux EN.
|
||||
48
docs/components/flow-merger.md
Normal file
48
docs/components/flow-merger.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# FlowMerger
|
||||
|
||||
Config type: `"FlowMerger"`
|
||||
Source: `crates/components/src/flow_merger.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
**N inlets → one outlet**. Mass and energy mix at common pressure:
|
||||
|
||||
```
|
||||
ṁ_out = Σ ṁ_in,i
|
||||
ṁ_out · h_out = Σ ṁ_in,i · h_in,i
|
||||
P_out = P_in,i (ideal junction)
|
||||
```
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Mixing mass + energy + pressure equality constraints as implemented.
|
||||
|
||||
### Ports
|
||||
|
||||
`inlet_0` … `inlet_{n-1}`, `outlet`.
|
||||
|
||||
### Calibration
|
||||
|
||||
None by default.
|
||||
|
||||
### JSON
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `n_inlets` | number of inlets | ≥ 2 |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Mélangeur** N → 1. Conservation ṁ et H ; pression commune idéale.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir EN.
|
||||
51
docs/components/flow-splitter.md
Normal file
51
docs/components/flow-splitter.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# FlowSplitter
|
||||
|
||||
Config type: `"FlowSplitter"`
|
||||
Source: `crates/components/src/flow_splitter.rs` (or equivalent)
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
One inlet → **N outlets**. Mass splits across outlet legs; pressure continuous at the node (common header assumption unless specialized ΔP models exist).
|
||||
|
||||
```
|
||||
ṁ_in = Σ ṁ_out,i
|
||||
P_out,i = P_in (ideal splitter)
|
||||
h_out,i = h_in (same enthalpy)
|
||||
```
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Mass split + equal-P / equal-h constraints per topology. Port count depends on `n_outlets` configuration.
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| `inlet` | single inlet |
|
||||
| `outlet_0` … `outlet_{n-1}` | outlets |
|
||||
|
||||
### Calibration
|
||||
|
||||
None by default.
|
||||
|
||||
### JSON
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `n_outlets` | number of legs | ≥ 2 |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Séparateur de débit** 1 → N. Conservation de ṁ ; même P/h idéalement.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Voir EN.
|
||||
44
docs/components/free-cooling-exchanger.md
Normal file
44
docs/components/free-cooling-exchanger.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# FreeCoolingExchanger
|
||||
|
||||
Config types: `"FreeCoolingExchanger"`, `"FreeCooling"`
|
||||
Source: free-cooling HX module
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Free-cooling heat exchanger between two liquid loops (e.g. tower water ↔ chilled water) without vapor-compression. Effectiveness–NTU or UA·LMTD between two single-phase streams.
|
||||
|
||||
```
|
||||
Q = ε · C_min · (T_hot,in − T_cold,in)
|
||||
```
|
||||
|
||||
### Residuals
|
||||
|
||||
Two-stream energy balances + optional ΔP per leg.
|
||||
|
||||
### Ports
|
||||
|
||||
Hot and cold in/out (4-port).
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua` default **1.0**.
|
||||
|
||||
### JSON
|
||||
|
||||
UA, fluids, optional secondary stream params — see CLI arm.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Échangeur de **free-cooling** (liquide–liquide), sans cycle frigo.
|
||||
|
||||
### Calibration
|
||||
|
||||
Z_UA = 1 par défaut.
|
||||
60
docs/components/heat-exchanger-generic.md
Normal file
60
docs/components/heat-exchanger-generic.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# HeatExchanger (generic)
|
||||
|
||||
Config type: `"HeatExchanger"`
|
||||
Source: `crates/components/src/heat_exchanger/exchanger.rs` + ε-NTU / LMTD models
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Generic two-stream HX with selectable model:
|
||||
|
||||
- **ε-NTU** effectiveness
|
||||
- **LMTD** / counterflow forms
|
||||
|
||||
Ports: hot_in/out, cold_in/out (Modelica-style 4-port).
|
||||
|
||||
```
|
||||
NTU = UA / C_min
|
||||
ε = f(NTU, C_r, flow arrangement)
|
||||
Q = ε · C_min · (T_hot,in − T_cold,in)
|
||||
```
|
||||
|
||||
**Requires live four-port edge state** for residual evaluation on the generic path — inlet-only scalar BCs do not invent outlet states.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Inner model residual count (often 2–3 per side balance depending on configuration).
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| `hot_inlet` / `hot_outlet` | hot stream |
|
||||
| `cold_inlet` / `cold_outlet` | cold stream |
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_ua` on UA (default 1.0).
|
||||
|
||||
### When not to use
|
||||
|
||||
For refrigeration condensers/evaporators prefer specialized `Condenser` / `Evaporator` / `FloodedEvaporator` / BPHX which know phase-change ε-NTU and secondary dual modes.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Échangeur **générique** 4 ports (ε-NTU / LMTD).
|
||||
|
||||
### Attention
|
||||
|
||||
Exige un état **4 ports live**. Pour frigo, préférer Condenser / Evaporator / Flooded / BPHX.
|
||||
|
||||
### Calibration
|
||||
|
||||
Z_UA = 1 par défaut.
|
||||
150
docs/components/isenthalpic-expansion-valve.md
Normal file
150
docs/components/isenthalpic-expansion-valve.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# IsenthalpicExpansionValve (EXV)
|
||||
|
||||
Config types: `"IsenthalpicExpansionValve"`, `"EXV"`
|
||||
Source: `crates/components/src/isenthalpic_expansion_valve.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
Isenthalpic expansion (throttling) valve for vapor-compression cycles. Three model families:
|
||||
|
||||
| Family | Trigger | Physics |
|
||||
|--------|---------|---------|
|
||||
| **A — Fixed pressure** | default | Pins `P_out = P_sat(T_evap)` + isenthalpy |
|
||||
| **B — Emergent pressure** | `emergent_pressure: true` | Isenthalpy only; low-side P from evaporator |
|
||||
| **C — Orifice** | `orifice_kv` set | Emergent + physical flow law; **opening is a free DoF** |
|
||||
|
||||
Orifice law (arch-6 physical actuator):
|
||||
|
||||
```
|
||||
ṁ = Kv · opening · √(2 · ρ_in · max(P_in − P_out, 0)) , opening ∈ [0, 1]
|
||||
```
|
||||
|
||||
`with_orifice(kv)` / JSON `orifice_kv` forces `emergent_pressure = true`.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
| Mode | same-branch | orifice | n_equations | Residuals |
|
||||
|------|-------------|---------|-------------|-----------|
|
||||
| Fixed P | no | no | 3 | r0 `P_out − P_sat(T_evap)`; r1 `h_out − h_in`; r2 `ṁ_out − ṁ_in` |
|
||||
| Fixed P | yes | no | 2 | r0, r1 |
|
||||
| Emergent | no | no | 2 | r0 `h_out − h_in`; r1 `ṁ_out − ṁ_in` |
|
||||
| Emergent | yes | no | 1 | r0 `h_out − h_in` |
|
||||
| Emergent + orifice | either | yes | +1 | + `ṁ − Kv·opening·√(2·ρ_in·ΔP)` |
|
||||
|
||||
Orifice adds **1 equation** and the opening adds **1 unknown** → DoF stays balanced. Pair with a `superheat_regulated` evaporator (drops its SH residual) and a controller on `opening` for regulated superheat.
|
||||
|
||||
### Ports
|
||||
|
||||
| Edge | Role |
|
||||
|------|------|
|
||||
| 0 | inlet (cond → EXV) |
|
||||
| 1 | outlet (EXV → evap) |
|
||||
|
||||
### Emergent pressure
|
||||
|
||||
Enabled by `emergent_pressure: true` or automatically by orifice mode. Removes the `P_out = P_sat(T_evap)` pin.
|
||||
|
||||
### Calibration / actuators
|
||||
|
||||
| Item | Notes |
|
||||
|------|-------|
|
||||
| Control factor `"opening"` | maps to `actuator` slot; requires `orifice_kv` |
|
||||
| Free actuator `{name}__opening` | registered when orifice configured without a loop |
|
||||
| `z_flow` / `z_dp` | **not** used on this component |
|
||||
|
||||
### measure_output / energy_transfers
|
||||
|
||||
Not specialized (`energy_transfers` none / adiabatic throttling: Q = W = 0).
|
||||
|
||||
### JSON parameters
|
||||
|
||||
| Key | Meaning | Unit | Default |
|
||||
|-----|---------|------|---------|
|
||||
| `t_evap_k` | target evaporating T for P_sat | K | 275.15 |
|
||||
| `fluid` | refrigerant | – | primary |
|
||||
| `emergent_pressure` | drop P_evap pin | bool | false |
|
||||
| `orifice_kv` | orifice coefficient Kv | m² | – (none ⇒ no orifice) |
|
||||
| `orifice_opening_init` | initial opening | – | 0.5 |
|
||||
| `orifice_opening_min` | min bound | – | 0.02 |
|
||||
| `orifice_opening_max` | max bound | – | 1.0 |
|
||||
|
||||
### Notes
|
||||
|
||||
Preferred EXV for modern cycle configs. For the older port-object valve with On/Off/Bypass see [expansion-valve.md](./expansion-valve.md).
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle physique
|
||||
|
||||
Détendeur isenthalpique (laminage) pour cycles à compression de vapeur. Trois familles :
|
||||
|
||||
| Famille | Déclencheur | Physique |
|
||||
|---------|-------------|----------|
|
||||
| **A — Pression fixée** | défaut | Impose `P_out = P_sat(T_evap)` + isenthalpie |
|
||||
| **B — Pression émergente** | `emergent_pressure: true` | Isenthalpie seule ; P BP par l'évaporateur |
|
||||
| **C — Orifice** | `orifice_kv` | Émergent + loi de débit ; **ouverture = DoF libre** |
|
||||
|
||||
Loi d'orifice :
|
||||
|
||||
```
|
||||
ṁ = Kv · opening · √(2 · ρ_in · max(P_in − P_out, 0)) , opening ∈ [0, 1]
|
||||
```
|
||||
|
||||
`orifice_kv` force `emergent_pressure = true`.
|
||||
|
||||
### Résiduels & `n_equations()`
|
||||
|
||||
| Mode | même branche | orifice | n_equations | Résidus |
|
||||
|------|--------------|---------|-------------|---------|
|
||||
| P fixe | non | non | 3 | r0 `P_out − P_sat(T_evap)` ; r1 `h_out − h_in` ; r2 `ṁ_out − ṁ_in` |
|
||||
| P fixe | oui | non | 2 | r0, r1 |
|
||||
| Émergent | non | non | 2 | r0 `h_out − h_in` ; r1 `ṁ_out − ṁ_in` |
|
||||
| Émergent | oui | non | 1 | r0 `h_out − h_in` |
|
||||
| Émergent + orifice | – | oui | +1 | + `ṁ − Kv·opening·√(2·ρ_in·ΔP)` |
|
||||
|
||||
L'orifice ajoute **1 équation** et l'ouverture **1 inconnu** → DoF équilibré. Couplé à un évaporateur `superheat_regulated` et un contrôleur sur `opening`, la surchauffe devient régulée.
|
||||
|
||||
### Ports
|
||||
|
||||
| Arête | Rôle |
|
||||
|-------|------|
|
||||
| 0 | entrée (cond → EXV) |
|
||||
| 1 | sortie (EXV → évap) |
|
||||
|
||||
### Pression émergente
|
||||
|
||||
Via `emergent_pressure: true` ou automatiquement en mode orifice. Supprime le pin `P_out = P_sat(T_evap)`.
|
||||
|
||||
### Calibration / actionneurs
|
||||
|
||||
| Élément | Notes |
|
||||
|---------|-------|
|
||||
| Facteur `"opening"` | mappe le slot `actuator` ; nécessite `orifice_kv` |
|
||||
| Actionneur libre `{name}__opening` | si orifice sans boucle |
|
||||
| `z_flow` / `z_dp` | **non** utilisés |
|
||||
|
||||
### measure_output / energy_transfers
|
||||
|
||||
Non spécialisés ; laminage adiabatique (Q = W = 0).
|
||||
|
||||
### Paramètres JSON
|
||||
|
||||
| Clé | Signification | Unité | Défaut |
|
||||
|-----|---------------|-------|--------|
|
||||
| `t_evap_k` | T évaporation cible pour P_sat | K | 275.15 |
|
||||
| `fluid` | frigorigène | – | primaire |
|
||||
| `emergent_pressure` | supprime le pin P_evap | bool | false |
|
||||
| `orifice_kv` | coefficient d'orifice Kv | m² | – (aucun ⇒ pas d'orifice) |
|
||||
| `orifice_opening_init` | ouverture initiale | – | 0.5 |
|
||||
| `orifice_opening_min` | borne min | – | 0.02 |
|
||||
| `orifice_opening_max` | borne max | – | 1.0 |
|
||||
|
||||
### Notes
|
||||
|
||||
EXV préféré pour les configs de cycle modernes. Ancienne vanne port-object → [expansion-valve.md](./expansion-valve.md).
|
||||
214
docs/components/isentropic-compressor.md
Normal file
214
docs/components/isentropic-compressor.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# IsentropicCompressor
|
||||
|
||||
Config type: `"IsentropicCompressor"`
|
||||
Source: `crates/components/src/isentropic_compressor.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & physical model
|
||||
|
||||
Vapor-compression compressor for cycle simulation. Two operating families:
|
||||
|
||||
| Mode | When | Mass / pressure behaviour |
|
||||
|------|------|---------------------------|
|
||||
| **Fixed-pressure** (default) | `emergent_pressure: false` | Pins `P_dis = P_sat(T_cond)`; mass continuity across suction/discharge |
|
||||
| **Emergent-pressure** | `emergent_pressure: true` | Closes ṁ with a **volumetric displacement law**; `P_dis` floats from the condenser ↔ secondary balance |
|
||||
|
||||
True isentropic path via CoolProp: `(P,h)→s` then `(P,s)→h_is`, corrected by isentropic efficiency:
|
||||
|
||||
```
|
||||
h_dis = h_suc + (h_is − h_suc) / η_is,eff
|
||||
```
|
||||
|
||||
Swept mass flow (emergent only):
|
||||
|
||||
```
|
||||
ṁ_calc = ρ_suc · V_s · N · η_vol(P_dis/P_suc) · f_VSD,vol
|
||||
ṁ = σ · z_flow · ṁ_calc
|
||||
```
|
||||
|
||||
Volumetric efficiency models:
|
||||
|
||||
| Model | Formula |
|
||||
|-------|---------|
|
||||
| Constant | `η_vol = const` (default 1.0) |
|
||||
| Clearance | `η_vol = 1 + C − C · (P_dis/P_suc)^(1/n)` |
|
||||
|
||||
Optional **VSD speed map** (quadratic, identity default `[1,0,0]`):
|
||||
|
||||
```
|
||||
f(r) = c0 + c1·r + c2·r² , r = N / N_ref , clamped ∈ [0.1, 1.2]
|
||||
η_vol,eff = η_vol · f_vol(r) ; η_is,eff = η_is · f_is(r)
|
||||
```
|
||||
|
||||
Optional **liquid injection** desuperheat (no extra equation; φ from controls):
|
||||
|
||||
```
|
||||
h_dis,eff = h_dis − φ_inj · (h_dis − h_f(P_dis)) , φ_inj ∈ [0, φ_max]
|
||||
```
|
||||
|
||||
Design anchors `t_cond_k`, `t_evap_k`, `superheat_k` are used for fixed-pressure pins and as initial-condition helpers; in emergent mode the live suction `(P,h)` drives the isentropic path.
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
```
|
||||
n_equations = (2 if same_branch else 3) + (1 if slide_valve active else 0)
|
||||
```
|
||||
|
||||
| Row | Fixed-pressure | Emergent-pressure |
|
||||
|-----|----------------|-------------------|
|
||||
| r0 | `P_dis − P_sat(T_cond)` | `ṁ − σ·z_flow·ṁ_calc` |
|
||||
| r1 | `H_dis − h_dis` | `H_dis − h_dis,eff` |
|
||||
| r2 | `ṁ_dis − ṁ_suc` (dropped if same-branch) | same |
|
||||
| r3 | — | (slide) `T_sat(P_suc) − SST_target` |
|
||||
|
||||
### Ports
|
||||
|
||||
| Index | Role |
|
||||
|-------|------|
|
||||
| 0 | suction (inlet) |
|
||||
| 1 | discharge (outlet) |
|
||||
|
||||
Edge-wired via `set_system_context` (CM1.3 ṁ/P/h triples). `get_ports()` may be empty.
|
||||
|
||||
### Emergent pressure & actuators
|
||||
|
||||
- Requires `displacement_m3` and `speed_hz` when `emergent_pressure: true`.
|
||||
- **Slide valve** (`slide_valve_sst_target_k` / `_c`): free actuator σ ∈ [σ_min, 1] scales swept volume and holds SST.
|
||||
- **Liquid injection** (`liquid_injection: true`): φ_inj on the `actuator` / control factor `"injection"`; closing equation from a user `controls[]` loop (e.g. max DGT), not hard-coded.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Factor | Effect | Default |
|
||||
|--------|--------|---------|
|
||||
| `z_flow` | scales swept ṁ (emergent r0) | **1.0** |
|
||||
| `actuator` | slide σ **or** injection φ | – |
|
||||
|
||||
### measure_output / energy_transfers
|
||||
|
||||
- `measure_output(Temperature)` → discharge gas temperature (DGT) for injection control.
|
||||
- `energy_transfers`: `(Q, W) = (0, −ṁ·(h_dis,work − h_suc))` — adiabatic; shaft work negative. With liquid injection, work uses un-desuperheated compression enthalpy.
|
||||
|
||||
### JSON parameters
|
||||
|
||||
| Key | Meaning | Unit | Default |
|
||||
|-----|---------|------|---------|
|
||||
| `isentropic_efficiency` | η_is | – | 0.75 |
|
||||
| `t_cond_k` | condensing sat. T (fixed pin / design) | K | 323.15 |
|
||||
| `t_evap_k` | evaporating sat. T (design) | K | 275.15 |
|
||||
| `superheat_k` | suction superheat design | K | 5.0 |
|
||||
| `fluid` | refrigerant | – | primary |
|
||||
| `emergent_pressure` | enable displacement closure | bool | false |
|
||||
| `displacement_m3` | swept volume V_s | m³/rev | 0.0 |
|
||||
| `speed_hz` | rotational speed N | rev/s | 0.0 |
|
||||
| `volumetric_efficiency` | constant η_vol | – | 1.0 |
|
||||
| `clearance` | clearance ratio C (enables clearance model) | – | – |
|
||||
| `polytropic_n` | re-expansion exponent | – | 1.1 |
|
||||
| `vsd_reference_speed_hz` | VSD N_ref (enables map) | rev/s | – |
|
||||
| `vsd_volumetric_coeffs` | `[c0,c1,c2]` η_vol map | – | [1,0,0] |
|
||||
| `vsd_isentropic_coeffs` | `[c0,c1,c2]` η_is map | – | [1,0,0] |
|
||||
| `slide_valve_sst_target_k` / `_c` | slide SST setpoint | K / °C | – |
|
||||
| `liquid_injection` | enable injection desuperheat | bool | false |
|
||||
| `slide_position_init` / `min` / `max` | free-actuator bounds | – | 1.0 / 0.1 / 1.0 |
|
||||
|
||||
### Notes
|
||||
|
||||
Preferred cycle compressor for physics-based machines. For manufacturer AHRI maps use `"Compressor"`; for economized screws use `"ScrewEconomizerCompressor"`.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle physique
|
||||
|
||||
Compresseur à compression de vapeur. Deux familles de fonctionnement :
|
||||
|
||||
| Mode | Quand | Comportement |
|
||||
|------|-------|--------------|
|
||||
| **Pression fixée** (défaut) | `emergent_pressure: false` | Impose `P_dis = P_sat(T_cond)` ; continuité de masse |
|
||||
| **Pression émergente** | `emergent_pressure: true` | Ferme ṁ par une **loi volumétrique** ; `P_dis` flotte via le condenseur |
|
||||
|
||||
Chemin isentropique CoolProp + rendement :
|
||||
|
||||
```
|
||||
h_dis = h_suc + (h_is − h_suc) / η_is,eff
|
||||
```
|
||||
|
||||
Débit balayé (émergent) :
|
||||
|
||||
```
|
||||
ṁ_calc = ρ_suc · V_s · N · η_vol(P_dis/P_suc) · f_VSD,vol
|
||||
ṁ = σ · z_flow · ṁ_calc
|
||||
```
|
||||
|
||||
Modèles de rendement volumétrique : constant, ou volume mort `η_vol = 1 + C − C·Pr^(1/n)`.
|
||||
Carte VSD optionnelle (quadratique, identité `[1,0,0]`).
|
||||
Injection liquide optionnelle : `h_dis,eff = h_dis − φ_inj·(h_dis − h_f(P_dis))` (pas d'équation interne).
|
||||
|
||||
### Résiduels & `n_equations()`
|
||||
|
||||
```
|
||||
n_equations = (2 si même branche sinon 3) + (1 si tiroir actif)
|
||||
```
|
||||
|
||||
| Ligne | Pression fixée | Pression émergente |
|
||||
|-------|----------------|--------------------|
|
||||
| r0 | `P_dis − P_sat(T_cond)` | `ṁ − σ·z_flow·ṁ_calc` |
|
||||
| r1 | `H_dis − h_dis` | `H_dis − h_dis,eff` |
|
||||
| r2 | `ṁ_dis − ṁ_suc` (supprimée si même branche) | idem |
|
||||
| r3 | — | (tiroir) `T_sat(P_suc) − SST_cible` |
|
||||
|
||||
### Ports
|
||||
|
||||
| Index | Rôle |
|
||||
|-------|------|
|
||||
| 0 | aspiration (entrée) |
|
||||
| 1 | refoulement (sortie) |
|
||||
|
||||
Câblage par arêtes (`set_system_context`, triples ṁ/P/h CM1.3).
|
||||
|
||||
### Pression émergente & actionneurs
|
||||
|
||||
- `displacement_m3` et `speed_hz` obligatoires en mode émergent.
|
||||
- **Tiroir** (`slide_valve_sst_target_k` / `_c`) : actionneur libre σ pour tenir la SST.
|
||||
- **Injection liquide** : φ_inj via boucle `controls[]` (ex. DGT max), facteur `"injection"`.
|
||||
|
||||
### Calibration
|
||||
|
||||
| Facteur | Effet | Défaut |
|
||||
|---------|-------|--------|
|
||||
| `z_flow` | échelle le débit balayé | **1.0** |
|
||||
| `actuator` | position tiroir σ **ou** ratio d'injection φ | – |
|
||||
|
||||
### measure_output / energy_transfers
|
||||
|
||||
- `Temperature` → température des gaz de refoulement (DGT).
|
||||
- `(Q, W) = (0, −ṁ·(h_dis,work − h_suc))` — adiabatique ; travail sur le compresseur négatif.
|
||||
|
||||
### Paramètres JSON
|
||||
|
||||
| Clé | Signification | Unité | Défaut |
|
||||
|-----|---------------|-------|--------|
|
||||
| `isentropic_efficiency` | η_is | – | 0.75 |
|
||||
| `t_cond_k` | T sat. condensation (pin / design) | K | 323.15 |
|
||||
| `t_evap_k` | T sat. évaporation (design) | K | 275.15 |
|
||||
| `superheat_k` | surchauffe aspiration design | K | 5.0 |
|
||||
| `fluid` | fluide frigorigène | – | primaire |
|
||||
| `emergent_pressure` | active la fermeture volumétrique | bool | false |
|
||||
| `displacement_m3` | cylindrée V_s | m³/tr | 0.0 |
|
||||
| `speed_hz` | vitesse N | tr/s | 0.0 |
|
||||
| `volumetric_efficiency` | η_vol constant | – | 1.0 |
|
||||
| `clearance` | rapport volume mort C | – | – |
|
||||
| `polytropic_n` | exposant de détente | – | 1.1 |
|
||||
| `vsd_reference_speed_hz` | N_ref carte VSD | tr/s | – |
|
||||
| `vsd_volumetric_coeffs` | `[c0,c1,c2]` carte η_vol | – | [1,0,0] |
|
||||
| `vsd_isentropic_coeffs` | `[c0,c1,c2]` carte η_is | – | [1,0,0] |
|
||||
| `slide_valve_sst_target_k` / `_c` | consigne SST tiroir | K / °C | – |
|
||||
| `liquid_injection` | active la désurchauffe par injection | bool | false |
|
||||
| `slide_position_init` / `min` / `max` | bornes actionneur libre | – | 1.0 / 0.1 / 1.0 |
|
||||
|
||||
### Notes
|
||||
|
||||
Compresseur de cycle préféré pour les machines physiques. Cartes fabricant AHRI → `"Compressor"` ; vis économisée → `"ScrewEconomizerCompressor"`.
|
||||
40
docs/components/mchx-condenser-coil.md
Normal file
40
docs/components/mchx-condenser-coil.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# MchxCondenserCoil / MchxCoil
|
||||
|
||||
Config types: `"MchxCondenserCoil"`, `"MchxCoil"`
|
||||
Source: microchannel condenser coil module
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
**Microchannel** air-cooled condenser coil. Compact multi-port tubes + air fins. UA from geometry and air/refrigerant side coefficients as coded; runtime residual path follows condenser-style energy balances.
|
||||
|
||||
### Residuals
|
||||
|
||||
Condenser-like refrigerant + air coupling residuals.
|
||||
|
||||
### Ports
|
||||
|
||||
Refrigerant + air.
|
||||
|
||||
### Calibration
|
||||
|
||||
Z-factors on UA/ΔP when exposed (default 1.0).
|
||||
|
||||
### JSON
|
||||
|
||||
Geometry and air-side setpoints per CLI arm / UI meta (`design_capacity_kw`, face velocity, OAT, …).
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Batterie **micro-canaux** de condensation.
|
||||
|
||||
### JSON
|
||||
|
||||
Voir meta UI / exemples.
|
||||
35
docs/components/moving-boundary-hx.md
Normal file
35
docs/components/moving-boundary-hx.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# MovingBoundaryHX
|
||||
|
||||
Source: moving-boundary / multi-zone HX identification helpers
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Research / identification path: multi-zone (SH / TP / SC) UA allocation feeding an ε-NTU or zone energy balance. **Not** the default production Condenser/Evaporator path.
|
||||
|
||||
### Residuals
|
||||
|
||||
Zone energy balances + interface quality/enthalpy consistency when fully enabled. Coverage may be partial — check source and tests before relying in production machines.
|
||||
|
||||
### Correlations
|
||||
|
||||
Zone UA may come from geometry or identified parameters rather than a single Longo map.
|
||||
|
||||
### Recommendation
|
||||
|
||||
Production chillers: use **Condenser**, **Evaporator**, **FloodedEvaporator**, or **BPHX** with documented dual-mode secondary and DoF discipline.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
HX **moving-boundary** multi-zones (recherche / identification).
|
||||
|
||||
### Recommandation
|
||||
|
||||
En production : Condenser / Evaporator / Flooded / BPHX.
|
||||
@@ -14,15 +14,17 @@ Source code: `crates/core/src/smoothing.rs` (`entropyk_core::smoothing`).
|
||||
|-------|----------|------|
|
||||
| **Canonical general toolkit** | `entropyk_core::smoothing` | `smooth_max` / `smooth_min` / `smooth_abs` / `smooth_clamp` (+ analytic derivatives) |
|
||||
| **Reference specialized pattern** | `heat_exchanger::flow_regularization` | Mass-flow activity / duty / transport blend for zero-flow HX residuals — **not** duplicated into core |
|
||||
| **Verification harness** | `entropyk_components::jacobian_fd` | Central FD vs analytic (Story 0.1); CI gate is Story 0.5 |
|
||||
| **Verification harness** | `entropyk_components::jacobian_fd` | Central FD vs analytic (Story 0.1); envelope CI gate Story 0.5 landed |
|
||||
|
||||
**Banned** in residual / Jacobian paths on Newton unknowns:
|
||||
|
||||
- bare `.clamp(...)` whose derivative jumps `0 ↔ 1`
|
||||
- hard `.max(0)` / early `Ok(0.0)` that zeros informative physics derivatives
|
||||
|
||||
Physical clamp → smooth migrations are Stories **0.3** (valve / EXV) and **0.4** (HX).
|
||||
This standard ships toolkit + docs only; it does not change component physics.
|
||||
Physical clamp → smooth migrations:
|
||||
- Story **0.3** (valve / EXV): `valve_flow.rs`, `isenthalpic_expansion_valve.rs` orifice path.
|
||||
- Story **0.4** (HX consumer): `two_phase_dp` quality `smooth_clamp`, condenser fan/flood
|
||||
actuators + Jacobian chain rule, MSH Hermite blend near `x→1` verified.
|
||||
|
||||
DoF rule: regularization **must not** change `n_equations()`.
|
||||
|
||||
@@ -85,4 +87,4 @@ Details: [flow-regularization.md](./flow-regularization.md).
|
||||
|
||||
- Unit / FD continuity tests: `crates/core/src/smoothing.rs` (`#[cfg(test)]`).
|
||||
- Component Jacobian health: `check_jacobian_health` (Story 0.1 audit harness).
|
||||
- Envelope CI gate: Story 0.5.
|
||||
- Envelope CI gate: Story **0.5 landed** — `cargo test -p entropyk-components --test jacobian_health_sweep` (see [`../audits/jacobian-health-report.md`](../audits/jacobian-health-report.md)).
|
||||
|
||||
54
docs/components/pipe.md
Normal file
54
docs/components/pipe.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Pipe
|
||||
|
||||
Config type: `"Pipe"`
|
||||
Source: `crates/components/src/pipe.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Fluid duct with **friction pressure drop** (Darcy/Colebrook-style or equivalent implementation) and near-isenthalpic or adiabatic energy transport:
|
||||
|
||||
```
|
||||
ΔP = f(L, D, ε, Re, ṁ, ρ)
|
||||
h_out ≈ h_in (or with small heat loss if modeled)
|
||||
```
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Pressure-drop residual + energy residual (typically **2** on a series branch).
|
||||
|
||||
### Ports
|
||||
|
||||
`inlet` / `outlet`.
|
||||
|
||||
### Calibration
|
||||
|
||||
`z_dp` (or equivalent) scales ΔP when exposed via calib — default **1.0**.
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `length_m` | length | required |
|
||||
| `diameter_m` | inner diameter | required |
|
||||
| `roughness_m` | roughness | small metal default |
|
||||
| fluid | water / refrigerant path | from circuit |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
**Conduite** avec pertes de charge et transport d’enthalpie.
|
||||
|
||||
### Calibration
|
||||
|
||||
Facteur de perte de charge (défaut 1).
|
||||
|
||||
### JSON
|
||||
|
||||
Voir EN.
|
||||
56
docs/components/pump.md
Normal file
56
docs/components/pump.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Pump
|
||||
|
||||
Config type: `"Pump"`
|
||||
Source: `crates/components/src/pump.rs`
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
Liquid pump with **head/power curves** vs volume flow and speed. Typestate connect pattern like Fan.
|
||||
|
||||
```
|
||||
ΔP = ρ · g · H(Q, N)
|
||||
Ẇ = f_power(Q, N)
|
||||
```
|
||||
|
||||
### Residuals & `n_equations()`
|
||||
|
||||
Head residual + energy/power residual as implemented; see source `n_equations()`.
|
||||
|
||||
### Ports
|
||||
|
||||
`inlet` / `outlet` on liquid (brine/water) branch.
|
||||
|
||||
### DoF warning
|
||||
|
||||
Do **not** impose `m_flow_kg_s` on a BrineSource **and** a pump curve on the same branch without freeing one — over-constrained loop.
|
||||
|
||||
### Calibration
|
||||
|
||||
Curve scale factors when exposed (default 1.0).
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| curves / preset | H–Q map | required |
|
||||
| speed | operating speed | – |
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But & modèle
|
||||
|
||||
**Pompe** liquide sur courbes H–Q.
|
||||
|
||||
### Attention DoF
|
||||
|
||||
Ne pas imposer ṁ Source **et** courbe pompe sur la même branche.
|
||||
|
||||
### Ports / JSON
|
||||
|
||||
Voir EN.
|
||||
44
docs/components/reversing-valve.md
Normal file
44
docs/components/reversing-valve.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ReversingValve / FourWayValve
|
||||
|
||||
Config types: `"ReversingValve"`, `"FourWayValve"`
|
||||
Source: reversing valve module
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose & model
|
||||
|
||||
4-way reversing valve for heat-pump mode swap (heating ↔ cooling). Routes compressor discharge/suction between indoor and outdoor exchangers according to `mode` (or boolean heat/cool).
|
||||
|
||||
Ideal model: port permutation with negligible ΔP/Δh; real models may add leakage or pressure drop.
|
||||
|
||||
### Residuals
|
||||
|
||||
Port coupling residuals matching the selected flow graph for the active mode.
|
||||
|
||||
### Ports
|
||||
|
||||
Four refrigerant ports (naming depends on implementation: e.g. compressor discharge/suction, indoor, outdoor).
|
||||
|
||||
### JSON (main)
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `mode` / `reversing_mode` | heat / cool | – |
|
||||
|
||||
### Calibration
|
||||
|
||||
Usually none; treat as topology switch.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
**Vanne 4 voies** pour inverser le cycle PAC.
|
||||
|
||||
### JSON
|
||||
|
||||
Mode chaud / froid. Voir EN.
|
||||
93
docs/components/screw-economizer-compressor.md
Normal file
93
docs/components/screw-economizer-compressor.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# ScrewEconomizerCompressor / ScrewCompressor
|
||||
|
||||
Config types: `"ScrewEconomizerCompressor"`, `"ScrewCompressor"`
|
||||
Source: `crates/components/src/screw_economizer_compressor.rs`
|
||||
Polynomials: `Polynomial2D` bilinear SST/SDT
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Purpose
|
||||
|
||||
Twin-screw compressor with **economizer injection** port. Manufacturer performance as **bi-quadratic (bilinear) maps** of SST and SDT.
|
||||
|
||||
### Performance maps
|
||||
|
||||
```
|
||||
ṁ_suction = z_flow · (a00 + a10·SST + a01·SDT + a11·SST·SDT)
|
||||
Ẇ_shaft = z_power · (b00 + b10·SST + b01·SDT + b11·SST·SDT)
|
||||
ṁ_eco ≈ eco_fraction · ṁ_suction (or eco poly)
|
||||
```
|
||||
|
||||
JSON coefficient names (CLI):
|
||||
|
||||
| Mass flow | Power |
|
||||
|-----------|-------|
|
||||
| `mf_a00`, `mf_a10`, `mf_a01`, `mf_a11` | `pw_b00`, `pw_b10`, `pw_b01`, `pw_b11` |
|
||||
|
||||
### Built-in presets
|
||||
|
||||
| `preset` | Meaning |
|
||||
|----------|---------|
|
||||
| `bitzer_generic_200kw` | Bitzer-like ~200 kW R134a map |
|
||||
| `grasso_generic_200kw` | Grasso-like ~200 kW map |
|
||||
| (empty) | generic defaults |
|
||||
|
||||
Explicit `mf_*` / `pw_*` **override** preset values.
|
||||
|
||||
### Ports
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| `suction` / `inlet` | main suction |
|
||||
| `discharge` / `outlet` | discharge |
|
||||
| `economizer` / `eco` | intermediate injection |
|
||||
|
||||
### Other parameters
|
||||
|
||||
| Key | Meaning | Default |
|
||||
|-----|---------|---------|
|
||||
| `frequency_hz` | drive frequency | 50 |
|
||||
| `nominal_frequency_hz` | rated f | 50 |
|
||||
| `mechanical_efficiency` | η_mech | 0.92 |
|
||||
| `economizer_fraction` | eco flow share | from preset |
|
||||
|
||||
### Calibration Z
|
||||
|
||||
| Factor | Default |
|
||||
|--------|---------|
|
||||
| `z_flow` | **1.0** |
|
||||
| `z_flow_eco` | **1.0** |
|
||||
| `z_power` | **1.0** |
|
||||
| `z_etav` | 1.0 |
|
||||
|
||||
### UI
|
||||
|
||||
- Tab **General**: frequency, efficiency, preset
|
||||
- Tab **Map (polynomials)**: mf_a** / pw_b** with defaults filled from preset
|
||||
- Help documents the bilinear formula
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### But
|
||||
|
||||
Compresseur **à vis** 3 ports + injection économiseur.
|
||||
|
||||
### Carte
|
||||
|
||||
Polynôme **bilinéaire** SST/SDT pour ṁ et puissance. Presets Bitzer / Grasso.
|
||||
|
||||
### Coeffs JSON
|
||||
|
||||
`mf_a00…a11` (débit), `pw_b00…b11` (puissance).
|
||||
|
||||
### Calibration
|
||||
|
||||
Z = **1** par défaut.
|
||||
|
||||
### Ports
|
||||
|
||||
Aspiration, refoulement, économiseur.
|
||||
157
docs/components/thermal-load.md
Normal file
157
docs/components/thermal-load.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# ThermalLoad
|
||||
|
||||
> Cold-side receiver of a physical inter-circuit thermal coupling.
|
||||
> Récepteur côté froid d'un couplage thermique physique inter-circuits.
|
||||
|
||||
---
|
||||
|
||||
## EN
|
||||
|
||||
### Physical model
|
||||
|
||||
`ThermalLoad` models a hydronic load segment — e.g. the cooling-water side of
|
||||
a shared heat exchanger — that receives an **externally-determined heat rate
|
||||
Q [W]** from the solver's thermal-coupling layer.
|
||||
|
||||
It follows the BOLT/Modelica boundary pattern
|
||||
(`BOLT.BoundaryNode.Coolant.Source → HX → Sink`): the loop's pressure and
|
||||
inlet temperature are fixed by **boundary components**, not by the load:
|
||||
|
||||
```text
|
||||
BrineSource(P_set, T_in) ──edge──▶ ThermalLoad ──edge──▶ BrineSink(P_back, T free)
|
||||
```
|
||||
|
||||
The outlet temperature is **emergent**: `T_out = T_in + Q / (ṁ·cp)` (the sink
|
||||
temperature must be left free — do not set `t_set_c` on the `BrineSink`, or
|
||||
the loop becomes over-determined).
|
||||
|
||||
### Residual equations — `n_equations() = 2`
|
||||
|
||||
```text
|
||||
r0: ṁ − ṁ_design (imposed design flow)
|
||||
r1: ṁ_design·(h_out − h_in) − Q_ext (energy balance, Q_ext = state[q_idx])
|
||||
```
|
||||
|
||||
The energy balance uses the *design* flow (a constant): r0 already pins
|
||||
`ṁ = ṁ_design`, and the constant form keeps the block linear and structurally
|
||||
nonsingular even when the initializer starts at `ṁ = 0`.
|
||||
|
||||
`Q_ext` is read from the per-coupling state unknown wired by
|
||||
`System::finalize()` via `Component::set_external_heat_index`. Unwired ⇒
|
||||
`Q_ext = 0` (adiabatic pass-through).
|
||||
|
||||
### DoF balance (water loop)
|
||||
|
||||
Unknowns: 1 ṁ (shared branch) + 2×(P,h) + 1 Q = 6.
|
||||
Equations: BrineSource 2 + ThermalLoad 2 + BrineSink 1 (T free) + coupling 1 = 6. ✓
|
||||
|
||||
### Jacobian
|
||||
|
||||
Exact and analytic (the whole block is linear): unit entry on the ṁ row,
|
||||
`±ṁ_design` on r1's enthalpy columns, `−1` on the coupling Q column.
|
||||
|
||||
### Operational states
|
||||
|
||||
| State | r0 | r1 |
|
||||
|---|---|---|
|
||||
| `On` | `ṁ = ṁ_design` | `ṁ_design·Δh = Q` |
|
||||
| `Bypass` | `ṁ = ṁ_design` | `h_out = h_in` (adiabatic) |
|
||||
| `Off` | `ṁ = 0` | `h_out = h_in` |
|
||||
|
||||
### `measure_output`
|
||||
|
||||
| Kind | Value |
|
||||
|---|---|
|
||||
| `Capacity` / `HeatTransferRate` | `abs(Q_ext)` [W] |
|
||||
| `MassFlowRate` | inlet ṁ [kg/s] |
|
||||
|
||||
### `energy_transfers`
|
||||
|
||||
`(Q_ext, 0)` — heat added *to* the component is positive. The component is
|
||||
**excluded from cycle-performance aggregation** (`counts_in_cycle_performance()
|
||||
= false`): the absorbed Q is the primary cycle's rejected duty, not extra
|
||||
cooling capacity. It still participates in per-component First Law validation.
|
||||
|
||||
### JSON parameters (CLI)
|
||||
|
||||
| Parameter | Unit | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `mass_flow_kg_s` | kg/s | `0.5` | Imposed design mass flow (must be > 0) |
|
||||
|
||||
### Usage with `thermal_couplings`
|
||||
|
||||
```json
|
||||
"components": [
|
||||
{ "type": "BrineSource", "name": "cw_in", "fluid": "Water",
|
||||
"p_set_bar": 2.0, "t_set_c": 30.0 },
|
||||
{ "type": "ThermalLoad", "name": "cw_load", "mass_flow_kg_s": 0.9 },
|
||||
{ "type": "BrineSink", "name": "cw_out", "fluid": "Water", "p_back_bar": 2.0 }
|
||||
],
|
||||
...
|
||||
"thermal_couplings": [
|
||||
{ "hot_circuit": 0, "cold_circuit": 1, "ua": 5000.0, "efficiency": 1.0,
|
||||
"hot_component": "cond", "cold_component": "cw_load" }
|
||||
]
|
||||
```
|
||||
|
||||
The coupling owns one unknown Q closed against the hot component's measured
|
||||
duty (`Q = η·duty_hot` via `measure_output(Capacity)`); the `ThermalLoad`
|
||||
consumes Q in r1, so the heat genuinely crosses the circuit boundary and the
|
||||
First Law closes across circuits. Keep the water-loop conditions consistent
|
||||
with the hot component's secondary stream (same T_in, ṁ, cp).
|
||||
|
||||
Full example: `crates/cli/examples/chiller_r410a_coupled_water_loop.json`.
|
||||
|
||||
---
|
||||
|
||||
## FR
|
||||
|
||||
### Modèle physique
|
||||
|
||||
`ThermalLoad` modélise un segment de charge hydronique — par exemple le côté
|
||||
eau de refroidissement d'un échangeur partagé — qui reçoit une **puissance
|
||||
thermique Q [W] déterminée extérieurement** par la couche de couplage
|
||||
thermique du solveur.
|
||||
|
||||
Il suit le pattern de frontières BOLT/Modelica
|
||||
(`BOLT.BoundaryNode.Coolant.Source → HX → Sink`) : la pression et la
|
||||
température d'entrée de la boucle sont fixées par des **composants
|
||||
frontières**, pas par la charge :
|
||||
|
||||
```text
|
||||
BrineSource(P_set, T_in) ──arête──▶ ThermalLoad ──arête──▶ BrineSink(P_back, T libre)
|
||||
```
|
||||
|
||||
La température de sortie est **émergente** : `T_out = T_in + Q / (ṁ·cp)`
|
||||
(laisser la température du sink libre — ne pas mettre `t_set_c` sur le
|
||||
`BrineSink`, sinon la boucle est surdéterminée).
|
||||
|
||||
### Équations résiduelles — `n_equations() = 2`
|
||||
|
||||
Débit imposé (`ṁ = ṁ_design`) + bilan d'énergie
|
||||
(`ṁ_design·(h_out − h_in) = Q_ext`). Le bilan utilise le débit de *conception*
|
||||
(constante) : r0 épingle déjà ṁ, et la forme constante garde le bloc linéaire
|
||||
et structurellement non singulier même si l'initialiseur part de ṁ = 0.
|
||||
|
||||
`Q_ext` est lu depuis l'inconnu d'état du couplage, câblé par
|
||||
`System::finalize()` via `set_external_heat_index`. Non câblé ⇒ `Q_ext = 0`
|
||||
(passage adiabatique).
|
||||
|
||||
### Bilan DoF (boucle d'eau)
|
||||
|
||||
Inconnues : 1 ṁ (branche partagée) + 2×(P,h) + 1 Q = 6.
|
||||
Équations : BrineSource 2 + ThermalLoad 2 + BrineSink 1 (T libre) + couplage 1 = 6. ✓
|
||||
|
||||
### `energy_transfers` et performance
|
||||
|
||||
`(Q_ext, 0)` — chaleur reçue positive. Le composant est **exclu de
|
||||
l'agrégation de performance du cycle** (`counts_in_cycle_performance() =
|
||||
false`) : le Q absorbé est la puissance rejetée du cycle primaire. Il
|
||||
participe néanmoins à la validation du 1er principe par composant.
|
||||
|
||||
### Paramètres JSON (CLI)
|
||||
|
||||
`mass_flow_kg_s` (kg/s, défaut 0.5) — débit de conception imposé.
|
||||
P et T_in se règlent sur le `BrineSource` ; P_back sur le `BrineSink`.
|
||||
|
||||
Exemple complet : `crates/cli/examples/chiller_r410a_coupled_water_loop.json`.
|
||||
@@ -6,6 +6,10 @@
|
||||
- **[EXAMPLES_FULL.md](../EXAMPLES_FULL.md)** - Deep-dive usage examples for all platforms.
|
||||
- **[README.md](../README.md)** - Main project overview and getting started guide.
|
||||
- **[TUTORIAL.md](./TUTORIAL.md)** - Step-by-step guide to using Entropyk (CLI & UI).
|
||||
- **[rating-and-seasonal-metrics.md](./rating-and-seasonal-metrics.md)** - Seasonal & part-load ratings (IPLV/NPLV/ESEER/SCOP/SEER) and the modular `rate` command.
|
||||
- **[model-ir.md](./model-ir.md)** - The unified Model IR (schema-versioned JSON) shared by CLI, web UI and bindings: `schema_version`, `subsystems`/`instances` templates, co-solved `controls`, and the `schema` command.
|
||||
- **[simulation-capabilities.md](./simulation-capabilities.md)** - Bilingual (EN/FR) analysis of what Entropyk can simulate and how it compares to Modelica-style system modeling.
|
||||
- **[components/README.md](./components/README.md)** - Bilingual (EN/FR) per-component reference: physics, residual equations, ports, DoF, calibration, and JSON parameters for every CLI component.
|
||||
- **[EXAMPLES.md](../EXAMPLES.md)** - Component-level usage snippets (Legacy).
|
||||
- **[README_STORY_1_3.md](../README_STORY_1_3.md)** - Technical details of Port and Connection system implementation.
|
||||
|
||||
|
||||
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.
|
||||
546
docs/model-ir.schema.json
Normal file
546
docs/model-ir.schema.json
Normal file
@@ -0,0 +1,546 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ScenarioConfig",
|
||||
"description": "Root configuration for a simulation scenario.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"fluid"
|
||||
],
|
||||
"properties": {
|
||||
"circuits": {
|
||||
"description": "Circuit configurations.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/CircuitConfig"
|
||||
}
|
||||
},
|
||||
"connections": {
|
||||
"description": "External connections between instance ports (and/or literal `component:port` endpoints). Resolved and routed into the appropriate circuit during flattening.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/EdgeConfig"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"description": "Steady-state control loops (co-solved saturated-PI controllers).",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ControlConfig"
|
||||
}
|
||||
},
|
||||
"fluid": {
|
||||
"description": "Fluid name (e.g., \"R134a\", \"R410A\", \"R744\").",
|
||||
"type": "string"
|
||||
},
|
||||
"fluid_backend": {
|
||||
"description": "Fluid backend to use (e.g., \"CoolProp\", \"Test\"). Defaults to \"Test\".",
|
||||
"default": null,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"instances": {
|
||||
"description": "Instantiations of `subsystems` templates. Each instance is expanded into concrete, name-prefixed components/edges in its target circuit.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/InstanceConfig"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Optional metadata.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"name": {
|
||||
"description": "Scenario name.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"schema_version": {
|
||||
"description": "Model IR schema version (`\"1\"` legacy flat graph, `\"2\"` adds controls/subsystems/instances/connections). Absent ⇒ `\"1\"`.",
|
||||
"default": "1",
|
||||
"type": "string"
|
||||
},
|
||||
"solver": {
|
||||
"description": "Solver configuration.",
|
||||
"default": {
|
||||
"max_iterations": 100,
|
||||
"strategy": "fallback",
|
||||
"timeout_ms": 0,
|
||||
"tolerance": 1e-6,
|
||||
"verbose": false
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SolverConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subsystems": {
|
||||
"description": "Reusable subsystem templates (parameterized assemblies of components + internal edges, exposing a reduced external port set). Flattened into `circuits` at load time — the solver never sees the hierarchy.",
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/SubsystemTemplate"
|
||||
}
|
||||
},
|
||||
"thermal_couplings": {
|
||||
"description": "Thermal couplings between circuits.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ThermalCouplingConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"ActuatorConfig": {
|
||||
"description": "Reference to a manipulated actuator on a component.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"component",
|
||||
"factor",
|
||||
"max",
|
||||
"min"
|
||||
],
|
||||
"properties": {
|
||||
"component": {
|
||||
"description": "Name of the component carrying the actuator.",
|
||||
"type": "string"
|
||||
},
|
||||
"factor": {
|
||||
"description": "Calibration Z-factor to manipulate: `z_flow`, `z_dp`, `z_ua`, `z_power`, or `z_etav` (BOLT `Z_flow_suc`, `Z_dpc`, `Z_UA`, `Z_power`; legacy `f_*` names accepted).",
|
||||
"type": "string"
|
||||
},
|
||||
"initial": {
|
||||
"description": "Initial actuator value (nominal, e.g. 1.0).",
|
||||
"default": 1.0,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"max": {
|
||||
"description": "Upper bound for the actuator.",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"min": {
|
||||
"description": "Lower bound for the actuator.",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CircuitConfig": {
|
||||
"description": "Configuration for a single circuit.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"components"
|
||||
],
|
||||
"properties": {
|
||||
"components": {
|
||||
"description": "Components in this circuit.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ComponentConfig"
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edge connections between components.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/EdgeConfig"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"description": "Circuit ID (default: 0).",
|
||||
"default": 0,
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"initial_state": {
|
||||
"description": "Initial state for edges.",
|
||||
"default": null,
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/InitialStateConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComponentConfig": {
|
||||
"description": "Configuration for a component.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"air_inlet_temp_c": {
|
||||
"description": "Air inlet temperature in Celsius.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"condenser_bank": {
|
||||
"description": "Condenser bank spec identifier (used for creating multiple instances).",
|
||||
"default": null,
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CondenserBankConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"fan_speed": {
|
||||
"description": "Fan speed ratio (0.0 to 1.0).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"n_air_exponent": {
|
||||
"description": "Air side heat transfer exponent.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"name": {
|
||||
"description": "Component name for referencing in edges.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Component type (e.g., \"Compressor\", \"Condenser\", \"Evaporator\", \"ExpansionValve\", \"HeatExchanger\").",
|
||||
"type": "string"
|
||||
},
|
||||
"ua_nominal_kw_k": {
|
||||
"description": "Nominal UA value (kW/K). Maps to ua_nominal_kw_k.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"CondenserBankConfig": {
|
||||
"description": "Configuration for a condenser bank (multi-circuit, multi-coil).",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"circuits",
|
||||
"coils_per_circuit"
|
||||
],
|
||||
"properties": {
|
||||
"circuits": {
|
||||
"description": "Number of circuits.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"coils_per_circuit": {
|
||||
"description": "Number of coils per circuit.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ControlConfig": {
|
||||
"description": "A steady-state control loop declaration.\n\nCurrently supports the `SaturatedController` type: a saturated-PI loop with anti-windup that is co-solved inside the Newton system. It drives a measured plant output (`measure`) to `target` by manipulating an actuator factor (`actuator`) within `[min, max]` bounds.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actuator",
|
||||
"id",
|
||||
"measure",
|
||||
"target"
|
||||
],
|
||||
"properties": {
|
||||
"actuator": {
|
||||
"description": "The manipulated actuator.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ActuatorConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"band": {
|
||||
"description": "Saturation band half-width `Q > 0`.",
|
||||
"default": 1.0,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"gain": {
|
||||
"description": "Loop gain `K` (sign must match actuator→output sensitivity).",
|
||||
"default": 1.0,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"id": {
|
||||
"description": "Unique identifier for this control loop.",
|
||||
"type": "string"
|
||||
},
|
||||
"measure": {
|
||||
"description": "The measured plant output to regulate.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MeasureConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"smooth_eps": {
|
||||
"description": "If set, uses the C¹ smooth saturation with this `eps`; otherwise hard clamp.",
|
||||
"default": null,
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"target": {
|
||||
"description": "Target value for the measured output (SI units: W, K, Pa, kg/s).",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"type": {
|
||||
"description": "Controller type. Only `\"SaturatedController\"` is supported today.",
|
||||
"default": "SaturatedController",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"EdgeConfig": {
|
||||
"description": "Configuration for an edge between components.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"to"
|
||||
],
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "Source component and port (e.g., \"comp1:outlet\").",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "Target component and port (e.g., \"cond1:inlet\").",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InitialStateConfig": {
|
||||
"description": "Initial state configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enthalpy_kj_kg": {
|
||||
"description": "Initial enthalpy in kJ/kg.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"pressure_bar": {
|
||||
"description": "Initial pressure in bar.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
},
|
||||
"temperature_k": {
|
||||
"description": "Initial temperature in Kelvin.",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InstanceConfig": {
|
||||
"description": "An instantiation of a [`SubsystemTemplate`].",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"of"
|
||||
],
|
||||
"properties": {
|
||||
"circuit": {
|
||||
"description": "Target circuit id the expanded components/edges are placed into (default 0).",
|
||||
"default": 0,
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"name": {
|
||||
"description": "Unique instance name; becomes the `\"{name}.\"` prefix on expanded components.",
|
||||
"type": "string"
|
||||
},
|
||||
"of": {
|
||||
"description": "Name of the template to instantiate (key in `subsystems`).",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"description": "Parameter overrides for this instance (override template defaults).",
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"MeasureConfig": {
|
||||
"description": "Reference to a measurable component output.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"component",
|
||||
"output"
|
||||
],
|
||||
"properties": {
|
||||
"component": {
|
||||
"description": "Name of the component whose output is measured.",
|
||||
"type": "string"
|
||||
},
|
||||
"output": {
|
||||
"description": "Output kind: one of `capacity`, `heatTransferRate`, `superheat`, `subcooling`, `saturationTemperature`, `massFlowRate`, `pressure`, `temperature`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SolverConfig": {
|
||||
"description": "Solver configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_iterations": {
|
||||
"description": "Maximum iterations.",
|
||||
"default": 100,
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"strategy": {
|
||||
"description": "Solver strategy: \"newton\", \"picard\", or \"fallback\".",
|
||||
"default": "fallback",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout_ms": {
|
||||
"description": "Timeout in milliseconds (0 = no timeout).",
|
||||
"default": 0,
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"tolerance": {
|
||||
"description": "Convergence tolerance.",
|
||||
"default": 1e-6,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"verbose": {
|
||||
"description": "Enable verbose output.",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SubsystemTemplate": {
|
||||
"description": "A reusable subsystem template: a parameterized assembly of components and internal edges, exposing a reduced set of external ports.\n\nThis is Modelica-style `model` composition. Templates are declared once under `subsystems` and instantiated any number of times via `instances`, each with its own parameter overrides. At load time every instance is **flattened** into the flat `circuits/components/edges` graph (component names prefixed by `\"{instance}.\"`), so the solver and every downstream consumer are unchanged.\n\n# Parameter substitution Any component parameter whose JSON value is a string of the form `\"$name\"` is replaced by the resolved value of parameter `name` (instance override, falling back to the template default in `params`). Because `ua`, `secondary_*`, `isentropic_efficiency`, `t_cond_k`, … all flow through the component `params` catch-all, this covers essentially every physical knob.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"components"
|
||||
],
|
||||
"properties": {
|
||||
"components": {
|
||||
"description": "Components inside the template (names are local to the template).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ComponentConfig"
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"description": "Internal edges between the template's own components.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/EdgeConfig"
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"description": "Default parameter values, overridable per instance.",
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"ports": {
|
||||
"description": "External ports: maps an exposed port name to an internal `\"component:port\"` endpoint. `connections` reference these as `\"{instance}.{external_port}\"`.",
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ThermalCouplingConfig": {
|
||||
"description": "Thermal coupling configuration between two circuits.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cold_circuit",
|
||||
"hot_circuit",
|
||||
"ua"
|
||||
],
|
||||
"properties": {
|
||||
"cold_circuit": {
|
||||
"description": "Cold circuit ID.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"efficiency": {
|
||||
"description": "Heat exchanger efficiency (0.0 to 1.0).",
|
||||
"default": 0.95,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"hot_circuit": {
|
||||
"description": "Hot circuit ID.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ua": {
|
||||
"description": "Thermal conductance in W/K.",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
763
docs/rating-and-seasonal-metrics.md
Normal file
763
docs/rating-and-seasonal-metrics.md
Normal file
@@ -0,0 +1,763 @@
|
||||
# Seasonal & Part-Load Ratings — IPLV / NPLV / ESEER / SCOP / SEER
|
||||
|
||||
> Modular, data-driven standardized ratings for chillers and heat pumps.
|
||||
>
|
||||
> Crate: [`entropyk::rating`](../crates/entropyk/src/rating.rs) · CLI:
|
||||
> [`entropyk-cli rate`](../crates/cli/src/rate.rs)
|
||||
|
||||
This document is the definitive reference for how Entropyk turns a machine's
|
||||
part-load performance into the **standardized seasonal metrics** used to qualify
|
||||
HVAC/R equipment, and — most importantly — how those standards are kept
|
||||
**modular** so that when a norm is revised (AHRI, Eurovent and EN periodically
|
||||
re-fit their coefficients and climate tables) you can adapt **without recompiling**.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Concepts & scope](#1-concepts--scope)
|
||||
2. [Design philosophy: standards are data, not code](#2-design-philosophy-standards-are-data-not-code)
|
||||
3. [Library API (`entropyk::rating`)](#3-library-api-entropykrating)
|
||||
- [`PartLoadStandard`](#31-partloadstandard)
|
||||
- [`PartLoadEfficiencies`](#32-partloadefficiencies)
|
||||
- [`BinClimateStandard`, `BinPerformance`, `scop`](#33-binclimatestandard-binperformance-and-scop)
|
||||
- [`RatingCondition`](#34-ratingcondition)
|
||||
- [`RatingError`](#35-ratingerror)
|
||||
- [Legacy constants](#36-legacy-constants)
|
||||
4. [The `rate` CLI command](#4-the-rate-cli-command)
|
||||
- [Configuration schema](#41-configuration-schema)
|
||||
- [Selecting the standard (precedence)](#42-selecting-the-standard-precedence)
|
||||
- [How a part-load point is solved](#43-how-a-part-load-point-is-solved)
|
||||
- [Report format](#44-report-format)
|
||||
- [Worked examples](#45-worked-examples)
|
||||
5. [Custom standard JSON schema](#5-custom-standard-json-schema)
|
||||
6. [How to change a standard when the norm changes](#6-how-to-change-a-standard-when-the-norm-changes)
|
||||
7. [The `scop` / `seer` CLI commands (bin method)](#7-the-scop--seer-cli-commands-bin-method)
|
||||
- [Configuration schema](#71-configuration-schema)
|
||||
- [How a bin is solved](#72-how-a-bin-is-solved)
|
||||
- [Report format](#73-report-format)
|
||||
- [Worked example (SCOP)](#74-worked-example-scop)
|
||||
- [Adding another bin-based metric](#75-adding-another-bin-based-metric)
|
||||
8. [Validation & testing](#8-validation--testing)
|
||||
9. [Reference tables](#9-reference-tables)
|
||||
10. [Standards references](#10-standards-references)
|
||||
|
||||
---
|
||||
|
||||
## 1. Concepts & scope
|
||||
|
||||
A single full-load efficiency figure (EER or COP at rated conditions) is a poor
|
||||
predictor of real energy use, because equipment spends most of its life at part
|
||||
load. Regulators therefore define **seasonal** metrics that weight several
|
||||
part-load operating points:
|
||||
|
||||
| Metric | Family | Standard | What it weights |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **IPLV** — Integrated Part Load Value | Weighted part-load | AHRI 550/590 | EER at 100/75/50/25 % load |
|
||||
| **NPLV** — Non-standard Part Load Value | Weighted part-load | AHRI 550/590 | Same formula as IPLV, taken at non-standard conditions |
|
||||
| **ESEER** — European SEER | Weighted part-load | Eurovent | EER at 100/75/50/25 % load, different weights |
|
||||
| **SCOP** — Seasonal COP | Temperature-bin | EN 14825 | COP across a climate's hourly temperature bins |
|
||||
| **SEER** — Seasonal EER | Temperature-bin / weighted | EN 14825 | EER across the cooling season |
|
||||
|
||||
Entropyk models these as **two calculation families**:
|
||||
|
||||
- **Weighted part-load** (`IPLV`, `NPLV`, `ESEER`, and weighted SEER variants):
|
||||
a weighted average of efficiencies measured at a small set of load fractions.
|
||||
Implemented by [`PartLoadStandard`](#31-partloadstandard).
|
||||
- **Temperature-bin** (`SCOP`, bin-based `SEER`): energy summed over a climate's
|
||||
hourly temperature bins, `Σ(h·demand) / Σ(h·demand/COP)`. Implemented by
|
||||
[`BinClimateStandard`](#33-binclimatestandard-binperformance-and-scop) +
|
||||
[`scop`](#33-binclimatestandard-binperformance-and-scop).
|
||||
|
||||
> **Purity guarantee.** All the metric arithmetic takes *already-solved*
|
||||
> efficiency values as input. Computing the part-load operating points — by
|
||||
> re-solving the coupled cycle at each rating condition — is the caller's job (the
|
||||
> `rate` CLI command does this). This keeps the metric math deterministic and
|
||||
> trivially unit-testable, and keeps the *physics* honest: nothing is imposed, all
|
||||
> efficiencies emerge from a genuine coupled solve.
|
||||
|
||||
---
|
||||
|
||||
## 2. Design philosophy: standards are data, not code
|
||||
|
||||
Rating standards change. AHRI and Eurovent re-fit their part-load weights; EN
|
||||
14825 revises its climate bins and reference seasons. If those coefficients were
|
||||
hard-coded arithmetic, every revision would mean a code change, a rebuild, and a
|
||||
re-release.
|
||||
|
||||
Instead, a standard in Entropyk is a **plain data record**:
|
||||
|
||||
- A weighted part-load standard is just `{ load_fractions, weights }` plus a name
|
||||
and a citation.
|
||||
- A climate standard is just `{ bins }` (temperature + annual hours) plus a name
|
||||
and a citation.
|
||||
|
||||
Because they are data, a standard can be:
|
||||
|
||||
- a **built-in preset** (a function returning the record — e.g.
|
||||
`PartLoadStandard::ahri_550_590_iplv()`),
|
||||
- looked up by **name** in a small registry (`PartLoadStandard::builtin("iplv")`),
|
||||
- or **loaded from a JSON file** at run time and validated
|
||||
(`serde_json::from_str::<PartLoadStandard>(...)?.validate()?`).
|
||||
|
||||
The number of load points is **arbitrary** (four for AHRI/Eurovent, but any N is
|
||||
accepted), so a future standard with more or fewer points needs no code change.
|
||||
|
||||
---
|
||||
|
||||
## 3. Library API (`entropyk::rating`)
|
||||
|
||||
```rust
|
||||
use entropyk::rating::{
|
||||
PartLoadStandard, PartLoadEfficiencies, BinClimateStandard,
|
||||
BinPerformance, TemperatureBin, RatingCondition, RatingError, scop,
|
||||
};
|
||||
```
|
||||
|
||||
### 3.1 `PartLoadStandard`
|
||||
|
||||
The data-driven weighting standard behind IPLV / NPLV / ESEER / weighted SEER.
|
||||
|
||||
```rust
|
||||
pub struct PartLoadStandard {
|
||||
pub name: String, // e.g. "AHRI 550/590 IPLV"
|
||||
pub reference: String, // citation / provenance (optional in JSON)
|
||||
pub load_fractions: Vec<f64>, // e.g. [1.0, 0.75, 0.50, 0.25]
|
||||
pub weights: Vec<f64>, // same length; must sum to 1.0
|
||||
}
|
||||
```
|
||||
|
||||
**Construction & validation**
|
||||
|
||||
| Method | Description |
|
||||
| :--- | :--- |
|
||||
| `new(name, reference, load_fractions, weights) -> Result<Self, RatingError>` | Build **and** validate in one step. |
|
||||
| `validate() -> Result<(), RatingError>` | Non-empty, equal-length fractions/weights, weights sum to `1.0` within `1e-6`. |
|
||||
| `len() -> usize` / `is_empty() -> bool` | Number of load points. |
|
||||
|
||||
**Integration**
|
||||
|
||||
| Method | Description |
|
||||
| :--- | :--- |
|
||||
| `integrate(&self, efficiencies: &[f64]) -> Result<f64, RatingError>` | Weighted sum `Σ eᵢ·wᵢ`. `efficiencies` must match `weights` in count **and order**. |
|
||||
|
||||
**Built-in presets & registry**
|
||||
|
||||
| Method | Returns |
|
||||
| :--- | :--- |
|
||||
| `ahri_550_590_iplv()` | AHRI IPLV/NPLV — weights `[0.01, 0.42, 0.45, 0.12]`. |
|
||||
| `eurovent_eseer()` | Eurovent ESEER — weights `[0.03, 0.33, 0.41, 0.23]`. |
|
||||
| `builtin(id: &str) -> Option<Self>` | Case/separator-insensitive lookup (see ids below). |
|
||||
| `builtin_ids() -> &'static [&'static str]` | `["iplv", "nplv", "eseer"]` (for help text). |
|
||||
|
||||
Recognised `builtin` ids (case-insensitive; spaces, `-` and `/` are normalised to
|
||||
`_`):
|
||||
|
||||
| Id(s) | Standard |
|
||||
| :--- | :--- |
|
||||
| `iplv`, `nplv`, `ahri`, `ahri_550_590`, `ahri_551_591` | AHRI 550/590 IPLV |
|
||||
| `eseer`, `eurovent` | Eurovent ESEER |
|
||||
|
||||
**Example — built-in**
|
||||
|
||||
```rust
|
||||
let iplv = PartLoadStandard::ahri_550_590_iplv();
|
||||
let value = iplv.integrate(&[4.0, 5.0, 6.0, 5.5]).unwrap();
|
||||
// value == 0.01*4.0 + 0.42*5.0 + 0.45*6.0 + 0.12*5.5
|
||||
```
|
||||
|
||||
**Example — custom, from JSON, no code change**
|
||||
|
||||
```rust
|
||||
let json = r#"{
|
||||
"name": "Custom SEER weighting",
|
||||
"reference": "EN 14825 moderate cooling season (illustrative)",
|
||||
"load_fractions": [1.0, 0.74, 0.47, 0.21],
|
||||
"weights": [0.03, 0.27, 0.41, 0.29]
|
||||
}"#;
|
||||
let std: PartLoadStandard = serde_json::from_str(json)?;
|
||||
std.validate()?;
|
||||
let seer = std.integrate(&[3.0, 4.0, 5.0, 4.5])?;
|
||||
```
|
||||
|
||||
### 3.2 `PartLoadEfficiencies`
|
||||
|
||||
A convenience holder for the classic **four-point** case, ordered
|
||||
`[100 %, 75 %, 50 %, 25 %]`.
|
||||
|
||||
```rust
|
||||
pub struct PartLoadEfficiencies { pub at_100: f64, pub at_75: f64, pub at_50: f64, pub at_25: f64 }
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
| :--- | :--- |
|
||||
| `new(at_100, at_75, at_50, at_25)` | Construct. |
|
||||
| `as_array() -> [f64; 4]` | `[100, 75, 50, 25]` order. |
|
||||
| `integrate(&self, std: &PartLoadStandard) -> Result<f64, RatingError>` | **Modular entry point** — apply any four-point standard. |
|
||||
| `iplv() -> f64` | Convenience wrapper (AHRI weights). |
|
||||
| `nplv() -> f64` | Alias of `iplv()` (identical arithmetic). |
|
||||
| `eseer() -> f64` | Convenience wrapper (Eurovent weights). |
|
||||
|
||||
```rust
|
||||
let eff = PartLoadEfficiencies::new(4.0, 5.2, 6.1, 5.4);
|
||||
let iplv = eff.iplv(); // legacy helper
|
||||
let iplv2 = eff.integrate(&PartLoadStandard::ahri_550_590_iplv())?; // modular
|
||||
assert!((iplv - iplv2).abs() < 1e-12);
|
||||
```
|
||||
|
||||
### 3.3 `BinClimateStandard`, `BinPerformance` and `scop`
|
||||
|
||||
The temperature-bin method behind **SCOP** (and bin-based **SEER**).
|
||||
|
||||
```rust
|
||||
pub struct TemperatureBin { pub temperature_c: f64, pub hours: f64 }
|
||||
|
||||
pub struct BinClimateStandard {
|
||||
pub name: String,
|
||||
pub reference: String,
|
||||
pub bins: Vec<TemperatureBin>,
|
||||
}
|
||||
|
||||
pub struct BinPerformance {
|
||||
pub bin: TemperatureBin,
|
||||
pub demand_w: f64, // building demand at this bin temperature [W]
|
||||
pub cop: f64, // machine COP at this bin temperature
|
||||
}
|
||||
|
||||
pub fn scop(bins: &[BinPerformance]) -> Option<f64>;
|
||||
```
|
||||
|
||||
`BinClimateStandard` methods:
|
||||
|
||||
| Method | Description |
|
||||
| :--- | :--- |
|
||||
| `en_14825_average()` | EN 14825 **average** heating season (Strasbourg reference, 4910 h). |
|
||||
| `builtin(id) -> Option<Self>` | Ids: `en_14825_average`, `average`, `en14825`. |
|
||||
| `builtin_ids() -> &'static [&'static str]` | For help text. |
|
||||
| `total_hours() -> f64` | Sum of `hours` across all bins. |
|
||||
| `validate() -> Result<(), RatingError>` | At least one bin. |
|
||||
|
||||
`scop(...)` computes:
|
||||
|
||||
```
|
||||
SCOP = Σ (hoursⱼ · demandⱼ) / Σ (hoursⱼ · demandⱼ / COPⱼ)
|
||||
```
|
||||
|
||||
Bins with non-positive hours, demand or COP are ignored. Returns `None` if no
|
||||
valid bin remains (total electrical energy would be zero).
|
||||
|
||||
**Example — SCOP over a climate**
|
||||
|
||||
```rust
|
||||
let climate = BinClimateStandard::en_14825_average();
|
||||
let bins: Vec<BinPerformance> = climate.bins.iter().map(|&bin| BinPerformance {
|
||||
bin,
|
||||
demand_w: building_demand_at(bin.temperature_c), // your building model
|
||||
cop: machine_cop_at(bin.temperature_c), // from a coupled solve
|
||||
}).collect();
|
||||
let scop = scop(&bins).expect("valid climate");
|
||||
```
|
||||
|
||||
> **Swapping the climate** is just choosing a different `BinClimateStandard` —
|
||||
> a preset, or a JSON file with a different `bins` table (warmer/colder season, a
|
||||
> revised reference table, or a local TMY-derived climate). The `scop`
|
||||
> arithmetic never changes.
|
||||
|
||||
### 3.4 `RatingCondition`
|
||||
|
||||
Named full-load rating envelopes (secondary-fluid temperatures) used to define
|
||||
where the 100 % point is measured. Refrigerant regimes emerge from the coupled
|
||||
heat-exchanger solve, so only the **secondary** conditions are prescribed.
|
||||
|
||||
```rust
|
||||
pub struct RatingCondition {
|
||||
pub name: &'static str,
|
||||
pub evap_secondary_out_c: f64, // chilled-fluid supply
|
||||
pub evap_secondary_in_c: f64, // chilled-fluid return
|
||||
pub cond_secondary_in_c: f64, // condenser/gas-cooler entering
|
||||
}
|
||||
```
|
||||
|
||||
Provided constants:
|
||||
|
||||
| Constant | Chilled supply/return | Condenser entering |
|
||||
| :--- | :--- | :--- |
|
||||
| `AHRI_550_590_WATER_COOLED` | 6.7 / 12.2 °C | 29.4 °C (water) |
|
||||
| `AHRI_550_590_AIR_COOLED` | 6.7 / 12.2 °C | 35.0 °C (air) |
|
||||
| `EN_14511_WATER_COOLED_A` | 7.0 / 12.0 °C | 30.0 °C (water) |
|
||||
| `EN_14511_AIR_COOLED_A` | 7.0 / 12.0 °C | 35.0 °C (air) |
|
||||
|
||||
### 3.5 `RatingError`
|
||||
|
||||
```rust
|
||||
pub enum RatingError {
|
||||
Empty, // no load points
|
||||
LengthMismatch { fractions: usize, weights: usize },
|
||||
WeightsNotNormalized { sum: f64 }, // |Σw − 1| > 1e-6
|
||||
EfficiencyCountMismatch { expected: usize, got: usize },
|
||||
}
|
||||
```
|
||||
|
||||
Implements `Display` + `std::error::Error`, so it composes with `?` and `anyhow`.
|
||||
|
||||
### 3.6 Legacy constants
|
||||
|
||||
Kept for backward compatibility (the presets are built from them):
|
||||
|
||||
| Constant | Value |
|
||||
| :--- | :--- |
|
||||
| `STANDARD_LOAD_FRACTIONS` | `[1.0, 0.75, 0.50, 0.25]` |
|
||||
| `IPLV_WEIGHTS` | `[0.01, 0.42, 0.45, 0.12]` |
|
||||
| `ESEER_WEIGHTS` | `[0.03, 0.33, 0.41, 0.23]` |
|
||||
| `EN_14825_AVERAGE_BINS` | 26 bins, −10…+15 °C, sum 4910 h |
|
||||
|
||||
---
|
||||
|
||||
## 4. The `rate` CLI command
|
||||
|
||||
`rate` re-solves a full cycle configuration at each standardized part-load point
|
||||
and aggregates the resulting EERs into the chosen seasonal metric. Every point is
|
||||
a **genuine coupled solve** (condensing/evaporating pressures, capacity and power
|
||||
all emerge from the heat-exchanger ↔ secondary balance), so the integrated value
|
||||
is real simulation output — not imposed design points.
|
||||
|
||||
```bash
|
||||
# Pretty table to stdout
|
||||
entropyk-cli rate --config crates/cli/examples/rate_chiller_iplv_ahri.json
|
||||
|
||||
# Also write a JSON report
|
||||
entropyk-cli rate -c rate_config.json -o rate_report.json
|
||||
|
||||
# Machine-readable JSON only
|
||||
entropyk-cli --quiet rate -c rate_config.json
|
||||
```
|
||||
|
||||
### 4.1 Configuration schema
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"base_config": "chiller_r134a_emergent_pressure.json", // required; path to a `run` scenario
|
||||
|
||||
// --- standard selection (see precedence below); all optional ---
|
||||
"metric": "iplv", // built-in enum: "iplv" (default) | "eseer"
|
||||
"standard_name": "eseer", // built-in id (overrides `metric`)
|
||||
"standard_file": "my_standard.json", // custom PartLoadStandard JSON (overrides `standard_name`)
|
||||
"standard": { // inline PartLoadStandard (highest precedence)
|
||||
"name": "…", "reference": "…",
|
||||
"load_fractions": [1.0, 0.75, 0.5, 0.25],
|
||||
"weights": [0.01, 0.42, 0.45, 0.12]
|
||||
},
|
||||
|
||||
// --- the part-load points (usually four) ---
|
||||
"points": [
|
||||
{
|
||||
"load_fraction": 1.00, // required; used to match the standard's fractions
|
||||
"condenser_secondary_inlet_c": 29.4, // optional overrides applied to the base config
|
||||
"evaporator_secondary_inlet_c": 12.0,
|
||||
"condenser_secondary_mass_flow_kg_s": 0.36,
|
||||
"evaporator_secondary_mass_flow_kg_s": 0.48,
|
||||
"compressor_speed_hz": 50.0
|
||||
}
|
||||
// … 0.75, 0.50, 0.25 …
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**`base_config`** is resolved relative to the rating file's directory when not
|
||||
absolute. **`standard_file`** is likewise resolved relative to the rating file.
|
||||
|
||||
**Per-point overrides** are written into the base config's matching components
|
||||
before re-solving. They map to these component params (see the
|
||||
[`run` documentation](./CLI_TUTORIAL.md) for the full param list):
|
||||
|
||||
| Rating override | Component(s) matched | Param set |
|
||||
| :--- | :--- | :--- |
|
||||
| `condenser_secondary_inlet_c` | `Condenser` | `secondary_inlet_temp_c` |
|
||||
| `condenser_secondary_mass_flow_kg_s` | `Condenser` | `secondary_mass_flow_kg_s` |
|
||||
| `evaporator_secondary_inlet_c` | `Evaporator`, `FloodedEvaporator` | `secondary_inlet_temp_c` |
|
||||
| `evaporator_secondary_mass_flow_kg_s` | `Evaporator`, `FloodedEvaporator` | `secondary_mass_flow_kg_s` |
|
||||
| `compressor_speed_hz` | `IsentropicCompressor`, `Compressor` | `speed_hz` |
|
||||
|
||||
Overrides apply to **all** matching components (single-circuit chillers, the
|
||||
common case, have exactly one each). Anything not overridden is inherited from the
|
||||
base config, so every point runs the *same machine* through a different operating
|
||||
envelope.
|
||||
|
||||
### 4.2 Selecting the standard (precedence)
|
||||
|
||||
Highest to lowest:
|
||||
|
||||
1. **`standard`** — an inline `PartLoadStandard` object.
|
||||
2. **`standard_file`** — a JSON `PartLoadStandard`, resolved relative to the config.
|
||||
3. **`standard_name`** — a built-in id (`iplv`, `nplv`, `eseer`, …).
|
||||
4. **`metric`** — the enum (`iplv` default, or `eseer`).
|
||||
|
||||
The resolved standard is **validated** before use; an invalid standard (bad
|
||||
lengths, weights not summing to 1, unknown id) fails fast with a clear error.
|
||||
|
||||
### 4.3 How a part-load point is solved
|
||||
|
||||
For each point, in parallel (rayon):
|
||||
|
||||
1. **Clone** the base `ScenarioConfig`.
|
||||
2. **Apply overrides** to matching components' params.
|
||||
3. **Serialize** and **re-solve** via the standard `run` pipeline
|
||||
(`simulate_from_json`).
|
||||
4. **Extract** `EER = performance.cop`, plus cooling capacity and power.
|
||||
|
||||
Aggregation then matches each of the standard's `load_fractions` to the **nearest**
|
||||
part-load point (within a tolerance of `0.02`) and integrates with the standard's
|
||||
weights. If any required load fraction has no converged point within tolerance,
|
||||
the integrated value is reported as `null` while the per-point rows are still
|
||||
shown.
|
||||
|
||||
### 4.4 Report format
|
||||
|
||||
```rust
|
||||
pub struct RateReport {
|
||||
pub base_config: String,
|
||||
pub metric: String, // the standard's NAME, e.g. "AHRI 550/590 IPLV"
|
||||
pub standard_reference: String, // the standard's citation
|
||||
pub points: Vec<RatePointResult>, // ordered by descending load fraction
|
||||
pub integrated_value: Option<f64>,
|
||||
}
|
||||
|
||||
pub struct RatePointResult {
|
||||
pub load_fraction: f64,
|
||||
pub status: String, // "converged" | "timeout" | "non_converged" | "error"
|
||||
pub eer: Option<f64>,
|
||||
pub q_cooling_kw: Option<f64>,
|
||||
pub power_kw: Option<f64>,
|
||||
pub error: Option<String>, // present only on failure
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 Worked examples
|
||||
|
||||
**Built-in AHRI IPLV** — [`rate_chiller_iplv_ahri.json`](../crates/cli/examples/rate_chiller_iplv_ahri.json):
|
||||
|
||||
```bash
|
||||
entropyk-cli rate -c crates/cli/examples/rate_chiller_iplv_ahri.json
|
||||
```
|
||||
|
||||
```text
|
||||
Base config: chiller_r134a_emergent_pressure.json
|
||||
Standard: AHRI 550/590 IPLV
|
||||
Reference: AHRI Standard 550/590 — Integrated Part Load Value
|
||||
|
||||
Load[%] Status Q_cool[kW] Power[kW] EER[-]
|
||||
──────────────────────────────────────────────────────────
|
||||
100 converged 7.439 1.819 4.090
|
||||
75 converged 6.363 1.117 5.697
|
||||
50 converged 4.938 0.542 9.108
|
||||
25 converged 2.938 0.223 13.183
|
||||
──────────────────────────────────────────────────────────
|
||||
|
||||
AHRI 550/590 IPLV = 8.114
|
||||
```
|
||||
|
||||
**External custom standard** —
|
||||
[`rate_chiller_custom_standard.json`](../crates/cli/examples/rate_chiller_custom_standard.json)
|
||||
loads its weights from [`standard_custom_iplv.json`](../crates/cli/examples/standard_custom_iplv.json)
|
||||
(`standard_file`). Same points, revised weights `[0.02, 0.40, 0.45, 0.13]`:
|
||||
|
||||
```text
|
||||
Standard: Custom IPLV (revised weights)
|
||||
…
|
||||
Custom IPLV (revised weights) = 8.173
|
||||
```
|
||||
|
||||
The only difference between the two runs is the weighting file — the physics
|
||||
(per-point EERs) is identical.
|
||||
|
||||
---
|
||||
|
||||
## 5. Custom standard JSON schema
|
||||
|
||||
A `PartLoadStandard` file (used by `standard_file`, or inline under `standard`):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Custom IPLV (revised weights)",
|
||||
"reference": "Illustrative custom weighting — edit to track a revised norm",
|
||||
"load_fractions": [1.0, 0.75, 0.50, 0.25],
|
||||
"weights": [0.02, 0.40, 0.45, 0.13]
|
||||
}
|
||||
```
|
||||
|
||||
Rules enforced by `validate()`:
|
||||
|
||||
- `load_fractions` and `weights` are **non-empty** and have the **same length**.
|
||||
- `weights` **sum to 1.0** within `1e-6`.
|
||||
- `reference` is optional (defaults to empty).
|
||||
- Any number of points is allowed — the `rate` command matches them to your
|
||||
configured `points` by nearest `load_fraction`.
|
||||
|
||||
---
|
||||
|
||||
## 6. How to change a standard when the norm changes
|
||||
|
||||
This is the core requirement the design serves. Three escalating options, none of
|
||||
which require touching the solver or the CLI:
|
||||
|
||||
**Option A — no rebuild: ship a JSON file.**
|
||||
Edit or add a `PartLoadStandard` JSON file and point `standard_file` at it (or
|
||||
inline it under `standard`). This is the recommended path for tracking a revised
|
||||
norm in the field.
|
||||
|
||||
```jsonc
|
||||
// rate_config.json
|
||||
{ "base_config": "chiller.json", "standard_file": "ahri_550_590_2026.json", "points": [ … ] }
|
||||
```
|
||||
|
||||
**Option B — a new built-in preset (small code change, for standards you ship).**
|
||||
Add a constructor and register it in the lookup, in
|
||||
[`crates/entropyk/src/rating.rs`](../crates/entropyk/src/rating.rs):
|
||||
|
||||
```rust
|
||||
impl PartLoadStandard {
|
||||
pub fn ahri_550_590_2026_iplv() -> Self {
|
||||
Self {
|
||||
name: "AHRI 550/590-2026 IPLV".into(),
|
||||
reference: "AHRI Standard 550/590 (2026 revision)".into(),
|
||||
load_fractions: vec![1.0, 0.75, 0.50, 0.25],
|
||||
weights: vec![/* new coefficients */],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// in builtin():
|
||||
"iplv_2026" | "ahri_550_590_2026" => Some(Self::ahri_550_590_2026_iplv()),
|
||||
```
|
||||
|
||||
Then add the id to `builtin_ids()` and a unit test asserting the weights and that
|
||||
they sum to 1.
|
||||
|
||||
**Option C — revise an existing preset's coefficients.**
|
||||
If a standard simply re-fits the weights of an existing metric, update the
|
||||
corresponding constant (`IPLV_WEIGHTS` / `ESEER_WEIGHTS`) or the preset body, and
|
||||
update the affected unit tests. The `validate()` invariant (weights sum to 1)
|
||||
guards against typos.
|
||||
|
||||
> Whichever option you choose, the surrounding cycle solve, the `rate` command and
|
||||
> the report format stay untouched — only the *data* changes.
|
||||
|
||||
---
|
||||
|
||||
## 7. The `scop` / `seer` CLI commands (bin method)
|
||||
|
||||
While `rate` integrates a handful of weighted part-load points, **SCOP** (seasonal
|
||||
heating) and **SEER** (seasonal cooling) follow the EN 14825 **bin method**: the
|
||||
machine is re-solved at every outdoor temperature bin of a climate, the building
|
||||
demand is derived from a linear load line, and the season is aggregated by an
|
||||
energy ratio.
|
||||
|
||||
```bash
|
||||
# Seasonal heating COP (SCOP) — defaults to the EN 14825 average heating season
|
||||
cargo run -p entropyk-cli -- scop --config crates/cli/examples/scop_heatpump_r134a.json
|
||||
|
||||
# Seasonal cooling EER (SEER) — requires an explicit cooling climate
|
||||
cargo run -p entropyk-cli -- seer --config my_seer.json --output seer.json
|
||||
|
||||
# Machine-readable JSON only
|
||||
cargo run -p entropyk-cli -- --quiet scop --config scop_heatpump_r134a.json
|
||||
```
|
||||
|
||||
Both commands share one module (`entropyk_cli::seasonal`) and one config shape;
|
||||
the subcommand only fixes the *mode* (which heat-exchanger side the outdoor
|
||||
temperature drives and which duty is the useful output):
|
||||
|
||||
| Mode | Subcommand | Useful duty | Outdoor side (default) | Default climate |
|
||||
|--------|------------|--------------------|------------------------|-----------------------|
|
||||
| SCOP | `scop` | heating (condenser)| evaporator | `en_14825_average` |
|
||||
| SEER | `seer` | cooling (evaporator)| condenser | *(explicit required)* |
|
||||
|
||||
### 7.1 Configuration schema
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"base_config": "heatpump_r134a_air_source.json", // base run scenario (rel. to this file)
|
||||
|
||||
// Climate selection — precedence: climate > climate_file > climate_name > default
|
||||
"climate_name": "en_14825_average", // built-in BinClimateStandard id
|
||||
// "climate_file": "my_cooling_bins.json", // external BinClimateStandard JSON
|
||||
// "climate": { ...inline BinClimateStandard... },
|
||||
|
||||
"outdoor_side": "evaporator", // "evaporator" | "condenser"; defaults by mode
|
||||
|
||||
// Building load line (linear demand vs outdoor temperature)
|
||||
"design_load_w": 6000.0, // demand [W] at design_outdoor_c
|
||||
"design_outdoor_c": -10.0, // outdoor temp where demand = design_load_w
|
||||
"threshold_outdoor_c": 16.0, // outdoor temp where demand reaches 0 (default 16)
|
||||
|
||||
"cd": 0.25, // cycling degradation coeff (PLF = 1 - Cd*(1-CR)); default 0.25
|
||||
"backup_cop": 1.0 // COP of electric backup for heating deficits; default 1.0
|
||||
}
|
||||
```
|
||||
|
||||
Only `base_config`, `design_load_w` and `design_outdoor_c` are required. The
|
||||
outdoor temperature is applied to the matching heat exchanger's
|
||||
`secondary_inlet_temp_c` — so the base config's secondary stream **flow** and
|
||||
**cp** (e.g. air cp 1006 J/kg·K) are kept and only the inlet temperature is swept.
|
||||
|
||||
### 7.2 How a bin is solved
|
||||
|
||||
For each bin at outdoor temperature `Tj`, with hours `h`:
|
||||
|
||||
1. **Demand** from the linear load line —
|
||||
heating: `demand = design_load · (threshold − Tj) / (threshold − design)`;
|
||||
cooling: `demand = design_load · (Tj − threshold) / (design − threshold)`,
|
||||
clamped at ≥ 0.
|
||||
2. **Full-load solve** — the base cycle is re-solved with the outdoor-side
|
||||
`secondary_inlet_temp_c = Tj` (a genuine coupled solve, *no* injected regime),
|
||||
giving `Q_full`, `W_full`, `COP_full`.
|
||||
3. **Capacity ratio** `CR = demand / Q_full`.
|
||||
- `CR ≤ 1` (part load): cycling degradation `PLF = 1 − Cd·(1 − CR)`,
|
||||
`COP_bin = COP_full · PLF`.
|
||||
- `CR > 1` (heating deficit): electric backup covers `demand − Q_full`,
|
||||
`COP_bin = demand / (W_full + (demand − Q_full)/backup_cop)`; the reported
|
||||
`backup` fraction is `(demand − Q_full)/demand`. For cooling the capacity
|
||||
simply caps at `Q_full`.
|
||||
4. **Aggregate** — seasonal useful energy `Σ demand·h`, seasonal electric energy
|
||||
`Σ (demand/COP_bin)·h`, and `SCOP/SEER = useful / electric`.
|
||||
|
||||
Bins whose demand is 0 (above the threshold for heating) contribute no energy and
|
||||
are skipped in the aggregation. If a *demanded* bin fails to converge, the
|
||||
integrated value is reported as unavailable rather than silently biased.
|
||||
|
||||
### 7.3 Report format
|
||||
|
||||
`SeasonalReport` (also the `--quiet` / `--output` JSON):
|
||||
|
||||
| Field | Meaning |
|
||||
|--------------------------|-----------------------------------------------------|
|
||||
| `base_config` | base scenario path |
|
||||
| `metric` | `"SCOP"` or `"SEER"` |
|
||||
| `climate` | climate standard name |
|
||||
| `climate_reference` | standard citation |
|
||||
| `total_hours` | Σ bin hours |
|
||||
| `bins[]` | per-bin rows (see below) |
|
||||
| `seasonal_useful_kwh` | Σ demand·hours / 1000 |
|
||||
| `seasonal_electric_kwh` | Σ electric·hours / 1000 |
|
||||
| `integrated_value` | SCOP/SEER, or `null` if a demanded bin failed |
|
||||
|
||||
Each `bins[]` row: `temperature_c`, `hours`, `demand_w`, `capacity_w`,
|
||||
`full_cop`, `backup_fraction`, `effective_cop`.
|
||||
|
||||
### 7.4 Worked example (SCOP)
|
||||
|
||||
`crates/cli/examples/scop_heatpump_r134a.json` runs the emergent air-source heat
|
||||
pump (`heatpump_r134a_air_source.json`, evaporator = outdoor air) over the EN 14825
|
||||
average heating season:
|
||||
|
||||
```
|
||||
T[°C] h Demand[kW] Cap[kW] COP_full backup COP_eff
|
||||
-10.0 1 6.000 4.967 3.649 17% 2.506
|
||||
-7.0 24 5.308 5.419 3.775 0% 3.756
|
||||
2.0 320 3.231 6.918 4.169 0% 3.613
|
||||
7.0 326 2.077 7.840 4.392 0% 3.585
|
||||
15.0 74 0.231 9.445 4.751 0% 3.592
|
||||
|
||||
Seasonal useful: 12393.7 kWh Seasonal electric: 3441.1 kWh
|
||||
SCOP = 3.602
|
||||
```
|
||||
|
||||
Full-load COP climbs monotonically as the outdoor air warms (less lift); at the
|
||||
coldest bins the capacity falls short of demand and electric backup kicks in
|
||||
(non-zero `backup` %), which is exactly where the effective COP is lowest.
|
||||
|
||||
### 7.5 Adding another bin-based metric
|
||||
|
||||
Because the climate is a data-driven `BinClimateStandard`, adding a new season
|
||||
(SEER climate, a regional heating season, a future EN revision) means authoring a
|
||||
bin table — a preset, a built-in id, or an external JSON `climate_file` — with no
|
||||
code change. See §5 for the JSON schema (the same `BinClimateStandard` shape used
|
||||
by `climate_file`/`climate`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Validation & testing
|
||||
|
||||
The rating math is pure and exhaustively unit-tested. Run:
|
||||
|
||||
```bash
|
||||
# Library: metric math, presets, validation, registries (+ doctests)
|
||||
cargo test -p entropyk --lib rating
|
||||
cargo test -p entropyk --doc rating
|
||||
|
||||
# CLI: override application, standard resolution & precedence, aggregation
|
||||
cargo test -p entropyk-cli rate
|
||||
|
||||
# CLI seasonal: bin method demand/degradation/backup + end-to-end SCOP
|
||||
cargo test -p entropyk-cli --lib seasonal
|
||||
cargo test -p entropyk-cli --test single_run test_scop_command
|
||||
```
|
||||
|
||||
Notable guarantees covered by tests:
|
||||
|
||||
- Presets reproduce the legacy constants bit-for-bit; `integrate()` agrees with
|
||||
`iplv()`/`eseer()` to `1e-12`.
|
||||
- `validate()` rejects length mismatches, non-normalized weights and empty
|
||||
standards with the right `RatingError`.
|
||||
- `builtin()` lookup is case- and separator-insensitive.
|
||||
- Custom standards with a different point count integrate correctly.
|
||||
- CLI standard **precedence** (`standard` > `standard_file` > `standard_name` >
|
||||
`metric`) resolves as specified; unknown ids error clearly.
|
||||
- End-to-end: the emergent chiller re-solves four points and produces a genuine
|
||||
IPLV, with per-point EER increasing monotonically as the pressure lift falls.
|
||||
- Seasonal bin method: demand tracks the linear load line, cycling degradation and
|
||||
electric backup apply only where expected, and the end-to-end SCOP over the 26
|
||||
EN 14825 bins is a physical energy ratio bounded by the per-bin COPs.
|
||||
|
||||
> **Build note.** In environments where the Python bindings can't build, restrict
|
||||
> to the relevant crates: `cargo test -p entropyk -p entropyk-cli …` (avoid
|
||||
> `--workspace`).
|
||||
|
||||
---
|
||||
|
||||
## 9. Reference tables
|
||||
|
||||
**AHRI 550/590 IPLV weights** (`IPLV_WEIGHTS`)
|
||||
|
||||
| Load | 100 % | 75 % | 50 % | 25 % |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| Weight | 0.01 | 0.42 | 0.45 | 0.12 |
|
||||
|
||||
**Eurovent ESEER weights** (`ESEER_WEIGHTS`)
|
||||
|
||||
| Load | 100 % | 75 % | 50 % | 25 % |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| Weight | 0.03 | 0.33 | 0.41 | 0.23 |
|
||||
|
||||
**EN 14825 average heating season** (`EN_14825_AVERAGE_BINS`) — 26 bins from
|
||||
−10 °C to +15 °C, total **4910 h** (Strasbourg reference; EU 813/2013 Annex III
|
||||
Table 5 / EN 14825:2018 Table A.4).
|
||||
|
||||
**Secondary-fluid cp defaults** (used by `run` when only a mass flow is given):
|
||||
air `1006 J/(kg·K)`, water `4186 J/(kg·K)`. Provide `secondary_cp_j_per_kgk` or
|
||||
`secondary_capacity_rate_w_per_k` to override.
|
||||
|
||||
---
|
||||
|
||||
## 10. Standards references
|
||||
|
||||
- **AHRI Standard 550/590 (2023)** — *Performance Rating of Water-Chilling and
|
||||
Heat Pump Water-Heating Packages Using the Vapor Compression Cycle.*
|
||||
- **AHRI Standard 551/591 (SI)** — metric counterpart of 550/590.
|
||||
- **Eurovent** — ESEER definition for liquid chilling packages.
|
||||
- **EN 14511** — rating conditions for air conditioners, liquid chilling packages
|
||||
and heat pumps.
|
||||
- **EN 14825:2018** — *Testing and rating at part load conditions and calculation
|
||||
of seasonal performance* (SCOP/SEER bin method).
|
||||
- **Commission Regulation (EU) No 813/2013**, Annex III — European reference
|
||||
heating seasons (average/warmer/colder).
|
||||
|
||||
---
|
||||
|
||||
### See also
|
||||
|
||||
- [`crates/entropyk/src/rating.rs`](../crates/entropyk/src/rating.rs) — the module.
|
||||
- [`crates/cli/src/rate.rs`](../crates/cli/src/rate.rs) — the `rate` command.
|
||||
- [CLI_TUTORIAL.md](./CLI_TUTORIAL.md) — building and running `run` scenarios (the
|
||||
`base_config` for a rating).
|
||||
- [DOCUMENTATION.md](../DOCUMENTATION.md) — the definitive technical manual.
|
||||
131
docs/simulation-capabilities.md
Normal file
131
docs/simulation-capabilities.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Entropyk — Simulation Capabilities & Modelica Comparison / Capacités de simulation & comparaison Modelica
|
||||
|
||||
> Bilingual document (English first, French below) / Document bilingue (anglais d'abord, français ensuite).
|
||||
>
|
||||
> This analysis is **code-grounded**: every claim is traceable to the Rust sources in `crates/`.
|
||||
> Cette analyse est **fondée sur le code** : chaque affirmation est traçable dans les sources Rust de `crates/`.
|
||||
|
||||
---
|
||||
|
||||
## EN — What Entropyk can simulate
|
||||
|
||||
### 1. Nature of the tool
|
||||
|
||||
Entropyk is a **steady-state, equation-based** thermodynamic-cycle simulator for refrigeration, heat-pump and HVAC systems. It is *not* a general-purpose DAE engine (like OpenModelica); it is a **domain-specific algebraic solver** built around a component graph.
|
||||
|
||||
- **Solving paradigm**: each component contributes residual equations `r(x) = 0` and an **analytic Jacobian**. The global system `F(x) = 0` is solved with Newton–Raphson, Picard (sequential substitution), a Newton-homotopy continuation, and a `FallbackSolver` that chains them. (`crates/solver/src/strategies/`)
|
||||
- **State vector**: per flow edge `(ṁ, P, h)`; series branches share a single mass-flow unknown after a topology presolve (`|B| + 2·|E|` layout). (`crates/solver/src/system.rs`)
|
||||
- **Fluid properties**: `CoolPropBackend` (production), `TestBackend` (mock/ideal), plus `TabularBackend` and `IncompressibleBackend` in the library. (`crates/fluids/`)
|
||||
|
||||
### 2. System-level capabilities
|
||||
|
||||
| Capability | Status | Evidence |
|
||||
|---|---|---|
|
||||
| Coupled algebraic solving with analytic Jacobian | ✅ | `Component::jacobian_entries`, `System::assemble_jacobian` |
|
||||
| Explicit degrees-of-freedom balancing | ✅ | `System::validate_inverse_control_dof`, mass-flow presolve |
|
||||
| Emergent pressures (SST/SDT float from HX↔secondary balance) | ✅ | `Condenser::with_emergent_pressure`, `Evaporator` `superheat_regulated` |
|
||||
| Multi-circuit networks (up to 5) | ✅ | `CircuitId 0..4`, per-circuit flow edges |
|
||||
| Inter-circuit thermal coupling (`thermal_couplings`) | ✅ physical mode | Set `hot_component` (duty provider, e.g. an ε-NTU `Condenser`) + `cold_component` (a `ThermalLoad` receiver): the coupling owns one unknown Q closed against the hot component's measured duty (`Q = η·duty_hot`) and injects Q into the cold loop's energy balance — First Law closes across circuits (`chiller_r410a_coupled_water_loop.json`). Entries without both fields are inert (Q pinned to 0, CLI warns). |
|
||||
| Secondary-stream heat exchange (air/water, ε-NTU) | ✅ | `Condenser`/`Evaporator` `secondary_inlet_temp_c` + `secondary_mass_flow_kg_s` + `secondary_cp_j_per_kgk`; emergent capacity/COP (e.g. `chiller_r410a_full_physics.json` → COP≈4.98, First Law closes) |
|
||||
| Steady-state supervisory control, co-solved | ✅ | `SaturatedController` (offset-free PI, anti-windup) |
|
||||
| Override / selector priority (min/max) | ✅ | `override_network.rs`, smoothed `softMin/softMax`, activation continuation |
|
||||
| Inverse / one-shot calibration | ✅ | `inverse/calibration.rs`, Z-factors `z_flow, z_dp, z_ua, z_power, z_etav` |
|
||||
| Free actuators (e.g. EXV orifice opening) | ✅ | `System` free-actuator DoF (+1 unknown / +1 component eq) |
|
||||
| Reusable parameterized subsystems (flattened) | ✅ | CLI `subsystems`/`instances`/`connections` → `flatten_instances()` |
|
||||
| Hierarchical composition in Rust | ⚠️ partial | `MacroComponent` couples P/h at ports; ṁ continuity not yet coupled |
|
||||
| Normative seasonal rating (IPLV/ESEER/SCOP/SEER) | ✅ | CLI `rate`, `scop`, `seer` (re-solves the cycle per load point/bin) |
|
||||
| ON/OFF/BYPASS staging | ✅ (pre-solve) | `OperationalState`, `circuits[].enabled` |
|
||||
| BOLT-style inline spec/probe nodes | ✅ | `Anchor` (= BOLT `BoundaryNode.Refrigerant.Node`): probe mode (DoF-neutral) or one imposed spec — signed `superheat_k` (dTsh, <0 = subcooling), `quality`, `t_c`/`t_k`, `p_bar` (`docs/components/anchor-heat-source.md`) |
|
||||
| Inline heat injection (fixed or coupling-linked) | ✅ | `HeatSource` (= BOLT `BoundaryNode.Heat.Source`): `q_w` fixed duty + optional coupling-driven Q (motor-cooling pattern), exact Jacobian, excluded from COP aggregation (`chiller_r410a_bolt_nodes.json`) |
|
||||
| Boundary-imposed loop flow | ✅ | `BrineSource`/`BrineSink` `m_flow_kg_s` (= BOLT `Vd_fixed=true`) |
|
||||
|
||||
### 3. Can we build models as complex as Modelica?
|
||||
|
||||
**Yes for the targeted domain, with caveats.** Entropyk reproduces the *ideas* that make Modelica HVAC libraries powerful:
|
||||
|
||||
- equation-based components with a coupled solve,
|
||||
- reusable parameterized subsystems,
|
||||
- co-solved supervisory control (setpoint tracking + min/max override priority, exactly the "BOLT"-style chiller control referenced in the code),
|
||||
- inverse calibration and normative rating.
|
||||
|
||||
You can assemble multi-circuit chillers/heat-pumps with emergent pressures, secondary-fluid–coupled heat exchangers, economizer screw compressors, liquid injection, head-pressure control, free-cooling, and a supervisory control tree — comparable in *structural* complexity to a Modelica chiller model.
|
||||
|
||||
### 4. Gaps vs Modelica (be precise)
|
||||
|
||||
- **Steady-state only** — no time integration, no `der(x)`, no dynamic states (thermal masses, sensor lags). Everything targets `F(x)=0`.
|
||||
- **No event handling** — no zero-crossing / reinit; `ModeSwitch` (hysteresis, min-runtime) lives *outside* the Newton solve and is time-based.
|
||||
- **Causal edge connectors** — state is carried on edges/branches as `(ṁ,P,h)`; continuity is enforced by component residuals referencing state indices (a TESPy-like pattern), not Modelica acausal connectors with shared variables and language-level connection equations.
|
||||
- **Hierarchy is mostly compile-time / flattened** — the CLI flattens subsystems before solving; nested subsystems inside the solver require Rust `MacroComponent`, whose port ṁ-continuity is still incomplete.
|
||||
- **`thermal_couplings` transfers a measured duty, not a UA·ΔT law** — the physical mode closes `Q = η·duty_hot` (the hot component's ε-NTU duty) into a `ThermalLoad` receiver; the `ua` field is not used as a coupling conductance. For a genuinely two-sided UA·LMTD interface exchanger, use a two-sided `HeatExchanger`/BPHX or a secondary stream instead. Legacy entries without `hot_component`/`cold_component` are inert (Q pinned to 0, CLI warns).
|
||||
- **CLI fluids limited** to CoolProp/Test (Tabular/Incompressible exist in the lib but are not wired to the CLI).
|
||||
- **No user-authored arbitrary equations** — new behaviour requires implementing the `Component` trait in Rust.
|
||||
|
||||
### 5. One-sentence verdict
|
||||
|
||||
Entropyk is closer to a **TESPy/BOLT-style steady-state cycle solver with co-solved algebraic supervisory control and inverse calibration** than to a general Modelica DAE engine: excellent structural parity for HVAC/refrigeration cycles, but without transients, events, or acausal language-level connectors.
|
||||
|
||||
---
|
||||
|
||||
## FR — Ce qu'Entropyk permet de simuler
|
||||
|
||||
### 1. Nature de l'outil
|
||||
|
||||
Entropyk est un simulateur de **cycles thermodynamiques en régime permanent (steady-state)**, basé sur les **équations**, pour la réfrigération, les pompes à chaleur et le CVC. Ce n'est *pas* un moteur DAE généraliste (comme OpenModelica) : c'est un **solveur algébrique dédié au domaine**, organisé autour d'un graphe de composants.
|
||||
|
||||
- **Paradigme de résolution** : chaque composant fournit des équations résiduelles `r(x) = 0` et un **Jacobien analytique**. Le système global `F(x) = 0` est résolu par Newton–Raphson, Picard (substitution séquentielle), une continuation par homotopie de Newton, et un `FallbackSolver` qui les enchaîne. (`crates/solver/src/strategies/`)
|
||||
- **Vecteur d'état** : par arête de flux `(ṁ, P, h)` ; les branches en série partagent un seul inconnu de débit après un presolve topologique (layout `|B| + 2·|E|`). (`crates/solver/src/system.rs`)
|
||||
- **Propriétés fluides** : `CoolPropBackend` (production), `TestBackend` (mock/gaz idéal), plus `TabularBackend` et `IncompressibleBackend` dans la bibliothèque. (`crates/fluids/`)
|
||||
|
||||
### 2. Capacités au niveau système
|
||||
|
||||
| Capacité | État | Preuve (code) |
|
||||
|---|---|---|
|
||||
| Résolution algébrique couplée + Jacobien analytique | ✅ | `Component::jacobian_entries`, `System::assemble_jacobian` |
|
||||
| Équilibrage explicite des degrés de liberté (DoF) | ✅ | `System::validate_inverse_control_dof`, presolve débit |
|
||||
| Pressions émergentes (SST/SDT flottent selon l'équilibre HX↔secondaire) | ✅ | `Condenser::with_emergent_pressure`, `Evaporator` `superheat_regulated` |
|
||||
| Réseaux multi-circuits (jusqu'à 5) | ✅ | `CircuitId 0..4`, arêtes de flux par circuit |
|
||||
| Couplage thermique inter-circuits (`thermal_couplings`) | ✅ mode physique | Renseigner `hot_component` (fournisseur de puissance, ex. `Condenser` ε-NTU) + `cold_component` (récepteur `ThermalLoad`) : le couplage porte un inconnu Q fermé sur la puissance mesurée du composant chaud (`Q = η·duty_hot`) et injecte Q dans le bilan d'énergie de la boucle froide — le 1er principe ferme entre circuits (`chiller_r410a_coupled_water_loop.json`). Les entrées sans ces deux champs sont inertes (Q épinglé à 0, la CLI avertit). |
|
||||
| Échange par flux secondaire (air/eau, ε-NTU) | ✅ | `Condenser`/`Evaporator` `secondary_inlet_temp_c` + `secondary_mass_flow_kg_s` + `secondary_cp_j_per_kgk` ; capacité/COP émergents (ex. `chiller_r410a_full_physics.json` → COP≈4.98, 1er principe fermé) |
|
||||
| Contrôle supervisoire steady-state, co-résolu | ✅ | `SaturatedController` (PI offset-free, anti-windup) |
|
||||
| Priorité par override / sélecteur (min/max) | ✅ | `override_network.rs`, `softMin/softMax` lissés, continuation d'activation |
|
||||
| Calibration inverse / one-shot | ✅ | `inverse/calibration.rs`, facteurs Z `z_flow, z_dp, z_ua, z_power, z_etav` |
|
||||
| Actuateurs libres (ex. ouverture orifice EXV) | ✅ | DoF free-actuator (+1 inconnu / +1 éq. composant) |
|
||||
| Sous-systèmes paramétrés réutilisables (aplatis) | ✅ | CLI `subsystems`/`instances`/`connections` → `flatten_instances()` |
|
||||
| Composition hiérarchique en Rust | ⚠️ partiel | `MacroComponent` couple P/h aux ports ; continuité ṁ pas encore couplée |
|
||||
| Rating saisonnier normatif (IPLV/ESEER/SCOP/SEER) | ✅ | CLI `rate`, `scop`, `seer` (re-résout le cycle par point/bin) |
|
||||
| Staging ON/OFF/BYPASS | ✅ (pré-solve) | `OperationalState`, `circuits[].enabled` |
|
||||
| Nœuds spec/sonde inline style BOLT | ✅ | `Anchor` (= `BoundaryNode.Refrigerant.Node` de BOLT) : mode sonde (neutre en DoF) ou une spec imposée — `superheat_k` signé (dTsh, <0 = sous-refroidissement), `quality`, `t_c`/`t_k`, `p_bar` (`docs/components/anchor-heat-source.md`) |
|
||||
| Injection de chaleur inline (fixe ou liée à un couplage) | ✅ | `HeatSource` (= `BoundaryNode.Heat.Source` de BOLT) : puissance fixe `q_w` + Q optionnel piloté par couplage (pattern refroidissement moteur), Jacobien exact, exclu de l'agrégation COP (`chiller_r410a_bolt_nodes.json`) |
|
||||
| Débit de boucle imposé à la frontière | ✅ | `BrineSource`/`BrineSink` `m_flow_kg_s` (= `Vd_fixed=true` de BOLT) |
|
||||
|
||||
### 3. Peut-on bâtir des modèles aussi complexes que Modelica ?
|
||||
|
||||
**Oui pour le domaine visé, avec des réserves.** Entropyk reproduit les *idées* qui font la puissance des bibliothèques HVAC Modelica :
|
||||
|
||||
- composants basés équations, résolus de façon couplée ;
|
||||
- sous-systèmes paramétrés réutilisables ;
|
||||
- contrôle supervisoire co-résolu (suivi de consigne + priorité min/max, exactement le style de contrôle chiller « BOLT » référencé dans le code) ;
|
||||
- calibration inverse et rating normatif.
|
||||
|
||||
On peut assembler des groupes froids / PAC multi-circuits avec pressions émergentes, échangeurs couplés au fluide secondaire, compresseurs à vis avec économiseur, injection liquide, contrôle de pression de refoulement, free-cooling et un arbre de contrôle supervisoire — d'une complexité *structurelle* comparable à un modèle chiller Modelica.
|
||||
|
||||
### 4. Écarts vs Modelica (précis)
|
||||
|
||||
- **Régime permanent uniquement** — pas d'intégration temporelle, pas de `der(x)`, pas d'états dynamiques (masses thermiques, retards capteurs). Tout vise `F(x)=0`.
|
||||
- **Pas de gestion d'événements** — pas de zero-crossing / reinit ; `ModeSwitch` (hystérésis, durée min) est *hors* du Newton et basé sur le temps.
|
||||
- **Connecteurs causaux par arête** — l'état est porté par les arêtes/branches en `(ṁ,P,h)` ; la continuité est imposée par des résidus composants référençant des indices d'état (motif type TESPy), pas des connecteurs acausaux Modelica à variables partagées.
|
||||
- **Hiérarchie surtout compile-time / aplatie** — la CLI aplatit les sous-systèmes avant résolution ; l'imbrication dans le solveur passe par `MacroComponent` (Rust), dont la continuité ṁ aux ports est incomplète.
|
||||
- **`thermal_couplings` transfère une puissance mesurée, pas une loi UA·ΔT** — le mode physique ferme `Q = η·duty_hot` (la puissance ε-NTU du composant chaud) dans un récepteur `ThermalLoad` ; le champ `ua` ne sert pas de conductance de couplage. Pour un échangeur d'interface UA·LMTD réellement à deux flux, utiliser un `HeatExchanger`/BPHX à deux flux ou un flux secondaire. Les entrées legacy sans `hot_component`/`cold_component` sont inertes (Q épinglé à 0, la CLI avertit).
|
||||
- **Fluides CLI limités** à CoolProp/Test (Tabular/Incompressible existent dans la lib mais ne sont pas branchés à la CLI).
|
||||
- **Pas d'équations utilisateur arbitraires** — tout nouveau comportement nécessite d'implémenter le trait `Component` en Rust.
|
||||
|
||||
### 5. Verdict en une phrase
|
||||
|
||||
Entropyk se rapproche d'un **solveur de cycles steady-state type TESPy/BOLT avec contrôle supervisoire algébrique co-résolu et calibration inverse**, plutôt que d'un moteur DAE Modelica généraliste : excellente parité structurelle pour les cycles CVC/réfrigération, mais sans transitoire, sans événements, ni connecteurs acausaux au niveau langage.
|
||||
|
||||
---
|
||||
|
||||
## Component catalog / Catalogue des composants
|
||||
|
||||
See per-component bilingual documentation in [`docs/components/`](./components/README.md).
|
||||
Voir la documentation bilingue par composant dans [`docs/components/`](./components/README.md).
|
||||
@@ -60,7 +60,7 @@ let compressor = Compressor::new(
|
||||
)?;
|
||||
```
|
||||
|
||||
**Calibration**: `f_m` (mass flow), `f_power` (power), `f_etav` (volumetric efficiency)
|
||||
**Calibration** (BOLT Z-factor convention): `z_flow` (mass flow, `Z_flow_suc`), `z_power` (power, `Z_power`), `z_etav` (volumetric efficiency). Legacy `f_m`/`f_power`/`f_etav` accepted in JSON.
|
||||
|
||||
---
|
||||
|
||||
@@ -220,7 +220,7 @@ let condenser = Condenser::new(10_000.0); // UA = 10 kW/K
|
||||
condenser.set_saturation_temp(323.15); // 50°C
|
||||
|
||||
// Calibration for matching real data
|
||||
condenser.set_calib(Calib { f_ua: 1.1, ..Default::default() });
|
||||
condenser.set_calib(Calib { z_ua: 1.1, ..Default::default() });
|
||||
```
|
||||
|
||||
**Thermodynamic Notes**:
|
||||
@@ -388,20 +388,25 @@ let merger = FlowMerger::new(
|
||||
|
||||
## Component Summary Table
|
||||
|
||||
| Component | Equations | Ports | Calibration |
|
||||
|-----------|-----------|-------|-------------|
|
||||
| Compressor | 2 | 2 | f_m, f_power, f_etav |
|
||||
| Pump | 2 | 2 | f_m, f_power |
|
||||
| Fan | 2 | 2 | f_m, f_power |
|
||||
| Pipe | 1 | 2 | f_dp |
|
||||
| ExpansionValve | 1 | 2 | f_m |
|
||||
| Condenser | 3 | 4 | f_ua, f_dp |
|
||||
| Evaporator | 3 | 4 | f_ua, f_dp |
|
||||
| Economizer | 3 | 4 | f_ua, f_dp |
|
||||
| FlowSource | 2 | 1 | - |
|
||||
| FlowSink | 1-2 | 1 | - |
|
||||
| FlowSplitter | N | 1+N | - |
|
||||
| FlowMerger | N | N+1 | - |
|
||||
Calibration uses **Z-factors** (BOLT/Modelica convention): multiplicative corrections on nominal quantities. See [components/README.md](../components/README.md#conventions) for the full `z_*` ↔ `Z_*` mapping.
|
||||
|
||||
| Component | Equations | Ports | Calibration (Z-factors) |
|
||||
|-----------|-----------|-------|-------------------------|
|
||||
| Compressor | 2 | 2 | z_flow, z_power, z_etav |
|
||||
| Pump | 2 | 2 | z_flow, z_power |
|
||||
| Fan | 2 | 2 | z_flow, z_power |
|
||||
| Pipe | 1 | 2 | z_dp |
|
||||
| ExpansionValve | 1 | 2 | z_flow |
|
||||
| Condenser | 3 | 4 | z_ua, z_dp |
|
||||
| Evaporator | 3 | 4 | z_ua, z_dp |
|
||||
| Economizer | 3 | 4 | z_ua, z_dp |
|
||||
| Anchor | 2–3 | 2 | — (spec mode consumes 1 DoF) |
|
||||
| HeatSource | 2 | 2 | — (fixed q_w or coupling-linked Q) |
|
||||
| ThermalLoad | 2 | 2 | — |
|
||||
| FlowSource | 2 | 1 | — |
|
||||
| FlowSink | 1-2 | 1 | — |
|
||||
| FlowSplitter | N | 1+N | — |
|
||||
| FlowMerger | N | N+1 | — |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user