@areb0s/scip.js 1.2.4 → 1.2.6

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.
@@ -25,10 +25,6 @@ let initPromise = null;
25
25
  let readyResolve = null;
26
26
  let readyReject = null;
27
27
 
28
- // Exception tracking for debugging WASM crashes
29
- let lastAbortReason = null;
30
- let lastExitCode = null;
31
-
32
28
  /**
33
29
  * Ready promise - resolves when SCIP is initialized
34
30
  * Usage: await SCIP.ready;
@@ -38,16 +34,12 @@ export const ready = new Promise((resolve, reject) => {
38
34
  readyReject = reject;
39
35
  });
40
36
 
41
- // Read version from package.json
42
- const packageJson = JSON.parse(
43
- readFileSync(join(rootDir, "package.json"), "utf-8")
44
- );
45
- const VERSION = packageJson.version;
46
-
47
37
  /**
48
38
  * Default CDN base URL for WASM files
39
+ * Using npm CDN with specific version to ensure JS/WASM version consistency
49
40
  */
50
- const DEFAULT_CDN_BASE = `https://cdn.jsdelivr.net/npm/@areb0s/scip.js@${VERSION}/dist/`;
41
+ const DEFAULT_CDN_BASE =
42
+ "https://cdn.jsdelivr.net/npm/@areb0s/scip.js@latest/dist/";
51
43
 
52
44
  /**
53
45
  * Get base URL from global SCIP_BASE_URL or default CDN
@@ -103,32 +95,29 @@ function parseStatus(output) {
103
95
 
104
96
  /**
105
97
  * Parse solution values from SCIP output
106
- * @param {string} output - stdout from SCIP
107
- * @param {string} rawSolution - solution file content (more reliable)
108
98
  */
109
- function parseSolution(output, rawSolution = null) {
99
+ function parseSolution(output) {
110
100
  const variables = {};
111
101
  const objective = { value: null, sense: null };
112
102
 
113
- // Use rawSolution if available (more reliable)
114
- const solText = rawSolution || output;
115
-
116
103
  // Parse objective value
117
- const objMatch = solText.match(/objective value:\s*([\d.e+-]+)/i);
104
+ const objMatch = output.match(/objective value:\s*([\d.e+-]+)/i);
118
105
  if (objMatch) {
119
106
  objective.value = parseFloat(objMatch[1]);
120
107
  }
121
108
 
122
109
  // Parse variable values from solution display
123
- // Match ZIMPL-style variable names: x$sun#0, effSum$star#1, b_sun_10, etc.
124
- // Format: variableName value (obj:coef)
125
- const varRegex = /^([\w$#]+)\s+([\d.e+-]+)/gm;
110
+ // Format: variable_name value (obj:coef)
111
+ const varRegex = /^(\w+)\s+([\d.e+-]+)/gm;
126
112
  let match;
127
113
 
128
- while ((match = varRegex.exec(solText)) !== null) {
114
+ // Look for solution section
115
+ const solSection = output.split("solution:")[1] || output;
116
+
117
+ while ((match = varRegex.exec(solSection)) !== null) {
129
118
  const name = match[1];
130
119
  const value = parseFloat(match[2]);
131
- if (!isNaN(value) && name !== "objective" && name !== "solution") {
120
+ if (!isNaN(value) && name !== "objective") {
132
121
  variables[name] = value;
133
122
  }
134
123
  }
@@ -203,17 +192,6 @@ export async function init(options = {}) {
203
192
  scipModule.onStderr(text);
204
193
  }
205
194
  },
206
- // Capture abort/exit reasons for better error messages
207
- onAbort: (reason) => {
208
- lastAbortReason = reason;
209
- console.error("[SCIP WASM Abort]", reason);
210
- },
211
- onExit: (code) => {
212
- lastExitCode = code;
213
- if (code !== 0) {
214
- console.error("[SCIP WASM Exit]", code);
215
- }
216
- },
217
195
  });
218
196
 
219
197
  // Create directories for problems, solutions, settings
@@ -303,18 +281,19 @@ export function isReady() {
303
281
  * console.log(result.objective); // 1.0
304
282
  * console.log(result.variables); // { x: 1, y: 0 }
305
283
  */
306
- export async function solve(problem, options = {}) {
307
- if (!isInitialized) {
308
- await init(options);
309
- }
310
-
311
- const {
312
- format = "lp",
313
- timeLimit = 3600,
314
- gap = null,
315
- verbose = false,
316
- parameters = {},
317
- } = options;
284
+ export async function solve(problem, options = {}) {
285
+ if (!isInitialized) {
286
+ await init(options);
287
+ }
288
+
289
+ const {
290
+ format = "lp",
291
+ timeLimit = 3600,
292
+ gap = null,
293
+ verbose = false,
294
+ parameters = {},
295
+ initialSolution = null, // Warm start: { varName: value, ... }
296
+ } = options;
318
297
 
319
298
  // Capture output
320
299
  let stdout = "";
@@ -350,13 +329,29 @@ export async function solve(problem, options = {}) {
350
329
  commands.push(`set limits gap ${gap}`);
351
330
  }
352
331
 
353
- // Custom parameters
354
- for (const [key, value] of Object.entries(parameters)) {
355
- commands.push(`set ${key} ${value}`);
356
- }
357
-
358
- // Read and solve
359
- commands.push(`read ${problemFile}`);
332
+ // Custom parameters
333
+ // SCIP CLI format: "set category param value" (space-separated, not slash)
334
+ for (const [key, value] of Object.entries(parameters)) {
335
+ // Convert "limits/objectivelimit" to "limits objectivelimit"
336
+ const paramPath = key.replace(/\//g, ' ');
337
+ commands.push(`set ${paramPath} ${value}`);
338
+ }
339
+
340
+ // Read problem
341
+ commands.push(`read ${problemFile}`);
342
+
343
+ // Warm start: write and read initial solution
344
+ if (initialSolution && Object.keys(initialSolution).length > 0) {
345
+ const solLines = ['solution status: unknown'];
346
+ for (const [varName, value] of Object.entries(initialSolution)) {
347
+ if (value !== 0) {
348
+ solLines.push(`${varName} ${value}`);
349
+ }
350
+ }
351
+ const initialSolutionFile = '/solutions/initial.sol';
352
+ scipModule.FS.writeFile(initialSolutionFile, solLines.join('\n'));
353
+ commands.push(`read ${initialSolutionFile}`);
354
+ }
360
355
  commands.push("optimize");
361
356
  commands.push("display solution");
362
357
  commands.push(`write solution ${solutionFile}`);
@@ -370,7 +365,12 @@ export async function solve(problem, options = {}) {
370
365
  // Run SCIP with batch mode
371
366
  const exitCode = scipModule.callMain(["-b", "/settings/commands.txt"]);
372
367
 
373
- // Try to read solution file first (more reliable for parsing)
368
+ // Parse results
369
+ const status = parseStatus(stdout);
370
+ const { variables, objective } = parseSolution(stdout);
371
+ const statistics = parseStatistics(stdout);
372
+
373
+ // Try to read solution file
374
374
  let rawSolution = null;
375
375
  try {
376
376
  rawSolution = scipModule.FS.readFile(solutionFile, { encoding: "utf8" });
@@ -378,11 +378,6 @@ export async function solve(problem, options = {}) {
378
378
  // Solution file may not exist if infeasible
379
379
  }
380
380
 
381
- // Parse results
382
- const status = parseStatus(stdout);
383
- const { variables, objective } = parseSolution(stdout, rawSolution);
384
- const statistics = parseStatistics(stdout);
385
-
386
381
  return {
387
382
  status,
388
383
  objective: objective.value,
@@ -393,71 +388,22 @@ export async function solve(problem, options = {}) {
393
388
  rawSolution,
394
389
  };
395
390
  } catch (error) {
396
- // Attempt to extract detailed exception message from WASM
397
- let errorMessage = error.message || String(error);
398
- let exceptionInfo = null;
399
-
400
- // Check if this is a WASM exception (pointer address)
401
- if (typeof error === "number" || /^\d+$/.test(String(error))) {
402
- const ptr =
403
- typeof error === "number" ? error : parseInt(String(error), 10);
404
-
405
- // Try to get exception message using Emscripten's exception handling
406
- if (scipModule) {
407
- try {
408
- // Modern Emscripten exception handling
409
- if (typeof scipModule.getExceptionMessage === "function") {
410
- exceptionInfo = scipModule.getExceptionMessage(ptr);
411
- errorMessage = `WASM Exception: ${exceptionInfo}`;
412
- } else if (typeof scipModule.UTF8ToString === "function") {
413
- // Fallback: try to read as string from memory
414
- try {
415
- const str = scipModule.UTF8ToString(ptr);
416
- if (str && str.length > 0 && str.length < 1000) {
417
- exceptionInfo = str;
418
- errorMessage = `WASM Exception: ${str}`;
419
- }
420
- } catch (e) {
421
- /* not a valid string pointer */
422
- }
423
- }
424
- } catch (e) {
425
- console.error("[SCIP] Failed to get exception message:", e);
426
- }
427
- }
428
-
429
- if (!exceptionInfo) {
430
- errorMessage = `WASM Exception (ptr: ${ptr}). Enable exception handling in build for details.`;
431
- }
432
- }
433
-
434
391
  return {
435
392
  status: Status.ERROR,
436
- error: errorMessage,
437
- errorDetails: {
438
- rawError: String(error),
439
- exceptionInfo,
440
- abortReason: lastAbortReason,
441
- exitCode: lastExitCode,
442
- type: typeof error,
443
- stdout: stdout,
444
- stderr: stderr,
445
- },
393
+ error: error?.message || String(error) || "Unknown error",
446
394
  output: stdout + stderr,
447
395
  };
448
396
  } finally {
449
- // Reset exception tracking
450
- lastAbortReason = null;
451
- lastExitCode = null;
452
- // Cleanup all possible problem files
453
- const cleanupFiles = [
454
- "/problems/problem.lp",
455
- "/problems/problem.mps",
456
- "/problems/problem.zpl",
457
- "/problems/problem.cip",
458
- "/solutions/solution.sol",
459
- "/settings/commands.txt",
460
- ];
397
+ // Cleanup all possible problem files
398
+ const cleanupFiles = [
399
+ "/problems/problem.lp",
400
+ "/problems/problem.mps",
401
+ "/problems/problem.zpl",
402
+ "/problems/problem.cip",
403
+ "/solutions/solution.sol",
404
+ "/solutions/initial.sol",
405
+ "/settings/commands.txt",
406
+ ];
461
407
  for (const file of cleanupFiles) {
462
408
  try {
463
409
  scipModule.FS.unlink(file);
package/dist/types.d.ts CHANGED
@@ -45,6 +45,12 @@ export interface SolveOptions extends InitOptions {
45
45
  verbose?: boolean;
46
46
  /** Additional SCIP parameters */
47
47
  parameters?: Record<string, string | number | boolean>;
48
+ /**
49
+ * Initial solution hint for warm start
50
+ * Object mapping variable names to values
51
+ * @example { "x$sun#0": 1, "x$moon#5": 1 }
52
+ */
53
+ initialSolution?: Record<string, number> | null;
48
54
  }
49
55
 
50
56
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@areb0s/scip.js",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "SCIP Optimization Solver compiled to WebAssembly - LP, MIP, and MINLP support",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",