use crate::Thermodynamics::ChemEquilibrium::equilibrium_log_moles::{SolverParams, Solvers};
use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::{
LMSolver, NRSolver, ReactionExtentError, TrustRegionSolver,
};
use nalgebra::DMatrix;
type ResidualFn<'a> = dyn Fn(&[f64]) -> Result<Vec<f64>, ReactionExtentError> + 'a;
type JacobianFn<'a> = dyn Fn(&[f64]) -> Result<DMatrix<f64>, ReactionExtentError> + 'a;
type FeasibilityFn<'a> = dyn Fn(&[f64]) -> bool + 'a;
#[allow(clippy::too_many_arguments)]
pub(crate) fn solve_legacy_backend(
backend: Solvers,
initial_guess: Vec<f64>,
residual: &ResidualFn<'_>,
jacobian: Option<&JacobianFn<'_>>,
feasible: &FeasibilityFn<'_>,
params: &SolverParams,
initial_moles: &[f64],
reactions: &DMatrix<f64>,
max_iterations: usize,
) -> Result<Vec<f64>, ReactionExtentError> {
let jacobian = jacobian.ok_or_else(|| ReactionExtentError::InvalidProblem {
field: "legacy_jacobian",
message: "a legacy backend requires the analytical Jacobian".to_string(),
})?;
match backend {
Solvers::LM => {
let mut solver = LMSolver {
f: residual,
jacobian,
feasible,
lambda: params.lambda,
tol: 1e-12,
max_iter: max_iterations,
alpha_min: params.alpha_min,
};
solver
.solve(initial_guess)
.map_err(ReactionExtentError::SolveError)
}
Solvers::NR => {
let mut solver = NRSolver {
f: residual,
jacobian,
feasible,
n0: initial_moles.to_vec(),
reactions: reactions.clone(),
tol: params.tol,
max_iter: max_iterations,
alpha_min: params.alpha_min,
};
solver
.solve(initial_guess)
.map_err(ReactionExtentError::SolveError)
}
Solvers::TR => {
let solver = TrustRegionSolver {
f: residual,
jacobian,
feasible,
tol: params.tol,
max_iter: max_iterations,
delta_init: params.delta_init,
delta_max: params.delta_max,
eta: params.eta,
};
solver
.solve(initial_guess)
.map_err(ReactionExtentError::SolveError)
}
}
}
#[cfg(test)]
mod tests {
use super::solve_legacy_backend;
use crate::Thermodynamics::ChemEquilibrium::equilibrium_log_moles::{SolverParams, Solvers};
use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::ReactionExtentError;
use nalgebra::DMatrix;
#[test]
fn missing_legacy_jacobian_is_a_typed_configuration_error() {
let residual = |_values: &[f64]| Ok(vec![0.0]);
let feasible = |_values: &[f64]| true;
assert!(matches!(
solve_legacy_backend(
Solvers::LM,
vec![0.0],
&residual,
None,
&feasible,
&SolverParams::default(),
&[1.0],
&DMatrix::zeros(1, 0),
1,
),
Err(ReactionExtentError::InvalidProblem {
field: "legacy_jacobian",
..
})
));
}
}