Skip to content

Commit

Permalink
Read/write problems to JSON files (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
goulart-paul authored May 28, 2024
1 parent 0e2c269 commit da5546c
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 11 deletions.
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 --exclude-files "src/python/*,src/julia/*"
cargo tarpaulin --out xml --features sdp-accelerate,serde --exclude-files "src/python/*,src/julia/*"
- name: Upload to codecov.io
uses: codecov/codecov-action@v4
Expand Down
21 changes: 19 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ itertools = "0.11"
# -------------------------------

[features]
default = ["serde"]

# enable reading / writing of problems from json files
serde = ["dep:serde", "dep:serde_json"]

# enables blas/lapack for SDP support, with blas/lapack src unspecified
# also enable packages required for chordal decomposition
Expand All @@ -41,14 +45,16 @@ sdp-openblas = ["sdp", "blas-src/openblas", "lapack-src/openblas"]
sdp-mkl = ["sdp", "blas-src/intel-mkl", "lapack-src/intel-mkl"]
sdp-r = ["sdp", "blas-src/r", "lapack-src/r"]


# build as the julia interface
julia = ["sdp", "dep:libc", "dep:num-derive", "dep:serde", "dep:serde_json"]
julia = ["sdp", "dep:libc", "dep:num-derive", "serde"]

# build as the python interface via maturin.
# NB: python builds use scipy shared libraries
# for blas/lapack, and should *not* explicitly
# enable a blas/lapack source package
python = ["sdp", "dep:libc", "dep:pyo3", "dep:num-derive"]
python = ["sdp", "dep:libc", "dep:pyo3", "dep:num-derive", "serde"]



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

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



# -------------------------------
# custom build profiles
Expand Down Expand Up @@ -162,3 +174,8 @@ crate-type = ["lib","cdylib"]
rustdoc-args = [ "--html-in-header", "./html/rustdocs-header.html" ]
features = ["sdp","sdp-mkl"]


# ------------------------------
# testing, benchmarking etc
[dev-dependencies]
tempfile = "3"
1 change: 1 addition & 0 deletions examples/data/hs35.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"P":{"m":3,"n":3,"colptr":[0,1,3,5],"rowval":[0,0,1,0,2],"nzval":[4.000000000000001,2.0000000000000004,4.000000000000001,2.0,2.0]},"q":[-8.0,-6.0,-4.0],"A":{"m":4,"n":3,"colptr":[0,2,4,6],"rowval":[0,1,0,2,0,3],"nzval":[1.0,-1.0,1.0,-1.0,2.0,-1.0]},"b":[3.0,0.0,0.0,0.0],"cones":[{"NonnegativeConeT":4}],"settings":{"max_iter":200,"time_limit":1.7976931348623157e308,"verbose":true,"max_step_fraction":0.99,"tol_gap_abs":1e-8,"tol_gap_rel":1e-8,"tol_feas":1e-8,"tol_infeas_abs":1e-8,"tol_infeas_rel":1e-8,"tol_ktratio":1e-6,"reduced_tol_gap_abs":0.00005,"reduced_tol_gap_rel":0.00005,"reduced_tol_feas":0.0001,"reduced_tol_infeas_abs":0.00005,"reduced_tol_infeas_rel":0.00005,"reduced_tol_ktratio":0.0001,"equilibrate_enable":true,"equilibrate_max_iter":10,"equilibrate_min_scaling":0.0001,"equilibrate_max_scaling":10000.0,"linesearch_backtrack_step":0.8,"min_switch_step_length":0.1,"min_terminate_step_length":0.0001,"direct_kkt_solver":true,"direct_solve_method":"qdldl","static_regularization_enable":true,"static_regularization_constant":1e-8,"static_regularization_proportional":4.930380657631324e-32,"dynamic_regularization_enable":true,"dynamic_regularization_eps":1e-13,"dynamic_regularization_delta":2e-7,"iterative_refinement_enable":true,"iterative_refinement_reltol":1e-13,"iterative_refinement_abstol":1e-12,"iterative_refinement_max_iter":10,"iterative_refinement_stop_ratio":5.0,"presolve_enable":true,"chordal_decomposition_enable":true,"chordal_decomposition_merge_method":"clique_graph","chordal_decomposition_compact":true,"chordal_decomposition_complete_dual":true}}
14 changes: 14 additions & 0 deletions examples/python/example_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import clarabel
import os

thisdir = os.path.dirname(__file__)
filename = os.path.join(thisdir, "../data/hs35.json")
print(filename)

# Load problem data from JSON file
solver = clarabel.read_from_file(filename)
solution = solver.solve()

# export problem data to JSON file
# filename = os.path.join(thisdir, "../data/out.json")
# solver.write_to_file(filename)
19 changes: 19 additions & 0 deletions examples/rust/example_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![allow(non_snake_case)]
use clarabel::solver::*;
use std::fs::File;

fn main() {
// HS35 is a small problem QP problem
// from the Maros-Meszaros test set

let filename = "./examples/data/hs35.json";
let mut file = File::open(filename).unwrap();
let mut solver = DefaultSolver::<f64>::read_from_file(&mut file).unwrap();
solver.solve();

// to write the back to a new file

// let outfile = "./examples/data/output.json";
// let mut file = File::create(outfile).unwrap();
// solver.write_to_file(&mut file).unwrap();
}
5 changes: 5 additions & 0 deletions src/algebra/csc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use crate::algebra::{Adjoint, MatrixShape, ShapedMatrix, SparseFormatError, Symm
use num_traits::Num;
use std::iter::{repeat, zip};

#[cfg(feature = "serde")]
use serde::{de::DeserializeOwned, Deserialize, Serialize};

/// Sparse matrix in standard Compressed Sparse Column (CSC) format
///
/// __Example usage__ : To construct the 3 x 3 matrix
Expand Down Expand Up @@ -38,6 +41,8 @@ use std::iter::{repeat, zip};
/// ```
///
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(bound = "T: Serialize + DeserializeOwned")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CscMatrix<T = f64> {
/// number of rows
Expand Down
4 changes: 2 additions & 2 deletions src/algebra/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ fn test_position_all() {
let idx = test.iter().position_all(|&v| *v > 2);
assert_eq!(idx, vec![0, 3, 4]);

let idx = test.iter().position_all(|&v| *v == 2);
assert_eq!(idx, vec![]);
let idx: Vec<usize> = test.iter().position_all(|&v| *v == 2);
assert_eq!(idx, Vec::<usize>::new());
}

#[test]
Expand Down
47 changes: 47 additions & 0 deletions src/julia/ClarabelRs/src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ function print_timers(solver::Solver)

end

function write_to_file(solver::Solver, filename::String)

solver_write_to_file_jlrs(solver::Solver, filename::String)

end

function read_from_file(filename::String)

solver_read_from_file_jlrs(filename::String)

end


# -------------------------------------
# Wrappers for rust-side interface
#--------------------------------------
Expand Down Expand Up @@ -77,6 +90,7 @@ function solver_solve_jlrs(solver::Solver)

end


function solver_get_info_jlrs(solver::Solver)

ccall(Libdl.dlsym(librust,:solver_get_info_jlrs),Clarabel.DefaultInfo{Float64},
Expand All @@ -92,6 +106,39 @@ function solver_print_timers_jlrs(solver::Solver)

end

function solver_write_to_file_jlrs(solver::Solver, filename::String)

status = ccall(Libdl.dlsym(librust,:solver_write_to_file_jlrs),Cint,
(
Ptr{Cvoid},
Cstring
),
solver.ptr,
filename,
)

if status != 0
error("Error writing to file $filename")
end

end

function solver_read_from_file_jlrs(filename::String)

ptr = ccall(Libdl.dlsym(librust,:solver_read_from_file_jlrs),Ptr{Cvoid},
(
Cstring,
),
filename,
)

if ptr == C_NULL
error("Error reading from file $filename")
end
return Solver{Float64}(ptr)

end

function solver_drop_jlrs(solver::Solver)
ccall(Libdl.dlsym(librust,:solver_drop_jlrs),Cvoid,
(Ptr{Cvoid},), solver.ptr)
Expand Down
71 changes: 69 additions & 2 deletions src/julia/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ use crate::solver::{
};
use num_traits::FromPrimitive;
use serde_json::*;
use std::{ffi::CStr, os::raw::c_void};
use std::fs::File;
use std::{
ffi::CStr,
os::raw::{c_char, c_int, c_void},
};

// functions for converting solver to / from c void pointers

Expand Down Expand Up @@ -54,7 +58,7 @@ pub(crate) extern "C" fn solver_new_jlrs(
A: &CscMatrixJLRS,
b: &VectorJLRS<f64>,
jlcones: &VectorJLRS<ConeDataJLRS>,
json_settings: *const std::os::raw::c_char,
json_settings: *const c_char,
) -> *mut c_void {
let P = P.to_CscMatrix();
let A = A.to_CscMatrix();
Expand Down Expand Up @@ -118,6 +122,69 @@ pub(crate) extern "C" fn solver_print_timers_jlrs(ptr: *mut c_void) {
std::mem::forget(solver);
}

// dump problem data to a file
// returns -1 on failure, 0 on success
#[no_mangle]
pub(crate) extern "C" fn solver_write_to_file_jlrs(
ptr: *mut c_void,
filename: *const std::os::raw::c_char,
) -> c_int {
let slice = unsafe { CStr::from_ptr(filename) };

let filename = match slice.to_str() {
Ok(s) => s,
Err(_) => {
return -1;
}
};

let mut file = match File::create(&filename) {
Ok(f) => f,
Err(_) => {
return -1;
}
};

let solver = from_ptr(ptr);
let status = solver.write_to_file(&mut file).is_ok();
let status = if status { 0 } else { -1 } as c_int;

// don't drop, since the memory is owned by Julia
std::mem::forget(solver);

return status;
}

// dump problem data to a file
// returns NULL on failure, pointer to solver on success
#[no_mangle]
pub(crate) extern "C" fn solver_read_from_file_jlrs(
filename: *const std::os::raw::c_char,
) -> *const c_void {
let slice = unsafe { CStr::from_ptr(filename) };

let filename = match slice.to_str() {
Ok(s) => s,
Err(_) => {
return std::ptr::null();
}
};

let mut file = match File::open(&filename) {
Ok(f) => f,
Err(_) => {
return std::ptr::null();
}
};

let solver = DefaultSolver::read_from_file(&mut file);

match solver {
Ok(solver) => to_ptr(Box::new(solver)),
Err(_) => std::ptr::null(),
}
}

// safely drop a solver object through its pointer.
// called by the Julia side finalizer when a solver
// is out of scope
Expand Down
13 changes: 13 additions & 0 deletions src/python/impl_default_py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,4 +448,17 @@ impl PyDefaultSolver {
None => println!("no timers enabled"),
};
}

fn write_to_file(&self, filename: &str) -> PyResult<()> {
let mut file = std::fs::File::create(filename)?;
self.inner.write_to_file(&mut file)?;
Ok(())
}
}

#[pyfunction(name = "read_from_file")]
pub fn read_from_file_py(filename: &str) -> PyResult<PyDefaultSolver> {
let mut file = std::fs::File::open(filename)?;
let solver = DefaultSolver::<f64>::read_from_file(&mut file)?;
Ok(PyDefaultSolver { inner: solver })
}
2 changes: 2 additions & 0 deletions src/python/module_py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ fn clarabel(_py: Python, m: &PyModule) -> PyResult<()> {
.unwrap();
m.add_function(wrap_pyfunction!(default_infinity_py, m)?)
.unwrap();
m.add_function(wrap_pyfunction!(read_from_file_py, m)?)
.unwrap();

// API Cone types
m.add_class::<PyZeroConeT>()?;
Expand Down
5 changes: 4 additions & 1 deletion src/solver/core/cones/supportedcone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ use super::*;

#[cfg(feature = "sdp")]
use crate::algebra::triangular_number;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

// ---------------------------------------------------
// We define some machinery here for enumerating the
// different cone types that can live in the composite cone
// ---------------------------------------------------

/// API type describing the type of a conic constraint.
///
///
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub enum SupportedConeT<T> {
/// The zero cone (used for equality constraints).
Expand Down
Loading

0 comments on commit da5546c

Please sign in to comment.