linny-r 1.3.3 → 1.3.4

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/console.js CHANGED
@@ -932,6 +932,23 @@ function commandLineSettings() {
932
932
  gurobi_path = path_list[i];
933
933
  max_v = parseInt(match[1]);
934
934
  }
935
+ match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
936
+ if(match) {
937
+ cplex_path = path_list[i];
938
+ } else {
939
+ // NOTE: CPLEX may create its own environment variable for its paths
940
+ match = path_list[i].match(/%(.*cplex.*)%/i);
941
+ if(match) {
942
+ const cpl = process.env[match[1]].split(path.delimiter);
943
+ for(let i = 0; i < cpl.length; i++) {
944
+ match = cpl[i].match(/[\/\\]cplex[\/\\]bin/i);
945
+ if(match) {
946
+ cplex_path = cpl[i];
947
+ break;
948
+ }
949
+ }
950
+ }
951
+ }
935
952
  match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
936
953
  if(match) scip_path = path_list[i];
937
954
  match = path_list[i].match(/inkscape/i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -1467,10 +1467,25 @@ function commandLineSettings() {
1467
1467
  gurobi_path = path_list[i];
1468
1468
  max_v = parseInt(match[1]);
1469
1469
  }
1470
+ match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
1471
+ if(match) {
1472
+ cplex_path = path_list[i];
1473
+ } else {
1474
+ // NOTE: CPLEX may create its own environment variable for its paths
1475
+ match = path_list[i].match(/%(.*cplex.*)%/i);
1476
+ if(match) {
1477
+ const cpl = process.env[match[1]].split(path.delimiter);
1478
+ for(let i = 0; i < cpl.length; i++) {
1479
+ match = cpl[i].match(/[\/\\]cplex[\/\\]bin/i);
1480
+ if(match) {
1481
+ cplex_path = cpl[i];
1482
+ break;
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1470
1487
  match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
1471
1488
  if(match) scip_path = path_list[i];
1472
- match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
1473
- if(match) cplex_path = path_list[i];
1474
1489
  match = path_list[i].match(/inkscape/i);
1475
1490
  if(match) settings.inkscape = path_list[i];
1476
1491
  }
@@ -5273,9 +5273,30 @@ class GUIController extends Controller {
5273
5273
  md.element('actor').value = mapping.actor || '';
5274
5274
  md.element('prefix').value = mapping.prefix || '';
5275
5275
  const
5276
- ft = Object.keys(mapping.from_to).sort(ciCompare),
5276
+ tc = (mapping.top_clusters ?
5277
+ Object.keys(mapping.top_clusters).sort(ciCompare) : []),
5278
+ ft = (mapping.from_to ?
5279
+ Object.keys(mapping.from_to).sort(ciCompare) : []),
5277
5280
  sl = [];
5281
+ if(tc.length) {
5282
+ sl.push('<div style="font-weight: bold; margin:4px 2px 2px 2px">',
5283
+ 'Names for top-level clusters:</div>');
5284
+ // Add text inputs for selected cluster nodes
5285
+ for(let i = 0; i < tc.length; i++) {
5286
+ const
5287
+ ti = mapping.top_clusters[tc[i]],
5288
+ state = (ti === tc[i] ? 'color: #e09000; ' :
5289
+ this.validName(ti) ? 'color: #0000c0; ' :
5290
+ 'font-style: italic; color: red; ');
5291
+ sl.push('<div class="paste-option"><span>', tc[i], '</span> ',
5292
+ '<div class="paste-select"><input id="paste-selc-', i,
5293
+ '" type="text" style="', state, 'font-size: 12px" value="',
5294
+ ti, '"></div></div>');
5295
+ }
5296
+ }
5278
5297
  if(ft.length) {
5298
+ sl.push('<div style="font-weight: bold; margin:4px 2px 2px 2px">',
5299
+ 'Mapping of nodes to link from/to:</div>');
5279
5300
  // Add selectors for unresolved FROM/TO nodes
5280
5301
  for(let i = 0; i < ft.length; i++) {
5281
5302
  const ti = mapping.from_to[ft[i]];
@@ -5286,8 +5307,8 @@ class GUIController extends Controller {
5286
5307
  '</option></select></div></div>');
5287
5308
  }
5288
5309
  }
5289
- md.element('scroll-area').innerHTML = sl.join('');
5290
5310
  }
5311
+ md.element('scroll-area').innerHTML = sl.join('');
5291
5312
  // Open dialog, which will call pasteSelection(...) on OK
5292
5313
  this.paste_modal.show();
5293
5314
  }
@@ -5297,10 +5318,18 @@ class GUIController extends Controller {
5297
5318
  // proceeds to paste
5298
5319
  const
5299
5320
  md = this.paste_modal,
5300
- mapping = Object.assign(md.mapping, {});
5321
+ mapping = Object.assign(md.mapping, {}),
5322
+ tc = (mapping.top_clusters ?
5323
+ Object.keys(mapping.top_clusters).sort(ciCompare) : []),
5324
+ ft = (mapping.from_to ?
5325
+ Object.keys(mapping.from_to).sort(ciCompare) : []);
5301
5326
  mapping.actor = md.element('actor').value;
5302
5327
  mapping.prefix = md.element('prefix').value.trim();
5303
5328
  mapping.increment = true;
5329
+ for(let i = 0; i < tc.length; i++) {
5330
+ const cn = md.element('selc-' + i).value.trim();
5331
+ if(this.validName(cn)) mapping.top_clusters[tc[i]] = cn;
5332
+ }
5304
5333
  this.pasteSelection(mapping);
5305
5334
  }
5306
5335
 
@@ -5325,6 +5354,7 @@ console.log('HERE xml', xml);
5325
5354
  entities_node = childNodeByTag(xml, 'entities'),
5326
5355
  from_tos_node = childNodeByTag(xml, 'from-tos'),
5327
5356
  extras_node = childNodeByTag(xml, 'extras'),
5357
+ selc_node = childNodeByTag(xml, 'selected-clusters'),
5328
5358
  selection_node = childNodeByTag(xml, 'selection'),
5329
5359
  actor_names = [],
5330
5360
  new_entities = [],
@@ -5335,7 +5365,7 @@ console.log('HERE xml', xml);
5335
5365
 
5336
5366
  function fullName(node) {
5337
5367
  // Returns full entity name inferred from XML node data
5338
- if(node.nodeName === 'from-to') {
5368
+ if(node.nodeName === 'from-to' || node.nodeName === 'selc') {
5339
5369
  const
5340
5370
  n = xmlDecoded(nodeParameterValue(node, 'name')),
5341
5371
  an = xmlDecoded(nodeParameterValue(node, 'actor-name'));
@@ -5525,6 +5555,17 @@ console.log('HERE xml', xml);
5525
5555
  if(parseInt(mts) === MODEL.time_created.getTime() &&
5526
5556
  ca === fca && mapping.from_prefix === mapping.to_prefix &&
5527
5557
  !(mapping.prefix || mapping.actor || mapping.increment)) {
5558
+ // Prompt for names of selected cluster nodes
5559
+ if(selc_node.childNodes.length && !mapping.prefix) {
5560
+ mapping.top_clusters = {};
5561
+ for(let i = 0; i < selc_node.childNodes.length; i++) {
5562
+ const
5563
+ c = selc_node.childNodes[i],
5564
+ fn = fullName(c),
5565
+ mn = mappedName(fn);
5566
+ mapping.top_clusters[fn] = mn;
5567
+ }
5568
+ }
5528
5569
  this.promptForMapping(mapping);
5529
5570
  return;
5530
5571
  }
@@ -6525,11 +6566,12 @@ class GUIMonitor {
6525
6566
  })
6526
6567
  .then((data) => {
6527
6568
  try {
6528
- const jsr = JSON.parse(data);
6529
- if(jsr.solver !== VM.solver_name) {
6530
- UI.notify(`Solver on ${jsr.server} is ${jsr.solver}`);
6531
- }
6569
+ const
6570
+ jsr = JSON.parse(data),
6571
+ svr = `Solver on ${jsr.server} is ${jsr.solver}`;
6572
+ if(jsr.solver !== VM.solver_name) UI.notify(svr);
6532
6573
  VM.solver_name = jsr.solver;
6574
+ document.getElementById('host-logo').title = svr;
6533
6575
  } catch(err) {
6534
6576
  console.log(err, data);
6535
6577
  UI.alert('ERROR: Unexpected data from server: ' +
@@ -1609,16 +1609,20 @@ class LinnyRModel {
1609
1609
  extras = [],
1610
1610
  from_tos = [],
1611
1611
  xml = [],
1612
- ft_xml = [],
1613
1612
  extra_xml = [],
1613
+ ft_xml = [],
1614
+ selc_xml = [],
1614
1615
  selected_xml = [];
1615
1616
  for(let i = 0; i < this.selection.length; i++) {
1616
1617
  const obj = this.selection[i];
1617
1618
  entities[obj.type].push(obj);
1618
- selected_xml.push('<sel>' + xmlEncoded(obj.displayName) + '</sel>');
1619
+ if(obj instanceof Cluster) selc_xml.push(
1620
+ '<selc name="', xmlEncoded(obj.name),
1621
+ '" actor-name="', xmlEncoded(obj.actor.name), '"></selc>');
1622
+ selected_xml.push(`<sel>${xmlEncoded(obj.displayName)}</sel>`);
1619
1623
  }
1620
- // Expand clusters by adding all its model entities to their respective
1621
- // lists
1624
+ // Expand (sub)clusters by adding all their model entities to their
1625
+ // respective lists
1622
1626
  for(let i = 0; i < entities.Cluster.length; i++) {
1623
1627
  const c = entities.Cluster[i];
1624
1628
  c.clearAllProcesses();
@@ -1714,7 +1718,8 @@ class LinnyRModel {
1714
1718
  '"><entities>', xml.join(''),
1715
1719
  '</entities><from-tos>', ft_xml.join(''),
1716
1720
  '</from-tos><extras>', extra_xml.join(''),
1717
- '</extras><selection>', selected_xml.join(''),
1721
+ '</extras><selected-clusters>', selc_xml.join(''),
1722
+ '</selected-clusters><selection>', selected_xml.join(''),
1718
1723
  '</selection></copy>'].join('');
1719
1724
  }
1720
1725
 
@@ -3236,6 +3241,7 @@ class LinnyRModel {
3236
3241
  for(let i = 0; i < constraints.length; i++) {
3237
3242
  const
3238
3243
  c = constraints[i],
3244
+ // NOTE: constraints in list have levels greater than near-zero
3239
3245
  fl = c.from_node.actualLevel(t),
3240
3246
  tl = c.to_node.actualLevel(t);
3241
3247
  let tcp;
@@ -3306,10 +3312,10 @@ class LinnyRModel {
3306
3312
  if(af > VM.NEAR_ZERO) {
3307
3313
  // Prevent division by zero
3308
3314
  // NOTE: level can be zero even if actual flow > 0!
3309
- const al = p.actualLevel(dt);
3315
+ const al = p.nonZeroLevel(dt);
3310
3316
  // NOTE: scale to level only when level > 1, or fixed
3311
3317
  // costs for start-up or first commit will be amplified
3312
- if(al > VM.ON_OFF_THRESHOLD) cp -= pr * af / Math.max(al, 1);
3318
+ if(al > VM.NEAR_ZERO) cp -= pr * af / Math.max(al, 1);
3313
3319
  }
3314
3320
  }
3315
3321
  }
@@ -3428,8 +3434,8 @@ class LinnyRModel {
3428
3434
  p.cost_price[t] = cp;
3429
3435
  // For stocks, the CP includes stock price on t-1
3430
3436
  if(p.is_buffer) {
3431
- const prevl = p.actualLevel(t-1);
3432
- if(prevl > 0) {
3437
+ const prevl = p.nonZeroLevel(t-1);
3438
+ if(prevl > VM.NEAR_ZERO) {
3433
3439
  cp = (cnp + prevl * p.stockPrice(t-1)) / (qnp + prevl);
3434
3440
  }
3435
3441
  p.stock_price[t] = cp;
@@ -3486,7 +3492,7 @@ class LinnyRModel {
3486
3492
  // Then (also) look for links having AF = 0 ...
3487
3493
  for(let i = links.length-1; i >= 0; i--) {
3488
3494
  const af = links[i].actualFlow(t);
3489
- if(Math.abs(af) < VM.SIG_DIF_FROM_ZERO) {
3495
+ if(Math.abs(af) < VM.NEAR_ZERO) {
3490
3496
  // ... and set their UCP to 0
3491
3497
  links[i].unit_cost_price = 0;
3492
3498
  links.splice(i, 1);
@@ -3522,7 +3528,7 @@ class LinnyRModel {
3522
3528
  let hcp = VM.MINUS_INFINITY;
3523
3529
  for(let i = 0; i < p.inputs.length; i++) {
3524
3530
  const l = p.inputs[i];
3525
- if(l.from_node instanceof Process && l.actualFlow(t) > 0) {
3531
+ if(l.from_node instanceof Process && l.actualFlow(t) > VM.NEAR_ZERO) {
3526
3532
  const ld = l.actualDelay(t);
3527
3533
  // NOTE: only consider the allocated share of cost
3528
3534
  let cp = l.from_node.costPrice(t - ld) * l.share_of_cost;
@@ -3255,19 +3255,22 @@ class VirtualMachine {
3255
3255
  if(ubx.isStatic) {
3256
3256
  // If UB is very high (typically: undefined, so +INF), try to infer
3257
3257
  // a lower value for UB to use for the ON/OFF binary
3258
- let ub = ubx.result(0);
3258
+ let ub = ubx.result(0),
3259
+ hub = ub;
3259
3260
  if(ub > VM.MEGA_UPPER_BOUND) {
3260
- ub = p.highestUpperBound([]);
3261
+ hub = p.highestUpperBound([]);
3261
3262
  // If UB still very high, warn modeler on infoline and in monitor
3262
- if(ub > VM.MEGA_UPPER_BOUND) {
3263
- const msg = 'High upper bound (' + this.sig4Dig(ub) +
3263
+ if(hub > VM.MEGA_UPPER_BOUND) {
3264
+ const msg = 'High upper bound (' + this.sig4Dig(hub) +
3264
3265
  ') for <strong>' + p.displayName + '</strong>' +
3265
3266
  ' will compromise computation of its binary variables';
3266
3267
  UI.warn(msg);
3267
3268
  this.logMessage(this.block_count,
3268
3269
  'WARNING: ' + msg.replace(/<\/?strong>/g, '"'));
3269
3270
  }
3270
- } else {
3271
+ }
3272
+ if(hub !== ub) {
3273
+ ub = hub;
3271
3274
  this.logMessage(this.block_count,
3272
3275
  `Inferred upper bound for ${p.displayName}: ${this.sig4Dig(ub)}`);
3273
3276
  }
@@ -3771,7 +3774,7 @@ class VirtualMachine {
3771
3774
  for(let k = 0; k < l; k++) {
3772
3775
  const
3773
3776
  vi = svl[k],
3774
- slack = x[vi + j],
3777
+ slack = parseFloat(x[vi + j]),
3775
3778
  absl = Math.abs(slack);
3776
3779
  if(absl > VM.NEAR_ZERO) {
3777
3780
  const v = this.variables[vi - 1];