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

supernodal LDL wrapper #112

Merged
merged 28 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ee62cc1
ldl wrappers for faer.rs
goulart-paul May 23, 2024
43e9f52
update tmp tagged versions
goulart-paul May 23, 2024
b5b787a
homebrew f/b faer solve
goulart-paul May 24, 2024
86fa111
minor refactor
goulart-paul May 25, 2024
5fbe92e
stability and speed improvements
goulart-paul May 27, 2024
9d38f91
test fix
goulart-paul May 27, 2024
673524c
update deps and tests
goulart-paul May 27, 2024
82fbf9c
no solve in equil unit test
goulart-paul May 27, 2024
8e226de
add feature to codecov generation
goulart-paul May 27, 2024
ad817e8
add tests for codecov
goulart-paul May 27, 2024
18f48f5
extend infnorm unit test
goulart-paul May 27, 2024
f6264e1
ldl wrappers for faer.rs
goulart-paul May 23, 2024
4e9caab
update tmp tagged versions
goulart-paul May 23, 2024
ed53753
homebrew f/b faer solve
goulart-paul May 24, 2024
5778280
minor refactor
goulart-paul May 25, 2024
68ba718
stability and speed improvements
goulart-paul May 27, 2024
18cbe93
test fix
goulart-paul May 27, 2024
5be8281
update deps and tests
goulart-paul May 27, 2024
ef5bbfa
no solve in equil unit test
goulart-paul May 27, 2024
220ed2d
add feature to codecov generation
goulart-paul May 27, 2024
295111f
add tests for codecov
goulart-paul May 27, 2024
4cea1e8
extend infnorm unit test
goulart-paul May 27, 2024
2eae401
Merge branch 'pg/v0.8-supernodal' of https://github.com/oxfordcontrol…
goulart-paul May 27, 2024
d0bf29e
revert test
goulart-paul May 27, 2024
4b95f6b
revert test
goulart-paul May 27, 2024
feaf52f
remove tmp comments
goulart-paul May 28, 2024
b4fc97c
comments
goulart-paul May 28, 2024
654c60e
Merge branch 'develop' into pg/v0.8-supernodal
goulart-paul May 28, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

- name: Generate code coverage
run: |
cargo tarpaulin --out xml --features sdp-accelerate,serde --exclude-files "src/python/*,src/julia/*"
cargo tarpaulin --out xml --features sdp-accelerate,serde,faer-sparse --exclude-files "src/python/*,src/julia/*"

- name: Upload to codecov.io
uses: codecov/codecov-action@v4
Expand Down
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ julia = ["sdp", "dep:libc", "dep:num-derive", "serde"]
python = ["sdp", "dep:libc", "dep:pyo3", "dep:num-derive", "serde"]


#compile with faer supernodal solver option
faer-sparse = ["dep:faer", "dep:faer-entity"]

# -------------------------------
# SDP configuration
Expand All @@ -81,6 +83,14 @@ optional = true
version = "2.2"
optional = true

[dependencies.faer]
version = "0.19"
optional = true

[dependencies.faer-entity]
version = "0.19"
optional = true


# -------------------------------
# examples
Expand Down Expand Up @@ -115,13 +125,19 @@ name = "sdp"
path = "examples/rust/example_sdp.rs"
required-features = ["sdp"]

[[example]]
name = "box_faer"
path = "examples/rust/example_box_faer.rs"
required-features = ["faer-sparse"]

[[example]]
name = "json"
path = "examples/rust/example_json.rs"
required-features = ["serde"]




# -------------------------------
# custom build profiles
# -------------------------------
Expand Down
2 changes: 1 addition & 1 deletion examples/rust/example_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clarabel::algebra::*;
use clarabel::solver::*;

fn problem_data() -> (CscMatrix<f64>, Vec<f64>, CscMatrix<f64>, Vec<f64>) {
let n = 20000;
let n = 2000000;

let P = CscMatrix::identity(n);

Expand Down
39 changes: 39 additions & 0 deletions examples/rust/example_box_faer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#![allow(non_snake_case)]
use clarabel::algebra::*;
use clarabel::solver::*;

fn problem_data() -> (CscMatrix<f64>, Vec<f64>, CscMatrix<f64>, Vec<f64>) {
let n = 2000000;

let P = CscMatrix::identity(n);

// construct A = [I; -I]
let I1 = CscMatrix::<f64>::identity(n);
let mut I2 = CscMatrix::<f64>::identity(n);
I2.negate();

let A = CscMatrix::vcat(&I1, &I2);

let q = vec![1.; n];
let b = vec![1.; 2 * n];

(P, q, A, b)
}

fn main() {
let (P, q, A, b) = problem_data();

let cones = [NonnegativeConeT(b.len())];

let settings = DefaultSettingsBuilder::default()
.direct_solve_method("faer".to_owned())
.equilibrate_enable(true)
.max_iter(50)
.verbose(true)
.build()
.unwrap();

let mut solver = DefaultSolver::new(&P, &q, &A, &b, &cones, settings);

solver.solve();
}
67 changes: 32 additions & 35 deletions src/algebra/floats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,53 +40,50 @@ impl<T> CoreFloatT for T where
{
}

// additional traits that add bounds to CoreFloatT
// when optional dependencies are enabled

// if "sdp" is enabled, we must add an additional trait
// trait bound to restrict compilation for f32/f64 types
// since there is no BLAS support otherwise

// Define the documentation string as a macro so that FloatT gets documentation
// regardless of whether the "sdp" feature is enabled.
macro_rules! floatT_doc_header {
() => {
r#"Main trait for floating point types used in the Clarabel solver."#
};
}
macro_rules! floatT_doc_long {
() => {
r#"All floating point calculations in Clarabel are represented internally on values
implementing the `FloatT` trait, with implementations provided only for f32 and f64
native types when compiled with BLAS/LAPACK support for SDPs. If SDP support is not
enabled then it should be possible to compile Clarabel to support any any other
floating point type provided that it satisfies the trait bounds of `CoreFloatT`.
\
\
`FloatT` relies on [`num_traits`](num_traits) for most of its constituent trait bounds."#
};
}
cfg_if::cfg_if! {
if #[cfg(not(feature="sdp"))] {
#[doc = floatT_doc_header!()]
///
#[doc = floatT_doc_long!()]
pub trait FloatT: CoreFloatT {}
} else{
#[doc = floatT_doc_header!()]
///
#[doc = floatT_doc_long!()]
///
/// The trait bound `BlasFloatT` is only enforced when compiling with the `sdp` feature.
pub trait FloatT: CoreFloatT + BlasFloatT {}
if #[cfg(feature="sdp")] {
pub trait MaybeBlasFloatT : BlasFloatT {}
impl<T> MaybeBlasFloatT for T where T: BlasFloatT {}
}
else {
pub trait MaybeBlasFloatT {}
impl<T> MaybeBlasFloatT for T {}
}
}

// if "faer" is enabled, we must add an additional bound
// to restrict compilation to Reals implementing SimpleEntity

cfg_if::cfg_if! {
if #[cfg(feature="sdp")] {
impl<T> FloatT for T where T: CoreFloatT + BlasFloatT {}
} else{
impl<T> FloatT for T where T: CoreFloatT {}
if #[cfg(feature="faer-sparse")] {
pub trait MaybeFaerFloatT : faer::SimpleEntity + faer::ComplexField<Real=Self> {}
impl<T> MaybeFaerFloatT for T where T: faer::SimpleEntity + faer::ComplexField<Real=Self> {}
}
else {
pub trait MaybeFaerFloatT {}
impl<T> MaybeFaerFloatT for T {}
}
}

/// Main trait for floating point types used in the Clarabel solver.
///
/// All floating point calculations in Clarabel are represented internally on values
/// implementing the `FloatT` trait, with implementations provided only for f32 and f64
/// native types when compiled with BLAS/LAPACK support for SDPs. If SDP support is not
/// enabled then it should be possible to compile Clarabel to support any any other
/// floating point type provided that it satisfies the trait bounds of `CoreFloatT`.
///
/// `FloatT` relies on [`num_traits`](num_traits) for most of its constituent trait bounds.
pub trait FloatT: CoreFloatT + MaybeBlasFloatT + MaybeFaerFloatT {}
impl<T> FloatT for T where T: CoreFloatT + MaybeBlasFloatT + MaybeFaerFloatT {}

/// Trait for convering Rust primitives to [`FloatT`](crate::algebra::FloatT)
///
/// This convenience trait is implemented on f32/64 and u32/64. This trait
Expand Down
3 changes: 3 additions & 0 deletions src/algebra/tests/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ fn test_norm_scaled() {
fn test_norm_inf() {
let x = [-3., 4., -12.];
assert_eq!(x.norm_inf(), 12.);

let x = [-3., f64::NAN, -12.];
assert!(x.norm_inf().is_nan());
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions src/algebra/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
// which serves as a vectorized version of the std::iter::position
// returning indices of *all* elements satisfying a predicate

use crate::qdldl;
use num_traits::Num;
use std::cmp::Ordering;
use std::iter::zip;

#[cfg_attr(not(sdp), allow(dead_code))]
pub(crate) trait PositionAll<T>: Iterator<Item = T> {
Expand All @@ -34,12 +34,12 @@ where

// permutation and inverse permutation
pub(crate) fn permute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
zip(p, x).for_each(|(p, x)| *x = b[*p]);
qdldl::permute(x, b, p);
}

#[allow(dead_code)]
pub(crate) fn ipermute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
zip(p, b).for_each(|(p, b)| x[*p] = *b);
qdldl::ipermute(x, b, p);
}

// Construct an inverse permutation from a permutation
Expand Down
4 changes: 2 additions & 2 deletions src/algebra/vecmath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ impl<T: FloatT> VectorMath<T> for [T] {
// Returns infinity norm
fn norm_inf(&self) -> T {
let mut out = T::zero();
for v in self.iter().map(|v| v.abs()) {
for &v in self {
if v.is_nan() {
return T::nan();
}
out = if v > out { v } else { out };
out = T::max(out, v.abs());
}
out
}
Expand Down
68 changes: 51 additions & 17 deletions src/qdldl/qdldl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ where

// permute b
let tmp = &mut self.workspace.fwork;
_permute(tmp, b, &self.perm);
permute(tmp, b, &self.perm);

//solve in place with tmp as permuted RHS
_solve(
Expand All @@ -113,7 +113,7 @@ where
);

// inverse permutation to put unpermuted soln in b
_ipermute(b, tmp, &self.perm);
ipermute(b, tmp, &self.perm);
}

pub fn update_values(&mut self, indices: &[usize], values: &[T]) {
Expand Down Expand Up @@ -195,20 +195,20 @@ fn _qdldl_new<T: FloatT>(
iperm = _invperm(&_perm)?;
perm = _perm;
} else {
(perm, iperm) = _get_amd_ordering(Ain, opts.amd_dense_scale);
(perm, iperm) = get_amd_ordering(Ain, opts.amd_dense_scale);
}

//permute to (another) upper triangular matrix and store the
//index mapping the input's entries to the permutation's entries
let (A, AtoPAPt) = _permute_symmetric(Ain, &iperm);
let (A, AtoPAPt) = permute_symmetric(Ain, &iperm);

// handle the (possibly permuted) vector of
// diagonal D signs if one was specified. Otherwise
// otherwise all signs are positive
let mut Dsigns = vec![1_i8; n];
if let Some(ds) = opts.Dsigns {
Dsigns = vec![1_i8; n];
_permute(&mut Dsigns, &ds, &perm);
permute(&mut Dsigns, &ds, &perm);
}

// allocate workspace
Expand Down Expand Up @@ -452,8 +452,8 @@ fn _factor_inner<T: FloatT>(
Lp[0] = 0;
let mut acc = 0;
for (Lp, Lnz) in zip(&mut Lp[1..], Lnz) {
*Lp = acc + Lnz;
acc = *Lp;
acc += Lnz;
*Lp = acc;
}

// set all y_idx to be 'unused' initially
Expand Down Expand Up @@ -681,15 +681,38 @@ fn _ltsolve_unsafe<T: FloatT>(Lp: &[usize], Li: &[usize], Lx: &[T], x: &mut [T])
}
}

// Solves D(L+I)'x = b, with x replacing b. Unchecked version.
fn _dltsolve_unsafe<T: FloatT>(Lp: &[usize], Li: &[usize], Lx: &[T], Dinv: &[T], x: &mut [T]) {
unsafe {
for i in (0..x.len()).rev() {
let mut s = T::zero();
let f = *Lp.get_unchecked(i);
let l = *Lp.get_unchecked(i + 1);
for (&Lxj, &Lij) in zip(&Lx[f..l], &Li[f..l]) {
s += Lxj * (*x.get_unchecked(Lij));
}

let xi = x.get_unchecked_mut(i);
*xi *= *Dinv.get_unchecked(i);
*xi -= s;
}
}
}

// Solves Ax = b where A has given LDL factors, with x replacing b
fn _solve<T: FloatT>(Lp: &[usize], Li: &[usize], Lx: &[T], Dinv: &[T], b: &mut [T]) {
// We call the `unsafe`d version of the forward and backward substitution
// functions here, since the matrix data should be well posed and x of
// compatible dimensions. For super safety or debugging purposes, there
// are also `safe` versions implemented above.
_lsolve_unsafe(Lp, Li, Lx, b);
zip(b.iter_mut(), Dinv).for_each(|(b, d)| *b *= *d);
_ltsolve_unsafe(Lp, Li, Lx, b);

// combined D and L^T solve in one pass
_dltsolve_unsafe(Lp, Li, Lx, Dinv, b);

// in separate passes for reference
//zip(b.iter_mut(), Dinv).for_each(|(b, d)| *b *= *d);
//_ltsolve_unsafe(Lp, Li, Lx, b);
}

// Construct an inverse permutation from a permutation
Expand All @@ -706,21 +729,32 @@ fn _invperm(p: &[usize]) -> Result<Vec<usize>, QDLDLError> {
Ok(b)
}

// internal permutation and inverse permutation
// functions that require no memory allocations
// permutation and inverse permutation
// functions that require no allocation
// p must be a valid permutation vector
// in both cases for safety

fn _permute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
zip(p, x).for_each(|(p, x)| *x = b[*p]);
pub(crate) fn permute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
debug_assert!(*p.iter().max().unwrap_or(&0) < x.len());
unsafe {
zip(p, x).for_each(|(p, x)| *x = *b.get_unchecked(*p));
}
}

fn _ipermute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
zip(p, b).for_each(|(p, b)| x[*p] = *b);
pub(crate) fn ipermute<T: Copy>(x: &mut [T], b: &[T], p: &[usize]) {
debug_assert!(*p.iter().max().unwrap_or(&0) < x.len());
unsafe {
zip(p, b).for_each(|(p, b)| *x.get_unchecked_mut(*p) = *b);
}
}

// Given a sparse symmetric matrix `A` (with only upper triangular entries), return
// permuted sparse symmetric matrix `P` (also only upper triangular) given the
// inverse permutation vector `iperm`."
fn _permute_symmetric<T: FloatT>(A: &CscMatrix<T>, iperm: &[usize]) -> (CscMatrix<T>, Vec<usize>) {
pub(crate) fn permute_symmetric<T: FloatT>(
A: &CscMatrix<T>,
iperm: &[usize],
) -> (CscMatrix<T>, Vec<usize>) {
// perform a number of argument checks
let (_m, n) = A.size();
let mut P = CscMatrix::<T>::spalloc((n, n), A.nnz());
Expand Down Expand Up @@ -816,7 +850,7 @@ fn _permute_symmetric_inner<T: FloatT>(
}
}

fn _get_amd_ordering<T: FloatT>(
pub(crate) fn get_amd_ordering<T: FloatT>(
A: &CscMatrix<T>,
amd_dense_scale: f64,
) -> (Vec<usize>, Vec<usize>) {
Expand Down
Loading