#![allow(clippy::needless_range_loop)]
pub(crate) mod driver;
pub(crate) mod geometry;
pub(crate) mod getact;
pub(crate) mod init;
pub(crate) mod trstep;
#[cfg(test)]
mod parity;
use crate::core::constraint::LinearConstraints;
use crate::core::inner::InitialState;
use crate::core::math::{MatTransposeVec, Scalar, VectorLen};
use crate::core::problem::{CostFunction, Problem};
use crate::core::solver::Solver;
use crate::core::state::LincoaState;
use crate::core::termination::TerminationReason;
use driver::{LincoaWork, Transition};
use init::fold_constraints;
pub struct Lincoa<F = f64> {
rho_beg: F,
rho_end: F,
npt: Option<usize>,
work: Option<LincoaWork<F>>,
}
impl<F: Scalar> Lincoa<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"),
npt: None,
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
}
pub fn with_npt(mut self, npt: usize) -> Self {
self.npt = Some(npt);
self
}
}
impl<F: Scalar> Default for Lincoa<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 Lincoa<F>
where
F: Scalar,
V: Clone,
{
type State = LincoaState<V, F>;
fn seed(&self, x: &V) -> Self::State {
LincoaState::new(x.clone())
}
}
impl<P, V, F> Solver<P, LincoaState<V, F>> for Lincoa<F>
where
F: Scalar,
P: CostFunction<Param = V, Output = F> + LinearConstraints,
P::Matrix: MatTransposeVec<V>,
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: LincoaState<V, F>,
) -> Result<LincoaState<V, F>, Self::Error> {
let n = state.param.vec_len();
assert!(n >= 1, "Lincoa 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 extract_rows = |a: &P::Matrix, b: &V| -> Vec<(Vec<F>, F)> {
let m = b.vec_len();
(0..m)
.map(|j| {
let mut ej = b.clone();
for i in 0..m {
ej[i] = if i == j { F::one() } else { F::zero() };
}
let normal = a.mat_transpose_vec(&ej);
((0..n).map(|i| normal[i]).collect(), b[j])
})
.collect()
};
let (amat, bvec_abs) = {
let inner = problem.inner();
let ineq = inner
.inequalities()
.map(|(a, b)| extract_rows(a, b))
.unwrap_or_default();
let eq = inner
.equalities()
.map(|(a, b)| extract_rows(a, b))
.unwrap_or_default();
let lower: Option<Vec<F>> = inner.lower().map(|v| (0..n).map(|i| v[i]).collect());
let upper: Option<Vec<F>> = inner.upper().map(|v| (0..n).map(|i| v[i]).collect());
fold_constraints(n, &x0, lower.as_deref(), upper.as_deref(), &eq, &ineq)
};
let (work, best_x, best_f) = {
let mut eval =
|slice: &[F]| -> Result<F, P::Error> { problem.cost(&fill_from(&template, slice)) };
LincoaWork::try_init(
x0,
amat,
bvec_abs,
self.rho_beg,
self.rho_end,
npt,
&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: LincoaState<V, F>,
) -> Result<(LincoaState<V, F>, Option<TerminationReason>), Self::Error> {
let template = state.param.clone();
let work = self
.work
.as_mut()
.expect("Lincoa::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)?
};
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 out.transition {
Transition::Converged => Some(TerminationReason::SolverConverged),
Transition::Continue | Transition::RhoReduced => None,
};
Ok((state, reason))
}
}