linny-r 2.0.8 → 2.0.9
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 +3 -40
- package/package.json +1 -1
- package/server.js +19 -157
- package/static/index.html +58 -20
- package/static/linny-r.css +20 -16
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +50 -72
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +43 -41
- package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
- package/static/scripts/linny-r-gui-controller.js +254 -230
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -36
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
- package/static/scripts/linny-r-gui-equation-manager.js +22 -22
- package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
- package/static/scripts/linny-r-gui-file-manager.js +42 -48
- package/static/scripts/linny-r-gui-finder.js +105 -51
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
- package/static/scripts/linny-r-gui-monitor.js +35 -41
- package/static/scripts/linny-r-gui-paper.js +42 -70
- package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
- package/static/scripts/linny-r-gui-receiver.js +1 -2
- package/static/scripts/linny-r-gui-repository-browser.js +44 -46
- package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
- package/static/scripts/linny-r-gui-undo-redo.js +94 -95
- package/static/scripts/linny-r-milp.js +20 -24
- package/static/scripts/linny-r-model.js +1832 -2248
- package/static/scripts/linny-r-utils.js +27 -27
- package/static/scripts/linny-r-vm.js +801 -905
- package/static/show-png.html +0 -113
@@ -72,19 +72,17 @@ class UndoEdit {
|
|
72
72
|
}
|
73
73
|
|
74
74
|
setSelection() {
|
75
|
-
// Compile the list of IDs of selected entities
|
75
|
+
// Compile the list of IDs of selected entities.
|
76
76
|
this.selection.length = 0;
|
77
|
-
for(
|
78
|
-
this.selection.push(MODEL.selection[i].identifier);
|
79
|
-
}
|
77
|
+
for(const obj of MODEL.selection) this.selection.push(obj.identifier);
|
80
78
|
}
|
81
79
|
|
82
80
|
get getSelection() {
|
83
|
-
// Return the list of entities that were selected at the time of the action
|
81
|
+
// Return the list of entities that were selected at the time of the action.
|
84
82
|
const ol = [];
|
85
|
-
for(
|
86
|
-
const obj = MODEL.objectByID(
|
87
|
-
// Guard against pushing NULL pointers in case object is not found
|
83
|
+
for(const id of this.selection) {
|
84
|
+
const obj = MODEL.objectByID(id);
|
85
|
+
// Guard against pushing NULL pointers in case object is not found.
|
88
86
|
if(obj) ol.push(obj);
|
89
87
|
}
|
90
88
|
return ol;
|
@@ -93,8 +91,8 @@ class UndoEdit {
|
|
93
91
|
|
94
92
|
|
95
93
|
// CLASS UndoStack
|
96
|
-
// NOTE:
|
97
|
-
// and one with redoable actions
|
94
|
+
// NOTE: This object actually comprises TWO stacks -- one with undoable actions
|
95
|
+
// and one with redoable actions.
|
98
96
|
class UndoStack {
|
99
97
|
constructor() {
|
100
98
|
this.undoables = [];
|
@@ -108,36 +106,36 @@ class UndoStack {
|
|
108
106
|
}
|
109
107
|
|
110
108
|
get topUndo() {
|
111
|
-
// Return the short name of the top undoable action (if any)
|
112
|
-
const
|
113
|
-
if(
|
109
|
+
// Return the short name of the top undoable action (if any).
|
110
|
+
const n = this.undoables.length;
|
111
|
+
if(n > 0) return this.undoables[n - 1].action;
|
114
112
|
return false;
|
115
113
|
}
|
116
114
|
|
117
115
|
get canUndo() {
|
118
|
-
// Return the "display name" of the top undoable action (if any)
|
119
|
-
const
|
120
|
-
if(
|
116
|
+
// Return the "display name" of the top undoable action (if any).
|
117
|
+
const n = this.undoables.length;
|
118
|
+
if(n > 0) return `Undo "${this.undoables[n - 1].fullAction}"`;
|
121
119
|
return false;
|
122
120
|
}
|
123
121
|
|
124
122
|
get topRedo() {
|
125
|
-
// Return the short name of the top undoable action (if any)
|
126
|
-
const
|
127
|
-
if(
|
123
|
+
// Return the short name of the top undoable action (if any).
|
124
|
+
const n = this.redoables.length;
|
125
|
+
if(n > 0) return this.redoables[n - 1].action;
|
128
126
|
return false;
|
129
127
|
}
|
130
128
|
|
131
129
|
get canRedo() {
|
132
|
-
// Return the "display name" of the top redoable action (if any)
|
133
|
-
const
|
134
|
-
if(
|
130
|
+
// Return the "display name" of the top redoable action (if any).
|
131
|
+
const n = this.redoables.length;
|
132
|
+
if(n > 0) return `Redo "${this.redoables[n - 1].fullAction}"`;
|
135
133
|
return false;
|
136
134
|
}
|
137
135
|
|
138
136
|
addXML(xml) {
|
139
137
|
// Insert xml at the start (!) of any XML added previously to the UndoEdit
|
140
|
-
// at the top of the UNDO stack
|
138
|
+
// at the top of the UNDO stack.
|
141
139
|
const i = this.undoables.length;
|
142
140
|
if(i === 0) return false;
|
143
141
|
this.undoables[i-1].xml = xml + this.undoables[i-1].xml;
|
@@ -145,7 +143,7 @@ class UndoStack {
|
|
145
143
|
|
146
144
|
addOffset(dx, dy) {
|
147
145
|
// Add (dx, dy) to the offset of the "move" UndoEdit that should be at the
|
148
|
-
// top of the UNDO stack
|
146
|
+
// top of the UNDO stack.
|
149
147
|
let i = this.undoables.length;
|
150
148
|
if(i === 0) return false;
|
151
149
|
this.undoables[i-1].properties[3] += dx;
|
@@ -154,115 +152,116 @@ class UndoStack {
|
|
154
152
|
|
155
153
|
push(action, args=null, tentative=false) {
|
156
154
|
// Add an UndoEdit to the undo stack, labeled with edit action that is
|
157
|
-
// about to be performed.
|
158
|
-
//
|
159
|
-
//
|
155
|
+
// about to be performed.
|
156
|
+
// NOTE: The IDs of objects are stored, rather than the objects themselves,
|
157
|
+
// because deleted objects will have different memory addresses when
|
158
|
+
// restored by an UNDO.
|
160
159
|
|
161
|
-
// Any action except "move" is likely to invalidate the solver result
|
160
|
+
// Any action except "move" is likely to invalidate the solver result.
|
162
161
|
if(action !== 'move' && !(
|
163
162
|
// Exceptions:
|
164
163
|
// (1) adding/modifying notes
|
165
164
|
(args instanceof Note)
|
166
165
|
)) VM.reset();
|
167
166
|
|
168
|
-
// If this edit is new (i.e., not a redo) then remove all "redoable" edits
|
167
|
+
// If this edit is new (i.e., not a redo) then remove all "redoable" edits.
|
169
168
|
if(!tentative) this.redoables.length = 0;
|
170
|
-
// If the undo stack is full then discard its bottom edit
|
169
|
+
// If the undo stack is full, then discard its bottom edit.
|
171
170
|
if(this.undoables.length == CONFIGURATION.undo_stack_size) this.undoables.splice(0, 1);
|
172
171
|
const ue = new UndoEdit(action);
|
173
|
-
// For specific actions, store the IDs of the selected entities
|
172
|
+
// For specific actions, store the IDs of the selected entities.
|
174
173
|
if(['move', 'delete', 'drop', 'lift'].indexOf(action) >= 0) {
|
175
174
|
ue.setSelection();
|
176
175
|
}
|
177
|
-
// Set the properties of this undoable, depending on the type of action
|
176
|
+
// Set the properties of this undoable, depending on the type of action.
|
178
177
|
if(action === 'move') {
|
179
|
-
// `args` holds the dragged node => store its ID and position
|
180
|
-
// NOTE:
|
178
|
+
// `args` holds the dragged node => store its ID and position.
|
179
|
+
// NOTE: For products, use their ProductPosition in the focal cluster.
|
181
180
|
const obj = (args instanceof Product ?
|
182
181
|
args.positionInFocalCluster : args);
|
183
182
|
ue.properties = [args.identifier, obj.x, obj.y, 0, 0];
|
184
183
|
// NOTE: object_id is NOT set, as dragged selection may contain
|
185
|
-
// multiple entities
|
184
|
+
// multiple entities.
|
186
185
|
} else if(action === 'add') {
|
187
|
-
// `args` holds the added entity => store its ID
|
186
|
+
// `args` holds the added entity => store its ID.
|
188
187
|
ue.object_id = args.identifier;
|
189
188
|
} else if(action === 'drop' || action === 'lift') {
|
190
|
-
// Store ID of target cluster
|
189
|
+
// Store ID of target cluster.
|
191
190
|
ue.object_id = args.identifier;
|
192
191
|
ue.properties = MODEL.getSelectionPositions;
|
193
192
|
} else if(action === 'replace') {
|
194
|
-
// Replace passes its undo information as an object
|
193
|
+
// Replace passes its undo information as an object.
|
195
194
|
ue.properties = args;
|
196
195
|
}
|
197
196
|
|
198
|
-
// NOTE:
|
199
|
-
// restore deleted entities will be added by the respective delete methods
|
197
|
+
// NOTE: For a DELETE action, no properties are stored; the XML needed to
|
198
|
+
// restore deleted entities will be added by the respective delete methods.
|
200
199
|
|
201
|
-
// Push the new edit onto the UNDO stack
|
200
|
+
// Push the new edit onto the UNDO stack.
|
202
201
|
this.undoables.push(ue);
|
203
202
|
// Update the GUI buttons
|
204
203
|
UI.updateButtons();
|
205
|
-
// NOTE:
|
206
|
-
// the "prepare for undo" is performed before the actual change
|
204
|
+
// NOTE: Update the Finder only if needed, and with a timeout because
|
205
|
+
// the "prepare for undo" is performed before the actual change.
|
207
206
|
if(action !== 'move') setTimeout(() => { FINDER.updateDialog(); }, 5);
|
208
|
-
//console.log('push ' + action);
|
209
|
-
//console.log(UNDO_STACK);
|
210
207
|
}
|
211
208
|
|
212
209
|
pop(action='') {
|
213
|
-
// Remove the top edit (if any) from the stack if it has the specified action
|
214
|
-
// NOTE: pop does NOT undo the action (the model is not modified)
|
210
|
+
// Remove the top edit (if any) from the stack if it has the specified action.
|
211
|
+
// NOTE: `pop` does NOT undo the action (the model is not modified).
|
215
212
|
let i = this.undoables.length - 1;
|
216
213
|
if(i >= 0 && (action === '' || this.undoables[i].action === action)) {
|
217
214
|
this.undoables.pop();
|
218
215
|
UI.updateButtons();
|
219
216
|
}
|
220
|
-
//console.log('pop ' + action);
|
221
|
-
//console.log(UNDO_STACK);
|
222
217
|
}
|
223
218
|
|
224
219
|
doMove(ue) {
|
225
|
-
// This method implements shared code for UNDO and REDO of "move" actions
|
226
|
-
// First get the dragged node
|
220
|
+
// This method implements shared code for UNDO and REDO of "move" actions.
|
221
|
+
// First get the dragged node.
|
227
222
|
let obj = MODEL.objectByID(ue.properties[0]);
|
228
223
|
if(obj) {
|
229
|
-
// For products, use the x and y of the ProductPosition
|
224
|
+
// For products, use the x and y of the ProductPosition.
|
230
225
|
if(obj instanceof Product) obj = obj.positionInFocalCluster;
|
231
|
-
// Calculate the relative move (dx, dy)
|
226
|
+
// Calculate the relative move (dx, dy).
|
232
227
|
const
|
233
228
|
dx = ue.properties[1] - obj.x,
|
234
229
|
dy = ue.properties[2] - obj.y,
|
235
230
|
tdx = -ue.properties[3],
|
236
231
|
tdy = -ue.properties[4];
|
237
232
|
// Update the undo edit's x and y properties so that it can be pushed onto
|
238
|
-
// the other stack (as the dragged node ID and the selection
|
233
|
+
// the other stack (as the dragged node ID and the selection stay the same).
|
239
234
|
ue.properties[1] = obj.x;
|
240
235
|
ue.properties[2] = obj.y;
|
241
|
-
// Prepare to translate back
|
236
|
+
// Prepare to translate back. NOTE: this will also prepare for REDO.
|
242
237
|
ue.properties[3] = tdx;
|
243
238
|
ue.properties[4] = tdy;
|
244
|
-
// Translate the entire graph
|
239
|
+
// Translate the entire graph.
|
240
|
+
// NOTE: This does nothing if dx and dy both equal 0.
|
245
241
|
MODEL.translateGraph(tdx, tdy);
|
246
|
-
// Restore the selection as it was at the time of the "move" action
|
242
|
+
// Restore the selection as it was at the time of the "move" action.
|
247
243
|
MODEL.selectList(ue.getSelection);
|
248
|
-
// Move the selection back to its original position
|
244
|
+
// Move the selection back to its original position.
|
249
245
|
MODEL.moveSelection(dx - tdx, dy - tdy);
|
250
246
|
}
|
251
247
|
}
|
252
248
|
|
253
249
|
restoreFromXML(xml) {
|
254
250
|
// Restore deleted objects from XML and add them to the UndoEdit's selection
|
255
|
-
// (so that they can be RE-deleted)
|
251
|
+
// (so that they can be RE-deleted).
|
256
252
|
// NOTES:
|
257
253
|
// (1) Store focal cluster, because this may change while initializing a
|
258
|
-
// cluster from XML
|
254
|
+
// cluster from XML.
|
259
255
|
// (2) Set "selected" attribute of objects to FALSE, as the selection will
|
260
|
-
// be restored from UndoEdit
|
256
|
+
// be restored from UndoEdit.
|
261
257
|
const n = parseXML(MODEL.xml_header + `<edits>${xml}</edits>`);
|
262
258
|
if(n && n.childNodes) {
|
263
|
-
|
259
|
+
const
|
260
|
+
li = [],
|
261
|
+
ppi = [],
|
262
|
+
ci = [];
|
264
263
|
for(let i = 0; i < n.childNodes.length; i++) {
|
265
|
-
c = n.childNodes[i];
|
264
|
+
const c = n.childNodes[i];
|
266
265
|
// Immediately restore "independent" entities ...
|
267
266
|
if(c.nodeName === 'dataset') {
|
268
267
|
MODEL.addDataset(xmlDecoded(nodeContentByTag(c, 'name')), c);
|
@@ -281,7 +280,7 @@ class UndoStack {
|
|
281
280
|
obj.selected = false;
|
282
281
|
} else if(c.nodeName === 'chart') {
|
283
282
|
MODEL.addChart(xmlDecoded(nodeContentByTag(c, 'title')), c);
|
284
|
-
// ... but merely collect indices of other entities
|
283
|
+
// ... but merely collect indices of other entities.
|
285
284
|
} else if(c.nodeName === 'link' || c.nodeName === 'constraint') {
|
286
285
|
li.push(i);
|
287
286
|
} else if(c.nodeName === 'product-position') {
|
@@ -290,12 +289,12 @@ class UndoStack {
|
|
290
289
|
ci.push(i);
|
291
290
|
}
|
292
291
|
}
|
293
|
-
// NOTE:
|
294
|
-
// saves the effort to iterate over ALL childnodes again
|
295
|
-
// First restore links and constraints
|
296
|
-
for(
|
297
|
-
c = n.childNodes[
|
298
|
-
// Double-check that this node defines a link or a constraint
|
292
|
+
// NOTE: Collecting the indices of links, product positions and clusters
|
293
|
+
// saves the effort to iterate over ALL childnodes again.
|
294
|
+
// First restore links and constraints.
|
295
|
+
for(const i of li) {
|
296
|
+
const c = n.childNodes[i];
|
297
|
+
// Double-check that this node defines a link or a constraint.
|
299
298
|
if(c.nodeName === 'link' || c.nodeName === 'constraint') {
|
300
299
|
let name = xmlDecoded(nodeContentByTag(c, 'from-name'));
|
301
300
|
let actor = xmlDecoded(nodeContentByTag(c, 'from-owner'));
|
@@ -319,10 +318,10 @@ class UndoStack {
|
|
319
318
|
// Then restore product positions.
|
320
319
|
// NOTE: These correspond to the products that were part of the
|
321
320
|
// selection; all other product positions are restored as part of their
|
322
|
-
// containing clusters
|
323
|
-
for(
|
324
|
-
c = n.childNodes[
|
325
|
-
// Double-check that this node defines a product position
|
321
|
+
// containing clusters.
|
322
|
+
for(const i of ppi) {
|
323
|
+
const c = n.childNodes[i];
|
324
|
+
// Double-check that this node defines a product position.
|
326
325
|
if(c.nodeName === 'product-position') {
|
327
326
|
const obj = MODEL.nodeBoxByID(UI.nameToID(
|
328
327
|
xmlDecoded(nodeContentByTag(c, 'product-name'))));
|
@@ -334,10 +333,10 @@ class UndoStack {
|
|
334
333
|
}
|
335
334
|
// Lastly, restore clusters.
|
336
335
|
// NOTE: Store focal cluster, because this may change while initializing
|
337
|
-
// a cluster from XML
|
336
|
+
// a cluster from XML.
|
338
337
|
const fc = MODEL.focal_cluster;
|
339
|
-
for(
|
340
|
-
c = n.childNodes[
|
338
|
+
for(const i of ci) {
|
339
|
+
const c = n.childNodes[i];
|
341
340
|
if(c.nodeName === 'cluster') {
|
342
341
|
const obj = MODEL.addCluster(xmlDecoded(nodeContentByTag(c, 'name')),
|
343
342
|
xmlDecoded(nodeContentByTag(c, 'owner')), c);
|
@@ -350,7 +349,7 @@ if (MODEL.focal_cluster === fc) {
|
|
350
349
|
console.log('Refocusing from ... to ... : ', MODEL.focal_cluster, fc);
|
351
350
|
}
|
352
351
|
// Restore original focal cluster because addCluster may shift focus
|
353
|
-
// to a sub-cluster
|
352
|
+
// to a sub-cluster.
|
354
353
|
MODEL.focal_cluster = fc;
|
355
354
|
}
|
356
355
|
}
|
@@ -359,15 +358,15 @@ if (MODEL.focal_cluster === fc) {
|
|
359
358
|
}
|
360
359
|
|
361
360
|
undo() {
|
362
|
-
// Undo the most recent "undoable" action
|
361
|
+
// Undo the most recent "undoable" action.
|
363
362
|
let ue;
|
364
363
|
if(this.undoables.length > 0) {
|
365
364
|
UI.reset();
|
366
|
-
// Get the action to be undone
|
365
|
+
// Get the action to be undone.
|
367
366
|
ue = this.undoables.pop();
|
368
|
-
// Focus on the cluster that was focal at the time of action
|
369
|
-
// NOTE:
|
370
|
-
// clears the selection and redraws the graph
|
367
|
+
// Focus on the cluster that was focal at the time of action.
|
368
|
+
// NOTE: Do this WITHOUT calling UI.makeFocalCluster because this
|
369
|
+
// clears the selection and redraws the graph.
|
371
370
|
MODEL.focal_cluster = ue.cluster;
|
372
371
|
//console.log('undo' + ue.fullAction);
|
373
372
|
//console.log(ue);
|
@@ -441,7 +440,7 @@ if (MODEL.focal_cluster === fc) {
|
|
441
440
|
} else if(ue.action === 'replace') {
|
442
441
|
let uep = ue.properties,
|
443
442
|
p = MODEL.objectByName(uep.p);
|
444
|
-
// First check whether product P needs to be restored
|
443
|
+
// First check whether product P needs to be restored.
|
445
444
|
if(!p && ue.xml) {
|
446
445
|
const n = parseXML(MODEL.xml_header + `<edits>${ue.xml}</edits>`);
|
447
446
|
if(n && n.childNodes) {
|
@@ -454,36 +453,36 @@ if (MODEL.focal_cluster === fc) {
|
|
454
453
|
}
|
455
454
|
}
|
456
455
|
if(p) {
|
457
|
-
// Restore product position of P in focal cluster
|
456
|
+
// Restore product position of P in focal cluster.
|
458
457
|
MODEL.focal_cluster.addProductPosition(p, uep.x, uep.y);
|
459
|
-
// Restore links in/out of P
|
460
|
-
for(
|
461
|
-
const l = MODEL.linkByID(
|
458
|
+
// Restore links in/out of P.
|
459
|
+
for(const id of uep.lt) {
|
460
|
+
const l = MODEL.linkByID(id);
|
462
461
|
if(l) {
|
463
462
|
const ml = MODEL.addLink(l.from_node, p);
|
464
463
|
ml.copyPropertiesFrom(l);
|
465
464
|
MODEL.deleteLink(l);
|
466
465
|
}
|
467
466
|
}
|
468
|
-
for(
|
469
|
-
const l = MODEL.linkByID(
|
467
|
+
for(const id of uep.lf) {
|
468
|
+
const l = MODEL.linkByID(id);
|
470
469
|
if(l) {
|
471
470
|
const ml = MODEL.addLink(p, l.to_node);
|
472
471
|
ml.copyPropertiesFrom(l);
|
473
472
|
MODEL.deleteLink(l);
|
474
473
|
}
|
475
474
|
}
|
476
|
-
// Restore constraints on/by P
|
477
|
-
for(
|
478
|
-
const c = MODEL.constraintByID(
|
475
|
+
// Restore constraints on/by P.
|
476
|
+
for(const id of uep.ct) {
|
477
|
+
const c = MODEL.constraintByID(id);
|
479
478
|
if(c) {
|
480
479
|
const mc = MODEL.addConstraint(c.from_node, p);
|
481
480
|
mc.copyPropertiesFrom(c);
|
482
481
|
MODEL.deleteConstraint(c);
|
483
482
|
}
|
484
483
|
}
|
485
|
-
for(
|
486
|
-
const c = MODEL.constraintByID(
|
484
|
+
for(const id of uep.cf) {
|
485
|
+
const c = MODEL.constraintByID(id);
|
487
486
|
if(c) c.fromNode = p;
|
488
487
|
if(c) {
|
489
488
|
const mc = MODEL.addConstraint(p, c.to_node);
|
@@ -96,12 +96,12 @@ module.exports = class MILPSolver {
|
|
96
96
|
windows = os.platform().startsWith('win'),
|
97
97
|
path_list = process.env.PATH.split(path.delimiter);
|
98
98
|
// Iterate over all seprate paths in environment variable PATH.
|
99
|
-
for(
|
99
|
+
for(const p of path_list) {
|
100
100
|
// Assume that path is not a solver path.
|
101
101
|
sp = '';
|
102
102
|
// Check whether it is a Gurobi path.
|
103
|
-
match =
|
104
|
-
if(match) sp =
|
103
|
+
match = p.match(/gurobi(\d+)/i);
|
104
|
+
if(match) sp = p;
|
105
105
|
// If so, ensure that it has a higher version number.
|
106
106
|
if(sp && parseInt(match[1]) > max_vn) {
|
107
107
|
// Check whether command line version is executable.
|
@@ -119,10 +119,10 @@ module.exports = class MILPSolver {
|
|
119
119
|
}
|
120
120
|
if(sp) continue;
|
121
121
|
// If no Gurobi path, check whether it is a MOSEK path.
|
122
|
-
match =
|
122
|
+
match = p.match(/[\/\\]mosek.*[\/\\]bin/i);
|
123
123
|
if(match) {
|
124
124
|
// Check whether mosek(.exe) exists in its directory
|
125
|
-
sp = path.join(
|
125
|
+
sp = path.join(p, 'mosek' + (windows ? '.exe' : ''));
|
126
126
|
try {
|
127
127
|
fs.accessSync(sp, fs.constants.X_OK);
|
128
128
|
console.log('Path to MOSEK:', sp);
|
@@ -134,17 +134,18 @@ module.exports = class MILPSolver {
|
|
134
134
|
}
|
135
135
|
if(sp) continue;
|
136
136
|
// If no MOSEK path, check whether it is a CPLEX path.
|
137
|
-
match =
|
137
|
+
match = p.match(/[\/\\]cplex[\/\\]bin/i);
|
138
138
|
if(match) {
|
139
|
-
sp =
|
139
|
+
sp = p;
|
140
140
|
} else {
|
141
141
|
// CPLEX may create its own environment variable for its paths.
|
142
|
-
match =
|
142
|
+
match = p.match(/%(.*cplex.*)%/i);
|
143
143
|
if(match) {
|
144
|
-
const
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
for(const cp of process.env[match[1]].split(path.delimiter)) {
|
145
|
+
if(cp.match(/[\/\\]cplex[\/\\]bin/i)) {
|
146
|
+
sp = cp;
|
147
|
+
break;
|
148
|
+
}
|
148
149
|
}
|
149
150
|
}
|
150
151
|
}
|
@@ -162,10 +163,10 @@ module.exports = class MILPSolver {
|
|
162
163
|
continue;
|
163
164
|
}
|
164
165
|
// If no CPLEX path, check whether it is a SCIP path.
|
165
|
-
match =
|
166
|
+
match = p.match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
|
166
167
|
if(match) {
|
167
168
|
// Check whether scip(.exe) exists in its directory
|
168
|
-
sp = path.join(
|
169
|
+
sp = path.join(p, 'scip' + (windows ? '.exe' : ''));
|
169
170
|
try {
|
170
171
|
fs.accessSync(sp, fs.constants.X_OK);
|
171
172
|
console.log('Path to SCIP:', sp);
|
@@ -569,11 +570,9 @@ module.exports = class MILPSolver {
|
|
569
570
|
const vlist = Object.keys(x_dict).sort();
|
570
571
|
// Start with column 1.
|
571
572
|
let col = 1;
|
572
|
-
for(
|
573
|
-
|
574
|
-
|
575
|
-
// Variable names have zero-padded column numbers, e.g. "X001".
|
576
|
-
vnr = parseInt(v.substring(1));
|
573
|
+
for(const v of vlist) {
|
574
|
+
// Variable names have zero-padded column numbers, e.g. "X001".
|
575
|
+
const vnr = parseInt(v.substring(1));
|
577
576
|
// Add zeros for unreported variables until column number matches.
|
578
577
|
while(col < vnr) {
|
579
578
|
x_values.push(0);
|
@@ -631,9 +630,7 @@ module.exports = class MILPSolver {
|
|
631
630
|
// Values of solution vector.
|
632
631
|
if(sol.Vars) {
|
633
632
|
// Fill dictionary with variable name: value entries.
|
634
|
-
for(
|
635
|
-
x_dict[sol.Vars[i].VarName] = sol.Vars[i].X;
|
636
|
-
}
|
633
|
+
for(const sv of sol.Vars) x_dict[sv.VarName] = sv.X;
|
637
634
|
// Fill the solution vector, adding 0 for missing columns.
|
638
635
|
getValuesFromDict();
|
639
636
|
}
|
@@ -805,8 +802,7 @@ module.exports = class MILPSolver {
|
|
805
802
|
console.log('No SCIP solution file');
|
806
803
|
}
|
807
804
|
// Look in messages for solver status and solving time.
|
808
|
-
for(
|
809
|
-
const m = result.messages[i];
|
805
|
+
for(const m of result.messages) {
|
810
806
|
if(m.startsWith('SCIP Status')) {
|
811
807
|
if(m.indexOf('problem is solved') >= 0) {
|
812
808
|
if(m.indexOf('infeasible') >= 0) {
|