M codes/constr_hw02/src/main.rs => codes/constr_hw02/src/main.rs +41 -26
@@ 42,23 42,22 @@ pub fn solve_with_stochastic_ranking<const DIM: usize, const CONSTRAINTS: usize>
let mut selection = TournamentSelection::new(5, 0.95);
let mut replacement = GenerationalReplacement;
let mut pairing = AdjacentPairing::new();
- let mut crossover = ArithmeticCrossover::new();
- // let mut crossover = BoundedCrossover::<nalgebra::Const<2>, 2, _>::new(
- // ArithmeticCrossover::new(),
- // problem.bounds.0,
- // problem.bounds.1,
- // BoundedCrossoverStrategy::Retry(5)
- // );
+ 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 mut perturbation = normal_perturbation;
- // let perturbation = BoundedPerturbation::new(
- // normal_perturbation,
- // problem.bounds.0,
- // problem.bounds.1,
- // BoundedPerturbationStrategy::Retry(5)
- // );
+ 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.
@@ 72,6 71,8 @@ pub fn solve_with_stochastic_ranking<const DIM: usize, const CONSTRAINTS: usize>
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,
@@ 87,15 88,22 @@ pub fn solve_with_stochastic_ranking<const DIM: usize, const CONSTRAINTS: usize>
&mut replacement,
&better_than,
iterations,
- rng)?;
+ 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;
- // For now, create placeholder constraint violations data
- // TODO: This needs library-level changes to properly track constraint violations
- let avg_constraint_violations = vec![0.0; feasible_fractions.len()];
-
Ok((evolution_result, feasible_fractions, avg_constraint_violations))
}
@@ 571,16 579,16 @@ pub fn solve_with_nsga_improved<const DIM: usize, const CONSTRAINTS: usize>(
Ok((objective_result, feasible_fractions, avg_constraint_violations))
}
-const ITERATIONS: usize = 1000;
-const POPULATION: usize = 500;
+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;
-const PARENTS_COUNT: usize = 500;
-const G11_EPS: f64 = 0.00015;
fn handle_g06_srank() -> Result<(), Box<dyn std::error::Error>> {
let problem = problem_g06();
@@ 617,7 625,7 @@ fn handle_g11_srank() -> Result<(), Box<dyn std::error::Error>> {
n_param: POPULATION * 2,
p_param: 0.45,
- mutation_std_dev: 0.01 / 50.0,
+ mutation_std_dev: 0.01,
};
run_stochastic_ranking(problem, config)
}
@@ 629,7 637,7 @@ fn handle_g04_srank() -> Result<(), Box<dyn std::error::Error>> {
parents_count: PARENTS_COUNT,
iterations: ITERATIONS,
n_param: 2 * POPULATION,
- p_param: 0.45,
+ p_param: 0.65,
mutation_std_dev: 1.0,
};
run_stochastic_ranking(problem, config)
@@ 642,7 650,7 @@ fn handle_g05_srank() -> Result<(), Box<dyn std::error::Error>> {
parents_count: PARENTS_COUNT,
iterations: ITERATIONS,
n_param: 2 * POPULATION,
- p_param: 0.45,
+ p_param: 0.20,
mutation_std_dev: 10.0,
};
run_stochastic_ranking(problem, config)
@@ 695,6 703,13 @@ fn save_evolution_results<const DIM: usize, const CONSTRAINTS: usize, TEval: std
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();
M codes/constr_hw02/src/problems.rs => codes/constr_hw02/src/problems.rs +4 -4
@@ 165,8 165,8 @@ pub fn problem_g06() -> ConstrainedProblem<2, 2> {
),
],
bounds: (
- SVector::<f64, 2>::new(0.0, 0.0), // min bounds
- SVector::<f64, 2>::new(50.0, 50.0), // max bounds
+ SVector::<f64, 2>::new(13.0, 0.0), // min bounds
+ SVector::<f64, 2>::new(100.0, 100.0), // max bounds
),
optimal_value: -6961.8137558015,
instantiate_fn: None,
@@ 269,8 269,8 @@ pub fn problem_g11(eps: f64) -> ConstrainedProblem<2, 1> {
})),
],
bounds: (
- SVector::<f64, 2>::new(-50.0, -50.0), // min bounds
- SVector::<f64, 2>::new(50.0, 50.0), // max bounds
+ SVector::<f64, 2>::new(-1.0, -1.0), // min bounds
+ SVector::<f64, 2>::new(1.0, 1.0), // max bounds
),
optimal_value: 0.7499, // Best known optimum
instantiate_fn: None,
M codes/eoa_lib/src/constraints.rs => codes/eoa_lib/src/constraints.rs +22 -22
@@ 240,11 240,11 @@ pub fn evolve_constraint_penalty_weight_tau_target
#[derive(PartialEq, Clone, Debug)]
pub struct ConstrainedEvaluation<const CONSTRAINTS: usize, TOut> {
- fitness: TOut,
- constraints: [TOut; CONSTRAINTS],
- weighted_sum: TOut,
- constr_weighted_sum: TOut,
- is_feasible: bool,
+ pub fitness: TOut,
+ pub constraints: [TOut; CONSTRAINTS],
+ pub weighted_sum: TOut,
+ pub constr_weighted_sum: TOut,
+ pub is_feasible: bool,
}
impl<const CONSTRAINTS: usize, TOut: PartialOrd> PartialOrd for ConstrainedEvaluation<CONSTRAINTS, TOut> {
@@ 430,17 430,18 @@ pub fn stochastic_ranking_evolution_algorithm
// TODO: termination condition
iterations: usize,
rng: &mut dyn RngCore,
- // mut evolutionary_strategy: impl FnMut(
- // usize,
- // &EvolutionStats<TChromosome, TResult>,
- // &EvaluatedPopulation<TChromosome, TResult>,
-
- // &mut TPairing,
- // &mut TCrossover,
- // &mut TPerturbation,
- // &mut TReplacement,
- // &mut ConstrainedEvalFitness<CONSTRAINTS, TChromosome, TResult, TFitness, TConstraint>,
- // ),
+ mut evolutionary_strategy: impl FnMut(
+ usize,
+ &EvolutionStats<TChromosome, ConstrainedEvaluation<CONSTRAINTS, TResult>>,
+ &EvaluatedPopulation<TChromosome, ConstrainedEvaluation<CONSTRAINTS, TResult>>,
+
+ // &mut TFitness,
+ // &mut TPairing,
+ // &mut TCrossover,
+ // &mut TPerturbation,
+ // &mut TReplacement,
+ // &mut ConstrainedEvalFitness<CONSTRAINTS, TChromosome, TResult, TFitness, TConstraint>,
+ ),
) -> Result<(EvolutionResult<TChromosome, TResult>, Vec<f64>), Box<dyn Error>>
{
let mut constrained_fitness = ConstrainedEvalFitness {
@@ 480,12 481,11 @@ pub fn stochastic_ranking_evolution_algorithm
feasible_fractions.push(feasible_fraction);
- // evolutionary_strategy(
- // iteration,
- // stats,
- // population,
- // fitness
- // )
+ evolutionary_strategy(
+ iteration,
+ stats,
+ population,
+ )
},
|_, evaluation, best_candidate| {
// Do not save enfeasible solutions!
M codes/eoa_lib/src/population.rs => codes/eoa_lib/src/population.rs +1 -1
@@ 2,7 2,7 @@ use crate::{comparison::BetterThanOperator, fitness::FitnessFunction};
#[derive(Clone, Debug)]
pub struct Population<TChromosome> {
- population: Vec<TChromosome>
+ pub population: Vec<TChromosome>
}
#[derive(Clone, Debug)]
A codes/py_plotter/README.md => codes/py_plotter/README.md +86 -0
@@ 0,0 1,86 @@
+# Python Plotter for Constraint Optimization Results
+
+This directory contains tools for plotting results from constraint optimization experiments.
+
+## Files
+
+- `plotter.py` - Main plotting script
+- `config_example.json` - Example configuration file
+- `config_feasible_fraction.json` - Example config for feasible fraction plots
+- `config_constraint_violation.json` - Example config for constraint violation plots
+- `objectives.json` - Global configuration with optimal objective values
+
+## Configuration
+
+The JSON configuration specifies:
+
+- `plot_type`: Type of data to plot (`best_candidates`, `constraint_violation`, `feasible_fraction`)
+- `algorithms`: List of algorithms with visualization settings
+ - `name`: Algorithm directory name
+ - `label`: Display name in legend
+ - `color`: Algorithm color (hex code)
+ - `linestyle`: Line style (`-`, `--`, `-.`, `:`)
+- `instances`: List of problem instances with colors
+ - `name`: Instance directory name
+ - `label`: Display name in legend
+ - `color`: Instance color (hex code)
+- `data_path`: Path to solutions directory
+- `output_dir`: Directory for saving plots
+- `plot_settings`: Figure settings (size, labels, grid, log scales, etc.)
+ - `log_x`: Enable logarithmic x-axis
+ - `log_y`: Enable logarithmic y-axis
+ - `show_std`: Show standard deviation bands (default: true)
+ - `color_combination`: How to combine algorithm and instance colors
+ - `"blend"`: Blend algorithm and instance colors (default)
+ - `"algorithm"`: Use only algorithm colors
+ - `"instance"`: Use only instance colors
+
+## Usage
+
+Basic usage:
+```bash
+python3 plotter.py config_example.json
+```
+
+With Guix (recommended):
+```bash
+guix shell python python-matplotlib python-pandas python-numpy -- python3 plotter.py config_example.json
+```
+
+## Data Structure
+
+The plotter expects data in this structure:
+```
+data_path/
+├── algorithm1/
+│ ├── instance1/
+│ │ ├── best_candidates_*.csv
+│ │ ├── constraint_violation/
+│ │ │ └── constraint_violations_*.csv
+│ │ └── feasible_fraction/
+│ │ └── feasible_fractions_*.csv
+│ └── instance2/...
+└── algorithm2/...
+```
+
+## Features
+
+- Averages multiple runs automatically
+- Shows standard deviation as transparent bands around mean
+- Single plot with multiple instances (colored by instance)
+- Different line styles for algorithms
+- Logarithmic axes support for both x and y
+- **Percentage deviation from optimal** for `best_candidates` plots
+- Absolute values for other plot types (`constraint_violation`, `feasible_fraction`)
+- Automatic legend with run counts
+- High-quality SVG output
+
+## Percentage Deviation Calculation
+
+For `best_candidates` plots, the plotter automatically:
+1. Loads optimal objective values from `objectives.json`
+2. Validates that all values are >= optimal (raises error if better values found)
+3. Calculates percentage deviation: `(current - optimal) / |optimal| * 100`
+4. Plots deviation percentages so you can see convergence to 0%
+
+Other plot types use absolute values as normal.<
\ No newline at end of file
A codes/py_plotter/config_best_g09.json => codes/py_plotter/config_best_g09.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "best_candidates",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g09",
+ "label": "G09",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Percentage deviation from optimum",
+ "title": "Best Candidates Percentage Deviation - G09",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": true,
+ "color_combination": "algorithm",
+ "show_std": true
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_constraint_violation.json => codes/py_plotter/config_constraint_violation.json +35 -0
@@ 0,0 1,35 @@
+{
+ "plot_type": "constraint_violation",
+ "algorithms": [
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#d62728",
+ "linestyle": "-."
+ }
+ ],
+ "instances": ["g04", "g05", "g06"],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [15, 5],
+ "xlabel": "Iteration",
+ "ylabel": "Average Constraint Violation",
+ "title": "Constraint Violation Over Time",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.3
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_example.json => codes/py_plotter/config_example.json +50 -0
@@ 0,0 1,50 @@
+{
+ "plot_type": "best_candidates",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "Stochastic ranking",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#2ca02c",
+ "linestyle": "-."
+ }
+ ],
+ "instances": [
+ {
+ "name": "g24",
+ "label": "G24",
+ "color": "#d62728"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Percentage Deviation from Optimal (%)",
+ "title": "Best Candidate Evolution - Deviation from Optimal",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.3,
+ "log_x": true,
+ "log_y": true,
+ "color_combination": "blend"
+ }
+}
A codes/py_plotter/config_feasible_fraction.json => codes/py_plotter/config_feasible_fraction.json +55 -0
@@ 0,0 1,55 @@
+{
+ "plot_type": "feasible_fraction",
+ "algorithms": [
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#2ca02c",
+ "linestyle": "-."
+ },
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#d62728",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g04",
+ "label": "G04",
+ "color": "#1f77b4"
+ },
+ {
+ "name": "g05",
+ "label": "G05",
+ "color": "#ff7f0e"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 6],
+ "xlabel": "Iteration",
+ "ylabel": "Feasible Fraction",
+ "title": "Feasible Solution Fraction Over Time",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": false,
+ "color_combination": "blend"
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_feasible_g05.json => codes/py_plotter/config_feasible_g05.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "feasible_fraction",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g05",
+ "label": "G05",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Feasible Fraction",
+ "title": "Feasible Solution Fraction - G05",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": false,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_feasible_g06.json => codes/py_plotter/config_feasible_g06.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "feasible_fraction",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g06",
+ "label": "G06",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Feasible Fraction",
+ "title": "Feasible Solution Fraction - G06",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": false,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_feasible_g11.json => codes/py_plotter/config_feasible_g11.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "feasible_fraction",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g11",
+ "label": "G11",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Feasible Fraction",
+ "title": "Feasible Solution Fraction - G11",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": false,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_violation_g05.json => codes/py_plotter/config_violation_g05.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "constraint_violation",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g05",
+ "label": "G05",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Average Constraint Violation",
+ "title": "Average Constraint Violation - G05",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": true,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_violation_g06.json => codes/py_plotter/config_violation_g06.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "constraint_violation",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g06",
+ "label": "G06",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Average Constraint Violation",
+ "title": "Average Constraint Violation - G06",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": true,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/config_violation_g11.json => codes/py_plotter/config_violation_g11.json +57 -0
@@ 0,0 1,57 @@
+{
+ "plot_type": "constraint_violation",
+ "algorithms": [
+ {
+ "name": "srank",
+ "label": "S-Rank",
+ "color": "#1f77b4",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga",
+ "label": "NSGA-II",
+ "color": "#ff7f0e",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_multi",
+ "label": "NSGA-II Multi",
+ "color": "#2ca02c",
+ "linestyle": "-"
+ },
+ {
+ "name": "nsga_improved",
+ "label": "NSGA-II Improved",
+ "color": "#ff7f0e",
+ "linestyle": "--"
+ },
+ {
+ "name": "nsga_constr",
+ "label": "NSGA-II Constr",
+ "color": "#ff7f0e",
+ "linestyle": ":"
+ }
+ ],
+ "instances": [
+ {
+ "name": "g11",
+ "label": "G11",
+ "color": "#8c564b"
+ }
+ ],
+ "data_path": "../constr_hw02/solutions",
+ "output_dir": "plots",
+ "plot_settings": {
+ "figsize": [12, 8],
+ "xlabel": "Function evaluation",
+ "ylabel": "Average Constraint Violation",
+ "title": "Average Constraint Violation - G11",
+ "grid": true,
+ "legend": true,
+ "alpha_fill": 0.2,
+ "log_x": true,
+ "log_y": true,
+ "color_combination": "algorithm",
+ "show_std": false
+ }
+}<
\ No newline at end of file
A codes/py_plotter/objectives.json => codes/py_plotter/objectives.json +10 -0
@@ 0,0 1,10 @@
+{
+ "g04": -30665.53867178333,
+ "g05": 5126.4967140071,
+ "g06": -6961.8137558015,
+ "g08": -0.0958250414180359,
+ "g09": 680.6300573745,
+ "g11": 0.7499,
+ "g21": 193.724510070035,
+ "g24": -5.50801327159536
+}<
\ No newline at end of file
A codes/py_plotter/plotter.py => codes/py_plotter/plotter.py +273 -0
@@ 0,0 1,273 @@
+#!/usr/bin/env python3
+
+import json
+import pandas as pd
+import matplotlib
+matplotlib.use('Agg') # Use non-interactive backend
+import matplotlib.pyplot as plt
+import matplotlib.colors as mcolors
+import numpy as np
+from pathlib import Path
+import glob
+import argparse
+
+class ConstraintOptimizationPlotter:
+ def __init__(self, config_path):
+ with open(config_path, 'r') as f:
+ self.config = json.load(f)
+
+ self.data_path = Path(self.config['data_path'])
+ self.output_dir = Path(self.config['output_dir'])
+ self.output_dir.mkdir(exist_ok=True)
+
+ # Load objectives for percentage deviation calculation
+ objectives_path = Path(__file__).parent / 'objectives.json'
+ with open(objectives_path, 'r') as f:
+ self.objectives = json.load(f)
+
+ def get_csv_pattern(self, plot_type):
+ patterns = {
+ 'best_candidates': 'best_candidates_*.csv',
+ 'constraint_violation': 'constraint_violation/constraint_violations_*.csv',
+ 'feasible_fraction': 'feasible_fraction/feasible_fractions_*.csv'
+ }
+ return patterns.get(plot_type, 'best_candidates_*.csv')
+
+ def get_value_column(self, plot_type):
+ columns = {
+ 'best_candidates': 'evaluation', # Changed from 'fitness' to 'evaluation'
+ 'constraint_violation': 'avg_constraint_violation',
+ 'feasible_fraction': 'feasible_fraction'
+ }
+ return columns.get(plot_type, 'evaluation')
+
+ def blend_colors(self, color1, color2, alpha=0.5):
+ """Blend two colors together"""
+ c1 = mcolors.to_rgb(color1)
+ c2 = mcolors.to_rgb(color2)
+ blended = tuple(alpha * c1[i] + (1 - alpha) * c2[i] for i in range(3))
+ return mcolors.to_hex(blended)
+
+ def get_combined_color(self, algorithm_color, instance_color, combination_method="blend"):
+ """Combine algorithm and instance colors based on the specified method"""
+ if combination_method == "blend":
+ return self.blend_colors(algorithm_color, instance_color, alpha=0.6)
+ elif combination_method == "algorithm":
+ return algorithm_color
+ elif combination_method == "instance":
+ return instance_color
+ else:
+ return self.blend_colors(algorithm_color, instance_color, alpha=0.5)
+
+ def calculate_percentage_deviation(self, values, instance_name):
+ """Calculate percentage deviation from optimal value for best_candidates plots"""
+ if self.config['plot_type'] != 'best_candidates' or instance_name not in self.objectives:
+ return values
+
+ optimal_value = self.objectives[instance_name]
+
+ # Check if any values are significantly better than the known optimum
+ # Allow small tolerance for numerical precision
+ tolerance = 1e-4 * np.abs(optimal_value)
+ significantly_better = values < (optimal_value - tolerance)
+ # print(values - optimal_value)
+
+ if np.any(significantly_better):
+ better_indices = np.where(significantly_better)[0]
+ best_found = np.min(values[better_indices])
+ improvement = optimal_value - best_found
+ improvement_pct = improvement / np.abs(optimal_value) * 100
+
+ print(f"WARNING: Found {np.sum(significantly_better)} values better than known optimum for {instance_name}!")
+ print(f"Known optimum: {optimal_value}")
+ print(f"Best found: {best_found}")
+ print(f"Improvement: {improvement} ({improvement_pct:.3f}%)")
+ print(f"Using best found value as new reference point.")
+
+ # Update the optimal value to the best found for this calculation
+ optimal_value = best_found
+
+
+ new_optimal_value = optimal_value
+ if optimal_value < 0:
+ new_optimal_value = - optimal_value
+ values = values + 2*new_optimal_value
+
+ # Calculate percentage deviation: (current - optimal) / |optimal| * 100
+ # This will always be >= 0 since current >= optimal
+ percentage_deviations = (values - new_optimal_value) / new_optimal_value * 100
+ return percentage_deviations
+
+ def load_data_for_algorithm_instance(self, algorithm, instance):
+ csv_pattern = self.get_csv_pattern(self.config['plot_type'])
+
+ algorithm_path = self.data_path / algorithm / instance
+ csv_files = list(algorithm_path.glob(csv_pattern))
+
+ if not csv_files:
+ print(f"Warning: No CSV files found for {algorithm}/{instance}")
+ return None
+
+ print(f"Found {len(csv_files)} files for {algorithm}/{instance}")
+
+ value_col = self.get_value_column(self.config['plot_type'])
+ all_data = []
+
+ for csv_file in csv_files:
+ try:
+ df = pd.read_csv(csv_file)
+ if 'iteration' in df.columns and value_col in df.columns:
+ all_data.append(df[['iteration', value_col]].copy())
+ except Exception as e:
+ print(f"Error reading {csv_file}: {e}")
+
+ if not all_data:
+ return None
+
+ # Find the maximum function evaluation (iteration value) across all runs
+ max_evaluation = max(df['iteration'].max() for df in all_data)
+
+ # Create a common evaluation grid - collect all unique evaluation points
+ all_evaluations = set()
+ for df in all_data:
+ all_evaluations.update(df['iteration'].tolist())
+ all_evaluations.add(max_evaluation) # Ensure max is included
+ common_grid = sorted(list(all_evaluations))
+
+ aligned_data = []
+
+ for df in all_data:
+ df_copy = df.copy().sort_values('iteration').reset_index(drop=True)
+
+ # Interpolate/extend this run to the common grid
+ aligned_values = []
+ current_value = df_copy[value_col].iloc[0] if len(df_copy) > 0 else 0
+ df_idx = 0
+
+ for eval_point in common_grid:
+ # Update current_value if we have data at this evaluation point or before
+ while df_idx < len(df_copy) and df_copy['iteration'].iloc[df_idx] <= eval_point:
+ current_value = df_copy[value_col].iloc[df_idx]
+ df_idx += 1
+
+ aligned_values.append(current_value)
+
+ aligned_data.append(aligned_values)
+
+ # All runs now have the same length and evaluation points
+ values_matrix = np.column_stack(aligned_data)
+ values_matrix = values_matrix.astype(np.float64)
+ iterations = np.array(common_grid)
+
+ # Apply percentage deviation calculation for best_candidates
+ if self.config['plot_type'] == 'best_candidates':
+ # Apply percentage deviation to each column (run) separately
+ for i in range(values_matrix.shape[1]):
+ values_matrix[:, i] = self.calculate_percentage_deviation(values_matrix[:, i], instance)
+
+ mean_values = np.mean(values_matrix, axis=1)
+ std_values = np.std(values_matrix, axis=1)
+
+ return {
+ 'iterations': iterations,
+ 'mean': mean_values,
+ 'std': std_values,
+ 'runs': len(all_data)
+ }
+
+ def create_plot(self):
+ fig, ax = plt.subplots(1, 1, figsize=self.config['plot_settings']['figsize'])
+
+ combination_method = self.config['plot_settings'].get('color_combination', 'blend')
+
+ for instance in self.config['instances']:
+ instance_name = instance['name'] if isinstance(instance, dict) else instance
+ instance_label = instance.get('label', instance_name) if isinstance(instance, dict) else instance_name
+ instance_color = instance.get('color', '#000000') if isinstance(instance, dict) else '#000000'
+
+ for algorithm in self.config['algorithms']:
+ alg_name = algorithm['name']
+ alg_label = algorithm['label']
+ alg_color = algorithm.get('color', '#000000')
+ linestyle = algorithm['linestyle']
+
+ # Get combined color
+ combined_color = self.get_combined_color(alg_color, instance_color, combination_method)
+
+ data = self.load_data_for_algorithm_instance(alg_name, instance_name)
+
+ if data is None:
+ print(f"No data found for {alg_name}/{instance_name}")
+ continue
+
+ # Handle data values (percentage deviation for best_candidates, absolute for others)
+ mean_values = data['mean']
+ std_values = data['std']
+
+ # For log scale with negative values (non-best_candidates plots), take absolute value
+ if self.config['plot_settings'].get('log_y', False) and self.config['plot_type'] != 'best_candidates':
+ mean_values = np.abs(mean_values)
+
+ ax.plot(data['iterations'], mean_values,
+ color=combined_color,
+ linestyle=linestyle,
+ label=f"{alg_label} - {instance_label}",
+ linewidth=2,
+ drawstyle='steps-post')
+
+ # Add fill_between for standard deviation bands (if enabled)
+ if self.config['plot_settings'].get('show_std', True):
+ lower_bound = mean_values - std_values
+ upper_bound = mean_values + std_values
+
+ # For log scale, ensure positive values
+ if self.config['plot_settings'].get('log_y', False):
+ lower_bound = np.maximum(lower_bound, 0.01)
+
+ ax.fill_between(data['iterations'],
+ lower_bound,
+ upper_bound,
+ color=combined_color,
+ alpha=self.config['plot_settings']['alpha_fill'])
+
+ ax.set_xlabel(self.config['plot_settings']['xlabel'], fontsize=14)
+ ax.set_ylabel(self.config['plot_settings']['ylabel'], fontsize=14)
+ ax.set_title(self.config['plot_settings']['title'], fontsize=16)
+
+ # Set log scale if requested
+ if self.config['plot_settings'].get('log_x', False):
+ ax.set_xscale('log')
+ if self.config['plot_settings'].get('log_y', False):
+ ax.set_yscale('log')
+
+ # Format y-axis with percentages for best_candidates plots
+ if self.config['plot_type'] == 'best_candidates':
+ ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x:.1f}%'))
+
+ # Set tick label font sizes
+ ax.tick_params(axis='both', which='major', labelsize=12)
+ ax.tick_params(axis='both', which='minor', labelsize=10)
+
+ if self.config['plot_settings']['grid']:
+ ax.grid(True, alpha=0.3)
+
+ if self.config['plot_settings']['legend']:
+ ax.legend()
+
+ plt.tight_layout()
+
+ instance_names = [inst['name'] if isinstance(inst, dict) else inst for inst in self.config['instances']]
+ output_file = self.output_dir / f"{self.config['plot_type']}_{'_'.join(instance_names)}.svg"
+ plt.savefig(output_file, format='svg', bbox_inches='tight')
+ print(f"Plot saved to: {output_file}")
+
+def main():
+ parser = argparse.ArgumentParser(description='Plot constraint optimization results')
+ parser.add_argument('config', help='Path to JSON configuration file')
+ args = parser.parse_args()
+
+ plotter = ConstraintOptimizationPlotter(args.config)
+ plotter.create_plot()
+
+if __name__ == '__main__':
+ main()