1use super::config::{
7 ConstraintHandling, FrontierUpdateStrategy, MultiObjectiveConfig, OptimizationConfiguration,
8 OptimizationObjective, ParetoFrontierConfig, ScalarizationMethod,
9};
10use crate::applications::ApplicationResult;
11use std::collections::{HashMap, VecDeque};
12use std::time::{Duration, Instant};
13
14pub struct MultiObjectiveOptimizer {
16 pub config: MultiObjectiveConfig,
18 pub pareto_frontier: ParetoFrontier,
20 pub scalarizers: Vec<Scalarizer>,
22 pub constraint_handlers: Vec<ConstraintHandler>,
24 pub decision_maker: DecisionMaker,
26}
27
28#[derive(Debug)]
30pub struct ParetoFrontier {
31 pub solutions: Vec<MultiObjectiveSolution>,
33 pub statistics: FrontierStatistics,
35 pub update_history: VecDeque<FrontierUpdate>,
37}
38
39#[derive(Debug, Clone)]
41pub struct MultiObjectiveSolution {
42 pub id: String,
44 pub objective_values: Vec<f64>,
46 pub decision_variables: OptimizationConfiguration,
48 pub dominance_rank: usize,
50 pub crowding_distance: f64,
52}
53
54#[derive(Debug, Clone)]
56pub struct FrontierStatistics {
57 pub size: usize,
59 pub hypervolume: f64,
61 pub spread: f64,
63 pub convergence: f64,
65 pub coverage: f64,
67}
68
69#[derive(Debug, Clone)]
71pub struct FrontierUpdate {
72 pub timestamp: Instant,
74 pub solutions_added: Vec<String>,
76 pub solutions_removed: Vec<String>,
78 pub reason: UpdateReason,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum UpdateReason {
85 NewNonDominated,
87 DominatedRemoval,
89 CapacityLimit,
91 QualityImprovement,
93}
94
95#[derive(Debug)]
97pub struct Scalarizer {
98 pub method: ScalarizationMethod,
100 pub weights: Vec<f64>,
102 pub reference_point: Option<Vec<f64>>,
104 pub parameters: HashMap<String, f64>,
106}
107
108#[derive(Debug)]
110pub struct ConstraintHandler {
111 pub method: ConstraintHandling,
113 pub constraints: Vec<Constraint>,
115 pub penalty_parameters: HashMap<String, f64>,
117}
118
119#[derive(Debug, Clone)]
121pub struct Constraint {
122 pub constraint_type: ConstraintType,
124 pub function: String,
126 pub bounds: (f64, f64),
128 pub tolerance: f64,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum ConstraintType {
135 Equality,
137 Inequality,
139 Box,
141 Linear,
143 Nonlinear,
145}
146
147#[derive(Debug)]
149pub struct DecisionMaker {
150 pub strategy: DecisionStrategy,
152 pub preferences: UserPreferences,
154 pub decision_history: VecDeque<Decision>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum DecisionStrategy {
161 Interactive,
163 APriori,
165 APosteriori,
167 Progressive,
169 Automated,
171}
172
173#[derive(Debug, Clone)]
175pub struct UserPreferences {
176 pub objective_weights: Vec<f64>,
178 pub trade_offs: HashMap<String, f64>,
180 pub user_constraints: Vec<Constraint>,
182 pub preference_functions: Vec<PreferenceFunction>,
184}
185
186#[derive(Debug, Clone)]
188pub struct PreferenceFunction {
189 pub function_type: PreferenceFunctionType,
191 pub parameters: Vec<f64>,
193 pub objectives: Vec<usize>,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum PreferenceFunctionType {
200 Linear,
202 Exponential,
204 Logarithmic,
206 Threshold,
208 Custom(String),
210}
211
212#[derive(Debug, Clone)]
214pub struct Decision {
215 pub timestamp: Instant,
217 pub selected_solution: String,
219 pub rationale: String,
221 pub confidence: f64,
223 pub user_feedback: Option<f64>,
225}
226
227impl MultiObjectiveOptimizer {
228 #[must_use]
229 pub fn new(config: MultiObjectiveConfig) -> Self {
230 Self {
231 config,
232 pareto_frontier: ParetoFrontier {
233 solutions: Vec::new(),
234 statistics: FrontierStatistics {
235 size: 0,
236 hypervolume: 0.0,
237 spread: 0.0,
238 convergence: 0.0,
239 coverage: 0.0,
240 },
241 update_history: VecDeque::new(),
242 },
243 scalarizers: Vec::new(),
244 constraint_handlers: Vec::new(),
245 decision_maker: DecisionMaker {
246 strategy: DecisionStrategy::Automated,
247 preferences: UserPreferences {
248 objective_weights: vec![0.5, 0.3, 0.2],
249 trade_offs: HashMap::new(),
250 user_constraints: Vec::new(),
251 preference_functions: Vec::new(),
252 },
253 decision_history: VecDeque::new(),
254 },
255 }
256 }
257
258 pub fn add_solution(&mut self, solution: MultiObjectiveSolution) -> ApplicationResult<bool> {
260 let is_non_dominated = self.is_non_dominated(&solution);
262
263 if is_non_dominated {
264 let solutions_to_keep: Vec<_> = self
266 .pareto_frontier
267 .solutions
268 .iter()
269 .filter(|existing| !self.dominates(&solution, existing))
270 .cloned()
271 .collect();
272 self.pareto_frontier.solutions = solutions_to_keep;
273
274 self.pareto_frontier.solutions.push(solution.clone());
276
277 self.update_frontier_statistics();
279
280 let update = FrontierUpdate {
282 timestamp: Instant::now(),
283 solutions_added: vec![solution.id],
284 solutions_removed: Vec::new(),
285 reason: UpdateReason::NewNonDominated,
286 };
287 self.pareto_frontier.update_history.push_back(update);
288
289 if self.pareto_frontier.update_history.len() > 1000 {
291 self.pareto_frontier.update_history.pop_front();
292 }
293
294 Ok(true)
295 } else {
296 Ok(false)
297 }
298 }
299
300 fn is_non_dominated(&self, solution: &MultiObjectiveSolution) -> bool {
302 for existing in &self.pareto_frontier.solutions {
303 if self.dominates(existing, solution) {
304 return false;
305 }
306 }
307 true
308 }
309
310 fn dominates(
312 &self,
313 solution1: &MultiObjectiveSolution,
314 solution2: &MultiObjectiveSolution,
315 ) -> bool {
316 let mut at_least_one_better = false;
317
318 for (val1, val2) in solution1
319 .objective_values
320 .iter()
321 .zip(&solution2.objective_values)
322 {
323 if val1 < val2 {
324 return false; }
326 if val1 > val2 {
327 at_least_one_better = true;
328 }
329 }
330
331 at_least_one_better
332 }
333
334 fn update_frontier_statistics(&mut self) {
336 self.pareto_frontier.statistics.size = self.pareto_frontier.solutions.len();
337
338 self.pareto_frontier.statistics.hypervolume = self.calculate_hypervolume();
340
341 self.pareto_frontier.statistics.spread = self.calculate_spread();
343
344 self.pareto_frontier.statistics.convergence = 0.8; self.pareto_frontier.statistics.coverage = 0.9; }
350
351 fn calculate_hypervolume(&self) -> f64 {
353 if self.pareto_frontier.solutions.is_empty() {
354 return 0.0;
355 }
356
357 let mut volume = 0.0;
359 for solution in &self.pareto_frontier.solutions {
360 let mut point_volume = 1.0;
361 for &value in &solution.objective_values {
362 point_volume *= value.max(0.0);
363 }
364 volume += point_volume;
365 }
366
367 volume
368 }
369
370 fn calculate_spread(&self) -> f64 {
372 if self.pareto_frontier.solutions.len() < 2 {
373 return 0.0;
374 }
375
376 let mut total_distance = 0.0;
378 let num_objectives = self.pareto_frontier.solutions[0].objective_values.len();
379
380 for i in 0..num_objectives {
381 let mut values: Vec<f64> = self
382 .pareto_frontier
383 .solutions
384 .iter()
385 .map(|s| s.objective_values[i])
386 .collect();
387 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
388
389 if let (Some(&min), Some(&max)) = (values.first(), values.last()) {
390 total_distance += max - min;
391 }
392 }
393
394 total_distance / num_objectives as f64
395 }
396
397 #[must_use]
399 pub fn scalarize_weighted_sum(
400 &self,
401 solution: &MultiObjectiveSolution,
402 weights: &[f64],
403 ) -> f64 {
404 solution
405 .objective_values
406 .iter()
407 .zip(weights)
408 .map(|(value, weight)| value * weight)
409 .sum()
410 }
411
412 pub fn select_solution(&mut self) -> ApplicationResult<Option<String>> {
414 if self.pareto_frontier.solutions.is_empty() {
415 return Ok(None);
416 }
417
418 match self.decision_maker.strategy {
419 DecisionStrategy::Automated => {
420 let weights = &self.decision_maker.preferences.objective_weights;
422
423 let mut best_solution = None;
424 let mut best_score = f64::NEG_INFINITY;
425
426 for solution in &self.pareto_frontier.solutions {
427 let score = self.scalarize_weighted_sum(solution, weights);
428 if score > best_score {
429 best_score = score;
430 best_solution = Some(solution.id.clone());
431 }
432 }
433
434 if let Some(ref solution_id) = best_solution {
435 let decision = Decision {
437 timestamp: Instant::now(),
438 selected_solution: solution_id.clone(),
439 rationale: "Automated selection using weighted sum".to_string(),
440 confidence: 0.8,
441 user_feedback: None,
442 };
443 self.decision_maker.decision_history.push_back(decision);
444
445 if self.decision_maker.decision_history.len() > 100 {
447 self.decision_maker.decision_history.pop_front();
448 }
449 }
450
451 Ok(best_solution)
452 }
453 _ => {
454 Ok(self.pareto_frontier.solutions.first().map(|s| s.id.clone()))
456 }
457 }
458 }
459
460 #[must_use]
462 pub const fn get_statistics(&self) -> &FrontierStatistics {
463 &self.pareto_frontier.statistics
464 }
465
466 #[must_use]
468 pub const fn get_pareto_solutions(&self) -> &Vec<MultiObjectiveSolution> {
469 &self.pareto_frontier.solutions
470 }
471
472 pub fn clear_frontier(&mut self) {
474 self.pareto_frontier.solutions.clear();
475 self.update_frontier_statistics();
476
477 let update = FrontierUpdate {
478 timestamp: Instant::now(),
479 solutions_added: Vec::new(),
480 solutions_removed: Vec::new(),
481 reason: UpdateReason::QualityImprovement,
482 };
483 self.pareto_frontier.update_history.push_back(update);
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::meta_learning::config::*;
491 use crate::meta_learning::config::{AlgorithmType, ResourceAllocation};
492
493 #[test]
494 fn test_multi_objective_optimizer_creation() {
495 let config = MultiObjectiveConfig::default();
496 let optimizer = MultiObjectiveOptimizer::new(config);
497
498 assert_eq!(optimizer.pareto_frontier.solutions.len(), 0);
499 assert_eq!(optimizer.pareto_frontier.statistics.size, 0);
500 }
501
502 #[test]
503 fn test_solution_addition() {
504 let config = MultiObjectiveConfig::default();
505 let mut optimizer = MultiObjectiveOptimizer::new(config);
506
507 let solution = MultiObjectiveSolution {
508 id: "test_solution".to_string(),
509 objective_values: vec![1.0, 2.0, 3.0],
510 decision_variables: OptimizationConfiguration {
511 algorithm: AlgorithmType::SimulatedAnnealing,
512 hyperparameters: HashMap::new(),
513 architecture: None,
514 resources: ResourceAllocation {
515 cpu: 1.0,
516 memory: 512,
517 gpu: 0.0,
518 time: Duration::from_secs(60),
519 },
520 },
521 dominance_rank: 0,
522 crowding_distance: 0.0,
523 };
524
525 let result = optimizer.add_solution(solution);
526 assert!(result.is_ok());
527 assert!(result.expect("add_solution should succeed"));
528 assert_eq!(optimizer.pareto_frontier.solutions.len(), 1);
529 }
530
531 #[test]
532 fn test_dominance_check() {
533 let config = MultiObjectiveConfig::default();
534 let optimizer = MultiObjectiveOptimizer::new(config);
535
536 let solution1 = MultiObjectiveSolution {
537 id: "solution1".to_string(),
538 objective_values: vec![1.0, 2.0],
539 decision_variables: OptimizationConfiguration {
540 algorithm: AlgorithmType::QuantumAnnealing,
541 hyperparameters: HashMap::new(),
542 architecture: None,
543 resources: ResourceAllocation {
544 cpu: 1.0,
545 memory: 512,
546 gpu: 0.0,
547 time: Duration::from_secs(60),
548 },
549 },
550 dominance_rank: 0,
551 crowding_distance: 0.0,
552 };
553
554 let solution2 = MultiObjectiveSolution {
555 id: "solution2".to_string(),
556 objective_values: vec![2.0, 1.0],
557 decision_variables: OptimizationConfiguration {
558 algorithm: AlgorithmType::TabuSearch,
559 hyperparameters: HashMap::new(),
560 architecture: None,
561 resources: ResourceAllocation {
562 cpu: 1.0,
563 memory: 512,
564 gpu: 0.0,
565 time: Duration::from_secs(60),
566 },
567 },
568 dominance_rank: 0,
569 crowding_distance: 0.0,
570 };
571
572 assert!(!optimizer.dominates(&solution1, &solution2));
574 assert!(!optimizer.dominates(&solution2, &solution1));
575 }
576
577 #[test]
578 fn test_weighted_sum_scalarization() {
579 let config = MultiObjectiveConfig::default();
580 let optimizer = MultiObjectiveOptimizer::new(config);
581
582 let solution = MultiObjectiveSolution {
583 id: "test_solution".to_string(),
584 objective_values: vec![2.0, 3.0, 1.0],
585 decision_variables: OptimizationConfiguration {
586 algorithm: AlgorithmType::GeneticAlgorithm,
587 hyperparameters: HashMap::new(),
588 architecture: None,
589 resources: ResourceAllocation {
590 cpu: 1.0,
591 memory: 512,
592 gpu: 0.0,
593 time: Duration::from_secs(60),
594 },
595 },
596 dominance_rank: 0,
597 crowding_distance: 0.0,
598 };
599
600 let weights = vec![0.5, 0.3, 0.2];
601 let score = optimizer.scalarize_weighted_sum(&solution, &weights);
602
603 assert!((score - 2.1).abs() < 1e-10);
605 }
606
607 #[test]
608 fn test_frontier_statistics() {
609 let config = MultiObjectiveConfig::default();
610 let mut optimizer = MultiObjectiveOptimizer::new(config);
611
612 let solution = MultiObjectiveSolution {
614 id: "test_solution".to_string(),
615 objective_values: vec![1.0, 2.0],
616 decision_variables: OptimizationConfiguration {
617 algorithm: AlgorithmType::ParticleSwarm,
618 hyperparameters: HashMap::new(),
619 architecture: None,
620 resources: ResourceAllocation {
621 cpu: 1.0,
622 memory: 512,
623 gpu: 0.0,
624 time: Duration::from_secs(60),
625 },
626 },
627 dominance_rank: 0,
628 crowding_distance: 0.0,
629 };
630
631 optimizer
632 .add_solution(solution)
633 .expect("add_solution should succeed");
634
635 let stats = optimizer.get_statistics();
636 assert_eq!(stats.size, 1);
637 assert!(stats.hypervolume > 0.0);
638 }
639
640 #[test]
641 fn test_solution_selection() {
642 let config = MultiObjectiveConfig::default();
643 let mut optimizer = MultiObjectiveOptimizer::new(config);
644
645 let solution1 = MultiObjectiveSolution {
647 id: "solution1".to_string(),
648 objective_values: vec![1.0, 2.0],
649 decision_variables: OptimizationConfiguration {
650 algorithm: AlgorithmType::AntColony,
651 hyperparameters: HashMap::new(),
652 architecture: None,
653 resources: ResourceAllocation {
654 cpu: 1.0,
655 memory: 512,
656 gpu: 0.0,
657 time: Duration::from_secs(60),
658 },
659 },
660 dominance_rank: 0,
661 crowding_distance: 0.0,
662 };
663
664 let solution2 = MultiObjectiveSolution {
665 id: "solution2".to_string(),
666 objective_values: vec![2.0, 1.0],
667 decision_variables: OptimizationConfiguration {
668 algorithm: AlgorithmType::VariableNeighborhood,
669 hyperparameters: HashMap::new(),
670 architecture: None,
671 resources: ResourceAllocation {
672 cpu: 1.0,
673 memory: 512,
674 gpu: 0.0,
675 time: Duration::from_secs(60),
676 },
677 },
678 dominance_rank: 0,
679 crowding_distance: 0.0,
680 };
681
682 optimizer
683 .add_solution(solution1)
684 .expect("add_solution for solution1 should succeed");
685 optimizer
686 .add_solution(solution2)
687 .expect("add_solution for solution2 should succeed");
688
689 let selected = optimizer.select_solution();
690 assert!(selected.is_ok());
691 assert!(selected.expect("select_solution should succeed").is_some());
692 }
693}