@bubblyworld/highs-ts

中文文档
1.4.1发布于 2026/09/04MITTypeScriptESM + CJS零依赖近 30 天下载 1.3万

A wasm build of the HiGHS (highs.dev) MILP solver with a convenient typescript interface. Runs with zero dependencies in both node and web environments.

原始使用文档
文档目录13 节
  1. Basic Usage
  2. Raw Columnar Input
  3. High-Level API
  4. Modelling Primitives
  5. Semi-Continuous Variables
  6. Integer Division
  7. Either-Or Constraints
  8. Reification
  9. Configuration
  10. Console Output
  11. Building
  12. Licensing
  13. Acknowledgements

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.

The solver's emscripten glue is loaded with a plain relative import(), so the package works both unbundled (served as native ES modules) and under bundlers like webpack and vite, which statically resolve the import and include the glue and wasm in your bundle.

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();

Raw Columnar Input

For large models, serializing to LP/MPS text and parsing it back becomes the dominant cost. passModel skips both: it copies typed arrays straight into solver memory via Highs_passLp (or Highs_passMip when integrality is given). The constraint matrix is sparse, row-wise by default, and bounds default to 0 <= x < +inf for columns and -inf < row < +inf for rows — pass HIGHS_INF explicitly where you need it. getSolutionValues reads the solution back densely by column index, which is the natural pairing since raw columns have no names.

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

// The same problem as above, in columnar form.
const highs = await HiGHS.create();
highs.passModel({
  numCol: 2,
  numRow: 2,
  sense: 'maximize',
  colCost: Float64Array.from([1, 2]),
  colUpper: Float64Array.from([10, 10]),
  rowUpper: Float64Array.from([10, 5]),
  matrix: {
    start: Int32Array.from([0, 2, 3]),
    index: Int32Array.from([0, 1, 0]),
    value: Float64Array.from([1, 1, 1]),
  },
});
const result = await highs.solve();
const values = highs.getSolutionValues();

console.log(result.status);     // @expect: result.status === 'optimal'
console.log(result.objective);  // @expect: result.objective === 20
console.log(values[1]);         // @expect: values[1] === 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

Variable and constraint names can be any whitespace-free string. solve() hands the model to the solver in MPS format, which — unlike the LP grammar — reserves no characters, so names like item-cost or x[1,2] are fine. To inspect a model yourself, model.print() returns CPLEX LP text and model.print('mps') returns MPS.

Expressions support chained arithmetic: plus(), minus(), times(), neg(). Constraints are created with leq(), geq(), and eq(). The sum() helper combines multiple terms, either spread as arguments or passed as a single array (prefer the array form for very large collections), and solution.getValue() evaluates a variable or any linear expression against the solution. 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.

版本与发布记录

@bubblyworld/highs-ts 共发布 6 个版本,首次发布于 2026/02/09;过去 12 个月发布了 6 个版本。

版本发布日期距今
1.4.1latest2026/09/042 个月前
1.4.02026/09/032 个月前
1.3.02026/07/213 个月前
1.2.02026/02/198 个月前
1.1.02026/02/138 个月前
1.0.02026/02/098 个月前

关于 @bubblyworld/highs-ts 的常见问题

@bubblyworld/highs-ts 是什么?

A wasm build of the HiGHS (highs.dev) MILP solver with a convenient typescript interface. Runs with zero dependencies in both node and web environments.

如何安装 @bubblyworld/highs-ts?

在项目目录运行 npm install @bubblyworld/highs-ts,或使用 pnpm add / yarn add / bun add。

@bubblyworld/highs-ts 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bubblyworld/highs-ts 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bubblyworld/highs-ts 可以商用吗?

@bubblyworld/highs-ts 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bubblyworld/highs-ts 的下载量有多少?

根据 npm 官方下载统计,@bubblyworld/highs-ts 在 2026/10/09 – 2026/11/07 这 30 天内被下载 12,947 次,日均约 432 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bubblyworld/highs-ts 还在维护吗?

最新版本 1.4.1 发布于 2026/09/04(2 个月前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/02/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。