@areb0s/scip.js 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -0
- package/dist/basic.html +445 -0
- package/dist/index.mjs +73 -0
- package/dist/scip-core.js +15 -0
- package/dist/scip-worker-client.js +167 -0
- package/dist/scip-worker.js +81 -0
- package/dist/scip-wrapper.js +429 -0
- package/dist/scip.js +3223 -0
- package/dist/scip.js.map +7 -0
- package/dist/scip.min.js +43 -0
- package/dist/scip.min.js.map +7 -0
- package/dist/scip.wasm +0 -0
- package/dist/test-browser.html +118 -0
- package/dist/test-worker.html +161 -0
- package/dist/types.d.ts +230 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# SCIP.js
|
|
2
|
+
|
|
3
|
+
**SCIP Optimization Solver compiled to WebAssembly**
|
|
4
|
+
|
|
5
|
+
Solve Linear Programming (LP), Mixed Integer Programming (MIP), and Mixed Integer Nonlinear Programming (MINLP) problems directly in the browser or Node.js.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **LP**: Linear Programming
|
|
10
|
+
- **MIP**: Mixed Integer Programming (binary, integer variables)
|
|
11
|
+
- **MINLP**: Mixed Integer Nonlinear Programming
|
|
12
|
+
- **Zero Dependencies**: Pure WebAssembly, no native bindings
|
|
13
|
+
- **Browser & Node.js**: Works everywhere JavaScript runs
|
|
14
|
+
- **Web Worker Support**: Non-blocking solver for long computations
|
|
15
|
+
- **TypeScript**: Full type definitions included
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install scip.js
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or use via CDN:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">
|
|
27
|
+
import SCIP from 'https://unpkg.com/scip.js/dist/index.mjs';
|
|
28
|
+
</script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import SCIP from 'scip.js';
|
|
35
|
+
|
|
36
|
+
// Solve a linear programming problem
|
|
37
|
+
const result = await SCIP.solve(`
|
|
38
|
+
Minimize
|
|
39
|
+
obj: 2 x + 3 y
|
|
40
|
+
Subject To
|
|
41
|
+
c1: x + y >= 4
|
|
42
|
+
c2: 2 x + y >= 5
|
|
43
|
+
Bounds
|
|
44
|
+
x >= 0
|
|
45
|
+
y >= 0
|
|
46
|
+
End
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
console.log(result.status); // 'optimal'
|
|
50
|
+
console.log(result.objective); // 7.0
|
|
51
|
+
console.log(result.variables); // { x: 1, y: 3 }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### `SCIP.solve(problem, options?)`
|
|
57
|
+
|
|
58
|
+
Solve an optimization problem.
|
|
59
|
+
|
|
60
|
+
**Parameters:**
|
|
61
|
+
- `problem` (string): Problem in LP format
|
|
62
|
+
- `options` (object, optional):
|
|
63
|
+
- `format`: `'lp'` | `'mps'` | `'zpl'` (default: `'lp'`)
|
|
64
|
+
- `timeLimit`: Time limit in seconds (default: 3600)
|
|
65
|
+
- `gap`: Relative MIP gap tolerance (e.g., 0.01 for 1%)
|
|
66
|
+
- `verbose`: Enable verbose output (default: false)
|
|
67
|
+
- `parameters`: Additional SCIP parameters
|
|
68
|
+
|
|
69
|
+
**Returns:** `Promise<Solution>`
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
interface Solution {
|
|
73
|
+
status: 'optimal' | 'infeasible' | 'unbounded' | 'timelimit' | 'unknown' | 'error';
|
|
74
|
+
objective: number | null;
|
|
75
|
+
variables: Record<string, number>;
|
|
76
|
+
statistics: {
|
|
77
|
+
solvingTime: number | null;
|
|
78
|
+
nodes: number | null;
|
|
79
|
+
iterations: number | null;
|
|
80
|
+
gap: number | null;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `SCIP.minimize(problem, options?)`
|
|
86
|
+
|
|
87
|
+
Convenience wrapper that ensures the problem is minimized.
|
|
88
|
+
|
|
89
|
+
### `SCIP.maximize(problem, options?)`
|
|
90
|
+
|
|
91
|
+
Convenience wrapper that ensures the problem is maximized.
|
|
92
|
+
|
|
93
|
+
### `SCIP.init(options?)`
|
|
94
|
+
|
|
95
|
+
Initialize the SCIP WASM module. Called automatically on first solve.
|
|
96
|
+
|
|
97
|
+
### `SCIP.version()`
|
|
98
|
+
|
|
99
|
+
Get SCIP version information.
|
|
100
|
+
|
|
101
|
+
## LP Format Reference
|
|
102
|
+
|
|
103
|
+
The LP format is a human-readable format for optimization problems:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
\ Comments start with backslash
|
|
107
|
+
Minimize (or Maximize)
|
|
108
|
+
obj: 2 x + 3 y - z
|
|
109
|
+
|
|
110
|
+
Subject To
|
|
111
|
+
constraint1: x + y >= 10
|
|
112
|
+
constraint2: x - y <= 5
|
|
113
|
+
constraint3: x + 2 y + 3 z = 15
|
|
114
|
+
|
|
115
|
+
Bounds
|
|
116
|
+
0 <= x <= 100
|
|
117
|
+
y >= 0
|
|
118
|
+
z free
|
|
119
|
+
|
|
120
|
+
General (Integer variables)
|
|
121
|
+
x
|
|
122
|
+
|
|
123
|
+
Binary (0-1 variables)
|
|
124
|
+
y
|
|
125
|
+
|
|
126
|
+
End
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
### Linear Programming
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const result = await SCIP.minimize(`
|
|
135
|
+
obj: 100 x1 + 150 x2
|
|
136
|
+
Subject To
|
|
137
|
+
labor: 2 x1 + 3 x2 <= 120
|
|
138
|
+
materials: 4 x1 + 2 x2 <= 100
|
|
139
|
+
Bounds
|
|
140
|
+
x1 >= 0
|
|
141
|
+
x2 >= 0
|
|
142
|
+
End
|
|
143
|
+
`);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Mixed Integer Programming (Knapsack)
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const result = await SCIP.maximize(`
|
|
150
|
+
obj: 60 item1 + 100 item2 + 120 item3
|
|
151
|
+
Subject To
|
|
152
|
+
weight: 10 item1 + 20 item2 + 30 item3 <= 50
|
|
153
|
+
Binary
|
|
154
|
+
item1 item2 item3
|
|
155
|
+
End
|
|
156
|
+
`);
|
|
157
|
+
|
|
158
|
+
// result.variables = { item2: 1, item3: 1 }
|
|
159
|
+
// result.objective = 220
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Transportation Problem
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const result = await SCIP.minimize(`
|
|
166
|
+
obj: 8 x11 + 6 x12 + 10 x13 + 9 x21 + 12 x22 + 7 x23
|
|
167
|
+
Subject To
|
|
168
|
+
supply1: x11 + x12 + x13 <= 100
|
|
169
|
+
supply2: x21 + x22 + x23 <= 150
|
|
170
|
+
demand1: x11 + x21 >= 80
|
|
171
|
+
demand2: x12 + x22 >= 70
|
|
172
|
+
demand3: x13 + x23 >= 60
|
|
173
|
+
Bounds
|
|
174
|
+
x11 >= 0
|
|
175
|
+
x12 >= 0
|
|
176
|
+
x13 >= 0
|
|
177
|
+
x21 >= 0
|
|
178
|
+
x22 >= 0
|
|
179
|
+
x23 >= 0
|
|
180
|
+
End
|
|
181
|
+
`);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### With Time Limit and Gap
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
const result = await SCIP.solve(problem, {
|
|
188
|
+
timeLimit: 60, // 60 seconds
|
|
189
|
+
gap: 0.01, // Stop when within 1% of optimal
|
|
190
|
+
verbose: true // Print solver output
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Web Worker Usage
|
|
195
|
+
|
|
196
|
+
For long-running optimizations, use the Web Worker API to avoid blocking the main thread:
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
import { createWorkerSolver } from 'scip.js';
|
|
200
|
+
|
|
201
|
+
const solver = await createWorkerSolver();
|
|
202
|
+
|
|
203
|
+
// Solve in background
|
|
204
|
+
const result = await solver.solve(largeProblem, { timeLimit: 300 });
|
|
205
|
+
|
|
206
|
+
// Clean up when done
|
|
207
|
+
solver.terminate();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Building from Source
|
|
211
|
+
|
|
212
|
+
### Prerequisites
|
|
213
|
+
|
|
214
|
+
- Docker
|
|
215
|
+
- Bash
|
|
216
|
+
|
|
217
|
+
### Build
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Clone repository
|
|
221
|
+
git clone https://github.com/user/scip.js
|
|
222
|
+
cd scip.js
|
|
223
|
+
|
|
224
|
+
# Build WASM (uses Docker)
|
|
225
|
+
./build.sh
|
|
226
|
+
|
|
227
|
+
# Output in dist/
|
|
228
|
+
ls dist/
|
|
229
|
+
# scip.js scip.wasm index.mjs types.d.ts
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The build process:
|
|
233
|
+
1. Downloads SCIP Optimization Suite
|
|
234
|
+
2. Compiles with Emscripten to WebAssembly
|
|
235
|
+
3. Bundles JavaScript wrapper
|
|
236
|
+
|
|
237
|
+
## File Sizes
|
|
238
|
+
|
|
239
|
+
| File | Size | Gzipped |
|
|
240
|
+
|------|------|---------|
|
|
241
|
+
| scip.wasm | ~5 MB | ~1.5 MB |
|
|
242
|
+
| scip.js | ~50 KB | ~15 KB |
|
|
243
|
+
|
|
244
|
+
## Limitations
|
|
245
|
+
|
|
246
|
+
- **Single-threaded**: WASM runs single-threaded (use Web Workers for parallelism at application level)
|
|
247
|
+
- **Memory**: Limited to ~2GB (WASM 32-bit limit)
|
|
248
|
+
- **No callbacks**: Progress callbacks not yet supported
|
|
249
|
+
- **ZIMPL disabled**: ZIMPL modeling language not included (use LP/MPS format)
|
|
250
|
+
|
|
251
|
+
## Performance
|
|
252
|
+
|
|
253
|
+
Typical solving times in browser (varies by problem complexity):
|
|
254
|
+
|
|
255
|
+
| Problem Type | Variables | Constraints | Time |
|
|
256
|
+
|--------------|-----------|-------------|------|
|
|
257
|
+
| LP | 1,000 | 500 | <1s |
|
|
258
|
+
| LP | 10,000 | 5,000 | ~5s |
|
|
259
|
+
| MIP | 100 binary | 50 | ~1s |
|
|
260
|
+
| MIP | 1,000 binary | 500 | ~30s |
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
Apache 2.0 (same as SCIP)
|
|
265
|
+
|
|
266
|
+
## Credits
|
|
267
|
+
|
|
268
|
+
- [SCIP Optimization Suite](https://scipopt.org) - The underlying solver
|
|
269
|
+
- [Emscripten](https://emscripten.org) - C++ to WebAssembly compiler
|
|
270
|
+
- [poker-chipper](https://github.com/jstrieb/poker-chipper) - Reference SCIP WASM build
|
|
271
|
+
|
|
272
|
+
## Links
|
|
273
|
+
|
|
274
|
+
- [SCIP Documentation](https://scipopt.org/doc/html/)
|
|
275
|
+
- [LP Format Reference](https://www.gurobi.com/documentation/current/refman/lp_format.html)
|
|
276
|
+
- [MPS Format Reference](https://www.gurobi.com/documentation/current/refman/mps_format.html)
|
package/dist/basic.html
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>SCIP.js - Optimization Solver Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
color: #e0e0e0;
|
|
18
|
+
padding: 2rem;
|
|
19
|
+
}
|
|
20
|
+
.container {
|
|
21
|
+
max-width: 1000px;
|
|
22
|
+
margin: 0 auto;
|
|
23
|
+
}
|
|
24
|
+
h1 {
|
|
25
|
+
text-align: center;
|
|
26
|
+
margin-bottom: 0.5rem;
|
|
27
|
+
color: #4fc3f7;
|
|
28
|
+
}
|
|
29
|
+
.subtitle {
|
|
30
|
+
text-align: center;
|
|
31
|
+
color: #888;
|
|
32
|
+
margin-bottom: 2rem;
|
|
33
|
+
}
|
|
34
|
+
.card {
|
|
35
|
+
background: rgba(255, 255, 255, 0.05);
|
|
36
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
37
|
+
border-radius: 12px;
|
|
38
|
+
padding: 1.5rem;
|
|
39
|
+
margin-bottom: 1.5rem;
|
|
40
|
+
}
|
|
41
|
+
.card h2 {
|
|
42
|
+
color: #4fc3f7;
|
|
43
|
+
margin-bottom: 1rem;
|
|
44
|
+
font-size: 1.2rem;
|
|
45
|
+
}
|
|
46
|
+
.examples {
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: 0.5rem;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
margin-bottom: 1rem;
|
|
51
|
+
}
|
|
52
|
+
.example-btn {
|
|
53
|
+
padding: 0.5rem 1rem;
|
|
54
|
+
background: rgba(79, 195, 247, 0.2);
|
|
55
|
+
border: 1px solid rgba(79, 195, 247, 0.3);
|
|
56
|
+
border-radius: 6px;
|
|
57
|
+
color: #4fc3f7;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
font-size: 0.9rem;
|
|
60
|
+
transition: all 0.2s;
|
|
61
|
+
}
|
|
62
|
+
.example-btn:hover {
|
|
63
|
+
background: rgba(79, 195, 247, 0.3);
|
|
64
|
+
}
|
|
65
|
+
textarea {
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 300px;
|
|
68
|
+
background: #0d1117;
|
|
69
|
+
border: 1px solid #30363d;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
color: #c9d1d9;
|
|
72
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
73
|
+
font-size: 13px;
|
|
74
|
+
padding: 1rem;
|
|
75
|
+
resize: vertical;
|
|
76
|
+
}
|
|
77
|
+
.options {
|
|
78
|
+
display: grid;
|
|
79
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
80
|
+
gap: 1rem;
|
|
81
|
+
margin-bottom: 1rem;
|
|
82
|
+
}
|
|
83
|
+
.option {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
gap: 0.3rem;
|
|
87
|
+
}
|
|
88
|
+
.option label {
|
|
89
|
+
font-size: 0.85rem;
|
|
90
|
+
color: #888;
|
|
91
|
+
}
|
|
92
|
+
.option input, .option select {
|
|
93
|
+
padding: 0.5rem;
|
|
94
|
+
background: #0d1117;
|
|
95
|
+
border: 1px solid #30363d;
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
color: #c9d1d9;
|
|
98
|
+
}
|
|
99
|
+
.solve-btn {
|
|
100
|
+
width: 100%;
|
|
101
|
+
padding: 1rem;
|
|
102
|
+
background: linear-gradient(135deg, #4fc3f7, #29b6f6);
|
|
103
|
+
border: none;
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
color: #1a1a2e;
|
|
106
|
+
font-size: 1.1rem;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
transition: transform 0.2s;
|
|
110
|
+
}
|
|
111
|
+
.solve-btn:hover:not(:disabled) {
|
|
112
|
+
transform: scale(1.02);
|
|
113
|
+
}
|
|
114
|
+
.solve-btn:disabled {
|
|
115
|
+
opacity: 0.6;
|
|
116
|
+
cursor: not-allowed;
|
|
117
|
+
}
|
|
118
|
+
.result {
|
|
119
|
+
background: #0d1117;
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
padding: 1rem;
|
|
122
|
+
}
|
|
123
|
+
.status {
|
|
124
|
+
display: inline-block;
|
|
125
|
+
padding: 0.3rem 0.8rem;
|
|
126
|
+
border-radius: 4px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
margin-bottom: 1rem;
|
|
129
|
+
}
|
|
130
|
+
.status.optimal { background: #238636; color: white; }
|
|
131
|
+
.status.infeasible { background: #da3633; color: white; }
|
|
132
|
+
.status.unknown { background: #9e6a03; color: white; }
|
|
133
|
+
.status.error { background: #da3633; color: white; }
|
|
134
|
+
.result-grid {
|
|
135
|
+
display: grid;
|
|
136
|
+
grid-template-columns: 1fr 1fr;
|
|
137
|
+
gap: 1rem;
|
|
138
|
+
}
|
|
139
|
+
.result-section h3 {
|
|
140
|
+
color: #4fc3f7;
|
|
141
|
+
margin-bottom: 0.5rem;
|
|
142
|
+
font-size: 1rem;
|
|
143
|
+
}
|
|
144
|
+
.result-section pre {
|
|
145
|
+
background: rgba(0,0,0,0.3);
|
|
146
|
+
padding: 0.8rem;
|
|
147
|
+
border-radius: 4px;
|
|
148
|
+
font-size: 0.9rem;
|
|
149
|
+
overflow-x: auto;
|
|
150
|
+
}
|
|
151
|
+
.loading {
|
|
152
|
+
display: none;
|
|
153
|
+
text-align: center;
|
|
154
|
+
padding: 2rem;
|
|
155
|
+
}
|
|
156
|
+
.loading.active {
|
|
157
|
+
display: block;
|
|
158
|
+
}
|
|
159
|
+
.spinner {
|
|
160
|
+
width: 40px;
|
|
161
|
+
height: 40px;
|
|
162
|
+
border: 3px solid rgba(79, 195, 247, 0.3);
|
|
163
|
+
border-top-color: #4fc3f7;
|
|
164
|
+
border-radius: 50%;
|
|
165
|
+
animation: spin 1s linear infinite;
|
|
166
|
+
margin: 0 auto 1rem;
|
|
167
|
+
}
|
|
168
|
+
@keyframes spin {
|
|
169
|
+
to { transform: rotate(360deg); }
|
|
170
|
+
}
|
|
171
|
+
.footer {
|
|
172
|
+
text-align: center;
|
|
173
|
+
margin-top: 2rem;
|
|
174
|
+
color: #666;
|
|
175
|
+
font-size: 0.85rem;
|
|
176
|
+
}
|
|
177
|
+
.footer a {
|
|
178
|
+
color: #4fc3f7;
|
|
179
|
+
text-decoration: none;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
</head>
|
|
183
|
+
<body>
|
|
184
|
+
<div class="container">
|
|
185
|
+
<h1>SCIP.js</h1>
|
|
186
|
+
<p class="subtitle">SCIP Optimization Solver in WebAssembly - LP, MIP, MINLP Support</p>
|
|
187
|
+
|
|
188
|
+
<div class="card">
|
|
189
|
+
<h2>Problem Input (LP Format)</h2>
|
|
190
|
+
<div class="examples">
|
|
191
|
+
<button class="example-btn" onclick="loadExample('lp')">LP</button>
|
|
192
|
+
<button class="example-btn" onclick="loadExample('mip')">MIP</button>
|
|
193
|
+
<button class="example-btn" onclick="loadExample('minlp')">MINLP (Quadratic)</button>
|
|
194
|
+
<button class="example-btn" onclick="loadExample('minlp2')">MINLP (Facility)</button>
|
|
195
|
+
<button class="example-btn" onclick="loadExample('knapsack')">Knapsack</button>
|
|
196
|
+
<button class="example-btn" onclick="loadExample('transportation')">Transportation</button>
|
|
197
|
+
</div>
|
|
198
|
+
<textarea id="problem"></textarea>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div class="card">
|
|
202
|
+
<h2>Solver Options</h2>
|
|
203
|
+
<div class="options">
|
|
204
|
+
<div class="option">
|
|
205
|
+
<label>Time Limit (seconds)</label>
|
|
206
|
+
<input type="number" id="timeLimit" value="60" min="1" max="3600">
|
|
207
|
+
</div>
|
|
208
|
+
<div class="option">
|
|
209
|
+
<label>Gap Tolerance (%)</label>
|
|
210
|
+
<input type="number" id="gap" value="0" min="0" max="100" step="0.1">
|
|
211
|
+
</div>
|
|
212
|
+
<div class="option">
|
|
213
|
+
<label>Verbose Output</label>
|
|
214
|
+
<select id="verbose">
|
|
215
|
+
<option value="false">No</option>
|
|
216
|
+
<option value="true">Yes</option>
|
|
217
|
+
</select>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<button class="solve-btn" id="solveBtn" onclick="solveProblem()">
|
|
221
|
+
Solve Optimization Problem
|
|
222
|
+
</button>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div class="card">
|
|
226
|
+
<h2>Results</h2>
|
|
227
|
+
<div class="loading" id="loading">
|
|
228
|
+
<div class="spinner"></div>
|
|
229
|
+
<p>Solving... Please wait</p>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="result" id="result">
|
|
232
|
+
<p style="color: #666;">Results will appear here after solving</p>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<p class="footer">
|
|
237
|
+
Powered by <a href="https://scipopt.org" target="_blank">SCIP Optimization Suite</a> |
|
|
238
|
+
<a href="https://github.com/user/scip.js" target="_blank">GitHub</a>
|
|
239
|
+
</p>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<script type="module">
|
|
243
|
+
import SCIP from './index.mjs';
|
|
244
|
+
|
|
245
|
+
// Make available globally
|
|
246
|
+
window.SCIP = SCIP;
|
|
247
|
+
|
|
248
|
+
// Example problems
|
|
249
|
+
const examples = {
|
|
250
|
+
lp: `\\ Simple Linear Programming Example
|
|
251
|
+
\\ Minimize production cost
|
|
252
|
+
Minimize
|
|
253
|
+
obj: 2 x1 + 3 x2 + 4 x3
|
|
254
|
+
Subject To
|
|
255
|
+
demand1: x1 + x2 + x3 >= 100
|
|
256
|
+
demand2: 2 x1 + x2 >= 80
|
|
257
|
+
capacity: x1 + 2 x2 + 3 x3 <= 200
|
|
258
|
+
Bounds
|
|
259
|
+
0 <= x1 <= 50
|
|
260
|
+
0 <= x2 <= 60
|
|
261
|
+
0 <= x3 <= 40
|
|
262
|
+
End`,
|
|
263
|
+
|
|
264
|
+
mip: `\\ Mixed Integer Programming Example
|
|
265
|
+
\\ Facility location problem
|
|
266
|
+
Minimize
|
|
267
|
+
obj: 100 y1 + 150 y2 + 80 y3 + 5 x11 + 8 x12 + 6 x13 + 10 x21 + 4 x22 + 9 x23
|
|
268
|
+
Subject To
|
|
269
|
+
\\ Customer demands
|
|
270
|
+
customer1: x11 + x21 >= 50
|
|
271
|
+
customer2: x12 + x22 >= 30
|
|
272
|
+
customer3: x13 + x23 >= 40
|
|
273
|
+
\\ Facility capacities (only if open)
|
|
274
|
+
facility1: x11 + x12 + x13 - 100 y1 <= 0
|
|
275
|
+
facility2: x21 + x22 + x23 - 100 y2 <= 0
|
|
276
|
+
Bounds
|
|
277
|
+
0 <= x11
|
|
278
|
+
0 <= x12
|
|
279
|
+
0 <= x13
|
|
280
|
+
0 <= x21
|
|
281
|
+
0 <= x22
|
|
282
|
+
0 <= x23
|
|
283
|
+
Binary
|
|
284
|
+
y1 y2 y3
|
|
285
|
+
End`,
|
|
286
|
+
|
|
287
|
+
minlp: `\\ Mixed Integer Nonlinear Programming (MINLP) Example
|
|
288
|
+
\\ Portfolio optimization with discrete lot sizes
|
|
289
|
+
\\
|
|
290
|
+
\\ Minimize quadratic risk (variance) with integer constraints
|
|
291
|
+
\\ This is a MIQP (Mixed Integer Quadratic Program)
|
|
292
|
+
Minimize
|
|
293
|
+
obj: [ x1^2 + 2 x2^2 + x1 * x2 ] / 2
|
|
294
|
+
Subject To
|
|
295
|
+
\\ Total investment must be at least 100 units
|
|
296
|
+
budget: x1 + x2 >= 100
|
|
297
|
+
\\ Risk constraint
|
|
298
|
+
risk: x1 - x2 <= 50
|
|
299
|
+
\\ Minimum diversification
|
|
300
|
+
diverse: x2 >= 20
|
|
301
|
+
Bounds
|
|
302
|
+
0 <= x1 <= 200
|
|
303
|
+
0 <= x2 <= 200
|
|
304
|
+
General
|
|
305
|
+
x1
|
|
306
|
+
End`,
|
|
307
|
+
|
|
308
|
+
minlp2: `\\ MINLP: Facility Location with Quadratic Costs
|
|
309
|
+
\\ Open facilities (binary) with nonlinear shipping costs
|
|
310
|
+
Minimize
|
|
311
|
+
obj: 50 open1 + 80 open2 + [ ship1^2 + ship2^2 ] / 2
|
|
312
|
+
Subject To
|
|
313
|
+
\\ Must ship at least 10 units total
|
|
314
|
+
demand: ship1 + ship2 >= 10
|
|
315
|
+
\\ Can only ship from open facilities
|
|
316
|
+
cap1: ship1 - 20 open1 <= 0
|
|
317
|
+
cap2: ship2 - 20 open2 <= 0
|
|
318
|
+
Bounds
|
|
319
|
+
0 <= ship1 <= 20
|
|
320
|
+
0 <= ship2 <= 20
|
|
321
|
+
Binary
|
|
322
|
+
open1 open2
|
|
323
|
+
End`,
|
|
324
|
+
|
|
325
|
+
knapsack: `\\ 0-1 Knapsack Problem
|
|
326
|
+
\\ Select items to maximize value within weight limit
|
|
327
|
+
Maximize
|
|
328
|
+
obj: 60 item1 + 100 item2 + 120 item3 + 80 item4 + 90 item5
|
|
329
|
+
Subject To
|
|
330
|
+
weight: 10 item1 + 20 item2 + 30 item3 + 15 item4 + 25 item5 <= 50
|
|
331
|
+
Binary
|
|
332
|
+
item1 item2 item3 item4 item5
|
|
333
|
+
End`,
|
|
334
|
+
|
|
335
|
+
transportation: `\\ Transportation Problem
|
|
336
|
+
\\ Ship goods from 2 plants to 3 customers at minimum cost
|
|
337
|
+
Minimize
|
|
338
|
+
obj: 8 x11 + 6 x12 + 10 x13 + 9 x21 + 12 x22 + 7 x23
|
|
339
|
+
Subject To
|
|
340
|
+
\\ Supply constraints
|
|
341
|
+
plant1: x11 + x12 + x13 <= 100
|
|
342
|
+
plant2: x21 + x22 + x23 <= 150
|
|
343
|
+
\\ Demand constraints
|
|
344
|
+
cust1: x11 + x21 >= 80
|
|
345
|
+
cust2: x12 + x22 >= 70
|
|
346
|
+
cust3: x13 + x23 >= 60
|
|
347
|
+
Bounds
|
|
348
|
+
x11 >= 0
|
|
349
|
+
x12 >= 0
|
|
350
|
+
x13 >= 0
|
|
351
|
+
x21 >= 0
|
|
352
|
+
x22 >= 0
|
|
353
|
+
x23 >= 0
|
|
354
|
+
End`
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
window.loadExample = function(name) {
|
|
358
|
+
document.getElementById('problem').value = examples[name] || '';
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
window.solveProblem = async function() {
|
|
362
|
+
const problem = document.getElementById('problem').value;
|
|
363
|
+
const timeLimit = parseInt(document.getElementById('timeLimit').value) || 60;
|
|
364
|
+
const gap = parseFloat(document.getElementById('gap').value) / 100 || null;
|
|
365
|
+
const verbose = document.getElementById('verbose').value === 'true';
|
|
366
|
+
|
|
367
|
+
const loading = document.getElementById('loading');
|
|
368
|
+
const result = document.getElementById('result');
|
|
369
|
+
const solveBtn = document.getElementById('solveBtn');
|
|
370
|
+
|
|
371
|
+
loading.classList.add('active');
|
|
372
|
+
result.innerHTML = '';
|
|
373
|
+
solveBtn.disabled = true;
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const solution = await SCIP.solve(problem, {
|
|
377
|
+
timeLimit,
|
|
378
|
+
gap,
|
|
379
|
+
verbose
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
displayResult(solution);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
result.innerHTML = `
|
|
385
|
+
<span class="status error">Error</span>
|
|
386
|
+
<pre>${error.message}</pre>
|
|
387
|
+
`;
|
|
388
|
+
} finally {
|
|
389
|
+
loading.classList.remove('active');
|
|
390
|
+
solveBtn.disabled = false;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
function displayResult(solution) {
|
|
395
|
+
const result = document.getElementById('result');
|
|
396
|
+
|
|
397
|
+
const statusClass = {
|
|
398
|
+
'optimal': 'optimal',
|
|
399
|
+
'infeasible': 'infeasible',
|
|
400
|
+
'unbounded': 'infeasible',
|
|
401
|
+
'timelimit': 'unknown',
|
|
402
|
+
'unknown': 'unknown',
|
|
403
|
+
'error': 'error'
|
|
404
|
+
}[solution.status] || 'unknown';
|
|
405
|
+
|
|
406
|
+
let html = `<span class="status ${statusClass}">${solution.status.toUpperCase()}</span>`;
|
|
407
|
+
|
|
408
|
+
if (solution.status === 'optimal' || solution.objective !== null) {
|
|
409
|
+
html += `
|
|
410
|
+
<div class="result-grid">
|
|
411
|
+
<div class="result-section">
|
|
412
|
+
<h3>Objective Value</h3>
|
|
413
|
+
<pre>${solution.objective}</pre>
|
|
414
|
+
|
|
415
|
+
<h3>Variables</h3>
|
|
416
|
+
<pre>${JSON.stringify(solution.variables, null, 2)}</pre>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="result-section">
|
|
419
|
+
<h3>Statistics</h3>
|
|
420
|
+
<pre>${JSON.stringify(solution.statistics, null, 2)}</pre>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (solution.error) {
|
|
427
|
+
html += `<pre style="color: #da3633;">${solution.error}</pre>`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
result.innerHTML = html;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Load LP example by default
|
|
434
|
+
loadExample('lp');
|
|
435
|
+
|
|
436
|
+
// Initialize SCIP
|
|
437
|
+
console.log('Initializing SCIP.js...');
|
|
438
|
+
SCIP.init().then(() => {
|
|
439
|
+
console.log('SCIP.js ready!');
|
|
440
|
+
}).catch(err => {
|
|
441
|
+
console.error('Failed to initialize SCIP:', err);
|
|
442
|
+
});
|
|
443
|
+
</script>
|
|
444
|
+
</body>
|
|
445
|
+
</html>
|