Formulation as Constraint Program (CP)¶
We formulate the RLSP into a constraint program using constructs available in OR-Tools.
Definition of Variables¶
For operations, we use interval variables. An interval variable is defined as:
consisting of integer values for:
start: \(i_s\)
duration: \(i_d\)
end: \(i_e\)
presence: \(i_p \in \{0, 1\}\)
The presence variable indicates whether the interval is active (i.e., part of the solution) or ignored by all constraints. Introducing an interval variable implicitly enforces:
To handle the assignment of machines (especially with multiple identical machines) and to simplify constraint formulation, we introduce roles of machines in operations.
Let \(o \in O\) be an operation. The roles in \(o\) are defined as:
The set of machines in a machine configuration \(L\) that can fulfill the main role is denoted:
If \(\hat{M}(o)\) is defined:
Otherwise:
We now formalize the assignment of machines to roles in an operation.
For each operation \(o \in O\) with roles \(R(o)\), define the set of feasible role assignments in machine configuration \(L\):
Each \(r \in R_L(o)\) is a mapping:
assigning each role \(R\) to a machine \(r(R) \in \tilde{M}(o, R)\). Each assignment represents one way to execute the operation using available machines.
Each operation \(o \in O\) is represented by a finite set of interval variables:
\(s_o\), \(e_o\): start and end variables of \(o\)
\(d_o\): known duration
\(p_r \in \{0,1\}\): presence variable for assignment \(r\)
Explanation of Used Constraints¶
Consistency Constraints¶
Only one assignment of machines to roles must be chosen:
In OR-Tools:
model.AddExactlyOne(p_r_list)
If operations \(o_i\) and \(o_j\) are connected and share a role, presence variables must agree:
In OR-Tools:
model.Add(p1 == p2)
Precedence Constraints¶
For each edge \(e = (o_i, o_j) \in E\):
In OR-Tools:
model.Add(end_i <= start_j)
model.Add(start_j - end_i >= wmin)
model.Add(start_j - end_i <= wmax)
Processing Capacity Constraints¶
Each machine \(M \in L\) has capacity \(c_p(M)\):
If \(c_p(M) = 1\): enforce no overlap:
model.AddNoOverlap(intervals)
If \(c_p(M) > 1\): allow overlaps up to capacity:
model.AddCumulative(intervals, demands, capacity)
Spatial Capacity Constraints¶
Machines have spatial capacity \(c_s(M)\). Model storage via reservoir constraints:
Transport into \(M\): increase level at \(s_o\)
Transport out of \(M\): decrease level at \(e_o\)
In OR-Tools:
model.AddReservoirConstraintWithActive(
times, level_changes, actives, min, max
)
No Load-While-Processing Constraints¶
Machines with \(l(M) = 0\) can’t load/unload during processing:
In OR-Tools:
model.AddNoOverlap([I_1, I_2])
Objective Function and Waiting Costs¶
Objective: minimize weighted sum of waiting costs and makespan:
Makespan:
model.AddMaxEquality(makespan, [e_o])
Waiting Costs:
For edge \((o_i, o_j)\):
\[ w_{ij} := c(e) \cdot (s_{o_j} - e_{o_i}) \]For initial operation:
\[ w_o := c_o \cdot s_o \]
Total:
Final objective:
model.Minimize(alpha * makespan + wait_cost)
Soft Constraints (Optional)¶
To allow bounded violations:
Final objective (with penalties \(\beta\)):
model.Minimize(alpha * makespan + wait_cost + beta * sum(aux_vars))
Discussion of the CP Model¶
Rescheduling¶
It is possible to reoptimize partially executed schedules after for example durations differed from the expectation. This can be done by simply fixing the starting and ending time as well as machine assignments of corresponding intervals.
Including Heuristics¶
Ortools offers the syntax of solution hints. That allows attempts to compute sulotions with different heuristics and feed the solutions into ortools which then tries to improve that solution.
Performance¶
CP has proven to be better than MIP. One can roughly expect reasonable solutions on a average computer within 1 minute to problems of up to 80 operations.