Skip to content

WASM build of the HiGHS solver with a simple TypeScript interface.

License

Notifications You must be signed in to change notification settings

Bubblyworld/highs-ts

Repository files navigation

highs-ts

npm version CI License

WASM build of the HiGHS solver with TypeScript bindings. Supports linear and mixed-integer linear programming, and runs in both node and browser environments with zero runtime dependencies.

Basic Usage

The HiGHS class provides direct access to the solver. Problems can be loaded from strings in CPLEX LP or MPS format.

import { HiGHS } from '@bubblyworld/highs-ts';

const lp = `
Maximize
  obj: x + 2 y
Subject To
  c1: x + y <= 10
  c2: x <= 5
Bounds
  0 <= x <= 10
  0 <= y <= 10
End
`;

const highs = await HiGHS.create();
await highs.parse(lp, 'lp');
const result = await highs.solve();

console.log(result.status);     // @expect: result.status === 'optimal'
console.log(result.objective);  // @expect: result.objective === 20
console.log(result.solution);   // Map { 'x' => 0, 'y' => 10 }

highs.free();

High-Level API

The Model class provides a builder interface for defining problems programmatically.

import { Model, sum } from '@bubblyworld/highs-ts';

const model = new Model();

// Variables: numVar (continuous), intVar (integer), boolVar (binary)
const x = model.numVar(0, 10, 'x');
const y = model.numVar(0, 10, 'y');

// Constraints with expressions
model.addConstraint(x.plus(y).leq(10), 'c1');
model.addConstraint(x.leq(5), 'c2');

// Objective
model.maximize(x.plus(y.times(2)));

const solution = await model.solve();
console.log(solution.status);      // @expect: solution.status === 'optimal'
console.log(solution.objective);   // @expect: solution.objective === 20
console.log(solution.getValue(x)); // 0
console.log(solution.getValue(y)); // 10

Expressions support chained arithmetic: plus(), minus(), times(), neg(). Constraints are created with leq(), geq(), and eq(). The sum() helper combines multiple terms. You can mix and match different kinds of variables and constraints as you like:

const model = new Model();
const x_a = model.intVar(0, 10, 'x_a');
const x_b = model.intVar(0, 8, 'x_b');
const y_a = model.boolVar('y_a');
const y_b = model.boolVar('y_b');

model.addConstraint(x_a.minus(y_a.times(10)).leq(0), 'cap_a');
model.addConstraint(x_b.minus(y_b.times(8)).leq(0), 'cap_b');
model.addConstraint(x_a.plus(x_b).leq(12), 'material');

model.maximize(
  x_a.times(10)
    .plus(x_b.times(12))
    .minus(y_a.times(50))
    .minus(y_b.times(60))
);

const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 50

Modelling Primitives

The Model class includes a number of higher-level primitives for common MILP reformulations, built on top of the basic variable and constraint API. Logical operations (and, or, not, xor), cardinality constraints (addAtMost, addAtLeast, addExactly), indicators, abs, max, min, and product are all available — see the source for full details. The rest of this section covers some of the less obvious primitives.

Semi-Continuous Variables

A semi-continuous variable is either exactly zero or lies within a range [lb, ub], with no values in between. This is useful for modelling minimum-batch-size or on/off decisions where partial activation doesn't make sense.

const model = new Model();
const batch = model.semiContVar(100, 500, 'batch');

model.maximize(batch);
const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 500

Integer Division

divMod(expr, d) returns quotient and remainder variables for dividing a non-negative integer expression by a positive integer constant.

const model = new Model();
const minutes = model.intVar(0, 1440, 'minutes');
const { quotient, remainder } = model.divMod(minutes, 60);

model.addConstraint(minutes.eq(145));
model.minimize(quotient);

const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 2

Either-Or Constraints

addEitherOr(c1, c2) enforces that at least one of two constraints must hold, using indicator variables internally.

const model = new Model();
const x = model.numVar(0, 100, 'x');
const y = model.numVar(0, 100, 'y');

model.addEitherOr(x.leq(10), y.leq(10));
model.maximize(x.plus(y));

const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 110

Reification

reify(constraint) returns a binary variable delta that is 1 if and only if the constraint is satisfied. This is the most general way to convert a constraint into a variable for use in other expressions. It supports <=, >=, and = senses.

const model = new Model();
const tasks = [
  model.intVar(0, 10, 'a'),
  model.intVar(0, 10, 'b'),
  model.intVar(0, 10, 'c'),
];

const overloaded = tasks.map(t => model.reify(t.geq(8)));
model.addConstraint(sum(...overloaded).leq(1));

model.maximize(sum(...tasks));
const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 24

Epsilon defaults to 1 for purely-integer expressions (exact reification) and 1e-6 for continuous expressions. Both epsilon and Big-M can be overridden via options:

const model = new Model();
const x = model.numVar(0, 100, 'x');
const delta = model.reify(x.leq(50), { bigM: 200, epsilon: 1e-4 });

model.addConstraint(delta.eq(1));
model.maximize(x);

const solution = await model.solve();
console.log(solution.objective); // @expect: solution.objective === 50

Configuration

Console Output

By default, solver output is suppressed. To enable progress logging, pass an explicit console configuration:

const highs = await HiGHS.create({
  console: {
    log: (msg) => console.log(msg),
    error: (msg) => console.error(msg)
  }
});

The same option works for Model.solve():

const solution = await model.solve({
  console: { log: console.log, error: console.error }
});

Building

Build the project (requires Emscripten for WASM compilation):

npm run build
npm run build:ts
npm run build:wasm

Run the test suite (requires a playwright-compatible browser):

npm test
npm run test:node    # Node only
npm run test:browser # Browser only (using Playwright)

Start the development server:

npm run serve

Licensing

This package is licensed under the MIT License. The WASM bundle includes HiGHS, also licensed under MIT (license).

Acknowledgements

The vast majority of the credit here goes to the authors of HiGHS. I've just packaged it nicely for use in the javascript/typescript world.

About

WASM build of the HiGHS solver with a simple TypeScript interface.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published