use crate::core::constraint::BoxConstrained;
use crate::core::executor::OptimizationResult;
use crate::core::inner::InnerExecutor;
use crate::core::math::{
ClampInPlace, ComponentMulAssign, MatDiagonal, 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, State};
use crate::core::termination::{TerminationCriterion, TerminationReason};
use crate::solver::bounded_cma_es::{evaluate_with_penalty, BoundedCmaEs};
use crate::solver::cma_es::sort_population_ascending;
use crate::solver::cma_inject::{default_c_y, MemeticInner};
pub struct BoundedCmaInject<I, V, M>
where
I: MemeticInner<V>,
{
cma: BoundedCmaEs<V, M>,
inner: InnerExecutor<I::State, I>,
k: usize,
c_y_override: Option<f64>,
}
impl<I, V, M> BoundedCmaInject<I, V, M>
where
I: MemeticInner<V>,
{
pub fn with_inner_solver(cma: BoundedCmaEs<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, "BoundedCmaInject 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, "BoundedCmaInject 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,
}
}
}
impl<P, I, V, M> Solver<P, BasicPopulationState<V>> for BoundedCmaInject<I, V, M>
where
P: CostFunction<Param = V, Output = f64> + BoxConstrained,
I: MemeticInner<V> + Solver<P, <I as MemeticInner<V>>::State>,
I::State: State<Param = V, Float = f64>,
V: VectorLen
+ Clone
+ ScaledAdd<f64>
+ ScaleInPlace
+ ComponentMulAssign
+ ClampInPlace
+ NormSquared
+ SampleStandardNormal
+ std::ops::Index<usize, Output = f64>
+ std::ops::IndexMut<usize, Output = f64>,
M: MatrixIdentity
+ MatVec<V>
+ MatTransposeVec<V>
+ MatDiagonal<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("BoundedCmaEs::init must run before BoundedCmaInject::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 = {
let w = self.cma.working().expect("working still populated");
let (_raw, pen) = evaluate_with_penalty(
problem,
&x_inj,
problem.lower(),
problem.upper(),
&w.gamma,
n,
);
pen
};
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> {
<BoundedCmaEs<V, M> as Solver<P, BasicPopulationState<V>>>::terminate(&self.cma, state)
}
}