From 950205d787c049b45149a927a0c7d2319410d404 Mon Sep 17 00:00:00 2001 From: Specy Date: Thu, 31 Oct 2024 11:42:04 +0100 Subject: [PATCH] add clarabel wasm feature flag --- .github/workflows/rust.yml | 9 +++++++++ Cargo.toml | 8 +++++++- README.md | 24 +++++++++++++----------- tests/readme_example.rs | 3 +++ tests/resource_allocation_problem.rs | 5 ++++- tests/shadow_price.rs | 5 ++++- tests/solver_scaling.rs | 5 ++++- tests/static_solver.rs | 4 +++- tests/variables.rs | 6 ++++++ 9 files changed, 53 insertions(+), 16 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 98b8889..2f072c9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -25,6 +25,9 @@ jobs: with: activate-environment: test allow-softlinks: true + - uses: actions/setup-node@v4 + with: + node-version: 22 - name: install deps run: | sudo apt-get install coinor-cbc coinor-libcbc-dev libgsl-dev @@ -38,6 +41,8 @@ jobs: curl -LO https://github.com/rust-or/good_lp/releases/download/cplex/cplex.bin chmod u+x cplex.bin ./cplex.bin -f ./.github/cplex/response.properties + # Install wasm-pack + cargo install wasm-pack - name: Build with all default solvers (no cplex) run: cargo build --features all_default_solvers --tests - name: Run tests with all default solvers (no cplex) @@ -57,4 +62,8 @@ jobs: run: cargo test --no-default-features --features cplex-rs - name: Run tests with Clarabel run: cargo test --no-default-features --features clarabel + - name: Run tests with Clarabel on WASM + run: wasm-pack test --node --no-default-features --features "clarabel,clarabel-wasm" + - name: Run tests with minilp on WASM + run: wasm-pack test --node --no-default-features --features "minilp" - run: cargo bench diff --git a/Cargo.toml b/Cargo.toml index 9af2e16..326eec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ default = ["coin_cbc", "singlethread-cbc"] singlethread-cbc = ["coin_cbc?/singlethread-cbc"] scip = ["russcip"] all_default_solvers = ["coin_cbc", "minilp", "lpsolve", "highs", "russcip", "lp-solvers", "clarabel"] # cplex-rs is not included because it is incompatible with lpsolve +clarabel-wasm = ["clarabel/wasm"] [dependencies] coin_cbc = { version = "0.1", optional = true, default-features = false } @@ -29,9 +30,14 @@ clarabel = { version = "0.9.0", optional = true, features = [] } fnv = "1.0.5" [dev-dependencies] -criterion = "0.5" float_eq = "1.0" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +criterion = "0.5" + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3.0" + [[bench]] name = "benchmark" harness = false diff --git a/README.md b/README.md index 2707330..3d37ea2 100644 --- a/README.md +++ b/README.md @@ -67,21 +67,22 @@ You can find a resource allocation problem example in This library offers an abstraction over multiple solvers. By default, it uses [cbc][cbc], but you can also activate other solvers using cargo features. -| solver feature name | integer variables | no C compiler\* | no additional libs\*\* | fast | -| ---------------------- | ----------------- | --------------- | ---------------------- | ---- | -| [`coin_cbc`][cbc] | ✅ | ✅ | ❌ | ✅ | -| [`highs`][highs] | ✅ | ❌ | ✅\+ | ✅ | -| [`lpsolve`][lpsolve] | ✅ | ❌ | ✅ | ❌ | -| [`minilp`][minilp] | ❌ | ✅ | ✅ | ❌ | -| [`lp-solvers`][lps] | ✅ | ✅ | ✅ | ❌ | -| [`scip`][scip] | ✅ | ✅ | ❌ | ✅ | -| [`cplex-rs`][cplex] | ✅ | ❌ | ✅\+\+ | ✅ | -| [`clarabel`][clarabel] | ❌ | ✅ | ✅ | ✅ | +| solver feature name | integer variables | no C compiler\* | no additional libs\*\* | fast | WASM | +| ---------------------- | ----------------- | --------------- | ---------------------- | ---- |---------| +| [`coin_cbc`][cbc] | ✅ | ✅ | ❌ | ✅ | ❌ | +| [`highs`][highs] | ✅ | ❌ | ✅\+ | ✅ | ❌ | +| [`lpsolve`][lpsolve] | ✅ | ❌ | ✅ | ❌ | ❌ | +| [`minilp`][minilp] | ❌ | ✅ | ✅ | ❌ | ✅ | +| [`lp-solvers`][lps] | ✅ | ✅ | ✅ | ❌ | ❌ | +| [`scip`][scip] | ✅ | ✅ | ❌ | ✅ | ❌ | +| [`cplex-rs`][cplex] | ✅ | ❌ | ✅\+\+ | ✅ | ❌ | +| [`clarabel`][clarabel] | ❌ | ✅ | ✅ | ✅ | ✅\+\+\+ | - \* no C compiler: builds with only cargo, without requiring you to install a C compiler - \*\* no additional libs: works without additional libraries at runtime, all the dependencies are statically linked - \+ highs itself is statically linked and does not require manual installation. However, on some systems, you may have to [install dependencies of highs itself](https://github.com/rust-or/good_lp/issues/29). - \+\+ the cplex_rs crate links statically to a local installation of the IBM ILOG CPLEX Optimizer. +- \+\+\+ to use clarabel for WASM targets, set the `clarabel-wasm` feature flag To use an alternative solver, put the following in your `Cargo.toml`: @@ -125,7 +126,7 @@ Minilp is written in pure rust, so you can use it without having to install a C or having to install any external library, but it is slower than other solvers. It performs very poorly when compiled in debug mode, so be sure to compile your code -in `--release` mode when solving large problems. +in `--release` mode when solving large problems. This solver can compile to WASM targets. ### [HiGHS][highs] @@ -201,6 +202,7 @@ linear programming solver written in Rust by the It does not support integer variables, but it is fast and easy to install. It does implement the [SolutionWithDual](https://docs.rs/good_lp/latest/good_lp/solvers/trait.SolutionWithDual.html) trait, which allows you to access the dual values of the constraints (the shadow prices). +If you want to use it with WASM targets, you must include the `clarabel-wasm` feature flag [clarabel]: https://github.com/oxfordcontrol/Clarabel.rs diff --git a/tests/readme_example.rs b/tests/readme_example.rs index d78f817..0a1d700 100644 --- a/tests/readme_example.rs +++ b/tests/readme_example.rs @@ -1,8 +1,11 @@ use std::error::Error; use good_lp::{constraint, default_solver, variables, Solution, SolverModel}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn main() -> Result<(), Box> { variables! { vars: diff --git a/tests/resource_allocation_problem.rs b/tests/resource_allocation_problem.rs index 6a8b948..1e483f7 100644 --- a/tests/resource_allocation_problem.rs +++ b/tests/resource_allocation_problem.rs @@ -12,7 +12,8 @@ use good_lp::variable::ProblemVariables; use good_lp::{default_solver, variable, variables, Expression, Solution, SolverModel, Variable}; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; struct Product { // amount of fuel producing 1 unit takes needed_fuel: f64, @@ -65,6 +66,7 @@ impl ResourceAllocationProblem { use float_eq::assert_float_eq; #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn resource_allocation() { let mut pb = ResourceAllocationProblem::new(5., 3.); let steel = pb.add(Product { @@ -87,6 +89,7 @@ fn resource_allocation() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn using_a_vector() { let products = vec![ Product { diff --git a/tests/shadow_price.rs b/tests/shadow_price.rs index 93d0b98..a777857 100644 --- a/tests/shadow_price.rs +++ b/tests/shadow_price.rs @@ -5,7 +5,8 @@ use good_lp::{ solvers::{DualValues, SolutionWithDual}, variable, variables, Solution, Solver, SolverModel, }; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; // Using a generic function here ensures the dual can be retrieved in a generic, // solver-independent manner. #[allow(dead_code)] @@ -75,6 +76,7 @@ where macro_rules! dual_test { ($([$solver_feature:literal, $solver:expr])*) => { #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn determine_shadow_prices() { $( #[cfg(feature = $solver_feature)] @@ -83,6 +85,7 @@ macro_rules! dual_test { } #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn furniture_problem() { $( #[cfg(feature = $solver_feature)] diff --git a/tests/solver_scaling.rs b/tests/solver_scaling.rs index b2f9256..878c0bd 100644 --- a/tests/solver_scaling.rs +++ b/tests/solver_scaling.rs @@ -1,6 +1,7 @@ use float_eq::assert_float_eq; use good_lp::{constraint, default_solver, variable, variables, Expression, Solution, SolverModel}; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; const BIG_NUM: usize = 1000; // <- Set this higher to test how good_lp and the solvers scale #[test] @@ -21,6 +22,7 @@ fn solve_large_problem() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn add_10_000_constraints() { let mut vars = variables!(); let v = vars.add_vector(variable(), 10_000); @@ -31,6 +33,7 @@ fn add_10_000_constraints() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn sum_binaries() { // See: https://github.com/rust-or/good_lp/issues/8 let mut vars = variables!(); diff --git a/tests/static_solver.rs b/tests/static_solver.rs index 422694a..764baab 100644 --- a/tests/static_solver.rs +++ b/tests/static_solver.rs @@ -1,5 +1,6 @@ use good_lp::{constraint, variables, Solution, SolverModel, StaticSolver}; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; // See: https://github.com/rust-or/good_lp/pull/5 fn generic_solve_example(solver: S) -> Result<(), Box> { @@ -19,6 +20,7 @@ fn generic_solve_example(solver: S) -> Result<(), Box = (0..100_000).map(|_i| var1.add_variable()).collect(); @@ -23,6 +27,7 @@ fn large_sum() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn complete() { let mut var1 = variables!(); let mut var2 = variables!(); @@ -38,6 +43,7 @@ fn complete() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn debug_format() { let mut vars = variables!(); let a = vars.add_variable();