Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
109
crates/solver/tests/dof_balance.rs
Normal file
109
crates/solver/tests/dof_balance.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! System-wide DoF balance tests.
|
||||
//!
|
||||
//! Verifies that the ledger counts equations and unknowns consistently and that
|
||||
//! `finalize` hard-fails on square-system violations.
|
||||
|
||||
use entropyk_components::{Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice};
|
||||
use entropyk_solver::dof::SystemDofBalance;
|
||||
use entropyk_solver::system::System;
|
||||
use entropyk_solver::TopologyError;
|
||||
|
||||
/// Minimal mock: N residuals that are identically zero (topology bookkeeping only).
|
||||
struct MockEq {
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl Component for MockEq {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &StateSlice,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
for r in residuals.iter_mut().take(self.n) {
|
||||
*r = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &StateSlice,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn flow_paths(&self) -> Vec<(usize, usize)> {
|
||||
// Single-stream pass-through so the edge pair shares one ṁ branch.
|
||||
vec![(0, 1)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Two-node cycle with matching residual count → square after CM1.4.
|
||||
///
|
||||
/// Unknowns: 1 ṁ + 2 edges × (P,h) = 5
|
||||
/// Equations: 3 + 2 = 5
|
||||
#[test]
|
||||
fn two_node_cycle_is_balanced() {
|
||||
let mut sys = System::new();
|
||||
let a = sys.add_component(Box::new(MockEq { n: 3 }));
|
||||
let b = sys.add_component(Box::new(MockEq { n: 2 }));
|
||||
sys.add_edge(a, b).unwrap();
|
||||
sys.add_edge(b, a).unwrap();
|
||||
sys.finalize().expect("balanced system must finalize");
|
||||
|
||||
let report = sys.dof_report();
|
||||
assert_eq!(report.n_unknowns, 5);
|
||||
assert_eq!(report.n_equations, 5);
|
||||
assert_eq!(report.balance, SystemDofBalance::Balanced);
|
||||
assert!(sys.validate_system_dof().is_ok());
|
||||
}
|
||||
|
||||
/// One extra residual without a free unknown → over-constrained, finalize fails.
|
||||
#[test]
|
||||
fn overconstrained_finalize_fails() {
|
||||
let mut sys = System::new();
|
||||
let a = sys.add_component(Box::new(MockEq { n: 4 })); // +1 excess
|
||||
let b = sys.add_component(Box::new(MockEq { n: 2 }));
|
||||
sys.add_edge(a, b).unwrap();
|
||||
sys.add_edge(b, a).unwrap();
|
||||
// unknowns = 5, equations = 6
|
||||
let err = sys.finalize().expect_err("must reject over-constrained system");
|
||||
match err {
|
||||
TopologyError::DofImbalance { message } => {
|
||||
assert!(
|
||||
message.contains("over-constrained") || message.contains("equations"),
|
||||
"unexpected message: {message}"
|
||||
);
|
||||
}
|
||||
other => panic!("expected DofImbalance, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Missing residual → under-constrained: finalize warns but allows topology tests;
|
||||
/// hard `validate_system_dof` still rejects (production path).
|
||||
#[test]
|
||||
fn underconstrained_detected_by_validate_system_dof() {
|
||||
let mut sys = System::new();
|
||||
let a = sys.add_component(Box::new(MockEq { n: 2 }));
|
||||
let b = sys.add_component(Box::new(MockEq { n: 2 }));
|
||||
sys.add_edge(a, b).unwrap();
|
||||
sys.add_edge(b, a).unwrap();
|
||||
// unknowns = 5, equations = 4
|
||||
sys.finalize()
|
||||
.expect("under-constrained allowed at finalize for topology mocks");
|
||||
let report = sys.dof_report();
|
||||
assert!(matches!(
|
||||
report.balance,
|
||||
SystemDofBalance::UnderConstrained { free_dofs: 1 }
|
||||
));
|
||||
assert!(sys.validate_system_dof().is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user