Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projection on the epigraph of the squared Euclidean norm #334

Merged
merged 15 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Note: This is the main Changelog file for the Rust solver. The Changelog file fo

### Added

- Rust implementation of epigraph of squared Euclidean norm (constraint)
- Implementation of `AffineSpace`

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,16 @@ rpmalloc = { version = "0.2.0", features = [
[target.'cfg(not(target_env = "msvc"))'.dependencies]
jemallocator = { version = "0.5.0", optional = true }


# computation of roots of cubic equation needed for the projection on the
# epigraph of the squared Eucliean norm
alphaville marked this conversation as resolved.
Show resolved Hide resolved
roots = "0.0.8"

# Least squares solver
ndarray = { version = "0.15", features = ["approx"] }
modcholesky = "0.1.3"


# --------------------------------------------------------------------------
# F.E.A.T.U.R.E.S.
# --------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ build: false
#directly or perform other testing commands. Rust will automatically be placed in the PATH
# environment variable.
test_script:
- cargo add roots
- cargo add ndarray --features approx
- cargo add modcholesky
- cargo build
Expand Down
97 changes: 97 additions & 0 deletions src/constraints/epigraph_squared_norm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::matrix_operations;

use super::Constraint;

#[derive(Copy, Clone)]
/// 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 {}
}

Check warning on line 16 in src/constraints/epigraph_squared_norm.rs

View workflow job for this annotation

GitHub Actions / clippy

you should consider adding a `Default` implementation for `EpigraphSquaredNorm`

warning: you should consider adding a `Default` implementation for `EpigraphSquaredNorm` --> src/constraints/epigraph_squared_norm.rs:14:5 | 14 | / pub fn new() -> Self { 15 | | EpigraphSquaredNorm {} 16 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default = note: `#[warn(clippy::new_without_default)]` on by default help: try adding this | 10 + impl Default for EpigraphSquaredNorm { 11 + fn default() -> Self { 12 + Self::new() 13 + } 14 + } |
}

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);

Check warning on line 42 in src/constraints/epigraph_squared_norm.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> src/constraints/epigraph_squared_norm.rs:42:58 | 42 | let norm_z_sq = matrix_operations::norm2_squared(&z); | ^^ help: change this to: `z` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
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;
}
return;

Check warning on line 65 in src/constraints/epigraph_squared_norm.rs

View workflow job for this annotation

GitHub Actions / clippy

unneeded `return` statement

warning: unneeded `return` statement --> src/constraints/epigraph_squared_norm.rs:64:18 | 64 | } | __________________^ 65 | | return; | |______________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 64 - } 65 - return; 64 + } |
}
});

// 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 = zsol - p_z / dp_z;

Check warning on line 80 in src/constraints/epigraph_squared_norm.rs

View workflow job for this annotation

GitHub Actions / clippy

manual implementation of an assign operation

warning: manual implementation of an assign operation --> src/constraints/epigraph_squared_norm.rs:80:13 | 80 | zsol = zsol - p_z / dp_z; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `zsol -= p_z / dp_z` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern = note: `#[warn(clippy::assign_op_pattern)]` on by default
refinement_error = p_z.abs();
iter += 1;
}
right_root = zsol;

// Projection
for i in 0..nx {

Check warning on line 87 in src/constraints/epigraph_squared_norm.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is only used to index `x`

warning: the loop variable `i` is only used to index `x` --> src/constraints/epigraph_squared_norm.rs:87:18 | 87 | for i in 0..nx { | ^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop = note: `#[warn(clippy::needless_range_loop)]` on by default help: consider using an iterator | 87 | for <item> in x.iter_mut().take(nx) { | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
x[i] /= scaling;
}
x[nx] = right_root;
}

/// This is a convex set, so this function returns `True`
fn is_convex(&self) -> bool {
true
}
}
2 changes: 2 additions & 0 deletions src/constraints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod ball1;
mod ball2;
mod ballinf;
mod cartesian_product;
mod epigraph_squared_norm;
mod finite;
mod halfspace;
mod hyperplane;
Expand All @@ -28,6 +29,7 @@ pub use ball1::Ball1;
pub use ball2::Ball2;
pub use ballinf::BallInf;
pub use cartesian_product::CartesianProduct;
pub use epigraph_squared_norm::EpigraphSquaredNorm;
pub use finite::FiniteSet;
pub use halfspace::Halfspace;
pub use hyperplane::Hyperplane;
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/sphere2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl<'a> Constraint for Sphere2<'a> {
if let Some(center) = &self.center {
let norm_difference = crate::matrix_operations::norm2_squared_diff(x, center).sqrt();
if norm_difference <= epsilon {
x.copy_from_slice(&center);
x.copy_from_slice(center);
x[0] += self.radius;
return;
}
Expand Down
47 changes: 47 additions & 0 deletions src/constraints/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::matrix_operations;

use super::*;
use modcholesky::ModCholeskySE99;

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

unused import: `modcholesky::ModCholeskySE99`

Check warning on line 4 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

unused import: `modcholesky::ModCholeskySE99`
use rand;

#[test]
Expand Down Expand Up @@ -875,7 +877,52 @@
}

#[test]
fn t_epigraph_squared_norm_inside() {
let epi = EpigraphSquaredNorm::new();
let mut x = [1., 2., 10.];
let x_correct = x.clone();
epi.project(&mut x);
unit_test_utils::assert_nearly_equal_array(
&x_correct,
&x,
1e-12,
1e-14,
"wrong projection on epigraph of squared norm",
);
}

#[test]
fn t_epigraph_squared_norm() {
let epi = EpigraphSquaredNorm::new();
for i in 0..100 {
let t = 0.01 * i as f64;
let mut x = [1., 2., 3., t];
epi.project(&mut x);
let err = (matrix_operations::norm2_squared(&x[..3]) - x[3]).abs();
assert!(err < 1e-10, "wrong projection on epigraph of squared norm");
}
}

#[test]
fn t_epigraph_squared_norm_correctness() {
let epi = EpigraphSquaredNorm::new();
let mut x = [1., 2., 3., 4.];
let x_correct = [
0.560142228903570,
1.120284457807140,
1.680426686710711,
4.392630432414829,
];
epi.project(&mut x);
unit_test_utils::assert_nearly_equal_array(
&x_correct,
&x,
1e-12,
1e-14,
"wrong projection on epigraph of squared norm",
);
}
fn t_affine_space() {

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci (ubuntu-20.04)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

function `t_affine_space` is never used

Check warning on line 925 in src/constraints/tests.rs

View workflow job for this annotation

GitHub Actions / ci_macos (macos-11)

function `t_affine_space` is never used
let a = vec![
0.5, 0.1, 0.2, -0.3, -0.6, 0.3, 0., 0.5, 1.0, 0.1, -1.0, -0.4,
];
Expand Down
Loading