optirustic 0.1.0

A multi-objective optimisation framework in Rust
docs.rs failed to build optirustic-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: optirustic-1.2.2

Optirustic

Optirustic is a framework written in Rust that provides algorithms and analysis tool to solve multi-objective problems using multi-objective evolutionary algorithms (MOEAs). It allows you to:

  • define minimisation and maximisation problems with custom objective functions;
  • define constraint and unconstrained variables (real, integer, boolean or choice);
  • use multi-thread to evaluate objectives and constraints on population with many individuals

At the moment, it comes with the NSGA2 and NSGA3 algorithms.

Installing Optirustic

Optirustic is available on crates.io. The recommended way to use it is to add a line into your Cargo.toml:

[dependencies]

optirustic = "*"

Example

Problem definition

In this example, we are going to solve the Schaffer’s problem with the NSGA2 algorithm. The problem aims to minimise the following 2 objectives:

  • f1(x) = x2
  • f2(x) = (x - 2)2

The problem has 1 variable (x) bounded to -1000 and 1000. The optional solution is expected to lie in the [0; 2] range.

Problem implementation

The problem is implemented below using the SCHProblem struct. When an algorithm runs, it first generates a set of potential solutions for the problem variables (in this case x). It then calculates the objectives (f1(x) and f2(x)) in the Evaluator trait exposed by this library.

#[derive(Debug)]
pub struct SCHProblem;

impl SCHProblem {
    /// Create the problem for the optimisation.
    pub fn create() -> Result<Problem, OError> {
        // define the objectives
        let objectives = vec![
            Objective::new("x^2", ObjectiveDirection::Minimise),
            Objective::new("(x-2)^2", ObjectiveDirection::Minimise),
        ];
        // define the variable
        let variables = vec![VariableType::Real(BoundedNumber::new(
            "x", -1000.0, 1000.0,
        )?)];
        // the problem has no constraints
        let constraints = None;

        let e = Box::new(SCHProblem);
        Problem::new(objectives, variables, constraints, e)
    }

    /// The first objective function
    pub fn f1(x: f64) -> f64 {
        x.powi(2)
    }

    /// The second objective function
    pub fn f2(x: f64) -> f64 {
        (x - 2.0).powi(2)
    }
}

// Implement the function to evaluate the objectives and constraints. The `evaluate`
// function below receives the individuals which contain the variables/solutions `x` 
// proposed by the algorithm. The function must returns the evaluated objectives and
// constraints in the `EvaluationResult` struct.
impl Evaluator for SCHProblem {
    fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
        let x = i.get_variable_value("x")?.as_real()?;
        let mut objectives = HashMap::new();
        objectives.insert("x^2".to_string(), SCHProblem::f1(x));
        objectives.insert("(x-2)^2".to_string(), SCHProblem::f2(x));
        Ok(EvaluationResult {
            constraints: None,
            objectives,
        })
    }
}

Setup and run the genetic algorithm

The code below set up the NSGA2 algorithm with 100 individuals and will stop when 250 population generations are reached.

...

fn main() -> Result<(), Box<dyn Error>> {
    // Setup the NSGA2 algorithm
    let args = NSGA2Arg {
        // use 100 individuals and stop the algorithm at 250 generations
        number_of_individuals: 100,
        stopping_condition: StoppingConditionType::MaxGeneration(MaxGeneration(250)),
        // use default options for the SBX and PM operators
        crossover_operator_options: None,
        mutation_operator_options: None,
        // no need to evaluate the objective in parallel
        parallel: Some(false),
        // do not export intermediate solutions
        export_history: None,
        resume_from_file: None,
        // to reproduce results
        seed: Some(10),
    };
    let mut algo = NSGA2::new(problem, args)?;

    // run the algorithm
    algo.run()?;

    // Export serialised results at last generation
    algo.save_to_json(&PathBuf::from("."), Some("SCH_2obj"))?;

    Ok(())
}

The full example is available in the examples folder of this repository and can be run using

cargo run --example nsga2_sch --release

This is the serialised data exported by the algorithm: SCH_2obj_NSGA2_gen250.json and these are the plotted solutions:

Additional examples

Additional examples showcasing this library's features are available in the examples folder of this repository.