use crate::error::OptimError;
#[derive(Debug, Clone, PartialEq)]
pub struct OptimControl {
pub maxit: usize,
pub fnscale: f64,
pub parscale: Vec<f64>,
pub ndeps: Vec<f64>,
pub factr: f64,
pub pgtol: f64,
pub lmm: usize,
pub trace: usize,
pub report: usize,
}
impl OptimControl {
pub fn default_for_dimension(n: usize) -> Self {
Self {
maxit: 100,
fnscale: 1.0,
parscale: vec![1.0; n],
ndeps: vec![1e-3; n],
factr: 1e7,
pgtol: 0.0,
lmm: 5,
trace: 0,
report: 10,
}
}
pub fn validate_for_dimension(&self, n: usize) -> Result<(), OptimError> {
validate_len("parscale", n, self.parscale.len())?;
validate_len("ndeps", n, self.ndeps.len())?;
if !self.fnscale.is_finite() || self.fnscale == 0.0 {
return Err(OptimError::InvalidControl {
field: "fnscale",
reason: "must be finite and nonzero".to_string(),
});
}
if !self.factr.is_finite() || self.factr < 0.0 {
return Err(OptimError::InvalidControl {
field: "factr",
reason: "must be finite and nonnegative".to_string(),
});
}
if !self.pgtol.is_finite() || self.pgtol < 0.0 {
return Err(OptimError::InvalidControl {
field: "pgtol",
reason: "must be finite and nonnegative".to_string(),
});
}
if self.lmm == 0 {
return Err(OptimError::InvalidControl {
field: "lmm",
reason: "must be greater than zero".to_string(),
});
}
for (index, value) in self.parscale.iter().copied().enumerate() {
if !value.is_finite() || value <= 0.0 {
return Err(OptimError::InvalidControl {
field: "parscale",
reason: format!("entry {index} must be finite and positive"),
});
}
}
for (index, value) in self.ndeps.iter().copied().enumerate() {
if !value.is_finite() || value <= 0.0 {
return Err(OptimError::InvalidControl {
field: "ndeps",
reason: format!("entry {index} must be finite and positive"),
});
}
}
Ok(())
}
}
impl Default for OptimControl {
fn default() -> Self {
Self::default_for_dimension(0)
}
}
fn validate_len(name: &'static str, expected: usize, actual: usize) -> Result<(), OptimError> {
if actual == expected {
Ok(())
} else {
Err(OptimError::DimensionMismatch {
name,
expected,
actual,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_dimensioned() {
let control = OptimControl::default_for_dimension(3);
assert_eq!(control.maxit, 100);
assert_eq!(control.parscale, vec![1.0; 3]);
assert_eq!(control.ndeps, vec![1e-3; 3]);
assert!(control.validate_for_dimension(3).is_ok());
}
#[test]
fn zero_dimension_defaults_are_valid() {
let control = OptimControl::default_for_dimension(0);
assert!(control.parscale.is_empty());
assert!(control.ndeps.is_empty());
assert!(control.validate_for_dimension(0).is_ok());
}
#[test]
fn rejects_invalid_scaling() {
let mut control = OptimControl::default_for_dimension(1);
control.parscale[0] = 0.0;
assert!(matches!(
control.validate_for_dimension(1),
Err(OptimError::InvalidControl {
field: "parscale",
..
})
));
}
}