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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1614
|
+
this.spanning_edges.length = 0;
|
|
1606
1615
|
this.cycle_edges.length = 0;
|
|
1607
1616
|
this.tree_incidence = {};
|
|
1608
|
-
|
|
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
|
-
|
|
1616
|
-
|
|
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(
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
|
1625
|
-
// add it to the tree
|
|
1626
|
-
this.
|
|
1627
|
-
|
|
1628
|
-
|
|
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(
|
|
1690
|
+
if(eop.indexOf(e) < 0) {
|
|
1654
1691
|
// NOTE: Edges are directed, but should not be considered as such.
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
if(
|
|
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.
|
|
1713
|
+
this.inferSpanningTrees();
|
|
1670
1714
|
for(const edge of this.cycle_edges) {
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
}
|
|
@@ -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),
|