Files
Entropyk/bindings/wasm/src/components.rs

272 lines
8.8 KiB
Rust

//! WASM component bindings.
//!
//! Provides JavaScript-friendly wrappers for thermodynamic components.
use entropyk_components::port::{FluidId, Port};
use entropyk_components::Component;
use wasm_bindgen::prelude::*;
/// WASM wrapper for a thermodynamic component.
#[wasm_bindgen]
pub struct WasmComponent {
pub(crate) inner: Box<dyn Component>,
name: String,
}
#[wasm_bindgen]
impl WasmComponent {
/// Get component type name.
pub fn name(&self) -> String {
self.name.clone()
}
}
/// WASM wrapper for Compressor.
#[wasm_bindgen]
pub struct WasmCompressor {
pub(crate) inner: entropyk_components::Compressor<entropyk_components::port::Connected>,
}
#[wasm_bindgen]
impl WasmCompressor {
/// Create a new Compressor with AHRI 540 performance model.
///
/// # Arguments
/// * `fluid` - Fluid identifier (e.g. "R134a", "R410A")
/// * `speed_rpm` - Rotational speed in RPM
/// * `displacement_m3_per_rev` - Displacement volume in m³/rev
/// * `efficiency` - Mechanical efficiency (0.0 to 1.0)
/// * `m1`..`m10` - AHRI 540 performance coefficients
#[wasm_bindgen(constructor)]
pub fn new(
fluid: String,
speed_rpm: f64,
displacement_m3_per_rev: f64,
efficiency: f64,
m1: f64,
m2: f64,
m3: f64,
m4: f64,
m5: f64,
m6: f64,
m7: f64,
m8: f64,
m9: f64,
m10: f64,
) -> Result<WasmCompressor, JsValue> {
if speed_rpm <= 0.0 {
return Err(js_sys::Error::new("speed_rpm must be positive").into());
}
if displacement_m3_per_rev <= 0.0 {
return Err(js_sys::Error::new("displacement_m3_per_rev must be positive").into());
}
if !(0.0..=1.0).contains(&efficiency) {
return Err(js_sys::Error::new("efficiency must be between 0.0 and 1.0").into());
}
let coeffs =
entropyk_components::Ahri540Coefficients::new(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10);
let fluid_id = FluidId::new(&fluid);
let p = entropyk_core::Pressure::from_bar(5.0);
let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0);
let suction = Port::new(fluid_id.clone(), p, h);
let discharge = Port::new(fluid_id.clone(), p, h);
let comp = entropyk_components::Compressor::new(
coeffs,
suction,
discharge,
speed_rpm,
displacement_m3_per_rev,
efficiency,
)
.map_err(|e| js_sys::Error::new(&format!("Compressor creation failed: {}", e)))?;
let suction_p = Port::new(fluid_id.clone(), p, h);
let discharge_p = Port::new(fluid_id, p, h);
let connected = comp
.connect(suction_p, discharge_p)
.map_err(|e| js_sys::Error::new(&format!("Compressor connect failed: {}", e)))?;
Ok(WasmCompressor { inner: connected })
}
/// Convert to a generic WasmComponent.
pub fn into_component(self) -> WasmComponent {
WasmComponent {
inner: Box::new(self.inner),
name: "Compressor".to_string(),
}
}
}
/// WASM wrapper for Condenser.
#[wasm_bindgen]
pub struct WasmCondenser {
inner: entropyk_components::Condenser,
}
#[wasm_bindgen]
impl WasmCondenser {
/// Create a new condenser with thermal conductance UA (W/K).
///
/// Rejects NaN, negative, zero, and infinite values.
#[wasm_bindgen(constructor)]
pub fn new(ua: f64) -> Result<WasmCondenser, JsValue> {
if ua.is_nan() || ua.is_infinite() {
return Err(js_sys::Error::new("UA must be a finite number").into());
}
if ua <= 0.0 {
return Err(js_sys::Error::new("UA must be positive").into());
}
Ok(WasmCondenser {
inner: entropyk_components::Condenser::new(ua),
})
}
/// Convert to a generic WasmComponent.
pub fn into_component(self) -> WasmComponent {
WasmComponent {
inner: Box::new(self.inner),
name: "Condenser".to_string(),
}
}
}
/// WASM wrapper for Evaporator.
#[wasm_bindgen]
pub struct WasmEvaporator {
inner: entropyk_components::Evaporator,
}
#[wasm_bindgen]
impl WasmEvaporator {
/// Create a new evaporator with thermal conductance UA (W/K).
///
/// Rejects NaN, negative, zero, and infinite values.
#[wasm_bindgen(constructor)]
pub fn new(ua: f64) -> Result<WasmEvaporator, JsValue> {
if ua.is_nan() || ua.is_infinite() {
return Err(js_sys::Error::new("UA must be a finite number").into());
}
if ua <= 0.0 {
return Err(js_sys::Error::new("UA must be positive").into());
}
Ok(WasmEvaporator {
inner: entropyk_components::Evaporator::new(ua),
})
}
/// Convert to a generic WasmComponent.
pub fn into_component(self) -> WasmComponent {
WasmComponent {
inner: Box::new(self.inner),
name: "Evaporator".to_string(),
}
}
}
/// WASM wrapper for ExpansionValve.
#[wasm_bindgen]
pub struct WasmExpansionValve {
pub(crate) inner: entropyk_components::ExpansionValve<entropyk_components::port::Connected>,
}
#[wasm_bindgen]
impl WasmExpansionValve {
/// Create a new expansion valve.
///
/// # Arguments
/// * `fluid` - Fluid identifier (e.g. "R134a", "R410A")
/// * `capacity` - Optional valve capacity (kW). Pass 0.0 for no capacity limit.
#[wasm_bindgen(constructor)]
pub fn new(fluid: String, capacity: f64) -> Result<WasmExpansionValve, JsValue> {
let fluid_id = FluidId::new(&fluid);
let p = entropyk_core::Pressure::from_bar(10.0);
let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0);
let inlet = Port::new(fluid_id.clone(), p, h);
let outlet = Port::new(fluid_id.clone(), p, h);
let cap = if capacity > 0.0 { Some(capacity) } else { None };
let valve = entropyk_components::ExpansionValve::new(inlet, outlet, cap)
.map_err(|e| js_sys::Error::new(&format!("ExpansionValve creation failed: {}", e)))?;
let inlet_p = Port::new(fluid_id.clone(), p, h);
let outlet_p = Port::new(fluid_id, p, h);
let connected = valve
.connect(inlet_p, outlet_p)
.map_err(|e| js_sys::Error::new(&format!("ExpansionValve connect failed: {}", e)))?;
Ok(WasmExpansionValve { inner: connected })
}
/// Convert to a generic WasmComponent.
pub fn into_component(self) -> WasmComponent {
WasmComponent {
inner: Box::new(self.inner),
name: "ExpansionValve".to_string(),
}
}
}
/// WASM wrapper for Pipe.
#[wasm_bindgen]
pub struct WasmPipe {
pub(crate) inner: entropyk_components::Pipe<entropyk_components::port::Connected>,
}
#[wasm_bindgen]
impl WasmPipe {
/// Create a new pipe.
///
/// # Arguments
/// * `fluid` - Fluid identifier (e.g. "Water")
/// * `length` - Pipe length in meters (must be positive)
/// * `diameter` - Pipe inner diameter in meters (must be positive)
/// * `density` - Fluid density in kg/m³ (default: 1000.0 for water)
/// * `viscosity` - Fluid dynamic viscosity in Pa·s (default: 0.001 for water)
#[wasm_bindgen(constructor)]
pub fn new(
fluid: String,
length: f64,
diameter: f64,
density: f64,
viscosity: f64,
) -> Result<WasmPipe, JsValue> {
if length.is_nan() || length <= 0.0 {
return Err(js_sys::Error::new("Pipe length must be a positive number").into());
}
if diameter.is_nan() || diameter <= 0.0 {
return Err(js_sys::Error::new("Pipe diameter must be a positive number").into());
}
let geometry = entropyk_components::PipeGeometry::smooth(length, diameter)
.map_err(|e| js_sys::Error::new(&format!("Invalid pipe geometry: {}", e)))?;
let fluid_id = FluidId::new(&fluid);
let p = entropyk_core::Pressure::from_bar(1.0);
let h = entropyk_core::Enthalpy::from_joules_per_kg(100000.0);
let inlet = Port::new(fluid_id.clone(), p, h);
let outlet = Port::new(fluid_id.clone(), p, h);
let pipe = entropyk_components::Pipe::new(geometry, inlet, outlet, density, viscosity)
.map_err(|e| js_sys::Error::new(&format!("Pipe creation failed: {}", e)))?;
let inlet_p = Port::new(fluid_id.clone(), p, h);
let outlet_p = Port::new(fluid_id, p, h);
let connected = pipe
.connect(inlet_p, outlet_p)
.map_err(|e| js_sys::Error::new(&format!("Pipe connect failed: {}", e)))?;
Ok(WasmPipe { inner: connected })
}
/// Convert to a generic WasmComponent.
pub fn into_component(self) -> WasmComponent {
WasmComponent {
inner: Box::new(self.inner),
name: "Pipe".to_string(),
}
}
}