use std::{cmp::Ordering, collections::HashSet, marker::PhantomData, ops::Not};
use typed_builder::TypedBuilder;
use super::Optimizer;
use crate::{
mutation::executor::MutationExecutor,
recombination::executor::RecombinationExecutor,
score::{ParetoDominance, Scores},
selection::executor::SelectionExecutor,
termination::executor::TerminationExecutor,
testing::executor::TestExecutor,
};
#[derive(TypedBuilder, Debug)]
pub struct Nsga2<
Solution,
Tst: TestExecutor<Solution, OBJECTIVE_NUM, TstExecStrat>,
Sel: SelectionExecutor<Solution, OBJECTIVE_NUM, SelExecStrat>,
Rec: RecombinationExecutor<Solution, PARENT_NUM, OFFSPRING_NUM, RecExecStrat>,
Mut: MutationExecutor<Solution, MutExecStrat>,
Ter: TerminationExecutor<Solution, OBJECTIVE_NUM, TerExecStrat>,
TstExecStrat,
TerExecStrat,
SelExecStrat,
MutExecStrat,
RecExecStrat,
const OBJECTIVE_NUM: usize,
const PARENT_NUM: usize,
const OFFSPRING_NUM: usize,
> {
#[builder(setter(
transform = |v: Vec<Solution>| {
v.is_empty()
.not()
.then_some(v)
.unwrap_or_else(|| panic!("initial population is empty"))
},
doc = "
The initial population setter.
# Panics
Panics if population is empty.",
))]
population: Vec<Solution>,
tester: Tst,
selector: Sel,
recombinator: Rec,
mutator: Mut,
terminator: Ter,
#[builder(setter(skip), default = population.len())]
initial_population_size: usize,
#[builder(setter(skip), default)]
_solution: PhantomData<Solution>,
#[builder(setter(skip), default)]
_eva_es: PhantomData<TstExecStrat>,
#[builder(setter(skip), default)]
_ter_es: PhantomData<TerExecStrat>,
#[builder(setter(skip), default)]
_sel_es: PhantomData<SelExecStrat>,
#[builder(setter(skip), default)]
_mut_es: PhantomData<MutExecStrat>,
#[builder(setter(skip), default)]
_rec_es: PhantomData<RecExecStrat>,
}
type SolutionIndex = usize;
type DominanceCounter = u32;
type CrowdingDistance = f64;
type FrontNumber = u32;
type DominanceList = Vec<SolutionIndex>;
type Front = Vec<SolutionIndex>;
impl<
Solution,
Tst: TestExecutor<Solution, OBJECTIVE_NUM, TstExecStrat>,
Sel: SelectionExecutor<Solution, OBJECTIVE_NUM, SelExecStrat>,
Rec: RecombinationExecutor<Solution, PARENT_NUM, OFFSPRING_NUM, RecExecStrat>,
Mut: MutationExecutor<Solution, MutExecStrat>,
Ter: TerminationExecutor<Solution, OBJECTIVE_NUM, TerExecStrat>,
TstExecStrat,
TerExecStrat,
SelExecStrat,
MutExecStrat,
RecExecStrat,
const OBJECTIVE_NUM: usize,
const PARENT_NUM: usize,
const OFFSPRING_NUM: usize,
>
Nsga2<
Solution,
Tst,
Sel,
Rec,
Mut,
Ter,
TstExecStrat,
TerExecStrat,
SelExecStrat,
MutExecStrat,
RecExecStrat,
OBJECTIVE_NUM,
PARENT_NUM,
OFFSPRING_NUM,
>
{
fn crowding_distance_selection(
&self,
solutions: Vec<Solution>,
scores: Vec<Scores<OBJECTIVE_NUM>>,
) -> (Vec<Solution>, Vec<Scores<OBJECTIVE_NUM>>) {
let mut dominance_lists: Vec<DominanceList> =
vec![Vec::new(); solutions.len()];
let mut dominance_counters: Vec<DominanceCounter> =
vec![0; solutions.len()];
let mut first_front: Front = Vec::new();
for p_idx in 0..solutions.len() {
let (p_sc, rest_scs) =
scores[p_idx..].split_first().expect("no scores remain");
for (i, q_sc) in rest_scs.iter().enumerate() {
let q_idx = p_idx + i + 1;
match p_sc.dominance(q_sc) {
Ordering::Less => {
dominance_lists[p_idx].push(q_idx);
dominance_counters[q_idx] += 1;
}
Ordering::Greater => {
dominance_lists[q_idx].push(p_idx);
dominance_counters[p_idx] += 1;
}
Ordering::Equal => {}
}
}
if dominance_counters[p_idx] == 0 {
first_front.push(p_idx); }
}
debug_assert!(
!first_front.is_empty(),
"first front must have at least 1 solution"
);
let mut front_numbers: Vec<FrontNumber> =
vec![FrontNumber::MAX; solutions.len()];
let mut last_front = first_front;
let mut new_solutions_indices: Vec<SolutionIndex> = Vec::new();
let mut front_idx = 0;
while new_solutions_indices.len() + last_front.len()
< self.initial_population_size
{
let mut next_front = Vec::new();
for p_idx in last_front.iter() {
for q_idx in dominance_lists[*p_idx].iter_mut() {
dominance_counters[*q_idx] -= 1;
if dominance_counters[*q_idx] == 0 {
front_numbers[*q_idx] = front_idx; next_front.push(*q_idx); }
}
}
new_solutions_indices.append(&mut last_front);
last_front = next_front;
front_idx += 1;
}
let mut crowding_distances: Vec<CrowdingDistance> =
vec![0.0; solutions.len()];
if last_front.len() > 2 {
for o_idx in 0..OBJECTIVE_NUM {
last_front.sort_by(|&a_idx, &b_idx| {
scores[a_idx][o_idx]
.partial_cmp(&scores[b_idx][o_idx])
.unwrap_or(Ordering::Greater) });
let first_idx = last_front[0];
let last_idx = last_front[last_front.len() - 1];
crowding_distances[first_idx] = f64::MAX;
crowding_distances[last_idx] = f64::MAX;
let min_score = scores[first_idx][o_idx];
let max_score = scores[last_idx][o_idx];
let score_diff = if max_score != min_score {
f64::from(max_score - min_score)
} else {
1.0
};
for idx in 2..last_front.len() - 2 {
if crowding_distances[idx] != f64::MAX {
let prev_cd = crowding_distances[idx - 1];
let next_cd = crowding_distances[idx + 1];
crowding_distances[idx] += (next_cd - prev_cd).abs() / score_diff;
}
}
}
last_front.sort_by(|&a_idx, &b_idx| {
crowding_distances[b_idx].total_cmp(&crowding_distances[a_idx])
});
}
new_solutions_indices.append(&mut last_front);
new_solutions_indices.truncate(self.initial_population_size);
new_solutions_indices.sort_by(|&a_idx, &b_idx| {
front_numbers[a_idx]
.cmp(&front_numbers[b_idx])
.then(crowding_distances[b_idx].total_cmp(&crowding_distances[a_idx]))
});
debug_assert_eq!(
new_solutions_indices.len(),
HashSet::<usize>::from_iter(new_solutions_indices.iter().cloned()).len(),
"new_solutions_indices must have only unique indices"
);
let mut some_sols: Vec<_> = solutions.into_iter().map(Some).collect();
let mut some_scs: Vec<_> = scores.into_iter().map(Some).collect();
let (new_sols, new_scs): (Vec<_>, Vec<_>) = new_solutions_indices
.into_iter()
.map(|idx| {
(
some_sols[idx].take().expect("must be something here"),
some_scs[idx].take().expect("must be something here"),
)
})
.unzip();
debug_assert_eq!(
new_sols.len(),
self.initial_population_size,
"new population size must match initial population size"
);
debug_assert_eq!(
new_sols.len(),
new_scs.len(),
"number of solutions must match number of scores"
);
(new_sols, new_scs)
}
}
impl<
Solution,
Tst: TestExecutor<Solution, OBJECTIVE_NUM, TstExecStrat>,
Sel: SelectionExecutor<Solution, OBJECTIVE_NUM, SelExecStrat>,
Rec: RecombinationExecutor<Solution, PARENT_NUM, OFFSPRING_NUM, RecExecStrat>,
Mut: MutationExecutor<Solution, MutExecStrat>,
Ter: TerminationExecutor<Solution, OBJECTIVE_NUM, TerExecStrat>,
TstExecStrat,
TerExecStrat,
SelExecStrat,
MutExecStrat,
RecExecStrat,
const OBJECTIVE_NUM: usize,
const PARENT_NUM: usize,
const OFFSPRING_NUM: usize,
> Optimizer<Solution, OBJECTIVE_NUM>
for Nsga2<
Solution,
Tst,
Sel,
Rec,
Mut,
Ter,
TstExecStrat,
TerExecStrat,
SelExecStrat,
MutExecStrat,
RecExecStrat,
OBJECTIVE_NUM,
PARENT_NUM,
OFFSPRING_NUM,
>
{
fn optimize(mut self) -> Vec<Solution> {
let mut population = std::mem::take(&mut self.population);
let mut scores = self.tester.execute_tests(&population);
while !self.terminator.execute_termination(&population, &scores) {
assert!(!population.is_empty(), "the population is empty");
assert_eq!(
scores.len(),
population.len(),
"the number of calculated fitness scores doesn't match size of the population"
);
let selected_population =
self.selector.execute_selection(&population, &scores);
let mut created_population =
self.recombinator.execute_recombination(selected_population);
self.mutator.execute_mutations(&mut created_population);
let mut created_scores = self.tester.execute_tests(&created_population);
population.append(&mut created_population);
scores.append(&mut created_scores);
(population, scores) =
self.crowding_distance_selection(population, scores);
}
population
}
}