Skip to main content

sim_lib_numbers_optimize/
model.rs

1//! Optimization plans, evidence, and shared numerical helpers.
2
3use super::*;
4
5/// Source of first derivatives.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum DerivativeSource {
8    Analytic,
9    Automatic,
10    FiniteDifference,
11}
12/// Globalization strategy; bounded paths are genuine projected/active-set methods.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum StepPolicy {
15    BrentGolden,
16    LevenbergMarquardt,
17    TrustRegionReflective,
18    ProjectedBfgs,
19}
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum Termination {
22    Converged,
23    BoundaryConverged,
24    Flat,
25    WorkLimit,
26    NonFinite,
27    NoProgress,
28    InvalidPlan,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq)]
32pub struct Tolerances {
33    pub argument: f64,
34    pub objective: f64,
35    pub gradient: f64,
36}
37impl Default for Tolerances {
38    fn default() -> Self {
39        Self {
40            argument: 1e-9,
41            objective: 1e-12,
42            gradient: 1e-8,
43        }
44    }
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub struct Limits {
49    pub evaluations: usize,
50    pub iterations: usize,
51    pub memory_bytes: usize,
52}
53impl Default for Limits {
54    fn default() -> Self {
55        Self {
56            evaluations: 10_000,
57            iterations: 500,
58            memory_bytes: 64 * 1024 * 1024,
59        }
60    }
61}
62
63#[derive(Clone, Debug, PartialEq)]
64pub struct Bounds {
65    pub lower: Vec<f64>,
66    pub upper: Vec<f64>,
67}
68impl Bounds {
69    pub fn new(lower: Vec<f64>, upper: Vec<f64>) -> Result<Self, Error> {
70        if lower.len() != upper.len()
71            || lower
72                .iter()
73                .zip(&upper)
74                .any(|(l, u)| !l.is_finite() || !u.is_finite() || l > u)
75        {
76            return Err(Error::InvalidPlan(
77                "bounds must be finite, ordered, and equal length",
78            ));
79        }
80        Ok(Self { lower, upper })
81    }
82    pub(crate) fn project(&self, x: &mut [f64]) {
83        for ((x, l), u) in x.iter_mut().zip(&self.lower).zip(&self.upper) {
84            *x = x.clamp(*l, *u);
85        }
86    }
87}
88
89#[derive(Clone, Debug, PartialEq)]
90pub struct ObjectivePlan {
91    pub bounds: Bounds,
92    pub scale: Vec<f64>,
93    pub derivative: DerivativeSource,
94    pub policy: StepPolicy,
95    pub tolerances: Tolerances,
96    pub limits: Limits,
97    pub initial_radius: f64,
98}
99#[derive(Clone, Debug, PartialEq)]
100pub struct LeastSquaresPlan {
101    pub bounds: Option<Bounds>,
102    pub variable_scale: Vec<f64>,
103    pub residual_scale: Vec<f64>,
104    pub derivative: DerivativeSource,
105    pub policy: StepPolicy,
106    pub tolerances: Tolerances,
107    pub limits: Limits,
108    pub initial_damping: f64,
109}
110
111#[derive(Clone, Debug, PartialEq)]
112pub struct Work {
113    pub evaluations: usize,
114    pub iterations: usize,
115    pub memory_bytes: usize,
116}
117#[derive(Clone, Debug, PartialEq)]
118pub struct OptimizeResult {
119    pub point: Vec<f64>,
120    pub value: f64,
121    pub gradient_norm: f64,
122    pub active: Vec<usize>,
123    pub termination: Termination,
124    pub work: Work,
125}
126#[derive(Clone, Debug, PartialEq)]
127pub struct ScalarResult {
128    pub minimizer: f64,
129    pub value: f64,
130    pub final_bracket: (f64, f64),
131    pub termination: Termination,
132    pub work: Work,
133}
134#[derive(Clone, Debug, PartialEq)]
135pub enum Covariance {
136    Available(Vec<Vec<f64>>),
137    Unavailable(CovarianceUnavailable),
138}
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub enum CovarianceUnavailable {
141    RankDeficient,
142    InsufficientDegreesOfFreedom,
143    StatisticalAssumptionsNotDeclared,
144}
145#[derive(Clone, Debug, PartialEq)]
146pub struct LeastSquaresResult {
147    pub point: Vec<f64>,
148    pub residuals: Vec<f64>,
149    pub residual_norm: f64,
150    pub rank: usize,
151    pub active: Vec<usize>,
152    pub covariance: Covariance,
153    pub termination: Termination,
154    pub work: Work,
155}
156
157#[derive(Clone, Debug, Eq, PartialEq)]
158pub enum Error {
159    InvalidPlan(&'static str),
160    Dimension(&'static str),
161}
162impl fmt::Display for Error {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            Self::InvalidPlan(s) | Self::Dimension(s) => f.write_str(s),
166        }
167    }
168}
169impl std::error::Error for Error {}
170
171pub(crate) fn finite(v: &[f64]) -> bool {
172    v.iter().all(|x| x.is_finite())
173}
174pub(crate) fn norm(v: &[f64]) -> f64 {
175    v.iter().map(|x| x * x).sum::<f64>().sqrt()
176}
177pub(crate) fn validate_scale(scale: &[f64], n: usize) -> Result<(), Error> {
178    if scale.len() != n || scale.iter().any(|x| !x.is_finite() || *x <= 0.0) {
179        Err(Error::InvalidPlan(
180            "scale must contain one finite positive value per variable",
181        ))
182    } else {
183        Ok(())
184    }
185}