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<const DIM: usize, const CONSTRAINTS: usize>(
mut problem: ConstrainedProblem<DIM, CONSTRAINTS>,
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<SVector<f64, DIM>, f64>, Vec<f64>, Vec<f64>), Box<dyn std::error::Error>> {
// 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::<DIM>, 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::<nalgebra::Const<DIM>, 2, _>::new(
crossover,
problem.bounds.0,
problem.bounds.1,
BoundedCrossoverStrategy::Retry(5)
);
// Setup bounded random distribution perturbation with Normal distribution
let normal_perturbation = RandomDistributionPerturbation::<DIM, Normal<f64>>::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::<Vec<_>>().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::<f64>() / 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<const DIM: usize, const CONSTRAINTS: usize>(
chromosome: &SVector<f64, DIM>,
constraints: &[LowerThanConstraintFunction<SVector<f64, DIM>, 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<const DIM: usize, const CONSTRAINTS: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
population_size: usize,
parents_count: usize,
iterations: usize,
mutation_std_dev: f64,
rng: &mut dyn RngCore,
) -> Result<(EvolutionResult<SVector<f64, DIM>, [f64; 2]>, Vec<f64>, Vec<f64>), Box<dyn std::error::Error>> {
// 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::<DIM>, 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::<DIM, Normal<f64>>::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::<Vec<_>>().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<dyn FitnessFunction<In = SVector<f64, DIM>, 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<SVector<f64, DIM>, _>, population: &EvaluatedPopulation<SVector<f64, DIM>, _>, _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::<f64>() / 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<const DIM: usize, const CONSTRAINTS: usize, const CONSTRS_PLUS_ONE: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
population_size: usize,
parents_count: usize,
iterations: usize,
mutation_std_dev: f64,
rng: &mut dyn RngCore,
capped: bool
) -> Result<(EvolutionResult<SVector<f64, DIM>, [f64; CONSTRS_PLUS_ONE]>, Vec<f64>, Vec<f64>), Box<dyn std::error::Error>> {
// 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::<DIM>, population_size, rng)
);
// Setup components
let mut pairing = AdjacentPairing::new();
let mut crossover = ArithmeticCrossover::new();
let normal_perturbation = RandomDistributionPerturbation::<DIM, Normal<f64>>::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<dyn FitnessFunction<In = SVector<f64, DIM>, Out = f64, Err = Infallible>>; CONSTRS_PLUS_ONE] = std::array::from_fn(move |i| {
let val: Box<dyn FitnessFunction<In = SVector<f64, DIM>, 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<SVector<f64, DIM>, _>, population: &EvaluatedPopulation<SVector<f64, DIM>, _>, _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::<f64>()
})
.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<const DIM: usize, const CONSTRAINTS: usize>(
problem: &ConstrainedProblem<DIM, CONSTRAINTS>,
population_size: usize,
parents_count: usize,
iterations: usize,
mutation_std_dev: f64,
rng: &mut dyn RngCore,
) -> Result<(EvolutionResult<SVector<f64, DIM>, [f64; 2]>, Vec<f64>, Vec<f64>), Box<dyn std::error::Error>> {
// 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::<DIM>, 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::<DIM, Normal<f64>>::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::<Vec<_>>().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<dyn FitnessFunction<In = SVector<f64, DIM>, Out = f64, Err = Infallible>>; 2] = [
Box::new(cloned_problem.objective),
Box::new(constrained_fitness_obj2),
];
// Convert constraints to boxed trait objects
let constraints: [Box<dyn ConstraintFunction<Chromosome = SVector<f64, DIM>, Out = f64, Err = Infallible>>; CONSTRAINTS] =
cloned_problem.constraints.into_iter().map(|c| Box::new(c) as Box<dyn ConstraintFunction<Chromosome = SVector<f64, DIM>, Out = f64, Err = Infallible>>).collect::<Vec<_>>().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, DIM>, f64, Infallible, 2, _, _, _>(
initial_population,
parents_count,
objectives,
constraints,
&mut pairing,
&mut crossover,
&mut mutation,
&better_than,
iterations,
rng,
|_iteration: usize, _stats: &EvolutionStats<SVector<f64, DIM>, _>, population: &EvaluatedPopulation<SVector<f64, DIM>, _>, _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::<f64>()
})
.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<const DIM: usize, const CONSTRAINTS: usize>(
problem: &ConstrainedProblem<DIM, CONSTRAINTS>,
population_size: usize,
parents_count: usize,
iterations: usize,
mutation_std_dev: f64,
rng: &mut dyn RngCore,
) -> Result<(EvolutionResult<SVector<f64, DIM>, [f64; 2]>, Vec<f64>, Vec<f64>), Box<dyn std::error::Error>> {
// 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::<DIM>, 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::<DIM, Normal<f64>>::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::<Vec<_>>().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<dyn FitnessFunction<In = SVector<f64, DIM>, 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, DIM>, f64, Infallible, 2, _, _, _>(
initial_population,
parents_count,
objectives,
&mut pairing,
&mut wrapped_crossover,
&mut mutation,
&better_than,
iterations,
rng,
|_iteration: usize, _stats: &EvolutionStats<SVector<f64, DIM>, _>, population: &EvaluatedPopulation<SVector<f64, DIM>, _>, 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::<f64>()
})
.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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<const DIM: usize, const CONSTRAINTS: usize, TEval: std::fmt::Debug>(
method: &str,
problem: &ConstrainedProblem<DIM, CONSTRAINTS>,
evolution_result: &EvolutionResult<SVector<f64, DIM>, TEval>,
feasible_fractions: &[f64],
avg_constraint_violations: &[f64],
) -> Result<(), Box<dyn std::error::Error>> {
// 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<const DIM: usize, const CONSTRAINTS: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
config: StochasticRankingConfig,
) -> Result<(), Box<dyn std::error::Error>> {
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<const DIM: usize, const CONSTRAINTS: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
config: NsgaConfig,
) -> Result<(), Box<dyn std::error::Error>> {
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<const DIM: usize, const CONSTRAINTS: usize, const CONSTRS_PLUS_ONE: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
evolution_result: EvolutionResult<SVector<f64, DIM>, [f64; CONSTRS_PLUS_ONE]>,
feasible_fractions: Vec<f64>,
avg_constraint_violations: Vec<f64>,
capped: bool
) -> Result<(), Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<const DIM: usize, const CONSTRAINTS: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
config: NsgaConfig,
) -> Result<(), Box<dyn std::error::Error>> {
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<const DIM: usize, const CONSTRAINTS: usize>(
problem: ConstrainedProblem<DIM, CONSTRAINTS>,
config: NsgaConfig,
) -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<String> = env::args().collect();
if args.len() != 3 {
eprintln!("Usage: {} <method> <problem>", 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<String>) -> Result<(), Box<dyn std::error::Error + Send>> {
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(())
}