@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 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)
@@ -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>