chore: remove deprecated flow_boundary and update docs to match new architecture
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user