use crate::core::executor::OptimizationResult;
use crate::core::inner::InnerExecutor;
use crate::core::math::{
ComponentMulAssign, MatTransposeVec, MatVec, MatrixIdentity, NormSquared, RankOneUpdate,
SampleStandardNormal, ScaleInPlace, ScaledAdd, SymmetricEigen, VectorLen,
};
use crate::core::problem::CostFunction;
use crate::core::solver::Solver;
use crate::core::state::{
BasicPopulationState, BasicSimplexState, BasicState, GradientState, LbfgsState, State,
};
use crate::core::termination::{TerminationCriterion, TerminationReason};
use crate::solver::cma_es::{sort_population_ascending, CmaEs};
use crate::solver::lbfgsb::LBFGSB;
use crate::solver::levenberg_marquardt::LevenbergMarquardt;
use crate::solver::nelder_mead::NelderMead;
pub trait MemeticInner<V> {
type State: State<Param = V>;
fn seed(&self, x: &V, sigma: f64) -> Self::State;
fn work_units(&self, state: &Self::State) -> u64;
}
type ClosureSeedFn<V, S> = Box<dyn Fn(&V, f64) -> S>;
type ClosureWorkFn<S> = Box<dyn Fn(&S) -> u64>;
pub struct ClosureInner<I, S, V> {
inner: I,
seed_fn: ClosureSeedFn<V, S>,
work_fn: ClosureWorkFn<S>,
}
impl<I, S, V> ClosureInner<I, S, V> {
pub fn new(
inner: I,
seed_fn: impl Fn(&V, f64) -> S + 'static,
work_fn: impl Fn(&S) -> u64 + 'static,
) -> Self {
Self {
inner,
seed_fn: Box::new(seed_fn),
work_fn: Box::new(work_fn),
}
}
}
impl<P, I, S, V> Solver<P, S> for ClosureInner<I, S, V>
where
I: Solver<P, S>,
S: State,
{
fn init(&mut self, problem: &P, state: S) -> S {
self.inner.init(problem, state)
}
fn next_iter(&mut self, problem: &P, state: S) -> (S, Option<TerminationReason>) {
self.inner.next_iter(problem, state)
}
fn terminate(&self, state: &S) -> Option<TerminationReason> {
self.inner.terminate(state)
}
}
impl<I, S, V> MemeticInner<V> for ClosureInner<I, S, V>
where
S: State<Param = V>,
{
type State = S;
fn seed(&self, x: &V, sigma: f64) -> S {
(self.seed_fn)(x, sigma)
}
fn work_units(&self, state: &S) -> u64 {
(self.work_fn)(state)
}
}
impl<V> MemeticInner<V> for NelderMead
where
V: VectorLen + Clone + std::ops::IndexMut<usize, Output = f64>,
{
type State = BasicSimplexState<V>;
fn seed(&self, x: &V, sigma: f64) -> BasicSimplexState<V> {
let n = x.vec_len();
let mut vertices = Vec::with_capacity(n + 1);
vertices.push(x.clone());
for j in 0..n {
let mut v = x.clone();
v[j] += sigma;
vertices.push(v);
}
BasicSimplexState::from_simplex(vertices)
}
fn work_units(&self, state: &BasicSimplexState<V>) -> u64 {
state.cost_evals()
}
}
impl<V> MemeticInner<V> for LevenbergMarquardt
where
V: Clone,
{
type State = BasicState<V>;
fn seed(&self, x: &V, _sigma: f64) -> BasicState<V> {
BasicState::new(x.clone())
}
fn work_units(&self, state: &BasicState<V>) -> u64 {
state.cost_evals() + state.gradient_evals()
}
}
impl<V, LS> MemeticInner<V> for LBFGSB<LS>
where
V: Clone,
{
type State = LbfgsState<V>;
fn seed(&self, x: &V, _sigma: f64) -> LbfgsState<V> {
LbfgsState::new(x.clone(), self.m_capacity)
}
fn work_units(&self, state: &LbfgsState<V>) -> u64 {
state.cost_evals() + state.gradient_evals()
}
}
pub struct CmaInject<I, V, M>
where
I: MemeticInner<V>,
{
cma: CmaEs<V, M>,
inner: InnerExecutor<I::State, I>,
k: usize,
c_y_override: Option<f64>,
}
impl<I, V, M> CmaInject<I, V, M>
where
I: MemeticInner<V>,
{
pub fn with_inner_solver(cma: CmaEs<V, M>, inner: I) -> Self {
Self {
cma,
inner: InnerExecutor::new(inner).max_iter(50),
k: 1,
c_y_override: None,
}
}
pub fn with_k(mut self, k: usize) -> Self {
assert!(k >= 1, "CmaInject requires k >= 1, got {}", k);
self.k = k;
self
}
pub fn with_c_y(mut self, c_y: f64) -> Self {
assert!(c_y > 0.0, "CmaInject requires c_y > 0, got {}", c_y);
self.c_y_override = Some(c_y);
self
}
pub fn with_inner_max_iter(self, n: u64) -> Self {
let Self {
cma,
inner,
k,
c_y_override,
} = self;
Self {
cma,
inner: inner.max_iter(n),
k,
c_y_override,
}
}
pub fn inner_terminate_on<C>(self, criterion: C) -> Self
where
C: TerminationCriterion<I::State> + 'static,
{
let Self {
cma,
inner,
k,
c_y_override,
} = self;
Self {
cma,
inner: inner.terminate_on(criterion),
k,
c_y_override,
}
}
}
pub(crate) fn default_c_y(n: usize) -> f64 {
let n = n as f64;
n.sqrt() + 2.0 * n / (n + 2.0)
}
impl<P, I, V, M> Solver<P, BasicPopulationState<V>> for CmaInject<I, V, M>
where
P: CostFunction<Param = V, Output = f64>,
I: MemeticInner<V> + Solver<P, <I as MemeticInner<V>>::State>,
I::State: State<Param = V, Float = f64>,
V: VectorLen
+ Clone
+ ScaledAdd<f64>
+ ScaleInPlace
+ ComponentMulAssign
+ NormSquared
+ SampleStandardNormal
+ std::ops::Index<usize, Output = f64>
+ std::ops::IndexMut<usize, Output = f64>,
M: MatrixIdentity
+ MatVec<V>
+ MatTransposeVec<V>
+ ScaleInPlace
+ RankOneUpdate<V>
+ SymmetricEigen<V>
+ Clone,
{
fn init(&mut self, problem: &P, state: BasicPopulationState<V>) -> BasicPopulationState<V> {
self.cma.init(problem, state)
}
fn next_iter(
&mut self,
problem: &P,
state: BasicPopulationState<V>,
) -> (BasicPopulationState<V>, Option<TerminationReason>) {
let (mut state, reason) = self.cma.next_iter(problem, state);
if let Some(r) = reason {
return (state, Some(r));
}
let (n, m, sigma) = {
let w = self
.cma
.working()
.expect("CmaEs::init must run before CmaInject::next_iter");
(w.n, w.m.clone(), w.sigma)
};
let c_y = self.c_y_override.unwrap_or_else(|| default_c_y(n));
let refine = self.k.min(state.candidates.len());
for i in 0..refine {
let inner_state = self.inner.solver().seed(&state.candidates[i], sigma);
let inner_result: OptimizationResult<I::State> = self.inner.run(problem, inner_state);
state.cost_evals += self.inner.solver().work_units(&inner_result.state);
if inner_result.reason.is_failure() {
return (state, Some(inner_result.reason));
}
let x_refined = inner_result.state.param().clone();
let mut y = x_refined;
y.scaled_add(-1.0, &m);
y.scale_in_place(1.0 / sigma);
let inv_sqrt_norm = {
let w = self.cma.working().expect("working still populated");
let mut bt_y = w.b.mat_transpose_vec(&y);
bt_y.component_mul_assign(&w.d_inv);
bt_y.norm_squared().sqrt()
};
if inv_sqrt_norm > 0.0 {
let alpha = (c_y / inv_sqrt_norm).min(1.0);
if alpha < 1.0 {
y.scale_in_place(alpha);
}
}
let mut x_inj = m.clone();
x_inj.scaled_add(sigma, &y);
let cost_new = problem.cost(&x_inj);
state.cost_evals += 1;
state.candidates[i] = x_inj;
state.costs[i] = cost_new;
}
if refine > 0 {
sort_population_ascending(&mut state.candidates, &mut state.costs);
}
(state, None)
}
fn terminate(&self, state: &BasicPopulationState<V>) -> Option<TerminationReason> {
<CmaEs<V, M> as Solver<P, BasicPopulationState<V>>>::terminate(&self.cma, state)
}
}