#![allow(clippy::needless_range_loop)]
#![allow(dead_code)]
pub(crate) mod bigden;
pub(crate) mod biglag;
pub(crate) mod driver;
pub(crate) mod init;
pub(crate) mod trsapp;
#[cfg(test)]
mod parity;
use crate::core::inner::InitialState;
use crate::core::math::{Scalar, VectorLen};
use crate::core::problem::{CostFunction, Problem};
use crate::core::solver::Solver;
use crate::core::state::{PointState, State};
use crate::core::termination::Termination;
use driver::{NewuoaWork, Transition};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Newuoa<F: Scalar = f64> {
radius_tolerance: Option<F>,
rho_beg: F,
rho_end: F,
npt: Option<usize>,
work: Option<NewuoaWork<F>>,
}
impl<F: Scalar> Newuoa<F> {
pub fn rho(&self) -> Option<F> {
self.work.as_ref().map(|work| work.rho())
}
#[allow(clippy::type_complexity)]
pub fn geometry_diagnostics(
&self,
) -> Option<(F, F, Vec<Vec<F>>, Vec<F>, Vec<F>)> {
self.work.as_ref().map(|work| work.geometry_diagnostics())
}
pub fn with_absolute_radius_tolerance(
mut self,
value: impl Into<Option<F>>,
) -> Self {
self.radius_tolerance =
crate::core::convergence::optional_tolerance(value);
self
}
pub fn new() -> Self {
Self {
rho_beg: F::from_f64(1.0).expect("1.0 representable"),
radius_tolerance: None,
rho_end: F::from_f64(1e-6).expect("1e-6 representable"),
npt: None,
work: None,
}
}
pub fn with_initial_radius(mut self, rho_beg: F) -> Self {
self.rho_beg = rho_beg;
self
}
pub fn with_final_radius(mut self, rho_end: F) -> Self {
self.rho_end = rho_end;
self
}
pub fn with_npt(mut self, npt: usize) -> Self {
self.npt = Some(npt);
self
}
}
impl<F: Scalar> Default for Newuoa<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<V, F> InitialState<V> for Newuoa<F>
where
F: Scalar,
V: Clone,
{
type State = PointState<V, F>;
fn seed(&self, x: &V) -> Self::State {
PointState::new(x.clone())
}
}
impl<P, V, F> Solver<P, PointState<V, F>> for Newuoa<F>
where
F: Scalar,
P: CostFunction<Param = V, Output = F>,
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: PointState<V, F>,
) -> Result<PointState<V, F>, Self::Error> {
state.reset();
self.work = None;
let n = state.param().vec_len();
assert!(n >= 1, "Newuoa requires a non-empty start point");
let npt = self.npt.unwrap_or(2 * n + 1);
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, P::Error> {
problem.cost(&fill_from(&template, slice))
};
NewuoaWork::try_init(
x0,
self.rho_beg,
self.rho_end,
npt,
&mut eval,
)?
};
state.replace(fill_from(&template, &best_x), best_f);
self.work = Some(work);
Ok(state)
}
fn next_iter(
&mut self,
problem: &mut Problem<P>,
mut state: PointState<V, F>,
) -> Result<crate::SolverStep<PointState<V, F>>, Self::Error> {
let template = state.param().clone();
let work = self
.work
.as_mut()
.expect("Newuoa::init must run before next_iter");
let out = {
let mut eval = |slice: &[F]| -> Result<F, P::Error> {
problem.cost(&fill_from(&template, slice))
};
work.step(&mut eval)?
};
let mut best_f = state.cost();
for (xabs, f_new) in &out.evaluated {
if *f_new < best_f {
best_f = *f_new;
state.replace(fill_from(&template, xabs), best_f);
}
}
let reason = match out.transition {
Transition::Converged => Some(Termination::custom(
"newuoa.final_radius",
"The algorithm completed its final trust-radius stage; this does not certify feasibility or global optimality.",
vec![crate::Measurement {
name: "final_radius".into(),
value: self.rho_end,
}],
)),
Transition::Continue | Transition::RhoReduced => None,
};
Ok(crate::SolverStep::from((state, reason)))
}
fn terminate(&self, _state: &PointState<V, F>) -> Option<Termination<F>> {
let tolerance = self.radius_tolerance?;
let metric = self.rho()?;
(metric.is_finite() && metric <= tolerance).then(|| {
Termination::upper_bound(
crate::ConvergenceTest::Radius,
metric,
tolerance,
tolerance,
None,
)
})
}
}