Skip to content

Commit

Permalink
readme and demo updates
Browse files Browse the repository at this point in the history
  • Loading branch information
DominikPeters committed Feb 21, 2024
1 parent 9fa152f commit 0885d00
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
106 changes: 100 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

JavaScript package for modelling (Integer) Linear Programs

This is a lightweight JS package for specifying LPs and ILPs using a convenient syntax. The constructed model can be exported to the `.lp` [CPLEX LP format](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm), and solved using the [highs-js](https://github.com/lovasoa/highs-js) and [glpk.js](https://github.com/jvail/glpk.js) solvers available as WebAssembly. This can be done both in the browser ([demo page](https://dominikpeters.github.io/lp-model/)) and in Node.js.
This is a lightweight JS package for specifying LPs and ILPs using a convenient syntax. The constructed model can be exported to the `.lp` [CPLEX LP format](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm), and solved using
* [highs-js](https://github.com/lovasoa/highs-js) (WebAssembly wrapper for the [HiGHS solver](https://github.com/ERGO-Code/HiGHS), a high-performance LP/ILP solver),
* [glpk.js](https://github.com/jvail/glpk.js) (WebAssembly wrapper for the [GLPK solver](https://www.gnu.org/software/glpk/)), and
* [jsLPSolver](https://github.com/JWally/jsLPSolver) (a pure JS solver, not as fast as the others, but small bundle size).

All solvers work both in the browser ([demo page](https://dominikpeters.github.io/lp-model/)) and in Node.js.

## Installation

Expand All @@ -17,6 +22,7 @@ npm install lp-model
# optionally install the solvers
npm install highs
npm install glpk.js
npm install javascript-lp-solver
```

In the browser:
Expand All @@ -42,21 +48,28 @@ async function main() {
// or
const glpk = await require("glpk.js")();
model.solve(glpk);
// or
const jsLPSolver = require("javascript-lp-solver");
model.solve(jsLPSolver);
}
main();
```

Setup in the browser for high-js:
Setup in the browser for high-js and jsLPSolver:

```html
<script src="https://cdn.jsdelivr.net/npm/lp-model@latest/dist/lp-model.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highs/build/highs.js"></script>
<script src="https://unpkg.com/javascript-lp-solver/prod/solver.js"></script>
<script>
async function main() {
const model = new LPModel.Model();
// ...
const highs = await Module();
model.solve(highs);
// or
const jsLPSolver = window.solver;
model.solve(jsLPSolver);
}
main();
</script>
Expand Down Expand Up @@ -110,17 +123,98 @@ const problem = {

const itemNames = problem.items.map(item => item.name);

// make binary variables for each item
const included = model.addVars(itemNames, { vtype: "BINARY" });
// included[A], included[B], included[C], included[D] are binary variables

model.addConstr(problem.items.map((item, i) => [item.weight, included[item.name]]), "<=", problem.capacity);
// sum of weights of included items <= capacity
model.addConstr(problem.items.map((item, i) => [item.weight, included[item.name]]), "<=", problem.capacity);
// equivalent to: 3*included[A] + 4*included[B] + 5*included[C] + 8*included[D] <= 15

model.setObjective(problem.items.map((item, i) => [item.value, included[item.name]]), "MAXIMIZE");
// maximize sum of values of included items
model.setObjective(
problem.items.map((item, i) => [item.value, included[item.name]]),
"MAXIMIZE"
);

await model.solve(highs);
console.log(`Objective value: ${model.ObjVal}`);
console.log(`Objective value: ${model.ObjVal}`); // 17
console.log(`Included items: ${itemNames.filter(name => included[name].value > 0.5)}`); // A,B,D
```
```

## API

### model.addVar(options) ⇒ <code>Var</code>
Adds a variable to the model.

**Kind**: instance method of [<code>Model</code>](#module_lp-model.Model)
**Returns**: <code>Var</code> - The created variable instance.


| Param | Type | Default | Description |
| --- | --- | --- | --- |
| options | <code>Object</code> | | Options for creating the variable. |
| [options.lb] | <code>number</code> \| <code>&quot;-infinity&quot;</code> | <code>0</code> | The lower bound of the variable. |
| [options.ub] | <code>number</code> \| <code>&quot;+infinity&quot;</code> | <code>&quot;+infinity&quot;</code> | The upper bound of the variable. |
| [options.vtype] | <code>&quot;CONTINUOUS&quot;</code> \| <code>&quot;BINARY&quot;</code> \| <code>&quot;INTEGER&quot;</code> | <code>&quot;CONTINUOUS&quot;</code> | The type of the variable. |
| [options.name] | <code>string</code> | | The name of the variable. If not provided, a unique name is generated. |

<a name="module_lp-model.Model+addVars"></a>

### model.addVars(varNames, options) ⇒ <code>Object</code>
Adds multiple variables to the model based on an array of names.
Each variable is created with the same provided options.

**Returns**: <code>Object</code> - An object where keys are variable names and values are the created variable instances.


| Param | Type | Default | Description |
| --- | --- | --- | --- |
| varNames | <code>Array.&lt;string&gt;</code> | | Array of names for the variables to be added. |
| options | <code>Object</code> | | Common options for creating the variables. |
| [options.lb] | <code>number</code> \| <code>&quot;-infinity&quot;</code> | <code>0</code> | The lower bound for all variables. |
| [options.ub] | <code>number</code> \| <code>&quot;+infinity&quot;</code> | <code>&quot;+infinity&quot;</code> | The upper bound for all variables. |
| [options.vtype] | <code>&quot;CONTINUOUS&quot;</code> \| <code>&quot;BINARY&quot;</code> \| <code>&quot;INTEGER&quot;</code> | <code>&quot;CONTINUOUS&quot;</code> | The type for all variables. |

<a name="module_lp-model.Model+setObjective"></a>

### model.setObjective(expression, sense)
Sets the objective function of the model.

| Param | Type | Description |
| --- | --- | --- |
| expression | <code>Array</code> | The linear expression representing the objective function. |
| sense | <code>&quot;MAXIMIZE&quot;</code> \| <code>&quot;MINIMIZE&quot;</code> | The sense of optimization, either "MAXIMIZE" or "MINIMIZE". |

<a name="module_lp-model.Model+addConstr"></a>

### model.addConstr(lhs, comparison, rhs) ⇒ <code>Constr</code>
Adds a constraint to the model.

**Returns**: <code>Constr</code> - The created constraint instance.

| Param | Type | Description |
| --- | --- | --- |
| lhs | <code>Array</code> | The left-hand side expression of the constraint. |
| comparison | <code>string</code> | The comparison operator, either "<=", "=", or ">=". |
| rhs | <code>number</code> \| <code>Array</code> | The right-hand side, which can be a number or a linear expression. |

<a name="module_lp-model.Model+toLPFormat"></a>

### model.toLPFormat() ⇒ <code>string</code>
Converts the model to CPLEX LP format string.

**Returns**: <code>string</code> - The model represented in LP format.
**See**: [https://web.mit.edu/lpsolve/doc/CPLEX-format.htm](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm)

<a name="module_lp-model.Model+solve"></a>

### model.solve(solver, [options])
Solves the model using the provided solver. highs-js, glpk.js, or jsLPSolver can be used.
The solution can be accessed from the variables' `value` properties and the constraints' `primal` and `dual` properties.

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| solver | <code>Object</code> | | The solver instance to use for solving the model, either from highs-js, glpk.js, or jsLPSolver. |
| [options] | <code>Object</code> | <code>{}</code> | Options to pass to the solver's solve method (refer to their respective documentation: https://ergo-code.github.io/HiGHS/dev/options/definitions/, https://www.npmjs.com/package/glpk.js, https://github.com/JWally/jsLPSolver?tab=readme-ov-file#options). |

7 changes: 6 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ <h1>lp-model</h1>
convenient syntax, used together with the <code>highs-js</code> and <code>glpk.js</code> solvers using
WebAssembly.
</p>
<pre id="output"></pre>
<pre id="output" style="font-size: 0.85rem;"></pre>
<p>If you see an ILP written in the CPLEX LP format, and both the HiGHS solution and the GLPK solution, it worked! <a href="https://github.com/DominikPeters/lp-model/blob/master/index.html">HTML source code</a></p>
</main>
<!-- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lp-model.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/highs/build/highs.js"></script>
<script src="https://unpkg.com/javascript-lp-solver/prod/solver.js"></script>
<script type="module">
import { Model } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/lp-model.es.min.js";
import GLPK from "https://cdn.jsdelivr.net/npm/glpk.js";
Expand All @@ -49,6 +50,10 @@ <h1>lp-model</h1>
const glpk = await GLPK();
await m.solve(glpk);
pre.textContent += `\n\nGLPK solution:\n x = ${x.value}\n y = ${y.value}`;

const jsLPSolver = window.solver;
await m.solve(jsLPSolver);
pre.textContent += `\n\njsLPSolver solution:\n x = ${x.value}\n y = ${y.value}`;
}

window.main = main;
Expand Down
5 changes: 5 additions & 0 deletions src/model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* A module for specifying LPs and ILPs using a convenient syntax, and solving them with various solvers.
* @module lp-model
*/

import { toGLPKFormat, readGLPKSolution } from './glpk-js-bridge.js';
import { toJSLPSolverFormat, readJSLPSolverSolution } from './jsLPSolver-bridge.js';
import { readHighsSolution } from './highs-js-bridge.js';
Expand Down

0 comments on commit 0885d00

Please sign in to comment.