use nalgebra::{convert, DimName, Dyn, OVector, U1};
use crate::{algo::TrustRegion, Domain, Function, Optimizer, Problem, Solver, System};
struct Builder<'a, P: Problem, A> {
p: &'a P,
dom: Domain<P::Field>,
algo: A,
x0: OVector<P::Field, Dyn>,
}
impl<'a, P: Problem> Builder<'a, P, TrustRegion<P>> {
fn new(p: &'a P) -> Self {
let dom = p.domain();
let algo = TrustRegion::new(p, &dom);
let dim = Dyn(dom.dim());
let x0 = OVector::from_element_generic(dim, U1::name(), convert(0.0));
Self { p, dom, algo, x0 }
}
}
impl<'a, P: Problem, A> Builder<'a, P, A> {
fn with_initial(mut self, x0: Vec<P::Field>) -> Self {
let dim = Dyn(self.dom.dim());
self.x0 = OVector::from_vec_generic(dim, U1::name(), x0);
self
}
fn with_algo<S2, FA>(self, factory: FA) -> Builder<'a, P, S2>
where
FA: FnOnce(&P, &Domain<P::Field>) -> S2,
{
let algo = factory(self.p, &self.dom);
Builder {
p: self.p,
dom: self.dom,
algo,
x0: self.x0,
}
}
fn build(mut self) -> Self {
self.dom.project(&mut self.x0);
self
}
}
pub struct SolverBuilder<'a, R: Problem, A>(Builder<'a, R, A>);
impl<'a, R: Problem, A> SolverBuilder<'a, R, A> {
pub fn with_initial(self, x0: Vec<R::Field>) -> Self {
Self(self.0.with_initial(x0))
}
pub fn with_algo<S2, FA>(self, factory: FA) -> SolverBuilder<'a, R, S2>
where
FA: FnOnce(&R, &Domain<R::Field>) -> S2,
{
SolverBuilder(self.0.with_algo(factory))
}
pub fn build(self) -> SolverDriver<'a, R, A> {
let Builder {
p: r,
dom,
algo,
x0,
} = self.0.build();
let rx = x0.clone_owned();
SolverDriver {
r,
dom,
algo,
x: x0,
rx,
}
}
}
pub struct SolverDriver<'a, R: Problem, A> {
r: &'a R,
dom: Domain<R::Field>,
algo: A,
x: OVector<R::Field, Dyn>,
rx: OVector<R::Field, Dyn>,
}
impl<'a, R: Problem> SolverDriver<'a, R, TrustRegion<R>> {
pub fn builder(r: &'a R) -> SolverBuilder<'a, R, TrustRegion<R>> {
SolverBuilder(Builder::new(r))
}
pub fn new(r: &'a R) -> Self {
SolverDriver::builder(r).build()
}
}
impl<'a, R: Problem, S> SolverDriver<'a, R, S> {
pub fn x(&self) -> &[R::Field] {
self.x.as_slice()
}
pub fn rx(&self) -> &[R::Field] {
self.rx.as_slice()
}
pub fn norm(&self) -> R::Field {
self.rx.norm()
}
}
impl<'a, R: System, A: Solver<R>> SolverDriver<'a, R, A> {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<(&[R::Field], R::Field), A::Error> {
self.algo
.solve_next(self.r, &self.dom, &mut self.x, &mut self.rx)?;
Ok((self.x.as_slice(), self.rx.norm()))
}
pub fn find<C>(&mut self, stop: C) -> Result<(&[R::Field], R::Field), A::Error>
where
C: Fn(SolverIterState<'_, R>) -> bool,
{
let mut iter = 0;
loop {
let norm = self.next()?.1;
let state = SolverIterState {
x: &self.x,
rx: &self.rx,
iter,
};
if stop(state) {
return Ok((self.x.as_slice(), norm));
}
iter += 1;
}
}
pub fn name(&self) -> &str {
A::NAME
}
}
pub struct SolverIterState<'a, R: Problem> {
x: &'a OVector<R::Field, Dyn>,
rx: &'a OVector<R::Field, Dyn>,
iter: usize,
}
impl<'a, R: Problem> SolverIterState<'a, R> {
pub fn x(&self) -> &[R::Field] {
self.x.as_slice()
}
pub fn rx(&self) -> &[R::Field] {
self.rx.as_slice()
}
pub fn norm(&self) -> R::Field {
self.rx.norm()
}
pub fn iter(&self) -> usize {
self.iter
}
}
pub struct OptimizerBuilder<'a, F: Problem, A>(Builder<'a, F, A>);
impl<'a, F: Problem, A> OptimizerBuilder<'a, F, A> {
pub fn with_initial(self, x0: Vec<F::Field>) -> Self {
Self(self.0.with_initial(x0))
}
pub fn with_algo<S2, FA>(self, factory: FA) -> OptimizerBuilder<'a, F, S2>
where
FA: FnOnce(&F, &Domain<F::Field>) -> S2,
{
OptimizerBuilder(self.0.with_algo(factory))
}
pub fn build(self) -> OptimizerDriver<'a, F, A> {
let Builder {
p: f,
dom,
algo,
x0,
} = self.0.build();
OptimizerDriver {
f,
dom,
algo,
x: x0,
fx: convert(f64::INFINITY),
}
}
}
pub struct OptimizerDriver<'a, F: Problem, A> {
f: &'a F,
dom: Domain<F::Field>,
algo: A,
x: OVector<F::Field, Dyn>,
fx: F::Field,
}
impl<'a, F: Problem> OptimizerDriver<'a, F, TrustRegion<F>> {
pub fn builder(f: &'a F) -> OptimizerBuilder<'a, F, TrustRegion<F>> {
OptimizerBuilder(Builder::new(f))
}
pub fn new(f: &'a F) -> Self {
OptimizerDriver::builder(f).build()
}
}
impl<'a, F: Problem, A> OptimizerDriver<'a, F, A> {
pub fn x(&self) -> &[F::Field] {
self.x.as_slice()
}
pub fn fx(&self) -> F::Field {
self.fx
}
}
impl<'a, F: Function, A: Optimizer<F>> OptimizerDriver<'a, F, A> {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<(&[F::Field], F::Field), A::Error> {
self.algo
.opt_next(self.f, &self.dom, &mut self.x)
.map(|fx| (self.x.as_slice(), fx))
}
pub fn find<C>(&mut self, stop: C) -> Result<(&[F::Field], F::Field), A::Error>
where
C: Fn(OptimizerIterState<'_, F>) -> bool,
{
let mut iter = 0;
loop {
self.fx = self.next()?.1;
let state = OptimizerIterState {
x: &self.x,
fx: self.fx,
iter,
no_progress: self.algo.no_progress(),
};
if stop(state) {
return Ok((self.x.as_slice(), self.fx));
}
iter += 1;
}
}
pub fn name(&self) -> &str {
A::NAME
}
}
pub struct OptimizerIterState<'a, F: Problem> {
x: &'a OVector<F::Field, Dyn>,
fx: F::Field,
iter: usize,
no_progress: bool,
}
impl<'a, F: Problem> OptimizerIterState<'a, F> {
pub fn x(&self) -> &[F::Field] {
self.x.as_slice()
}
pub fn fx(&self) -> F::Field {
self.fx
}
pub fn iter(&self) -> usize {
self.iter
}
pub fn no_progress(&self) -> bool {
self.no_progress
}
}
#[cfg(test)]
mod tests {
use crate::{
algo::{NelderMead, Steffensen},
testing::Sphere,
};
use super::*;
struct WithDomain(pub Domain<f64>);
impl Problem for WithDomain {
type Field = f64;
fn domain(&self) -> Domain<Self::Field> {
self.0.clone()
}
}
#[test]
fn solver_basic_use_case() {
let r = Sphere::new(4);
let mut solver = SolverDriver::builder(&r)
.with_initial(vec![10.0; 4])
.build();
let tolerance = 1e-6;
let (_, norm) = solver
.find(|state| state.iter() >= 100 || state.norm() < tolerance)
.unwrap();
assert!(norm <= tolerance);
}
#[test]
fn solver_custom() {
let r = Sphere::new(1);
let mut solver = SolverDriver::builder(&r)
.with_algo(Steffensen::new)
.with_initial(vec![10.0])
.build();
let tolerance = 1e-6;
let (_, norm) = solver
.find(|state| state.iter() >= 100 || state.norm() < tolerance)
.unwrap();
assert!(norm <= tolerance);
}
#[test]
fn solver_initial() {
let x0 = vec![10.0; 4];
let f = Sphere::new(4);
let solver = SolverDriver::builder(&f).with_initial(x0.clone()).build();
assert_eq!(solver.x(), &x0);
}
#[test]
fn solver_initial_in_domain() {
let f = WithDomain(Domain::rect(vec![0.0, 0.0], vec![1.0, 1.0]));
let solver = SolverDriver::builder(&f)
.with_initial(vec![10.0, -10.0])
.build();
assert_eq!(solver.x(), &[1.0, 0.0]);
}
#[test]
fn optimizer_basic_use_case() {
let f = Sphere::new(4);
let mut optimizer = OptimizerDriver::builder(&f)
.with_initial(vec![10.0; 4])
.build();
let tolerance = 1e-6;
let (_, value) = optimizer
.find(|state| state.iter() >= 100 || state.fx() < tolerance)
.unwrap();
assert!(value <= tolerance);
}
#[test]
fn optimizer_custom() {
let f = Sphere::new(4);
let mut optimizer = OptimizerDriver::builder(&f)
.with_algo(NelderMead::new)
.with_initial(vec![10.0; 4])
.build();
let tolerance = 1e-6;
let (_, value) = optimizer
.find(|state| state.iter() >= 100 || state.fx() < tolerance)
.unwrap();
assert!(value <= tolerance);
}
#[test]
fn optimizer_initial() {
let x0 = vec![10.0; 4];
let f = Sphere::new(4);
let optimizer = OptimizerDriver::builder(&f)
.with_initial(x0.clone())
.build();
assert_eq!(optimizer.x(), &x0);
}
#[test]
fn optimizer_initial_in_domain() {
let f = WithDomain(Domain::rect(vec![0.0, 0.0], vec![1.0, 1.0]));
let optimizer = OptimizerDriver::builder(&f)
.with_initial(vec![10.0, -10.0])
.build();
assert_eq!(optimizer.x(), &[1.0, 0.0]);
}
#[test]
fn optimizer_no_progress() {
let x0 = vec![-2.0, 2.0];
let f = Sphere::new(2);
let mut optimizer = OptimizerDriver::builder(&f)
.with_initial(x0)
.with_algo(NelderMead::new)
.build();
let tolerance = 1e-6;
let (_, value) = optimizer
.find(|state| state.iter() >= 100 || state.no_progress())
.unwrap();
assert!(value <= tolerance);
}
}