Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .github/workflows/action-sanitizers-bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: bazelbuild/setup-bazelisk@v3
- uses: bazel-contrib/setup-bazel@0.19.1

- name: Bazel clean
run: bazel clean
Expand All @@ -40,7 +40,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: bazelbuild/setup-bazelisk@v3
- uses: bazel-contrib/setup-bazel@0.19.1

- name: Bazel clean
run: bazel clean
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: bazelbuild/setup-bazelisk@v3
- uses: bazel-contrib/setup-bazel@0.19.1

- name: Bazel clean
run: bazel clean
Expand All @@ -99,7 +99,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: bazelbuild/setup-bazelisk@v3
- uses: bazel-contrib/setup-bazel@0.19.1

- name: bazel clean
run: bazel clean
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v6

- uses: bazelbuild/setup-bazelisk@v3
- uses: bazel-contrib/setup-bazel@0.19.1

- name: bazel clean
run: bazel clean
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/test-interfaces.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: test-interfaces

# Builds HiGHS from this checkout and solves a small LP and MIP through
# downstream interfaces: PuLP and Pyomo (via highspy) and the Rust highs crate

on:
push:
branches: [ latest, master ]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install highspy from this checkout
run: python3 -m pip install .

- name: Install PuLP and Pyomo
run: python3 -m pip install pulp pyomo

- name: Solve with PuLP
run: python3 check/interfaces/pulp_smoke.py

- name: Solve with Pyomo
run: python3 check/interfaces/pyomo_smoke.py

rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Build and install HiGHS
run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/highs-install
cmake --build build --parallel
cmake --install build

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Solve with the highs crate
working-directory: check/interfaces/rust
env:
PKG_CONFIG_PATH: ${{ github.workspace }}/highs-install/lib/pkgconfig
LD_LIBRARY_PATH: ${{ github.workspace }}/highs-install/lib
run: HIGHS_EXPECTED_GITHASH=$(git rev-parse HEAD) cargo run --release
18 changes: 18 additions & 0 deletions check/interfaces/pulp_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Solve a small LP and MIP through PuLP's highspy-based HiGHS solver
import pulp

prob = pulp.LpProblem("smoke", pulp.LpMinimize)
x = prob.add_variable("x", 0, 4.5)
y = prob.add_variable("y", 1, 1)
z = prob.add_variable("z", 0, None, pulp.LpInteger)
prob += x + 4 * y + 9 * z
prob += x + y <= 5.5
prob += x + z >= 10
prob += -y + z <= 7.5

# LP relaxation, then the MIP
for mip, expected in ((False, 58.0), (True, 62.0)):
stats = prob.solve(pulp.HiGHS(msg=False, mip=mip))
print(f"mip={mip}: {stats.status_str} obj={pulp.value(prob.objective)}")
assert stats.status_str == "Optimal"
assert abs(pulp.value(prob.objective) - expected) < 1e-6
22 changes: 22 additions & 0 deletions check/interfaces/pyomo_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Solve a small LP and MIP through Pyomo's highspy-based appsi_highs solver
import pyomo.environ as pyo

m = pyo.ConcreteModel()
m.x = pyo.Var(bounds=(0, 4.5))
m.y = pyo.Var(bounds=(1, 1))
m.z = pyo.Var(within=pyo.NonNegativeIntegers)
m.obj = pyo.Objective(expr=m.x + 4 * m.y + 9 * m.z, sense=pyo.minimize)
m.c1 = pyo.Constraint(expr=m.x + m.y <= 5.5)
m.c2 = pyo.Constraint(expr=m.x + m.z >= 10)
m.c3 = pyo.Constraint(expr=-m.y + m.z <= 7.5)

solver = pyo.SolverFactory("appsi_highs")
assert solver.available(), "appsi_highs (highspy) is not available"

# LP relaxation, then the MIP
for relax, expected in ((True, 58.0), (False, 62.0)):
model = pyo.TransformationFactory("core.relax_integer_vars").create_using(m) if relax else m
res = solver.solve(model)
print(f"relax={relax}: {res.solver.termination_condition} obj={pyo.value(model.obj)}")
assert res.solver.termination_condition == pyo.TerminationCondition.optimal
assert abs(pyo.value(model.obj) - expected) < 1e-6
2 changes: 2 additions & 0 deletions check/interfaces/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Cargo.lock
target/
9 changes: 9 additions & 0 deletions check/interfaces/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "highs-rust-smoke"
version = "0.1.0"
edition = "2021"

[dependencies]
highs = "2"
# "discover" links the HiGHS found via pkg-config instead of the bundled copy
highs-sys = { version = "1", features = ["discover"] }
43 changes: 43 additions & 0 deletions check/interfaces/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Solve a small LP and MIP through the highs crate, linked against an
// installed HiGHS found by pkg-config (highs-sys "discover" feature)
use highs::{HighsModelStatus, RowProblem, Sense};
use std::ffi::CStr;

fn solve(integer_z: bool) -> f64 {
let mut pb = RowProblem::default();
let x = pb.add_column(1., 0.0..=4.5);
let y = pb.add_column(4., 1.0..=1.0);
let z = if integer_z {
pb.add_integer_column(9., 0.0..)
} else {
pb.add_column(9., 0.0..)
};
pb.add_row(..=5.5, &[(x, 1.), (y, 1.)]);
pb.add_row(10.0.., &[(x, 1.), (z, 1.)]);
pb.add_row(..=7.5, &[(y, -1.), (z, 1.)]);

let mut model = pb.optimise(Sense::Minimise);
model.make_quiet();
let solved = model.solve();
assert_eq!(solved.status(), HighsModelStatus::Optimal);
solved.objective_value()
}

fn main() {
let githash = unsafe { CStr::from_ptr(highs_sys::Highs_githash()) }
.to_str()
.unwrap();
println!("Linked HiGHS git hash: {githash}");
if let Ok(expected) = std::env::var("HIGHS_EXPECTED_GITHASH") {
assert!(
expected.starts_with(githash) || githash.starts_with(&expected),
"linked HiGHS {githash} is not the checkout {expected}"
);
}

let lp = solve(false);
let mip = solve(true);
println!("LP obj = {lp}, MIP obj = {mip}");
assert!((lp - 58.).abs() < 1e-6);
assert!((mip - 62.).abs() < 1e-6);
}
Loading