1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use crate::matrix_operations;

use super::Constraint;

#[derive(Copy, Clone, Default)]
/// The epigraph of the squared Eucliden norm is a set of the form
/// $X = \\{x = (z, t) \in \mathbb{R}^{n}\times \mathbb{R} {}:{} \\|z\\|^2 \leq t \\}.$
pub struct EpigraphSquaredNorm {}

impl EpigraphSquaredNorm {
    /// Create a new instance of the epigraph of the squared norm.
    ///
    /// Note that you do not need to specify the dimension.
    pub fn new() -> Self {
        EpigraphSquaredNorm {}
    }
}

impl Constraint for EpigraphSquaredNorm {
    ///Project on the epigraph of the squared Euclidean norm.
    ///
    /// The projection is computed as detailed
    /// [here](https://mathematix.wordpress.com/2017/05/02/projection-on-the-epigraph-of-the-squared-euclidean-norm/).
    ///
    /// ## Arguments
    /// - `x`: The given vector $x$ is updated with the projection on the set
    ///
    /// ## Example
    ///
    /// ```rust
    /// use optimization_engine::constraints::*;
    ///
    /// let epi = EpigraphSquaredNorm::new();
    /// let mut x = [1., 2., 3., 4.];    
    /// epi.project(&mut x);
    /// ```
    fn project(&self, x: &mut [f64]) {
        let nx = x.len() - 1;
        assert!(nx > 0, "x must have a length of at least 2");
        let z: &[f64] = &x[..nx];
        let t: f64 = x[nx];
        let norm_z_sq = matrix_operations::norm2_squared(z);
        if norm_z_sq <= t {
            return;
        }

        let theta = 1. - 2. * t;
        let a3 = 4.;
        let a2 = 4. * theta;
        let a1 = theta * theta;
        let a0 = -norm_z_sq;

        let cubic_poly_roots = roots::find_roots_cubic(a3, a2, a1, a0);
        let mut right_root = f64::NAN;
        let mut scaling = f64::NAN;

        // Find right root
        cubic_poly_roots.as_ref().iter().for_each(|ri| {
            if *ri > 0. {
                let denom = 1. + 2. * (*ri - t);
                if ((norm_z_sq / (denom * denom)) - *ri).abs() < 1e-6 {
                    right_root = *ri;
                    scaling = denom;
                }
            }
        });

        // Refinement of root with Newton-Raphson
        let mut refinement_error = 1.;
        let newton_max_iters: usize = 5;
        let newton_eps = 1e-14;
        let mut zsol = right_root;
        let mut iter = 0;
        while refinement_error > newton_eps && iter < newton_max_iters {
            let zsol_sq = zsol * zsol;
            let zsol_cb = zsol_sq * zsol;
            let p_z = a3 * zsol_cb + a2 * zsol_sq + a1 * zsol + a0;
            let dp_z = 3. * a3 * zsol_sq + 2. * a2 * zsol + a1;
            zsol -= p_z / dp_z;
            refinement_error = p_z.abs();
            iter += 1;
        }
        right_root = zsol;

        // Projection
        for xi in x.iter_mut().take(nx) {
            *xi /= scaling;
        }
        x[nx] = right_root;
    }

    /// This is a convex set, so this function returns `True`
    fn is_convex(&self) -> bool {
        true
    }
}