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

update to faer v0.21 #155

Merged
merged 15 commits into from
Jan 17, 2025
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ julia = ["sdp", "dep:libc", "dep:num-derive", "serde", "faer-sparse", "buildinf
python = ["sdp", "dep:libc", "dep:pyo3", "dep:num-derive", "serde", "faer-sparse", "buildinfo"]

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

# -------------------------------
# SDP configuration
Expand Down Expand Up @@ -96,11 +96,11 @@ optional = true
# -------------------------------

[dependencies.faer]
version = "0.20"
version = "0.21"
optional = true

[dependencies.faer-entity]
version = "0.20"
[dependencies.faer-traits]
version = "0.21"
optional = true


Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fn config_python_blas() {
feature = "sdp-mkl",
feature = "sdp-r"
)) {
println!("cargo:rustc-cfg=sdp_pyblas");
printinfo!("Python: compiling with python blas from scipy");
} else {
printinfo!("Python: compiling with local blas/lapack libraries");
Expand Down
2 changes: 1 addition & 1 deletion src/algebra/dense/blas/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cfg_if::cfg_if! {
// imports via scipy
use crate::python::pyblas::*;
}
else {
else {
// standard imports via blas-lapack-rs crates
extern crate blas_src;
extern crate lapack_src;
Expand Down
4 changes: 2 additions & 2 deletions src/algebra/floats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ cfg_if::cfg_if! {

cfg_if::cfg_if! {
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> {}
pub trait MaybeFaerFloatT : faer_traits::RealField {}
impl<T> MaybeFaerFloatT for T where T: faer_traits::RealField {}
}
else {
pub trait MaybeFaerFloatT {}
Expand Down
2 changes: 1 addition & 1 deletion src/julia/ClarabelRs/src/ClarabelRs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module ClarabelRs
global libpath
global librust

result = true
result = false
while !result
result = Libdl.dlclose(librust)
end
Expand Down
24 changes: 11 additions & 13 deletions src/julia/ClarabelRs/test/test_problem_depot.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
using Convex, Clarabel, ClarabelRs, Test

if false
#=
#tests everything
@testset "ClarabelRs" begin
Convex.ProblemDepot.run_tests([r""]; exclude=[r"mip"]) do p
solve!(p, Convex.MOI.OptimizerWithAttributes(
ClarabelRs.Optimizer, "verbose" => true,
"chordal_decomposition_compact" => true,
"chordal_decomposition_enable" => true,
"chordal_decomposition_merge_method" => :clique_graph,
"chordal_decomposition_complete_dual" => true,
))
end
@testset "ClarabelRs" begin
Convex.ProblemDepot.run_tests([r""]; exclude=[r"mip"]) do p
solve!(p, Convex.MOI.OptimizerWithAttributes(
ClarabelRs.Optimizer, "verbose" => true,
"chordal_decomposition_compact" => true,
"chordal_decomposition_enable" => true,
"chordal_decomposition_merge_method" => :clique_graph,
"chordal_decomposition_complete_dual" => true,
))
end

end
=#

@testset "ClarabelRs" begin
Convex.ProblemDepot.run_tests([r"sdp_quantum_relative"]; exclude=[r"mip",r"sdp_quantum_relative_entropy3_lowrank"]) do p
Expand Down
6 changes: 5 additions & 1 deletion src/python/impl_default_py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ pub struct PyDefaultSettings {
#[pyo3(get, set)]
pub min_terminate_step_length: f64,

// KKT settings incomplete
// KKT settings
#[pyo3(get, set)]
pub max_threads: u32,
#[pyo3(get, set)]
pub direct_kkt_solver: bool,
#[pyo3(get, set)]
Expand Down Expand Up @@ -312,6 +314,7 @@ impl PyDefaultSettings {
linesearch_backtrack_step: set.linesearch_backtrack_step,
min_switch_step_length: set.min_switch_step_length,
min_terminate_step_length: set.min_terminate_step_length,
max_threads: set.max_threads,
direct_kkt_solver: set.direct_kkt_solver,
direct_solve_method: set.direct_solve_method.clone(),
static_regularization_enable: set.static_regularization_enable,
Expand Down Expand Up @@ -360,6 +363,7 @@ impl PyDefaultSettings {
linesearch_backtrack_step: self.linesearch_backtrack_step,
min_switch_step_length: self.min_switch_step_length,
min_terminate_step_length: self.min_terminate_step_length,
max_threads: self.max_threads,
direct_kkt_solver: self.direct_kkt_solver,
direct_solve_method: self.direct_solve_method.clone(),
static_regularization_enable: self.static_regularization_enable,
Expand Down
4 changes: 4 additions & 0 deletions src/python/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ pub(crate) fn stdout() -> PythonStdout {
pub(crate) fn stderr() -> PythonStderr {
PythonStderr::new()
}

#[allow(unused_imports)]
pub(crate) use PythonStderr as Stderr;
pub(crate) use PythonStdout as Stdout;
91 changes: 58 additions & 33 deletions src/solver/core/kktsolvers/direct/quasidef/ldlsolvers/faer_ldl.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
#![allow(non_snake_case)]

use faer::dyn_stack::{MemBuffer, MemStack, StackReq};
// use faer::{
// dyn_stack::{GlobalPodBuffer, PodStack, StackReq},
// linalg::cholesky::ldlt::compute::LdltRegularization,
// sparse::{
// linalg::{amd::Control, cholesky::*, SupernodalThreshold},
// SparseColMatRef, SymbolicSparseColMatRef,
// },
// Conj, Parallelism, Side,
// };
use faer::{
dyn_stack::{GlobalPodBuffer, PodStack, StackReq},
linalg::cholesky::ldlt_diagonal::compute::LdltRegularization,
linalg::cholesky::ldlt::factor::{LdltParams, LdltRegularization},
sparse::{
linalg::{amd::Control, cholesky::*, SupernodalThreshold},
linalg::{
amd::Control,
cholesky::{
factorize_symbolic_cholesky, CholeskySymbolicParams, LdltRef, SymbolicCholesky,
SymmetricOrdering,
},
SupernodalThreshold,
},
SparseColMatRef, SymbolicSparseColMatRef,
},
Conj, Parallelism, Side,
Conj, MatMut, Par, Side, Spec,
};

use crate::algebra::*;
Expand Down Expand Up @@ -56,29 +72,39 @@ pub struct FaerDirectLDLSolver<T: FloatT> {

// regularizer parameters captured from settings
regularizer_params: FaerLDLRegularizerParams<T>,
ldlt_params: Spec<LdltParams, T>,

// symbolic + numeric cholesky data
symbolic_cholesky: SymbolicCholesky<usize>,
ld_vals: Vec<T>,

// workspace for faer factor/solve calls
work: GlobalPodBuffer,
work: MemBuffer,

parallelism: Parallelism<'static>,
parallelism: Par,
}

impl<T> FaerDirectLDLSolver<T>
where
T: FloatT,
{
pub fn nthreads_from_settings(setting: usize) -> usize {
faer::utils::thread::parallelism_degree(faer::Par::rayon(setting))
}

pub fn new(KKT: &CscMatrix<T>, Dsigns: &[i8], settings: &CoreSettings<T>) -> Self {
assert!(KKT.is_square(), "KKT matrix is not square");

// -----------------------------

// Rayon(0) here is equivalent to rayon::current_num_threads()
// PJG: this should be a user-settable option
let parallelism = Parallelism::Rayon(0);
// Par::rayon(0) here is equivalent to rayon::current_num_threads()
let parallelism = {
match settings.max_threads {
0 => Par::rayon(0),
1 => Par::Seq,
_ => Par::rayon(settings.max_threads as usize),
}
};

// manually compute an AMD ordering for the KKT matrix
// and permute it to match the ordering used in QDLDL
Expand Down Expand Up @@ -125,21 +151,22 @@ where
)
.unwrap();

let ld_vals = vec![T::zero(); symbolic_cholesky.len_values()];
let ld_vals = vec![T::zero(); symbolic_cholesky.len_val()];

let regularizer_params = FaerLDLRegularizerParams {
regularize: settings.dynamic_regularization_enable,
delta: settings.dynamic_regularization_delta,
eps: settings.dynamic_regularization_eps,
};

let ldlt_params: Spec<LdltParams, T> = Default::default();

// Required workspace for faer factor and solve
let req_factor = symbolic_cholesky
.factorize_numeric_ldlt_req::<f64>(true, parallelism)
.unwrap();
let req_solve = symbolic_cholesky.solve_in_place_req::<f64>(1).unwrap();
let req = StackReq::any_of([req_factor, req_solve]);
let work = GlobalPodBuffer::new(req);
let req_factor =
symbolic_cholesky.factorize_numeric_ldlt_scratch::<T>(parallelism, ldlt_params);
let req_solve = symbolic_cholesky.solve_in_place_scratch::<T>(1, parallelism); // 1 is the number of RHS
let req = StackReq::any_of(&[req_factor, req_solve]);
let work = MemBuffer::new(req);

let bperm = vec![T::zero(); perm_kkt.n];

Expand All @@ -151,6 +178,7 @@ where
perm_dsigns,
bperm,
regularizer_params,
ldlt_params,
symbolic_cholesky,
ld_vals,
work,
Expand All @@ -161,7 +189,7 @@ where

impl<T> DirectLDLSolver<T> for FaerDirectLDLSolver<T>
where
T: FloatT + faer::Entity + faer::SimpleEntity,
T: FloatT,
{
fn update_values(&mut self, index: &[usize], values: &[T]) {
// PJG: this is replicating the update_values function in qdldl
Expand Down Expand Up @@ -198,18 +226,14 @@ where
// NB: faer solves in place. Permute b to match the ordering used internally
permute(&mut self.bperm, b, &self.perm);

let rhs = faer::mat::from_column_major_slice_mut::<T, usize, usize>(
&mut self.bperm[0..],
b.len(),
1,
);
let rhs = MatMut::from_column_major_slice_mut(&mut self.bperm[0..], b.len(), 1);
let ldlt = LdltRef::new(&self.symbolic_cholesky, self.ld_vals.as_slice());

ldlt.solve_in_place_with_conj(
Conj::No,
rhs,
self.parallelism,
PodStack::new(&mut self.work),
MemStack::new(&mut self.work),
);

// bperm is now the solution, permute it back to the original ordering
Expand All @@ -230,16 +254,17 @@ where

let regularizer = self.regularizer_params.to_faer(&self.perm_dsigns);

self.symbolic_cholesky.factorize_numeric_ldlt(
self.ld_vals.as_mut_slice(),
a,
Side::Upper,
regularizer,
self.parallelism,
PodStack::new(&mut self.work),
);

true // assume success for experimental purposes
self.symbolic_cholesky
.factorize_numeric_ldlt(
self.ld_vals.as_mut_slice(),
a,
Side::Upper,
regularizer,
self.parallelism,
MemStack::new(&mut self.work),
self.ldlt_params,
)
.is_ok() // PJG: convert to bool for consistency with qdldl. Should really return Result here and elsewhere
}

fn required_matrix_shape() -> MatrixTriangle {
Expand Down
26 changes: 25 additions & 1 deletion src/solver/implementations/default/info_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,14 @@ fn _print_settings<T: FloatT>(settings: &DefaultSettings<T>) -> std::io::Result<
writeln!(out, "settings:")?;

if set.direct_kkt_solver {
writeln!(
write!(
out,
" linear algebra: direct / {}, precision: {} bit",
set.direct_solve_method,
_get_precision_string::<T>()
)?;
print_nthreads(&mut out, settings)?;
writeln!(out)?;
}

let time_lim_str = {
Expand Down Expand Up @@ -246,6 +248,28 @@ fn _print_settings<T: FloatT>(settings: &DefaultSettings<T>) -> std::io::Result<
std::io::Result::Ok(())
}

#[allow(unused_variables)] //out is unused if faer-sparse is not enabled
fn print_nthreads<T: FloatT>(
out: &mut stdio::Stdout,
settings: &DefaultSettings<T>,
) -> std::io::Result<()> {
match settings.direct_solve_method.as_str() {
#[cfg(feature = "faer-sparse")]
"faer" => {
let nthreads =
crate::solver::core::kktsolvers::direct::ldlsolvers::faer_ldl::FaerDirectLDLSolver::<
T,
>::nthreads_from_settings(settings.max_threads as usize);
if nthreads == 1 {
write!(out, " ({nthreads} thread) ")
} else {
write!(out, " ({nthreads} threads) ")
}
}
_ => std::io::Result::Ok(()),
}
}

#[cfg(feature = "sdp")]
fn print_chordal_decomposition<T: FloatT>(
chordal_info: &ChordalInfo<T>,
Expand Down
5 changes: 5 additions & 0 deletions src/solver/implementations/default/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ pub struct DefaultSettings<T: FloatT> {
#[builder(default = "(1e-4).as_T()")]
pub min_terminate_step_length: T,

///maximum solver threads for multithreaded KKT solvers
///choosing 0 lets the solver choose for itself
#[builder(default = "0")]
pub max_threads: u32,

///use a direct linear solver method (required true)
#[builder(default = "true")]
pub direct_kkt_solver: bool,
Expand Down
10 changes: 5 additions & 5 deletions src/stdio/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![allow(unused_imports)]
#[cfg(not(feature = "python"))]
#[allow(unused_imports)]
pub(crate) use std::io::{stderr, stdout, Stdout};

// configure python specific stdout and stdin streams
// when compiled with the python feature. This avoids
// problems when running within python notebooks etc.
#[cfg(feature = "python")]
pub(crate) use crate::python::io::{stderr, stdout};

#[cfg(not(feature = "python"))]
pub(crate) use std::io::{stderr, stdout};
#[allow(unused_imports)]
pub(crate) use crate::python::io::{stderr, stdout, Stdout};