@areb0s/scip.js 1.1.3 → 1.1.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/dist/scip-core.js +313 -3
- package/dist/scip-wrapper.js +18 -15
- package/dist/scip.js +324 -26
- package/dist/scip.min.js +15 -9
- package/package.json +1 -1
package/dist/scip-wrapper.js
CHANGED
|
@@ -88,29 +88,32 @@ function parseStatus(output) {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Parse solution values from SCIP output
|
|
91
|
+
* @param {string} output - stdout from SCIP
|
|
92
|
+
* @param {string} rawSolution - solution file content (more reliable)
|
|
91
93
|
*/
|
|
92
|
-
function parseSolution(output) {
|
|
94
|
+
function parseSolution(output, rawSolution = null) {
|
|
93
95
|
const variables = {};
|
|
94
96
|
const objective = { value: null, sense: null };
|
|
95
97
|
|
|
98
|
+
// Use rawSolution if available (more reliable)
|
|
99
|
+
const solText = rawSolution || output;
|
|
100
|
+
|
|
96
101
|
// Parse objective value
|
|
97
|
-
const objMatch =
|
|
102
|
+
const objMatch = solText.match(/objective value:\s*([\d.e+-]+)/i);
|
|
98
103
|
if (objMatch) {
|
|
99
104
|
objective.value = parseFloat(objMatch[1]);
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
// Parse variable values from solution display
|
|
103
|
-
//
|
|
104
|
-
|
|
108
|
+
// Match ZIMPL-style variable names: x$sun#0, effSum$star#1, b_sun_10, etc.
|
|
109
|
+
// Format: variableName value (obj:coef)
|
|
110
|
+
const varRegex = /^([\w$#]+)\s+([\d.e+-]+)/gm;
|
|
105
111
|
let match;
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
const solSection = output.split('solution:')[1] || output;
|
|
109
|
-
|
|
110
|
-
while ((match = varRegex.exec(solSection)) !== null) {
|
|
113
|
+
while ((match = varRegex.exec(solText)) !== null) {
|
|
111
114
|
const name = match[1];
|
|
112
115
|
const value = parseFloat(match[2]);
|
|
113
|
-
if (!isNaN(value) && name !== 'objective') {
|
|
116
|
+
if (!isNaN(value) && name !== 'objective' && name !== 'solution') {
|
|
114
117
|
variables[name] = value;
|
|
115
118
|
}
|
|
116
119
|
}
|
|
@@ -329,12 +332,7 @@ export async function solve(problem, options = {}) {
|
|
|
329
332
|
// Run SCIP with batch mode
|
|
330
333
|
const exitCode = scipModule.callMain(['-b', '/settings/commands.txt']);
|
|
331
334
|
|
|
332
|
-
//
|
|
333
|
-
const status = parseStatus(stdout);
|
|
334
|
-
const { variables, objective } = parseSolution(stdout);
|
|
335
|
-
const statistics = parseStatistics(stdout);
|
|
336
|
-
|
|
337
|
-
// Try to read solution file
|
|
335
|
+
// Try to read solution file first (more reliable for parsing)
|
|
338
336
|
let rawSolution = null;
|
|
339
337
|
try {
|
|
340
338
|
rawSolution = scipModule.FS.readFile(solutionFile, { encoding: 'utf8' });
|
|
@@ -342,6 +340,11 @@ export async function solve(problem, options = {}) {
|
|
|
342
340
|
// Solution file may not exist if infeasible
|
|
343
341
|
}
|
|
344
342
|
|
|
343
|
+
// Parse results
|
|
344
|
+
const status = parseStatus(stdout);
|
|
345
|
+
const { variables, objective } = parseSolution(stdout, rawSolution);
|
|
346
|
+
const statistics = parseStatistics(stdout);
|
|
347
|
+
|
|
345
348
|
return {
|
|
346
349
|
status,
|
|
347
350
|
objective: objective.value,
|
package/dist/scip.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* SCIP.js Browser Bundle
|
|
4
|
+
* Supports: LP, MIP, MINLP (Mixed Integer Nonlinear Programming)
|
|
5
|
+
*/
|
|
6
|
+
(function(global) {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Script directory detection for WASM loading
|
|
10
|
+
var __SCIP_SCRIPT_DIR__ = (function() {
|
|
11
|
+
// Check for explicit SCIP_BASE_URL
|
|
12
|
+
if (typeof SCIP_BASE_URL !== 'undefined' && SCIP_BASE_URL) {
|
|
13
|
+
return SCIP_BASE_URL + (SCIP_BASE_URL.endsWith('/') ? '' : '/');
|
|
14
|
+
}
|
|
15
|
+
// Try to detect from current script
|
|
16
|
+
if (typeof document !== 'undefined' && document.currentScript && document.currentScript.src) {
|
|
17
|
+
var src = document.currentScript.src;
|
|
18
|
+
return src.substring(0, src.lastIndexOf('/') + 1);
|
|
19
|
+
}
|
|
20
|
+
// Default CDN (npm)
|
|
21
|
+
return 'https://cdn.jsdelivr.net/npm/@areb0s/scip.js@latest/dist/';
|
|
22
|
+
})();
|
|
23
|
+
|
|
24
|
+
// Inline the transformed scip-core.js (createSCIP factory function)
|
|
25
|
+
|
|
2
26
|
/**
|
|
3
27
|
* SCIP.js Browser Bundle
|
|
4
28
|
* Supports: LP, MIP, MINLP (Mixed Integer Nonlinear Programming)
|
|
@@ -67,23 +91,27 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
67
91
|
return Status.UNKNOWN;
|
|
68
92
|
}
|
|
69
93
|
|
|
70
|
-
function parseSolution(output) {
|
|
94
|
+
function parseSolution(output, rawSolution) {
|
|
71
95
|
var variables = {};
|
|
72
96
|
var objective = { value: null };
|
|
73
97
|
|
|
74
|
-
|
|
98
|
+
// Use rawSolution if available (more reliable)
|
|
99
|
+
var solText = rawSolution || output;
|
|
100
|
+
|
|
101
|
+
var objMatch = solText.match(/objective value:\s*([\d.e+-]+)/i);
|
|
75
102
|
if (objMatch) {
|
|
76
103
|
objective.value = parseFloat(objMatch[1]);
|
|
77
104
|
}
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
// Match ZIMPL-style variable names: x$sun#0, effSum$star#1, b_sun_10, etc.
|
|
107
|
+
// Format: variableName value (obj:coef)
|
|
108
|
+
var varRegex = /^([\w$#]+)\s+([\d.e+-]+)/gm;
|
|
80
109
|
var match;
|
|
81
|
-
var solSection = output.split('solution:')[1] || output;
|
|
82
110
|
|
|
83
|
-
while ((match = varRegex.exec(
|
|
111
|
+
while ((match = varRegex.exec(solText)) !== null) {
|
|
84
112
|
var name = match[1];
|
|
85
113
|
var value = parseFloat(match[2]);
|
|
86
|
-
if (!isNaN(value) && name !== 'objective') {
|
|
114
|
+
if (!isNaN(value) && name !== 'objective' && name !== 'solution') {
|
|
87
115
|
variables[name] = value;
|
|
88
116
|
}
|
|
89
117
|
}
|
|
@@ -122,22 +150,10 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
122
150
|
initPromise = new Promise(function(resolve, reject) {
|
|
123
151
|
try {
|
|
124
152
|
var wasmPath = options.wasmPath || (__SCIP_SCRIPT_DIR__ + 'scip.wasm');
|
|
125
|
-
console.log('[SCIP.js] __SCIP_SCRIPT_DIR__:', __SCIP_SCRIPT_DIR__);
|
|
126
|
-
console.log('[SCIP.js] Loading WASM from:', wasmPath);
|
|
127
|
-
|
|
128
|
-
// Verify WASM is accessible
|
|
129
|
-
fetch(wasmPath, { method: 'HEAD' })
|
|
130
|
-
.then(function(res) {
|
|
131
|
-
console.log('[SCIP.js] WASM file check:', res.ok ? 'OK' : 'FAILED', res.status);
|
|
132
|
-
})
|
|
133
|
-
.catch(function(err) {
|
|
134
|
-
console.error('[SCIP.js] WASM file check failed:', err);
|
|
135
|
-
});
|
|
136
153
|
|
|
137
154
|
createSCIP({
|
|
138
155
|
locateFile: function(path) {
|
|
139
156
|
if (path.endsWith('.wasm')) {
|
|
140
|
-
console.log('[SCIP.js] locateFile called for:', path, '-> returning:', wasmPath);
|
|
141
157
|
return wasmPath;
|
|
142
158
|
}
|
|
143
159
|
return path;
|
|
@@ -153,7 +169,6 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
171
|
}).then(function(module) {
|
|
156
|
-
console.log('[SCIP.js] WASM loaded successfully');
|
|
157
172
|
scipModule = module;
|
|
158
173
|
|
|
159
174
|
if (scipModule.FS) {
|
|
@@ -166,16 +181,11 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
166
181
|
if (readyResolve) readyResolve();
|
|
167
182
|
resolve();
|
|
168
183
|
}).catch(function(err) {
|
|
169
|
-
|
|
170
|
-
console.error('[SCIP.js] Attempted WASM path:', wasmPath);
|
|
171
|
-
console.error('[SCIP.js] Make sure the WASM file is accessible at this URL.');
|
|
172
|
-
console.error('[SCIP.js] You can set window.SCIP_BASE_URL before loading this script to specify a custom path.');
|
|
173
|
-
var error = new Error('SCIP WASM loading failed: ' + (err.message || err) + '. WASM path: ' + wasmPath);
|
|
184
|
+
var error = new Error('SCIP WASM loading failed: ' + (err.message || err));
|
|
174
185
|
if (readyReject) readyReject(error);
|
|
175
186
|
reject(error);
|
|
176
187
|
});
|
|
177
188
|
} catch (err) {
|
|
178
|
-
console.error('[SCIP.js] Initialization error:', err);
|
|
179
189
|
if (readyReject) readyReject(err);
|
|
180
190
|
reject(err);
|
|
181
191
|
}
|
|
@@ -240,15 +250,303 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
240
250
|
|
|
241
251
|
var exitCode = scipModule.callMain(['-b', '/settings/commands.txt']);
|
|
242
252
|
|
|
253
|
+
// Read rawSolution first (more reliable for parsing)
|
|
254
|
+
var rawSolution = null;
|
|
255
|
+
try {
|
|
256
|
+
rawSolution = scipModule.FS.readFile(solutionFile, { encoding: 'utf8' });
|
|
257
|
+
} catch (e) {}
|
|
258
|
+
|
|
243
259
|
var status = parseStatus(stdout);
|
|
244
|
-
var parsed = parseSolution(stdout);
|
|
260
|
+
var parsed = parseSolution(stdout, rawSolution);
|
|
245
261
|
var statistics = parseStatistics(stdout);
|
|
246
262
|
|
|
263
|
+
// Cleanup
|
|
264
|
+
var cleanupFiles = [
|
|
265
|
+
'/problems/problem.lp', '/problems/problem.mps',
|
|
266
|
+
'/problems/problem.zpl', '/problems/problem.cip',
|
|
267
|
+
'/solutions/solution.sol', '/settings/commands.txt'
|
|
268
|
+
];
|
|
269
|
+
for (var i = 0; i < cleanupFiles.length; i++) {
|
|
270
|
+
try { scipModule.FS.unlink(cleanupFiles[i]); } catch (e) {}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
status: status,
|
|
275
|
+
objective: parsed.objective.value,
|
|
276
|
+
variables: parsed.variables,
|
|
277
|
+
statistics: statistics,
|
|
278
|
+
exitCode: exitCode,
|
|
279
|
+
output: verbose ? stdout : undefined,
|
|
280
|
+
rawSolution: rawSolution
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
status: Status.ERROR,
|
|
286
|
+
error: error.message,
|
|
287
|
+
output: stdout + stderr
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (!isInitialized) {
|
|
293
|
+
return init(options).then(doSolve);
|
|
294
|
+
}
|
|
295
|
+
return Promise.resolve(doSolve());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function minimize(problem, options) {
|
|
299
|
+
if (!problem.toLowerCase().includes('minimize')) {
|
|
300
|
+
problem = 'Minimize\n' + problem;
|
|
301
|
+
}
|
|
302
|
+
return solve(problem, options);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function maximize(problem, options) {
|
|
306
|
+
if (!problem.toLowerCase().includes('maximize')) {
|
|
307
|
+
problem = 'Maximize\n' + problem;
|
|
308
|
+
}
|
|
309
|
+
return solve(problem, options);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function version() {
|
|
313
|
+
var getVersion = function() {
|
|
314
|
+
var output = '';
|
|
315
|
+
scipModule.onStdout = function(text) { output += text + '\n'; };
|
|
316
|
+
scipModule.callMain(['--version']);
|
|
317
|
+
return output.trim();
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
if (!isInitialized) {
|
|
321
|
+
return init().then(getVersion);
|
|
322
|
+
}
|
|
323
|
+
return Promise.resolve(getVersion());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isReady() {
|
|
327
|
+
return isInitialized;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Expose SCIP API
|
|
331
|
+
var SCIP = {
|
|
332
|
+
init: init,
|
|
333
|
+
ready: readyPromise,
|
|
334
|
+
isReady: isReady,
|
|
335
|
+
solve: solve,
|
|
336
|
+
minimize: minimize,
|
|
337
|
+
maximize: maximize,
|
|
338
|
+
version: version,
|
|
339
|
+
Status: Status
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
global.SCIP = SCIP;
|
|
343
|
+
|
|
344
|
+
// Auto-initialize
|
|
345
|
+
init().catch(function(err) {
|
|
346
|
+
console.error('[SCIP.js] Auto-initialization failed:', err.message);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
})(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : globalThis);
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
// SCIP wrapper implementation
|
|
353
|
+
var scipModule = null;
|
|
354
|
+
var isInitialized = false;
|
|
355
|
+
var initPromise = null;
|
|
356
|
+
var readyResolve = null;
|
|
357
|
+
var readyReject = null;
|
|
358
|
+
|
|
359
|
+
var readyPromise = new Promise(function(resolve, reject) {
|
|
360
|
+
readyResolve = resolve;
|
|
361
|
+
readyReject = reject;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
var Status = {
|
|
365
|
+
OPTIMAL: 'optimal',
|
|
366
|
+
INFEASIBLE: 'infeasible',
|
|
367
|
+
UNBOUNDED: 'unbounded',
|
|
368
|
+
TIME_LIMIT: 'timelimit',
|
|
369
|
+
UNKNOWN: 'unknown',
|
|
370
|
+
ERROR: 'error'
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
function parseStatus(output) {
|
|
374
|
+
if (output.includes('optimal solution found')) return Status.OPTIMAL;
|
|
375
|
+
if (output.includes('problem is infeasible')) return Status.INFEASIBLE;
|
|
376
|
+
if (output.includes('problem is unbounded')) return Status.UNBOUNDED;
|
|
377
|
+
if (output.includes('time limit reached')) return Status.TIME_LIMIT;
|
|
378
|
+
return Status.UNKNOWN;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function parseSolution(output, rawSolution) {
|
|
382
|
+
var variables = {};
|
|
383
|
+
var objective = { value: null };
|
|
384
|
+
|
|
385
|
+
// Use rawSolution if available (more reliable)
|
|
386
|
+
var solText = rawSolution || output;
|
|
387
|
+
|
|
388
|
+
var objMatch = solText.match(/objective value:\s*([\d.e+-]+)/i);
|
|
389
|
+
if (objMatch) {
|
|
390
|
+
objective.value = parseFloat(objMatch[1]);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Match ZIMPL-style variable names: x$sun#0, effSum$star#1, b_sun_10, etc.
|
|
394
|
+
// Format: variableName value (obj:coef)
|
|
395
|
+
var varRegex = /^([\w$#]+)\s+([\d.e+-]+)/gm;
|
|
396
|
+
var match;
|
|
397
|
+
|
|
398
|
+
while ((match = varRegex.exec(solText)) !== null) {
|
|
399
|
+
var name = match[1];
|
|
400
|
+
var value = parseFloat(match[2]);
|
|
401
|
+
if (!isNaN(value) && name !== 'objective' && name !== 'solution') {
|
|
402
|
+
variables[name] = value;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { variables: variables, objective: objective };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function parseStatistics(output) {
|
|
410
|
+
var stats = { solvingTime: null, nodes: null, iterations: null, gap: null };
|
|
411
|
+
|
|
412
|
+
var timeMatch = output.match(/Solving Time \(sec\)\s*:\s*([\d.]+)/);
|
|
413
|
+
if (timeMatch) stats.solvingTime = parseFloat(timeMatch[1]);
|
|
414
|
+
|
|
415
|
+
var nodesMatch = output.match(/Nodes\s*:\s*(\d+)/);
|
|
416
|
+
if (nodesMatch) stats.nodes = parseInt(nodesMatch[1]);
|
|
417
|
+
|
|
418
|
+
var iterMatch = output.match(/LP Iterations\s*:\s*(\d+)/);
|
|
419
|
+
if (iterMatch) stats.iterations = parseInt(iterMatch[1]);
|
|
420
|
+
|
|
421
|
+
var gapMatch = output.match(/Gap\s*:\s*([\d.]+)\s*%/);
|
|
422
|
+
if (gapMatch) stats.gap = parseFloat(gapMatch[1]);
|
|
423
|
+
|
|
424
|
+
return stats;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function init(options) {
|
|
428
|
+
options = options || {};
|
|
429
|
+
|
|
430
|
+
if (isInitialized) {
|
|
431
|
+
return Promise.resolve();
|
|
432
|
+
}
|
|
433
|
+
if (initPromise) {
|
|
434
|
+
return initPromise;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
initPromise = new Promise(function(resolve, reject) {
|
|
438
|
+
try {
|
|
439
|
+
var wasmPath = options.wasmPath || (__SCIP_SCRIPT_DIR__ + 'scip.wasm');
|
|
440
|
+
|
|
441
|
+
createSCIP({
|
|
442
|
+
locateFile: function(path) {
|
|
443
|
+
if (path.endsWith('.wasm')) {
|
|
444
|
+
return wasmPath;
|
|
445
|
+
}
|
|
446
|
+
return path;
|
|
447
|
+
},
|
|
448
|
+
print: function(text) {
|
|
449
|
+
if (scipModule && scipModule.onStdout) {
|
|
450
|
+
scipModule.onStdout(text);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
printErr: function(text) {
|
|
454
|
+
if (scipModule && scipModule.onStderr) {
|
|
455
|
+
scipModule.onStderr(text);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}).then(function(module) {
|
|
459
|
+
scipModule = module;
|
|
460
|
+
|
|
461
|
+
if (scipModule.FS) {
|
|
462
|
+
try { scipModule.FS.mkdir('/problems'); } catch (e) {}
|
|
463
|
+
try { scipModule.FS.mkdir('/solutions'); } catch (e) {}
|
|
464
|
+
try { scipModule.FS.mkdir('/settings'); } catch (e) {}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
isInitialized = true;
|
|
468
|
+
if (readyResolve) readyResolve();
|
|
469
|
+
resolve();
|
|
470
|
+
}).catch(function(err) {
|
|
471
|
+
var error = new Error('SCIP WASM loading failed: ' + (err.message || err));
|
|
472
|
+
if (readyReject) readyReject(error);
|
|
473
|
+
reject(error);
|
|
474
|
+
});
|
|
475
|
+
} catch (err) {
|
|
476
|
+
if (readyReject) readyReject(err);
|
|
477
|
+
reject(err);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
return initPromise;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function solve(problem, options) {
|
|
485
|
+
options = options || {};
|
|
486
|
+
|
|
487
|
+
var doSolve = function() {
|
|
488
|
+
var format = options.format || 'lp';
|
|
489
|
+
var timeLimit = options.timeLimit || 3600;
|
|
490
|
+
var gap = options.gap || null;
|
|
491
|
+
var verbose = options.verbose || false;
|
|
492
|
+
var parameters = options.parameters || {};
|
|
493
|
+
|
|
494
|
+
var stdout = '';
|
|
495
|
+
var stderr = '';
|
|
496
|
+
|
|
497
|
+
scipModule.onStdout = function(text) {
|
|
498
|
+
stdout += text + '\n';
|
|
499
|
+
if (verbose) console.log('[SCIP]', text);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
scipModule.onStderr = function(text) {
|
|
503
|
+
stderr += text + '\n';
|
|
504
|
+
if (verbose) console.error('[SCIP Error]', text);
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
var formatExtMap = { mps: 'mps', zpl: 'zpl', cip: 'cip', lp: 'lp' };
|
|
509
|
+
var ext = formatExtMap[format] || 'lp';
|
|
510
|
+
var problemFile = '/problems/problem.' + ext;
|
|
511
|
+
var solutionFile = '/solutions/solution.sol';
|
|
512
|
+
|
|
513
|
+
scipModule.FS.writeFile(problemFile, problem);
|
|
514
|
+
|
|
515
|
+
var commands = [];
|
|
516
|
+
commands.push('set limits time ' + timeLimit);
|
|
517
|
+
|
|
518
|
+
if (gap !== null) {
|
|
519
|
+
commands.push('set limits gap ' + gap);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
for (var key in parameters) {
|
|
523
|
+
if (parameters.hasOwnProperty(key)) {
|
|
524
|
+
commands.push('set ' + key + ' ' + parameters[key]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
commands.push('read ' + problemFile);
|
|
529
|
+
commands.push('optimize');
|
|
530
|
+
commands.push('display solution');
|
|
531
|
+
commands.push('write solution ' + solutionFile);
|
|
532
|
+
commands.push('display statistics');
|
|
533
|
+
commands.push('quit');
|
|
534
|
+
|
|
535
|
+
var settingsContent = commands.join('\n');
|
|
536
|
+
scipModule.FS.writeFile('/settings/commands.txt', settingsContent);
|
|
537
|
+
|
|
538
|
+
var exitCode = scipModule.callMain(['-b', '/settings/commands.txt']);
|
|
539
|
+
|
|
540
|
+
// Read rawSolution first (more reliable for parsing)
|
|
247
541
|
var rawSolution = null;
|
|
248
542
|
try {
|
|
249
543
|
rawSolution = scipModule.FS.readFile(solutionFile, { encoding: 'utf8' });
|
|
250
544
|
} catch (e) {}
|
|
251
545
|
|
|
546
|
+
var status = parseStatus(stdout);
|
|
547
|
+
var parsed = parseSolution(stdout, rawSolution);
|
|
548
|
+
var statistics = parseStatistics(stdout);
|
|
549
|
+
|
|
252
550
|
// Cleanup
|
|
253
551
|
var cleanupFiles = [
|
|
254
552
|
'/problems/problem.lp', '/problems/problem.mps',
|