Network models are an important class of optimizations, in itself but also as part of larger models. In [1] a nice presentation is given on how to set up a network model in the Python-based modeling tool Pyomo.
The simple shortest path problem is reproduced here:
The output is simply:
The output looks like:
1 | from collections import defaultdict |
[0, 2, 5, 8, 9]
Let's see how I would write the same problem in GAMS:
1 | * |
The output looks like:
---- 69 VARIABLE x.L flow
node2 node5 node8 node9
node0 1
node2 1
node5 1
node8 1
---- 69 VARIABLE totalLength.L = 15.000
---- 82 SET visit path
step1.node0
step2.node2
step3.node5
step4.node8
step5.node9
Notes:
- The Pyomo model is using numbers as indices. The GAMS model uses strings for this.
- The GAMS model uses distances to populate the arcs set. There is one complication; zeros are not stored (in GAMS all data is stored sparse). So to make sure the link from node 7 to node 4 is not lost we use an EPS value (this value "exists" but is numerically zero).
- I handled the flow balance equation a bit differently. I like simple equations, so I encoded the special handling of the source and sink node into a sparse parameter inflow. With this, we are left with just a regular flow balance equation.
- The GAMS model solves the model as an RMIP: a relaxed MIP model. The variables are declared as binary.
- The Pyomo model uses more concepts and constructs. The GAMS language is very small (I like to say: if you understand the sum, you understand 90% of the language), but somehow we can express many complex concepts with it. Notable dictionaries are implicit in GAMS (it is set-oriented and all indices are basically a dict-like construct). But the same also means that sometimes you need to use somewhat unnatural code in order to stay inside this small language. Pyomo uses more language constructs and as there is always some Python function to help you out, this can lead to very compact code.
- In post-processing , the Pyomo code uses: if model.x[i, j].value == 1. That looks a bit dangerous to me. If the variable is 0.9999999 or 1.000001 it would not be able to construct the correct path. I typically use 0.5 as threshold. That is a bit ridiculous, but as I don't know a good choice for the tolerance, why not be as conservative as we can (of course in a non-political sense).
References
- Optimization in Python: Intermediate Pyomo Workshop - Brent Austgen - UT Austin INFORMS, https://www.youtube.com/watch?v=T5LjmbyA1o0