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:
@@ -67,7 +67,11 @@ pub fn load_fluid_table(json: String) -> Result<(), String> {
|
||||
/// Get list of available fluids in the default backend.
|
||||
#[wasm_bindgen]
|
||||
pub fn list_available_fluids() -> Vec<String> {
|
||||
global_backend().list_fluids().into_iter().map(|f| f.0).collect()
|
||||
global_backend()
|
||||
.list_fluids()
|
||||
.into_iter()
|
||||
.map(|f| f.0)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -100,6 +100,96 @@ impl WasmCompressor {
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for a screw compressor with economizer injection.
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmScrewEconomizerCompressor {
|
||||
pub(crate) inner: entropyk_components::ScrewEconomizerCompressor,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmScrewEconomizerCompressor {
|
||||
/// Create a new screw economizer compressor with default bilinear curves.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
fluid: String,
|
||||
nominal_frequency_hz: f64,
|
||||
frequency_hz: f64,
|
||||
mechanical_efficiency: f64,
|
||||
economizer_fraction: f64,
|
||||
) -> Result<WasmScrewEconomizerCompressor, JsValue> {
|
||||
if nominal_frequency_hz <= 0.0 {
|
||||
return Err(js_sys::Error::new("nominal_frequency_hz must be positive").into());
|
||||
}
|
||||
if frequency_hz < 0.0 {
|
||||
return Err(js_sys::Error::new("frequency_hz cannot be negative").into());
|
||||
}
|
||||
if !(0.0..=1.0).contains(&mechanical_efficiency) || mechanical_efficiency == 0.0 {
|
||||
return Err(js_sys::Error::new("mechanical_efficiency must be in (0.0, 1.0]").into());
|
||||
}
|
||||
if !(0.0..=0.4).contains(&economizer_fraction) {
|
||||
return Err(
|
||||
js_sys::Error::new("economizer_fraction must be between 0.0 and 0.4").into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn connected_port(
|
||||
fluid: &str,
|
||||
pressure_bar: f64,
|
||||
enthalpy_kj_kg: f64,
|
||||
) -> Result<entropyk_components::ConnectedPort, JsValue> {
|
||||
let fluid_id = FluidId::new(fluid);
|
||||
let port_a = Port::new(
|
||||
fluid_id.clone(),
|
||||
entropyk_core::Pressure::from_bar(pressure_bar),
|
||||
entropyk_core::Enthalpy::from_joules_per_kg(enthalpy_kj_kg * 1000.0),
|
||||
);
|
||||
let port_b = Port::new(
|
||||
fluid_id,
|
||||
entropyk_core::Pressure::from_bar(pressure_bar),
|
||||
entropyk_core::Enthalpy::from_joules_per_kg(enthalpy_kj_kg * 1000.0),
|
||||
);
|
||||
port_a
|
||||
.connect(port_b)
|
||||
.map(|(connected, _)| connected)
|
||||
.map_err(|e| js_sys::Error::new(&format!("Port connection failed: {}", e)).into())
|
||||
}
|
||||
|
||||
let curves = entropyk_components::ScrewPerformanceCurves::with_fixed_eco_fraction(
|
||||
entropyk_components::Polynomial2D::bilinear(1.2, 0.003, -0.002, 0.000_01),
|
||||
entropyk_components::Polynomial2D::bilinear(55_000.0, 200.0, -300.0, 0.5),
|
||||
economizer_fraction,
|
||||
);
|
||||
let mut inner = entropyk_components::ScrewEconomizerCompressor::new(
|
||||
curves,
|
||||
&fluid,
|
||||
nominal_frequency_hz,
|
||||
mechanical_efficiency,
|
||||
connected_port(&fluid, 3.2, 400.0)?,
|
||||
connected_port(&fluid, 12.8, 440.0)?,
|
||||
connected_port(&fluid, 6.4, 260.0)?,
|
||||
)
|
||||
.map_err(|e| js_sys::Error::new(&format!("Screw compressor creation failed: {}", e)))?;
|
||||
inner
|
||||
.set_frequency_hz(frequency_hz)
|
||||
.map_err(|e| js_sys::Error::new(&format!("Frequency update failed: {}", e)))?;
|
||||
|
||||
Ok(WasmScrewEconomizerCompressor { inner })
|
||||
}
|
||||
|
||||
/// Current VFD frequency in Hz.
|
||||
pub fn frequency_hz(&self) -> f64 {
|
||||
self.inner.frequency_hz()
|
||||
}
|
||||
|
||||
/// Convert to a generic WasmComponent.
|
||||
pub fn into_component(self) -> WasmComponent {
|
||||
WasmComponent {
|
||||
inner: Box::new(self.inner),
|
||||
name: "ScrewEconomizerCompressor".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for Condenser.
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmCondenser {
|
||||
@@ -269,3 +359,48 @@ impl WasmPipe {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for CapillaryTube.
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmCapillaryTube {
|
||||
pub(crate) inner: entropyk_components::CapillaryTube<entropyk_components::port::Connected>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmCapillaryTube {
|
||||
/// Create a capillary tube (diameter_m, length_m).
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
fluid: String,
|
||||
diameter_m: f64,
|
||||
length_m: f64,
|
||||
) -> Result<WasmCapillaryTube, JsValue> {
|
||||
use entropyk_components::{CapillaryGeometry, CapillaryTube};
|
||||
let fluid_id = FluidId::new(&fluid);
|
||||
let p = entropyk_core::Pressure::from_bar(10.0);
|
||||
let h = entropyk_core::Enthalpy::from_joules_per_kg(250000.0);
|
||||
let geom = CapillaryGeometry {
|
||||
diameter_m,
|
||||
length_m,
|
||||
n_segments: 20,
|
||||
};
|
||||
let tube = CapillaryTube::new(
|
||||
geom,
|
||||
Port::new(fluid_id.clone(), p, h),
|
||||
Port::new(fluid_id.clone(), p, h),
|
||||
)
|
||||
.map_err(|e| js_sys::Error::new(&format!("{e}")))?;
|
||||
let connected = tube
|
||||
.connect(Port::new(fluid_id.clone(), p, h), Port::new(fluid_id, p, h))
|
||||
.map_err(|e| js_sys::Error::new(&format!("{e}")))?;
|
||||
Ok(WasmCapillaryTube { inner: connected })
|
||||
}
|
||||
|
||||
/// Convert to a generic WasmComponent.
|
||||
pub fn into_component(self) -> WasmComponent {
|
||||
WasmComponent {
|
||||
inner: Box::new(self.inner),
|
||||
name: "CapillaryTube".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
//! This crate provides WebAssembly wrappers for the Entropyk thermodynamic
|
||||
//! simulation library via wasm-bindgen.
|
||||
|
||||
// Pre-existing lint debt: wasm-bindgen exposes camelCase JS names.
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod backend;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::components::WasmComponent;
|
||||
use crate::types::{WasmConvergedState, WasmThermoState};
|
||||
use entropyk_fluids::FluidBackend;
|
||||
use entropyk_solver::{
|
||||
ConvergenceStatus, ConvergedState, FallbackConfig, FallbackSolver, NewtonConfig, PicardConfig,
|
||||
ConvergedState, ConvergenceStatus, FallbackConfig, FallbackSolver, NewtonConfig, PicardConfig,
|
||||
Solver, SolverStrategy, System,
|
||||
};
|
||||
use petgraph::graph::NodeIndex;
|
||||
@@ -202,7 +202,7 @@ impl WasmSystem {
|
||||
pub fn solve(&mut self, config: WasmFallbackConfig) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = FallbackSolver::new(config.inner);
|
||||
let state = solver
|
||||
.solve(&mut *self.inner.borrow_mut())
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
*self.last_state.borrow_mut() = Some(state.state.clone());
|
||||
@@ -216,7 +216,7 @@ impl WasmSystem {
|
||||
) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = SolverStrategy::NewtonRaphson(config.inner);
|
||||
let state = solver
|
||||
.solve(&mut *self.inner.borrow_mut())
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
*self.last_state.borrow_mut() = Some(state.state.clone());
|
||||
@@ -251,7 +251,9 @@ impl WasmSystem {
|
||||
if *p_idx >= state.len() || *h_idx >= state.len() {
|
||||
return Err(js_sys::Error::new(&format!(
|
||||
"State index out of bounds: p_idx={}, h_idx={}, state_len={}",
|
||||
p_idx, h_idx, state.len()
|
||||
p_idx,
|
||||
h_idx,
|
||||
state.len()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user