feat: implement mass balance validation for Story 7.1
- Added port_mass_flows to Component trait and implements for core components. - Added System::check_mass_balance and integrated it into the solver. - Restored connect methods for ExpansionValve, Compressor, and Pipe to fix integration tests. - Updated Python and C bindings for validation errors. - Updated sprint status and story documentation.
This commit is contained in:
72
bindings/wasm/src/backend.rs
Normal file
72
bindings/wasm/src/backend.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
//! WASM-specific backend initialization.
|
||||
//!
|
||||
//! Provides TabularBackend with embedded fluid tables for WASM builds.
|
||||
//! CoolProp C++ cannot compile to WASM, so we must use tabular interpolation.
|
||||
|
||||
use entropyk_fluids::{FluidBackend, TabularBackend};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Embedded R134a fluid table data.
|
||||
const R134A_TABLE: &str = include_str!("../../../crates/fluids/data/r134a.json");
|
||||
|
||||
/// Create the default backend for WASM with embedded fluid tables.
|
||||
pub fn create_default_backend() -> TabularBackend {
|
||||
let mut backend = TabularBackend::new();
|
||||
|
||||
backend
|
||||
.load_table_from_str(R134A_TABLE)
|
||||
.expect("Embedded R134a table must be valid");
|
||||
|
||||
backend
|
||||
}
|
||||
|
||||
/// Create an empty backend for custom fluid loading.
|
||||
pub fn create_empty_backend() -> TabularBackend {
|
||||
TabularBackend::new()
|
||||
}
|
||||
|
||||
/// Load a fluid table from a JSON string (exposed to JS).
|
||||
#[wasm_bindgen]
|
||||
pub fn load_fluid_table(json: String) -> Result<(), String> {
|
||||
let mut backend = create_empty_backend();
|
||||
backend
|
||||
.load_table_from_str(&json)
|
||||
.map_err(|e| format!("Failed to load fluid table: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get list of available fluids in the default backend.
|
||||
#[wasm_bindgen]
|
||||
pub fn list_available_fluids() -> Vec<String> {
|
||||
let backend = create_default_backend();
|
||||
backend.list_fluids().into_iter().map(|f| f.0).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_create_default_backend() {
|
||||
let backend = create_default_backend();
|
||||
let fluids = backend.list_fluids();
|
||||
assert!(!fluids.is_empty());
|
||||
assert!(fluids.iter().any(|f| f.0 == "R134a"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_list_available_fluids() {
|
||||
let fluids = list_available_fluids();
|
||||
assert!(!fluids.is_empty());
|
||||
assert!(fluids.contains(&"R134a".to_string()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_create_empty_backend() {
|
||||
let backend = create_empty_backend();
|
||||
let fluids = backend.list_fluids();
|
||||
assert!(fluids.is_empty());
|
||||
}
|
||||
}
|
||||
150
bindings/wasm/src/components.rs
Normal file
150
bindings/wasm/src/components.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
//! WASM component bindings (stub).
|
||||
//!
|
||||
//! Provides JavaScript-friendly wrappers for thermodynamic components.
|
||||
//! NOTE: This is a minimal implementation to demonstrate the WASM build.
|
||||
//! Full component bindings require additional development.
|
||||
|
||||
use crate::types::{WasmEnthalpy, WasmMassFlow, WasmPressure, WasmTemperature};
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// WASM wrapper for Compressor component (stub).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmCompressor {
|
||||
_fluid: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmCompressor {
|
||||
/// Create a new compressor.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(fluid: String) -> Result<WasmCompressor, JsValue> {
|
||||
Ok(WasmCompressor { _fluid: fluid })
|
||||
}
|
||||
|
||||
/// Get component name.
|
||||
pub fn name(&self) -> String {
|
||||
"Compressor".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for Condenser component (stub).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmCondenser {
|
||||
_fluid: String,
|
||||
_ua: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmCondenser {
|
||||
/// Create a new condenser.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(fluid: String, ua: f64) -> Result<WasmCondenser, JsValue> {
|
||||
Ok(WasmCondenser {
|
||||
_fluid: fluid,
|
||||
_ua: ua,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get component name.
|
||||
pub fn name(&self) -> String {
|
||||
"Condenser".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for Evaporator component (stub).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmEvaporator {
|
||||
_fluid: String,
|
||||
_ua: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmEvaporator {
|
||||
/// Create a new evaporator.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(fluid: String, ua: f64) -> Result<WasmEvaporator, JsValue> {
|
||||
Ok(WasmEvaporator {
|
||||
_fluid: fluid,
|
||||
_ua: ua,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get component name.
|
||||
pub fn name(&self) -> String {
|
||||
"Evaporator".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for ExpansionValve component (stub).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmExpansionValve {
|
||||
_fluid: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmExpansionValve {
|
||||
/// Create a new expansion valve.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(fluid: String) -> Result<WasmExpansionValve, JsValue> {
|
||||
Ok(WasmExpansionValve { _fluid: fluid })
|
||||
}
|
||||
|
||||
/// Get component name.
|
||||
pub fn name(&self) -> String {
|
||||
"ExpansionValve".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for Economizer component (stub).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmEconomizer {
|
||||
_fluid: String,
|
||||
_ua: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmEconomizer {
|
||||
/// Create a new economizer.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(fluid: String, ua: f64) -> Result<WasmEconomizer, JsValue> {
|
||||
Ok(WasmEconomizer {
|
||||
_fluid: fluid,
|
||||
_ua: ua,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get component name.
|
||||
pub fn name(&self) -> String {
|
||||
"Economizer".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_compressor_creation() {
|
||||
let compressor = WasmCompressor::new("R134a".to_string());
|
||||
assert!(compressor.is_ok());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_condenser_creation() {
|
||||
let condenser = WasmCondenser::new("R134a".to_string(), 1000.0);
|
||||
assert!(condenser.is_ok());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_evaporator_creation() {
|
||||
let evaporator = WasmEvaporator::new("R134a".to_string(), 800.0);
|
||||
assert!(evaporator.is_ok());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_expansion_valve_creation() {
|
||||
let valve = WasmExpansionValve::new("R134a".to_string());
|
||||
assert!(valve.is_ok());
|
||||
}
|
||||
}
|
||||
10
bindings/wasm/src/errors.rs
Normal file
10
bindings/wasm/src/errors.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! Error handling for WASM bindings.
|
||||
//!
|
||||
//! Maps errors to JavaScript exceptions with human-readable messages.
|
||||
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Convert a Result to a Result with JsValue error.
|
||||
pub fn result_to_js<T, E: std::fmt::Display>(result: Result<T, E>) -> Result<T, JsValue> {
|
||||
result.map_err(|e| js_sys::Error::new(&e.to_string()).into())
|
||||
}
|
||||
24
bindings/wasm/src/lib.rs
Normal file
24
bindings/wasm/src/lib.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
//! Entropyk WebAssembly bindings.
|
||||
//!
|
||||
//! This crate provides WebAssembly wrappers for the Entropyk thermodynamic
|
||||
//! simulation library via wasm-bindgen.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub(crate) mod backend;
|
||||
pub(crate) mod components;
|
||||
pub(crate) mod errors;
|
||||
pub(crate) mod solver;
|
||||
pub(crate) mod types;
|
||||
|
||||
/// Initialize the WASM module.
|
||||
#[wasm_bindgen]
|
||||
pub fn init() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// Get the library version.
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
260
bindings/wasm/src/solver.rs
Normal file
260
bindings/wasm/src/solver.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
//! WASM solver bindings.
|
||||
//!
|
||||
//! Provides JavaScript-friendly wrappers for the solver and system.
|
||||
|
||||
use crate::backend::create_default_backend;
|
||||
use entropyk_solver::{
|
||||
ConvergedState, FallbackConfig, FallbackSolver, NewtonConfig, PicardConfig, Solver,
|
||||
SolverStrategy, System,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// WASM wrapper for Newton-Raphson solver configuration.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmNewtonConfig {
|
||||
pub(crate) inner: NewtonConfig,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmNewtonConfig {
|
||||
/// Create default Newton-Raphson configuration.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
WasmNewtonConfig {
|
||||
inner: NewtonConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set maximum iterations.
|
||||
pub fn set_max_iterations(&mut self, max: usize) {
|
||||
self.inner.max_iterations = max;
|
||||
}
|
||||
|
||||
/// Set convergence tolerance.
|
||||
pub fn set_tolerance(&mut self, tol: f64) {
|
||||
self.inner.tolerance = tol;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WasmNewtonConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for Picard (Sequential Substitution) solver configuration.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmPicardConfig {
|
||||
pub(crate) inner: PicardConfig,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmPicardConfig {
|
||||
/// Create default Picard configuration.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
WasmPicardConfig {
|
||||
inner: PicardConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set maximum iterations.
|
||||
pub fn set_max_iterations(&mut self, max: usize) {
|
||||
self.inner.max_iterations = max;
|
||||
}
|
||||
|
||||
/// Set relaxation factor.
|
||||
pub fn set_relaxation_factor(&mut self, omega: f64) {
|
||||
self.inner.relaxation_factor = omega;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WasmPicardConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for fallback solver configuration.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmFallbackConfig {
|
||||
newton_config: NewtonConfig,
|
||||
picard_config: PicardConfig,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmFallbackConfig {
|
||||
/// Create default fallback configuration.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
WasmFallbackConfig {
|
||||
newton_config: NewtonConfig::default(),
|
||||
picard_config: PicardConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WasmFallbackConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for converged state (solver result).
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmConvergedState {
|
||||
/// Convergence status
|
||||
pub converged: bool,
|
||||
/// Number of iterations
|
||||
pub iterations: usize,
|
||||
/// Final residual
|
||||
pub final_residual: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmConvergedState {
|
||||
/// Convert to JSON string.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(
|
||||
r#"{{"converged":{},"iterations":{},"final_residual":{}}}"#,
|
||||
self.converged, self.iterations, self.final_residual
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ConvergedState> for WasmConvergedState {
|
||||
fn from(state: &ConvergedState) -> Self {
|
||||
WasmConvergedState {
|
||||
converged: state.is_converged(),
|
||||
iterations: state.iterations,
|
||||
final_residual: state.final_residual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM wrapper for System (thermodynamic system).
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmSystem {
|
||||
inner: Rc<RefCell<System>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmSystem {
|
||||
/// Create a new thermodynamic system.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Result<WasmSystem, JsValue> {
|
||||
let system = System::new();
|
||||
Ok(WasmSystem {
|
||||
inner: Rc::new(RefCell::new(system)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Solve the system with fallback strategy.
|
||||
pub fn solve(&mut self, _config: WasmFallbackConfig) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = FallbackSolver::default();
|
||||
let state = solver
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e: entropyk_solver::SolverError| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
Ok((&state).into())
|
||||
}
|
||||
|
||||
/// Solve with Newton-Raphson method.
|
||||
pub fn solve_newton(
|
||||
&mut self,
|
||||
config: WasmNewtonConfig,
|
||||
) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = SolverStrategy::NewtonRaphson(config.inner);
|
||||
let state = solver
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e: entropyk_solver::SolverError| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
Ok((&state).into())
|
||||
}
|
||||
|
||||
/// Solve with Picard (Sequential Substitution) method.
|
||||
pub fn solve_picard(
|
||||
&mut self,
|
||||
config: WasmPicardConfig,
|
||||
) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = SolverStrategy::SequentialSubstitution(config.inner);
|
||||
let state = solver
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e: entropyk_solver::SolverError| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
Ok((&state).into())
|
||||
}
|
||||
|
||||
/// Solve with Picard (Sequential Substitution) method.
|
||||
pub fn solve_picard(
|
||||
&mut self,
|
||||
config: WasmPicardConfig,
|
||||
) -> Result<WasmConvergedState, JsValue> {
|
||||
let mut solver = config.inner;
|
||||
let state = solver
|
||||
.solve(&mut self.inner.borrow_mut())
|
||||
.map_err(|e| js_sys::Error::new(&e.to_string()))?;
|
||||
|
||||
Ok((&state).into())
|
||||
}
|
||||
|
||||
/// Get node count.
|
||||
pub fn node_count(&self) -> usize {
|
||||
self.inner.borrow().node_count()
|
||||
}
|
||||
|
||||
/// Get edge count.
|
||||
pub fn edge_count(&self) -> usize {
|
||||
self.inner.borrow().edge_count()
|
||||
}
|
||||
|
||||
/// Convert system state to JSON.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(
|
||||
r#"{{"node_count":{},"edge_count":{}}}"#,
|
||||
self.node_count(),
|
||||
self.edge_count()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WasmSystem {
|
||||
fn default() -> Self {
|
||||
Self::new().expect("Failed to create default system")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_newton_config_creation() {
|
||||
let config = WasmNewtonConfig::new();
|
||||
assert!(config.inner.max_iterations > 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_picard_config_creation() {
|
||||
let config = WasmPicardConfig::new();
|
||||
assert!(config.inner.max_iterations > 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_fallback_config_creation() {
|
||||
let config = WasmFallbackConfig::new();
|
||||
assert!(config.newton_config.max_iterations > 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_system_creation() {
|
||||
let system = WasmSystem::new();
|
||||
assert!(system.is_ok());
|
||||
}
|
||||
}
|
||||
243
bindings/wasm/src/types.rs
Normal file
243
bindings/wasm/src/types.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
//! WASM type wrappers for core physical types.
|
||||
//!
|
||||
//! Provides JavaScript-friendly wrappers for Pressure, Temperature,
|
||||
//! Enthalpy, and MassFlow with JSON serialization support.
|
||||
|
||||
use entropyk_core::{Enthalpy, MassFlow, Pressure, Temperature};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Pressure in Pascals.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WasmPressure {
|
||||
pascals: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmPressure {
|
||||
/// Create pressure from Pascals.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(pascals: f64) -> Self {
|
||||
WasmPressure { pascals }
|
||||
}
|
||||
|
||||
/// Create pressure from bar.
|
||||
pub fn from_bar(bar: f64) -> Self {
|
||||
WasmPressure {
|
||||
pascals: bar * 100_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get pressure in Pascals.
|
||||
pub fn pascals(&self) -> f64 {
|
||||
self.pascals
|
||||
}
|
||||
|
||||
/// Get pressure in bar.
|
||||
pub fn bar(&self) -> f64 {
|
||||
self.pascals / 100_000.0
|
||||
}
|
||||
|
||||
/// Convert to JSON string.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(r#"{{"pascals":{},"bar":{}}}"#, self.pascals, self.bar())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pressure> for WasmPressure {
|
||||
fn from(p: Pressure) -> Self {
|
||||
WasmPressure {
|
||||
pascals: p.to_pascals(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmPressure> for Pressure {
|
||||
fn from(p: WasmPressure) -> Self {
|
||||
Pressure::from_pascals(p.pascals)
|
||||
}
|
||||
}
|
||||
|
||||
/// Temperature in Kelvin.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WasmTemperature {
|
||||
kelvin: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmTemperature {
|
||||
/// Create temperature from Kelvin.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(kelvin: f64) -> Self {
|
||||
WasmTemperature { kelvin }
|
||||
}
|
||||
|
||||
/// Create temperature from Celsius.
|
||||
pub fn from_celsius(celsius: f64) -> Self {
|
||||
WasmTemperature {
|
||||
kelvin: celsius + 273.15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get temperature in Kelvin.
|
||||
pub fn kelvin(&self) -> f64 {
|
||||
self.kelvin
|
||||
}
|
||||
|
||||
/// Get temperature in Celsius.
|
||||
pub fn celsius(&self) -> f64 {
|
||||
self.kelvin - 273.15
|
||||
}
|
||||
|
||||
/// Convert to JSON string.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(
|
||||
r#"{{"kelvin":{},"celsius":{}}}"#,
|
||||
self.kelvin,
|
||||
self.celsius()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Temperature> for WasmTemperature {
|
||||
fn from(t: Temperature) -> Self {
|
||||
WasmTemperature {
|
||||
kelvin: t.to_kelvin(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmTemperature> for Temperature {
|
||||
fn from(t: WasmTemperature) -> Self {
|
||||
Temperature::from_kelvin(t.kelvin)
|
||||
}
|
||||
}
|
||||
|
||||
/// Enthalpy in J/kg.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WasmEnthalpy {
|
||||
joules_per_kg: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmEnthalpy {
|
||||
/// Create enthalpy from J/kg.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(joules_per_kg: f64) -> Self {
|
||||
WasmEnthalpy { joules_per_kg }
|
||||
}
|
||||
|
||||
/// Create enthalpy from kJ/kg.
|
||||
pub fn from_kj_per_kg(kj_per_kg: f64) -> Self {
|
||||
WasmEnthalpy {
|
||||
joules_per_kg: kj_per_kg * 1000.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get enthalpy in J/kg.
|
||||
pub fn joules_per_kg(&self) -> f64 {
|
||||
self.joules_per_kg
|
||||
}
|
||||
|
||||
/// Get enthalpy in kJ/kg.
|
||||
pub fn kj_per_kg(&self) -> f64 {
|
||||
self.joules_per_kg / 1000.0
|
||||
}
|
||||
|
||||
/// Convert to JSON string.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(
|
||||
r#"{{"joules_per_kg":{},"kj_per_kg":{}}}"#,
|
||||
self.joules_per_kg,
|
||||
self.kj_per_kg()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Enthalpy> for WasmEnthalpy {
|
||||
fn from(h: Enthalpy) -> Self {
|
||||
WasmEnthalpy {
|
||||
joules_per_kg: h.to_joules_per_kg(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmEnthalpy> for Enthalpy {
|
||||
fn from(h: WasmEnthalpy) -> Self {
|
||||
Enthalpy::from_joules_per_kg(h.joules_per_kg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mass flow in kg/s.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WasmMassFlow {
|
||||
kg_per_s: f64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmMassFlow {
|
||||
/// Create mass flow from kg/s.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(kg_per_s: f64) -> Self {
|
||||
WasmMassFlow { kg_per_s }
|
||||
}
|
||||
|
||||
/// Get mass flow in kg/s.
|
||||
pub fn kg_per_s(&self) -> f64 {
|
||||
self.kg_per_s
|
||||
}
|
||||
|
||||
/// Convert to JSON string.
|
||||
pub fn toJson(&self) -> String {
|
||||
format!(r#"{{"kg_per_s":{}}}"#, self.kg_per_s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MassFlow> for WasmMassFlow {
|
||||
fn from(m: MassFlow) -> Self {
|
||||
WasmMassFlow {
|
||||
kg_per_s: m.to_kg_per_s(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmMassFlow> for MassFlow {
|
||||
fn from(m: WasmMassFlow) -> Self {
|
||||
MassFlow::from_kg_per_s(m.kg_per_s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_pressure_creation() {
|
||||
let p = WasmPressure::from_bar(1.0);
|
||||
assert!((p.pascals() - 100000.0).abs() < 1e-6);
|
||||
assert!((p.bar() - 1.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_temperature_creation() {
|
||||
let t = WasmTemperature::from_celsius(25.0);
|
||||
assert!((t.kelvin() - 298.15).abs() < 1e-6);
|
||||
assert!((t.celsius() - 25.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_enthalpy_creation() {
|
||||
let h = WasmEnthalpy::from_kj_per_kg(400.0);
|
||||
assert!((h.joules_per_kg() - 400000.0).abs() < 1e-6);
|
||||
assert!((h.kj_per_kg() - 400.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_massflow_creation() {
|
||||
let m = WasmMassFlow::new(0.1);
|
||||
assert!((m.kg_per_s() - 0.1).abs() < 1e-9);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user