use std::fmt::Debug;
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::Float;
use scirs2_core::random::Random;
use scirs2_core::random::Rng;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
use crate::error::{OptimError, Result};
use crate::nas_engine::{
ConstraintHandlingMethod, DiversityStrategy, MultiObjectiveAlgorithm, MultiObjectiveConfig,
ObjectiveConfig, ObjectivePriority, ObjectiveType, OptimizationDirection,
OptimizerArchitecture, SearchResult, UserPreferences,
};
use crate::EvaluationMetric;
pub trait MultiObjectiveOptimizer<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
fn initialize(&mut self, config: &MultiObjectiveConfig<T>) -> Result<()>;
fn update_pareto_front(&mut self, results: &[SearchResult<T>]) -> Result<ParetoFront<T>>;
fn get_pareto_front(&self) -> &ParetoFront<T>;
fn select_candidates(
&mut self,
population: &[OptimizerArchitecture<T>],
objectives: &[T],
) -> Result<Vec<OptimizerArchitecture<T>>>;
fn name(&self) -> &str;
fn get_statistics(&self) -> MultiObjectiveStatistics<T>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParetoFront<T: Float + Debug + Send + Sync + 'static> {
pub solutions: Vec<ParetoSolution<T>>,
pub objective_bounds: ObjectiveBounds<T>,
pub metrics: FrontMetrics<T>,
pub generation: usize,
pub last_updated: std::time::SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParetoSolution<T: Float + Debug + Send + Sync + 'static> {
pub architecture: OptimizerArchitecture<T>,
pub objectives: Vec<T>,
pub constraint_violations: Vec<T>,
pub rank: usize,
pub crowding_distance: T,
pub metadata: SolutionMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolutionMetadata {
pub id: String,
pub generation: usize,
pub evaluation_count: usize,
pub parents: Vec<String>,
pub creation_method: CreationMethod,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CreationMethod {
RandomGeneration,
Crossover,
Mutation,
LocalSearch,
Repair,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectiveBounds<T: Float + Debug + Send + Sync + 'static> {
pub min_values: Vec<T>,
pub max_values: Vec<T>,
pub ideal_point: Vec<T>,
pub nadir_point: Vec<T>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrontMetrics<T: Float + Debug + Send + Sync + 'static> {
pub hypervolume: T,
pub spread: T,
pub spacing: T,
pub convergence: T,
pub num_solutions: usize,
pub coverage: CoverageMetrics<T>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoverageMetrics<T: Float + Debug + Send + Sync + 'static> {
pub objective_space_coverage: T,
pub reference_distance: T,
pub epsilon_dominance: T,
}
#[derive(Debug, Clone)]
pub struct MultiObjectiveStatistics<T: Float + Debug + Send + Sync + 'static> {
pub generation: usize,
pub total_evaluations: usize,
pub pareto_front_size: usize,
pub best_hypervolume: T,
pub convergence_history: Vec<T>,
pub diversity_history: Vec<T>,
pub algorithm_metrics: HashMap<String, T>,
}
pub struct NSGA2<T: Float + Debug + Send + Sync + 'static> {
config: MultiObjectiveConfig<T>,
population: Vec<Individual<T>>,
pareto_front: ParetoFront<T>,
generation: usize,
statistics: MultiObjectiveStatistics<T>,
population_size: usize,
crossover_prob: f64,
mutation_prob: f64,
rng: Random<scirs2_core::random::rngs::StdRng>,
}
#[derive(Debug, Clone)]
pub struct Individual<T: Float + Debug + Send + Sync + 'static> {
pub architecture: OptimizerArchitecture<T>,
pub objectives: Vec<T>,
pub constraints: Vec<T>,
pub rank: usize,
pub crowding_distance: T,
pub fitness: T,
pub id: String,
}
pub struct NSGA3<T: Float + Debug + Send + Sync + 'static> {
base: NSGA2<T>,
reference_directions: Vec<Array1<T>>,
association_count: Vec<usize>,
niche_count: Vec<usize>,
}
pub struct MOEADOptimizer<T: Float + Debug + Send + Sync + 'static> {
config: MultiObjectiveConfig<T>,
weight_vectors: Vec<Array1<T>>,
population: Vec<Individual<T>>,
neighbors: Vec<Vec<usize>>,
pareto_front: ParetoFront<T>,
ideal_point: Vec<T>,
decomposition: DecompositionMethod,
neighborhood_size: usize,
generation: usize,
statistics: MultiObjectiveStatistics<T>,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> MOEADOptimizer<T> {
pub fn new(config: MultiObjectiveConfig<T>) -> Result<Self> {
let _population_size = 100; let neighborhood_size = 20;
Ok(Self {
config,
weight_vectors: Vec::new(),
population: Vec::new(),
neighbors: Vec::new(),
pareto_front: ParetoFront::default(),
ideal_point: Vec::new(),
decomposition: DecompositionMethod::WeightedSum,
neighborhood_size,
generation: 0,
statistics: MultiObjectiveStatistics {
generation: 0,
total_evaluations: 0,
pareto_front_size: 0,
best_hypervolume: T::zero(),
convergence_history: Vec::new(),
diversity_history: Vec::new(),
algorithm_metrics: HashMap::new(),
},
})
}
}
impl<
T: Float
+ Debug
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ PartialOrd
+ std::iter::Sum,
> MultiObjectiveOptimizer<T> for MOEADOptimizer<T>
{
fn initialize(&mut self, config: &MultiObjectiveConfig<T>) -> Result<()> {
Ok(())
}
fn update_pareto_front(
&mut self,
_new_solutions: &[SearchResult<T>],
) -> Result<ParetoFront<T>> {
Ok(self.pareto_front.clone())
}
fn get_pareto_front(&self) -> &ParetoFront<T> {
&self.pareto_front
}
fn select_candidates(
&mut self,
_population: &[OptimizerArchitecture<T>],
_objectives: &[T],
) -> Result<Vec<OptimizerArchitecture<T>>> {
Ok(Vec::new())
}
fn name(&self) -> &str {
"MOEA/D"
}
fn get_statistics(&self) -> MultiObjectiveStatistics<T> {
self.statistics.clone()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecompositionMethod {
WeightedSum,
Tchebycheff,
PBI,
ASF,
}
pub struct WeightedSum<T: Float + Debug + Send + Sync + 'static> {
weights: Vec<T>,
best_solution: Option<Individual<T>>,
statistics: MultiObjectiveStatistics<T>,
pareto_front: ParetoFront<T>,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> WeightedSum<T> {
pub fn new(objectives: &[ObjectiveConfig<T>]) -> Result<Self> {
let weights = objectives.iter().map(|obj| obj.weight).collect();
Ok(Self {
weights,
best_solution: None,
statistics: MultiObjectiveStatistics::default(),
pareto_front: ParetoFront::default(),
})
}
}
impl<
T: Float
+ Debug
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ PartialOrd
+ std::iter::Sum,
> MultiObjectiveOptimizer<T> for WeightedSum<T>
{
fn initialize(&mut self, config: &MultiObjectiveConfig<T>) -> Result<()> {
Ok(())
}
fn update_pareto_front(
&mut self,
_new_solutions: &[SearchResult<T>],
) -> Result<ParetoFront<T>> {
Ok(ParetoFront::default())
}
fn get_pareto_front(&self) -> &ParetoFront<T> {
&self.pareto_front
}
fn select_candidates(
&mut self,
_population: &[OptimizerArchitecture<T>],
_objectives: &[T],
) -> Result<Vec<OptimizerArchitecture<T>>> {
Ok(Vec::new())
}
fn name(&self) -> &str {
"WeightedSum"
}
fn get_statistics(&self) -> MultiObjectiveStatistics<T> {
self.statistics.clone()
}
}
pub struct SmsEmoa<T: Float + Debug + Send + Sync + 'static> {
population: Vec<Individual<T>>,
pareto_front: ParetoFront<T>,
hypervolume_calculator: HypervolumeCalculator<T>,
reference_point: Vec<T>,
generation: usize,
statistics: MultiObjectiveStatistics<T>,
}
#[derive(Debug)]
pub struct HypervolumeCalculator<T: Float + Debug + Send + Sync + 'static> {
method: HypervolumeMethod,
reference_point: Vec<T>,
cache: HashMap<String, T>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HypervolumeMethod {
WFG,
Quick,
HSO,
MonteCarlo,
}
pub struct IBEA<T: Float + Debug + Send + Sync + 'static> {
population: Vec<Individual<T>>,
indicator_fitness: Vec<T>,
quality_indicator: QualityIndicator<T>,
scaling_factor: T,
pareto_front: ParetoFront<T>,
generation: usize,
statistics: MultiObjectiveStatistics<T>,
}
#[derive(Debug)]
pub struct QualityIndicator<T: Float + Debug + Send + Sync + 'static> {
indicator_type: IndicatorType,
parameters: HashMap<String, T>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndicatorType {
AdditiveEpsilon,
MultiplicativeEpsilon,
HypervolumeContribution,
R2,
}
pub struct PreferenceHandler<T: Float + Debug + Send + Sync + 'static> {
preferences: UserPreferences<T>,
articulation_method: ArticulationMethod,
utilities: Vec<T>,
preference_history: Vec<PreferenceSnapshot<T>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArticulationMethod {
APriori,
Interactive,
APosteriori,
Progressive,
}
#[derive(Debug, Clone)]
pub struct PreferenceSnapshot<T: Float + Debug + Send + Sync + 'static> {
timestamp: std::time::SystemTime,
preferences: HashMap<String, T>,
confidence: HashMap<String, T>,
context: String,
}
pub struct ConstraintHandler<T: Float + Debug + Send + Sync + 'static> {
method: ConstraintHandlingMethod,
constraints: Vec<ConstraintFunction<T>>,
tolerance: T,
penalty_parameters: PenaltyParameters<T>,
}
#[derive(Debug)]
pub struct ConstraintFunction<T: Float + Debug + Send + Sync + 'static> {
function_type: ConstraintType,
parameters: HashMap<String, T>,
bound: T,
direction: ConstraintDirection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintType {
Linear,
Quadratic,
Nonlinear,
Resource,
Performance,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintDirection {
LessThanOrEqual,
GreaterThanOrEqual,
Equal,
}
#[derive(Debug, Clone)]
pub struct PenaltyParameters<T: Float + Debug + Send + Sync + 'static> {
static_weight: T,
dynamic_weight: T,
increase_rate: T,
penalty_function: PenaltyFunctionType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PenaltyFunctionType {
Linear,
Quadratic,
Exponential,
Logarithmic,
}
impl<
T: Float
+ Debug
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ PartialOrd
+ std::iter::Sum,
> NSGA2<T>
{
pub fn new(population_size: usize, crossover_prob: f64, mutation_prob: f64) -> Self {
Self {
config: MultiObjectiveConfig::default(),
population: Vec::new(),
pareto_front: ParetoFront::new(),
generation: 0,
statistics: MultiObjectiveStatistics::default(),
population_size,
crossover_prob,
mutation_prob,
rng: Random::seed(42),
}
}
fn initialize_population(&mut self) -> Result<()> {
self.population.clear();
for i in 0..self.population_size {
let architecture = self.generate_random_architecture()?;
let individual = Individual {
architecture,
objectives: vec![T::zero(); self.config.objectives.len()],
constraints: Vec::new(),
rank: 0,
crowding_distance: T::zero(),
fitness: T::zero(),
id: format!("ind_{}", i),
};
self.population.push(individual);
}
Ok(())
}
fn generate_random_architecture(&self) -> Result<OptimizerArchitecture<T>> {
let mut parameters = HashMap::new();
parameters.insert(
"learning_rate".to_string(),
scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero()),
);
parameters.insert(
"beta1".to_string(),
scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
);
parameters.insert(
"beta2".to_string(),
scirs2_core::numeric::NumCast::from(0.999).unwrap_or_else(|| T::zero()),
);
Ok(OptimizerArchitecture {
components: vec!["Adam".to_string()],
parameters: parameters.clone(),
connections: Vec::new(),
metadata: HashMap::new(),
hyperparameters: parameters,
architecture_id: format!(
"arch_{}",
scirs2_core::random::Random::default().random::<u32>()
),
})
}
fn non_dominated_sort(&mut self) -> Vec<Vec<usize>> {
let n = self.population.len();
let mut fronts = Vec::new();
let mut domination_count = vec![0; n];
let mut dominated_solutions = vec![Vec::new(); n];
let mut first_front = Vec::new();
for i in 0..n {
for j in 0..n {
if i != j {
let dominance = self.dominance_relation(i, j);
match dominance {
DominanceRelation::Dominates => {
dominated_solutions[i].push(j);
}
DominanceRelation::DominatedBy => {
domination_count[i] += 1;
}
DominanceRelation::NonDominated => {}
}
}
}
if domination_count[i] == 0 {
self.population[i].rank = 0;
first_front.push(i);
}
}
fronts.push(first_front.clone());
let mut current_front = first_front;
let mut rank = 0;
while !current_front.is_empty() {
let mut next_front = Vec::new();
for &i in ¤t_front {
for &j in &dominated_solutions[i] {
domination_count[j] -= 1;
if domination_count[j] == 0 {
self.population[j].rank = rank + 1;
next_front.push(j);
}
}
}
rank += 1;
current_front = next_front.clone();
if !next_front.is_empty() {
fronts.push(next_front);
}
}
fronts
}
fn dominance_relation(&self, i: usize, j: usize) -> DominanceRelation {
let ind_i = &self.population[i];
let ind_j = &self.population[j];
let mut i_dominates = false;
let mut j_dominates = false;
for k in 0..ind_i.objectives.len() {
let obj_config = &self.config.objectives[k];
let val_i = ind_i.objectives[k];
let val_j = ind_j.objectives[k];
match obj_config.direction {
OptimizationDirection::Minimize => {
if val_i < val_j {
i_dominates = true;
} else if val_i > val_j {
j_dominates = true;
}
}
OptimizationDirection::Maximize => {
if val_i > val_j {
i_dominates = true;
} else if val_i < val_j {
j_dominates = true;
}
}
}
}
if i_dominates && !j_dominates {
DominanceRelation::Dominates
} else if j_dominates && !i_dominates {
DominanceRelation::DominatedBy
} else {
DominanceRelation::NonDominated
}
}
fn calculate_crowding_distance(&mut self, front: &[usize]) {
let front_size = front.len();
for &idx in front {
self.population[idx].crowding_distance = T::zero();
}
if front_size <= 2 {
for &idx in front {
self.population[idx].crowding_distance = T::infinity();
}
return;
}
let num_objectives = self.config.objectives.len();
for obj_idx in 0..num_objectives {
let mut sorted_front = front.to_vec();
sorted_front.sort_by(|&a, &b| {
self.population[a].objectives[obj_idx]
.partial_cmp(&self.population[b].objectives[obj_idx])
.unwrap_or(Ordering::Equal)
});
self.population[sorted_front[0]].crowding_distance = T::infinity();
self.population[sorted_front[front_size - 1]].crowding_distance = T::infinity();
let obj_min = self.population[sorted_front[0]].objectives[obj_idx];
let obj_max = self.population[sorted_front[front_size - 1]].objectives[obj_idx];
let obj_range = obj_max - obj_min;
if obj_range > T::zero() {
for i in 1..front_size - 1 {
let idx = sorted_front[i];
let prev_obj = self.population[sorted_front[i - 1]].objectives[obj_idx];
let next_obj = self.population[sorted_front[i + 1]].objectives[obj_idx];
let distance = (next_obj - prev_obj) / obj_range;
self.population[idx].crowding_distance =
self.population[idx].crowding_distance + distance;
}
}
}
}
fn environmental_selection(
&mut self,
combined_population: Vec<Individual<T>>,
) -> Vec<Individual<T>> {
self.population = combined_population;
let fronts = self.non_dominated_sort();
let mut new_population = Vec::new();
let mut front_idx = 0;
while front_idx < fronts.len() {
let front = &fronts[front_idx];
if new_population.len() + front.len() <= self.population_size {
self.calculate_crowding_distance(front);
for &idx in front {
new_population.push(self.population[idx].clone());
}
front_idx += 1;
} else {
self.calculate_crowding_distance(front);
let mut front_individuals: Vec<_> = front
.iter()
.map(|&idx| (idx, self.population[idx].crowding_distance))
.collect();
front_individuals.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
let remaining_slots = self.population_size - new_population.len();
for &(idx, _) in front_individuals.iter().take(remaining_slots) {
new_population.push(self.population[idx].clone());
}
break;
}
}
new_population
}
fn update_pareto_front_from_population(&mut self) {
let mut pareto_solutions = Vec::new();
for individual in &self.population {
if individual.rank == 0 {
let solution = ParetoSolution {
architecture: individual.architecture.clone(),
objectives: individual.objectives.clone(),
constraint_violations: individual.constraints.clone(),
rank: individual.rank,
crowding_distance: individual.crowding_distance,
metadata: SolutionMetadata {
id: individual.id.clone(),
generation: self.generation,
evaluation_count: self.statistics.total_evaluations,
parents: Vec::new(),
creation_method: CreationMethod::RandomGeneration,
},
};
pareto_solutions.push(solution);
}
}
self.pareto_front.solutions = pareto_solutions;
self.pareto_front.generation = self.generation;
self.pareto_front.last_updated = std::time::SystemTime::now();
self.update_objective_bounds();
self.calculate_front_metrics();
}
fn update_objective_bounds(&mut self) {
if self.pareto_front.solutions.is_empty() {
return;
}
let num_objectives = self.pareto_front.solutions[0].objectives.len();
let mut min_values = vec![T::infinity(); num_objectives];
let mut max_values = vec![T::neg_infinity(); num_objectives];
for solution in &self.pareto_front.solutions {
for (i, &obj_val) in solution.objectives.iter().enumerate() {
if obj_val < min_values[i] {
min_values[i] = obj_val;
}
if obj_val > max_values[i] {
max_values[i] = obj_val;
}
}
}
self.pareto_front.objective_bounds = ObjectiveBounds {
min_values: min_values.clone(),
max_values: max_values.clone(),
ideal_point: min_values,
nadir_point: max_values,
};
}
fn calculate_front_metrics(&mut self) {
let hypervolume = self.calculate_hypervolume();
let spread = self.calculate_spread();
let spacing = self.calculate_spacing();
self.pareto_front.metrics = FrontMetrics {
hypervolume,
spread,
spacing,
convergence: T::zero(), num_solutions: self.pareto_front.solutions.len(),
coverage: CoverageMetrics {
objective_space_coverage: scirs2_core::numeric::NumCast::from(0.5)
.unwrap_or_else(|| T::zero()),
reference_distance: T::zero(),
epsilon_dominance: T::zero(),
},
};
self.statistics.pareto_front_size = self.pareto_front.solutions.len();
self.statistics.best_hypervolume = hypervolume;
self.statistics.convergence_history.push(T::zero());
self.statistics.diversity_history.push(spread);
}
fn calculate_hypervolume(&self) -> T {
if self.pareto_front.solutions.is_empty() {
return T::zero();
}
let bounds = &self.pareto_front.objective_bounds;
let mut volume = T::one();
for i in 0..bounds.max_values.len() {
let range = bounds.max_values[i] - bounds.min_values[i];
volume = volume
* range.max(scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()));
}
volume * T::from(self.pareto_front.solutions.len() as f64).expect("unwrap failed")
}
fn calculate_spread(&self) -> T {
if self.pareto_front.solutions.len() < 2 {
return T::zero();
}
let mut total_distance = T::zero();
let num_objectives = self.pareto_front.solutions[0].objectives.len();
for i in 0..self.pareto_front.solutions.len() - 1 {
let mut distance = T::zero();
for j in 0..num_objectives {
let diff = self.pareto_front.solutions[i + 1].objectives[j]
- self.pareto_front.solutions[i].objectives[j];
distance = distance + diff * diff;
}
total_distance = total_distance + distance.sqrt();
}
total_distance / T::from(self.pareto_front.solutions.len() - 1).expect("unwrap failed")
}
fn calculate_spacing(&self) -> T {
if self.pareto_front.solutions.len() < 2 {
return T::zero();
}
let mut distances = Vec::new();
for i in 0..self.pareto_front.solutions.len() {
let mut min_distance = T::infinity();
for j in 0..self.pareto_front.solutions.len() {
if i != j {
let mut distance = T::zero();
for k in 0..self.pareto_front.solutions[i].objectives.len() {
let diff = self.pareto_front.solutions[i].objectives[k]
- self.pareto_front.solutions[j].objectives[k];
distance = distance + diff.abs();
}
if distance < min_distance {
min_distance = distance;
}
}
}
distances.push(min_distance);
}
let mean: T =
distances.iter().cloned().sum::<T>() / T::from(distances.len()).expect("unwrap failed");
let variance: T = distances
.iter()
.map(|&d| (d - mean) * (d - mean))
.sum::<T>()
/ T::from(distances.len()).expect("unwrap failed");
variance.sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DominanceRelation {
Dominates,
DominatedBy,
NonDominated,
}
impl<
T: Float
+ Debug
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ PartialOrd
+ std::iter::Sum,
> MultiObjectiveOptimizer<T> for NSGA2<T>
{
fn initialize(&mut self, config: &MultiObjectiveConfig<T>) -> Result<()> {
self.config = config.clone();
self.initialize_population()?;
Ok(())
}
fn update_pareto_front(&mut self, results: &[SearchResult<T>]) -> Result<ParetoFront<T>> {
for (i, result) in results.iter().enumerate() {
if i < self.population.len() {
let mut objectives = Vec::new();
for obj_config in &self.config.objectives {
let metric = match obj_config.objective_type {
ObjectiveType::Accuracy => EvaluationMetric::Accuracy,
ObjectiveType::Loss => EvaluationMetric::FinalPerformance,
ObjectiveType::TrainingTime => EvaluationMetric::TrainingTime,
ObjectiveType::InferenceTime => EvaluationMetric::ComputationTime,
ObjectiveType::MemoryUsage => EvaluationMetric::MemoryUsage,
ObjectiveType::EnergyConsumption => EvaluationMetric::ComputationTime,
ObjectiveType::ModelSize => EvaluationMetric::MemoryUsage,
ObjectiveType::Performance => EvaluationMetric::FinalPerformance,
ObjectiveType::Efficiency => EvaluationMetric::ComputationalEfficiency,
ObjectiveType::Robustness => EvaluationMetric::Robustness,
ObjectiveType::Interpretability => EvaluationMetric::FinalPerformance,
ObjectiveType::Fairness => EvaluationMetric::FinalPerformance,
ObjectiveType::Privacy => EvaluationMetric::FinalPerformance,
ObjectiveType::Sustainability => EvaluationMetric::ComputationalEfficiency,
ObjectiveType::Cost => EvaluationMetric::ComputationalEfficiency,
ObjectiveType::Custom(_) => EvaluationMetric::FinalPerformance,
};
let value = result
.evaluation_results
.metric_scores
.get(&metric)
.cloned()
.unwrap_or(T::zero());
objectives.push(value);
}
self.population[i].objectives = objectives;
self.population[i].architecture = result.architecture.clone();
}
}
self.generation += 1;
self.statistics.total_evaluations += results.len();
self.update_pareto_front_from_population();
Ok(self.pareto_front.clone())
}
fn get_pareto_front(&self) -> &ParetoFront<T> {
&self.pareto_front
}
fn select_candidates(
&mut self,
_population: &[OptimizerArchitecture<T>],
_objectives: &[T],
) -> Result<Vec<OptimizerArchitecture<T>>> {
let mut new_population = Vec::new();
for _ in 0..self.population_size {
let parent1 = self.tournament_selection(2)?;
let parent2 = self.tournament_selection(2)?;
let mut offspring = if self.rng.gen_range(0.0..1.0) < self.crossover_prob {
self.crossover(&parent1, &parent2)?
} else {
parent1.clone()
};
if self.rng.gen_range(0.0..1.0) < self.mutation_prob {
self.mutate(&mut offspring)?;
}
new_population.push(offspring.architecture);
}
Ok(new_population)
}
fn name(&self) -> &str {
"NSGA-II"
}
fn get_statistics(&self) -> MultiObjectiveStatistics<T> {
self.statistics.clone()
}
}
impl<
T: Float
+ Debug
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ PartialOrd
+ std::iter::Sum,
> NSGA2<T>
{
fn tournament_selection(&mut self, tournamentsize: usize) -> Result<Individual<T>> {
if self.population.is_empty() {
return Err(OptimError::InvalidConfig("Empty population".to_string()));
}
let mut best_idx = self.rng.gen_range(0..self.population.len());
for _ in 1..tournamentsize {
let idx = self.rng.gen_range(0..self.population.len());
if self.population[idx].rank < self.population[best_idx].rank
|| (self.population[idx].rank == self.population[best_idx].rank
&& self.population[idx].crowding_distance
> self.population[best_idx].crowding_distance)
{
best_idx = idx;
}
}
Ok(self.population[best_idx].clone())
}
fn crossover(
&mut self,
parent1: &Individual<T>,
parent2: &Individual<T>,
) -> Result<Individual<T>> {
let mut offspring = parent1.clone();
offspring.id = format!("offspring_{}", self.rng.gen_range(0..u32::MAX));
for (key, value) in &parent1.architecture.parameters {
if self.rng.gen_range(0.0..1.0) < 0.5 {
if let Some(parent2_value) = parent2.architecture.parameters.get(key) {
offspring
.architecture
.parameters
.insert(key.clone(), *parent2_value);
} else {
offspring
.architecture
.parameters
.insert(key.clone(), *value);
}
} else {
offspring
.architecture
.parameters
.insert(key.clone(), *value);
}
}
Ok(offspring)
}
fn mutate(&mut self, individual: &mut Individual<T>) -> Result<()> {
for (_key, value) in individual.architecture.parameters.iter_mut() {
if self.rng.gen_range(0.0..1.0) < 0.1 {
let noise = T::from(self.rng.gen_range(-0.05..0.05)).expect("unwrap failed"); *value = *value + noise;
}
}
Ok(())
}
}
impl<T: Float + Debug + Default + Send + Sync> Default for ParetoFront<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Float + Debug + Default + Send + Sync> ParetoFront<T> {
fn new() -> Self {
Self {
solutions: Vec::new(),
objective_bounds: ObjectiveBounds {
min_values: Vec::new(),
max_values: Vec::new(),
ideal_point: Vec::new(),
nadir_point: Vec::new(),
},
metrics: FrontMetrics {
hypervolume: T::zero(),
spread: T::zero(),
spacing: T::zero(),
convergence: T::zero(),
num_solutions: 0,
coverage: CoverageMetrics {
objective_space_coverage: T::zero(),
reference_distance: T::zero(),
epsilon_dominance: T::zero(),
},
},
generation: 0,
last_updated: std::time::SystemTime::now(),
}
}
}
impl<T: Float + Debug + Default + Send + Sync> Default for MultiObjectiveStatistics<T> {
fn default() -> Self {
Self {
generation: 0,
total_evaluations: 0,
pareto_front_size: 0,
best_hypervolume: T::zero(),
convergence_history: Vec::new(),
diversity_history: Vec::new(),
algorithm_metrics: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nsga2_creation() {
let nsga2 = NSGA2::<f64>::new(50, 0.8, 0.1);
assert_eq!(nsga2.population_size, 50);
assert_eq!(nsga2.name(), "NSGA-II");
}
#[test]
fn test_pareto_front_creation() {
let front = ParetoFront::<f64>::new();
assert!(front.solutions.is_empty());
assert_eq!(front.generation, 0);
}
#[test]
fn test_dominance_relation() {
let mut nsga2 = NSGA2::<f64>::new(2, 0.8, 0.1);
let arch = nsga2.generate_random_architecture().expect("unwrap failed");
let ind1 = Individual {
architecture: arch.clone(),
objectives: vec![1.0, 2.0], constraints: Vec::new(),
rank: 0,
crowding_distance: 0.0,
fitness: 0.0,
id: "ind1".to_string(),
};
let ind2 = Individual {
architecture: arch,
objectives: vec![2.0, 1.0], constraints: Vec::new(),
rank: 0,
crowding_distance: 0.0,
fitness: 0.0,
id: "ind2".to_string(),
};
nsga2.population = vec![ind1, ind2];
nsga2.config.objectives = vec![
ObjectiveConfig {
name: "obj1".to_string(),
objective_type: ObjectiveType::Performance,
direction: OptimizationDirection::Minimize,
weight: 0.5,
priority: ObjectivePriority::High,
normalization_bounds: None,
},
ObjectiveConfig {
name: "obj2".to_string(),
objective_type: ObjectiveType::Efficiency,
direction: OptimizationDirection::Minimize,
weight: 0.5,
priority: ObjectivePriority::High,
normalization_bounds: None,
},
];
let relation = nsga2.dominance_relation(0, 1);
assert_eq!(relation, DominanceRelation::NonDominated);
}
}