@areb0s/scip.js 1.1.5 → 1.1.7
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 +3 -313
- package/dist/scip-wrapper.js +15 -18
- package/dist/scip.js +0 -311
- package/dist/scip.min.js +9 -15
- package/dist/scip.wasm +0 -0
- package/package.json +66 -66
package/dist/scip-wrapper.js
CHANGED
|
@@ -88,32 +88,29 @@ 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)
|
|
93
91
|
*/
|
|
94
|
-
function parseSolution(output
|
|
92
|
+
function parseSolution(output) {
|
|
95
93
|
const variables = {};
|
|
96
94
|
const objective = { value: null, sense: null };
|
|
97
95
|
|
|
98
|
-
// Use rawSolution if available (more reliable)
|
|
99
|
-
const solText = rawSolution || output;
|
|
100
|
-
|
|
101
96
|
// Parse objective value
|
|
102
|
-
const objMatch =
|
|
97
|
+
const objMatch = output.match(/objective value:\s*([\d.e+-]+)/i);
|
|
103
98
|
if (objMatch) {
|
|
104
99
|
objective.value = parseFloat(objMatch[1]);
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
// Parse variable values from solution display
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
const varRegex = /^([\w$#]+)\s+([\d.e+-]+)/gm;
|
|
103
|
+
// Format: variable_name value (obj:coef)
|
|
104
|
+
const varRegex = /^(\w+)\s+([\d.e+-]+)/gm;
|
|
111
105
|
let match;
|
|
112
106
|
|
|
113
|
-
|
|
107
|
+
// Look for solution section
|
|
108
|
+
const solSection = output.split('solution:')[1] || output;
|
|
109
|
+
|
|
110
|
+
while ((match = varRegex.exec(solSection)) !== null) {
|
|
114
111
|
const name = match[1];
|
|
115
112
|
const value = parseFloat(match[2]);
|
|
116
|
-
if (!isNaN(value) && name !== 'objective'
|
|
113
|
+
if (!isNaN(value) && name !== 'objective') {
|
|
117
114
|
variables[name] = value;
|
|
118
115
|
}
|
|
119
116
|
}
|
|
@@ -332,7 +329,12 @@ export async function solve(problem, options = {}) {
|
|
|
332
329
|
// Run SCIP with batch mode
|
|
333
330
|
const exitCode = scipModule.callMain(['-b', '/settings/commands.txt']);
|
|
334
331
|
|
|
335
|
-
//
|
|
332
|
+
// Parse results
|
|
333
|
+
const status = parseStatus(stdout);
|
|
334
|
+
const { variables, objective } = parseSolution(stdout);
|
|
335
|
+
const statistics = parseStatistics(stdout);
|
|
336
|
+
|
|
337
|
+
// Try to read solution file
|
|
336
338
|
let rawSolution = null;
|
|
337
339
|
try {
|
|
338
340
|
rawSolution = scipModule.FS.readFile(solutionFile, { encoding: 'utf8' });
|
|
@@ -340,11 +342,6 @@ export async function solve(problem, options = {}) {
|
|
|
340
342
|
// Solution file may not exist if infeasible
|
|
341
343
|
}
|
|
342
344
|
|
|
343
|
-
// Parse results
|
|
344
|
-
const status = parseStatus(stdout);
|
|
345
|
-
const { variables, objective } = parseSolution(stdout, rawSolution);
|
|
346
|
-
const statistics = parseStatistics(stdout);
|
|
347
|
-
|
|
348
345
|
return {
|
|
349
346
|
status,
|
|
350
347
|
objective: objective.value,
|
package/dist/scip.js
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
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
|
-
|
|
26
2
|
/**
|
|
27
3
|
* SCIP.js Browser Bundle
|
|
28
4
|
* Supports: LP, MIP, MINLP (Mixed Integer Nonlinear Programming)
|
|
@@ -346,291 +322,4 @@ var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=
|
|
|
346
322
|
console.error('[SCIP.js] Auto-initialization failed:', err.message);
|
|
347
323
|
});
|
|
348
324
|
|
|
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)
|
|
541
|
-
var rawSolution = null;
|
|
542
|
-
try {
|
|
543
|
-
rawSolution = scipModule.FS.readFile(solutionFile, { encoding: 'utf8' });
|
|
544
|
-
} catch (e) {}
|
|
545
|
-
|
|
546
|
-
var status = parseStatus(stdout);
|
|
547
|
-
var parsed = parseSolution(stdout, rawSolution);
|
|
548
|
-
var statistics = parseStatistics(stdout);
|
|
549
|
-
|
|
550
|
-
// Cleanup
|
|
551
|
-
var cleanupFiles = [
|
|
552
|
-
'/problems/problem.lp', '/problems/problem.mps',
|
|
553
|
-
'/problems/problem.zpl', '/problems/problem.cip',
|
|
554
|
-
'/solutions/solution.sol', '/settings/commands.txt'
|
|
555
|
-
];
|
|
556
|
-
for (var i = 0; i < cleanupFiles.length; i++) {
|
|
557
|
-
try { scipModule.FS.unlink(cleanupFiles[i]); } catch (e) {}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
return {
|
|
561
|
-
status: status,
|
|
562
|
-
objective: parsed.objective.value,
|
|
563
|
-
variables: parsed.variables,
|
|
564
|
-
statistics: statistics,
|
|
565
|
-
exitCode: exitCode,
|
|
566
|
-
output: verbose ? stdout : undefined,
|
|
567
|
-
rawSolution: rawSolution
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
} catch (error) {
|
|
571
|
-
return {
|
|
572
|
-
status: Status.ERROR,
|
|
573
|
-
error: error.message,
|
|
574
|
-
output: stdout + stderr
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
if (!isInitialized) {
|
|
580
|
-
return init(options).then(doSolve);
|
|
581
|
-
}
|
|
582
|
-
return Promise.resolve(doSolve());
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function minimize(problem, options) {
|
|
586
|
-
if (!problem.toLowerCase().includes('minimize')) {
|
|
587
|
-
problem = 'Minimize\n' + problem;
|
|
588
|
-
}
|
|
589
|
-
return solve(problem, options);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function maximize(problem, options) {
|
|
593
|
-
if (!problem.toLowerCase().includes('maximize')) {
|
|
594
|
-
problem = 'Maximize\n' + problem;
|
|
595
|
-
}
|
|
596
|
-
return solve(problem, options);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function version() {
|
|
600
|
-
var getVersion = function() {
|
|
601
|
-
var output = '';
|
|
602
|
-
scipModule.onStdout = function(text) { output += text + '\n'; };
|
|
603
|
-
scipModule.callMain(['--version']);
|
|
604
|
-
return output.trim();
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
if (!isInitialized) {
|
|
608
|
-
return init().then(getVersion);
|
|
609
|
-
}
|
|
610
|
-
return Promise.resolve(getVersion());
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function isReady() {
|
|
614
|
-
return isInitialized;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Expose SCIP API
|
|
618
|
-
var SCIP = {
|
|
619
|
-
init: init,
|
|
620
|
-
ready: readyPromise,
|
|
621
|
-
isReady: isReady,
|
|
622
|
-
solve: solve,
|
|
623
|
-
minimize: minimize,
|
|
624
|
-
maximize: maximize,
|
|
625
|
-
version: version,
|
|
626
|
-
Status: Status
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
global.SCIP = SCIP;
|
|
630
|
-
|
|
631
|
-
// Auto-initialize
|
|
632
|
-
init().catch(function(err) {
|
|
633
|
-
console.error('[SCIP.js] Auto-initialization failed:', err.message);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
325
|
})(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : globalThis);
|