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.
Files changed (31) hide show
  1. package/README.md +3 -40
  2. package/package.json +1 -1
  3. package/server.js +19 -157
  4. package/static/index.html +58 -20
  5. package/static/linny-r.css +20 -16
  6. package/static/scripts/iro.min.js +7 -7
  7. package/static/scripts/linny-r-ctrl.js +50 -72
  8. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  9. package/static/scripts/linny-r-gui-chart-manager.js +43 -41
  10. package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
  11. package/static/scripts/linny-r-gui-controller.js +254 -230
  12. package/static/scripts/linny-r-gui-dataset-manager.js +17 -36
  13. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  14. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  15. package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
  16. package/static/scripts/linny-r-gui-file-manager.js +42 -48
  17. package/static/scripts/linny-r-gui-finder.js +105 -51
  18. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  19. package/static/scripts/linny-r-gui-monitor.js +35 -41
  20. package/static/scripts/linny-r-gui-paper.js +42 -70
  21. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  22. package/static/scripts/linny-r-gui-receiver.js +1 -2
  23. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  24. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  25. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  26. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  27. package/static/scripts/linny-r-milp.js +20 -24
  28. package/static/scripts/linny-r-model.js +1832 -2248
  29. package/static/scripts/linny-r-utils.js +27 -27
  30. package/static/scripts/linny-r-vm.js +801 -905
  31. 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(let i = 0; i < MODEL.selection.length; i++) {
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(let i = 0; i < this.selection.length; i++) {
86
- const obj = MODEL.objectByID(this.selection[i]);
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: this object actually comprises TWO stacks -- one with undoable actions
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 i = this.undoables.length;
113
- if(i > 0) return this.undoables[i - 1].action;
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 i = this.undoables.length;
120
- if(i > 0) return `Undo "${this.undoables[i - 1].fullAction}"`;
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 i = this.redoables.length;
127
- if(i > 0) return this.redoables[i - 1].action;
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 i = this.redoables.length;
134
- if(i > 0) return `Redo "${this.redoables[i - 1].fullAction}"`;
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. NOTE: the IDs of objects are stored, rather than
158
- // the objects themselves, because deleted objects will have different
159
- // memory addresses when restored by an UNDO
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: for products, use their ProductPosition in the focal cluster
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: for a DELETE action, no properties are stored; the XML needed to
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: update the Finder only if needed, and with a delay because
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 stays the same)
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 (NOTE: this will also prepare for REDO)
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 (NOTE: this does nothing if dx and dy both equal 0)
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
- let c, li = [], ppi = [], ci = [];
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: collecting the indices of links, product positions and clusters
294
- // saves the effort to iterate over ALL childnodes again
295
- // First restore links and constraints
296
- for(let i = 0; i < li.length; i++) {
297
- c = n.childNodes[li[i]];
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(let i = 0; i < ppi.length; i++) {
324
- c = n.childNodes[ppi[i]];
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(let i = 0; i < ci.length; i++) {
340
- c = n.childNodes[ci[i]];
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: do this WITHOUT calling UI.makeFocalCluster because this
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(let i = 0; i < uep.lt.length; i++) {
461
- const l = MODEL.linkByID(uep.lt[i]);
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(let i = 0; i < uep.lf.length; i++) {
469
- const l = MODEL.linkByID(uep.lf[i]);
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(let i = 0; i < uep.ct.length; i++) {
478
- const c = MODEL.constraintByID(uep.ct[i]);
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(let i = 0; i < uep.cf.length; i++) {
486
- const c = MODEL.constraintByID(uep.cf[i]);
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(let i = 0; i < path_list.length; i++) {
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 = path_list[i].match(/gurobi(\d+)/i);
104
- if(match) sp = path_list[i];
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 = path_list[i].match(/[\/\\]mosek.*[\/\\]bin/i);
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(path_list[i], 'mosek' + (windows ? '.exe' : ''));
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 = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
137
+ match = p.match(/[\/\\]cplex[\/\\]bin/i);
138
138
  if(match) {
139
- sp = path_list[i];
139
+ sp = p;
140
140
  } else {
141
141
  // CPLEX may create its own environment variable for its paths.
142
- match = path_list[i].match(/%(.*cplex.*)%/i);
142
+ match = p.match(/%(.*cplex.*)%/i);
143
143
  if(match) {
144
- const cpl = process.env[match[1]].split(path.delimiter);
145
- for(let i = 0; i < cpl.length && !sp; i++) {
146
- match = cpl[i].match(/[\/\\]cplex[\/\\]bin/i);
147
- if(match) sp = cpl[i];
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 = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
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(path_list[i], 'scip' + (windows ? '.exe' : ''));
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(let i = 0; i < vlist.length; i++) {
573
- const
574
- v = vlist[i],
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(let i = 0; i < sol.Vars.length; i++) {
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(let i = 0; i < result.messages.length; i++) {
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) {