#![allow(clippy::needless_range_loop)]
pub(crate) mod driver;
pub(crate) mod filter;
pub(crate) mod geometry;
pub(crate) mod init;
pub(crate) mod linalg;
pub(crate) mod model;
pub(crate) mod trstlp;
pub(crate) mod update;
#[cfg(test)]
mod parity;
#[cfg(test)]
mod tests;
use crate::core::constraint::NonlinearInequalityConstraints;
use crate::core::math::{Scalar, VectorLen};
use crate::core::problem::{CostFunction, Problem};
use crate::core::solver::Solver;
use crate::core::state::CobylaState;
use crate::core::termination::TerminationReason;
use driver::{CobylaWork, Transition};
pub struct Cobyla<F = f64> {
rho_beg: F,
rho_end: F,
work: Option<CobylaWork<F>>,
}
impl<F: Scalar> Cobyla<F> {
pub fn new() -> Self {
Self {
rho_beg: F::from_f64(1.0).expect("1.0 representable"),
rho_end: F::from_f64(1e-6).expect("1e-6 representable"),
work: None,
}
}
pub fn with_rho_beg(mut self, rho_beg: F) -> Self {
self.rho_beg = rho_beg;
self
}
pub fn with_rho_end(mut self, rho_end: F) -> Self {
self.rho_end = rho_end;
self
}
}
impl<F: Scalar> Default for Cobyla<F> {
fn default() -> Self {
Self::new()
}
}
fn fill_from<V, F>(template: &V, slice: &[F]) -> V
where
V: Clone + std::ops::IndexMut<usize, Output = F>,
F: Copy,
{
let mut v = template.clone();
for (i, &x) in slice.iter().enumerate() {
v[i] = x;
}
v
}
impl<P, V, F> Solver<P, CobylaState<V, F>> for Cobyla<F>
where
F: Scalar,
P: CostFunction<Param = V, Output = F> + NonlinearInequalityConstraints,
V: Clone
+ VectorLen
+ std::ops::Index<usize, Output = F>
+ std::ops::IndexMut<usize, Output = F>,
{
type Error = P::Error;
fn init(
&mut self,
problem: &mut Problem<P>,
mut state: CobylaState<V, F>,
) -> Result<CobylaState<V, F>, Self::Error> {
let n = state.param.vec_len();
assert!(n >= 1, "Cobyla requires a non-empty start point");
let m = problem.inner().num_constraints();
let x0: Vec<F> = (0..n).map(|i| state.param[i]).collect();
let template = state.param.clone();
let (work, best_x, best_f) = {
let mut eval = |slice: &[F]| -> Result<(F, Vec<F>), P::Error> {
let xv = fill_from(&template, slice);
let f = problem.cost(&xv)?;
let cv = problem.inner().constraints(&xv)?;
debug_assert_eq!(
cv.vec_len(),
m,
"constraints() returned {} values but num_constraints() = {m}",
cv.vec_len(),
);
let c: Vec<F> = (0..m).map(|i| cv[i]).collect();
Ok((f, c))
};
CobylaWork::try_init(x0, m, self.rho_beg, self.rho_end, &mut eval)?
};
state.param = fill_from(&template, &best_x);
state.cost = Some(best_f);
state.rho = work.rho();
self.work = Some(work);
Ok(state)
}
fn next_iter(
&mut self,
problem: &mut Problem<P>,
mut state: CobylaState<V, F>,
) -> Result<(CobylaState<V, F>, Option<TerminationReason>), Self::Error> {
let template = state.param.clone();
let m = problem.inner().num_constraints();
let work = self
.work
.as_mut()
.expect("Cobyla::init must run before next_iter");
let transition = {
let mut eval = |slice: &[F]| -> Result<(F, Vec<F>), P::Error> {
let xv = fill_from(&template, slice);
let f = problem.cost(&xv)?;
let cv = problem.inner().constraints(&xv)?;
debug_assert_eq!(
cv.vec_len(),
m,
"constraints() returned {} values but num_constraints() = {m}",
cv.vec_len(),
);
let c: Vec<F> = (0..m).map(|i| cv[i]).collect();
Ok((f, c))
};
work.step(&mut eval)?
};
state.rho = work.rho();
let (best_x, best_f) = work.best();
state.param = fill_from(&template, &best_x);
state.cost = Some(best_f);
let reason = match transition {
Transition::Converged => Some(TerminationReason::SolverConverged),
Transition::Failed => Some(TerminationReason::SolverFailed),
Transition::Continue | Transition::RhoReduced => None,
};
Ok((state, reason))
}
}