use crate::error::OptimError;
#[derive(Debug, Clone, PartialEq)]
pub struct Bounds {
pub lower: Vec<f64>,
pub upper: Vec<f64>,
}
impl Bounds {
pub fn new(lower: Vec<f64>, upper: Vec<f64>) -> Result<Self, OptimError> {
let bounds = Self { lower, upper };
bounds.validate()?;
Ok(bounds)
}
pub fn unbounded(n: usize) -> Self {
Self {
lower: vec![f64::NEG_INFINITY; n],
upper: vec![f64::INFINITY; n],
}
}
pub fn len(&self) -> usize {
self.lower.len()
}
pub fn is_empty(&self) -> bool {
self.lower.is_empty()
}
pub fn validate(&self) -> Result<(), OptimError> {
if self.lower.len() != self.upper.len() {
return Err(OptimError::DimensionMismatch {
name: "bounds",
expected: self.lower.len(),
actual: self.upper.len(),
});
}
for (index, (&lower, &upper)) in self.lower.iter().zip(&self.upper).enumerate() {
if lower.is_nan() || upper.is_nan() {
return Err(OptimError::InvalidBounds {
index: Some(index),
lower: Some(lower),
upper: Some(upper),
reason: "NaN bounds are not allowed".to_string(),
});
}
if lower > upper {
return Err(OptimError::InvalidBounds {
index: Some(index),
lower: Some(lower),
upper: Some(upper),
reason: "lower must be less than or equal to upper".to_string(),
});
}
}
Ok(())
}
pub fn validate_for_dimension(&self, n: usize) -> Result<(), OptimError> {
self.validate()?;
if self.len() == n {
Ok(())
} else {
Err(OptimError::DimensionMismatch {
name: "bounds",
expected: n,
actual: self.len(),
})
}
}
pub fn contains(&self, par: &[f64]) -> Result<(), OptimError> {
self.validate_for_dimension(par.len())?;
for (index, ((&value, &lower), &upper)) in par
.iter()
.zip(self.lower.iter())
.zip(self.upper.iter())
.enumerate()
{
if value < lower || value > upper {
return Err(OptimError::InitialParameterOutOfBounds {
index,
value,
lower,
upper,
});
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_infinite_bounds() {
let bounds = Bounds::unbounded(2);
assert!(bounds.validate_for_dimension(2).is_ok());
}
#[test]
fn rejects_nan_bounds() {
let err = Bounds::new(vec![f64::NAN], vec![1.0]).unwrap_err();
assert!(matches!(err, OptimError::InvalidBounds { .. }));
}
}