From 84ad01848b0004b524d31948eb05066bf3e5d9c9 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 5 Dec 2025 20:22:18 +0100 Subject: [PATCH] feat(constr_hw02): Add nsga_constr algorithm --- codes/constr_hw02/src/main.rs | 259 +++++++++++++++++++++++++++++++++- 1 file changed, 254 insertions(+), 5 deletions(-) diff --git a/codes/constr_hw02/src/main.rs b/codes/constr_hw02/src/main.rs index fed1a2f99096b280afa7615e64c7d5a54d40fdaf..8c5b5a93d6d4a7f939f565b47d6ca55a0a9cf362 100644 --- a/codes/constr_hw02/src/main.rs +++ b/codes/constr_hw02/src/main.rs @@ -1,7 +1,7 @@ use std::{convert::Infallible, env, fs, io::Write, rc::Rc}; use eoa_lib::{ - bounded::BoundedOVector, comparison::MinimizingOperator, constraints::{stochastic_ranking_evolution_algorithm, ConstrainedEvalFitness, ConstrainedFitnessFunction, ConstraintFunction, LowerThanConstraintFunction}, crossover::{ArithmeticCrossover, BoundedCrossover, BoundedCrossoverStrategy}, evolution::{EvolutionCandidate, EvolutionResult, EvolutionStats}, fitness::FitnessFunction, initializer::{Initializer, RandomInitializer}, multi_objective_evolution::nsga_2, pairing::AdjacentPairing, perturbation::{BoundedPerturbation, BoundedPerturbationStrategy, MutationPerturbation, RandomDistributionPerturbation}, population::{EvaluatedChromosome, EvaluatedPopulation, Population}, replacement::{BestReplacement, GenerationalReplacement}, selection::{BestSelection, TournamentSelection} + bounded::BoundedOVector, comparison::MinimizingOperator, constraints::{stochastic_ranking_evolution_algorithm, ConstrainedEvalFitness, ConstrainedFitnessFunction, ConstraintFunction, LowerThanConstraintFunction}, crossover::{ArithmeticCrossover, BoundedCrossover, BoundedCrossoverStrategy}, evolution::{EvolutionCandidate, EvolutionResult, EvolutionStats}, fitness::FitnessFunction, initializer::{Initializer, RandomInitializer}, multi_objective_evolution::{nsga_2, constrained_nsga_2}, pairing::AdjacentPairing, perturbation::{BoundedPerturbation, BoundedPerturbationStrategy, MutationPerturbation, RandomDistributionPerturbation}, population::{EvaluatedChromosome, EvaluatedPopulation, Population}, replacement::{BestReplacement, GenerationalReplacement}, selection::{BestSelection, TournamentSelection} }; use nalgebra::{ArrayStorage, Const, Matrix, SVector}; use rand::RngCore; @@ -460,11 +460,11 @@ pub fn solve_with_stochastic_ranking // Extract feasible fractions from the result let (evolution_result, feasible_fractions) = result; - + // For now, create placeholder constraint violations data // TODO: This needs library-level changes to properly track constraint violations let avg_constraint_violations = vec![0.0; feasible_fractions.len()]; - + Ok((evolution_result, feasible_fractions, avg_constraint_violations)) } @@ -710,6 +710,108 @@ pub fn solve_with_nsga_multi( + problem: &ConstrainedProblem, + population_size: usize, + parents_count: usize, + iterations: usize, + mutation_std_dev: f64, + rng: &mut dyn RngCore, +) -> Result<(EvolutionResult, [f64; 1]>, Vec, Vec), Box> { + // Create initial population + let initializer = RandomInitializer::new( + Box::new(BoundedOVector::new(problem.bounds.0, problem.bounds.1)) + ); + let initial_population = Population::from_vec( + initializer.initialize(nalgebra::Const::, population_size, rng) + ); + + // Setup components + let mut pairing = AdjacentPairing::new(); + let mut crossover = ArithmeticCrossover::new(); + + // Setup bounded random distribution perturbation with Normal distribution + let normal_perturbation = RandomDistributionPerturbation::>::normal(mutation_std_dev)?; + let mut mutation = MutationPerturbation::new(Box::new(normal_perturbation), 0.1); + + let better_than = MinimizingOperator::new(); + + // Clone the problem to access its fields + let cloned_problem = problem.clone(); + + // Create objectives array + let objectives: [Box, Out = f64, Err = Infallible>>; 1] = [ + Box::new(cloned_problem.objective), + ]; + + // Convert constraints to boxed trait objects + let constraints: [Box, Out = f64, Err = Infallible>>; CONSTRAINTS] = + cloned_problem.constraints.into_iter().map(|c| Box::new(c) as Box, Out = f64, Err = Infallible>>).collect::>().try_into() + .map_err(|_| "Failed to convert constraint references")?; + + let mut feasible_fractions = Vec::with_capacity(iterations); + let mut avg_constraint_violations = Vec::with_capacity(iterations); + + let result = constrained_nsga_2::<1, CONSTRAINTS, SVector, f64, Infallible, 2, _, _, _>( + initial_population, + parents_count, + objectives, + constraints, + &mut pairing, + &mut crossover, + &mut mutation, + &better_than, + iterations, + rng, + |_iteration: usize, _stats: &EvolutionStats, _>, population: &EvaluatedPopulation, _>| { + // Calculate feasible fraction and average constraint violation + let feasible_count = population.population + .iter() + .filter(|individual| { + // Check if the individual satisfies all constraints + problem.constraints.iter().all(|constraint| { + constraint.is_feasible(&individual.chromosome).unwrap_or(false) + }) + }) + .count(); + + // Calculate average constraint violation + let total_violation: f64 = population.population + .iter() + .map(|individual| { + problem.constraints + .iter() + .map(|constraint| constraint.evaluate(&individual.chromosome).unwrap_or(0.0).max(0.0)) + .sum::() + }) + .sum(); + let avg_constraint_violation = total_violation / population.population.len() as f64; + + let feasible_fraction = feasible_count as f64 / population.population.len() as f64; + feasible_fractions.push(feasible_fraction); + avg_constraint_violations.push(avg_constraint_violation); + }, + |chromosome, evaluation, best_candidate| { + // Only save feasible solutions (all constraints satisfied) + if !problem.constraints.iter().all(|constraint| { + constraint.is_feasible(chromosome).unwrap_or(false) + }) { + return false; + } + + if best_candidate.is_none() { + return true; + } + + // Compare based on fitness (first objective) + evaluation.evaluations[0] < best_candidate.as_ref().unwrap().evaluated_chromosome.evaluation.evaluations[0] + } + )?; + + Ok((result, feasible_fractions, avg_constraint_violations)) +} + const ITERATIONS: usize = 1000; const POPULATION: usize = 500; const PARENTS_COUNT: usize = 500; @@ -1121,6 +1223,145 @@ fn handle_g24_nsga() -> Result<(), Box> { run_nsga_ii(problem, config) } +fn run_nsga_constr( + problem: ConstrainedProblem, + config: NsgaConfig, +) -> Result<(), Box> { + let mut rng = rand::rng(); + + let result = solve_with_nsga_constr( + &problem, + config.population_size, + config.parents_count, + config.iterations, + config.mutation_std_dev, + &mut rng, + )?; + + let (evolution_result, feasible_fractions, avg_constraint_violations) = result; + + // Save results to CSV files + save_evolution_results("nsga_constr", &problem, &evolution_result.clone().map(|x| x[0]), &feasible_fractions, &avg_constraint_violations)?; + + if let Some(best_candidate) = evolution_result.best_candidate { + println!("Best solution found:"); + println!(" Chromosome: {:?}", best_candidate.chromosome); + println!(" Objectives: {:?} ({} %)", best_candidate.evaluation, ((best_candidate.evaluation[0] - problem.optimal_value) / problem.optimal_value).abs() * 100.0); + println!(" Iterations: {}", evolution_result.iterations); + println!(" Evaluations: {}", evolution_result.evaluations); + println!(" Final feasible fraction: {:.2}%", feasible_fractions.last().unwrap_or(&0.0) * 100.0); + + // Sanity check: verify the best candidate is feasible + let best_chromosome = &best_candidate.chromosome; + + println!("\nFeasibility check for best solution:"); + for (i, constraint) in problem.constraints.iter().enumerate() { + match constraint.evaluate(best_chromosome) { + Ok(value) => { + let is_feasible = value <= 0.0; + println!(" Constraint {}: {} ({})", i+1, value, if is_feasible { "FEASIBLE" } else { "INFEASIBLE" }); + } + Err(e) => { + println!(" Constraint {}: Error evaluating - {}", i+1, e); + } + } + } + } else { + println!("Could not find any feasible solution!") + } + + Ok(()) +} + +// NSGA-Constr handler functions +fn handle_g04_nsga_constr() -> Result<(), Box> { + let problem = problem_g04(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 1.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g05_nsga_constr() -> Result<(), Box> { + let problem = problem_g05(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 10.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g06_nsga_constr() -> Result<(), Box> { + let problem = problem_g06(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 1.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g08_nsga_constr() -> Result<(), Box> { + let problem = problem_g08(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 0.5, + }; + run_nsga_constr(problem, config) +} + +fn handle_g09_nsga_constr() -> Result<(), Box> { + let problem = problem_g09(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 1.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g11_nsga_constr() -> Result<(), Box> { + let problem = problem_g11(G11_EPS); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 0.01 / 50.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g21_nsga_constr() -> Result<(), Box> { + let problem = problem_g21(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 10.0, + }; + run_nsga_constr(problem, config) +} + +fn handle_g24_nsga_constr() -> Result<(), Box> { + let problem = problem_g24(); + let config = NsgaConfig { + population_size: POPULATION, + parents_count: PARENTS_COUNT, + iterations: ITERATIONS, + mutation_std_dev: 0.1, + }; + run_nsga_constr(problem, config) +} + // NSGA-Multi handler functions for individual problems fn handle_nsga_multi_g06(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); @@ -1319,7 +1560,7 @@ fn main() { if args.len() != 3 { eprintln!("Usage: {} ", args[0]); - eprintln!("Methods: srank, nsga, nsga_multi, nsga_multi_noncapped"); + eprintln!("Methods: srank, nsga, nsga_constr, nsga_multi, nsga_multi_noncapped"); eprintln!("Problems: g04, g05, g06, g08, g09, g11, g21, g24"); std::process::exit(1); } @@ -1344,6 +1585,14 @@ fn main() { ("nsga", "g11") => handle_g11_nsga(), ("nsga", "g21") => handle_g21_nsga(), ("nsga", "g24") => handle_g24_nsga(), + ("nsga_constr", "g04") => handle_g04_nsga_constr(), + ("nsga_constr", "g05") => handle_g05_nsga_constr(), + ("nsga_constr", "g06") => handle_g06_nsga_constr(), + ("nsga_constr", "g08") => handle_g08_nsga_constr(), + ("nsga_constr", "g09") => handle_g09_nsga_constr(), + ("nsga_constr", "g11") => handle_g11_nsga_constr(), + ("nsga_constr", "g21") => handle_g21_nsga_constr(), + ("nsga_constr", "g24") => handle_g24_nsga_constr(), ("nsga_multi", "g04") => handle_nsga_multi_g04(true), ("nsga_multi", "g05") => handle_nsga_multi_g05(true), ("nsga_multi", "g06") => handle_nsga_multi_g06(true), @@ -1362,7 +1611,7 @@ fn main() { ("nsga_multi_noncapped", "g24") => handle_nsga_multi_g24(false), (_, _) => { eprintln!("Invalid method '{}' or problem '{}'", method, problem); - eprintln!("Methods: srank, nsga, nsga_multi, nsga_multi_noncapped"); + eprintln!("Methods: srank, nsga, nsga_constr, nsga_multi, nsga_multi_noncapped"); eprintln!("Problems: g04, g05, g06, g08, g09, g11, g21, g24"); std::process::exit(1); }