Skip to content

Commit

Permalink
Add Rsnsga2 algorithm not exposed to user yet. Compute diversity metr… (
Browse files Browse the repository at this point in the history
#30)

* Add Rsnsga2 algorithm not exposed to user yet. Compute diversity metrix at Survival
  • Loading branch information
andresliszt authored Feb 10, 2025
1 parent 9418048 commit 20466d8
Show file tree
Hide file tree
Showing 22 changed files with 837 additions and 188 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pymoors"
version = "0.1.1-rc1"
version = "0.1.1"
edition = "2021"

[lib]
Expand Down
38 changes: 38 additions & 0 deletions python/pymoors/_pymoors.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,44 @@ class Nsga3:

def run(self) -> None: ...

class _RNsg2Kwargs(_MooAlgorithmKwargs, total=False):
reference_points: TwoDArray
epsilon: float

class RNsga2:
"""
Implementation of RNsga2 (Reference-based NSGA-II).
RNsga2 is a variant of NSGA-II that incorporates reference points into the selection process
to enhance diversity in many-objective optimization problems. By integrating a reference
point–based ranking into the survival operator, RNsga2 aims to obtain a well-distributed
approximation of the Pareto front even in high-dimensional objective spaces.
References:
Deb, K., Pratap, A., Agarwal, S., & Meyarivan, T. (2002).
A Fast and Elitist Multiobjective Genetic Algorithm: NSGA-II.
IEEE Transactions on Evolutionary Computation, 6(2), 182–197.
Deb, K., & Jain, H. (2014).
An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based
Nondominated Sorting Approach, Part I: Solving Problems with Box Constraints.
IEEE Transactions on Evolutionary Computation, 18(4), 577–601.
"""

def __init__(self, **kwargs: Unpack[_RNsg2Kwargs]) -> None: ...
@property
def population(self) -> Population:
"""
Returns the current population of individuals.
Returns:
Population: The current population.
"""
...

def run(self) -> None: ...

# Custom Errors

class NoFeasibleIndividualsError(BaseException):
Expand Down
5 changes: 3 additions & 2 deletions src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod macros;
pub mod nsga2;
pub mod nsga3;
pub mod py_errors;
pub mod rnsga2;

#[derive(Debug)]
pub enum MultiObjectiveAlgorithmError {
Expand Down Expand Up @@ -167,15 +168,15 @@ impl MultiObjectiveAlgorithm {
)
.expect("Failed to concatenate current population genes with offspring genes");
// Build fronts from the combined genes.
let fronts = self.evaluator.build_fronts(combined_genes);
let mut fronts = self.evaluator.build_fronts(combined_genes);

// Check if there are no feasible individuals
if fronts.is_empty() {
return Err(MultiObjectiveAlgorithmError::NoFeasibleIndividuals);
}

// Select the new population
self.population = self.survivor.operate(&fronts, self.pop_size);
self.population = self.survivor.operate(&mut fronts, self.pop_size);
Ok(())
}

Expand Down
6 changes: 3 additions & 3 deletions src/algorithms/nsga2.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::define_multiobj_pyclass;
use crate::operators::selection::RankAndCrowdingSelection;
use crate::operators::survival::RankCrowdingSurvival;
use pyo3::prelude::*;

use crate::define_multiobj_pyclass;
use crate::helpers::functions::{
create_population_constraints_closure, create_population_fitness_closure,
};
use crate::helpers::parser::{
unwrap_crossover_operator, unwrap_duplicates_cleaner, unwrap_mutation_operator,
unwrap_sampling_operator,
};
use crate::operators::selection::RankAndCrowdingSelection;
use crate::operators::survival::RankCrowdingSurvival;

// Define the NSGA-II algorithm using the macro
define_multiobj_pyclass!(Nsga2, PyNsga2, "Nsga2");
Expand Down
121 changes: 121 additions & 0 deletions src/algorithms/rnsga2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use pyo3::prelude::*;

use crate::define_multiobj_pyclass;
use crate::helpers::functions::{
create_population_constraints_closure, create_population_fitness_closure,
};
use crate::helpers::parser::{
unwrap_crossover_operator, unwrap_duplicates_cleaner, unwrap_mutation_operator,
unwrap_sampling_operator,
};
use crate::operators::selection::{DiversityComparison, RankAndCrowdingSelection};
use crate::operators::survival::RankReferencePointsSurvival;

use numpy::{PyArray2, PyArrayMethods};

// Define the NSGA-II algorithm using the macro
define_multiobj_pyclass!(RNsga2, PyRNsga2, "RNsga2");

// Implement PyO3 methods
#[pymethods]
impl PyRNsga2 {
#[new]
#[pyo3(signature = (
reference_points,
sampler,
crossover,
mutation,
fitness_fn,
n_vars,
pop_size,
n_offsprings,
num_iterations,
epsilon = 0.001,
mutation_rate=0.1,
crossover_rate=0.9,
keep_infeasible=false,
verbose=true,
duplicates_cleaner=None,
constraints_fn=None,
lower_bound=None,
upper_bound=None
))]
pub fn py_new<'py>(
reference_points: &Bound<'py, PyArray2<f64>>,
sampler: PyObject,
crossover: PyObject,
mutation: PyObject,
fitness_fn: PyObject,
n_vars: usize,
pop_size: usize,
n_offsprings: usize,
num_iterations: usize,
epsilon: f64,
mutation_rate: f64,
crossover_rate: f64,
keep_infeasible: bool,
verbose: bool,
duplicates_cleaner: Option<PyObject>,
constraints_fn: Option<PyObject>,
// Optional lower bound for each gene.
lower_bound: Option<f64>,
// Optional upper bound for each gene.
upper_bound: Option<f64>,
) -> PyResult<Self> {
// Unwrap the genetic operators
let sampler_box = unwrap_sampling_operator(sampler)?;
let crossover_box = unwrap_crossover_operator(crossover)?;
let mutation_box = unwrap_mutation_operator(mutation)?;
let duplicates_box = if let Some(py_obj) = duplicates_cleaner {
Some(unwrap_duplicates_cleaner(py_obj)?)
} else {
None
};

// Build the MANDATORY population-level fitness closure
let fitness_closure = create_population_fitness_closure(fitness_fn)?;

// Build OPTIONAL population-level constraints closure
let constraints_closure = if let Some(py_obj) = constraints_fn {
Some(create_population_constraints_closure(py_obj)?)
} else {
None
};

// Convert PyArray2 to Array2
let reference_points_array = reference_points.to_owned_array();

// Create an instance of the selection/survival struct
let selector_box = Box::new(RankAndCrowdingSelection::new_with_comparison(
DiversityComparison::Minimize,
));
let survivor_box = Box::new(RankReferencePointsSurvival::new(
reference_points_array,
epsilon,
));

// Create the Rust struct
let rs_obj = RNsga2::new(
sampler_box,
crossover_box,
mutation_box,
selector_box,
survivor_box,
duplicates_box,
fitness_closure,
n_vars,
pop_size,
n_offsprings,
num_iterations,
mutation_rate,
crossover_rate,
keep_infeasible,
verbose,
constraints_closure,
lower_bound,
upper_bound,
)?;

Ok(Self { inner: rs_obj })
}
}
File renamed without changes.
5 changes: 5 additions & 0 deletions src/diversity_metrics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod crowding;
pub mod reference;

pub use crowding::crowding_distance;
pub use reference::{reference_points_rank_distance, weighted_distance_matrix};
Loading

0 comments on commit 20466d8

Please sign in to comment.