use std::{convert::Infallible, env, fs, io::Write, rc::Rc, cell::RefCell}; use eoa_lib::{ bounded::BoundedOVector, comparison::MinimizingOperator, constraints::{stochastic_ranking_evolution_algorithm, ConstrainedEvalFitness, ConstrainedFitnessFunction, ConstraintFunction, LowerThanConstraintFunction}, crossover::{ArithmeticCrossover, BoundedCrossover, BoundedCrossoverStrategy, Crossover}, evolution::{EvolutionCandidate, EvolutionResult, EvolutionStats}, fitness::FitnessFunction, initializer::{Initializer, RandomInitializer}, multi_objective_evolution::{constrained_nsga_2, nsga_2, NSGAEvaluation}, pairing::{AdjacentPairing, ParentPairing}, perturbation::{BoundedPerturbation, BoundedPerturbationStrategy, MutationPerturbation, RandomDistributionPerturbation}, population::{EvaluatedChromosome, EvaluatedPopulation, Population}, replacement::{BestReplacement, GenerationalReplacement}, selection::{BestSelection, TournamentSelection} }; use nalgebra::{ArrayStorage, Const, Matrix, SVector}; use rand::{Rng, RngCore}; use rand_distr::Normal; use chrono::prelude::*; pub mod problems; use problems::*; use problems::{StochasticRankingConfig, NsgaConfig}; pub mod feasible_crossover_wrapper; use feasible_crossover_wrapper::*; /// Solve a constrained optimization problem using stochastic ranking /// /// Returns the evolution result with feasible fractions for each iteration pub fn solve_with_stochastic_ranking( mut problem: ConstrainedProblem, population_size: usize, parents_count: usize, iterations: usize, stochastic_params: (usize, f64), // (N, p) for stochastic ranking mutation_std_dev: f64, rng: &mut dyn RngCore, ) -> Result<(EvolutionResult, f64>, 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 as specified // let mut selection = TournamentSelection::new(5, 0.9); let mut selection = TournamentSelection::new(5, 0.95); let mut replacement = GenerationalReplacement; let mut pairing = AdjacentPairing::new(); let crossover = ArithmeticCrossover::new(); let mut crossover = BoundedCrossover::, 2, _>::new( crossover, problem.bounds.0, problem.bounds.1, BoundedCrossoverStrategy::Retry(5) ); // Setup bounded random distribution perturbation with Normal distribution let normal_perturbation = RandomDistributionPerturbation::>::normal(mutation_std_dev)?; let perturbation = BoundedPerturbation::new( normal_perturbation, problem.bounds.0, problem.bounds.1, BoundedPerturbationStrategy::Retry(5) ); let mut mutation = MutationPerturbation::new(Box::new(perturbation), 0.1); // The weight is so large mainly because of the g11 that has very small values. // Somehow the higher weights do seem to help, even though I am unsure why exactly. let constraint_weights = [1.0; CONSTRAINTS]; let (N, p) = stochastic_params; let better_than = MinimizingOperator::new(); // Convert constraint array references let constraint_refs = problem.constraints.iter().collect::>().try_into() .map_err(|_| "Failed to convert constraint references")?; let mut avg_constraint_violations = vec![0.0; initial_population.population.len()]; let result = stochastic_ranking_evolution_algorithm( initial_population, parents_count, N, p, &mut problem.objective, constraint_refs, constraint_weights, &mut pairing, &mut selection, &mut crossover, &mut mutation, &mut replacement, &better_than, iterations, rng, |_, _, population| { let avg_constraint_violation = population.population .iter() .map(|individual| { individual.evaluation.weighted_sum.max(0.0) // Only positive values are violations }) .sum::() / population.population.len() as f64; avg_constraint_violations.push(avg_constraint_violation); })?; // Extract feasible fractions from the result let (evolution_result, feasible_fractions) = result; Ok((evolution_result, feasible_fractions, avg_constraint_violations)) } /// Helper function to check if a chromosome is feasible fn check_feasibility( chromosome: &SVector, constraints: &[LowerThanConstraintFunction, f64>; CONSTRAINTS], ) -> bool { constraints.iter().all(|constraint| { constraint.is_feasible(chromosome).unwrap_or(false) }) } pub mod single_constraint_fitness; use single_constraint_fitness::SingleConstraintFitness; /// Solve a constrained optimization problem using NSGA-II pub fn solve_with_nsga_ii( problem: ConstrainedProblem, population_size: usize, parents_count: usize, iterations: usize, mutation_std_dev: f64, rng: &mut dyn RngCore, ) -> Result<(EvolutionResult, [f64; 2]>, 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(); // Create objectives: both using ConstrainedFitnessFunction with different fitness components let constraint_weights = [1.0e9; CONSTRAINTS]; let constraint_refs = problem.constraints.iter().collect::>().try_into() .map_err(|_| "Failed to convert constraint references")?; let zero_fitness = ArbitraryFitness::zero(); // Second objective: constraint violation only (zero fitness + constraints) let constrained_fitness_obj2 = ConstrainedFitnessFunction { fitness: &zero_fitness, constraints: constraint_refs, constraint_weights, capped: true }; let objectives: [Box, Out = f64, Err = Infallible>>; 2] = [ Box::new(problem.objective), Box::new(constrained_fitness_obj2), ]; let mut feasible_fractions = Vec::with_capacity(iterations); let mut avg_constraint_violations = Vec::with_capacity(iterations); let result = nsga_2( initial_population, parents_count, objectives, &mut pairing, &mut crossover, &mut mutation, &better_than, iterations, rng, |_iteration: usize, _stats: &EvolutionStats, _>, population: &EvaluatedPopulation, _>, _crossover: &mut _| { // Calculate feasible fraction based on second objective being close to zero let feasible_count = population.population .iter() .filter(|individual| { individual.evaluation.evaluations[1] <= 0.0 }) .count(); // Calculate average constraint violation (second objective is constraint violation) let avg_constraint_violation = population.population .iter() .map(|individual| { individual.evaluation.evaluations[1].max(0.0) // Only positive values are violations }) .sum::() / 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); }, |_, evaluation, best_candidate| { // Do not save infeasible solutions! if evaluation.evaluations[1] > 0.0 { return false; } if best_candidate.is_none() { return true; } evaluation.evaluations[0] < best_candidate.as_ref().unwrap().evaluated_chromosome.evaluation.evaluations[0] } )?; Ok((result, feasible_fractions, avg_constraint_violations)) } /// Solve a constrained optimization problem using NSGA-II with individual constraint objectives /// For simplicity, this function only works with 2-constraint problems pub fn solve_with_nsga_multi( problem: ConstrainedProblem, population_size: usize, parents_count: usize, iterations: usize, mutation_std_dev: f64, rng: &mut dyn RngCore, capped: bool ) -> Result<(EvolutionResult, [f64; CONSTRS_PLUS_ONE]>, Vec, Vec), Box> { // Unfortunately Rustc doesn't support addition in generics... assert_eq!(CONSTRAINTS + 1, CONSTRS_PLUS_ONE); // Clone the problem to get bounds info first let bounds = (problem.bounds.0, problem.bounds.1); // Create initial population let initializer = RandomInitializer::new( Box::new(BoundedOVector::new(bounds.0, 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(); let normal_perturbation = RandomDistributionPerturbation::>::normal(mutation_std_dev)?; let mut mutation = MutationPerturbation::new(Box::new(normal_perturbation), 0.1); let better_than = MinimizingOperator::new(); // Create objectives: fitness + individual constraints using cloned problem let mut objective = Some(problem.objective); let mut constraints = problem.constraints.into_iter(); let objectives: [Box, Out = f64, Err = Infallible>>; CONSTRS_PLUS_ONE] = std::array::from_fn(move |i| { let val: Box, Out = f64, Err = Infallible>> = if i == 0 { let obj = objective.take().expect("Taken already!"); Box::new(obj) } else { Box::new(SingleConstraintFitness::new(constraints.next().unwrap(), capped)) }; val }); let mut feasible_fractions = Vec::with_capacity(iterations); let mut avg_constraint_violations = Vec::with_capacity(iterations); let result = nsga_2( initial_population, parents_count, objectives, &mut pairing, &mut crossover, &mut mutation, &better_than, iterations, rng, |_iteration: usize, _stats: &EvolutionStats, _>, population: &EvaluatedPopulation, _>, _crossover: &mut _| { // Calculate feasible fraction - all constraints (objectives 1 and 2) must be <= 0 let feasible_count: f64 = population.population.iter().filter( |individual| { individual.evaluation.evaluations .iter() .skip(1) .all(|&eval| eval <= 0.0) } ).count() as f64; // Calculate average constraint violation (sum all constraint violations from objectives 1+) let total_violation: f64 = population.population .iter() .map(|individual| { individual.evaluation.evaluations .iter() .skip(1) // Skip fitness objective, only look at constraints .map(|&eval| eval.max(0.0)) // Only positive values are violations .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); }, |_, evaluation, best_candidate| { // Only save feasible solutions (all constraints satisfied) // Skip the first objective (fitness), check constraints (objectives 1+) if evaluation.evaluations.iter().skip(1).any(|&eval| eval > 0.0) { 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)) } /// Solve a constrained optimization problem using constrained NSGA-II pub fn solve_with_nsga_constr( problem: &ConstrainedProblem, population_size: usize, parents_count: usize, iterations: usize, mutation_std_dev: f64, rng: &mut dyn RngCore, ) -> Result<(EvolutionResult, [f64; 2]>, 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(); let constraint_weights = [1.0; CONSTRAINTS]; // Convert constraint array references let constraint_refs = problem.constraints.iter().collect::>().try_into() .map_err(|_| "Failed to convert constraint references")?; let zero_fitness = ArbitraryFitness::zero(); // Second objective: constraint violation only (zero fitness + constraints) let constrained_fitness_obj2 = ConstrainedFitnessFunction { fitness: &zero_fitness, constraints: constraint_refs, constraint_weights, capped: true }; let objectives: [Box, Out = f64, Err = Infallible>>; 2] = [ Box::new(cloned_problem.objective), Box::new(constrained_fitness_obj2), ]; // 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::<2, 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, _>, _crossover: &mut _| { // 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)) } pub fn solve_with_nsga_improved( problem: &ConstrainedProblem, population_size: usize, parents_count: usize, iterations: usize, mutation_std_dev: f64, rng: &mut dyn RngCore, ) -> Result<(EvolutionResult, [f64; 2]>, 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(); // Create the wrapped crossover with arithmetic crossover inside let mut wrapped_crossover = FeasibleCrossoverWrapper { p_single_replaced: P_SINGLE_REPLACED, p_double_first_replaced: P_DOUBLE_FIRST_REPLACED, p_double_second_replaced: P_DOUBLE_SECOND_REPLACED, archived_count: ARCHIVE_SIZE, archived_population: Vec::new(), 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: following the nsga algorithm pattern let constraint_weights = [1.0e9; CONSTRAINTS]; let constraint_refs = problem.constraints.iter().collect::>().try_into() .map_err(|_| "Failed to convert constraint references")?; let zero_fitness = ArbitraryFitness::zero(); // Second objective: constraint violation only (zero fitness + constraints) let constrained_fitness_obj2 = ConstrainedFitnessFunction { fitness: &zero_fitness, constraints: constraint_refs, constraint_weights, capped: true }; let objectives: [Box, Out = f64, Err = Infallible>>; 2] = [ Box::new(cloned_problem.objective), Box::new(constrained_fitness_obj2), ]; let mut feasible_fractions = Vec::with_capacity(iterations); let mut avg_constraint_violations = Vec::with_capacity(iterations); let result = nsga_2::<2, SVector, f64, Infallible, 2, _, _, _>( initial_population, parents_count, objectives, &mut pairing, &mut wrapped_crossover, &mut mutation, &better_than, iterations, rng, |_iteration: usize, _stats: &EvolutionStats, _>, population: &EvaluatedPopulation, _>, crossover: &mut _| { // Update archive with feasible solutions let feasible_individuals: Vec<_> = population.population.iter() .filter(|individual| { problem.constraints.iter().all(|constraint| { constraint.is_feasible(&individual.chromosome).unwrap_or(false) }) }) .cloned() .collect(); // Update the crossover's archive now that we have access to it crossover.update_archive(population); // Calculate feasible fraction and average constraint violation let feasible_count = population.population .iter() .filter(|individual| { // Check if the individual satisfies all constraints using the actual constraint functions 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 using actual constraint functions 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] } )?; // Return both objectives (original objective and constraint sum) let objective_result = result; Ok((objective_result, feasible_fractions, avg_constraint_violations)) } const ITERATIONS: usize = 5000; const POPULATION: usize = 250; const PARENTS_COUNT: usize = 125; const G11_EPS: f64 = 0.00015; // FeasibleCrossoverWrapper global probability parameters const P_SINGLE_REPLACED: f64 = 0.4; const P_DOUBLE_FIRST_REPLACED: f64 = 0.6; const P_DOUBLE_SECOND_REPLACED: f64 = 0.3; const ARCHIVE_SIZE: usize = 100; fn handle_g06_srank() -> Result<(), Box> { let problem = problem_g06(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.45, mutation_std_dev: 1.0, }; run_stochastic_ranking(problem, config) } fn handle_g08_srank() -> Result<(), Box> { let problem = problem_g08(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.45, mutation_std_dev: 0.5, }; run_stochastic_ranking(problem, config) } fn handle_g11_srank() -> Result<(), Box> { let problem = problem_g11(G11_EPS); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: POPULATION * 2, p_param: 0.45, mutation_std_dev: 0.01, }; run_stochastic_ranking(problem, config) } fn handle_g04_srank() -> Result<(), Box> { let problem = problem_g04(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.65, mutation_std_dev: 1.0, }; run_stochastic_ranking(problem, config) } fn handle_g05_srank() -> Result<(), Box> { let problem = problem_g05(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.20, mutation_std_dev: 10.0, }; run_stochastic_ranking(problem, config) } fn handle_g09_srank() -> Result<(), Box> { let problem = problem_g09(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.45, mutation_std_dev: 1.0, }; run_stochastic_ranking(problem, config) } fn handle_g21_srank() -> Result<(), Box> { let problem = problem_g21(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.45, mutation_std_dev: 10.0, }; run_stochastic_ranking(problem, config) } fn handle_g24_srank() -> Result<(), Box> { let problem = problem_g24(); let config = StochasticRankingConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, n_param: 2 * POPULATION, p_param: 0.45, mutation_std_dev: 0.1, }; run_stochastic_ranking(problem, config) } /// Generic function to save evolution results to CSV files with date-based naming fn save_evolution_results( method: &str, problem: &ConstrainedProblem, evolution_result: &EvolutionResult, TEval>, feasible_fractions: &[f64], avg_constraint_violations: &[f64], ) -> Result<(), Box> { // Nothing to save... if evolution_result.best_candidate.is_none() { return Ok(()); } // Get current date and time for unique file naming let now = Local::now(); let timestamp = now.format("%Y%m%d_%H%M%S").to_string(); // Create output directories let output_dir = format!("solutions/{}/{}", method, problem.name); let feasible_dir = format!("{}/feasible_fraction", output_dir); let constraint_dir = format!("{}/constraint_violation", output_dir); fs::create_dir_all(&output_dir)?; fs::create_dir_all(&feasible_dir)?; fs::create_dir_all(&constraint_dir)?; // Write best candidates CSV with timestamp let best_candidates_path = format!("{}/best_candidates_{}.csv", output_dir, timestamp); let mut best_file = fs::File::create(&best_candidates_path)?; writeln!(best_file, "iteration,evaluation,fitness")?; // Write evolution stats (best candidates through iterations) for candidate in &evolution_result.stats.best_candidates { writeln!(best_file, "{},{:?}", candidate.evaluation, candidate.evaluated_chromosome.evaluation)?; } // Write final best candidate and total evaluations if let Some(ref best_candidate) = evolution_result.best_candidate { writeln!(best_file, "{},{:?}", evolution_result.evaluations, best_candidate.evaluation)?; } // Write feasible fractions CSV with timestamp let feasible_path = format!("{}/feasible_fractions_{}.csv", feasible_dir, timestamp); let mut feasible_file = fs::File::create(&feasible_path)?; writeln!(feasible_file, "iteration,feasible_fraction")?; for (i, fraction) in feasible_fractions.iter().enumerate() { writeln!(feasible_file, "{},{}", i, fraction)?; } // Write constraint violations CSV with timestamp let constraint_path = format!("{}/constraint_violations_{}.csv", constraint_dir, timestamp); let mut constraint_file = fs::File::create(&constraint_path)?; writeln!(constraint_file, "iteration,avg_constraint_violation")?; for (i, violation) in avg_constraint_violations.iter().enumerate() { writeln!(constraint_file, "{},{}", i, violation)?; } println!("Results saved to:"); println!(" Best candidates: {}", best_candidates_path); println!(" Feasible fractions: {}", feasible_path); println!(" Constraint violations: {}", constraint_path); Ok(()) } fn run_stochastic_ranking( problem: ConstrainedProblem, config: StochasticRankingConfig, ) -> Result<(), Box> { let mut rng = rand::rng(); let result = solve_with_stochastic_ranking( problem.clone(), config.population_size, config.parents_count, config.iterations, (config.n_param, config.p_param), config.mutation_std_dev, &mut rng, )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; // Save results to CSV files save_evolution_results("srank", &problem, &evolution_result, &feasible_fractions, &avg_constraint_violations)?; if let Some(best_candidate) = evolution_result.best_candidate { println!("Best solution found:"); println!(" Chromosome: {:?}", best_candidate.chromosome); println!( " Fitness: {} ({} %)", best_candidate.evaluation, ((best_candidate.evaluation - 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(()) } fn run_nsga_ii( problem: ConstrainedProblem, config: NsgaConfig, ) -> Result<(), Box> { let mut rng = rand::rng(); let result = solve_with_nsga_ii( problem.clone(), 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", &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(()) } fn run_nsga_multi( problem: ConstrainedProblem, evolution_result: EvolutionResult, [f64; CONSTRS_PLUS_ONE]>, feasible_fractions: Vec, avg_constraint_violations: Vec, capped: bool ) -> Result<(), Box> { // Unfortunately Rustc doesn't support addition in generics... assert_eq!(CONSTRAINTS + 1, CONSTRS_PLUS_ONE); // Save results to CSV files if capped { save_evolution_results("nsga_multi", &problem, &evolution_result.clone().map(|x| x[0]), &feasible_fractions, &avg_constraint_violations)?; } else { save_evolution_results("nsga_multi_noncapped", &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-II handler functions fn handle_g06_nsga() -> 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_ii(problem, config) } fn handle_g08_nsga() -> 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_ii(problem, config) } fn handle_g11_nsga() -> 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_ii(problem, config) } fn handle_g04_nsga() -> 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_ii(problem, config) } fn handle_g05_nsga() -> 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_ii(problem, config) } fn handle_g09_nsga() -> 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_ii(problem, config) } fn handle_g21_nsga() -> 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_ii(problem, config) } fn handle_g24_nsga() -> 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_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(()) } fn run_nsga_improved( problem: ConstrainedProblem, config: NsgaConfig, ) -> Result<(), Box> { let mut rng = rand::rng(); let result = solve_with_nsga_improved( &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_improved", &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-Improved handler functions fn handle_g04_nsga_improved() -> Result<(), Box> { let problem = problem_g04(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g05_nsga_improved() -> Result<(), Box> { let problem = problem_g05(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g06_nsga_improved() -> Result<(), Box> { let problem = problem_g06(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g08_nsga_improved() -> Result<(), Box> { let problem = problem_g08(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g09_nsga_improved() -> Result<(), Box> { let problem = problem_g09(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g11_nsga_improved() -> Result<(), Box> { let problem = problem_g11(G11_EPS); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g21_nsga_improved() -> Result<(), Box> { let problem = problem_g21(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; run_nsga_improved(problem, config) } fn handle_g24_nsga_improved() -> 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_improved(problem, config) } // NSGA-Multi handler functions for individual problems fn handle_nsga_multi_g06(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g06(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.5, }; let result = solve_with_nsga_multi::<2, 2, 3>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g08(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g08(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.5, }; let result = solve_with_nsga_multi::<2, 2, 3>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g11(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); 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, }; let result = solve_with_nsga_multi::<2, 1, 2>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g04(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g04(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 1.0, }; let result = solve_with_nsga_multi::<5, 6, 7>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g05(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g05(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 10.0, }; let result = solve_with_nsga_multi::<4, 5, 6>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g09(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g09(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 1.0, }; let result = solve_with_nsga_multi::<7, 4, 5>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g21(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g21(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 10.0, }; let result = solve_with_nsga_multi::<7, 6, 7>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn handle_nsga_multi_g24(capped: bool) -> Result<(), Box> { let mut rng = rand::rng(); let problem = problem_g24(); let config = NsgaConfig { population_size: POPULATION, parents_count: PARENTS_COUNT, iterations: ITERATIONS, mutation_std_dev: 0.1, }; let result = solve_with_nsga_multi::<2, 2, 3>( problem.clone(), config.population_size, config.parents_count, config.iterations, config.mutation_std_dev, &mut rng, capped )?; let (evolution_result, feasible_fractions, avg_constraint_violations) = result; run_nsga_multi(problem, evolution_result, feasible_fractions, avg_constraint_violations, capped) } fn main() { let args: Vec = env::args().collect(); if args.len() != 3 { eprintln!("Usage: {} ", args[0]); eprintln!("Methods: srank, nsga, nsga_constr, nsga_improved, nsga_multi, nsga_multi_noncapped"); eprintln!("Problems: g04, g05, g06, g08, g09, g11, g21, g24"); std::process::exit(1); } // Run the main logic with increased stack size let result = std::thread::Builder::new() .stack_size(16 * 1024 * 1024) // 16MB stack .spawn(move || main_with_args(args)) .expect("Failed to spawn thread") .join() .expect("Thread panicked"); if let Err(e) = result { eprintln!("Error: {}", e); std::process::exit(1); } } fn main_with_args(args: Vec) -> Result<(), Box> { let method = &args[1]; let problem = &args[2]; let result = match (method.as_str(), problem.as_str()) { ("srank", "g04") => handle_g04_srank(), ("srank", "g05") => handle_g05_srank(), ("srank", "g06") => handle_g06_srank(), ("srank", "g08") => handle_g08_srank(), ("srank", "g09") => handle_g09_srank(), ("srank", "g11") => handle_g11_srank(), ("srank", "g21") => handle_g21_srank(), ("srank", "g24") => handle_g24_srank(), ("nsga", "g04") => handle_g04_nsga(), ("nsga", "g05") => handle_g05_nsga(), ("nsga", "g06") => handle_g06_nsga(), ("nsga", "g08") => handle_g08_nsga(), ("nsga", "g09") => handle_g09_nsga(), ("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_improved", "g04") => handle_g04_nsga_improved(), ("nsga_improved", "g05") => handle_g05_nsga_improved(), ("nsga_improved", "g06") => handle_g06_nsga_improved(), ("nsga_improved", "g08") => handle_g08_nsga_improved(), ("nsga_improved", "g09") => handle_g09_nsga_improved(), ("nsga_improved", "g11") => handle_g11_nsga_improved(), ("nsga_improved", "g21") => handle_g21_nsga_improved(), ("nsga_improved", "g24") => handle_g24_nsga_improved(), ("nsga_multi", "g04") => handle_nsga_multi_g04(true), ("nsga_multi", "g05") => handle_nsga_multi_g05(true), ("nsga_multi", "g06") => handle_nsga_multi_g06(true), ("nsga_multi", "g08") => handle_nsga_multi_g08(true), ("nsga_multi", "g09") => handle_nsga_multi_g09(true), ("nsga_multi", "g11") => handle_nsga_multi_g11(true), ("nsga_multi", "g21") => handle_nsga_multi_g21(true), ("nsga_multi", "g24") => handle_nsga_multi_g24(true), ("nsga_multi_noncapped", "g04") => handle_nsga_multi_g04(false), ("nsga_multi_noncapped", "g05") => handle_nsga_multi_g05(false), ("nsga_multi_noncapped", "g06") => handle_nsga_multi_g06(false), ("nsga_multi_noncapped", "g08") => handle_nsga_multi_g08(false), ("nsga_multi_noncapped", "g09") => handle_nsga_multi_g09(false), ("nsga_multi_noncapped", "g11") => handle_nsga_multi_g11(false), ("nsga_multi_noncapped", "g21") => handle_nsga_multi_g21(false), ("nsga_multi_noncapped", "g24") => handle_nsga_multi_g24(false), (_, _) => { eprintln!("Invalid method '{}' or problem '{}'", method, problem); eprintln!("Methods: srank, nsga, nsga_constr, nsga_improved, nsga_multi, nsga_multi_noncapped"); eprintln!("Problems: g04, g05, g06, g08, g09, g11, g21, g24"); std::process::exit(1); } }; if let Err(e) = result { eprintln!("Error: {}", e); std::process::exit(1); } Ok(()) }