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

@@ -22,7 +22,10 @@ fn test_verbose_config_default_is_disabled() {
// All features should be disabled by default for backward compatibility
assert!(!config.enabled, "enabled should be false by default");
assert!(!config.log_residuals, "log_residuals should be false by default");
assert!(
!config.log_residuals,
"log_residuals should be false by default"
);
assert!(
!config.log_jacobian_condition,
"log_jacobian_condition should be false by default"
@@ -48,8 +51,14 @@ fn test_verbose_config_all_enabled() {
assert!(config.enabled, "enabled should be true");
assert!(config.log_residuals, "log_residuals should be true");
assert!(config.log_jacobian_condition, "log_jacobian_condition should be true");
assert!(config.log_solver_switches, "log_solver_switches should be true");
assert!(
config.log_jacobian_condition,
"log_jacobian_condition should be true"
);
assert!(
config.log_solver_switches,
"log_solver_switches should be true"
);
assert!(config.dump_final_state, "dump_final_state should be true");
}
@@ -86,7 +95,10 @@ fn test_verbose_config_is_any_enabled() {
log_residuals: true,
..Default::default()
};
assert!(config.is_any_enabled(), "should be true when one feature is enabled");
assert!(
config.is_any_enabled(),
"should be true when one feature is enabled"
);
}
// =============================================================================
@@ -102,6 +114,7 @@ fn test_iteration_diagnostics_creation() {
alpha: Some(0.5),
jacobian_frozen: true,
jacobian_condition: Some(1e3),
..Default::default()
};
assert_eq!(diag.iteration, 5);
@@ -122,6 +135,7 @@ fn test_iteration_diagnostics_without_alpha() {
alpha: None,
jacobian_frozen: false,
jacobian_condition: None,
..Default::default()
};
assert_eq!(diag.alpha, None);
@@ -139,7 +153,9 @@ fn test_jacobian_condition_number_well_conditioned() {
let entries = vec![(0, 0, 2.0), (1, 1, 1.0)];
let j = JacobianMatrix::from_builder(&entries, 2, 2);
let cond = j.estimate_condition_number().expect("should compute condition number");
let cond = j
.estimate_condition_number()
.expect("should compute condition number");
// Condition number of diagonal matrix is max/min diagonal entry
assert!(
@@ -152,15 +168,12 @@ fn test_jacobian_condition_number_well_conditioned() {
#[test]
fn test_jacobian_condition_number_ill_conditioned() {
// Nearly singular matrix
let entries = vec![
(0, 0, 1.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 1.0000001),
];
let entries = vec![(0, 0, 1.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 1.0000001)];
let j = JacobianMatrix::from_builder(&entries, 2, 2);
let cond = j.estimate_condition_number().expect("should compute condition number");
let cond = j
.estimate_condition_number()
.expect("should compute condition number");
assert!(
cond > 1e6,
@@ -175,7 +188,9 @@ fn test_jacobian_condition_number_identity() {
let entries = vec![(0, 0, 1.0), (1, 1, 1.0), (2, 2, 1.0)];
let j = JacobianMatrix::from_builder(&entries, 3, 3);
let cond = j.estimate_condition_number().expect("should compute condition number");
let cond = j
.estimate_condition_number()
.expect("should compute condition number");
assert!(
(cond - 1.0).abs() < 1e-10,
@@ -191,10 +206,7 @@ fn test_jacobian_condition_number_empty_matrix() {
let cond = j.estimate_condition_number();
assert!(
cond.is_none(),
"Expected None for empty matrix"
);
assert!(cond.is_none(), "Expected None for empty matrix");
}
// =============================================================================
@@ -220,10 +232,7 @@ fn test_solver_switch_event_creation() {
#[test]
fn test_solver_type_display() {
assert_eq!(
format!("{}", SolverType::NewtonRaphson),
"Newton-Raphson"
);
assert_eq!(format!("{}", SolverType::NewtonRaphson), "Newton-Raphson");
assert_eq!(
format!("{}", SolverType::SequentialSubstitution),
"Sequential Substitution"
@@ -232,7 +241,10 @@ fn test_solver_type_display() {
#[test]
fn test_switch_reason_display() {
assert_eq!(format!("{}", SwitchReason::Divergence), "divergence detected");
assert_eq!(
format!("{}", SwitchReason::Divergence),
"divergence detected"
);
assert_eq!(
format!("{}", SwitchReason::SlowConvergence),
"slow convergence"
@@ -283,6 +295,7 @@ fn test_convergence_diagnostics_push_iteration() {
alpha: None,
jacobian_frozen: false,
jacobian_condition: None,
..Default::default()
});
diag.push_iteration(IterationDiagnostics {
@@ -292,6 +305,7 @@ fn test_convergence_diagnostics_push_iteration() {
alpha: Some(1.0),
jacobian_frozen: false,
jacobian_condition: Some(100.0),
..Default::default()
});
assert_eq!(diag.iteration_history.len(), 2);
@@ -410,6 +424,7 @@ fn test_convergence_diagnostics_json_serialization() {
alpha: Some(1.0),
jacobian_frozen: false,
jacobian_condition: Some(100.0),
..Default::default()
});
diag.push_switch(SolverSwitchEvent {
@@ -439,16 +454,18 @@ fn test_convergence_diagnostics_round_trip() {
// Serialize to JSON
let json = serde_json::to_string(&diag).expect("Should serialize");
// Deserialize back
let restored: ConvergenceDiagnostics =
serde_json::from_str(&json).expect("Should deserialize");
let restored: ConvergenceDiagnostics = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(restored.iterations, 25);
assert!((restored.final_residual - 1e-8).abs() < 1e-20);
assert!(restored.converged);
assert_eq!(restored.timing_ms, 100);
assert_eq!(restored.final_solver, Some(SolverType::SequentialSubstitution));
assert_eq!(
restored.final_solver,
Some(SolverType::SequentialSubstitution)
);
}
#[test]
@@ -457,7 +474,7 @@ fn test_dump_diagnostics_json_format() {
diag.iterations = 10;
diag.final_residual = 1e-4;
diag.converged = false;
let json_output = diag.dump_diagnostics(VerboseOutputFormat::Json);
assert!(json_output.starts_with('{'));
// to_string_pretty adds spaces after colons
@@ -471,7 +488,7 @@ fn test_dump_diagnostics_log_format() {
diag.iterations = 10;
diag.final_residual = 1e-4;
diag.converged = false;
let log_output = diag.dump_diagnostics(VerboseOutputFormat::Log);
assert!(log_output.contains("Convergence Diagnostics Summary"));
assert!(log_output.contains("Converged: NO"));