use std::marker::PhantomData;
use crate::SamplingBounds;
use crate::core::inner::ResumableInner;
use crate::core::math::{
NormSquared, SampleUniformBox, Scalar, ScaledAdd, VectorLen,
};
use crate::core::problem::{CostFunction, Problem};
use crate::core::rng::{ChaCha8Rng, RngExt, SeedableRng};
use crate::core::solver::Solver;
use crate::core::state::PopulationProgress;
use rand::distr::uniform::SampleUniform;
use crate::solver::cma_es::{apply_permutation, nan_last_cmp};
use crate::solver::ssga::{
bga_mutate_in_place, blx_alpha_crossover, nam_select,
replace_worst_if_better,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct ChainHistory<LS, S, F> {
chains: Vec<Option<(LS, S)>>,
last_ls_cost: Vec<F>,
ls_application_count: Vec<u32>,
}
impl<LS, S, F> Default for ChainHistory<LS, S, F> {
fn default() -> Self {
Self {
chains: Vec::new(),
last_ls_cost: Vec::new(),
ls_application_count: Vec::new(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "LS: serde::Serialize, LS::State: serde::Serialize, F: serde::Serialize",
deserialize = "LS: serde::Deserialize<'de>, LS::State: serde::Deserialize<'de>, F: serde::Deserialize<'de>"
))
)]
pub struct MaLsCh<V, LS, F: Scalar = f64>
where
LS: ResumableInner<V, F>,
{
pop_size: usize,
blx_alpha: F,
nam_pool: usize,
mutation_prob: F,
bga_range_fraction: F,
ls_intensity: u64,
ls_improvement_threshold: F,
nfrec: Option<u64>,
initial_scale_fallback: F,
seed: u64,
rng: Option<ChaCha8Rng>,
pub(crate) ls: LS,
history: ChainHistory<LS, LS::State, F>,
#[cfg_attr(feature = "serde", serde(default))]
inner_diagnostics: Option<Vec<crate::core::inner::diagnostics::Record<F>>>,
_phantom: PhantomData<V>,
}
impl<V, LS, F: Scalar> MaLsCh<V, LS, F>
where
LS: ResumableInner<V, F>,
{
pub fn with_inner(seed: u64, ls: LS) -> Self {
Self {
pop_size: 60,
blx_alpha: F::from_f64(0.5).unwrap(),
nam_pool: 4,
mutation_prob: F::from_f64(0.125).unwrap(),
bga_range_fraction: F::from_f64(0.1).unwrap(),
ls_intensity: 300,
ls_improvement_threshold: F::from_f64(1e-8).unwrap(),
nfrec: None,
initial_scale_fallback: F::one(),
seed,
rng: None,
ls,
history: ChainHistory::default(),
inner_diagnostics: None,
_phantom: PhantomData,
}
}
pub fn with_inner_diagnostics(mut self, enabled: bool) -> Self {
self.inner_diagnostics = enabled.then(Vec::new);
self
}
pub fn inner_diagnostics(
&self,
) -> &[crate::core::inner::diagnostics::Record<F>] {
self.inner_diagnostics.as_deref().unwrap_or(&[])
}
pub fn ls_application_count(&self, i: usize) -> u32 {
self.history.ls_application_count[i]
}
pub fn chain(&self, i: usize) -> Option<(&LS, &LS::State)> {
self.history
.chains
.get(i)?
.as_ref()
.map(|(solver, state)| (solver, state))
}
pub fn with_pop_size(mut self, pop_size: usize) -> Self {
assert!(
pop_size >= self.nam_pool,
"MaLsCh requires pop_size >= nam_pool (got pop_size={}, nam_pool={})",
pop_size,
self.nam_pool
);
self.pop_size = pop_size;
self
}
pub fn with_blx_alpha(mut self, alpha: F) -> Self {
assert!(
alpha >= F::zero(),
"blx_alpha must be >= 0, got {:?}",
alpha
);
self.blx_alpha = alpha;
self
}
pub fn with_nam_pool(mut self, pool: usize) -> Self {
assert!(pool >= 2, "nam_pool must be >= 2, got {:?}", pool);
assert!(
pool <= self.pop_size,
"MaLsCh requires nam_pool <= pop_size (got nam_pool={}, pop_size={})",
pool,
self.pop_size
);
self.nam_pool = pool;
self
}
pub fn with_mutation_prob(mut self, p: F) -> Self {
assert!(
(F::zero()..=F::one()).contains(&p),
"mutation_prob must be in [0, 1], got {:?}",
p
);
self.mutation_prob = p;
self
}
pub fn with_bga_range_fraction(mut self, f: F) -> Self {
assert!(f > F::zero(), "bga_range_fraction must be > 0, got {:?}", f);
self.bga_range_fraction = f;
self
}
pub fn with_ls_intensity(mut self, istr: u64) -> Self {
assert!(istr >= 1, "ls_intensity must be >= 1, got {:?}", istr);
self.ls_intensity = istr;
self
}
pub fn with_ls_improvement_threshold(mut self, delta: F) -> Self {
assert!(
delta >= F::zero(),
"ls_improvement_threshold must be >= 0, got {:?}",
delta
);
self.ls_improvement_threshold = delta;
self
}
pub fn with_nfrec(mut self, n: u64) -> Self {
assert!(n >= 1, "nfrec must be >= 1, got {:?}", n);
self.nfrec = Some(n);
self
}
pub fn with_initial_scale_fallback(mut self, scale: F) -> Self {
assert!(
scale > F::zero(),
"initial_scale_fallback must be > 0, got {:?}",
scale
);
self.initial_scale_fallback = scale;
self
}
}
fn sigma_init_for<V, F: Scalar>(candidates: &[V], i: usize) -> Option<F>
where
V: Clone + ScaledAdd<F> + NormSquared<F>,
{
if candidates.len() < 2 {
return None;
}
let mut best_sq = F::infinity();
for (j, x) in candidates.iter().enumerate() {
if j == i {
continue;
}
let mut diff = candidates[i].clone();
diff.scaled_add(-F::one(), x);
let d_sq = diff.norm_squared();
if d_sq < best_sq {
best_sq = d_sq;
}
}
Some(F::from_f64(0.5).unwrap() * best_sq.sqrt())
}
impl<P, V, LS, F> Solver<P, PopulationProgress<V, F>> for MaLsCh<V, LS, F>
where
F: Scalar + SampleUniform,
P: CostFunction<Param = V, Output = F> + SamplingBounds<Param = V>,
V: VectorLen
+ Clone
+ SampleUniformBox
+ ScaledAdd<F>
+ NormSquared<F>
+ std::ops::Index<usize, Output = F>
+ std::ops::IndexMut<usize, Output = F>,
LS: ResumableInner<V, F>
+ Solver<P, <LS as ResumableInner<V, F>>::State, Error = P::Error>,
{
type Error = crate::ConstraintSolveError<P::Error>;
fn init(
&mut self,
problem: &mut Problem<P>,
mut state: PopulationProgress<V, F>,
) -> Result<PopulationProgress<V, F>, Self::Error> {
crate::core::constraint::validate_sampling(problem.inner(), None)?;
if let Some(records) = &mut self.inner_diagnostics {
records.clear();
}
let lo = problem.inner().sampling_lower().clone();
let hi = problem.inner().sampling_upper().clone();
let mut rng = ChaCha8Rng::seed_from_u64(self.seed);
state.reset();
self.history = ChainHistory::default();
super::population::prepare_population(
&mut state.candidates,
&lo,
&hi,
self.pop_size,
&mut rng,
);
for x in &state.candidates {
state.costs.push(problem.cost(x)?);
self.history.chains.push(None);
self.history.last_ls_cost.push(F::infinity());
self.history.ls_application_count.push(0);
}
sort_parallel_arrays(&mut state, &mut self.history);
self.rng = Some(rng);
Ok(state)
}
fn next_iter(
&mut self,
problem: &mut Problem<P>,
mut state: PopulationProgress<V, F>,
) -> Result<crate::SolverStep<PopulationProgress<V, F>>, Self::Error> {
let history = &mut self.history;
let lo = problem.inner().sampling_lower().clone();
let hi = problem.inner().sampling_upper().clone();
let rng = self
.rng
.as_mut()
.expect("MaLsCh::init must run before next_iter");
let nfrec = self.nfrec.unwrap_or(self.ls_intensity);
let phase_start_counts = *problem.counts();
while problem.counts().cost_evals - phase_start_counts.cost_evals
< nfrec
{
let (p1, p2) = nam_select(&state.candidates, self.nam_pool, rng);
let mut child = blx_alpha_crossover(
&state.candidates[p1],
&state.candidates[p2],
self.blx_alpha,
&lo,
&hi,
rng,
);
bga_mutate_in_place(
&mut child,
&lo,
&hi,
self.mutation_prob.to_f64().unwrap(),
self.bga_range_fraction,
rng,
);
let c_child = problem.cost(&child)?;
if let Some(replaced_idx) = replace_worst_if_better(
&mut state.candidates,
&mut state.costs,
child,
c_child,
) {
history.chains[replaced_idx] = None;
history.last_ls_cost[replaced_idx] = F::infinity();
history.ls_application_count[replaced_idx] = 0;
}
}
sort_parallel_arrays(&mut state, history);
let mut c_ls: Option<usize> = None;
let mut best_cost_in_s_ls = F::infinity();
for i in 0..state.candidates.len() {
let eligible = history.ls_application_count[i] == 0
|| (history.last_ls_cost[i] - state.costs[i]
>= self.ls_improvement_threshold);
if eligible && state.costs[i] < best_cost_in_s_ls {
best_cost_in_s_ls = state.costs[i];
c_ls = Some(i);
}
}
let c_ls = c_ls.unwrap_or(0);
let resumed = history.chains[c_ls].is_some();
let mut initial_scale = None;
let (mut ls, inner_state, initialized) =
match history.chains[c_ls].take() {
Some((ls, mut s)) => {
ls.prepare_resume(&mut s);
(ls, s, true)
}
None => {
let scale = sigma_init_for(&state.candidates, c_ls)
.filter(|s| s.is_finite() && *s > F::zero())
.unwrap_or(self.initial_scale_fallback);
initial_scale = Some(scale);
let derived_seed = rng.random::<u64>();
let (ls, state) = self.ls.seed_chain(
&state.candidates[c_ls],
state.costs[c_ls],
scale,
derived_seed,
);
let initialized = ls.seeded_chain_is_initialized();
(ls, state, initialized)
}
};
let mut control = crate::RunControl::new()
.max_iter(u64::MAX)
.max_cost_evals(self.ls_intensity);
ls.configure_segment(&inner_state, &mut control);
let inner_result = crate::core::inner::diagnostics::run(
&mut self.inner_diagnostics,
problem,
crate::core::inner::diagnostics::Context {
outer_iteration: crate::State::iter(&state),
member: Some(c_ls),
resumed,
initial_scale,
},
|problem| {
crate::core::executor::run_segment_with_control(
problem,
inner_state,
&mut ls,
&mut control,
initialized,
)
},
)?;
if !inner_result
.report
.termination
.can_continue_as_inner(crate::PartialResultPolicy::Consume)
{
return Ok(crate::SolverStep::from((
state,
inner_result.report.into_outer_termination(
crate::PartialResultPolicy::Consume,
),
)));
}
let new_cost = inner_result.best_cost();
let pre_segment_cost = state.costs[c_ls];
if !new_cost.is_nan()
&& new_cost < F::infinity()
&& (state.costs[c_ls].is_nan() || new_cost < state.costs[c_ls])
{
state.candidates[c_ls] = inner_result.best_param().clone();
state.costs[c_ls] = new_cost;
}
history.last_ls_cost[c_ls] = pre_segment_cost;
history.ls_application_count[c_ls] =
history.ls_application_count[c_ls].saturating_add(1);
if pre_segment_cost - state.costs[c_ls] >= self.ls_improvement_threshold
{
history.chains[c_ls] = Some((ls, inner_result.state));
}
sort_parallel_arrays(&mut state, history);
Ok(crate::SolverStep::from((state, None)))
}
}
fn sort_parallel_arrays<V, LS, S, F: Scalar>(
state: &mut PopulationProgress<V, F>,
history: &mut ChainHistory<LS, S, F>,
) {
let n = state.candidates.len();
debug_assert_eq!(n, state.costs.len());
debug_assert_eq!(n, history.chains.len());
debug_assert_eq!(n, history.last_ls_cost.len());
debug_assert_eq!(n, history.ls_application_count.len());
let mut idx: Vec<usize> = (0..n).collect();
idx.sort_by(|&i, &j| nan_last_cmp(&state.costs[i], &state.costs[j]));
apply_permutation(&mut state.candidates, &idx);
apply_permutation(&mut state.costs, &idx);
apply_permutation(&mut history.chains, &idx);
apply_permutation(&mut history.last_ls_cost, &idx);
apply_permutation(&mut history.ls_application_count, &idx);
state.select_best_member();
}