Skip to content

Commit

Permalink
add wasm support (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
Specy authored Nov 2, 2024
1 parent d4a4942 commit 32687d6
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 16 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
Expand Down
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions tests/readme_example.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Error>> {
variables! {
vars:
Expand Down
5 changes: 4 additions & 1 deletion tests/resource_allocation_problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -87,6 +89,7 @@ fn resource_allocation() {
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn using_a_vector() {
let products = vec![
Product {
Expand Down
5 changes: 4 additions & 1 deletion tests/shadow_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand All @@ -83,6 +85,7 @@ macro_rules! dual_test {
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn furniture_problem() {
$(
#[cfg(feature = $solver_feature)]
Expand Down
5 changes: 4 additions & 1 deletion tests/solver_scaling.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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);
Expand All @@ -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!();
Expand Down
4 changes: 3 additions & 1 deletion tests/static_solver.rs
Original file line number Diff line number Diff line change
@@ -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<S: StaticSolver>(solver: S) -> Result<(), Box<dyn std::error::Error>> {
Expand All @@ -19,6 +20,7 @@ fn generic_solve_example<S: StaticSolver>(solver: S) -> Result<(), Box<dyn std::
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn concrete() {
generic_solve_example(good_lp::default_solver).expect("solve")
}
6 changes: 6 additions & 0 deletions tests/variables.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use good_lp::{variables, Expression};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn complex_expression() {
let mut var1 = variables!();
let a = var1.add_variable();
Expand All @@ -14,6 +17,7 @@ fn complex_expression() {
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn large_sum() {
let mut var1 = variables!();
let var_vec: Vec<_> = (0..100_000).map(|_i| var1.add_variable()).collect();
Expand All @@ -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!();
Expand All @@ -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();
Expand Down

0 comments on commit 32687d6

Please sign in to comment.