feat(components): add ThermoState generators and Eurovent backend demo
This commit is contained in:
254
crates/solver/tests/newton_raphson.rs
Normal file
254
crates/solver/tests/newton_raphson.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
//! Integration tests for Newton-Raphson solver (Story 4.2).
|
||||
//!
|
||||
//! Tests cover:
|
||||
//! - AC #1: Solver trait and strategy dispatch
|
||||
//! - AC #2: Configuration options
|
||||
//! - AC #3: Timeout enforcement
|
||||
//! - AC #4: Error handling for empty/invalid systems
|
||||
//! - AC #5: Pre-allocated buffers (no panic)
|
||||
|
||||
use entropyk_solver::{NewtonConfig, Solver, SolverError, System};
|
||||
use approx::assert_relative_eq;
|
||||
use std::time::Duration;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #1: Solver Trait and Strategy Dispatch
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_newton_config_default() {
|
||||
let cfg = NewtonConfig::default();
|
||||
|
||||
assert_eq!(cfg.max_iterations, 100);
|
||||
assert_relative_eq!(cfg.tolerance, 1e-6);
|
||||
assert!(!cfg.line_search);
|
||||
assert!(cfg.timeout.is_none());
|
||||
assert!(!cfg.use_numerical_jacobian);
|
||||
assert_relative_eq!(cfg.line_search_armijo_c, 1e-4);
|
||||
assert_eq!(cfg.line_search_max_backtracks, 20);
|
||||
assert_relative_eq!(cfg.divergence_threshold, 1e10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newton_config_with_timeout() {
|
||||
let timeout = Duration::from_millis(500);
|
||||
let cfg = NewtonConfig::default().with_timeout(timeout);
|
||||
|
||||
assert_eq!(cfg.timeout, Some(timeout));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newton_config_custom_values() {
|
||||
let cfg = NewtonConfig {
|
||||
max_iterations: 50,
|
||||
tolerance: 1e-8,
|
||||
line_search: true,
|
||||
timeout: Some(Duration::from_millis(500)),
|
||||
use_numerical_jacobian: true,
|
||||
line_search_armijo_c: 1e-3,
|
||||
line_search_max_backtracks: 10,
|
||||
divergence_threshold: 1e8,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(cfg.max_iterations, 50);
|
||||
assert_relative_eq!(cfg.tolerance, 1e-8);
|
||||
assert!(cfg.line_search);
|
||||
assert_eq!(cfg.timeout, Some(Duration::from_millis(500)));
|
||||
assert!(cfg.use_numerical_jacobian);
|
||||
assert_relative_eq!(cfg.line_search_armijo_c, 1e-3);
|
||||
assert_eq!(cfg.line_search_max_backtracks, 10);
|
||||
assert_relative_eq!(cfg.divergence_threshold, 1e8);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #2: Empty System Handling
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_empty_system_returns_invalid() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = NewtonConfig::default();
|
||||
let result = solver.solve(&mut sys);
|
||||
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(SolverError::InvalidSystem { message }) => {
|
||||
assert!(message.contains("Empty") || message.contains("no state"));
|
||||
}
|
||||
other => panic!("Expected InvalidSystem, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "finalize")]
|
||||
fn test_empty_system_without_finalize_panics() {
|
||||
// System panics if solve() is called without finalize()
|
||||
// This is expected behavior - the solver requires a finalized system
|
||||
let mut sys = System::new();
|
||||
// Don't call finalize
|
||||
|
||||
let mut solver = NewtonConfig::default();
|
||||
let _ = solver.solve(&mut sys);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #3: Timeout Enforcement
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_timeout_value_in_error() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let timeout_ms = 10u64;
|
||||
let mut solver = NewtonConfig {
|
||||
timeout: Some(Duration::from_millis(timeout_ms)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = solver.solve(&mut sys);
|
||||
|
||||
// Empty system returns InvalidSystem immediately (before timeout check)
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #4: Error Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_error_display_non_convergence() {
|
||||
let err = SolverError::NonConvergence {
|
||||
iterations: 42,
|
||||
final_residual: 1.23e-3,
|
||||
};
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("42"));
|
||||
assert!(msg.contains("1.23"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_display_timeout() {
|
||||
let err = SolverError::Timeout { timeout_ms: 500 };
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("500"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_display_divergence() {
|
||||
let err = SolverError::Divergence {
|
||||
reason: "test reason".to_string(),
|
||||
};
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("test reason"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_display_invalid_system() {
|
||||
let err = SolverError::InvalidSystem {
|
||||
message: "test message".to_string(),
|
||||
};
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("test message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_equality() {
|
||||
let e1 = SolverError::NonConvergence {
|
||||
iterations: 10,
|
||||
final_residual: 1e-3,
|
||||
};
|
||||
let e2 = SolverError::NonConvergence {
|
||||
iterations: 10,
|
||||
final_residual: 1e-3,
|
||||
};
|
||||
assert_eq!(e1, e2);
|
||||
|
||||
let e3 = SolverError::Timeout { timeout_ms: 100 };
|
||||
assert_ne!(e1, e3);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #5: Pre-Allocated Buffers (No Panic)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_solver_does_not_panic_on_empty_system() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = NewtonConfig::default();
|
||||
|
||||
// Should complete without panic
|
||||
let result = solver.solve(&mut sys);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solver_does_not_panic_with_line_search() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = NewtonConfig {
|
||||
line_search: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Should complete without panic
|
||||
let result = solver.solve(&mut sys);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solver_does_not_panic_with_numerical_jacobian() {
|
||||
let mut sys = System::new();
|
||||
sys.finalize().unwrap();
|
||||
|
||||
let mut solver = NewtonConfig {
|
||||
use_numerical_jacobian: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Should complete without panic
|
||||
let result = solver.solve(&mut sys);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AC #6: ConvergedState
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_converged_state_is_converged() {
|
||||
use entropyk_solver::ConvergenceStatus;
|
||||
use entropyk_solver::ConvergedState;
|
||||
|
||||
let state = ConvergedState::new(
|
||||
vec![1.0, 2.0, 3.0],
|
||||
10,
|
||||
1e-8,
|
||||
ConvergenceStatus::Converged,
|
||||
);
|
||||
|
||||
assert!(state.is_converged());
|
||||
assert_eq!(state.iterations, 10);
|
||||
assert_eq!(state.state, vec![1.0, 2.0, 3.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_converged_state_timed_out() {
|
||||
use entropyk_solver::ConvergenceStatus;
|
||||
use entropyk_solver::ConvergedState;
|
||||
|
||||
let state = ConvergedState::new(
|
||||
vec![1.0],
|
||||
50,
|
||||
1e-3,
|
||||
ConvergenceStatus::TimedOutWithBestState,
|
||||
);
|
||||
|
||||
assert!(!state.is_converged());
|
||||
}
|
||||
Reference in New Issue
Block a user