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:
2026-07-17 22:46:46 +02:00
parent 62efea0646
commit 3358b74342
275 changed files with 70187 additions and 5230 deletions

View File

@@ -7,14 +7,12 @@
//! - Configurable timeout behavior
//! - Timeout across fallback switches preserves best state
use entropyk_components::{
Component, ComponentError, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_components::{Component, ComponentError, JacobianBuilder, ResidualVector, StateSlice};
use entropyk_solver::solver::{
ConvergenceStatus, FallbackConfig, FallbackSolver, NewtonConfig, PicardConfig, Solver,
SolverError, TimeoutConfig,
};
use entropyk_solver::system::System;
use entropyk_solver::system::{System, DEFAULT_MASS_FLOW_SEED_KG_S};
use std::time::Duration;
// ─────────────────────────────────────────────────────────────────────────────
@@ -42,8 +40,12 @@ impl Component for LinearSystem2x2 {
state: &StateSlice,
residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
residuals[0] = self.a[0][0] * state[0] + self.a[0][1] * state[1] - self.b[0];
residuals[1] = self.a[1][0] * state[0] + self.a[1][1] * state[1] - self.b[1];
// CM1.3: per-edge state is (ṁ, P, h); the 2×2 system acts on (P, h) at
// global indices 1 and 2. The third equation pins ṁ (state[0]) to the
// default seed so the system is square (3 equations, 3 unknowns).
residuals[0] = self.a[0][0] * state[1] + self.a[0][1] * state[2] - self.b[0];
residuals[1] = self.a[1][0] * state[1] + self.a[1][1] * state[2] - self.b[1];
residuals[2] = state[0] - DEFAULT_MASS_FLOW_SEED_KG_S;
Ok(())
}
@@ -52,15 +54,16 @@ impl Component for LinearSystem2x2 {
_state: &StateSlice,
jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
jacobian.add_entry(0, 0, self.a[0][0]);
jacobian.add_entry(0, 1, self.a[0][1]);
jacobian.add_entry(1, 0, self.a[1][0]);
jacobian.add_entry(1, 1, self.a[1][1]);
jacobian.add_entry(0, 1, self.a[0][0]);
jacobian.add_entry(0, 2, self.a[0][1]);
jacobian.add_entry(1, 1, self.a[1][0]);
jacobian.add_entry(1, 2, self.a[1][1]);
jacobian.add_entry(2, 0, 1.0);
Ok(())
}
fn n_equations(&self) -> usize {
2
3
}
fn get_ports(&self) -> &[entropyk_components::ConnectedPort] {
@@ -161,7 +164,7 @@ fn test_best_state_is_lowest_residual() {
#[test]
fn test_zoh_fallback_returns_previous_state() {
let mut system = create_test_system(Box::new(LinearSystem2x2::well_conditioned()));
let previous_state = vec![1.0, 2.0];
let previous_state = vec![DEFAULT_MASS_FLOW_SEED_KG_S, 1.0, 2.0];
let timeout = Duration::from_nanos(1);
let mut solver = NewtonConfig {
@@ -202,7 +205,7 @@ fn test_zoh_fallback_ignored_without_previous_state() {
let result = solver.solve(&mut system);
if let Ok(state) = result {
if state.status == ConvergenceStatus::TimedOutWithBestState {
assert_eq!(state.state.len(), 2);
assert_eq!(state.state.len(), 3);
}
}
}
@@ -210,7 +213,7 @@ fn test_zoh_fallback_ignored_without_previous_state() {
#[test]
fn test_zoh_fallback_picard() {
let mut system = create_test_system(Box::new(LinearSystem2x2::well_conditioned()));
let previous_state = vec![5.0, 10.0];
let previous_state = vec![DEFAULT_MASS_FLOW_SEED_KG_S, 5.0, 10.0];
let timeout = Duration::from_nanos(1);
let mut solver = PicardConfig {
@@ -235,7 +238,7 @@ fn test_zoh_fallback_picard() {
#[test]
fn test_zoh_fallback_uses_previous_residual() {
let mut system = create_test_system(Box::new(LinearSystem2x2::well_conditioned()));
let previous_state = vec![1.0, 2.0];
let previous_state = vec![DEFAULT_MASS_FLOW_SEED_KG_S, 1.0, 2.0];
let previous_residual = 1e-4;
let timeout = Duration::from_nanos(1);
@@ -298,6 +301,10 @@ fn test_picard_timeout_returns_error_when_configured() {
return_best_state_on_timeout: false,
zoh_fallback: false,
},
// CM1.2: Picard's positional update is misaligned by the ṁ-front /
// closure-back layout for this synthetic 2×2, so seed it at the analytical
// solution (ṁ=seed, P=1, h=1). CM1.3 restores alignment with real residuals.
initial_state: Some(vec![DEFAULT_MASS_FLOW_SEED_KG_S, 1.0, 1.0]),
..Default::default()
};
@@ -409,6 +416,9 @@ fn test_picard_config_best_state_preallocated() {
let mut solver = PicardConfig {
timeout: Some(Duration::from_millis(100)),
max_iterations: 10,
// CM1.2: seed Picard at the analytical solution (ṁ=seed, P=1, h=1) — the
// synthetic ṁ-closure misaligns Picard's positional update until CM1.3.
initial_state: Some(vec![DEFAULT_MASS_FLOW_SEED_KG_S, 1.0, 1.0]),
..Default::default()
};