use super::config::{
ConstraintHandling, FrontierUpdateStrategy, MultiObjectiveConfig, OptimizationConfiguration,
OptimizationObjective, ParetoFrontierConfig, ScalarizationMethod,
};
use crate::applications::ApplicationResult;
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
pub struct MultiObjectiveOptimizer {
pub config: MultiObjectiveConfig,
pub pareto_frontier: ParetoFrontier,
pub scalarizers: Vec<Scalarizer>,
pub constraint_handlers: Vec<ConstraintHandler>,
pub decision_maker: DecisionMaker,
}
#[derive(Debug)]
pub struct ParetoFrontier {
pub solutions: Vec<MultiObjectiveSolution>,
pub statistics: FrontierStatistics,
pub update_history: VecDeque<FrontierUpdate>,
}
#[derive(Debug, Clone)]
pub struct MultiObjectiveSolution {
pub id: String,
pub objective_values: Vec<f64>,
pub decision_variables: OptimizationConfiguration,
pub dominance_rank: usize,
pub crowding_distance: f64,
}
#[derive(Debug, Clone)]
pub struct FrontierStatistics {
pub size: usize,
pub hypervolume: f64,
pub spread: f64,
pub convergence: f64,
pub coverage: f64,
}
#[derive(Debug, Clone)]
pub struct FrontierUpdate {
pub timestamp: Instant,
pub solutions_added: Vec<String>,
pub solutions_removed: Vec<String>,
pub reason: UpdateReason,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpdateReason {
NewNonDominated,
DominatedRemoval,
CapacityLimit,
QualityImprovement,
}
#[derive(Debug)]
pub struct Scalarizer {
pub method: ScalarizationMethod,
pub weights: Vec<f64>,
pub reference_point: Option<Vec<f64>>,
pub parameters: HashMap<String, f64>,
}
#[derive(Debug)]
pub struct ConstraintHandler {
pub method: ConstraintHandling,
pub constraints: Vec<Constraint>,
pub penalty_parameters: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct Constraint {
pub constraint_type: ConstraintType,
pub function: String,
pub bounds: (f64, f64),
pub tolerance: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstraintType {
Equality,
Inequality,
Box,
Linear,
Nonlinear,
}
#[derive(Debug)]
pub struct DecisionMaker {
pub strategy: DecisionStrategy,
pub preferences: UserPreferences,
pub decision_history: VecDeque<Decision>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecisionStrategy {
Interactive,
APriori,
APosteriori,
Progressive,
Automated,
}
#[derive(Debug, Clone)]
pub struct UserPreferences {
pub objective_weights: Vec<f64>,
pub trade_offs: HashMap<String, f64>,
pub user_constraints: Vec<Constraint>,
pub preference_functions: Vec<PreferenceFunction>,
}
#[derive(Debug, Clone)]
pub struct PreferenceFunction {
pub function_type: PreferenceFunctionType,
pub parameters: Vec<f64>,
pub objectives: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PreferenceFunctionType {
Linear,
Exponential,
Logarithmic,
Threshold,
Custom(String),
}
#[derive(Debug, Clone)]
pub struct Decision {
pub timestamp: Instant,
pub selected_solution: String,
pub rationale: String,
pub confidence: f64,
pub user_feedback: Option<f64>,
}
impl MultiObjectiveOptimizer {
#[must_use]
pub fn new(config: MultiObjectiveConfig) -> Self {
Self {
config,
pareto_frontier: ParetoFrontier {
solutions: Vec::new(),
statistics: FrontierStatistics {
size: 0,
hypervolume: 0.0,
spread: 0.0,
convergence: 0.0,
coverage: 0.0,
},
update_history: VecDeque::new(),
},
scalarizers: Vec::new(),
constraint_handlers: Vec::new(),
decision_maker: DecisionMaker {
strategy: DecisionStrategy::Automated,
preferences: UserPreferences {
objective_weights: vec![0.5, 0.3, 0.2],
trade_offs: HashMap::new(),
user_constraints: Vec::new(),
preference_functions: Vec::new(),
},
decision_history: VecDeque::new(),
},
}
}
pub fn add_solution(&mut self, solution: MultiObjectiveSolution) -> ApplicationResult<bool> {
let is_non_dominated = self.is_non_dominated(&solution);
if is_non_dominated {
let solutions_to_keep: Vec<_> = self
.pareto_frontier
.solutions
.iter()
.filter(|existing| !self.dominates(&solution, existing))
.cloned()
.collect();
self.pareto_frontier.solutions = solutions_to_keep;
self.pareto_frontier.solutions.push(solution.clone());
self.update_frontier_statistics();
let update = FrontierUpdate {
timestamp: Instant::now(),
solutions_added: vec![solution.id],
solutions_removed: Vec::new(),
reason: UpdateReason::NewNonDominated,
};
self.pareto_frontier.update_history.push_back(update);
if self.pareto_frontier.update_history.len() > 1000 {
self.pareto_frontier.update_history.pop_front();
}
Ok(true)
} else {
Ok(false)
}
}
fn is_non_dominated(&self, solution: &MultiObjectiveSolution) -> bool {
for existing in &self.pareto_frontier.solutions {
if self.dominates(existing, solution) {
return false;
}
}
true
}
fn dominates(
&self,
solution1: &MultiObjectiveSolution,
solution2: &MultiObjectiveSolution,
) -> bool {
let mut at_least_one_better = false;
for (val1, val2) in solution1
.objective_values
.iter()
.zip(&solution2.objective_values)
{
if val1 < val2 {
return false; }
if val1 > val2 {
at_least_one_better = true;
}
}
at_least_one_better
}
fn update_frontier_statistics(&mut self) {
self.pareto_frontier.statistics.size = self.pareto_frontier.solutions.len();
self.pareto_frontier.statistics.hypervolume = self.calculate_hypervolume();
self.pareto_frontier.statistics.spread = self.calculate_spread();
self.pareto_frontier.statistics.convergence = 0.8;
self.pareto_frontier.statistics.coverage = 0.9; }
fn calculate_hypervolume(&self) -> f64 {
if self.pareto_frontier.solutions.is_empty() {
return 0.0;
}
let mut volume = 0.0;
for solution in &self.pareto_frontier.solutions {
let mut point_volume = 1.0;
for &value in &solution.objective_values {
point_volume *= value.max(0.0);
}
volume += point_volume;
}
volume
}
fn calculate_spread(&self) -> f64 {
if self.pareto_frontier.solutions.len() < 2 {
return 0.0;
}
let mut total_distance = 0.0;
let num_objectives = self.pareto_frontier.solutions[0].objective_values.len();
for i in 0..num_objectives {
let mut values: Vec<f64> = self
.pareto_frontier
.solutions
.iter()
.map(|s| s.objective_values[i])
.collect();
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
if let (Some(&min), Some(&max)) = (values.first(), values.last()) {
total_distance += max - min;
}
}
total_distance / num_objectives as f64
}
#[must_use]
pub fn scalarize_weighted_sum(
&self,
solution: &MultiObjectiveSolution,
weights: &[f64],
) -> f64 {
solution
.objective_values
.iter()
.zip(weights)
.map(|(value, weight)| value * weight)
.sum()
}
pub fn select_solution(&mut self) -> ApplicationResult<Option<String>> {
if self.pareto_frontier.solutions.is_empty() {
return Ok(None);
}
match self.decision_maker.strategy {
DecisionStrategy::Automated => {
let weights = &self.decision_maker.preferences.objective_weights;
let mut best_solution = None;
let mut best_score = f64::NEG_INFINITY;
for solution in &self.pareto_frontier.solutions {
let score = self.scalarize_weighted_sum(solution, weights);
if score > best_score {
best_score = score;
best_solution = Some(solution.id.clone());
}
}
if let Some(ref solution_id) = best_solution {
let decision = Decision {
timestamp: Instant::now(),
selected_solution: solution_id.clone(),
rationale: "Automated selection using weighted sum".to_string(),
confidence: 0.8,
user_feedback: None,
};
self.decision_maker.decision_history.push_back(decision);
if self.decision_maker.decision_history.len() > 100 {
self.decision_maker.decision_history.pop_front();
}
}
Ok(best_solution)
}
_ => {
Ok(self.pareto_frontier.solutions.first().map(|s| s.id.clone()))
}
}
}
#[must_use]
pub const fn get_statistics(&self) -> &FrontierStatistics {
&self.pareto_frontier.statistics
}
#[must_use]
pub const fn get_pareto_solutions(&self) -> &Vec<MultiObjectiveSolution> {
&self.pareto_frontier.solutions
}
pub fn clear_frontier(&mut self) {
self.pareto_frontier.solutions.clear();
self.update_frontier_statistics();
let update = FrontierUpdate {
timestamp: Instant::now(),
solutions_added: Vec::new(),
solutions_removed: Vec::new(),
reason: UpdateReason::QualityImprovement,
};
self.pareto_frontier.update_history.push_back(update);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::meta_learning::config::*;
use crate::meta_learning::config::{AlgorithmType, ResourceAllocation};
#[test]
fn test_multi_objective_optimizer_creation() {
let config = MultiObjectiveConfig::default();
let optimizer = MultiObjectiveOptimizer::new(config);
assert_eq!(optimizer.pareto_frontier.solutions.len(), 0);
assert_eq!(optimizer.pareto_frontier.statistics.size, 0);
}
#[test]
fn test_solution_addition() {
let config = MultiObjectiveConfig::default();
let mut optimizer = MultiObjectiveOptimizer::new(config);
let solution = MultiObjectiveSolution {
id: "test_solution".to_string(),
objective_values: vec![1.0, 2.0, 3.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::SimulatedAnnealing,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
let result = optimizer.add_solution(solution);
assert!(result.is_ok());
assert!(result.expect("add_solution should succeed"));
assert_eq!(optimizer.pareto_frontier.solutions.len(), 1);
}
#[test]
fn test_dominance_check() {
let config = MultiObjectiveConfig::default();
let optimizer = MultiObjectiveOptimizer::new(config);
let solution1 = MultiObjectiveSolution {
id: "solution1".to_string(),
objective_values: vec![1.0, 2.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::QuantumAnnealing,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
let solution2 = MultiObjectiveSolution {
id: "solution2".to_string(),
objective_values: vec![2.0, 1.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::TabuSearch,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
assert!(!optimizer.dominates(&solution1, &solution2));
assert!(!optimizer.dominates(&solution2, &solution1));
}
#[test]
fn test_weighted_sum_scalarization() {
let config = MultiObjectiveConfig::default();
let optimizer = MultiObjectiveOptimizer::new(config);
let solution = MultiObjectiveSolution {
id: "test_solution".to_string(),
objective_values: vec![2.0, 3.0, 1.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::GeneticAlgorithm,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
let weights = vec![0.5, 0.3, 0.2];
let score = optimizer.scalarize_weighted_sum(&solution, &weights);
assert!((score - 2.1).abs() < 1e-10);
}
#[test]
fn test_frontier_statistics() {
let config = MultiObjectiveConfig::default();
let mut optimizer = MultiObjectiveOptimizer::new(config);
let solution = MultiObjectiveSolution {
id: "test_solution".to_string(),
objective_values: vec![1.0, 2.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::ParticleSwarm,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
optimizer
.add_solution(solution)
.expect("add_solution should succeed");
let stats = optimizer.get_statistics();
assert_eq!(stats.size, 1);
assert!(stats.hypervolume > 0.0);
}
#[test]
fn test_solution_selection() {
let config = MultiObjectiveConfig::default();
let mut optimizer = MultiObjectiveOptimizer::new(config);
let solution1 = MultiObjectiveSolution {
id: "solution1".to_string(),
objective_values: vec![1.0, 2.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::AntColony,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
let solution2 = MultiObjectiveSolution {
id: "solution2".to_string(),
objective_values: vec![2.0, 1.0],
decision_variables: OptimizationConfiguration {
algorithm: AlgorithmType::VariableNeighborhood,
hyperparameters: HashMap::new(),
architecture: None,
resources: ResourceAllocation {
cpu: 1.0,
memory: 512,
gpu: 0.0,
time: Duration::from_secs(60),
},
},
dominance_rank: 0,
crowding_distance: 0.0,
};
optimizer
.add_solution(solution1)
.expect("add_solution for solution1 should succeed");
optimizer
.add_solution(solution2)
.expect("add_solution for solution2 should succeed");
let selected = optimizer.select_solution();
assert!(selected.is_ok());
assert!(selected.expect("select_solution should succeed").is_some());
}
}