linny-r 3.0.8 → 3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "3.0.8",
3
+ "version": "3.0.9",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  "test": "echo \"Error: no test specified\" && exit 1"
9
9
  },
10
10
  "dependencies": {
11
- "@xmldom/xmldom": ">=0.8.2"
11
+ "@xmldom/xmldom": ">=0.9.10"
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
@@ -1480,8 +1480,9 @@ class PowerGridManager {
1480
1480
  // to add constraints that enforce Kirchhoff's voltage law.
1481
1481
  this.nodes = {};
1482
1482
  this.edges = {};
1483
- this.spanning_tree = [];
1484
1483
  this.tree_incidence = {};
1484
+ this.node_sets = [];
1485
+ this.spanning_edges = [];
1485
1486
  this.cycle_edges = [];
1486
1487
  this.cycle_basis = [];
1487
1488
  this.min_length = 0;
@@ -1599,33 +1600,68 @@ class PowerGridManager {
1599
1600
  grid.join(', ').toLowerCase());
1600
1601
  }
1601
1602
 
1602
- inferSpanningTree() {
1603
+ nodeSetIndex(n) {
1604
+ // Return index of node set containing `n`, or -1 if no such set exists.
1605
+ for(let i = 0; i < this.node_sets.length; i++) {
1606
+ if(this.node_sets[i][n]) return i;
1607
+ }
1608
+ return -1;
1609
+ }
1610
+
1611
+ inferSpanningTrees() {
1603
1612
  // Use Kruksal's algorithm to build spanning tree.
1604
1613
  // NOTE: Tree needs not be minimal, so edges are not sorted.
1605
- this.spanning_tree.length = 0;
1614
+ this.spanning_edges.length = 0;
1606
1615
  this.cycle_edges.length = 0;
1607
1616
  this.tree_incidence = {};
1608
- const node_set = {};
1617
+ this.node_sets = [];
1609
1618
  for(let k in this.edges) if(this.edges.hasOwnProperty(k)) {
1610
1619
  const
1611
1620
  edge = this.edges[k],
1612
1621
  efn = edge.from_node,
1613
1622
  etn = edge.to_node,
1614
1623
  kvl = edge.process.grid.kirchhoff,
1615
- fn_in_tree = node_set.hasOwnProperty(efn),
1616
- tn_in_tree = node_set.hasOwnProperty(etn);
1624
+ fn_set = this.nodeSetIndex(efn),
1625
+ tn_set = this.nodeSetIndex(etn);
1617
1626
  // Only add edges of grids for which Kirchhoff's voltage law
1618
1627
  // has to be enforced.
1619
1628
  if(kvl) {
1620
- if(fn_in_tree && tn_in_tree) {
1621
- // Edge forms a cycle, so add it to the cycle edge list.
1622
- this.cycle_edges.push(edge);
1629
+ if(fn_set === tn_set) {
1630
+ if(fn_set < 0) {
1631
+ // Edge disconnected from tree => add nodes as a new set.
1632
+ const new_set = {};
1633
+ new_set[efn] = true;
1634
+ new_set[etn] = true;
1635
+ this.node_sets.push(new_set);
1636
+ this.spanning_edges.push(edge);
1637
+ } else {
1638
+ // Edge forms a cycle, so add it to the cycle edge list.
1639
+ this.cycle_edges.push(edge);
1640
+ }
1623
1641
  } else {
1624
- // Edge is not incident with *two* nodes already in the tree, so
1625
- // add it to the tree.
1626
- this.spanning_tree.push(edge);
1627
- node_set[efn] = true;
1628
- node_set[etn] = true;
1642
+ // Edge is not incident with *two* nodes in same node set, so
1643
+ // add it to the tree...
1644
+ this.spanning_edges.push(edge);
1645
+ if(fn_set < 0) {
1646
+ // Add FROM node to the TO node set.
1647
+ this.node_sets[tn_set][efn] = true;
1648
+ } else if(tn_set < 0) {
1649
+ // Add TO node to the FROM node set.
1650
+ this.node_sets[fn_set][etn] = true;
1651
+ } else {
1652
+ // If both nodes are "known" but in diferent sets, then merge
1653
+ // the sets into the one having the lowest index...
1654
+ let
1655
+ target = fn_set,
1656
+ other = tn_set;
1657
+ if(fn_set > tn_set) {
1658
+ target = tn_set;
1659
+ other = fn_set;
1660
+ }
1661
+ Object.assign(this.node_sets[target], this.node_sets[other]);
1662
+ // ... and delete the other set.
1663
+ this.node_sets.splice(other, 1);
1664
+ }
1629
1665
  }
1630
1666
  const ti = this.tree_incidence;
1631
1667
  // Always record that both its nodes are incident with it.
@@ -1643,19 +1679,27 @@ class PowerGridManager {
1643
1679
  }
1644
1680
  }
1645
1681
 
1646
- pathInSpanningTree(fn, tn, path) {
1682
+ pathInSpanningTree(fn, tn, path, eop) {
1647
1683
  // Recursively constructs `path` as the list of edges forming the path
1648
1684
  // from `fn` to `tn` in the spanning tree of this grid.
1649
1685
  // If edge connects path with TO node, `path` is complete.
1650
1686
  if(fn === tn) return true;
1687
+ // Consider all edges that connect with the FROM node.
1651
1688
  for(const e of this.tree_incidence[fn]) {
1652
1689
  // Ignore edges already in the path.
1653
- if(path.indexOf(e) < 0) {
1690
+ if(eop.indexOf(e) < 0) {
1654
1691
  // NOTE: Edges are directed, but should not be considered as such.
1655
- const nn = (e.from_node === fn ? e.to_node : e.from_node);
1656
- path.push(e);
1657
- if(this.pathInSpanningTree(nn, tn, path)) return true;
1692
+ let nn = e.to_node,
1693
+ sign = 1;
1694
+ if(e.from_node !== fn) {
1695
+ nn = e.from_node;
1696
+ sign = -1;
1697
+ }
1698
+ path.push({process: e.process, orientation: sign});
1699
+ eop.push(e);
1700
+ if(this.pathInSpanningTree(nn, tn, path, eop)) return true;
1658
1701
  path.pop();
1702
+ eop.pop();
1659
1703
  }
1660
1704
  }
1661
1705
  return false;
@@ -1666,28 +1710,19 @@ class PowerGridManager {
1666
1710
  this.cycle_basis.length = 0;
1667
1711
  if(!(MODEL.with_power_flow && MODEL.powerGridsWithKVL.length)) return;
1668
1712
  this.inferNodesAndEdges();
1669
- this.inferSpanningTree();
1713
+ this.inferSpanningTrees();
1670
1714
  for(const edge of this.cycle_edges) {
1671
- const path = [];
1672
- if(this.pathInSpanningTree(edge.from_node, edge.to_node, path)) {
1673
- // Add flags that indicate whether the edge on the path is reversed.
1674
- // The closing edge determines the orientation.
1675
- const cycle = [{process: edge.process, orientation: 1}];
1676
- let node = edge.to_node;
1677
- for(let i = path.length - 1; i >= 0; i--) {
1678
- const
1679
- pe = path[i],
1680
- ce = {process: pe.process};
1681
- if(pe.from_node === node) {
1682
- ce.orientation = 1;
1683
- node = pe.to_node;
1684
- } else {
1685
- ce.orientation = -1;
1686
- node = pe.from_node;
1687
- }
1688
- cycle.push(ce);
1689
- }
1690
- this.cycle_basis.push(cycle);
1715
+ const
1716
+ path = [],
1717
+ // Path cannot include the closing edge.
1718
+ eop = [edge];
1719
+ if(this.pathInSpanningTree(edge.to_node, edge.from_node, path, eop)) {
1720
+ // The closing edge has orientation +1.
1721
+ path.push({process: edge.process, orientation: 1});
1722
+ this.cycle_basis.push(path);
1723
+ } else {
1724
+ // Log that edge did not close a cycle.
1725
+ console.log('ANOMALY: No path for edge', edge);
1691
1726
  }
1692
1727
  }
1693
1728
  }
@@ -293,4 +293,4 @@ class GUIPowerGridManager extends PowerGridManager {
293
293
  return flows.join('\n');
294
294
  }
295
295
 
296
- } // END of class GUIPowerGridManager
296
+ } // END of class GUIPowerGridManager
@@ -4125,8 +4125,8 @@ class LinnyRModel {
4125
4125
  }
4126
4126
  // All potential inflows known => CP proxy can be calculated.
4127
4127
  if(count === p.inputs.length) {
4128
- // Also consider output links to products having price < 0.
4129
- for(const l of p.outputs) {
4128
+ // Also consider regular output links to products having price < 0.
4129
+ for(const l of p.outputs) if(l.multiplier === VM.LM_LEVEL) {
4130
4130
  // NOTE: Here, a delay may apply.
4131
4131
  const
4132
4132
  d = l.actualDelay(t),