linny-r 1.3.1 → 1.3.3
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 +45 -6
- package/package.json +1 -1
- package/server.js +47 -6
- package/static/index.html +59 -1
- package/static/linny-r.css +56 -0
- package/static/scripts/linny-r-ctrl.js +16 -1
- package/static/scripts/linny-r-gui.js +335 -7
- package/static/scripts/linny-r-milp.js +237 -13
- package/static/scripts/linny-r-model.js +109 -21
- package/static/scripts/linny-r-utils.js +10 -2
- package/static/scripts/linny-r-vm.js +233 -170
@@ -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
|
-
|
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
|
-
|
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
|
-
|
1619
|
-
|
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
|
-
|
1625
|
-
|
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++)
|
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
|
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
|
-
|
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><
|
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
|
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
|
-
|
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, '
|
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
|
-
|
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(', ') + '}';
|