chore: remove deprecated flow_boundary and update docs to match new architecture

This commit is contained in:
Sepehr
2026-03-01 20:00:09 +01:00
parent 20700afce8
commit d88914a44f
105 changed files with 11222 additions and 2994 deletions

View File

@@ -9,8 +9,9 @@ use crate::criteria::ConvergenceCriteria;
use crate::jacobian::JacobianMatrix;
use crate::metadata::SimulationMetadata;
use crate::solver::{
apply_newton_step, ConvergedState, ConvergenceStatus, JacobianFreezingConfig, Solver,
SolverError, TimeoutConfig,
apply_newton_step, ConvergedState, ConvergenceDiagnostics, ConvergenceStatus,
IterationDiagnostics, JacobianFreezingConfig, Solver, SolverError, SolverType,
TimeoutConfig, VerboseConfig,
};
use crate::system::System;
use entropyk_components::JacobianBuilder;
@@ -49,6 +50,8 @@ pub struct NewtonConfig {
pub convergence_criteria: Option<ConvergenceCriteria>,
/// Jacobian-freezing optimization.
pub jacobian_freezing: Option<JacobianFreezingConfig>,
/// Verbose mode configuration for diagnostics.
pub verbose_config: VerboseConfig,
}
impl Default for NewtonConfig {
@@ -68,6 +71,7 @@ impl Default for NewtonConfig {
initial_state: None,
convergence_criteria: None,
jacobian_freezing: None,
verbose_config: VerboseConfig::default(),
}
}
}
@@ -91,6 +95,12 @@ impl NewtonConfig {
self
}
/// Enables verbose mode for diagnostics.
pub fn with_verbose(mut self, config: VerboseConfig) -> Self {
self.verbose_config = config;
self
}
/// Computes the L2 norm of the residual vector.
fn residual_norm(residuals: &[f64]) -> f64 {
residuals.iter().map(|r| r * r).sum::<f64>().sqrt()
@@ -208,10 +218,19 @@ impl Solver for NewtonConfig {
fn solve(&mut self, system: &mut System) -> Result<ConvergedState, SolverError> {
let start_time = Instant::now();
// Initialize diagnostics collection if verbose mode enabled
let verbose_enabled = self.verbose_config.enabled && self.verbose_config.is_any_enabled();
let mut diagnostics = if verbose_enabled {
Some(ConvergenceDiagnostics::with_capacity(self.max_iterations))
} else {
None
};
tracing::info!(
max_iterations = self.max_iterations,
tolerance = self.tolerance,
line_search = self.line_search,
verbose = verbose_enabled,
"Newton-Raphson solver starting"
);
@@ -254,6 +273,9 @@ impl Solver for NewtonConfig {
let mut jacobian_matrix = JacobianMatrix::zeros(n_equations, n_state);
let mut frozen_count: usize = 0;
let mut force_recompute: bool = true;
// Cached condition number (for verbose mode when Jacobian frozen)
let mut cached_condition: Option<f64> = None;
// Pre-compute clipping mask
let clipping_mask: Vec<Option<(f64, f64)>> = (0..n_state)
@@ -323,6 +345,8 @@ impl Solver for NewtonConfig {
true
};
let jacobian_frozen_this_iter = !should_recompute;
if should_recompute {
// Fresh Jacobian assembly (in-place update)
jacobian_builder.clear();
@@ -350,6 +374,19 @@ impl Solver for NewtonConfig {
frozen_count = 0;
force_recompute = false;
// Compute and cache condition number if verbose mode enabled
if verbose_enabled && self.verbose_config.log_jacobian_condition {
let cond = jacobian_matrix.estimate_condition_number();
cached_condition = cond;
if let Some(c) = cond {
tracing::info!(iteration, condition_number = c, "Jacobian condition number");
if c > 1e10 {
tracing::warn!(iteration, condition_number = c, "Ill-conditioned Jacobian detected (κ > 1e10)");
}
}
}
tracing::debug!(iteration, "Fresh Jacobian computed");
} else {
frozen_count += 1;
@@ -391,6 +428,13 @@ impl Solver for NewtonConfig {
previous_norm = current_norm;
current_norm = Self::residual_norm(&residuals);
// Compute delta norm for diagnostics
let delta_norm: f64 = state.iter()
.zip(prev_iteration_state.iter())
.map(|(s, p)| (s - p).powi(2))
.sum::<f64>()
.sqrt();
if current_norm < best_residual {
best_state.copy_from_slice(&state);
@@ -409,6 +453,30 @@ impl Solver for NewtonConfig {
}
}
// Verbose mode: Log iteration residuals
if verbose_enabled && self.verbose_config.log_residuals {
tracing::info!(
iteration,
residual_norm = current_norm,
delta_norm = delta_norm,
alpha = alpha,
jacobian_frozen = jacobian_frozen_this_iter,
"Newton iteration"
);
}
// Collect iteration diagnostics
if let Some(ref mut diag) = diagnostics {
diag.push_iteration(IterationDiagnostics {
iteration,
residual_norm: current_norm,
delta_norm,
alpha: Some(alpha),
jacobian_frozen: jacobian_frozen_this_iter,
jacobian_condition: cached_condition,
});
}
tracing::debug!(iteration, residual_norm = current_norm, alpha, "Newton iteration complete");
// Check convergence
@@ -420,10 +488,29 @@ impl Solver for NewtonConfig {
} else {
ConvergenceStatus::Converged
};
// Finalize diagnostics
if let Some(ref mut diag) = diagnostics {
diag.iterations = iteration;
diag.final_residual = current_norm;
diag.best_residual = best_residual;
diag.converged = true;
diag.timing_ms = start_time.elapsed().as_millis() as u64;
diag.jacobian_condition_final = cached_condition;
diag.final_solver = Some(SolverType::NewtonRaphson);
if self.verbose_config.log_residuals {
tracing::info!("{}", diag.summary());
}
}
tracing::info!(iterations = iteration, final_residual = current_norm, "Converged (criteria)");
return Ok(ConvergedState::with_report(
let result = ConvergedState::with_report(
state, iteration, current_norm, status, report, SimulationMetadata::new(system.input_hash()),
));
);
return Ok(if let Some(d) = diagnostics {
ConvergedState { diagnostics: Some(d), ..result }
} else { result });
}
false
} else {
@@ -436,10 +523,29 @@ impl Solver for NewtonConfig {
} else {
ConvergenceStatus::Converged
};
// Finalize diagnostics
if let Some(ref mut diag) = diagnostics {
diag.iterations = iteration;
diag.final_residual = current_norm;
diag.best_residual = best_residual;
diag.converged = true;
diag.timing_ms = start_time.elapsed().as_millis() as u64;
diag.jacobian_condition_final = cached_condition;
diag.final_solver = Some(SolverType::NewtonRaphson);
if self.verbose_config.log_residuals {
tracing::info!("{}", diag.summary());
}
}
tracing::info!(iterations = iteration, final_residual = current_norm, "Converged");
return Ok(ConvergedState::new(
let result = ConvergedState::new(
state, iteration, current_norm, status, SimulationMetadata::new(system.input_hash()),
));
);
return Ok(if let Some(d) = diagnostics {
ConvergedState { diagnostics: Some(d), ..result }
} else { result });
}
if let Some(err) = self.check_divergence(current_norm, previous_norm, &mut divergence_count) {
@@ -448,6 +554,28 @@ impl Solver for NewtonConfig {
}
}
// Non-convergence: dump diagnostics if enabled
if let Some(ref mut diag) = diagnostics {
diag.iterations = self.max_iterations;
diag.final_residual = current_norm;
diag.best_residual = best_residual;
diag.converged = false;
diag.timing_ms = start_time.elapsed().as_millis() as u64;
diag.jacobian_condition_final = cached_condition;
diag.final_solver = Some(SolverType::NewtonRaphson);
if self.verbose_config.dump_final_state {
diag.final_state = Some(state.clone());
let json_output = diag.dump_diagnostics(self.verbose_config.output_format);
tracing::warn!(
iterations = self.max_iterations,
final_residual = current_norm,
"Non-convergence diagnostics:\n{}",
json_output
);
}
}
tracing::warn!(max_iterations = self.max_iterations, final_residual = current_norm, "Did not converge");
Err(SolverError::NonConvergence {
iterations: self.max_iterations,