General considerations: genetic algorithms vs. LocalSolver
To develop algorithms for the RCPSP-ROC, two different approaches appear to be very promising. On the one hand, population-based genetic algorithms (see Holland (1975)) turned out to be very powerful to solve RCPSPs, in particular with respect to the makespan minimization objective, see, e.g., the results reported in Kolisch and Hartmann (2006). If one follows this approach, the central question is how solutions are encoded and how schedules are derived from this encoding, so that the operators of genetic algorithms can lead to new and still feasible schedules. (Note that a direct representation based on a possibly extremely large number of decision variables from the RCPSP-ROC model in Sect. 3 does not meet this fundamental requirement.) However, such a solution representation and corresponding schedule generation scheme can also be used within a (heuristic) local search algorithm. A commercial solver named LocalSolver, see http://www.localsolver.com, has recently gained attention as it offers a flexible modeling interface to define in particular combinatorial optimization problems, for example, vehicle routing problems. The solver is based on a hybrid approach combining local search, constraint propagation, and inference, see Benoist et al. (2011). It turned out to be relatively easy to use LocalSolver to solve our problem; given the solution representation and decoding mechanisms, we developed for the genetic algorithms. Furthermore, LocalSolver easily beat the Gurobi MIP solver operating on the RCPSP-ROC formulation in Sect. 3. For those reasons, we used a relatively lightweight LocalSolver implementation based on the solution representations for the genetic algorithms as a surprisingly strong benchmark.
Alternative solution encodings and corresponding schedule generation schemes
The serial schedule generation scheme based on an activity list \(\lambda\)
In the context of the RCPSP, the serial schedule generation scheme (SSGS) is widely used to decode a solution representation based on an activity list \(\lambda\) into a schedule, see Kolisch and Hartmann (1999), p. 150ff. In an activity list \(\lambda\), all jobs (including the dummy activities) are included once. If an activity i in the project has to precede another activity j, then this order has to be respected in the activity list as well. Hence, the first and last entries of the activity list are always the (dummy) start and end activities. Using the SSGS, one iteratively schedules activities in the order implied by the activity list. Starting with the first activity on this list \(\lambda\), one determines its earliest starting point that is feasible both with respect to capacity constraints and activity precedence relations.
Consider, for example, the project depicted in Fig. 1 requiring a single resource \(r\,=\,1\) with a period capacity of \(K_1\,=\,4\) capacity units (and no overtime capacity, that is \(\overline{z}_r\,=\,0, \forall \; r\)). Then, the activity list
$$\begin{aligned} \lambda _1\, = \,\left( 0, 1, 3, 2, 5, 4, 6, 7 \right) \end{aligned}$$
(8)
is decoded into the schedule in Fig. 2. Note that the SSGS is not injective, meaning different activity lists can lead to the same schedule. For details on this established procedure, see Kolisch and Hartmann (1999).
This serial schedule generation scheme operating on an activity list \(\lambda\) only generates active schedules \(\mathcal {AS}\) which are a subset of the quasi-stable schedules, i.e., \(\mathcal {AS} \subseteq \mathcal {QSS}\), when enumerating over all possible activity lists \(\lambda\) as input data. As mentioned before, it is not even sufficient to consider (only) the set of \(\mathcal {QSS}\) schedules to find an optimal solution for the RCPSP-ROC. We are hence not aware of any established construction rule operating on an activity list \(\lambda\) and the SSGS to build promising schedules for the RCPSP-ROC, due to its specific objective function.
For this reason, we developed several extended solution representations and modified decoding mechanisms that can all be seen as generalizations of the established SSGS approach for the RCPSP. We describe these below in detail.
The basic reasoning is that when constructing a schedule, it is (with respect to revenues) essentially attractive to schedule activities as early as possible. This tends to be achieved by the SSGS. However, in the RCPSP-ROC, there is the additional question of when or for which activities overtime should be used. We present below three different approaches in which this decision is directly determined by the solution representation.
Solution encoding \((\lambda |{\hat{z}}_r)\)
One possible solution encoding for the RCPSP-ROC is the representation \((\lambda |{\hat{z}}_r)\). Here, \(\hat{z}_r\) denotes a column vector specifying the maximum permissible overtime usage for resource r. When decoding a solution via the SSGS, we hence operate on a modified and time invariant period capacity \(K_{{rt}}^{{\bmod }}\,=\,K_r\,+\,\hat{z}_r\) for each resource r and period t.
For the project introduced in Fig. 1 requiring a single resource \(r\,=\,1\) with a regular period capacity of \(K_1\,=\,4\) capacity units, the representation
$$\begin{aligned} (\lambda |{\hat{z}}_r)\,=\,\begin{array}{cccccccc}(0&1&3&2&5&4&6&7\;|\;[0])\end{array} \end{aligned}$$
(9)
(without overtime permission) is decoded into the schedule in Fig. 2, whereas the representation
$$\begin{aligned} (\lambda |{\hat{z}}_r)=\begin{array}{cccccccc}(0&1&3&2&5&4&6&7\;|\;[2])\end{array} \end{aligned}$$
(10)
leads to the schedule in Fig. 3. In this case, the additional decision on a fixed upper bound \(\hat{z}_1\,=\,2\) for overtime throughout the entire planning horizon is a component of the encoded solution. Please note that \(\hat{z}_r\) only gives the maximum permissible overtime and that the amount of overtime actually used has to be derived from the schedule to compute the objective function value related to this schedule.
For a single resource r, the set of possible (integer) \(\hat{z}_r\) values is rather small and contains \(\overline{z}_r+1\) elements \(\{0, \ldots , \overline{z}_r\}\). However, even a full state space enumeration for this representation does not necessarily yield an optimal schedule, since this representation only explores a subset of the entire set of all possible overtime profiles. An obvious advantage of this representation is that it is quite lean.
Solution encoding \((\lambda |\hat{z}_{rt})\)
The \((\lambda |{\hat{z}}_{rt})\) representation is quite similar to the \((\lambda |{\hat{z}}_{r})\) representation. It generalizes the \((\lambda |{\hat{z}}_r)\) representation by deciding on a per-period basis on the allowed amount of overtime and, therefore, inducing a time-varying profile. Here \(\hat{z}_{rt}\) denotes a matrix of permissible overtime capacities. When applying the SSGS, the modified and time-variant period capacity \(K_{{rt}}^{{\bmod }}\,=\,K_r+\hat{z}_{rt}\) for each resource r and period t is considered.
For the project introduced in Fig. 1, the representation:
$$\begin{aligned} (\lambda |\hat{z}_{rt})\,=\,\begin{array}{cccccccccc}(0&1&3&2&5&4&6&7\;|\;\begin{bmatrix} 0&0&0&2&2&2&0&0&0&0 \end{bmatrix})\end{array} \end{aligned}$$
(11)
with permission of two units of overtime in periods four, five, and six is decoded into the schedule in Fig. 2 (in which no overtime is actually used), whereas the representation
$$\begin{aligned} (\lambda |\hat{z}_{rt})\,=\,\begin{array}{cccccccccc}(0&1&3&2&5&4&6&7\;|\;\begin{bmatrix} 0&0&1&1&0&0&0&0&0&0 \end{bmatrix})\end{array} \end{aligned}$$
(12)
with permission of one unit of overtime in periods three and four leads to the schedule in Fig. 4.
Since this representation can be used to explore the entire set of possible overtime profiles, it is in principle guaranteed to find an optimal solution when doing a full state space exploration. However, this is of course not practical, since it is still an \(\mathcal {NP}\)-hard problem and the matrix of permissible overtime capacities \(\hat{z}_{rt}\) can get very large very quickly, requiring a high-dimensional search process in combination with all possible activity lists.
Solution encoding \((\lambda |\beta )\)
In the \((\lambda | \beta )\) representation, the need to use overtime is tied to activities j as opposed to resources r as assumed in the \((\lambda | \hat{z}_r)\) representation or resource-period combinations (r, t) in the \((\lambda |\hat{z}_{rt})\) representation. The idea behind this representation is that some activities may be especially critical, for example, due to the fact that they have many successors or a long duration. The \((\lambda |\beta )\) representation hence explicitly contains the information whether an activity is allowed to be scheduled with or only without additional overtime usage. When the activity list \(\lambda\) is being decoded and an activity j is being treated given a partial schedule, the original capacity \(K_r\) is used with respect to resource r if \(\beta _j \,=\, 0\), i.e., if activity j is not allowed to use overtime while being inserted into the partial schedule. However, if \(\beta _j \,= \,1\), i.e., if activity j is allowed to use overtime, the modified capacity \(K_{{rt}}^{{\bmod }}\,=\,K_r+\overline{z}_{r}\) is being used.
For the project depicted in Fig. 1 requiring a single resource \(r\,=\,1\) with a regular period capacity of \(K_1\,=\,4\) capacity units the representation
$$\begin{aligned} \left( \begin{array}{c} \lambda \\ \beta \end{array} \right) \, =\, \left( \begin{array}{cccccccc} 0 &{} 1 &{} 3 &{} 2 &{} 5 &{} 4 &{} 6 &{} 7\\ 0 &{} 0 &{} 0 &{} 1 &{} 0 &{} 0 &{} 1 &{} 0\end{array} \right) \end{aligned}$$
(13)
with permission of overtime for activities two and six is decoded into the schedule in Fig. 3, whereas the representation:
$$\begin{aligned} \left( \begin{array}{c} \lambda \\ \beta \end{array} \right) \, = \,\left( \begin{array}{cccccccc} 0 &{} 1 &{} 3 &{} 2 &{} 5 &{} 4 &{} 6 &{} 7\\ 0 &{} 0 &{} 1 &{} 0 &{} 1 &{} 0 &{} 0 &{} 0\end{array} \right) \end{aligned}$$
(14)
with permission of overtime for activities three and five leads to the schedule in Fig. 2. Note that even a full state space enumeration along this representation is not guaranteed to yield an optimal solution as it may be optimal to use overtime during only a part of the execution of an activity (see Fig. 6d).
Iterative forward–backward improvement without cost increase
Since hybrid genetic algorithms incorporating a neighborhood search based additional improvement step are shown to be efficient for solving the RCPSP in the literature, see Kolisch and Hartmann (2006), we adapted the so-called iterative forward–backward improvement technique by Li and Willis (1992) for the RCPSP-ROC. In its original form, this technique tries to decrease the project duration by subsequently shifting and aligning all activities to the right and afterwards to the left. This is iteratively repeated until there is no improvement as opposed to the more widespread double justification variant with just two shifting passes. The approach tends to decrease makespan while maintaining the feasibility of the schedule. For a more detailed description of this procedure, see Valls et al. (2005). We modified the procedure by allowing only such shifts that do not increase overtime consumption, therefore, leading to schedules with possibly both decreased makespan and lowered overtime costs. We apply this improvement step to each given schedule for all decoding schemes introduced.
Genetic algorithms for the RCPSP-ROC
Basis solution approach of genetic algorithms
Genetic algorithms are nature-inspired meta-heuristics that have been successfully applied to many different combinatorial optimization problems. They were first presented by Holland (1975) and are now widely and successfully used in computer science and operations research. As shown in Kolisch and Hartmann (2006), they are the dominating heuristic method in the literature to solve the RCPSP as they can often find high-quality solutions for challenging problems very quickly.
Genetic algorithms for an optimization problem operate on a population of individuals or candidate solutions over a sequence of generations with reproduction and selection mimicking the “survival of the fittest”. A candidate solution can in principle contain a complete set of numerical values as assignment for the decision variables of an underlying optimization model as the binary \(x_{jt}\) and the integer \(z_{rt}\) variables in the RCPSP-ROC model presented in Sect. 3. However, it is often more convenient and efficient to use a more compact encoding like the activity list \(\lambda\) introduced before. The objective function value of the schedule then serves as a fitness value for the particular activity list of that individual.
The individuals of one generation are taken as parents, combined in the so-called crossover operation, and potentially mutated to hopefully create better child individuals. Out of the set of parents and children, a new generation of parents for the next generation is selected. This process is repeated iteratively until a specified termination criterion is met.
To characterize our genetic algorithms, we hence have to specify the solution representation, the decoding scheme and the fitness computation as well as the structure of the initial population, the combination of solutions, and finally the mutation and selection operators. As representations, we use the encodings introduced in Sect. 4.2 together with their corresponding schedule generation schemes. For each of these considered representations, we specify the remaining elements of the genetic algorithm in the following subsections.
Generation of the initial population
All our solution representations contain activity lists as a substantial element. We construct each activity list in the initial population step by step, starting with the first position of that list. For each position, we determine the set of activities \(j \in \mathcal {D}\) that have not yet been assigned to the activity list, while all immediate predecessor activities \(i \in P_j\) have already been assigned to that particular list. Each such activity \(j \in \mathcal {D}\) can hence be selected for the currently considered position of the activity list without violating any precedence constraint between activities. One of these activities \(j \in \mathcal {D}\) is chosen randomly following a distribution, where the selection probability positively correlates with the priority value of that activity (biased sampling). The chosen activity is then appended to the activity list and this procedure repeats until all activities are included.
In our case, we are using the latest finishing times \(LFT_j\) of the activities as priority values, so that the priority of activities decreases with increasing latest finishing times. This is a useful priority rule, since delaying an activity j with a small \(LFT_j\) value is likely to postpone project completion. Based on these priorities, the weight \(w_j\,=\,\max _{i \in \mathcal {D}} LFT_i \,- \,LFT_j\) is the relative regret of not selecting activity j, i.e., the difference between highest overall priority value for the assignable activities \(i \in \mathcal {D}\) and priority value of activity j. To randomly select one of the schedulable activities \(j\, \in \, \mathcal {D}\), we use non-uniform selection probabilities
$$\begin{aligned} Prob_j \,=\, \frac{(w_j+1)}{\sum _{i \,\in \,\mathcal {D}} (w_i\,+\,1)} \end{aligned}$$
in a regret-based biased random sampling (RBBRS) as proposed by Kolisch and Drexl (1996) and Tormos and Lova (2001).
In the \((\lambda |\hat{z}_r)\) representation, an additional initial limit on the permissible overtime \(\hat{z}_r\) for each resource r has to be assigned for each individual. We draw it randomly from a uniform distribution over the integer values in the set \(\{0, 1, 2, ..., \overline{z}_r \,-\, 1, \overline{z}_r\}\) for each resource r. In the case of the \((\lambda |\hat{z}_{rt})\) representation, we use this limit for resource r over all periods t, so that initially, we have \(\hat{z}_{r,1}\,=\,\hat{z}_{r,2}\,=\,...=\hat{z}_{r,T}\) for each resource r of an individual. In the \((\lambda |\beta )\) representation, the binary parameter \(\beta _j\) which indicates whether activity j may be scheduled using overtime is set to 0 or 1 with probabilities of 0.5 each.
Crossover
During each iteration of the genetic algorithm, we build pairs of individuals by randomly matching distinct individuals from the current parent set until each individual has been matched with one other individual from that set. Denote one individual from such a pair as the mother “M” and the other as the father “F”.
Let \(\lambda ^\mathrm{M}\) be the mother’s activity list and \(\lambda ^\mathrm{F}\) be the father’s activity list. Following Hartmann (1998), we perform a one-point crossover on those activity lists. We pick a random number q between 1 and J. A daughter is characterized by choosing the first q elements from the mother. The remaining elements, not yet chosen from the mother, are taken in the order of the father. The son is determined analogously by switching the roles of mother and father. With this approach, all precedence restrictions are always met, so that each activity list is feasible. For the \((\lambda |\beta )\) representation containing an additional overtime decision per job, the overtime decision for each job is linked to the overtime decision from the passing parent.
We describe this procedure using an example for the \((\lambda |\beta )\) representation of the sample project in Fig. 1 (considering only the non-dummy activities) with the crossover point \(q=3\):
$$\begin{aligned} I^\mathrm{M} \,=\, \left( \begin{array}{ccc|ccc} 1 &{} 2 &{} 3 &{} 4 &{} 5 &{} 6 \\ 1 &{} 1 &{} 0 &{} 0 &{} 0 &{} 0\end{array} \right) \quad I^\mathrm{F} \,= \,\left( \begin{array}{ccc|ccc} 1 &{}3&{} 5 &{} 2 &{} 4 &{} 6\\ 1 &{} 0 &{} 1 &{} 0 &{} 1 &{} 0\end{array} \right) \end{aligned}$$
$$\begin{aligned} I^\mathrm{D} \,=\, \left( \begin{array}{ccc|ccc} 1 &{} 2 &{} 3 &{} 5 &{} 4 &{} 6 \\ 1 &{} 1 &{} 0 &{} 1 &{} 1 &{} 0\end{array} \right) \quad I^\mathrm{S} \,= \,\left( \begin{array}{ccc|ccc} 1 &{} 3 &{} 5 &{} 2 &{} 4 &{} 6 \\ 1 &{} 0 &{} 1 &{} 1 &{} 0 &{} 0\end{array} \right) \end{aligned}$$
The first three positions on the activity list for the daughter “D” are taken in the sequence given by the mother. It is not possible to simply take the last three elements from the father as activity two would be implemented twice and activity five never. For this reason, we only take the sequence of the remaining activities from the father: five, four, and six. The \(\beta _j\) values for the first three positions are inherited from the mother. For the other activities, the values are inherited from the father. The son “S” analogously inherits in the opposite direction.
In the \((\lambda |\hat{z}_{r})\) and \((\lambda |\hat{z}_{rt})\) representations, a second one-point crossover is performed. The crossover of the \(\hat{z}_r\) and \(\hat{z}_{rt}\) components is independent of the crossover of the activity lists. The reason is that there exists no direct relationship between the elements of an activity list and the entries of the matrix \(\hat{z}_{rt}\) or vector \(\hat{z}_r\). We show the procedure for the case of two resources r and six periods t and first assume that the crossover is performed periodwise between periods four and five. For clarity reasons, the activity lists as part of the complete \((\lambda |\hat{z}_{rt})\)-genotypes were omitted in this example. Their crossover again follows the procedure from Hartmann (1998):
$$\begin{aligned} {\hat{z}_{rt}^\mathrm{M}} \,=\, \left( \begin{array}{cccc|cc} 3 &{} 3 &{} 3 &{} 3 &{} 3 &{} 3 \\ 1 &{} 1 &{} 4 &{} 4&{} 4 &{} 4\end{array} \right) \quad {\hat{z}_{rt}^\mathrm{F}}\, = \,\left( \begin{array}{cccc|cc} 2 &{}2&{} 2 &{} 2 &{} 6 &{} 6\\ 0 &{} 0 &{} 1 &{} 1 &{} 1 &{} 1\end{array} \right) \end{aligned}$$
$$\begin{aligned} {\hat{z}_{rt}^\mathrm{D}} \,=\, \left( \begin{array}{cccc|cc} 3 &{} 3 &{} 3 &{} 3 &{} 6 &{} 6 \\ 1 &{} 1 &{} 4 &{} 4 &{} 1 &{} 1\end{array} \right) \quad {\hat{z}_{rt}^\mathrm{S}} \,=\, \left( \begin{array}{cccc|cc} 2 &{} 2 &{} 2 &{} 2 &{} 3 &{} 3 \\ 0 &{} 0 &{} 1 &{} 1 &{} 4 &{} 4\end{array} \right) \end{aligned}$$
The result shows, e.g., for the son individual and resource \(r\,=\,1\), that the permissible overtime is two units for the first four periods (as inherited from the father) and three units for the remaining two periods (as inherited from the mother). In a similar way, the crossover can be performed resourcewise between resources, in this case the only two resources one and two:
$$\begin{aligned} I^\mathrm{M}_{\hat{z}_{rt}}\, =\, \left( \begin{array}{cccccc} 3 &{} 3 &{} 3 &{} 3 &{} 3 &{} 3 \\ 1 &{} 1 &{} 4 &{} 4&{} 4 &{} 4\end{array} \right) \quad I^\mathrm{F}_{\hat{z}_{rt}}\, = \,\left( \begin{array}{cccccc} 2 &{}2&{} 2 &{} 2 &{} 6 &{} 6\\ 0 &{} 0 &{} 1 &{} 1 &{} 1 &{} 1\end{array} \right) \end{aligned}$$
$$\begin{aligned} I^\mathrm{D}_{\hat{z}_{rt}} \,= \,\left( \begin{array}{cccccc} 3 &{} 3 &{} 3 &{} 3 &{} 3 &{} 3 \\ 0 &{} 0 &{} 1 &{} 1 &{} 1 &{} 1\end{array} \right) \quad I^\mathrm{S}_{\hat{z}_{rt}} \,= \,\left( \begin{array}{cccccc} 2 &{} 2 &{} 2 &{} 2 &{} 6 &{} 6 \\ 1 &{} 1 &{} 4 &{} 4 &{} 4 &{} 4\end{array} \right) \end{aligned}$$
In the case of the \((\lambda |\hat{z}_{rt})\) representation, we either perform the crossover periodwise or resourcewise (with probabilities of 0.5 each). For the \((\lambda |\hat{z}_{r})\) representation, it can only be performed resourcewise.
Mutation
Mutation is only applied with a certain small mutation probability \(P_{mutate}\). For activity lists, we apply a so-called adjacent pairwise interchange, see Hartmann (1999), p. 90, and Brucker and Knust (2012), p. 130. With probability \(P_{mutate}\) an activity \(j\,=\,\lambda _i\) on position i of list \(\lambda\) is exchanged with the activity \(\lambda _{i\,+\,1}\) on the next position \(i\,+\,1\) (if available) unless this would result in an infeasible activity list. Therefore, feasibility-violating swaps are avoided.
In the case of the \((\lambda |\hat{z}_{r})\) or the \((\lambda |\hat{z}_{rt})\) representation, we mutate the overtime capacities \(\hat{z}_{r}\) and \({z}_{rt}\) by randomly either increasing or decreasing all values by one capacity unit (unless this would violate the limits 0 and \(\overline{z}_r\), respectively). For the \((\lambda |\beta )\) representation, we mutate by flipping a randomly selected bit, i.e., by setting \(\beta _j \leftarrow 1\, -\, \beta _j\).
Selection
In the selection step, individuals with low fitness values get discarded. For our implementation, we chose to discard the 50% worst individuals out of the set of all parents and all children of the current generation, i.e., we use an elite selection scheme.
Central elements of the local search implementation using LocalSolver
The commercial general-purpose local search solver LocalSolver offers as its front end a descriptive modeling language. It can be used to declare and define the elements of an optimization model such as variables, parameters, objective function, and constraints. The exploration of the search space is completely done in a proprietary black-box fashion by LocalSolver.
In principle, it is possible to use a direct solution representation based on the binary \(x_{jt}\) variables, as defined in the RCPSP-ROC. However, this representation is neither suitable for a local search algorithm nor for a genetic algorithm. Swapping two jobs in a schedule, for example, is conceptually rather simple and easily implemented in an activity list encoding, whereas in a direct binary encoding of a schedule, potentially, many different variables must be modified and it can be difficult to achieve feasibility again.
Fortunately, the descriptive LocalSolver language contains not only the modeling elements commonly used in MIP models like the RCPSP-ROC in Sect. 3, but also other language elements like if–then clauses, maximum or minimum operators, and so-called list variables that can be used to describe problems in a very compact way. In particular, this list variable can be used to directly model an activity list and perform a local search over this activity list using the black-box LocalSolver search engine, i.e., its back end.
Using a list variable to directly model, the activity list turned out to be very simple and effective. A list variable in LocalSolver holds a permutation of numbers in a certain range \([0, n-1] \cap \mathbb {N}_0\) of n elements. The upper limit \(n-1\), and therefore, the cardinality n of this range can be externally specified. For our problem setting, we set \(n=J\,+\,2\). LocalSolver explores possible solutions by permuting this list.
When using an indirect encoding via an activity list in a modeling language, a corresponding decoding procedure must be implemented. The current version of the LocalSolver language is well suited for descriptive programming but not yet suitable for highly efficient algorithmic procedural programming. However, the LocalSolver Software Development Kit (SDK) offers an Application Programming Interface (API) for implementing parts of the model as function callbacks written in another general-purpose programming language. With these so-called native functions, a custom algorithm can be integrated in a LocalSolver model and the respective search process. This allows us to reuse and plug in the schedule generation and decoding procedures described in Sect. 4.2 and already implemented in the genetic algorithm.
The additional effort to use LocalSolver within our C++ program is hence very limited. The main component for the case of the \((\lambda |\beta )\) representation is shown in Listing 1. This short code fragment is sufficient to declare the model’s data object (code lines 1 and 2), embed the native decoding function (code lines 5 and 6), and establish the activity list augmented by the binary \(\beta _j\) vector (code lines 9–24) using a list variable. This way, additional data representing the decisions on overtime is integrated into LocalSolver.
Note that the list variable, i.e., the activity list, is not guaranteed to be topologically ordered after a position swap performed by LocalSolver. There are two ways to resolve this issue. First, one could add an additional constraint to the model, which enforces that list elements (i.e., activities) can only occur at a certain list position if all predecessors are at earlier positions on that list. In this case, the local search via the LocalSolver engine is forced to generate only feasible activity lists. Alternatively, one could omit this constraint and instead modify the decoding procedure to be usable with all possible permutations. To this end, it is sufficient to delay scheduling an activity on the list until all its immediate predecessors have been scheduled when decoding the activity list. Numerical experiments show that the second option is more efficient on average, because LocalSolver performs worse when using a model with order constraints. This negative effect overcompensates the advantage of the topologically ordered list.
In summary, LocalSolver only needs a trivial model consisting of the list variable, setting its length to the number of jobs and possible additional decision variables for overtime. LocalSolver decides on the assigned values of the decision variables and then passes these variables to the native function facilities of the LocalSolver API. The called decoding procedure maps the list and overtime decisions to a schedule and an objective function value, as described in Sect. 4.2. The local search stops when a pre-determined clock time has elapsed. Contrasting it with the genetic algorithm, we observe that we now operate on a single solution as opposed to a population of solutions and that all the modification and selection is done by a black-box engine as opposed to the crossover, mutation, and selection operators required in the genetic algorithms.