linny-r 1.3.1 → 1.3.2

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.
@@ -277,6 +277,21 @@ class LinnyRModel {
277
277
  return null;
278
278
  }
279
279
 
280
+ productByID(id) {
281
+ if(this.products.hasOwnProperty(id)) return this.products[id];
282
+ return null;
283
+ }
284
+
285
+ processByID(id) {
286
+ if(this.processes.hasOwnProperty(id)) return this.processes[id];
287
+ return null;
288
+ }
289
+
290
+ clusterByID(id) {
291
+ if(this.clusters.hasOwnProperty(id)) return this.clusters[id];
292
+ return null;
293
+ }
294
+
280
295
  nodeBoxByID(id) {
281
296
  if(this.products.hasOwnProperty(id)) return this.products[id];
282
297
  if(this.processes.hasOwnProperty(id)) return this.processes[id];
@@ -1572,7 +1587,7 @@ class LinnyRModel {
1572
1587
  }
1573
1588
  return true;
1574
1589
  }
1575
-
1590
+
1576
1591
  get selectionAsXML() {
1577
1592
  // Returns XML for the selected entities, and also for the entities
1578
1593
  // referenced by expressions for their attributes.
@@ -1595,34 +1610,92 @@ class LinnyRModel {
1595
1610
  from_tos = [],
1596
1611
  xml = [],
1597
1612
  ft_xml = [],
1598
- extra_xml = [];
1613
+ extra_xml = [],
1614
+ selected_xml = [];
1599
1615
  for(let i = 0; i < this.selection.length; i++) {
1600
1616
  const obj = this.selection[i];
1601
1617
  entities[obj.type].push(obj);
1618
+ selected_xml.push('<sel>' + xmlEncoded(obj.displayName) + '</sel>');
1602
1619
  }
1620
+ // Expand clusters by adding all its model entities to their respective
1621
+ // lists
1622
+ for(let i = 0; i < entities.Cluster.length; i++) {
1623
+ const c = entities.Cluster[i];
1624
+ c.clearAllProcesses();
1625
+ c.categorizeEntities();
1626
+ // All processes and products in (sub)clusters must be copied
1627
+ mergeDistinct(c.all_processes, entities.Process);
1628
+ mergeDistinct(c.all_products, entities.Product);
1629
+ // Likewise for all related links and constraints
1630
+ mergeDistinct(c.related_links, entities.Link);
1631
+ mergeDistinct(c.related_constraints, entities.Constraint);
1632
+ // NOTE: add entities referenced by notes within selected clusters
1633
+ // to `extras`, but not the XML for these notes, as this is already
1634
+ // part of the clusters' XML
1635
+ const an = c.allNotes;
1636
+ // Add selected notes as these must also be checked for "extras"
1637
+ mergeDistinct(entities.Note, an);
1638
+ for(let i = 0; i < an.length; i++) {
1639
+ const n = an[i];
1640
+ mergeDistinct(n.color.referencedEntities, extras);
1641
+ for(let i = 0; i < n.fields.length; i++) {
1642
+ addDistinct(n.object, extras);
1643
+ }
1644
+ }
1645
+ }
1646
+ // Only add the XML for notes in the selection
1603
1647
  for(let i = 0; i < entities.Note.length; i++) {
1604
- const n = entities.Note[i];
1605
1648
  xml.push(n.asXML);
1606
1649
  }
1607
1650
  for(let i = 0; i < entities.Product.length; i++) {
1608
- xml.push(entities.Product[i].asXML);
1651
+ const p = entities.Product[i];
1652
+ mergeDistinct(p.lower_bound.referencedEntities, extras);
1653
+ mergeDistinct(p.upper_bound.referencedEntities, extras);
1654
+ mergeDistinct(p.initial_level.referencedEntities, extras);
1655
+ mergeDistinct(p.price.referencedEntities, extras);
1656
+ xml.push(p.asXML);
1609
1657
  }
1610
1658
  for(let i = 0; i < entities.Process.length; i++) {
1611
- xml.push(entities.Process[i].asXML);
1612
- }
1659
+ const p = entities.Process[i];
1660
+ mergeDistinct(p.lower_bound.referencedEntities, extras);
1661
+ mergeDistinct(p.upper_bound.referencedEntities, extras);
1662
+ mergeDistinct(p.initial_level.referencedEntities, extras);
1663
+ mergeDistinct(p.pace_expression.referencedEntities, extras);
1664
+ xml.push(p.asXML);
1665
+ }
1666
+ // Only now add the XML for the selected clusters
1613
1667
  for(let i = 0; i < entities.Cluster.length; i++) {
1614
1668
  xml.push(entities.Cluster[i].asXML);
1615
1669
  }
1670
+ // Add all links that have (implicitly via clusters) been selected
1616
1671
  for(let i = 0; i < entities.Link.length; i++) {
1617
1672
  const l = entities.Link[i];
1618
- if(this.selection.indexOf(l.from_node) < 0) addDistinct(l.from_node, from_tos);
1619
- if(this.selection.indexOf(l.to_node) < 0) addDistinct(l.to_node, from_tos);
1673
+ // NOTE: the FROM and/or TO node need not be selected; if not, put
1674
+ // them in a separate list
1675
+ if(entities.Process.indexOf(l.from_node) < 0 &&
1676
+ entities.Product.indexOf(l.from_node) < 0) {
1677
+ addDistinct(l.from_node, from_tos);
1678
+ }
1679
+ if(entities.Process.indexOf(l.to_node) < 0 &&
1680
+ entities.Product.indexOf(l.to_node) < 0) {
1681
+ addDistinct(l.to_node, from_tos);
1682
+ }
1683
+ mergeDistinct(l.relative_rate.referencedEntities, extras);
1684
+ mergeDistinct(l.flow_delay.referencedEntities, extras);
1620
1685
  xml.push(l.asXML);
1621
1686
  }
1622
1687
  for(let i = 0; i < entities.Constraint.length; i++) {
1623
1688
  const c = entities.Constraint[i];
1624
- if(this.selection.indexOf(c.from_node) < 0) addDistinct(c.from_node, from_tos);
1625
- if(this.selection.indexOf(c.to_node) < 0) addDistinct(c.to_node, from_tos);
1689
+ // NOTE: the FROM and/or TO node need not be selected; if not, put
1690
+ // them in a separate list
1691
+ if(entities.Process.indexOf(c.from_node) < 0 &&
1692
+ entities.Product.indexOf(c.from_node) < 0) {
1693
+ addDistinct(c.from_node, from_tos);
1694
+ }
1695
+ if(entities.Process.indexOf(c.to_node) < 0 &&
1696
+ entities.Product.indexOf(c.to_node) < 0) {
1697
+ addDistinct(c.to_node, from_tos);
1698
+ }
1626
1699
  xml.push(c.asXML);
1627
1700
  }
1628
1701
  for(let i = 0; i < from_tos.length; i++) {
@@ -1631,7 +1704,9 @@ class LinnyRModel {
1631
1704
  if(p instanceof Process) ft_xml.push('" actor-name="', xmlEncoded(p.actor.name));
1632
1705
  ft_xml.push('"></from-to>');
1633
1706
  }
1634
- for(let i = 0; i < extras.length; i++) extra_xml.push(extras[i].asXML);
1707
+ for(let i = 0; i < extras.length; i++) {
1708
+ extra_xml.push(extras[i].asXML);
1709
+ }
1635
1710
  return ['<copy timestamp="', Date.now(),
1636
1711
  '" model-timestamp="', this.time_created.getTime(),
1637
1712
  '" cluster-name="', xmlEncoded(fc_name),
@@ -1639,7 +1714,8 @@ class LinnyRModel {
1639
1714
  '"><entities>', xml.join(''),
1640
1715
  '</entities><from-tos>', ft_xml.join(''),
1641
1716
  '</from-tos><extras>', extra_xml.join(''),
1642
- '</extras></copy>'].join('');
1717
+ '</extras><selection>', selected_xml.join(''),
1718
+ '</selection></copy>'].join('');
1643
1719
  }
1644
1720
 
1645
1721
  dropSelectionIntoCluster(c) {
@@ -4375,7 +4451,10 @@ class Actor {
4375
4451
 
4376
4452
  rename(name) {
4377
4453
  // Change the name of this actor
4378
- name = UI.cleanName(name);
4454
+ // NOTE: since version 1.3.2, colons are prohibited in actor names to
4455
+ // avoid confusion with prefixed entities; they are silently removed
4456
+ // to avoid model compatibility issues
4457
+ name = UI.cleanName(name).replace(':', '');
4379
4458
  if(!UI.validName(name)) {
4380
4459
  UI.warn(UI.WARNING.INVALID_ACTOR_NAME);
4381
4460
  return null;
@@ -5414,6 +5493,7 @@ class Cluster extends NodeBox {
5414
5493
  get asXML() {
5415
5494
  let xml;
5416
5495
  const
5496
+ cmnts = xmlEncoded(this.comments),
5417
5497
  flags = (this.collapsed ? ' collapsed="1"' : '') +
5418
5498
  (this.ignore ? ' ignore="1"' : '') +
5419
5499
  (this.black_box ? ' black-box="1"' : '') +
@@ -5422,7 +5502,8 @@ class Cluster extends NodeBox {
5422
5502
  '</name><owner>', xmlEncoded(this.actor.name),
5423
5503
  '</owner><x-coord>', this.x,
5424
5504
  '</x-coord><y-coord>', this.y,
5425
- '</y-coord><process-set>'].join('');
5505
+ '</y-coord><comments>', cmnts,
5506
+ '</comments><process-set>'].join('');
5426
5507
  for(let i = 0; i < this.processes.length; i++) {
5427
5508
  let n = this.processes[i].displayName;
5428
5509
  const id = UI.nameToID(n);
@@ -5458,6 +5539,7 @@ class Cluster extends NodeBox {
5458
5539
  initFromXML(node) {
5459
5540
  this.x = safeStrToInt(nodeContentByTag(node, 'x-coord'));
5460
5541
  this.y = safeStrToInt(nodeContentByTag(node, 'y-coord'));
5542
+ this.comments = xmlDecoded(nodeContentByTag(node, 'comments'));
5461
5543
  this.collapsed = nodeParameterValue(node, 'collapsed') === '1';
5462
5544
  this.ignore = nodeParameterValue(node, 'ignore') === '1';
5463
5545
  this.black_box = nodeParameterValue(node, 'black-box') === '1';
@@ -5468,8 +5550,8 @@ class Cluster extends NodeBox {
5468
5550
  name,
5469
5551
  actor;
5470
5552
 
5471
- // NOTE: to compensate for shameful bug in earlier version, look for
5472
- // "product-positions" node and for "notes" node in the process-set,
5553
+ // NOTE: to compensate for a shameful bug in an earlier version, look
5554
+ // for "product-positions" node and for "notes" node in the process-set,
5473
5555
  // as it may have been put there instead of in the cluster node itself
5474
5556
  const
5475
5557
  hidden_pp = childNodeByTag(n, 'product-positions'),
@@ -5649,13 +5731,19 @@ class Cluster extends NodeBox {
5649
5731
  addDistinct(this.product_positions[i].product, prods);
5650
5732
  }
5651
5733
  for(let i = 0; i < this.sub_clusters.length; i++) {
5652
- const ap = this.sub_clusters[i].allProducts; // recursion!
5653
- for(let j = 0; j < ap.length; j++) {
5654
- addDistinct(ap[j], prods);
5655
- }
5734
+ mergeDistinct(this.sub_clusters[i].allProducts, prods); // recursion!
5656
5735
  }
5657
5736
  return prods;
5658
5737
  }
5738
+
5739
+ get allNotes() {
5740
+ // Return the set of all notes in this cluster and its subclusters
5741
+ let notes = this.notes.slice();
5742
+ for(let i = 0; i < this.sub_clusters.length; i++) {
5743
+ notes = notes.concat(this.sub_clusters[i].allNotes); // recursion!
5744
+ }
5745
+ return notes;
5746
+ }
5659
5747
 
5660
5748
  clearAllProcesses() {
5661
5749
  // Clear `all_processes` property of this cluster AND of all its parent clusters
@@ -6897,7 +6985,7 @@ class Process extends Node {
6897
6985
  // The production level in time T thus corresponds to decision variable
6898
6986
  // X[Math.floor((T-1) / PACE + 1]
6899
6987
  this.pace = 1;
6900
- this.pace_expression = new Expression(this, 'PACE', '1');
6988
+ this.pace_expression = new Expression(this, 'LCF', '1');
6901
6989
  // NOTE: processes have NO input attributes other than LB, UB and IL
6902
6990
  // for processes, the default bounds are [0, +INF]
6903
6991
  this.equal_bounds = false;
@@ -256,8 +256,9 @@ function markFirstDifference(s1, s2) {
256
256
  //
257
257
 
258
258
  function ciCompare(a, b) {
259
- // Performs case-insensitive comparison
260
- return a.localeCompare(b, undefined, {sensitivity: 'base'});
259
+ // Performs case-insensitive comparison that does differentiate
260
+ // between accented characters (as this differentiates between identifiers)
261
+ return a.localeCompare(b, undefined, {sensitivity: 'accent'});
261
262
  }
262
263
 
263
264
  function endsWithDigits(str) {
@@ -439,6 +440,13 @@ function addDistinct(e, list) {
439
440
  if(list.indexOf(e) < 0) list.push(e);
440
441
  }
441
442
 
443
+ function mergeDistinct(list, into) {
444
+ // Adds elements of `list` to `into` if not already in `into`
445
+ for(let i = 0; i < list.length; i++) {
446
+ addDistinct(list[i], into);
447
+ }
448
+ }
449
+
442
450
  function setString(sl) {
443
451
  // Returns elements of stringlist `sl` in set notation
444
452
  return '{' + sl.join(', ') + '}';