Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
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:
2026-07-19 16:35:31 +02:00
parent 88620790d6
commit 5bd180b5b8
1363 changed files with 101041 additions and 58547 deletions

View File

@@ -0,0 +1,107 @@
//! Quick test: EXV OperationalState Off path.
//! Validates that the EXV with `Off` produces ṁ = 0 in its residual and keeps
//! the Jacobian non-singular. Run with:
//! cargo test --release -p entropyk-components --test test_exv_off -- --nocapture
use entropyk_components::isenthalpic_expansion_valve::IsenthalpicExpansionValve;
use entropyk_components::state_machine::{OperationalState, StateManageable};
use entropyk_components::{Component, JacobianBuilder};
fn make_exv() -> IsenthalpicExpansionValve {
let mut exv = IsenthalpicExpansionValve::new(278.15)
.with_refrigerant("R134a")
.with_emergent_pressure()
.with_orifice_fixed(2.0e-6, 1.0);
// Layout: [m_inlet=0, m_outlet=1, p_inlet=2, h_inlet=3, p_outlet=4, h_outlet=5]
exv.set_system_context(0, &[(0, 2, 3), (1, 4, 5)]);
exv
}
#[test]
fn exv_off_produces_zero_flow_residual() {
let mut exv = make_exv();
// Turn the valve OFF.
exv.set_operational_state_unchecked(OperationalState::Off);
assert!(exv.state().is_off());
// State where m_outlet = 0.05 (non-zero flow) — the Off residual must drive it to 0.
let state: Vec<f64> = vec![0.05, 0.05, 1.5e6, 250_000.0, 0.4e6, 250_000.0];
let mut r = vec![0.0_f64; exv.n_equations()];
exv.compute_residuals(&state, &mut r).expect("residuals");
// The orifice equation is the last one (after isenthalpic + mass conservation).
// r_orifice should equal m_outlet - 0 = 0.05 (non-zero → Newton will push it to 0).
let orifice_residual = *r.last().unwrap();
assert!(
(orifice_residual - 0.05).abs() < 1e-12,
"Off residual should be ṁ = 0.05 (forcing flow to 0), got {}",
orifice_residual
);
// Jacobian: ∂r_orifice/∂m_outlet = 1 (keeps Newton coupled on the mass flow).
let mut jb = JacobianBuilder::new();
exv.jacobian_entries(&state, &mut jb).expect("jacobian");
let entries = jb.entries();
let m_out_idx = 1;
let orifice_row = exv.n_equations() - 1;
let dm_out: f64 = entries
.iter()
.filter(|(row, col, _)| *row == orifice_row && *col == m_out_idx)
.map(|(_, _, v)| *v)
.sum();
assert!(
(dm_out - 1.0).abs() < 1e-12,
"Off Jacobian ∂r/∂m_outlet should be 1.0, got {}",
dm_out
);
println!(
"EXV Off: residual={:.4e} (target 0.05), ∂r/∂m_out={:.3} (target 1.0) — OK",
orifice_residual, dm_out
);
}
#[test]
fn exv_on_still_uses_orifice_equation() {
let mut exv = make_exv();
// Default state is On.
assert!(exv.state().is_on());
// Use a backend so ρ_in can be evaluated.
let backend: std::sync::Arc<dyn entropyk_fluids::FluidBackend> =
std::sync::Arc::new(entropyk_fluids::TestBackend::new());
exv.set_fluid_backend_from_builder(backend);
// State with a physical ΔP across the valve: P_in = 13 bar, P_out = 3.5 bar.
let state: Vec<f64> = vec![0.05, 0.05, 1.3e6, 250_000.0, 0.35e6, 250_000.0];
let mut r = vec![0.0_f64; exv.n_equations()];
exv.compute_residuals(&state, &mut r).expect("residuals");
let orifice_residual = *r.last().unwrap();
// Orifice equation is active: residual is non-trivial (not just ṁ).
assert!(
orifice_residual.abs() > 1e-6,
"On residual should be non-trivial (orifice equation active), got {}",
orifice_residual
);
println!(
"EXV On: residual={:.4e} (orifice equation active) — OK",
orifice_residual
);
}
#[test]
fn exv_state_transitions_are_validated() {
let mut exv = IsenthalpicExpansionValve::new(278.15);
assert!(exv.state().is_on());
// On → Off is a legal transition.
assert!(exv.can_transition_to(OperationalState::Off));
exv.set_state(OperationalState::Off).expect("On → Off");
assert!(exv.state().is_off());
// Off → On is legal.
assert!(exv.can_transition_to(OperationalState::On));
exv.set_state(OperationalState::On).expect("Off → On");
assert!(exv.state().is_on());
}