AMPL: Model terminals within a destination city - ampl

I've encountered a problem which I have not found any solution to reading the AMPL documentation of sets.
What I want to model is that a city, say Kir, must have for instance 9 deliveries from another city, for instance Sto. However, these deliveries must arrive in Kir at some specific terminals, each terminal being open only for a small amount of time (approx 2 minutes) each day. The same must be true for the origin node. The route from Sto must be specified from a specific terminal (so the path can be "followed" in the results).
I've started to model using the "set V within K" operation for sets, but that requires that V must be the same set, or a subset of K where K is the set representing the "nodes" - Kir, Sto and so on and V is the set of names of the terminals "Terminal1", "Terminal2" etc.
I've started to check for instance "set K dimension 4" defined as for instance:
set K dimension 4;
data;
set K:=
Sto Kir Terminal1 Terminal2
Bod Kir Terminal3 Terminal2;
Where set K represents from which city (for example Sto) a delivery should be driven (to for example Kir), where the departing terminal in Sto is Terminal1 and the delivering terminal in Kir is Terminal2. This has the downside of having to specifiy a large number of combinations (there are approximately 22 terminals in Kir alone etc) manually. I don't know how to model the constraints then either. For instance the "one dimension" set I've previously had:
subject to yvar{i in V, j in V}:
sum{k in H} x[i,j,k] <= maxVisits[i,j];
where V is the set of cities alone, and H is the set of vehicles, maxVisits represents the maximum amount of deliviries from city i to city j and x is 1 if a delivery is made from i to j using vehicle k. I don't understand how this could be modeled, using the four dimensional set K.
Regards,

One way to model this is to index x over K and H and change the summation to include terminals:
var x{K, H} binary;
subject to yvar{i in V, j in V}:
sum{(i,j,t,u) in K, k in H} x[i,j,t,u,k] <= maxVisits[i,j];
The indexing (i,j,t,u) in K in the summation will iterate over pairs of terminals that are endpoints of routes from city i to city j. Note that i and j are fixed here because they are defined in the constraint indexing {i in V, j in V}.

Related

Indexing variables in sets in Xpress Mosel

I'm trying to solve a linear relaxation of a problem I've already solved with a Python library in order to see if it behaves in the same way in Xpress Mosel.
One of the index sets I'm using is not the typical c=1..n but a set of sets, meaning I've taken the 1..n set and have created all the combinations of subsets possible (for example the set 1..3 creates the set of sets {{1},{2},{3},{1,2},{2,3},{1,2,3}}).
In one of my constraints, one of the indexes must run inside each one of those subsets.
The respective code in Python is as follows (using the Gurobi library):
cluster=[1,2,3,4,5,6]
cluster1=[]
for L in range(1,len(cluster)+1):
for subset in itertools.combinations(cluster, L):
clusters1.append(list(subset))
ConstraintA=LinExpr()
ConstraintB=LinExpr()
for i in range(len(nodes)):
for j in range(len(nodes)):
if i<j and A[i][j]==1:
for l in range(len(clusters1)):
ConstraintA+=z[i,j]
for h in clusters1[l]:
restricao2B+=(x[i][h]-x[j][h])
model.addConstr(ConstraintA,GRB.GREATER_EQUAL,ConstraintB)
ConstraintA=LinExpr()
ConstraintB=LinExpr()
(In case the code above is confusing, which I suspect it to be)The constraint I'm trying to write is:
z(i,j)>= sum_{h in C1}(x(i,h)-x(j,h)) forall C1 in C
in which the C1 is each of those subsets.
Is there a way to do this in Mosel?
You could use some Mosel code along these lines (however, independently of the language that you are using, please be aware that the calculated 'set of all subsets' very quickly grows in size with an increasing number of elements in the original set C, so this constraint formulation will not scale up well):
declarations
C: set of integer
CS: set of set of integer
z,x: array(I:range,J:range) of mpvar
end-declarations
C:=1..6
CS:=union(i in C) {{i}}
forall(j in 1..C.size-1)
forall(s in CS | s.size=j, i in C | i > max(k in s) k ) CS+={s+{i}}
forall(s in CS,i in I, j in J) z(i,j) >= sum(h in s) (x(i,h)-x(j,h))
Giving this some more thought, the following version working with lists in place of sets is more efficient (that is, faster):
uses "mmsystem"
declarations
C: set of integer
L: list of integer
CS: list of list of integer
z,x: array(I:range,J:range) of mpvar
end-declarations
C:=1..6
L:=list(C)
qsort(SYS_UP, L) ! Making sure L is ordered
CS:=union(i in L) [[i]]
forall(j in 1..L.size-1)
forall(s in CS | s.size=j, i in L | i > s.last ) CS+=[s+[i]]
forall(s in CS,i in I, j in J) z(i,j) >= sum(h in s) (x(i,h)-x(j,h))

Taking the difference of 2 nodes in a decision problem while keeping the model as an MILP

To explain the question it's best to start with this
picture
I am modeling an optimization decision problem and a feature that I'm trying to implement is heat transfer between the process stages (a = 1, 2) taking into account which equipment type is chosen (j = 1, 2, 3) by the binary decision variable y.
The temperatures for the equipment are fixed values and my goal is to find (in the case of the picture) dT = 120 - 70 = 50 while keeping the temperature difference as a parameter (I want to keep the problem linear and need to multiply the temperature difference with a variable later on).
Things I have tried:
dT = T[a,j] - T[a-1,j]
(this obviously gives T = 80 for T[a-1,j] which is incorrect)
T[a-1] = sum(T[a-1,j] * y[a-1,j] for j in (1,2,3)
This will make the problem non-linear when I multiply with another variable.
I am using pyomo and the linear "glpk" solver. Thank you for reading my post and if someone could help me with this it is greatly appreciated!
If you only have 2 stages and 3 pieces of equipment at each stage, you could reformulate and let a binary decision variable Y[i] represent each of the 9 possible connections and delta_T[i] be a parameter that represents the temp difference associated with the same 9 connections which could easily be calculated and put into a model parameter.
If you want to keep in double-indexed, and assuming that there will only be 1 piece of equipment selected at each stage, you could take the sum-product of the selection variable and temps at each stage and subtract them.
dT[a] = sum(T[a, j]*y[a, j] for j in J) - sum(T[a-1, j]*y[a-1, j] for j in J)
for a ∈ {2, 3, ..., N}

Logic code of AMPL about TSPTW

I am studing this AMPL code (TSPTW: https://github.com/inter0509/TSPTW).
It is a code to resolve a TSP with Time Window (I hope...)
rank: binary-matrix (2x2) -> if rank[i,j]==1, then take that arc from "node-i" to "node-j"
constrain11 : only one arc into the node
constrain12 only one arc from the node
constrain13: must select arc to node-0 to node-0
constrain21: Why is there that piece of code ("+sum{i in V,j in V:i!=j}(c[i,j])*y[i,k-1,j,k];")?
constrain31/32/33: I have no idea..
constrain4: you must arrive after sum{j in V}(r[j]*rank[j,k]);
constarin5: you don't left node before sum{j in V}(d[j]*rank[j,k]);
"r": hour when you arrive
"d": hour when you leave the node
"p": hours you must stop in the node.
Is it correct? I think no...
It's a bit painful trying to interpret uncommented code with uninformative variable names, but I think I've figured it out.
In the previous tsp.mod presented at the same place, there's a binary decision variable x{V,V} which has x[i,j] = 1 if and only if the path goes from node i to node j.
In tsptw.mod there is a binary decision variable rank{V,V}, so we might expect it to work the same way as x{V,V} in the previous model, but it doesn't.
Instead, rank[j,k] = 1 if and only if node j is the k'th city to be visited in the salesman's tour. For instance, if rank[8,3] = rank[5,4] = rank [12,5] = 1, then the third city in the tour is city #8, the fourth is city #5, the fifth is city #12, and so on.
Cities are numbered from 0 to n, so there are actually n+1 cities in this problem.
The decision variable start[j] means "start time of the j'th city in the tour ordering": e.g. if rank[8,3] = 1, then start[3] gives the time at which we start in city #8. (The counting starts at 0 for the start city.)
r and d represent the earliest start time and latest finish time permitted for each city (indexed on the cities' numeric ID, and NOT on their order in the tour). p represents the time required to stay within each city, again indexed on the city's numeric ID.
c[j,k] is the time taken to travel from city j to city k.
y[i,k-1,j,k] equals 1 if the k-1'th city in the tour is city i, and the k'th city is j. (Enforced by constraints 31-33.)
Breaking down the objective function:
minimize total_time : start[n]+sum{j in V:j!=0}(c[j,0]*rank[j,n])
+sum{j in V:j!=0} (p[j]*rank[j,n]);
start[n] : this is just the start time for the n'th (last) city in the tour.
sum{j in V:j!=0}(c[j,0]*rank[j,n]) : rank[j,n] = 1 if and only if city with ID #j is the n'th city in the tour, 0 otherwise. c[j,0] is the time to travel from that city to city #0 (the start). So this gives the travel time from the n'th (last) city to return to where we started.
sum{j in V:j!=0} (p[j]*rank[j,n]) : this is the time required to spend at the n'th city.
Working through the constraints:
subject to constrain11 {j in V}: sum{k in V} rank[j,k]=1;
subject to constrain12 {k in V}: sum{j in V} rank[j,k]=1;
Each city must have one and only one rank (position in the tour). For each rank, there must be exactly one city assigned to that rank. (These two are actually redundant, but that doesn't matter.)
subject to constrain13: rank[0,0] = 1;
We start at city 0.
subject to constrain21 {k in V2}: start[k] >=
start[k-1] + sum{i in V}(p[i]*rank[i,k-1])
+sum{i in V,j in V:i!=j}(c[i,j])*y[i,k-1,j,k];
The time at which we start in the k'th city on our route must be no less than the start time for the previous city, plus the time we needed to spend in that previous city, plus the cost of travelling between the k-1'th and k'th cities.
subject to constrain22: start[0] = 0;
Start the trip at time zero.
subject to constrain31 {i in V,j in V,k in V2}: y[i,k-1,j,k]<=rank[i,k-1];
subject to constrain32 {i in V,j in V,k in V2}: y[i,k-1,j,k]<=rank[j,k];
subject to constrain33 {i in V,j in V,k in V2}: y[i,k-1,j,k]>=rank[i,k-1]+rank[j,k]-1;
Collectively, these ensure that y[i,k-1,j,k] is 1 if the k-1'th city visited is i, and the k'th city visited is j, and otherwise it is zero.
Note: although y is indexed on {V,V,V,V2}, most of those indices are actually irrelevant; only the ones where the second index is one less than the fourth index have any significance to the problem.
subject to constrain4 {k in V2}: start[k] >= sum{j in V}(r[j]*rank[j,k]);
The time at which we start the k'th city of the tour must be greater than or equal to the earliest start time (r) for whatever the k'th city actually is (j such that rank[j,k] = 1).
subject to constarin5 {k in V2}: start[k]+sum{j in V}(p[j]*rank[j,k])
<= sum{j in V}(d[j]*rank[j,k]);
The time at which we start the k'th city, plus the time (p) required for that city, must not exceed the latest finish time (d) for that city.
I haven't gone looking for bugs, so I won't vouch for its correctness, but I think the general approach makes sense under that interpretation.
If you found this answer helpful, please pay it forward by promising always to comment your code and use informative variable names ;-)

Segment tree - query complexity

I am having problems with understanding segment tree complexity. It is clear that if you have update function which has to change only one node, its complexity will be log(n).
But I have no idea why complexity of query(a,b), where (a,b) is interval that needs to be checked, is log(n).
Can anyone provide me with intuitive / formal proof to understand this?
There are four cases when query the interval (x,y)
FIND(R,x,y) //R is the node
% Case 1
if R.first = x and R.last = y
return {R}
% Case 2
if y <= R.middle
return FIND(R.leftChild, x, y)
% Case 3
if x >= R.middle + 1
return FIND(R.rightChild, x, y)
% Case 4
P = FIND(R.leftChild, x, R.middle)
Q = FIND(R.rightChild, R.middle + 1, y)
return P union Q.
Intuitively, first three cases reduce the level of tree height by 1, since the tree has height log n, if only first three cases happen, the running time is O(log n).
For the last case, FIND() divide the problem into two subproblems. However, we assert that this can only happen at most once. After we called FIND(R.leftChild, x, R.middle), we are querying R.leftChild for the interval [x, R.middle]. R.middle is the same as R.leftChild.last. If x > R.leftChild.middle, then it is Case 1; if x <= R.leftChild, then we will call
FIND ( R.leftChild.leftChild, x, R.leftChild.middle );
FIND ( R.leftChild.rightChild, R.leftChild.middle + 1, , R.leftChild.last );
However, the second FIND() returns R.leftChild.rightChild.sum and therefore takes constant time, and the problem will not be separate into two subproblems (strictly speaking, the problem is separated, though one subproblem takes O(1) time to solve).
Since the same analysis holds on the rightChild of R, we conclude that after case4 happens the first time, the running time T(h) (h is the remaining level of the tree) would be
T(h) <= T(h-1) + c (c is a constant)
T(1) = c
which yields:
T(h) <= c * h = O(h) = O(log n) (since h is the height of the tree)
Hence we end the proof.
This is my first time to contribute, hence if there are any problems, please kindly point them out and I would edit my answer.
A range query using a segment tree basically involves recursing from the root node. You can think of the entire recursion process as a traversal on the segment tree: any time a recursion is needed on a child node, you are visiting that child node in your traversal. So analyzing the complexity of a range query is equivalent to finding the upper bound for the total number of nodes that are visited.
It turns out that at any arbitrary level, there are at most 4 nodes that can be visited. Since the segment tree has a height of log(n) and that at any level there are at most 4 nodes that can be visited, the upper bound is actually 4*log(n). The time complexity is therefore O(log(n)).
Now we can prove this with induction. The base case is at the first level where the root node lies. Since the root node has at most two child nodes, we can only visit at most those two child nodes, which is at most 4 nodes.
Now suppose it is true that at an arbitrary level (say level i) we visit at most 4 nodes. We want to show that we will visit at most 4 nodes at the next level (level i+1) as well. If we had visited only 1 or 2 nodes at level i, it's trivial to show that at level i+1 we will visit at most 4 nodes because each node can have at most 2 child nodes.
So let's focus on the assumption that 3 or 4 nodes were visited at level i, and try to show that at level i+1 we can also have at most 4 visited nodes. Now since the range query is asking for a contiguous range, we know that the 3 or 4 nodes visited at level i can be categorized into 3 partitions of nodes: a leftmost single node whose segment range is only partially covered by the query range, a rightmost single node whose segment range is only partially covered by the query range, and 1 or 2 middle nodes whose segment range is fully covered by the query range. Since the middle nodes have their segment range(s) fully covered by the query range, there would be no recursion at the next level; we just use their precomputed sums. We are left with possible recursions on the leftmost node and the rightmost node at the next level, which is obviously at most 4.
This completes the proof by induction. We have proven that at any level at most 4 nodes are visited. The time complexity for a range query is therefore O(log(n)).
An interval of length n can be represented by k nodes where k <= log(n)
We can prove it based on how the binary system works.

AMPL: Modeling vehicles to departure "every n hours"

I want to model that departures from a node can only take place in a "every n hours" manner. I've started to model this using two variables - starttime[i,j,k] shows when vehicle k departured i with j as destination, x[i,j,k] is a binary variable having value 1 if vehicle k drove from i to j, and 0 otherwise. The model is:
maximize maxdrive: sum{i in V, j in V, k in K} traveltime[i,j]*x[i,j,k];
subject to TimeConstraint {k in K}:
sum{i in V, j in V} (traveltime[i,j]+servicetime[i])*x [i,j,k] <= 1440;
subject to StartTime{i in V,j in V, k in K}:
starttime[i,j,k] + traveltime[i,j] - 9000 * (1 - x[i,j,k]) <= starttime[j,i,k];
subject to yvar{i in V, j in V}:
sum{k in K} x[i,j,k] <= maxVisits[i,j];
subject to Constraint1{i in V, j in V, k in K, g in V, h in K}:
starttime[i,j,k] + TimeInterval[i]*x[i,j,k] <= starttime[i,g,h];
The constraint in question is "Constraint1" where i is the origin node, j the destination node, and k is the vehicle. The index g is used to show that the later departure can be to any destination node. TimeInterval corresponds to the interval intended, i.e. if TimeInterval at i is 2 hours, the starttime of the next vehicle to departure from i must not be less than 2 hours from previous departure. The origins corresponds to specific products (only available from said origin node) whereas I want the vehicles to not be bounded to a specific origin node - they should be able to jump between nodes to utilize backhauling etc. In other words, I want to conduct this constraint without having restraints on the vehicles themselves but rather the origin nodes.
The objective function to "maximize the traveltime" may seem strange, but the objective function is rather obsolete really. If the constraints are met, the solution is adequate. To maximize traveltime is merely an attempt to "force" the x variables to become 1.
The question is: how can I do this? With this formulation, all x[i,j,k] variables dissappears from the answer (without this constraint, some of the binary variables x becomes 1 and the other 0. The solution meets the maxVisits requirement. With the constraint all x variables becomes 0 and all starttimes becomes 0 as well. MINTO (The solver) doesn't state that the problem is infeasible either.
Also, how to separate the vehicles so the program recognizes that it is a comparison between all departures? I would rather to not include time dimensions, at it would give so much more variables.
EDIT: After trying a new model using a non-linear solver I've seen some strange results. Specifically, I'm using the limit 1440 (minutes) as an upper bound as to for how long a vehicle can operate each day. Using this model below the solution is 0 for every variable, but the starttime for all combinations of i,j,k is 720 (half of 1440). Does anyone have any clue in regards of what causing this solution? How did this constraint remove the link between starttime being higher than 0 requiring that x must be 1.
subject to StartTimeSelf{i in V, j in V, k in K, g in K, h in V}:
starttime[i,j,k]*x[i,j,k] + TimeInterval[i]*x[i,j,k] + y[i,k] <= starttime[i,h,g]*x[i,j,k];