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:
107
crates/components/tests/test_exv_off.rs
Normal file
107
crates/components/tests/test_exv_off.rs
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user