linny-r 2.0.4 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/static/index.html CHANGED
@@ -378,7 +378,9 @@ and move the cursor over the status bar">
378
378
  title="Add cluster">
379
379
  <img id="note-btn" class="btn toggle enab sep" src="images/note.png"
380
380
  title="Add note">
381
- <img id="clone-btn" class="btn disab sep" src="images/clone.png"
381
+ <img id="replace-btn" class="btn disab" src="images/replace-product.png"
382
+ title="Replace selected product by some other product (Alt-P)">
383
+ <img id="clone-btn" class="btn disab" src="images/clone.png"
382
384
  title="Copy selection (Ctrl-C) &ndash; Alt-click to clone (Alt-C)">
383
385
  <img id="paste-btn" class="btn disab sep" src="images/paste.png"
384
386
  title="Paste selection (Ctrl-V)">
@@ -1375,6 +1377,9 @@ NOTE: Products directly linked to such processes should have a proportional unit
1375
1377
  <option id="link-shutdown" value="10">
1376
1378
  &#x25BC; (shut-down: 1 if X[t-1] > 0 &and; X[t] = 0, otherwise 0)
1377
1379
  </option>
1380
+ <option id="link-slack" value="12">
1381
+ &#x21A5; (available capacity: UB - X[t])
1382
+ </option>
1378
1383
  <option id="link-spinning" value="8">
1379
1384
  &#x2934; (spinning reserve: UB - X[t] if X[t] > 0, otherwise 0)
1380
1385
  </option>
@@ -418,7 +418,7 @@ class GUIController extends Controller {
418
418
  // Initialize controller buttons.
419
419
  this.node_btns = ['process', 'product', 'link', 'constraint',
420
420
  'cluster', 'module', 'note'];
421
- this.edit_btns = ['clone', 'paste', 'delete', 'undo', 'redo'];
421
+ this.edit_btns = ['replace', 'clone', 'paste', 'delete', 'undo', 'redo'];
422
422
  this.model_btns = ['settings', 'save', 'repository', 'actors',
423
423
  'dataset', 'equation', 'chart', 'sensitivity', 'experiment',
424
424
  'diagram', 'savediagram', 'finder', 'monitor', 'tex', 'solve'];
@@ -599,6 +599,8 @@ class GUIController extends Controller {
599
599
  UI.copySelection();
600
600
  }
601
601
  });
602
+ this.buttons.replace.addEventListener('click',
603
+ () => UI.replaceSelectedProduct());
602
604
  this.buttons.paste.addEventListener('click',
603
605
  () => UI.pasteSelection());
604
606
  this.buttons['delete'].addEventListener('click',
@@ -827,7 +829,7 @@ class GUIController extends Controller {
827
829
  this.modals.move.cancel.addEventListener('click',
828
830
  () => UI.doNotMoveNode());
829
831
 
830
- // The REPLACE dialog appears when a product is Ctrl-clicked.
832
+ // The REPLACE dialog appears when a product is Shift-Alt-clicked.
831
833
  this.modals.replace.ok.addEventListener('click',
832
834
  () => UI.replaceProduct());
833
835
  this.modals.replace.cancel.addEventListener('click',
@@ -925,6 +927,9 @@ class GUIController extends Controller {
925
927
  UNDO_STACK.clear();
926
928
  // Autosaving should start anew.
927
929
  AUTO_SAVE.setAutoSaveInterval();
930
+ // Finder dialog is closed, but may still display results for
931
+ // previous model.
932
+ FINDER.updateDialog();
928
933
  // Signal success or failure.
929
934
  return loaded;
930
935
  }
@@ -1132,7 +1137,6 @@ class GUIController extends Controller {
1132
1137
  'confirm when prompted by your browser.');
1133
1138
  // Hide "update" button in server dialog.
1134
1139
  UI.modals.server.element('update').style.display = 'none';
1135
- return;
1136
1140
  } else {
1137
1141
  // Inform user that install appears to have failed.
1138
1142
  msg.push(
@@ -1142,7 +1146,8 @@ class GUIController extends Controller {
1142
1146
  }
1143
1147
  md.element('msg').innerHTML = msg.join('<br>');
1144
1148
  // Reload `index.html`. This will start Linny-R anew.
1145
- window.open('./', '_self');
1149
+ // NOTE: Wait for 2 seconds so the message can be read.
1150
+ setTimeout(() => { window.open('./', '_self'); }, 2000);
1146
1151
  }
1147
1152
  })
1148
1153
  .catch((err) => {
@@ -1591,7 +1596,7 @@ class GUIController extends Controller {
1591
1596
  // Updates the buttons on the main GUI toolbars
1592
1597
  const
1593
1598
  node_btns = 'process product link constraint cluster note ',
1594
- edit_btns = 'clone paste delete undo redo ',
1599
+ edit_btns = 'replace clone paste delete undo redo ',
1595
1600
  model_btns = 'settings save actors dataset equation chart ' +
1596
1601
  'diagram savediagram finder monitor solve';
1597
1602
  if(MODEL === null) {
@@ -1617,9 +1622,30 @@ class GUIController extends Controller {
1617
1622
  this.enableButtons(node_btns + model_btns);
1618
1623
  this.active_button = this.stayActiveButton;
1619
1624
  this.disableButtons(edit_btns);
1620
- if(MODEL.selection.length > 0) this.enableButtons('clone delete');
1625
+ if(MODEL.selection.length > 0) {
1626
+ this.enableButtons('clone delete');
1627
+ // Replace applies only to a single product.
1628
+ if(MODEL.selection.length === 1) {
1629
+ const p = MODEL.selection[0];
1630
+ if(p instanceof Product) {
1631
+ const
1632
+ b = this.buttons.replace,
1633
+ t = 'Replace selected product by some other product (Alt-P)';
1634
+ // Differentiate between product types, as products can be
1635
+ // replaced only by products of the same type.
1636
+ if(p.is_data) {
1637
+ b.title = t.replaceAll('product', 'data product');
1638
+ b.src = 'images/replace-data-product.png';
1639
+ } else {
1640
+ b.title = t;
1641
+ b.src = 'images/replace-product.png';
1642
+ }
1643
+ this.enableButtons('replace');
1644
+ }
1645
+ }
1646
+ }
1621
1647
  if(this.canPaste) this.enableButtons('paste');
1622
- // Only allow target seeking when some target or process constraint is defined
1648
+ // Only allow soling when some target or process constraint is defined.
1623
1649
  if(MODEL.hasTargets) this.enableButtons('solve');
1624
1650
  var u = UNDO_STACK.canUndo;
1625
1651
  if(u) {
@@ -2315,13 +2341,16 @@ class GUIController extends Controller {
2315
2341
  } else if(alt && code === 'KeyR') {
2316
2342
  // Alt-R means: run to diagnose infeasible/unbounded problem.
2317
2343
  VM.solveModel(true);
2318
- } else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
2319
- // Special shortcut keys for "clone selection" and "model settings".
2344
+ } else if(alt && ['KeyC', 'KeyM', 'KeyP'].indexOf(code) >= 0) {
2345
+ // Special shortcut keys for "clone selection", "model settings"
2346
+ // and "replace product".
2320
2347
  const be = new Event('click');
2321
2348
  if(code === 'KeyC') {
2322
2349
  this.buttons.clone.dispatchEvent(be);
2323
- } else {
2350
+ } else if(code === 'KeyM') {
2324
2351
  this.buttons.settings.dispatchEvent(be);
2352
+ } else if(code === 'KeyP') {
2353
+ this.buttons.replace.dispatchEvent(be);
2325
2354
  }
2326
2355
  } else if(!e.shiftKey && !alt &&
2327
2356
  (!topmod || ['KeyA', 'KeyC', 'KeyV'].indexOf(code) < 0)) {
@@ -2333,7 +2362,9 @@ class GUIController extends Controller {
2333
2362
  CONSTRAINT_EDITOR.deleteBoundLine();
2334
2363
  } else if(!this.hidden('variable-modal')) {
2335
2364
  CHART_MANAGER.deleteVariable();
2336
- } else {
2365
+ } else if(!topmod) {
2366
+ // Do not delete entity from model diagram when some modal
2367
+ // is showing.
2337
2368
  this.buttons['delete'].dispatchEvent(new Event('click'));
2338
2369
  }
2339
2370
  } else if (code === 'Period' && (e.ctrlKey || e.metaKey)) {
@@ -4276,6 +4307,15 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4276
4307
  soc = 0;
4277
4308
  this.warn('Cost can only be attributed to level-based links');
4278
4309
  }
4310
+ // For multipliers requiring a binary variable, and also for those
4311
+ // based on the node's upper bound, warn the modeler when the UB for
4312
+ // this node is infinite or unspecified.
4313
+ if(VM.LM_NEEDING_ON_OFF.indexOf(m) >= 0 || m === VM.LM_AVAILABLE_CAPACITY) {
4314
+ if(!l.from_node.upper_bound.text) {
4315
+ UI.warn('Infinite upper bound of <strong>' + l.from_node.displayName +
4316
+ `</strong> will cause issues for ${VM.LM_SYMBOLS[m]} link`);
4317
+ }
4318
+ }
4279
4319
  // NOTE: share of cost is input as a percentage, but stored as a floating
4280
4320
  // point value between 0 and 1
4281
4321
  l.share_of_cost = soc / 100;
@@ -4307,9 +4347,17 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4307
4347
  'constraint-to-name').innerHTML = c.to_node.displayName;
4308
4348
  CONSTRAINT_EDITOR.showDialog();
4309
4349
  }
4350
+
4351
+ replaceSelectedProduct() {
4352
+ // Check whether selection contains one product, and if so, prompt
4353
+ // for replacement.
4354
+ if(MODEL.selection.length !== 1) return;
4355
+ const p = MODEL.selection[0];
4356
+ if(p instanceof Product) this.showReplaceProductDialog(p);
4357
+ }
4310
4358
 
4311
4359
  showReplaceProductDialog(p) {
4312
- // Prompts for a product (different from `p`) by which `p` should be
4360
+ // Prompt for a product (different from `p`) by which `p` should be
4313
4361
  // replaced for the selected product position
4314
4362
  const pp = MODEL.focal_cluster.indexOfProduct(p);
4315
4363
  if(pp >= 0) {
@@ -4076,16 +4076,24 @@ class LinnyRModel {
4076
4076
  replaceProduct(p, r, global) {
4077
4077
  const
4078
4078
  ppi = this.focal_cluster.indexOfProduct(p),
4079
- // NOTE: record whether `r` is show in focal cluster
4079
+ // NOTE: Record whether `r` is shown in focal cluster.
4080
4080
  rshown = this.focal_cluster.indexOfProduct(r) >= 0;
4081
4081
  // NOTE: since `ppi` should always be >= 0
4082
4082
  if(ppi >= 0) {
4083
- // Build list of information needed for "undo"
4084
- const undo_info = {p: p.displayName, r: r.displayName, g: global,
4085
- lf: [], lt: [], cf: [], ct: [], cl: []};
4086
- // Keep track of redirected links
4083
+ // Build list of information needed for "undo".
4084
+ const undo_info = {
4085
+ p: p.displayName,
4086
+ r: r.displayName,
4087
+ g: global,
4088
+ lf: [],
4089
+ lt: [],
4090
+ cf: [],
4091
+ ct: [],
4092
+ cl: []
4093
+ };
4094
+ // Keep track of redirected links.
4087
4095
  const rl = [];
4088
- // First replace product in (local) links
4096
+ // First replace product in (local) links.
4089
4097
  for(let i = p.inputs.length - 1; i >= 0; i--) {
4090
4098
  const l = p.inputs[i];
4091
4099
  if(global || l.hasArrow) {
@@ -4093,7 +4101,7 @@ class LinnyRModel {
4093
4101
  ml.copyPropertiesFrom(l);
4094
4102
  this.deleteLink(l);
4095
4103
  rl.push(ml);
4096
- // NOTE: push identifier of *modified* link
4104
+ // NOTE: push identifier of *modified* link.
4097
4105
  undo_info.lt.push(ml.identifier);
4098
4106
  }
4099
4107
  }
@@ -4107,7 +4115,7 @@ class LinnyRModel {
4107
4115
  }
4108
4116
  }
4109
4117
  // Then also replace product in (local) constraints
4110
- // (also keeping track of affected constraints)
4118
+ // (also keeping track of affected constraints).
4111
4119
  const rc = [];
4112
4120
  for(let k in this.constraints) {
4113
4121
  if(this.constraints.hasOwnProperty(k)) {
@@ -4125,26 +4133,26 @@ class LinnyRModel {
4125
4133
  }
4126
4134
  }
4127
4135
  }
4128
- // Replace `p` by `r` as the positioned product
4136
+ // Replace `p` by `r` as the positioned product.
4129
4137
  const pp = this.focal_cluster.product_positions[ppi];
4130
4138
  undo_info.x = pp.x;
4131
4139
  undo_info.y = pp.y;
4132
4140
  pp.product = r;
4133
- // Change coordinates only if `r` is also shown in the focal cluster
4141
+ // Change coordinates only if `r` is also shown in the focal cluster.
4134
4142
  if(rshown) {
4135
4143
  pp.x = r.x;
4136
4144
  pp.y = r.y;
4137
4145
  }
4138
- // Likewise replace product of other placeholders of `p` by `r`
4146
+ // Likewise replace product of other placeholders of `p` by `r`.
4139
4147
  for(let k in this.clusters) if(this.clusters.hasOwnProperty(k)) {
4140
4148
  const
4141
4149
  c = this.clusters[k],
4142
4150
  ppi = c.indexOfProduct(p);
4143
- // NOTE: when local, replace only if sub-cluster is in view
4151
+ // NOTE: When local, replace only if sub-cluster is in view...
4144
4152
  if(ppi >= 0 && (global || this.focal_cluster.containsCluster(c))) {
4145
4153
  const pp = c.product_positions[ppi];
4146
- // And then it MAY be that within this sub-cluster, the local
4147
- // links to `p` were NOT redirected
4154
+ // ... and then it MAY be that within this sub-cluster, the local
4155
+ // links to `p` were NOT redirected.
4148
4156
  const ll = [];
4149
4157
  for(let i = 0; i < p.inputs.length; i++) {
4150
4158
  const l = p.inputs[i];
@@ -4169,7 +4177,7 @@ class LinnyRModel {
4169
4177
  }
4170
4178
  // Now prepare for undo, so that deleteNode can add its XML
4171
4179
  UNDO_STACK.push('replace', undo_info);
4172
- // Delete original product `p` if it has no more product positions
4180
+ // Delete original product `p` if it has no more product positions.
4173
4181
  if(!this.top_cluster.containsProduct(p)) this.deleteNode(p);
4174
4182
  }
4175
4183
  // Prepare for redraw
@@ -2157,6 +2157,10 @@ class VirtualMachine {
2157
2157
  // diagnostic purposes -- see below.
2158
2158
  this.PLUS_INFINITY = 1e+25;
2159
2159
  this.MINUS_INFINITY = -1e+25;
2160
+ // Expression results having an infinite term may be less than infinity,
2161
+ // but still exceptionally high, and this should be shown.
2162
+ this.NEAR_PLUS_INFINITY = this.PLUS_INFINITY / 200;
2163
+ this.NEAR_MINUS_INFINITY = this.MINUS_INFINITY / 200;
2160
2164
  // As of version 1.8.0, Linny-R imposes no +INF bounds on processes
2161
2165
  // unless diagnosing an unbounded problem. For such diagnosis, the
2162
2166
  // (relatively) low value 9.999999999e+9 is used.
@@ -2218,11 +2222,12 @@ class VirtualMachine {
2218
2222
  this.LM_FIRST_COMMIT = 9; // Symbol: hollow asterisk
2219
2223
  this.LM_SHUTDOWN = 10; // Symbol: thick chevron down
2220
2224
  this.LM_PEAK_INC = 11; // Symbol: plus inside triangle ("peak-plus")
2225
+ this.LM_AVAILABLE_CAPACITY = 12; // Symbol: up-arrow with baseline
2221
2226
  // List of link multipliers that require binary ON/OFF variables
2222
2227
  this.LM_NEEDING_ON_OFF = [5, 6, 7, 8, 9, 10];
2223
2228
  this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u25B2',
2224
- '+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39'];
2225
- this.LM_LETTERS = ' TDSMU+0RFDP';
2229
+ '+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39', '\u21A5'];
2230
+ this.LM_LETTERS = ' TDSMU+0RFDPA';
2226
2231
 
2227
2232
  // VM max. expression stack size.
2228
2233
  this.MAX_STACK = 200;
@@ -2560,14 +2565,14 @@ class VirtualMachine {
2560
2565
  if(n <= this.CYCLIC) return [true, '#CYCLE!'];
2561
2566
  // Any other number less than or equal to 10^30 is considered as
2562
2567
  // minus infinity.
2563
- if(n <= this.MINUS_INFINITY) return [true, '-\u221E'];
2568
+ if(n <= this.NEAR_MINUS_INFINITY) return [true, '-\u221E'];
2564
2569
  // Other special values are very big POSITIVE numbers, so start
2565
2570
  // comparing `n` with the highest value.
2566
2571
  if(n >= this.COMPUTING) return [true, '\u25A6']; // Checkered square
2567
2572
  // NOTE: The prettier circled bold X 2BBF does not display on macOS !!
2568
2573
  if(n >= this.NOT_COMPUTED) return [true, '\u2297']; // Circled X
2569
2574
  if(n >= this.UNDEFINED) return [true, '\u2047']; // Double question mark ??
2570
- if(n >= this.PLUS_INFINITY) return [true, '\u221E'];
2575
+ if(n >= this.NEAR_PLUS_INFINITY) return [true, '\u221E'];
2571
2576
  if(n === this.NO_COST) return [true, '\u00A2']; // c-slash (cent symbol)
2572
2577
  return [false, n];
2573
2578
  }
@@ -3693,6 +3698,15 @@ class VirtualMachine {
3693
3698
  VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
3694
3699
  l.flow_delay, vi, l.from_node.upper_bound, tnpx,
3695
3700
  l.relative_rate]]);
3701
+ } else if(l.multiplier === VM.LM_REMAINING_CAPACITY) {
3702
+ // "remaining capacity" equals UB - level. This is a
3703
+ // simpler version of "spinning reserve". We signal this
3704
+ // by passing -1 as the index of the secondary variable,
3705
+ // and the level variable index as the primary variable.
3706
+ this.code.push([VMI_update_cash_coefficient, [
3707
+ VM.PRODUCE, VM.SPIN_RES, vi, // <-- now as primary
3708
+ l.flow_delay, -1, // <-- signal that it is "REM_CAP"
3709
+ l.from_node.upper_bound, tnpx, l.relative_rate]]);
3696
3710
  } else if(l.multiplier === VM.LM_PEAK_INC) {
3697
3711
  // NOTE: "peak increase" may be > 0 only in the first
3698
3712
  // time step of the block being optimized, and in the
@@ -4035,6 +4049,12 @@ class VirtualMachine {
4035
4049
  // NOTE: no delay on this type of link
4036
4050
  this.code.push([VMI_add_peak_increase_at_t_0,
4037
4051
  [vi, l.relative_rate]]);
4052
+ } else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
4053
+ // The "available capacity" equals UB - level, so subtract
4054
+ // UB * rate from RHS, while considering the delay.
4055
+ // NOTE: New instruction style that passes pointers to
4056
+ // model entities instead of their properties.
4057
+ this.code.push([VMI_add_available_capacity, l]);
4038
4058
  } else if(l.relative_rate.isStatic) {
4039
4059
  // Static rates permit simpler VM instructions
4040
4060
  c = l.relative_rate.result(0);
@@ -4311,7 +4331,7 @@ class VirtualMachine {
4311
4331
  hub = ub;
4312
4332
  if(ub > VM.MEGA_UPPER_BOUND) {
4313
4333
  hub = p.highestUpperBound([]);
4314
- // If UB still very high, warn modeler on infoline and in monitor
4334
+ // If UB still very high, warn modeler on infoline and in monitor.
4315
4335
  if(hub > VM.MEGA_UPPER_BOUND) {
4316
4336
  const msg = 'High upper bound (' + this.sig4Dig(hub) +
4317
4337
  ') for <strong>' + p.displayName + '</strong>' +
@@ -5005,6 +5025,8 @@ class VirtualMachine {
5005
5025
  } else if(l.multiplier === VM.LM_SHUTDOWN) {
5006
5026
  // Similar to STARTUP, but now look in the shut-down list.
5007
5027
  pl = (p.shut_downs.indexOf(bt) < 0 ? 0 : 1);
5028
+ } else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
5029
+ pl = p.upper_bound.result(bt) - pl;
5008
5030
  } else if(l.multiplier === VM.LM_INCREASE) {
5009
5031
  const ppl = p.actualLevel(bt - 1);
5010
5032
  pl = this.severestIssue([pl, ppl], pl - ppl);
@@ -6423,6 +6445,8 @@ Solver status = ${json.status}`);
6423
6445
  this.MINUS_INFINITY = this.SOLVER_MINUS_INFINITY;
6424
6446
  console.log('DIAGNOSIS OFF');
6425
6447
  }
6448
+ this.NEAR_PLUS_INFINITY = this.PLUS_INFINITY / 200;
6449
+ this.NEAR_MINUS_INFINITY = this.MINUS_INFINITY / 200;
6426
6450
  // The "propt to diagnose" flag is set when some block posed an
6427
6451
  // infeasible or unbounded problem.
6428
6452
  this.prompt_to_diagnose = false;
@@ -8538,9 +8562,12 @@ function VMI_update_cash_coefficient(args) {
8538
8562
  // The ON/OFF variable index is passed as third argument, hence `plvi`
8539
8563
  // (process level variable index) as first extra parameter, plus three
8540
8564
  // expressions (UB, price, rate).
8565
+ // NOTE: This type is also used to compute "available capacity".
8541
8566
  const
8542
- plvi = args[4],
8543
- // NOTE: Column of second variable will be relative to same offset.
8567
+ // args[4] = -1 signals that the remaining capacity should be
8568
+ // computed, which means that ON/OFF should be disregarded.
8569
+ plvi = (args[4] < 0 ? vi : args[4]),
8570
+ // Column of second variable will be relative to same offset.
8544
8571
  plk = k + plvi - vi,
8545
8572
  ub = args[5].result(VM.t),
8546
8573
  price_rate = args[6].result(VM.t) * args[7].result(VM.t);
@@ -9198,6 +9225,44 @@ function VMI_add_peak_increase_at_t_0(args) {
9198
9225
  // series of coefficient-setting instructions
9199
9226
  }
9200
9227
 
9228
+ function VMI_add_available_capacity(link) {
9229
+ // Adds the "available capacity" of the FROM node (process) to the
9230
+ // level of the TO node (data product) while considering the delay.
9231
+ // NOTE: New instruction style that passes pointers to model entities
9232
+ // instead of their properties.
9233
+ const
9234
+ d = link.actualDelay(VM.t),
9235
+ fnvi = link.from_node.level_var_index,
9236
+ // Column number in the tableau.
9237
+ fnk = VM.offset + fnvi - d * VM.cols,
9238
+ // Use flow rate and upper bound for t minus delay.
9239
+ t = VM.t - d,
9240
+ r = link.relative_rate.result(t),
9241
+ u = link.from_node.upper_bound.result(t);
9242
+ if(DEBUGGING) {
9243
+ console.log('VMI_add_available_capacity (t = ' + VM.t + ')',
9244
+ link.displayName, 'UB', u, 'rate', r);
9245
+ }
9246
+ // Available capacity equals UB - level, so subtract UB * rate
9247
+ // from RHS...
9248
+ VM.rhs -= u * r;
9249
+ // ... and subtract rate from FROM node coefficient.
9250
+ if(fnk <= 0) {
9251
+ // NOTE: If `fnk` falls PRIOR to the start of the block being solved,
9252
+ // this means that the value of the decision variable X for which the
9253
+ // coefficient C is to be set by this instruction has been calculated
9254
+ // while solving a previous block. Since the value of X is known,
9255
+ // adding X*rate to C is implemented as subtracting X*rate from the
9256
+ // right hand side of the constraint.
9257
+ VM.rhs += knownValue(fnvi, t) * r;
9258
+ } else if(fnk in VM.coefficients) {
9259
+ VM.coefficients[fnk] -= r;
9260
+ } else {
9261
+ VM.coefficients[fnk] = -r;
9262
+ }
9263
+ }
9264
+
9265
+
9201
9266
  // NOTE: the global constants below are not defined in linny-r-globals.js
9202
9267
  // because some comprise the identifiers of functions for VM instructions
9203
9268