Initial commit: BMAD framework + Story 1.1 Component Trait Definition
Features: - BMAD (Build Modular AI-driven Development) framework setup - BMM, BMB, CIS, Core modules configured - Story 1.1: Component trait with error handling - Workspace Cargo.toml with components crate - 31 tests passing (19 unit + 12 doc tests) Technical: - Component trait with compute_residuals, jacobian_entries, n_equations - ComponentError enum with thiserror - JacobianBuilder for sparse matrix construction - Object-safe trait supporting Box<dyn Component> - Comprehensive documentation and examples
This commit is contained in:
26
crates/components/Cargo.toml
Normal file
26
crates/components/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "entropyk-components"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Sepehr <sepehr@entropyk.com>"]
|
||||
description = "Core component trait definitions for Entropyk thermodynamic simulation library"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/entropyk/entropyk"
|
||||
|
||||
[dependencies]
|
||||
# Core types will be added when core crate is created
|
||||
# entropyk-core = { path = "../core" }
|
||||
|
||||
# Error handling
|
||||
thiserror = "1.0"
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Testing utilities
|
||||
# tokio-test = "0.4"
|
||||
|
||||
[lib]
|
||||
name = "entropyk_components"
|
||||
path = "src/lib.rs"
|
||||
670
crates/components/src/lib.rs
Normal file
670
crates/components/src/lib.rs
Normal file
@@ -0,0 +1,670 @@
|
||||
//! # Entropyk Components
|
||||
//!
|
||||
//! This crate provides the core component trait definitions for the Entropyk
|
||||
//! thermodynamic simulation library. All thermodynamic components (compressors,
|
||||
//! condensers, evaporators, etc.) implement the [`Component`] trait.
|
||||
//!
|
||||
//! ## Core Concept
|
||||
//!
|
||||
//! The [`Component`] trait defines the interface between thermodynamic components
|
||||
//! and the solver engine. Each component is responsible for:
|
||||
//!
|
||||
//! - Computing residuals based on current system state
|
||||
//! - Providing Jacobian entries for numerical solving
|
||||
//! - Reporting the number of equations it contributes
|
||||
//!
|
||||
//! ## Object Safety
|
||||
//!
|
||||
//! The [`Component`] trait is designed to be **object-safe**, meaning it supports
|
||||
//! dynamic dispatch via `Box<dyn Component>` or `&dyn Component`. This is essential
|
||||
//! for the solver to work with collections of heterogeneous components.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
//!
|
||||
//! struct MockComponent {
|
||||
//! n_equations: usize,
|
||||
//! }
|
||||
//!
|
||||
//! impl Component for MockComponent {
|
||||
//! fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
//! // Component-specific residual computation
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
//! // Component-specific Jacobian contributions
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn n_equations(&self) -> usize {
|
||||
//! self.n_equations
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Trait object usage
|
||||
//! let component: Box<dyn Component> = Box::new(MockComponent { n_equations: 3 });
|
||||
//! ```
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during component operations.
|
||||
///
|
||||
/// This enum represents all possible error conditions that components
|
||||
/// may encounter during computation, providing detailed context for debugging.
|
||||
#[derive(Error, Debug, Clone, PartialEq)]
|
||||
pub enum ComponentError {
|
||||
/// Invalid state vector dimensions.
|
||||
///
|
||||
/// The state vector provided does not have the expected dimensions
|
||||
/// for this component's computation.
|
||||
#[error("Invalid state vector dimensions: expected at least {expected}, got {actual}")]
|
||||
InvalidStateDimensions {
|
||||
/// Expected minimum dimension
|
||||
expected: usize,
|
||||
/// Actual dimension received
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// Invalid residual vector dimensions.
|
||||
///
|
||||
/// The residual vector does not match the expected size for this component.
|
||||
#[error("Invalid residual vector dimensions: expected {expected}, got {actual}")]
|
||||
InvalidResidualDimensions {
|
||||
/// Expected dimension (from n_equations)
|
||||
expected: usize,
|
||||
/// Actual dimension received
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// Numerical computation error.
|
||||
///
|
||||
/// Occurs when a numerical operation fails (e.g., division by zero,
|
||||
/// logarithm of non-positive number, NaN or infinite results).
|
||||
#[error("Numerical error in component computation: {0}")]
|
||||
NumericalError(String),
|
||||
|
||||
/// Invalid component state.
|
||||
///
|
||||
/// The component is in an invalid state for computation (e.g.,
|
||||
/// disconnected ports, uninitialized parameters).
|
||||
#[error("Invalid component state: {0}")]
|
||||
InvalidState(String),
|
||||
}
|
||||
|
||||
/// Represents the state of the entire thermodynamic system.
|
||||
///
|
||||
/// This type will be refined in future iterations as the system architecture
|
||||
/// evolves. For now, it provides a placeholder for system-wide state information.
|
||||
pub type SystemState = Vec<f64>;
|
||||
|
||||
/// Vector of residual values for equation solving.
|
||||
///
|
||||
/// Residuals represent the difference between expected and actual values
|
||||
/// in the system of equations. The solver aims to drive all residuals to zero.
|
||||
pub type ResidualVector = Vec<f64>;
|
||||
|
||||
/// Builder for constructing Jacobian matrix entries.
|
||||
///
|
||||
/// The Jacobian matrix contains partial derivatives of residuals with respect
|
||||
/// to state variables. This builder accumulates entries in coordinate format
|
||||
/// (row, column, value) before assembly into a sparse matrix.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct JacobianBuilder {
|
||||
entries: Vec<(usize, usize, f64)>,
|
||||
}
|
||||
|
||||
impl JacobianBuilder {
|
||||
/// Creates a new empty Jacobian builder.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let builder = JacobianBuilder::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a single entry to the Jacobian.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `row` - Row index in the Jacobian matrix (equation index)
|
||||
/// * `col` - Column index in the Jacobian matrix (state variable index)
|
||||
/// * `value` - Partial derivative value ∂residual/∂state
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let mut builder = JacobianBuilder::new();
|
||||
/// builder.add_entry(0, 1, 2.5);
|
||||
/// ```
|
||||
pub fn add_entry(&mut self, row: usize, col: usize, value: f64) {
|
||||
self.entries.push((row, col, value));
|
||||
}
|
||||
|
||||
/// Adds multiple entries at once.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `entries` - Iterator of (row, col, value) tuples
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let mut builder = JacobianBuilder::new();
|
||||
/// builder.add_entries(vec![(0, 0, 1.0), (0, 1, 2.0)]);
|
||||
/// ```
|
||||
pub fn add_entries(&mut self, entries: impl IntoIterator<Item = (usize, usize, f64)>) {
|
||||
self.entries.extend(entries);
|
||||
}
|
||||
|
||||
/// Returns all accumulated entries.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let mut builder = JacobianBuilder::new();
|
||||
/// builder.add_entry(0, 0, 1.0);
|
||||
/// let entries = builder.entries();
|
||||
/// assert_eq!(entries.len(), 1);
|
||||
/// ```
|
||||
pub fn entries(&self) -> &[(usize, usize, f64)] {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
/// Clears all entries from the builder.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let mut builder = JacobianBuilder::new();
|
||||
/// builder.add_entry(0, 0, 1.0);
|
||||
/// builder.clear();
|
||||
/// assert!(builder.entries().is_empty());
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
}
|
||||
|
||||
/// Returns the number of accumulated entries.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let mut builder = JacobianBuilder::new();
|
||||
/// builder.add_entry(0, 0, 1.0);
|
||||
/// assert_eq!(builder.len(), 1);
|
||||
/// ```
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
/// Returns true if no entries have been added.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::JacobianBuilder;
|
||||
///
|
||||
/// let builder = JacobianBuilder::new();
|
||||
/// assert!(builder.is_empty());
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Core trait for all thermodynamic components.
|
||||
///
|
||||
/// The `Component` trait defines the interface between thermodynamic components
|
||||
/// (compressors, heat exchangers, valves, etc.) and the solver engine. All
|
||||
/// components in the system must implement this trait.
|
||||
///
|
||||
/// # Object Safety
|
||||
///
|
||||
/// This trait is **object-safe**, meaning it can be used with dynamic dispatch:
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
///
|
||||
/// struct SimpleComponent;
|
||||
/// impl Component for SimpleComponent {
|
||||
/// fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// }
|
||||
///
|
||||
/// let component: Box<dyn Component> = Box::new(SimpleComponent);
|
||||
/// ```
|
||||
///
|
||||
/// # Required Methods
|
||||
///
|
||||
/// Implementors must provide three methods:
|
||||
///
|
||||
/// - [`compute_residuals`](Self::compute_residuals): Compute residual values
|
||||
/// representing the component's contribution to the system of equations.
|
||||
///
|
||||
/// - [`jacobian_entries`](Self::jacobian_entries): Provide partial derivatives
|
||||
/// of residuals with respect to state variables.
|
||||
///
|
||||
/// - [`n_equations`](Self::n_equations): Report how many equations this
|
||||
/// component contributes to the overall system.
|
||||
///
|
||||
/// # Error Handling
|
||||
///
|
||||
/// Both computation methods return [`Result`] to allow components to report
|
||||
/// errors such as invalid state dimensions, numerical issues, or invalid
|
||||
/// component configuration.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// Currently, this trait uses simple slice types for state and residuals.
|
||||
/// Future iterations may introduce generic type parameters for enhanced
|
||||
/// type safety and performance.
|
||||
pub trait Component {
|
||||
/// Computes residual values for this component.
|
||||
///
|
||||
/// Residuals represent the difference between the component's expected
|
||||
/// behavior and its actual state. The solver attempts to drive all
|
||||
/// residuals to zero.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - Current state vector of the entire system
|
||||
/// * `residuals` - Mutable slice to store computed residual values
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(())` on success, or a [`ComponentError`] if computation fails.
|
||||
///
|
||||
/// # Implementation Notes
|
||||
///
|
||||
/// The `residuals` slice has length equal to [`n_equations`](Self::n_equations).
|
||||
/// Each index corresponds to a specific equation for this component.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
///
|
||||
/// struct MassBalanceComponent;
|
||||
/// impl Component for MassBalanceComponent {
|
||||
/// fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
/// // Validate dimensions
|
||||
/// if state.len() < 2 {
|
||||
/// return Err(ComponentError::InvalidStateDimensions { expected: 2, actual: state.len() });
|
||||
/// }
|
||||
/// // Mass balance: inlet - outlet = 0
|
||||
/// residuals[0] = state[0] - state[1];
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// }
|
||||
/// ```
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError>;
|
||||
|
||||
/// Provides Jacobian matrix entries for this component.
|
||||
///
|
||||
/// The Jacobian contains partial derivatives of residuals with respect
|
||||
/// to state variables: J[i,j] = ∂residual[i]/∂state[j]
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - Current state vector of the entire system
|
||||
/// * `jacobian` - Builder for accumulating Jacobian entries
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(())` on success, or a [`ComponentError`] if computation fails.
|
||||
///
|
||||
/// # Implementation Notes
|
||||
///
|
||||
/// Use [`JacobianBuilder::add_entry`] to add individual partial derivatives.
|
||||
/// Only add non-zero entries to optimize sparse matrix storage.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
///
|
||||
/// struct LinearComponent;
|
||||
/// impl Component for LinearComponent {
|
||||
/// fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn jacobian_entries(&self, _state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
/// // ∂r₀/∂s₀ = 2.0
|
||||
/// jacobian.add_entry(0, 0, 2.0);
|
||||
/// // ∂r₀/∂s₁ = -1.0
|
||||
/// jacobian.add_entry(0, 1, -1.0);
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn n_equations(&self) -> usize { 1 }
|
||||
/// }
|
||||
/// ```
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
state: &SystemState,
|
||||
jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError>;
|
||||
|
||||
/// Returns the number of equations contributed by this component.
|
||||
///
|
||||
/// This determines the size of the residual vector passed to
|
||||
/// [`compute_residuals`](Self::compute_residuals).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder};
|
||||
///
|
||||
/// struct ThreeEquationComponent;
|
||||
/// impl Component for ThreeEquationComponent {
|
||||
/// fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// fn n_equations(&self) -> usize { 3 }
|
||||
/// }
|
||||
///
|
||||
/// let component = ThreeEquationComponent;
|
||||
/// assert_eq!(component.n_equations(), 3);
|
||||
/// ```
|
||||
fn n_equations(&self) -> usize;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Mock component for testing trait object safety
|
||||
struct MockComponent {
|
||||
n_equations: usize,
|
||||
}
|
||||
|
||||
impl Component for MockComponent {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
// Validate dimensions
|
||||
if residuals.len() != self.n_equations {
|
||||
return Err(ComponentError::InvalidResidualDimensions {
|
||||
expected: self.n_equations,
|
||||
actual: residuals.len(),
|
||||
});
|
||||
}
|
||||
|
||||
for (i, residual) in residuals.iter_mut().enumerate() {
|
||||
*residual = state.get(i).copied().unwrap_or(0.0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
// Add identity-like entries for testing
|
||||
for i in 0..self.n_equations {
|
||||
jacobian.add_entry(i, i, 1.0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_equations
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_trait_object_compiles() {
|
||||
// This test verifies that Component trait is object-safe
|
||||
let component: Box<dyn Component> = Box::new(MockComponent { n_equations: 3 });
|
||||
assert_eq!(component.n_equations(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_reference_trait_object() {
|
||||
// Test with reference trait object
|
||||
let mock = MockComponent { n_equations: 2 };
|
||||
let component: &dyn Component = &mock;
|
||||
assert_eq!(component.n_equations(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_residuals() {
|
||||
let component = MockComponent { n_equations: 3 };
|
||||
let state = vec![1.0, 2.0, 3.0];
|
||||
let mut residuals = vec![0.0; 3];
|
||||
|
||||
let result = component.compute_residuals(&state, &mut residuals);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(residuals, vec![1.0, 2.0, 3.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_residuals_with_wrong_residual_size() {
|
||||
let component = MockComponent { n_equations: 3 };
|
||||
let state = vec![1.0, 2.0, 3.0];
|
||||
let mut residuals = vec![0.0; 2]; // Wrong size
|
||||
|
||||
let result = component.compute_residuals(&state, &mut residuals);
|
||||
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(ComponentError::InvalidResidualDimensions { expected, actual }) => {
|
||||
assert_eq!(expected, 3);
|
||||
assert_eq!(actual, 2);
|
||||
}
|
||||
_ => panic!("Expected InvalidResidualDimensions error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_residuals_with_empty_state() {
|
||||
let component = MockComponent { n_equations: 3 };
|
||||
let state: Vec<f64> = vec![];
|
||||
let mut residuals = vec![0.0; 3];
|
||||
|
||||
// Should still work, using unwrap_or(0.0) for missing state values
|
||||
let result = component.compute_residuals(&state, &mut residuals);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(residuals, vec![0.0, 0.0, 0.0]); // Defaults to 0.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_residuals_with_zero_equations() {
|
||||
let component = MockComponent { n_equations: 0 };
|
||||
let state = vec![1.0, 2.0, 3.0];
|
||||
let mut residuals: Vec<f64> = vec![];
|
||||
|
||||
let result = component.compute_residuals(&state, &mut residuals);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(residuals.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jacobian_entries() {
|
||||
let component = MockComponent { n_equations: 2 };
|
||||
let state = vec![0.0; 2];
|
||||
let mut jacobian = JacobianBuilder::new();
|
||||
|
||||
let result = component.jacobian_entries(&state, &mut jacobian);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let entries = jacobian.entries();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(entries[0], (0, 0, 1.0));
|
||||
assert_eq!(entries[1], (1, 1, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jacobian_entries_with_zero_equations() {
|
||||
let component = MockComponent { n_equations: 0 };
|
||||
let state = vec![1.0, 2.0];
|
||||
let mut jacobian = JacobianBuilder::new();
|
||||
|
||||
let result = component.jacobian_entries(&state, &mut jacobian);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(jacobian.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jacobian_builder() {
|
||||
let mut builder = JacobianBuilder::new();
|
||||
assert!(builder.is_empty());
|
||||
|
||||
builder.add_entry(0, 1, 2.5);
|
||||
assert_eq!(builder.len(), 1);
|
||||
assert!(!builder.is_empty());
|
||||
|
||||
let entries = builder.entries();
|
||||
assert_eq!(entries[0], (0, 1, 2.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jacobian_builder_add_entries() {
|
||||
let mut builder = JacobianBuilder::new();
|
||||
let entries = vec![(0, 0, 1.0), (0, 1, 2.0), (1, 0, 3.0)];
|
||||
|
||||
builder.add_entries(entries);
|
||||
|
||||
assert_eq!(builder.len(), 3);
|
||||
assert_eq!(builder.entries()[0], (0, 0, 1.0));
|
||||
assert_eq!(builder.entries()[1], (0, 1, 2.0));
|
||||
assert_eq!(builder.entries()[2], (1, 0, 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jacobian_builder_clear() {
|
||||
let mut builder = JacobianBuilder::new();
|
||||
builder.add_entry(0, 0, 1.0);
|
||||
builder.add_entry(1, 1, 2.0);
|
||||
|
||||
assert_eq!(builder.len(), 2);
|
||||
|
||||
builder.clear();
|
||||
|
||||
assert!(builder.is_empty());
|
||||
assert_eq!(builder.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_n_equations() {
|
||||
let component = MockComponent { n_equations: 5 };
|
||||
assert_eq!(component.n_equations(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_n_equations_zero() {
|
||||
let component = MockComponent { n_equations: 0 };
|
||||
assert_eq!(component.n_equations(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_of_components() {
|
||||
// Verify we can create a vector of trait objects
|
||||
let components: Vec<Box<dyn Component>> = vec![
|
||||
Box::new(MockComponent { n_equations: 1 }),
|
||||
Box::new(MockComponent { n_equations: 2 }),
|
||||
Box::new(MockComponent { n_equations: 3 }),
|
||||
];
|
||||
|
||||
let total_equations: usize = components.iter().map(|c| c.n_equations()).sum();
|
||||
assert_eq!(total_equations, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_with_zero_equation_component() {
|
||||
let components: Vec<Box<dyn Component>> = vec![
|
||||
Box::new(MockComponent { n_equations: 0 }),
|
||||
Box::new(MockComponent { n_equations: 1 }),
|
||||
Box::new(MockComponent { n_equations: 0 }),
|
||||
];
|
||||
|
||||
let total_equations: usize = components.iter().map(|c| c.n_equations()).sum();
|
||||
assert_eq!(total_equations, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_error_display() {
|
||||
let err = ComponentError::InvalidStateDimensions {
|
||||
expected: 5,
|
||||
actual: 3,
|
||||
};
|
||||
let msg = format!("{}", err);
|
||||
assert!(msg.contains("Invalid state vector dimensions"));
|
||||
assert!(msg.contains("expected at least 5"));
|
||||
assert!(msg.contains("got 3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_error_numerical() {
|
||||
let err = ComponentError::NumericalError("Division by zero".to_string());
|
||||
let msg = format!("{}", err);
|
||||
assert!(msg.contains("Numerical error"));
|
||||
assert!(msg.contains("Division by zero"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_error_invalid_state() {
|
||||
let err = ComponentError::InvalidState("Port not connected".to_string());
|
||||
let msg = format!("{}", err);
|
||||
assert!(msg.contains("Invalid component state"));
|
||||
assert!(msg.contains("Port not connected"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_clonable() {
|
||||
let err = ComponentError::InvalidStateDimensions {
|
||||
expected: 2,
|
||||
actual: 1,
|
||||
};
|
||||
let cloned = err.clone();
|
||||
assert_eq!(err, cloned);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user