#[cfg(test)]
mod test;
mod constraint;
mod gradient_descent;
mod line_search;
mod newton_raphson;
mod strategy;
mod tolerance;
mod trust_region;
pub use constraint::EqualityConstraint;
pub use gradient_descent::GradientDescent;
pub use line_search::{LineSearch, LineSearchError};
pub use newton_raphson::NewtonRaphson;
pub use strategy::SolveStrategy;
pub use tolerance::Tolerances;
pub use trust_region::TrustRegion;
use crate::{
math::{
Erase, Jacobian, Quantity, Scalar, Solution, Style, StyledError, Tensor, Vector,
assert::AssertionError,
matrix::square::SquareMatrixError,
sparse::{CscMatrix, SparseError, SparseSolver},
styled_error,
},
units::UnitDiv,
};
use std::{fmt::Debug, ops::Mul};
pub type StepSize<D, X> = Quantity<<<X as Tensor>::Unit as UnitDiv<<D as Tensor>::Unit>>::Output>;
pub trait ZerothOrderRootFinding<F, X> {
fn root(
&self,
function: impl FnMut(&X) -> Result<F, String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
) -> Result<X, OptimizationError>;
}
pub trait FirstOrderRootFinding<F, J, X> {
fn root(
&self,
function: impl FnMut(&X) -> Result<F, String>,
jacobian: impl FnMut(&X) -> Result<J, String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
sparse: Option<SparseSolver>,
) -> Result<X, OptimizationError>;
}
pub trait FirstOrderRootFindingIncremental<F, J, X> {
fn root_incremental(
&self,
function: impl FnMut(&X) -> Result<F, String>,
jacobian: impl FnMut(&X) -> Result<J, String>,
update: impl FnMut(&X, &Vector, Scalar, bool) -> Result<(), String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
sparse: Option<SparseSolver>,
) -> Result<X, OptimizationError>;
}
pub trait FirstOrderOptimization<F, J, X> {
fn minimize(
&self,
function: impl FnMut(&X) -> Result<F, String>,
jacobian: impl FnMut(&X) -> Result<J, String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
) -> Result<X, OptimizationError>;
}
pub trait SecondOrderOptimization<F, J, H, X> {
fn minimize(
&self,
function: impl FnMut(&X) -> Result<F, String>,
jacobian: impl FnMut(&X) -> Result<J, String>,
hessian: impl FnMut(&X) -> Result<H, String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
sparse: Option<SparseSolver>,
) -> Result<X, OptimizationError>;
}
pub trait SecondOrderOptimizationIncremental<F, J, H, X> {
#[allow(clippy::too_many_arguments)]
fn minimize_incremental(
&self,
function: impl FnMut(&X) -> Result<F, String>,
jacobian: impl FnMut(&X) -> Result<J, String>,
hessian: impl FnMut(&X) -> Result<H, String>,
update: impl FnMut(&X, &Vector, Scalar, bool) -> Result<(), String>,
initial_guess: X,
equality_constraint: EqualityConstraint,
sparse: Option<SparseSolver>,
) -> Result<X, OptimizationError>;
}
#[allow(clippy::too_many_arguments)]
pub trait FirstOrderRootFindingBlock<U, V, Ru, Rv, Kuu, Kvu, Kuv, Kvv> {
fn root_block(
&self,
residual_global: impl FnMut(&U, &V) -> Result<Ru, String>,
residual_local: impl FnMut(&U, &V) -> Result<Rv, String>,
tangents: impl FnMut(&U, &V) -> Result<(Kuu, Kvu, Kuv, Kvv), String>,
initial_guess: (U, V),
constraint_global: (CscMatrix, Vector),
constraint_local: (CscMatrix, Vector),
sparse: Option<SparseSolver>,
strategy: SolveStrategy,
) -> Result<(U, V), OptimizationError>;
}
#[allow(clippy::too_many_arguments)]
pub trait SecondOrderOptimizationBlock<F, U, V, Ru, Rv, Kuu, Kvu, Kuv, Kvv> {
fn minimize_block(
&self,
function: impl FnMut(&U, &V) -> Result<F, String>,
residual_global: impl FnMut(&U, &V) -> Result<Ru, String>,
residual_local: impl FnMut(&U, &V) -> Result<Rv, String>,
tangents: impl FnMut(&U, &V) -> Result<(Kuu, Kvu, Kuv, Kvv), String>,
initial_guess: (U, V),
constraint_global: (CscMatrix, Vector),
constraint_local: (CscMatrix, Vector),
sparse: Option<SparseSolver>,
strategy: SolveStrategy,
) -> Result<(U, V), OptimizationError>;
}
trait BacktrackingLineSearch<J, X>
where
Self: Debug,
{
fn backtracking_line_search<D, E>(
&self,
mut function: impl FnMut(&X, Scalar) -> Result<Scalar, String>,
mut jacobian: impl FnMut(&X) -> Result<J, String>,
argument: &X,
jacobian0: &J,
decrement: &D,
step_size: Scalar,
) -> Result<Scalar, OptimizationError>
where
J: Erase<Erased = E> + Jacobian,
D: Erase<Erased = E> + Tensor,
E: Tensor,
X: Solution,
<X as Tensor>::Unit: UnitDiv<<D as Tensor>::Unit>,
for<'a> &'a D: Mul<StepSize<D, X>, Output = X>,
{
if matches!(self.get_line_search(), LineSearch::None) {
Ok(step_size)
} else {
match self.get_line_search().backtrack(
&mut function,
&mut jacobian,
argument,
jacobian0,
decrement,
step_size,
) {
Ok(step_size) => Ok(step_size),
Err(error) => Err(OptimizationError::Upstream(
format!("{error}"),
format!("{self:?}"),
)),
}
}
}
fn get_line_search(&self) -> &LineSearch;
}
pub enum OptimizationError {
Intermediate(String),
MaximumStepsReached(usize, String),
NotMinimum(String, String),
Upstream(String, String),
SingularMatrix,
}
impl From<String> for OptimizationError {
fn from(error: String) -> Self {
Self::Intermediate(error)
}
}
impl StyledError for OptimizationError {
fn message(&self, style: &Style) -> String {
let (h, c) = (style.headline, style.frame);
match self {
Self::Intermediate(message) => message.to_string(),
Self::MaximumStepsReached(steps, solver) => format!(
"{h}Maximum number of steps ({steps}) reached.{c}\n\
In solver: {solver}."
),
Self::NotMinimum(solution, solver) => format!(
"{h}The obtained solution is not a minimum.{c}\n\
For solution: {solution}.\n\
In solver: {solver}."
),
Self::SingularMatrix => format!("{h}Matrix is singular."),
Self::Upstream(error, solver) => format!(
"{error}{c}\n\
In solver: {solver}."
),
}
}
}
styled_error!(OptimizationError);
impl From<OptimizationError> for String {
fn from(error: OptimizationError) -> Self {
error.to_string()
}
}
impl From<OptimizationError> for AssertionError {
fn from(error: OptimizationError) -> Self {
Self {
message: error.to_string(),
}
}
}
impl From<SquareMatrixError> for OptimizationError {
fn from(_error: SquareMatrixError) -> Self {
Self::SingularMatrix
}
}
impl From<SparseError> for OptimizationError {
fn from(_error: SparseError) -> Self {
Self::SingularMatrix
}
}