src/tolerance.rs

Sun, 11 Dec 2022 23:25:53 +0200

author
Tuomo Valkonen <tuomov@iki.fi>
date
Sun, 11 Dec 2022 23:25:53 +0200
changeset 24
d29d1fcf5423
parent 0
eb3c7813b67a
permissions
-rw-r--r--

Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.

* Fixes the conditional gradient methods that were incorrectly solving
positivity constrained subproblems although the infinite-dimensional
versions do not include such constraints.

* Introduces the `ExperimentV2` struct that has `regularisation` in place
of `α`. The `Experiment` struct is now deprecated.

* The L^2-squared experiments were switch to be unconstrained, as the
Franke-Wolfe implementations do not support constraints. (This would
be easy to add for the “fully corrective” variant, but is not
immediate for the “relaxed” variant.)

//! Tolerance update schemes for subproblem solution quality
use serde::{Serialize, Deserialize};
use numeric_literals::replace_float_literals;
use crate::types::*;

/// Update style for optimality system solution tolerance
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[allow(dead_code)]
pub enum Tolerance<F : Float> {
    /// $ε_k = εθ^k$ for the `factor` $θ$ and initial tolerance $ε$.
    Exponential{ factor : F, initial : F },
    /// $ε_k = ε/(1+θk)^p$ for the `factor` $θ$, `exponent` $p$, and initial tolerance $ε$.
    Power{ factor : F, exponent : F, initial : F},
    /// $ε_k = εθ^{⌊k^p⌋}$ for the `factor` $θ$, initial tolerance $ε$, and exponent $p$.
    SlowExp{ factor : F, exponent : F, initial : F }
}

#[replace_float_literals(F::cast_from(literal))]
impl<F : Float> Default for Tolerance<F> {
    fn default() -> Self {
        Tolerance::Power {
            initial : 0.5,
            factor : 0.2,
            exponent : 1.4  // 1.5 works but is already slower in practise on our examples.
        }
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<F : Float> Tolerance<F> {
    /// Get the initial tolerance
    pub fn initial(&self) -> F {
        match self {
            &Tolerance::Exponential { initial, .. } => initial,
            &Tolerance::Power { initial, .. } => initial,
            &Tolerance::SlowExp { initial, .. } => initial,
        }
    }

    /// Get mutable reference to the initial tolerance
    fn initial_mut(&mut self) -> &mut F {
        match self {
            Tolerance::Exponential { ref mut initial, .. } => initial,
            Tolerance::Power { ref mut initial, .. } => initial,
            Tolerance::SlowExp { ref mut initial, .. } => initial,
        }
    }

    /// Set the initial tolerance
    pub fn set_initial(&mut self, set : F)  {
        *self.initial_mut() = set;
    }

    /// Update `tolerance` for iteration `iter`.
    /// `tolerance` may or may not be used depending on the specific
    /// update scheme.
    pub fn update(&self, tolerance : F, iter : usize) -> F {
        match self {
            &Tolerance::Exponential { factor, .. } => {
                tolerance * factor
            },
            &Tolerance::Power { factor, exponent, initial } => {
                initial /(1.0 + factor * F::cast_from(iter)).powf(exponent)
            },
            &Tolerance::SlowExp { factor, exponent, initial } => {
                // let m = (speed
                //          * factor.powi(-(iter as i32))
                //          * F::cast_from(iter).powf(-exponent)
                //         ).floor().as_();
                let m = F::cast_from(iter).powf(exponent).floor().as_();
                initial * factor.powi(m)
            },
        }
    }
}

impl<F: Float> std::ops::MulAssign<F> for Tolerance<F> {
    fn mul_assign(&mut self, factor : F) {
        *self.initial_mut() *= factor;
    }
}

impl<F: Float> std::ops::Mul<F> for Tolerance<F> {
    type Output = Tolerance<F>;
    fn mul(mut self, factor : F) -> Self::Output {
        *self.initial_mut() *= factor;
        self
    }
}

mercurial