qopt 0.19.4

A simple optimization library.
Documentation
//! Enumerates optimization paradigms.

use std::fmt;

use rand::{
    random,
    thread_rng,
    seq::SliceRandom,
};

use maria_linalg::Vector;

use super::optimizer::Optimizer;

/// Provides for optimization paradigms.
pub enum Paradigm {
    SteepestDescent,
    Newton,
    Genetic,
    SimulatedAnnealing,
}

impl fmt::Display for Paradigm {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let p = match self {
            Self::SteepestDescent => "Steepest Descent",
            Self::Newton => "Newton's Method",
            Self::Genetic => "Genetic Algorithm",
            Self::SimulatedAnnealing => "Simulated Annealing",
        };

        write!(f, "{}", p)
    }
}

impl Paradigm {
    pub fn update<const C: usize, const D: usize, const K: usize>(
        &self,
        optimizer: &Optimizer<C, D, K>,
        iter: usize,
        continuous: [Vector<C>; K],
        discrete: [Vector<D>; K],
        permitted: &[f64],
    ) -> ([Vector<C>; K], [Vector<D>; K]) {
        match self {
            Self::SteepestDescent => Self::update_steepest_descent(optimizer, continuous, discrete),
            Self::Newton => Self::update_newton(optimizer, continuous, discrete),
            Self::Genetic => Self::update_genetic(optimizer, continuous, discrete, permitted),
            Self::SimulatedAnnealing => Self::update_simulated_annealing(optimizer, iter, continuous, discrete, permitted),
        }
    }

    /// Given an input population, update it according to steepest descent.
    fn update_steepest_descent<const C: usize, const D: usize, const K: usize>(
        optimizer: &Optimizer<C, D, K>,
        continuous: [Vector<C>; K],
        discrete: [Vector<D>; K],
    ) -> ([Vector<C>; K], [Vector<D>; K]) {
        let mut output = [Vector::zero(); K];

        for i in 0..K {
            let input = continuous[i];
            let d = discrete[i];
            let hessian = optimizer.hessian(input, d);
            let step = optimizer.step(input, d);

            if step.dot(hessian.mult(step)) < 0.0 {
                println!();
                println!("Non-convex region");
                let mut stepsize = 0.1;
                let mut i = 0;
                while optimizer.objective(input + step.scale(0.01), d) > optimizer.objective(input, d) {
                    i += 1;
                    println!("Line search iteration: {}", i);
                    stepsize *= 0.5;
                }
                println!("Step size: {:.8}", stepsize);
    
                output[i] = input + step.scale(stepsize)
            } else {
                output[i] = input + step.scale(step.dot(step) / hessian.mult(step).dot(step))
            }
        }

        (output, discrete)
    }

    /// Given an input population, update it according to Newton's method.
    fn update_newton<const C: usize, const D: usize, const K: usize>(
        optimizer: &Optimizer<C, D, K>,
        continuous: [Vector<C>; K],
        discrete: [Vector<D>; K],
    ) -> ([Vector<C>; K], [Vector<D>; K]) {
        let mut output = [Vector::zero(); K];
        
        for i in 0..K {
            let input = continuous[i];
            let d = discrete[i];
            let hessian = optimizer.hessian(input, d);
            let step = optimizer.step(input, d);

            if step.dot(hessian.mult(step)) < 0.0 {
                println!();
                println!("Non-convex region");
                let mut stepsize = 0.1;
                let mut i = 0;
                while optimizer.objective(input + step.scale(0.01), d) > optimizer.objective(input, d) {
                    i += 1;
                    println!("Line search iteration: {}", i);
                    stepsize *= 0.5;
                }
                println!("Step size: {:.8}", stepsize);
    
                output[i] = input + step.scale(stepsize)
            } else {
                output[i] = input + hessian.inverse().mult(optimizer.step(input, d))
            }
        }

        (output, discrete)
    }

    /// Given an input population, update it according to a genetic algorithm.
    fn update_genetic<const C: usize, const D: usize, const K: usize>(
        optimizer: &Optimizer<C, D, K>,
        continuous: [Vector<C>; K],
        discrete: [Vector<D>; K],
        permitted: &[f64],
    ) -> ([Vector<C>; K], [Vector<D>; K]) {
        let mut nextgen_continuous = [Vector::zero(); K];
        let mut nextgen_discrete = [Vector::zero(); K];
        
        let (sorted_continuous, sorted_discrete) = optimizer.sort(continuous, discrete);

        for i in 0..K {
            if i < K / 2 {
                // Carry over top half from previous generation
                nextgen_continuous[i] = sorted_continuous[i];
                nextgen_discrete[i] = sorted_discrete[i];
            } else {
                // Generate other half of population from original population
                let mut rng = thread_rng();
                
                // UPDATE CONTINUOUS
                if C > 0 {
                    let mother = continuous.choose(&mut rng).unwrap();
                    let father = continuous.choose(&mut rng).unwrap();
                    let child = Vector::<C>::child(mother, father, optimizer.stdev);
                    nextgen_continuous[i] = child;
                }

                // UPDATE DISCRETE
                if D > 0 {
                    let mother = discrete.choose(&mut rng).unwrap();
                    let father = discrete.choose(&mut rng).unwrap();
                    let child = Vector::<D>::child_discrete(mother, father, permitted);
                    nextgen_discrete[i] = child;
                }
            }
        }

        (nextgen_continuous, nextgen_discrete)
    }

    /// Given an input population, update it according to simulated annealing.
    fn update_simulated_annealing<const C: usize, const D: usize, const K: usize>(
        optimizer: &Optimizer<C, D, K>,
        iter: usize,
        continuous: [Vector<C>; K],
        discrete: [Vector<D>; K],
        permitted: &[f64],
    ) -> ([Vector<C>; K], [Vector<D>; K]) {
        let mut nextgen_continuous = [Vector::zero(); K];
        let mut nextgen_discrete = [Vector::zero(); K];
        let temp = optimizer.maxtemp / (iter as f64);
       
        for i in 0..K {
            // Generate a child, similar to as in the genetic algorithm
            let mut rng = thread_rng();

            // UPDATE CONTINUOUS
            if C > 0 {
                let mother = continuous.choose(&mut rng).unwrap();
                let father = continuous.choose(&mut rng).unwrap();
                let child = Vector::<C>::child(mother, father, optimizer.stdev);
                let obj = optimizer.objective(continuous[i], discrete[i]);
                let child_obj = optimizer.objective(child, discrete[i]);
                let p = if child_obj < obj {
                    1.0
                } else {
                    // This is non-positive because child_obj >= obj
                    let t = obj - child_obj;
                    (t / temp).exp()
                };

                // With probability `p`, replace this item with a randomly updated one
                if random::<f64>() < p {
                    nextgen_continuous[i] = child;
                }
            }

            // UPDATE DISCRETE
            if D > 0 {
                let mother = discrete.choose(&mut rng).unwrap();
                let father = discrete.choose(&mut rng).unwrap();
                let child = Vector::<D>::child_discrete(mother, father, permitted);

                let obj = optimizer.objective(continuous[i], discrete[i]);
                let child_obj = optimizer.objective(continuous[i], child);
                let p = if child_obj < obj {
                    1.0
                } else {
                    // This is non-positive because child_obj >= obj
                    let t = obj - child_obj;
                    (t / temp).exp()
                };

                // With probability `p`, replace this item with a randomly updated one
                if random::<f64>() < p {
                    nextgen_discrete[i] = child;
                }
            }
        }

        (nextgen_continuous, nextgen_discrete)
    }
}