Sun, 11 Dec 2022 23:25:53 +0200
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.)
0 | 1 | //! Tolerance update schemes for subproblem solution quality |
2 | use serde::{Serialize, Deserialize}; | |
3 | use numeric_literals::replace_float_literals; | |
4 | use crate::types::*; | |
5 | ||
6 | /// Update style for optimality system solution tolerance | |
7 | #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] | |
8 | #[allow(dead_code)] | |
9 | pub enum Tolerance<F : Float> { | |
10 | /// $ε_k = εθ^k$ for the `factor` $θ$ and initial tolerance $ε$. | |
11 | Exponential{ factor : F, initial : F }, | |
12 | /// $ε_k = ε/(1+θk)^p$ for the `factor` $θ$, `exponent` $p$, and initial tolerance $ε$. | |
13 | Power{ factor : F, exponent : F, initial : F}, | |
14 | /// $ε_k = εθ^{⌊k^p⌋}$ for the `factor` $θ$, initial tolerance $ε$, and exponent $p$. | |
15 | SlowExp{ factor : F, exponent : F, initial : F } | |
16 | } | |
17 | ||
18 | #[replace_float_literals(F::cast_from(literal))] | |
19 | impl<F : Float> Default for Tolerance<F> { | |
20 | fn default() -> Self { | |
21 | Tolerance::Power { | |
22 | initial : 0.5, | |
23 | factor : 0.2, | |
24 | exponent : 1.4 // 1.5 works but is already slower in practise on our examples. | |
25 | } | |
26 | } | |
27 | } | |
28 | ||
29 | #[replace_float_literals(F::cast_from(literal))] | |
30 | impl<F : Float> Tolerance<F> { | |
31 | /// Get the initial tolerance | |
32 | pub fn initial(&self) -> F { | |
33 | match self { | |
34 | &Tolerance::Exponential { initial, .. } => initial, | |
35 | &Tolerance::Power { initial, .. } => initial, | |
36 | &Tolerance::SlowExp { initial, .. } => initial, | |
37 | } | |
38 | } | |
39 | ||
40 | /// Get mutable reference to the initial tolerance | |
41 | fn initial_mut(&mut self) -> &mut F { | |
42 | match self { | |
43 | Tolerance::Exponential { ref mut initial, .. } => initial, | |
44 | Tolerance::Power { ref mut initial, .. } => initial, | |
45 | Tolerance::SlowExp { ref mut initial, .. } => initial, | |
46 | } | |
47 | } | |
48 | ||
49 | /// Set the initial tolerance | |
50 | pub fn set_initial(&mut self, set : F) { | |
51 | *self.initial_mut() = set; | |
52 | } | |
53 | ||
54 | /// Update `tolerance` for iteration `iter`. | |
55 | /// `tolerance` may or may not be used depending on the specific | |
56 | /// update scheme. | |
57 | pub fn update(&self, tolerance : F, iter : usize) -> F { | |
58 | match self { | |
59 | &Tolerance::Exponential { factor, .. } => { | |
60 | tolerance * factor | |
61 | }, | |
62 | &Tolerance::Power { factor, exponent, initial } => { | |
63 | initial /(1.0 + factor * F::cast_from(iter)).powf(exponent) | |
64 | }, | |
65 | &Tolerance::SlowExp { factor, exponent, initial } => { | |
66 | // let m = (speed | |
67 | // * factor.powi(-(iter as i32)) | |
68 | // * F::cast_from(iter).powf(-exponent) | |
69 | // ).floor().as_(); | |
70 | let m = F::cast_from(iter).powf(exponent).floor().as_(); | |
71 | initial * factor.powi(m) | |
72 | }, | |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | impl<F: Float> std::ops::MulAssign<F> for Tolerance<F> { | |
78 | fn mul_assign(&mut self, factor : F) { | |
79 | *self.initial_mut() *= factor; | |
80 | } | |
81 | } | |
82 | ||
83 | impl<F: Float> std::ops::Mul<F> for Tolerance<F> { | |
84 | type Output = Tolerance<F>; | |
85 | fn mul(mut self, factor : F) -> Self::Output { | |
86 | *self.initial_mut() *= factor; | |
87 | self | |
88 | } | |
89 | } |