Fico Fico Xpress Optimization Xpress Optimization: User Guide
Fico Fico Xpress Optimization Xpress Optimization: User Guide
Fico Fico Xpress Optimization Xpress Optimization: User Guide
12.6
USER GUIDE
If you need information on other Xpress Optimization products, or you need to discuss maintenance contracts or other sales-related
items, contact FICO by:
Phone: +1 (408) 535-1500 or +44 207 940 8718
Web: www.fico.com/optimization and use the available contact forms
Product Support
For the latest news and Xpress software and documentation updates, please visit the Xpress website at http://www.fico.com/xpress
or subscribe to our mailing list.
For the latest news about Kalis, training course programs, and examples, please visit the Artelys website at http://www.artelys.com.
Contents
1 Introduction 1
1.1 Xpress Kalis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Note on product versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Software installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Basic concepts of Constraint Programming . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Contents of this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3 Constraints 17
3.1 Constraint handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.1.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.1.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.1.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.4 Naming constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.5 Explicit posting of constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.1.6 Explicit constraint propagation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.2 Arithmetic constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3 all_different: Sudoku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.3.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.4 abs and distance: Frequency assignment . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.4.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.4.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.4.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.5 element: Sequencing jobs on a single machine . . . . . . . . . . . . . . . . . . . . . . . . 28
3.5.1 Model formulation 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.5.2 Implementation of model 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.5.4 Alternative formulation using disjunctions . . . . . . . . . . . . . . . . . . . . . . . 33
3.5.5 Implementation of model 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.6 occurrence: Sugar production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.6.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.6.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.6.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.7 distribute: Personnel planning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.7.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.7.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.7.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.8 implies: Paint production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.8.1 Formulation of model 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.8.2 Implementation of model 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.8.3 Formulation of model 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.8.4 Implementation of model 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.8.5 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.9 equiv: Location of income tax offices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.9.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.9.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.9.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.10 cycle: Paint production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.10.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.10.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.10.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.11 Generic binary constraints: Euler knight tour . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.11.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.11.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.11.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.11.4 Alternative implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.11.5 Alternative implementation 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.12 Symmetry breaking: scenes allocation problem . . . . . . . . . . . . . . . . . . . . . . . . 56
3.12.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.12.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.12.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4 Enumeration 60
4.1 Predefined search strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.2 Interrupting and restarting the search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.3 Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.4 User-defined enumeration strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.4.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4.1.1 Parallel machine assignment . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4.1.2 Machines working in series . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.4.3 User search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.4.4 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.5 Reversible numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.6 Analyzing infeasibility and handling conflicts . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.6.1 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.6.2 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5 Scheduling 73
5.1 Tasks and resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2 Precedences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.2.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.2.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.2.4 Alternative formulation without scheduling objects . . . . . . . . . . . . . . . . . . 76
5.3 Disjunctive scheduling: unary resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.3.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.3.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.3.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.4 Cumulative scheduling: discrete resources . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.4.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.4.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.4.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.4.4 Alternative formulation without scheduling objects . . . . . . . . . . . . . . . . . . 82
5.4.5 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.5 Renewable and non-renewable resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.5.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.5.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.5.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.5.4 Alternative formulation without scheduling objects . . . . . . . . . . . . . . . . . . 88
5.5.5 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.6 Extensions: setup times, resource choice, usage profiles . . . . . . . . . . . . . . . . . . . 90
5.6.1 Setup times . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.6.1.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.6.1.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.6.1.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.6.2 Alternative resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.6.2.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.6.2.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.6.2.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.6.3 Resource profiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.6.3.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.6.3.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.6.3.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.6.4 Resource idle times and preemption of tasks . . . . . . . . . . . . . . . . . . . . . 97
5.6.4.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.6.4.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.6.4.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.7 Enumeration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.7.1 Variable-based enumeration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.7.1.1 Using cp_minimize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.7.1.2 Using cp_schedule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.7.2 Task-based enumeration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.7.2.1 Model formulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.7.2.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.7.2.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.7.2.4 Alternative search strategies . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.7.3 Choice of the propagation algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information iii
Contents
Appendix 115
A Trouble shooting 116
Bibliography 119
Index 120
Constraint Programming is an approach to problem solving that has been particularly successful for
dealing with nonlinear constraint relations over discrete variables. In the following we therefore often
use ‘Constraint Programming’ or ‘CP’ synonymously to ‘finite domain Constraint Programming’.
In the past, CP has been successfully applied to such different problem types as production scheduling
(with or without resource constraints), sequencing and assignment of tasks, workforce planning and
timetabling, frequency assignment, loading and cutting, and also graph coloring.
The strength of CP lies in its use of a high-level semantics for stating the constraints that preserves the
original meaning of the constraint relations (such high-level constraints are referred to as global
constraints). It is not necessary to translate constraints into an arithmetic form—a process whereby
sometimes much of the problem structure is lost. The knowledge about a problem inherent to the
constraints is exploited by the solution algorithms, rendering them more efficient.
Xpress Kalis, or Kalis for Mosel, provides access to the Artelys Kalis© Constraint Programming solver
from a Mosel module, kalis. Through Xpress Kalis, the Constraint Programming functionality of Kalis
becomes available in the Mosel environment, allowing the user to formulate and solve CP models in the
Mosel language. Xpress Kalis combines a finite domain solver and a solver over continuous (floating
point) variables. To aid the formulation of scheduling problems, the software defines specific aggregate
modeling objects representing tasks and resources that will automatically setup certain (implicit)
constraint relations and trigger the use of built-in search strategies specialized for this type of problem.
Standard scheduling problems may thus be defined and solved simply by setting up the corresponding
task and resource objects. Additionally, Xpress Kalis provides automatic linear relaxations of its
constraints to ease the formulation and solving of a wide range of optimization problems.
All data handling facilities of the Mosel environment, including data transfer in memory (using the
Mosel IO drivers) and ODBC access to databases (through the module mmodbc) can be used with kalis
without any extra effort.
The Mosel language supports typical programming constructs, such as loops, subroutines, etc., that
may be required to implement more complicated algorithms. Mosel can also be used as a platform for
combining different solvers, in particular Xpress Kalis with Xpress Optimizer for joint CP – LP/MIP
problem solving1 . This manual explains the basics on modeling and programming with Mosel and,
where necessary, also some more advanced features. For a complete documentation and a more
thorough introduction to its use the reader is referred to the Mosel language reference manual and the
Mosel user guide.
Beyond the functionality readily available from Xpress Kalis the software also provides a unique
extension mechanism that opens it up to various kinds of additions of new functionality on the library
1 See the Xpress Whitepaper Multiple models and parallel solving with Mosel
level that become available within the Mosel language through the Mosel Native Interface (see the
Mosel NI reference manual and the Mosel NI user guide). Such user-defined extensions principally
relate to the definition of new constraints and branching schemes.
A Constraint Programming (CP) problem is defined by its decision variables with their domains and
constraints over these variables. The problem definition is usually completed by a branching strategy
(also referred to as enumeration or search strategy).
CP makes active use of the concept of variable domains, that is, the set out of which a decision variable
takes its values. In finite domain Constraint Programming these are sets or intervals of integer numbers.
Each constraint in CP comes with its own (set of) solution algorithm(s), typically based on results from
other areas, such as graph theory. Once a constraint has been established it maintains its set of
variables in a solved state, i.e., its solution algorithm removes any values that it finds infeasible from
the domains of the variables.
The constraints in a CP problem are linked by a mechanism called constraint propagation: whenever the
domain of a variable is modified this triggers a re-evaluation of all constraints on this variable which in
turn may cause modifications to other variables or further reduction of the domain of the first variable
as shown in the example in Figure 1.1 (the original domains of the variables are reduced by the addition
of two constraints; in the last step the effect of the second constraint is propagated to the first
constraint, triggering its re-evaluation).
A CP problem is built up incrementally by adding constraints and bounds on its variables. The solving of
a CP problem starts with the statement of the first constraint—values that violate the constraint relation
are removed from the domains of the involved variables. Since the effect of a newly added constraint is
propagated immediately to the entire CP problem it is generally not possible to modify or delete this
constraint from the problem later on.
In some cases the combination of constraint solving and the propagation mechanism may be sufficient
to prove that a problem instance is infeasible, but most of the time it will be necessary to add an
enumeration for reducing all variable domains to a single value (consistent instantiation or feasible
solution) or proving that no such solution exists. In addition it is possible to define an objective function
(a) (b)
(1) x y z (1) x y z
[1..10] [5..15] [1..15] [1.5,10] [5,15] [1,15]
y<z y _
< z
(2) x y z (2) x y z
[1..10] [5..14] [6..15] [1.5,10] [5,15] [5,15]
Figure 1.1: Example of constraint propagation, (a) finite domain (discrete) variables, (b) continuous variables.
(or cost function) and search for a feasible solution with the best objective function value (optimal
solution).
The first chapter deals with the basics of writing CP models with Mosel. It explains the general
structure of CP models and some basics on working with Mosel, including data handling. Then follows
a series of small problems illustrating the use of the different constraint relation types in Xpress Kalis.
The next chapter introduces in a more systematic way the different possibilities of defining
enumeration strategies, some of which already appear in the preceding model examples. The chapter
dedicated to the topic of scheduling introduces the modeling objects ‘task’ and ‘resource’ that simplify
the formulation of scheduling problems. The following chapter familiarizes the reader with the concept
of linear relaxations and shows how to work with them.
Apart from the initial examples, every example is presented as follows:
1. Example description
2. Formulation as a CP model
3. Implementation with Xpress Kalis: code listing and explanations
4. Results
All example models of this document are included with the set of examples that is provided as part of
the Xpress Kalis distribution.
model "Meeting"
uses "kalis"
declarations
MEETINGS = {'A','B','C','D'} ! Set of meetings
TIME = 1..3 ! Set of time slots
plan: array(MEETINGS) of cpvar ! Time slot per meeting
end-declarations
! Respect incompatibilities
plan('A') <> plan('B')
plan('A') <> plan('D')
plan('B') <> plan('C')
plan('B') <> plan('D')
! Solution printing
forall(m in MEETINGS)
writeln("Meeting ", m, ": ", getsol(plan(m)))
end-model
This Mosel model is saved as a text file with the name meeting.mos. Let us now take a closer look at
what we have just written.
Every Mosel program starts with the keyword model, followed by a model name chosen by the user.
The Mosel program is terminated with the keyword end-model.
As Mosel is itself not a solver, we specify that the Kalis constraint solver is to be used with the
statement
uses "kalis"
TIME is a so-called range set—i.e., a set of consecutive integers (here: from 1 to 3).
plan is an array of decision variables of type cpvar (finite domain CP variables; a second
decision variable type of Xpress Kalis is cpfloatvar for continuous variables), indexed by the
set MEETINGS.
The model then defines the domains of the variables using the Xpress Kalis procedure setdomain.
The decision variables are indeed created at their declaration with a large default domain and
setdomain reduces these domains to the intersection of the default domain with the indicated values.
As in the mathematical model, we use a forall loop to enumerate all the indices in the set MEETINGS.
This is followed by the statement of the constraints, in this model we have four disequality constraints.
With the function cp_find_next_sol, we call Kalis to solve the problem (find a feasible assignment
of values to all decision variables). We test the return value of this function: if no solution is found it
returns false and we stop the model execution at this point (by calling the Mosel procedure exit),
otherwise we print out the solution.
To solve the problem Xpress Kalis uses its built-in default search strategies. We shall see later how to
modify these strategies.
The solution for a CP variable is obtained with the function getsol. To write several items on a single
line use write instead of writeln for printing the output.
2.1.1.3 Formatting
Indentation, spaces, and empty lines in our model have been added to increase readability. They are
skipped by Mosel.
Line breaks: It is possible to place several statements on a single line, separating them by semicolons,
as such:
plan('A') <> plan('B'); plan('A') <> plan('D')
But since there are no special ‘line end’ or continuation characters, every line of a statement that
continues over several lines must end with an operator (+, >=, etc.) or characters like ‘,’ that make it
obvious that the statement is not terminated.
As shown in the example, single line comments in Mosel are preceded by !. Multiple line comments
start with (! and terminate with !).
1. From the Mosel command line: this method can be used on all platforms for which Mosel is
available. It is especially useful if you wish to execute a (batch) sequence of model runs—for
instance, with different parameter settings. The full Mosel functionality, including its debugger, is
accessible in this run mode.
2. Within the graphical environment Xpress Workbench: available to Windows users. Workbench is a
complete modeling and optimization development environment with a built-in text editor for
working with Mosel models and a number of displays that help analyze models and solution
algorithms in the development phase. Models can be modified and re-run interactively.
3. From within an application program: Mosel models may be executed and accessed from
application programs (C/C++, Java, VBA, C#). This functionality is typically used for the
deployment of Mosel models, integrating them into a company’s information system.
In this manual we shall use the first two methods for running the models we develop. For further detail
on embedding models into application programs the user is referred to the Mosel user guide.
When you have entered the complete model into the file meeting.mos, we can proceed to the solution
to our problem. We start Mosel at the command prompt by typing the following command
Meeting A: 1
Meeting B: 2
Meeting C: 1
Meeting D: 3
or simply
mosel meeting
The model execution performed by the command execute comprises three stages:
1. Compiling meeting.mos
Instead of using execute, you may choose to explicitly generate the compiled model file chess.bim
mosel compile meeting.mos
followed by
mosel run meeting.bim
To execute the model file meeting.mos with Workbench you need to carry out the following steps.
Start up Workbench: if you have followed the standard installation procedure for Xpress
Workbench, start the program by double clicking the icon on the desktop or selecting Start
Programs Xpress Xpress Workbench. Otherwise, you may also start up Workbench by double
clicking a model file (file with extension .mos).
Open the model file by choosing File Open. The model source is then displayed in the central
window (the Workbench Editor).
Click the green Run button or, alternatively, choose menu entry Run for the desired model.
The Build pane at the bottom of the workspace displays the model execution status messages from
Mosel and any outpu generated by it. If syntax errors are found in the model they are displayed here,
with details of the line and character position where the error was detected and a description of the
problem, if available.
Workbench makes all information about the model available for inspection through the Debugger pane
in the right hand window if the model is run in debug mode (green bug icon).
A first step for debugging a model certainly is to add additional output. In our model, we could, for
instance, print out the definition of the decision variables by adding the line
writeln(plan)
after the definition of the variables’ domains, or even print out the complete problem definition with the
procedure cp_show_prob. To obtain a more easily readable output for debugging the user may give
names to the decision variables of his problem. For example:
forall(m in MEETINGS) setname(plan(m), "Meeting "+m)
For using the Mosel debugger (please see the Mosel language reference manual for further detail) the
compilation flag -G needs to be used instead.
Workbench by default compiles models in standard mode, debug+trace information (option -G) is
automatically enabled when launching a debugging run (the debugger is started by clicking the ’debug’
button or via the Debug entry of the Run menu).
2.2.2 Implementation
The Mosel model now looks as follows.
declarations
EXAM = 1..11 ! Set of exams
TIME = 1..8 ! Set of time slots
INCOMP: array(EXAM,EXAM) of integer ! Incompatibility between exams
EXAMNAME: array(EXAM) of string
EXAMNAME:: (1..11)["DA","NA","C++","SE","PM","J","GMA","LP","MP","S","DSE"]
! Respect incompatibilities
forall(d,e in EXAM | d<e and INCOMP(d,e)=1) plan(d) <> plan(e)
! Solution printing
forall(t in TIME) do
write("Slot ", t, ": ")
forall(e in EXAM)
if (getsol(plan(e))=t) then write(EXAMNAME(e)," "); end-if
writeln
end-do
end-model
The values of the array INCOMP are read in from the file i4exam.dat in an initializations block.
In the definition of the disequality constraints we check the value of the corresponding array
entry—conditions on the indices for loops, sums, and other aggregate operators are marked by a
vertical bar.
The data file has the following contents.
INCOMP: [0 1 0 0 1 0 1 0 0 1 1
1 0 0 0 1 0 1 0 0 1 1
0 0 0 1 1 1 1 0 1 1 1
0 0 1 0 1 1 1 0 0 1 1
1 1 1 1 0 1 1 1 1 1 1
0 0 1 1 1 0 1 0 1 1 1
1 1 1 1 1 1 0 1 1 1 1
0 0 0 0 1 0 1 0 0 1 1
0 0 1 0 1 1 1 0 0 1 1
1 1 1 1 1 1 1 1 1 0 1
1 1 1 1 1 1 1 1 1 1 0]
2.2.3 Results
The model prints out the following results. Only the first seven time slots are used for scheduling
exams.
Slot 1: DA C++ LP
Slot 2: NA SE MP
Slot 3: PM
Slot 4: GMA
Slot 5: S
Slot 6: DSE
Slot 7: J
Slot 8:
INCOMP: [("DA" "NA") 1 ("DA" "PM") 1 ("DA" "GMA") 1 ("DA" "S") 1 ("DA" "DSE") 1
("NA" "DA") 1 ("NA" "PM") 1 ("NA" "GMA") 1 ("NA" "S") 1 ("NA" "DSE") 1
("C++" "SE") 1 ("C++" "PM") 1 ("C++" "J") 1 ("C++" "GMA") 1
("C++" "MP") 1 ("C++" "S") 1 ("C++" "DSE") 1
("SE" "C++") 1 ("SE" "PM") 1 ("SE" "J") 1 ("SE" "GMA") 1 ("SE" "S") 1
("SE" "DSE") 1
("PM" "DA") 1 ("PM" "NA") 1 ("PM" "C++") 1 ("PM" "SE") 1 ("PM" "J") 1
("PM" "GMA") 1 ("PM" "LP") 1 ("PM" "MP") 1 ("PM" "S") 1 ("PM" "DSE") 1
("J" "C++") 1 ("J" "SE") 1 ("J" "PM") 1 ("J" "GMA") 1 ("J" "MP") 1
("J" "S") 1 ("J" "DSE") 1
("GMA" "DA") 1 ("GMA" "NA") 1 ("GMA" "C++") 1 ("GMA" "SE") 1
("GMA" "PM") 1 ("GMA" "J") 1 ("GMA" "LP") 1 ("GMA" "MP") 1
("GMA" "S") 1 ("GMA" "DSE") 1
("LP" "PM") 1 ("LP" "GMA") 1 ("LP" "S") 1 ("LP" "DSE") 1
("MP" "C++") 1 ("MP" "PM") 1 ("MP" "J") 1 ("MP" "GMA") 1 ("MP" "S") 1
("MP" "DSE") 1
("S" "DA") 1 ("S" "NA") 1 ("S" "C++") 1 ("S" "SE") 1 ("S" "PM") 1
("S" "J") 1 ("S" "GMA") 1 ("S" "LP") 1 ("S" "MP") 1 ("S" "DSE") 1
("DSE" "DA") 1 ("DSE" "NA") 1 ("DSE" "C++") 1 ("DSE" "SE") 1
("DSE" "PM") 1 ("DSE" "J") 1 ("DSE" "GMA") 1 ("DSE" "LP") 1
("DSE" "MP") 1 ("DSE" "S") 1 ]
NT: 8
Our model also needs to undergo a few changes: the sets EXAM and TIME are now declared by stating
their types, which turns them into dynamic sets (as opposed to their previous constant definition by
stating their values). As a consequence, the array of decision variables plan is declared before the
indexing set EXAM is known and Mosel creates this array as a dynamic array, meaning that the
declaration results in an empty array and its elements need to be created explicitly (using the Mosel
procedure create) once the indices are known. Before creating the variables, we modify the default
bounds of Xpress Kalis to the values corresponding to the set TIME, thus replacing the call to
setdomain.
The array INCOMP is explicitly declared as a dynamic array. The initializations block will assign
values to just those entries that are listed in the data file (with the previous, constant declaration, all
entries were defined) and the explicit dynamic marker means that Mosel’s automatic finalization is not
applied—it would try to transform the array into a static array. This makes it possible to reformulate the
condition on the loop defining the disequality constraints: we now simply test for the existence of an
entry instead of comparing all data values. With larger data sets, using the keyword exists may
greatly reduce the execution time of loops involving sparse arrays (multidimensional data arrays with
few entries different from 0).
declarations
NT: integer ! Number of time slots
EXAM: set of string ! Set of exams
TIME: set of integer ! Set of time slots
INCOMP: dynamic array(EXAM,EXAM) of integer ! Incompatibility between exams
TIME:= 1..NT
! Respect incompatibilities
forall(d,e in EXAM | exists(INCOMP(d,e)) and d<e) plan(d) <> plan(e)
! Solution printing
forall(t in TIME) do
write("Slot ", t, ": ")
forall(e in EXAM)
if (getsol(plan(e))=t) then write(e," "); end-if
writeln
end-do
end-model
Running this fully data-driven model produces the same solution as the previous version.
An alternative to the explicit creation of the decision variables plan is to move their declaration after the
initialization of the data as shown in the code extract below. In this case, it is important to finalize
the indexing set EXAM, which turns it into a constant set with its current contents and allows Mosel to
create any subsequently declared arrays indexed by this set as static arrays.
declarations
NT: integer ! Number of time slots
EXAM: set of string ! Set of exams
TIME: set of integer ! Set of time slots
INCOMP: dynamic array(EXAM,EXAM) of integer ! Incompatibility between exams
end-declarations
finalize(EXAM)
TIME:= 1..NT
2.3.1 Optimization
Since running our model i4exam_ka.mos in Section 2.2.2 has produced a solution to the problem that
does not use all time slots one might wonder which is the minimum number of time slots that are
required for this problem. This question leads us to the formulation of an optimization problem.
We introduce a new decision variable numslot over the same value range as the plani variables and add
the constraints that this variable is greater or equal to every plani variable. A simplified formulation is to
say that the variable numslot equals the maximum value of all plani variables.
The objective then is to minimize the value of numslot, which results in the following model.
declarations
NT: integer ! Number of time slots
EXAM: set of string ! Set of exams
TIME: set of integer ! Set of time slots
INCOMP: dynamic array(EXAM,EXAM) of integer ! Incompatibility between exams
finalize(EXAM)
TIME:= 1..NT
! Respect incompatibilities
forall(d,e in EXAM | exists(INCOMP(d,e)) and d<e) plan(d) <> plan(e)
! Solution printing
forall(t in TIME) do
write("Slot ", t, ": ")
forall(e in EXAM)
if (getsol(plan(e))=t) then write(e," "); end-if
writeln
end-do
end-model
Instead of cp_find_next_sol we now use cp_minimize with the objective function variable
2.3.2 Enumeration
When comparing the problem statistics obtained by adding a call to cp_show_stats to the end of the
different versions of our model, we can see that switching from finding a feasible solution to
optimization considerably increases the number of nodes explored by the CP solver.
So far we have simply relied on the default enumeration strategies of Xpress Kalis. We shall now try to
see whether we can reduce the number of nodes explored and hence shorten the time spent by the
search for proving optimality.
The default strategy of Kalis for enumerating finite domain variables corresponds to adding the
statement
cp_set_branching(assign_var(KALIS_SMALLEST_DOMAIN, KALIS_MIN_TO_MAX))
before the start of the search. assign_var denotes the branching scheme (‘a branch is formed by
assigning the next chosen value to the branching variable’), KALIS_SMALLEST_DOMAIN is the variable
selection strategy (‘choose the variable with the smallest number of values remaining in its domain’),
and KALIS_MIN_TO_MAX the value selection strategy (‘from smallest to largest value’).
Since we are minimizing the number of time slots, enumeration starting with the smallest value seems
to be a good idea. We therefore keep the default value selection criterion. However, we may try to
change the variable selection heuristic: replacing KALIS_SMALLEST_DOMAIN by KALIS_MAX_DEGREE
results in a reduction of the tree size and search time to less than half of its default size.
Here follows once more the complete model.
declarations
NT: integer ! Number of time slots
EXAM: set of string ! Set of exams
TIME: set of integer ! Set of time slots
INCOMP: dynamic array(EXAM,EXAM) of integer ! Incompatibility between exams
finalize(EXAM)
TIME:= 1..NT
! Respect incompatibilities
forall(d,e in EXAM | exists(INCOMP(d,e)) and d<e) plan(d) <> plan(e)
! Solution printing
forall(t in TIME) do
write("Slot ", t, ": ")
forall(e in EXAM)
if (getsol(plan(e))=t) then write(e," "); end-if
writeln
end-do
cp_show_stats
end-model
NB: In the model versions without optimization we may try to obtain a more evenly distributed schedule
by choosing values randomly, that is, by using the value selection criterion
KALIS_RANDOM_VALUE instead of KALIS_MIN_TO_MAX.
Further detail on the definition of branching strategies is given in Chapter 4.
All through this chapter we have worked with the decision variable type cpvar (discrete variables). A
second variable type in Xpress Kalis are continuous variables (type cpfloatvar). Such variables are
used in a similar way to what we have seen above for discrete variables, for example:
setparam("KALIS_DEFAULT_CONTINUOUS_LB", 0)
setparam("KALIS_DEFAULT_CONTINUOUS_UB", 10)
declarations
x,y: cpfloatvar
end-declarations
A few differences in the use of the two decision variable types exist:
Constraints involving cpfloatvar cannot be strict inequalities (that is, only the operators <=, >=,
and = may be used).
Most global constraints (see Chapter 3) only apply to cpvar; also applicable to cpfloatvar are
maximum and minimum relations.
Search strategies enumerating the values in a variable’s domain can only be used with cpvar;
with cpfloatvar domain splitting must be used (see Chapter 4).
Access functions for enumerating domain values such getnext are not applicable to
cpfloatvar.
This chapter contains a collection of examples demonstrating the use of Xpress Kalis for solving
different types of (optimization) problems. The first section shows different ways of defining and
posting constraints for simple linear constraints. The following sections each introduce a new
constraint type. Since most examples use a combination of different constraints, the following list may
help in finding examples of the use of a certain constraint type quickly.
3.1.2 Implementation
The Mosel implementation of the new constraints is quite straightforward.
declarations
MEETINGS = {'A','B','C','D'} ! Set of meetings
TIME = 1..3 ! Set of time slots
plan: array(MEETINGS) of cpvar ! Time slot per meeting
end-declarations
forall(m in MEETINGS) do
setdomain(plan(m), TIME)
setname(plan(m), "plan"+m)
end-do
writeln("Original domains: ", plan)
! Respect incompatibilities
plan('A') <> plan('B')
plan('A') <> plan('D')
plan('B') <> plan('C')
plan('B') <> plan('D')
! Solution printing
forall(m in MEETINGS)
writeln("Meeting ", m, ": ", getsol(plan(m)))
end-model
3.1.3 Results
As the reader may have noticed, we have added printout of the variables planm at several places in the
model. The output generated by the execution of this model therefore is the following.
As can be seen from this output, immediately after stating the constraints, the domains of the
concerned variables have been reduced. The constraints are immediately and automatically posted to
the solver and their effects are propagated to the whole problem.
declarations
MEETINGS = {'A','B','C','D'} ! Set of meetings
TIME = 1..3 ! Set of time slots
plan: array(MEETINGS) of cpvar ! Time slot per meeting
Ctr: array(range) of cpctr
end-declarations
forall(m in MEETINGS) do
setdomain(plan(m), TIME)
setname(plan(m), "plan"+m)
end-do
writeln("Original domains: ", plan)
! Respect incompatibilities
plan('A') <> plan('B')
plan('A') <> plan('D')
plan('B') <> plan('C')
plan('B') <> plan('D')
! Solution printing
forall(m in MEETINGS) writeln("Meeting ", m, ": ", getsol(plan(m)))
end-model
From the output produced by this model, we can see that the mere definition of named constraints
does not have any effect on the domains of the variables (the constraints are defined in Mosel but not
yet sent to the Kalis solver). Only after stating the names of the constraints (that is, sending them to the
solver) we obtain the same domain reductions as with the previous version of the model.
forall(i in 1..3)
if not cp_post(Ctr(i)) then
writeln("Constraint ", i, " makes problem infeasible")
exit(1)
end-if
The return value of the every constraint posting is checked and the program is stopped if the addition
of a constraint leads to an infeasibility.
The output produced by this model version is exactly the same as what we have seen in the previous
section.
then constraints posted to the solver are not propagated. In this case, constraint propagation will only
be launched by a call to cp_propagate or by starting the enumeration (subroutines
cp_find_next_sol, cp_minimize, etc.).
setparam("KALIS_DEFAULT_LB", 0)
setparam("KALIS_DEFAULT_UB", 5)
setparam("KALIS_DEFAULT_CONTINUOUS_LB", -10)
setparam("KALIS_DEFAULT_CONTINUOUS_UB", 10)
declarations
a,b,c: cpvar
x,y,z: cpfloatvar
end-declarations
x = ln(y)
y = abs(z)
x⁎y <= z^2
z = -a/b
a⁎b⁎c^3 >= 150
while (cp_find_next_sol)
writeln("a:", getsol(a), ", b:", getsol(b), ", c:", getsol(c),
", x:", getsol(x), ", y:", getsol(y), ", z:", getsol(z))
end-model
A B C D E F G H I
1 4 3 6
2 6 5 7
3 8 7 3
4 5 1 3 7
5 1 2 8 4
6 9 7 5 2
7 4 5 9
8 9 4 5
9 3 4 6
A B C D E F G H I
1 8 3
2 5 4
3 2 7 6
4 1 5
5 3 9
6 6 4
7 7 2 3
8 4 1
9 9 8
These constraints can be stated with Xpress Kalis’s all_different relation. This constraint ensures
that all variables in the relation take different values.
3.3.2 Implementation
The Mosel implementation for the Sudoku puzzle in Table 3.2 looks as follows.
setparam("kalis_default_lb", 1)
setparam("kalis_default_ub", 9) ! Default variable bounds
declarations
XS = {'A','B','C','D','E','F','G','H','I'} ! Columns
YS = 1..9 ! Rows
v: array(XS,YS) of cpvar ! Number assigned to cell (x,y)
end-declarations
while (cp_find_next_sol) do
solct+=1
print_solution(solct)
end-do
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Solution printing
procedure print_solution(numsol: integer)
writeln(getparam("KALIS_COMPUTATION_TIME"), "sec: Solution ", numsol)
writeln(" A B C D E F G H I")
forall(y in YS) do
write(y, ": ")
forall(x in XS)
write(getsol(v(x,y)), if(x in {'C','F'}, " | ", " "))
writeln
if y mod 3 = 0 then
writeln(" ---------------------")
end-if
end-do
end-procedure
end-model
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
In this model, the call to cp_find_next_sol is embedded in a while loop to search all feasible
solutions. At every loop execution the procedure print_solution is called to print out the solution
found nicely formatted. Subroutines in Mosel may have declarations blocks for local declarations
and they may take any number of arguments. Since, in our model, the call to the procedure occurs
before its definition, we need to declare it before the first call using the keyword forward.
For selecting the information that is to be printed by the subroutine we use two different versions of
Mosel’s if statement: the inline if and if-then that includes a block of statements.
At the end of the model run we retrieve from the solver the run time measurement (parameter
KALIS_COMPUTATION_TIME) and the number of nodes explored by the search (parameter
KALIS_NODES).
To obtain a complete list of parameters defined by Xpress Kalis type the command
mosel exam -p kalis
(for a listing of the complete functionality of Xpress Kalis leave out the flag -p).
3.3.3 Results
The model shown above generates the following output; this puzzle has only one solution, as is usually
the case for Sudoku puzzles.
0.16sec: Solution 1
A B C D E F G H I
1: 8 6 9 | 2 4 3 | 1 5 7
2: 3 5 7 | 6 1 9 | 4 8 2
3: 2 4 1 | 8 7 5 | 3 6 9
---------------------
4: 4 9 8 | 1 3 2 | 6 7 5
5: 7 1 3 | 5 8 6 | 9 2 4
6: 6 2 5 | 7 9 4 | 8 3 1
---------------------
7: 1 7 6 | 4 2 8 | 5 9 3
8: 9 8 4 | 3 5 7 | 2 1 6
9: 5 3 2 | 9 6 1 | 7 4 8
---------------------
Number of solutions: 1
Time spent in enumeration: 0.41sec
Number of nodes: 2712
The all_different relation takes an optional second argument that allows the user to specify the
propagation algorithm to be used for evaluating the constraint. If we change from the default setting
(KALIS_FORWARD_CHECKING) to the more aggressive strategy KALIS_GEN_ARC_CONSISTENCY by
adding this choice as the second argument, for example,
forall(y in YS)
all_different(union(x in XS) {v(x,y)}, KALIS_GEN_ARC_CONSISTENCY)
we observe that the number of nodes is reduced to a single node—the problem is solved by simply
posting the constraints. Whereas the time spent in the search is down to zero, the constraint posting
now takes 4-5 times longer (still just a fraction of a second) due to the larger computational overhead
of the generalized arc consistency algorithm. Allover, the time for problem definition and solving is
reduced to less than a tenth of the previous time.
As a general rule, the generalized arc consistency algorithm achieves stronger pruning (i.e., it removes
more values from the domains of the variables). However, due to the increase in computation time its
use is not always justified. The reader is therefore encouraged to try both algorithm settings in his
models.
6
1
4
9
10
8
2
7
Figure 3.1: Telecommunications network
Table 3.3 lists the number of frequency demands for every cell.
The objective function is to minimize to the number of frequencies used. We formulate this by
minimizing the largest frequency number that occurs for the used variables:
3.4.2 Implementation
The edges forming the telecommunications network are modeled as a list LINK, where edge l is given
as (LINK(l,1),LINK(l,2)).
For the implementation of the constraints on the values of frequencies assigned to the same node we
have two equivalent choices with Kalis, namely using abs or distance constraints.
declarations
NODES = 1..10 ! Range of nodes
LINKS = 1..18 ! Range of links between nodes
DEM: array(NODES) of integer ! Demand of nodes
LINK: array(LINKS,1..2) of integer ! Neighboring nodes
INDEX: array(NODES) of integer ! Start index in 'use'
NUMDEM: integer ! Upper bound on no. of freq.
end-declarations
DEM :: (1..10)[4, 5, 2, 3, 2, 4, 3, 4, 3, 2]
LINK:: (1..18,1..2)[1, 3, 1, 4, 1, 6,
2, 4, 2, 7,
3, 4, 3, 6, 3, 8, 3, 9,
4, 7, 4, 9, 4,10,
5, 7, 5, 8, 5, 9,
6, 9, 7, 8, 8,10]
NUMDEM:= sum(n in NODES) DEM(n)
declarations
DEMANDS = 1..NUMDEM ! Range of frequency demands
use: array(DEMANDS) of cpvar ! Frequency used for a demand
numfreq: cpvar ! Number of frequencies used
Strategy: array(range) of cpbranching
end-declarations
! Search strategy
Strategy(1):=assign_var(KALIS_SMALLEST_DOMAIN, KALIS_MIN_TO_MAX, use)
Strategy(2):=assign_var(KALIS_MAX_DEGREE, KALIS_MIN_TO_MAX, use)
cp_set_branching(Strategy(1))
setparam("KALIS_MAX_COMPUTATION_TIME", 1)
cp_set_solution_callback("print_solution")
if (cp_minimize(numfreq)) then
cp_show_stats
elif sol>0 then
writeln("Optimality proven")
else
writeln("Problem has no solution")
end-if
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! ⁎⁎⁎⁎ Solution printout ⁎⁎⁎⁎
public procedure print_solution
writeln("Number of frequencies: ", getsol(numfreq))
writeln("Frequency assignment: ")
forall(n in NODES) do
write("Node ", n, ": ")
end-model
With just the default search strategy this model finds a solution of value 11 but it runs for a long time
without being able to prove optimality. When experimenting with different search strategies we have
found that the strategy obtained by changing the variable selection criterion to KALIS_MAX_DEGREE is
able to prove optimality easily once a good solution is known. This problem is therefore solved in two
steps: First, we use the default strategy for finding a good solution. This search is stopped after one
second by setting a time limit. The search is then restarted (previously, we needed to reset the search
tree in the solver with cp_reset_search) with a second strategy and the bound on the objective
value from the previous run.
To ease the experiments with different search strategies we have defined an array Strategy of type
cpbranching that stores the different search strategy definitions.
Another new feature demonstrated by this implementation is the use of a callback, more precisely the
solution callback of Xpress Kalis. The solution callback is defined with a user subroutine that will be
called by the solver whenever the search has found a solution. Its typical uses are logging or storing of
intermediate solutions or performing some statistics. Our procedure print_solution simply prints
out the solution that has been found.
Improving the problem formulation: we may observe that in our problem formulation all demand
variables within a node and the constraints on these variables are entirely symmetric. In the absence of
other constraints, we may reduce these symmetries by imposing an order on the use variables,
used + 1 ≤ used+1
for demands d and d + 1 belonging to the same cell. Doing so, the problem is solved to optimality within
less than 40 nodes using just the default strategy. We may take this a step further by writing
used + 2 ≤ used+1
The addition of these constraints shortens the search by yet a few more nodes. They can even be used
simply in replacement of the abs or distance constraints.
3.4.3 Results
An optimal solution to this problem uses 11 different frequencies. The model shown in the program
listing prints out the following assignment of frequencies to nodes:
Node 1: 1 3 5 7
Node 2: 1 3 5 7 10
Node 3: 2 8
Node 4: 4 6 9
Node 5: 4 6
Node 6: 4 6 9 11
Node 7: 2 8 11
Node 8: 1 3 5 7
Node 9: 1 3 5
Node 10: 2 8
The aim of this problem is to provide a model that may be used with different objective functions for
scheduling operations on a single (bottleneck) machine. We shall see here how to minimize the total
processing time, the average processing time, and the total tardiness.
A set of tasks (or jobs) is to be processed on a single machine. The execution of tasks is
non-preemptive (that is, an operation may not be interrupted before its completion). For every task i its
release date, duration, and due date are given in Table 3.4.
What is the optimal value for each of the objectives: minimizing the total duration of the schedule
(makespan), the mean processing time or the total tardiness (that is, the amount of time by which the
completion of jobs exceeds their respective due dates)?
The processing time durk for the job in position k is given by DURrankk (where DURj denotes the duration
given in the table in the previous section). Similarly, the release time relk is given by RELrankk (where
RELj denotes the given release date):
If startk is the start time of the job at position k, this value must be at least as great as the release date
of the job assigned to this position. The completion time compk of this job is the sum of its start time
plus its duration:
Another constraint is needed to specify that two jobs cannot be processed simultaneously. The job in
position k + 1 must start after the job in position k has finished, hence the following constraints:
Objective 1: The first objective is to minimize the makespan (completion time of the schedule), or,
equivalently, to minimize the completion time of the last job (job with rank NJ). The complete model is
then given by the following (where MAXTIME is a sufficiently large value, such as the sum of all release
minimize compNJ
∀k ∈ JOBS : rankk ∈ JOBS
∀k ∈ JOBS : startk , compk ∈ {0, ..., MAXTIME}
∀k ∈ JOBS : durk ∈ {minj∈JOBS DURj , ..., maxj∈JOBS DURj }
∀k ∈ JOBS : relk ∈ {minj∈JOBS RELj , ..., maxj∈JOBS RELj }
all-different(rank1 , ..., rankNJ )
∀k ∈ JOBS : durk = DURrankk
∀k ∈ JOBS : relk = RELrankk
∀k ∈ JOBS : startk ≥ relk
∀k ∈ JOBS : compk = startk + durk
∀k ∈ {1, ..., NJ – 1} : startk+1 ≥ startk + durk
Objective 2: For minimizing the average processing time, we introduce an additional variable totComp
representing the sum of the completion times of all jobs. We add the following constraint to the
problem to calculate totComp:
X
totComp = compk
k∈JOBS
The new objective consists of minimizing the average processing time, or equivalently, minimizing the
sum of the job completion times:
minimize totComp
Objective 3: If we now aim to minimize the total tardiness, we again introduce new variables—this time
to measure the amount of time that jobs finish after their due date. We write latek for the variable that
corresponds to the tardiness of the job with rank k. Its value is the difference between the completion
time of a job j and its due date DUEj . If the job finishes before its due date, the value must be zero. We
thus obtain the following constraints:
For the formulation of the new objective function we introduce the variable totLate representing the
total tardiness of all jobs. The objective now is to minimize the value of this variable:
minimize totLate
X
totLate = latek
k∈JOBS
declarations
NJ = 7 ! Number of jobs
JOBS=1..NJ
forall(j in JOBS) do
1 <= rank(j); rank(j) <= NJ
0 <= start(j); start(j) <= MAXTIME
MINDUR <= dur(j); dur(j) <= MAXDUR
0 <= comp(j); comp(j) <= MAXTIME
MINREL <= rel(j); rel(j) <= MAXREL
end-do
! Sequence of jobs
forall(k in 1..NJ-1) start(k+1) >= start(k) + dur(k)
! Start times
forall(k in JOBS) start(k) >= rel(k)
! Completion times
forall(k in JOBS) comp(k) = start(k) + dur(k)
if cp_minimize(totComp) then
print_sol
end-if
forall(k in JOBS) do
MINDUE <= due(k); due(k) <= MAXDUE
0 <= late(k); late(k) <= MAXTIME
end-do
if cp_minimize(totLate) then
writeln("Tardiness: ", getsol(totLate))
print_sol
print_sol3
end-if
!-----------------------------------------------------------------
! Solution printing
procedure print_sol
writeln("Completion time: ", getsol(comp(NJ)) ,
" average: ", getsol(sum(k in JOBS) comp(k)))
write("\t")
forall(k in JOBS) write(strfmt(getsol(rank(k)),4))
write("\nRel\t")
forall(k in JOBS) write(strfmt(getsol(rel(k)),4))
write("\nDur\t")
forall(k in JOBS) write(strfmt(getsol(dur(k)),4))
write("\nStart\t")
forall(k in JOBS) write(strfmt(getsol(start(k)),4))
write("\nEnd\t")
forall(k in JOBS) write(strfmt(getsol(comp(k)),4))
writeln
end-procedure
procedure print_sol3
write("Due\t")
forall(k in JOBS) write(strfmt(getsol(due(k)),4))
write("\nLate\t")
forall(k in JOBS) write(strfmt(getsol(late(k)),4))
writeln
end-procedure
end-model
NB: The reader may have been wondering why we did not use the more obvious pair start – end for
naming the variables in this example: end is a keyword of the Mosel language (see the list of reserved
words in the Mosel language reference manual), which means that neither end nor END may be
redefined by a Mosel program. It is possible though, to use versions combining lower and upper case
letters, like End, but to prevent any possible confusion we do not recommend their use.
3.5.3 Results
The minimum makespan of the schedule is 31, the minimum sum of completion times is 103 (which
The completion time compj of a job j is the sum of its start time plus its duration DURj .
∀j ∈ JOBS : compj = startj + DURj
Objective 1: The first objective is to minimize the makespan (completion time of the schedule) or,
equivalently, to minimize the completion time finish of the last job. The complete model is then given by
the following (where MAXTIME is a sufficiently large value, such as the sum of all release dates and all
durations):
minimize finish
finish = maximumj∈JOBS (compj )
∀j ∈ JOBS : compj ∈ {0, ..., MAXTIME}
∀j ∈ JOBS : startj ∈ {RELj , ..., MAXTIME}
disjunctive([start1 , ..., startNJ ], [DUR1 , ..., DURNJ ])
∀j ∈ JOBS : compj = startj + DURj
Objective 2: The formulation of the second objective (minimizing the average processing time or,
equivalently, minimizing the sum of the job completion times) remains unchanged from the first
model—we introduce an additional variable totComp representing the sum of the completion times of
all jobs.
minimize totComp
X
totComp = compk
k∈JOBS
Objective 3: To formulate the objective of minimizing the total tardiness, we introduce new variables
latej to measure the amount of time that a job finishes after its due date. The value of these variables
corresponds to the difference between the completion time of a job j and its due date DUEj . If the job
finishes before its due date, the value must be zero. The objective now is to minimize the sum of these
tardiness variables:
minimize totLate
X
totLate = latej
j∈JOBS
∀j ∈ JOBS : latej ∈ {0, ..., MAXTIME}
∀j ∈ JOBS : latej ≥ compj – DUEj
declarations
NJ = 7 ! Number of jobs
JOBS=1..NJ
forall(j in JOBS) do
0 <= start(j); start(j) <= MAXTIME
0 <= comp(j); comp(j) <= MAXTIME
end-do
! Start times
forall(j in JOBS) start(j) >= REL(j)
! Completion times
forall(j in JOBS) comp(j) = start(j) + DUR(j)
Strategy(1):= settle_disjunction(Disj)
Strategy(2):= split_domain(KALIS_LARGEST_MAX, KALIS_MIN_TO_MAX)
cp_set_branching(Strategy)
if cp_minimize(finish) then
print_sol
end-if
if cp_minimize(totComp) then
print_sol
end-if
forall(k in JOBS) do
0 <= late(k); late(k) <= MAXTIME
end-do
!-----------------------------------------------------------------
! Solution printing
procedure print_sol
writeln("Completion time: ", getsol(finish) ,
" average: ", getsol(sum(j in JOBS) comp(j)))
write("Rel\t")
forall(j in JOBS) write(strfmt(REL(j),4))
write("\nDur\t")
forall(j in JOBS) write(strfmt(DUR(j),4))
write("\nStart\t")
forall(j in JOBS) write(strfmt(getsol(start(j)),4))
write("\nEnd\t")
forall(j in JOBS) write(strfmt(getsol(comp(j)),4))
writeln
end-procedure
procedure print_sol3
write("Due\t")
forall(j in JOBS) write(strfmt(DUE(j),4))
write("\nLate\t")
forall(j in JOBS) write(strfmt(getsol(late(j)),4))
writeln
end-procedure
end-model
opposed to the branching strategies we have seen so far this scheme defines a branching strategy over
constraints, and not over variables. With this scheme a node is created by choosing a constraint from
the given set and the branches from the node are obtained by adding one of the mutually exclusive
constraints forming this disjunctive constraint to the constraint system.
NB: the disjunctive constraint of Xpress Kalis establishes pair-wise inequalities between the
processing times of tasks. However, the definition of disjunctive constraints is not restricted to this
case: a disjunction may have more than two components, and involve constraints of any type, including
other logic relations obtained by combining constraints with and or or.
Every lot may be processed by any of the three, fully equivalent production lines of the sugarhouse. The
processing of a lot takes two hours. It must be finished at the latest at the end of the life span of the
wagonload. The manager of the sugarhouse wishes to determine a production schedule for the
currently available lots that minimizes the total loss of sugar.
The loss of sugar per wagonload w and time slot s is COSTws = s · DUR · LOSSw . Let variables lossw
The objective function (total loss of sugar) is then given as the sum of all losses:
X
minimize lossw
w∈WAGONS
3.6.2 Implementation
The following model is the Mosel implementation of this problem. It uses the function ceil to
calculate the maximum number of time slots.
The constraints on the processing variables are expressed by occurrence relations and the losses
are obtained via element constraints. The branching strategy uses the variable selection criterion
KALIS_SMALLEST_MAX, that is, choosing the variable with the smallest upper bound.
declarations
NW = 11 ! Number of wagon loads of sugar
NL = 3 ! Number of production lines
WAGONS = 1..NW
NS = ceil(NW/NL)
SLOTS = 1..NS ! Time slots for production
! Solution printing
writeln("Total loss: ", getsol(totalLoss))
forall(s in SLOTS) do
write("Slot ", s, ": ")
forall(w in WAGONS)
if(getsol(process(w))=s) then
write("wagon ", strfmt(w,2), strfmt(" (" + s⁎DUR⁎LOSS(w) + ") ", 8))
end-if
writeln
end-do
end-model
An alternative formulation of the constraints on the processing variables is to replace them by a single
distribute relation, indicating for every time slot the minimum and maximum number (MINUSEs = 0
and MAXUSEs = NL) of production lines that may be used.
Yet another formulation of this problem is possible with Xpress Kalis, namely interpreting it as a
cumulative scheduling problem (see Section 5.4), where the wagon loads are represented by tasks of
unit duration, scheduled on a discrete resource with a capacity corresponding to the number of
production lines.
3.6.3 Results
We obtain a total loss of 1620 kg of sugar. The corresponding schedule of lots is shown in the following
Table 3.6 (there are several equivalent solutions).
We introduce decision variables placep for the working location assigned to person p. The four
individual constraints on working locations can then be stated as follows:
placeLeslie = 3
placeMichael = 2
all-different(placeDavid , placeMichael , placeJason )
placeOliver = 1 ⇒ placeMarylin = 1
3.7.2 Implementation
For the implementation of the staffing requirements we may choose among two different constraints
of Xpress Kalis, namely occurrence constraints (one per working location) or a distribute
constraint (also known as global cardinality constraint) over all working locations. The following model
shows both options. As opposed to the previous example (Section 3.6.2 we now have equality
constraints. We therefore use the three-argument version of distribute (instead of the version with
four arguments where the last two are the lower and upper bounds respectively).
For an attractive display, the model also introduces a few auxiliary data structures with the names of
the working locations and the corresponding index values in the set LOC.
declarations
PERS = {"David","Andrew","Leslie","Jason","Oliver","Michael",
"Jane","Marilyn"} ! Set of personnel
LOC = 1..4 ! Set of locations
LOCNAMES = {"Ticketoffice", "Theater1", "Theater2",
"Cloakroom"} ! Names of locations
LOCNUM: array(LOCNAMES) of integer ! Numbers assoc. with loc.s
REQ: array(LOC) of integer ! No. of pers. req. per loc.
! Initialize data
LOCNUM("Ticketoffice"):= 1; LOCNUM("Theater1"):= 2
LOCNUM("Theater2"):= 3; LOCNUM("Cloakroom"):= 4
REQ:: (1..4)[3, 2, 2, 1]
implies(place("Oliver")=LOCNUM("Ticketoffice"),
place("Marilyn")=LOCNUM("Ticketoffice"))
! Solution output
nbSolutions:= 1
print_solution
end-model
Since we merely wish to find a feasible assignment of working locations, this model first tests whether
a feasible solution exists. If this is the case, it also enumerates all other feasible solutions. Each time a
solution is found it is printed out by call to the procedure print_solution.
3.7.3 Results
This problem has 38 feasible solutions. A graphical representation of a feasible assignment is shown
in Figure 3.2.
The following Mosel code was used for generating the graphic (see the documentation of module
mmsvg in the Mosel language reference manual for further explanation).
forall(d in LOCNAMES)
svgaddtext("LocGraph", 4, LOCNUM(d), d)
FACT:=LOCNAMES.size/PERS.size
idx:= 1
forall(p in PERS, idx as counter) do
svgaddline("AsgnGraph", 2, idx⁎FACT, 4, getsol(place(p)))
svgaddtext("PersGraph", 2, idx⁎FACT, p)
end-do
svgsetgraphscale(75)
svgsetgraphviewbox(0,0,5,LOCNAMES.size+1)
svgrefresh
The objective function sums up the processing and cleaning times of all batches. The last (all-different)
constraint guarantees that every batch occurs exactly once in the production sequence.
Unfortunately, this model does not guarantee that the solution forms a single cycle. Solving it indeed
results in a total duration of 239 with an invalid solution that contains two sub-cycles 1 → 3 → 2 → 1
and 4 → 5 → 4. A first possibility is to add a disjunction excluding this solution to our model and
re-solve it iteratively until we reach a solution without sub-cycles.
However, this procedure is likely to become impractical with larger data sets since it may potentially
introduce an extremely large number of disjunctions. We therefore choose a different, a-priori
formulation of the sub-cycle elimination constraints with a variable yj per batch and NJ · (NJ – 1)
implication constraints.
The variables yj correspond to the position of job j in the production cycle. With these constraints, job 1
always takes the first position.
declarations
NJ = 5 ! Number of paint batches (=jobs)
JOBS=1..NJ
forall(j in JOBS) do
1 <= succ(j); succ(j) <= NJ; succ(j) <> j
1 <= y(j); y(j) <= NJ
end-do
! Exclude subtours
forall(i in JOBS, j in 2..NJ | i<>j)
implies(succ(i) = j, y(j) = y(i) + 1)
! Solution printing
writeln("Minimum cycle time: ", getsol(cycleTime))
writeln("Sequence of batches:\nBatch Duration Cleaning")
first:=1
repeat
writeln(" ", first, strfmt(DUR(first),8), strfmt(getsol(clean(first)),9))
first:=getsol(succ(first))
until (first=1)
end-model
As before, let JOBS = {1, ..., NJ} be the set of batches to produce, DURj the processing time for batch j,
and CLEANij the cleaning time between the consecutive batches i and j. We introduce decision variables
rankk taking their values in JOBS, for the number of the job in position k. Variables cleank (k ∈ JOBS)
now denote the duration of the kth cleaning time. This duration is obtained by indexing CLEANij with the
values of two consecutive rankk variables. We thus have the following problem formulation.
X X
minimize DURj + cleank
j∈JOBS k∈JOBS
∀k ∈ JOBS : rankk ∈ JOBS
∀k ∈ {1, ..., NJ – 1} : cleank = CLEANrankk ,rankk+1
cleanNJ = CLEANrankNJ ,rank1
[
all-different rankk
k∈JOBS
As in model 1, the objective function sums up the processing and cleaning times of all batches.
Although not strictly necessary from the mathematical point of view, we use different sum indices for
durations and cleaning times to show the difference between summing over jobs or job positions. We
now have an all-different constraint over the rank variables to guarantee that every batch occurs exactly
once in the production sequence.
declarations
NJ = 5 ! Number of paint batches (=jobs)
JOBS=1..NJ
! Solution printing
writeln("Minimum cycle time: ", getsol(cycleTime))
writeln("Sequence of batches:\nBatch Duration Cleaning")
forall(k in JOBS)
writeln(" ", getsol(rank(k)), strfmt(DUR(getsol(rank(k))),8),
strfmt(getsol(clean(k)),9))
end-model
3.8.5 Results
The minimum cycle time for this problem is 243 minutes which is achieved with the following sequence
of batches: 1 → 4 → 3 → 5 → 2 → 1. This time includes 202 minutes of (incompressible) processing
time and 41 minutes of cleaning.
When comparing the problem statistics produced by Xpress Kalis for this problem we see that the
second model is a weaker formulation resulting in a considerably longer enumeration (using the default
strategies).
necessary: a variable buildc that is one if and only if a tax office is established in city c, and a variable
dependc that takes the number of the office on which city c depends. For the formulation of the
constraints, we further introduce two sets of auxiliary variables: depdistc , the distance from city c to the
office indicated by dependc , and numdepc , the number of cities depending on an office location.
The following relations are required to link the buildc with the dependc variables:
(1) numdepc counts the number of occurrences of office location c among the variables dependc .
(2) numdepc ≥ 1 if and only if the office in c is built (as a consequence, if the office in c is not built, then
we must have numdepc = 0).
Since the number of offices built is limited by the given bound NUMLOC
X
buildc ≤ NUMLOC
c∈CITIES
it would actually be sufficient to formulate the second relation between the buildc and dependc
variables as the implication ‘If numdepc ≥ 1 then the office in c must be built, and inversely, if the office
in c is not built, then we must have numdepc = 0’.
The objective function to be minimized is the total distance weighted by the number of inhabitants of
the cities. We need to divide the resulting value by the total population of the region to obtain the
average distance per inhabitant to the closest income tax office. The distance depdistc from city c to
the closest tax office location is obtained by a discrete function, namely the row c of the distance
matrix DISTcd indexed by the value of dependc :
depdistc = DISTc,dependc
3.9.2 Implementation
To solve this problem, we define a branching strategy with two parts, one for the buildc variables and a
second strategy for the depdistc variables. The latter are enumerated using the split_domain
branching scheme that divides the domain of the branching variable into several disjoint subsets
(instead of assigning a value to the variable). We now pass an array of type cpbranching as the
argument to procedure cp_set_branching. The different strategies will be applied in their order in
this array. Since our enumeration strategy does not explicitly include all decision variables of the
problem, Xpress Kalis will enumerate these using the default strategy if any unassigned variables
remain after the application of our search strategy.
uses "kalis"
setparam("KALIS_DEFAULT_LB", 0)
declarations
NC = 12
CITIES = 1..NC ! Set of cities
forall(c in CITIES) do
build(c) <= 1
1 <= depend(c); depend(c) <= NC
min(d in CITIES) DIST(c,d) <= depdist(c)
depdist(c) <= max(d in CITIES) DIST(c,d)
numdep(c) <= NC
end-do
! Branching strategy
Strategy(1):= assign_and_forbid(KALIS_MAX_DEGREE, KALIS_MAX_TO_MIN, build)
Strategy(2):= split_domain(KALIS_SMALLEST_DOMAIN, KALIS_MIN_TO_MAX,
depdist, true, 5)
cp_set_branching(Strategy)
! Solution printing
writeln("Total weighted distance: ", getsol(totDist),
" (average per inhabitant: ",
getsol(totDist)/sum(c in CITIES) POP(c), ")")
forall(c in CITIES) if(getsol(build(c))>0) then
write("Office in ", c, ": ")
forall(d in CITIES) write(if(getsol(depend(d))=c, " "+d, ""))
writeln
end-if
!-----------------------------------------------------------------
end-model
This implementation contains another example of the use of a subroutine in Mosel: the calculation of
the distance data is carried out in the procedure calculate_dist. We thus use a subroutine to
structure our model, removing secondary tasks from the main model formulation.
3.9.3 Results
The optimal solution to this problem has a total weighted distance of 2438. Since the region has a total
of 185,000 inhabitants, the average distance per inhabitant is 2438/185 ≈ 13.178 km. The three offices
are established at nodes 1, 6, and 11. The first serves cities 1, 2, 5, 7, the office in node 6 cities 3, 4, 6, 9,
and the office in node 11 cities 8, 10, 11, 12.
CLEANij the cleaning time between the consecutive batches i and j. As before we define decision
variables succj taking their values in JOBS, to indicate the successor of every job. The complete model
formulation is the following,
X
minimize DURj + cleantime
j∈JOBS
∀j ∈ JOBS : succj ∈ JOBS\{j}
cleantime = cycle((succj )j∈JOBS , (CLEANij )i,j∈JOBS )
where ’cycle’ stands for the relation ’sequence into a single cycle without subcycles or repetitions’. The
variable cleantime equals the total duration of the cycle.
3.10.2 Implementation
The Mosel model using the cycle constraint looks as follows.
setparam("KALIS_DEFAULT_LB", 0)
declarations
NJ = 5 ! Number of paint batches (=jobs)
JOBS=0..NJ-1
forall(j in JOBS) do
0 <= succ(j); succ(j) <= NJ-1; succ(j) <> j
end-do
! Solution printing
writeln("Minimum cycle time: ", getsol(cycleTime))
writeln("Sequence of batches:\nBatch Duration Cleaning")
first:=1
repeat
writeln(" ", first, strfmt(DUR(first),8),
strfmt(CLEAN(first,getsol(succ(first))),9) )
first:=getsol(succ(first))
until (first=1)
end-model
Notice that we have renumbered the tasks, starting the index range with 0, to conform with the input
format expected by the cycle constraint.
3.10.3 Results
The optimal solution to this problem has a minimum cycle time of 243 minutes, resulting from 202
minutes of (incompressible) processing time and 41 minutes of cleaning.
The problem statistics produced by Xpress Kalis for a model run reveal that the ’cycle’ version of this
model is the most efficient way of representing and solving the problem: it takes fewer nodes and a
shorter execution time than the two versions of Section 3.8.
We are now left with the necessity to establish a constraint relation that checks whether consecutive
positions define a valid knight move. To this aim we define a new binary constraint ’valid_knight_move’
that checks whether a given pair of values defines a permissible move according to the chess rules for
knight moves. Vertically and horizontally, the two values must be no more than two cells apart and the
sum of the vertical and horizontal difference must be equal to three. The complete model then looks as
follows.
3.11.2 Implementation
Testing whether moving from position a to position b is a valid move for a knight figure can be done
with the following function valid_knight_move where ’div’ means integer division without rest and ’mod’
is the rest of the integer division:
function valid_knight_move(a,b)
xa := a div E
ya := a mod E
xb := b div E
yb := b mod E
deltax := |xa – xb|
deltay := |ya – yb|
return ((deltax ≤ 2) and (deltay ≤ 2) and (deltax + deltay = 3))
end-function
The following Mosel model defines the user constraint function valid_knight_move as the
implementation of the new binary constraints on pairs of movep variables (the constraints are
established with generic_binary_constraint).
parameters
S = 8 ! Number of rows/columns
NBSOL = 1 ! Number of solutions sought
end-parameters
declarations
PATH = 1..N ! Cells on the chessboard
pos: array(PATH) of cpvar ! Cell at position p in the tour
end-declarations
! The path of the knight obeys the chess rules for valid knight moves
forall(i in 1..N-1)
generic_binary_constraint(pos(i), pos(i+1), "valid_knight_move")
generic_binary_constraint(pos(N), pos(1), "valid_knight_move")
xa := a div S
ya := a mod S
xb := b div S
yb := b mod S
delta_x := abs(xa-xb)
delta_y := abs(ya-yb)
returned := (delta_x<=2) and (delta_y<=2) and (delta_x+delta_y=3)
end-function
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Solution printing
procedure print_solution(sol: integer)
writeln("Solution ", sol, ":")
forall(i in PATH)
write(getval(pos(i)), if(i mod 10 = 0, "\n ", ""), " -> ")
writeln("0")
end-procedure
end-model
The branching scheme used in this model is the probe_assign_var heuristic, in combination with
the variable selection KALIS_SMALLEST_MIN (choose variable with smallest lower bound) and the
value selection criterion KALIS_MAX_TO_MIN (from largest to smallest domain value). Another search
strategy that was found to work well (though slower than the strategy in the code listing) is
Our model defines two parameters. It is thus possible to change either the size of the chessboard (S)
or the number of solutions sought (NBSOL) when executing the model without having to modify the
model source.
3.11.3 Results
The first solution printed out by our model is the following tour.
parameters
S = 8 ! Number of rows/columns
NBSOL = 1 ! Number of solutions sought
end-parameters
declarations
PATH = 0..N-1 ! Cells on the chessboard
succ: array(PATH) of cpvar ! Successor of cell p
end-declarations
xp := p div S
yp := p mod S
forall(q in PATH) do
xq := q div S
yq := q mod S
delta_x := abs(xp-xq)
delta_y := abs(yp-yq)
if (delta_x<=2) and (delta_y<=2) and (delta_x+delta_y=3) then
SuccSet +={q}
end-if
end-do
setdomain(succ(p), SuccSet)
end-procedure
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! ⁎⁎⁎⁎ Solution printing ⁎⁎⁎⁎
procedure print_solution(sol: integer)
writeln("Solution ", sol, ":")
thispos:=0
nextpos:=getval(succ(0))
ct:=1
while (nextpos<>0) do
write(thispos, if(ct mod 10 = 0, "\n ", ""), " -> ")
val:=getval(succ(thispos))
thispos:=nextpos
nextpos:=getval(succ(thispos))
ct+=1
end-do
writeln("0")
end-procedure
end-model
The calculation of the domains for the succp variables reduces these to 2-8 elements (as compared to
the N = 64 values for every posp variables), which clearly reduces the search space for this problem.
This second model finds the first solution to the problem after 131 nodes taking just a fraction of a
second to execute on a standard PC whereas the first model requires several thousand nodes and
considerably longer running times. It is possible to reduce the number of branch-and-bound nodes even
further by using yet another version of the ’cycle’ constraint that works with successor and predecessor
variables. This version of ’cycle’ performs a larger amount of propagation, at the expense of (slightly)
slower execution times for our problem. The procedure calculate_successors now sets the
domain of predp to the same values as succp for all cells p.
declarations
PATH = 0..N-1 ! Cells on the chessboard
succ: array(PATH) of cpvar ! Successor of cell p
pred: array(PATH) of cpvar ! Predecessor of cell p
end-declarations
Figure 3.4 shows the graphical display of a knight’s tour created with the user graph drawing
functionality.
parameters
S = 8 ! Number of rows/columns
NBSOL = 1 ! Number of solutions sought
end-parameters
declarations
PATH = 0..N-1 ! Cells on the chessboard
succ: array(PATH) of cpvar ! Successor of cell p
end-declarations
solct+=1
cp_show_stats
print_solution(solct)
end-do
xp := p div S
yp := p mod S
forall(q in PATH) do
xq := q div S
yq := q mod S
delta_x := abs(xp-xq)
delta_y := abs(yp-yq)
if (delta_x<=2) and (delta_y<=2) and (delta_x+delta_y=3) then
SuccSet +={q}
end-if
end-do
setdomain(succ(p), SuccSet)
end-procedure
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! ⁎⁎⁎⁎ Solution printing ⁎⁎⁎⁎
procedure print_solution(sol: integer)
writeln("Solution ", sol, ":")
thispos:=0
nextpos:=getval(succ(0))
ct:=1
while (nextpos<>0) do
write(thispos, if(ct mod 10 = 0, "\n ", ""), " -> ")
val:=getval(succ(thispos))
thispos:=nextpos
nextpos:=getval(succ(thispos))
ct+=1
end-do
writeln("0")
end-procedure
end-model
The objective function is to minimize the total cost incurred through actors’ fees:
X
minimize DAILYFEEa · workad
a∈ACTORS,d∈DAYS
Symmetry breaking constraints: it is easy to see that days are entirely interchangeable (e.g. all scenes
from day 1 could be exchanged with those assigned to day 2 without any impact on the total cost).
Indeed, any permuation of days with the same scene allocations will result in the same total cost. So it
might be worthwhile to investigate how we can reduce the number of permutations in order to shorten
solving times.
Consider a scene s1 : it needs to be assigned some specific day, say day 1 (all days are interchangeable
at this point). Now consider a second scene s2 : it can either be assigned the same day as the first or
some other (additional) day—all other days being interchangeable at this point we can assume this will
be day 2. Generalizing this observation, for any scene s we can limit its value set to the days already
used by scenes 1, ..., s – 1 plus one additional day, that is:
shoot1 = 1
∀s ∈ SCENES, s > 1 : shoots ≤ maximum(shoot1 , ..., shoots–1 ) + 1
3.12.2 Implementation
For the implementation of the lower and upper bounds on the number of scenes to be shot per day we
use a global cardinality (’distribute’) constraint that distributes the scenes to be shot over the available
days.
For the formulation of the symmetry breaking constraints some auxiliary objects are introduced,
namely the list shootListSb of the predecessors of scene s (that is, shoot1 , ..., shoots–1 ) in
enumeration order of the set of scenes, and an additional decision variable maxShootSbs defined as
the maximum value among the variables in this list.
! Initialize minimum and maximum numbers of scenes that have to be shot per day
MINSCENESHOT::([1,2,3,4,5])[2,2,2,2,2]
MAXSCENESHOT::([1,2,3,4,5])[5,5,5,5,5]
! The total cost has a lower bound of $0 and an upper bound of $200000000
setname(totalCost, "totalCost")
setdomain(totalCost, 0, 200000000)
! Symmetry breaking
shoot(1) = 1 ! All days are interchangeable at this stage
forall(s in SCENES | s > 1) do
shootListSb += shoot(s-1) ! For a scene s, we need to consider
maxShootSb(s) = maximum(shootListSb) ! just the previously used days + 1:
shoot(s) <= maxShootSb(s)+1 ! D(s)={1,...,max(shoot(1),...,shoot(s-1))+1}
end-do
! Objective function
totalCost = sum(a in ACTORS, d in DAYS) DAILYFEE(a)⁎work(a,d)
! Branching strategy
cp_set_branching(assign_and_forbid(KALIS_INPUT_ORDER, KALIS_MIN_TO_MAX, shoot))
end-model
3.12.3 Results
The model shown above generates the following output:
Day 1: scenes 1 8
Day 2: scenes 2 5 11 12 15
Day 3: scenes 3 7 10 13 17
Day 4: scenes 4 19
Day 5: scenes 6 9 14 16 18
Georges Clooney : Day 2 Day 3
Penelope Cruz : Day 3 Day 4 Day 5
Leonardo DiCaprio: Day 3 Day 5
Nathalie Portman : Day 1 Day 2 Day 3
Ryan Gosling : Day 2 Day 3
Monica Bellucci : Day 1 Day 2 Day 3 Day 5
Javier Bardem : Day 2 Day 5
Keira Knightley : Day 2 Day 3 Day 5
Vincent Cassel : Day 1 Day 2 Day 3 Day 4 Day 5
Marion Cotillard : Day 2 Day 5
Emma Watson : Day 2 Day 5
Without the symmetry breaking constraints, the solving time is more than one order of magnitude
longer (both for finding the optimal solution and proving its optimality) than when these constraints are
included in the model.
This chapter gives an overview on different issues related to the definition of search strategies with
Xpress Kalis, namely
The last section discusses what may be done in the case of an infeasible constraint system:
The branching scheme determines the shape of the search tree, this includes exhaustive schemes
like assign_var and split_domain (enumeration of decision variables),
settle_disjunction (enumeration over constraints), and task_serialize (enumeration of
tasks in scheduling problems), or potentially incomplete searches with probe_assign_var or
probe_settle_disjunction.
The variable selection strategy determines the choice of the branching variables. Predefined
selection criteria include
The user may also define his own variable selection strategy (see Section 4.4.3 below).
The value selection strategy determines the choice of the branching value once the variable to be
branched on is known. The following predefined selection criteria are available.
The predefined criteria can be replaced by the user’s own value selection strategy (see Section
4.4.3 below).
Depending on the choice of the branching scheme, it may be possible to specify additional parameters
to configure the enumeration. The reader is referred to the Xpress Kalis reference manual for further
detail. In the case of constraint branching, there is quite obviously no variable or branching value
selection. The special case of task-based branching strategies (branching scheme task_serialize)
is discussed in Section 5.7. Enumeration of continuous variables (type cpfloatvar) always uses the
branching scheme split_domain with the KALIS_SMALLEST_DOMAIN or KALIS_WIDEST_DOMAIN
variable selection (the former is used by the default strategy, the latter often works better in purely
continuous problems) and only a subset of the value selection strategies listed above.
Branching strategies may be defined for certain specified variables (or, where applicable, constraints or
tasks), or, if the corresponding argument of the branching scheme function is left out, are applied to all
decision variables in a model (see, for instance, model b4seq_ka.mos in Section 3.5).
If the user’s model does not specify any branching strategy, then Xpress Kalis will apply default
strategies to enumerate all variables in the model. Even if one does not wish to change the default
enumeration (for discrete variables: ‘smallest domain first’ and ‘smallest value first’), it may still be
desirable to define a branching strategy to fix a certain order for the enumeration of different groups of
decision variables. The previous chapter contains several examples of this: in the model
a4sugar_ka.mos (Section 3.6) we define an enumeration over some of the variables of a model,
giving them thus preference over the remainder. In model j5tax_ka.mos (Section 3.9) we have seen
an example of a search strategy composed of different enumerations for groups of decision variables.
If a model contains both, discrete and continuous variables (cpvar and cpfloatvar) the default
strategies enumerate first the discrete and then the continuous variables.
These parameters are accessed with the Mosel functions setparam and getparam (see, for example,
the output of problem statistics in model sudoku_ka.mos in Section 3.3, and the search time limit set
in the model freqasgn.mos of Section 3.4).
In optimization problems, after a solution has been found the search continues from this point unless
the setting of parameter KALIS_OPTIMIZE_WITH_RESTART is changed. The same is true if the
search is interrupted by means of one of the above-named criteria and then continued, for instance
with a different search strategy. To restart the search from the root node the procedure
cp_reset_search needs to be called (as an example, see model freqasgn.mos in Section 3.4)
4.3 Callbacks
During the search the user’s model may interact with the solver at certain predefined points by means
of callback functions. This functionality is particularly useful to retrieve solution information for
intermediate solutions during an optimization run as shown in the model freqasgn.mos (Section 3.4).
Other than this solution callback, the user may set functions that will be called at every branch or at
every node (see the Xpress Kalis reference manual for further detail).
Furthermore, let outputp denote the output produced by person p. The values of these variables are
obtained as discrete functions in the assignp variables:
Certain assignments may be infeasible. In such a case, the value of the corresponding machine needs
to be removed from the domain of the variable assignp .
If the machines work in series, the least productive worker on the machine she has been assigned to
determines the total productivity of the workshop. An assignment will still be described by N variables
assignp and an all-different constraint on these variables. We also have the outputp variables with the
constraints linking them to the values of the assignp variables from the previous model. To this we add
a variable pmin for the minimum productivity. The objective is to maximize pmin. This type of
optimization problem where one wants to maximize a minimum is called maximin, or bottleneck.
maximize pmin
pmin = minimump∈PERS (outputp )
4.4.2 Implementation
The following Mosel program first implements and solves the model for the case of parallel machines.
Afterwards, we define the variable pmin that is required for solving the problem for the case that the
machines work in series.
declarations
PERS = 1..6 ! Personnel
MACH = 1..6 ! Machines
OUTP: array(PERS,MACH) of integer ! Productivity
end-declarations
declarations
assign: array(PERS) of cpvar ! Machine assigned to a person
output: array(PERS) of cpvar ! Productivity of every person
totalProd: cpvar ! Total productivity
O: array(MACH) of integer ! Auxiliary array for constraint def.
Strategy: cpbranching ! Branching strategy
end-declarations
! Branching strategy
Strategy:= assign_var(KALIS_LARGEST_MAX, KALIS_MAX_TO_MIN, output)
cp_set_branching(Strategy)
declarations
pmin: cpvar ! Minimum productivity
end-declarations
! Branching strategy
Strategy:= assign_var(KALIS_SMALLEST_MIN, KALIS_MAX_TO_MIN, output)
cp_set_branching(Strategy)
When the solution to the parallel assignment problem is found, we print out the solution and re-start the
search with a new branching strategy and a new objective function. Since the first search has finished
completely (no interruption by a time limit, etc.) there is no need to reset the solver between the two
runs.
The branching strategy chosen for the parallel assignment problem is inspired by the intuitive
procedure described in the introduction to Section 4.4: instead of enumerating the possible
assignments of workers to machines (= enumeration of the assignp variables) we define an
enumeration over the outputp variables, choosing the variable with the largest remaining value
(KALIS_LARGEST_MAX) and branch on its values in decreasing order (KALIS_MAX_TO_MIN). For the
second problem we need to proceed differently: to avoid being left with a small productivity value for
some worker p we pick first the outputp variable with the smallest lower bound
(KALIS_SMALLEST_MIN); again we enumerate the values starting with the largest one.
The following procedure parallel_heur may be added to the above program. It heuristically
calculates a (non-optimal) solution to the parallel assignment problem using the intuitive procedure
described in the introduction to Section 4.4.
procedure parallel_heur
declarations
ALLP, ALLM: set of integer ! Copies of sets PERS and MACH
HProd: integer ! Total productivity value
pmax,omax,mmax: integer
end-declarations
! Find the highest productivity among the remaining workers and machines
forall(p in ALLP, m in ALLM)
if OUTP(p,m) > omax then
omax:=OUTP(p,m)
pmax:=p; mmax:=m
end-if
The model is completed with a procedure for printing out the solution in a properly formatted way.
end-procedure
end-model
if Vset={} then
returned:= 0
else
! Get the variable(s) with largest upper bound
dmax:= max(i in Vset) getub(getvar(Vars,i))
forall(i in Vset)
if getub(getvar(Vars,i)) = dmax then Iset+= {i}; end-if
dmin:= dmax
! Choose variable with smallest next-best value among those indexed by 'Iset'
forall(i in Iset) do
prev:= getprev(getvar(Vars,i),dmax)
if prev < dmin then
returned:= i
dmin:= prev
end-if
end-do
end-if
end-function
The variable selection strategy varchoicemin for the second optimization run (serial machines) is
implemented in a similar way. We first establish the set of variables with the smallest lower bound value
(using getlb), Iset; among these we choose the variable with the smallest upper bound (getub).
forall(i in 1..getsize(Vars))
if not is_fixed(getvar(Vars,i)) then Vset+= {i}; end-if
if Vset={} then
returned:= 0
else
! Get the variable(s) with smallest lower bound
dmin:= min(i in Vset) getlb(getvar(Vars,i))
forall(i in Vset)
if getlb(getvar(Vars,i)) = dmin then Iset+= {i}; end-if
! Choose variable with smallest upper bound among those indexed by 'Iset'
dmax:= getparam("kalis_default_ub")
forall(i in Iset)
if getub(getvar(Vars,i)) < dmax then
returned:= i
dmax:= getub(getvar(Vars,i))
end-if
end-if
end-function
Value selection: the value selection function receives as its argument the chosen branching variable
and returns a branching value for this variable. The value selection criterion we have chosen
(corresponding to KALIS_MAX_TO_MIN) is to enumerate all values for the branching variable, starting
with the largest remaining one (that is, the variable’s upper bound):
Notice that with an assign_var or assign_and_forbid strategy, the user’s value selection strategy
should make sure to return a value that is currently in the branching variable’s domain (a value chosen
between the lower and upper bound is not guaranteed to lie in the domain) by using function
contains.
Setting user search strategies: to indicate that we wish to use our own variable or value selection
strategy we simply need to replace the predefined constants by the name of our Mosel functions:
Strategy:= assign_var("varchoice", "valchoice", output)
re-defines it for the serial machine case. Since our function valchoice does just the same as the
KALIS_MAX_TO_MIN criterion, we could also combine it with our variable choice function:
4.4.4 Results
The following table summarizes the results found with the different solution methods for the two
problems of parallel and serial machines. There is a notable difference between the heuristic method
and the exact solution to the problem with parallel machines.
By adding output of solver statistics to our model (cp_show_stats) we find that our user search
strategies result in the same search trees and program execution durations as with the predefined
strategies for the parallel assignment and arrive at a slightly different solution for the serial case.
declarations
N = 5
R = 1..N
C: array(R) of integer
x: array(R) of cpvar
k,depth: cpreversible
ka: cpreversiblearray
end-declarations
C::(1..5)[-7,15,-3,19,-45]
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
cp_set_node_callback("save_node_state")
cp_set_branch_callback("branch_down", "branch_up")
cp_set_branching(assign_var(KALIS_SMALLEST_DOMAIN, KALIS_MAX_TO_MIN))
setparam("KALIS_MAX_SOLUTIONS", 3)
while (cp_find_next_sol) do
The output produced by the example is shown here, notice how the tree depth value is used to indent
the lines, and observe how the values of reversibles change between nodes, in particular the value of
the reversible number k.
down 0.000000 [0 0 0 0 0 ]
down -7.000000 [-7 0 0 0 0 ]
down 8.000000 [-7 15 0 0 0 ]
down 5.000000 [-7 15 -3 0 0 ]
down 24.000000 [-7 15 -3 19 0 ]
Solution: [V001[1],V002[1],V003[1],V004[1],V005[1]]
up 5.000000 [-7 15 -3 0 0 ]
down 24.000000 [-7 15 -3 19 0 ]
Solution: [V001[1],V002[1],V003[1],V004[1],V005[0]]
up 5.000000 [-7 15 -3 0 0 ]
up 8.000000 [-7 15 0 0 0 ]
down 5.000000 [-7 15 -3 0 0 ]
down 5.000000 [-7 15 -3 0 0 ]
Solution: [V001[1],V002[1],V003[1],V004[0],V005[1]]
up 5.000000 [-7 15 -3 0 0 ]
up 8.000000 [-7 15 0 0 0 ]
up -7.000000 [-7 0 0 0 0 ]
up 0.000000 [0 0 0 0 0 ]
up 0.000000 [0 0 0 0 0 ]
4.6.1 Implementation
The problem statement and setting of start data remain exactly the same as with the standard
implementation of the problem seen in Section 3.3. What is new are the prompt for user input and the
subsequent handling of infeasibilities. The underlying mechanisms are markers (often referred to as
choice points) to save the state of the constraint solver at a given moment and the possibility to return
to the last such saved state. The corresponding Mosel subroutines are cp_save_state and
cp_restore_state. After stating a new constraint but before propagating its effect (the automated
propagation has been switched off through the setting of AUTO_PROPAGATE at the beginning of the
model) we save the state of the constraint system. If the new constraint turns out to be infeasible the
solver is set back to the state before the constraint propagation and we can then invoke the infeasibility
analysis (cp_infeas_analysis).
The subroutine for solution printing has been replaced by a routine printing the values of all fixed
variables.
model "Conflicts"
uses "kalis"
setparam("kalis_default_lb", 1)
setparam("kalis_default_ub", 9) ! Default variable bounds
declarations
XS = {'A','B','C','D','E','F','G','H','I'} ! Columns
YS = 1..9 ! Rows
v: array(XS,YS) of cpvar ! Number assigned to cell (x,y)
end-declarations
NX::(1..9)['A','B','C','D','E','F','G','H','I']
cp_save_state
res:=cp_propagate
print_values
setrandseed(3)
end-do
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Print fixed values
procedure print_values
writeln(" A B C D E F G H I")
forall(y in YS) do
write(y, ": ")
forall(x in XS)
write(if(is_fixed(v(x,y)), string(getval(v(x,y))), "."),
if(x in {'C','F'}, " | ", " "))
writeln
if y mod 3 = 0 then
writeln(" ---------------------")
end-if
end-do
end-procedure
end-model
4.6.2 Results
With the following user value input sequence:
v(F,7)[1,5..6,8] =
1
v(I,1)[1..2,7,9] =
1
v(I,8)[2,6..7] =
2
we obtain an infeasibility for the third value. The values printed at this stage and the report generated
by the solver show why this value leads to a conflict.
A B C D E F G H I
1: 8 . . | . . 3 | . . 1
2: . 5 . | . . . | 4 . 7
3: 2 . 1 | . 7 . | . 6 9
---------------------
4: . . . | 1 . . | . . 5
5: . . 3 | . . . | 9 . .
6: 6 . . | . . 4 | . . 7
---------------------
7: . 7 . | . 2 1 | . . 3
8: . . 4 | . . . | . 1 2
9: . . . | 9 . . | . . 8
---------------------
------------------------
Minimal Conflict Set
------------------------
1 : AllDifferent(v(I,1),v(I,2),v(I,3),v(I,4),v(I,5),v(I,6),v(I,7),v(I,8),v(I,9
))
Note: in the present example we examine the effect of adding just a single (bound) constraint to our
problem. However, the infeasibility analysis functionality can be applied for any number and type of
constraints.
The simplest case of a scheduling problem involves only tasks and precedence constraints
between tasks (project scheduling problem in Section 5.2).
Tasks may be mutually exclusive, e.g. because they use the same unitary resource (disjunctive
scheduling / sequencing problem in Section 5.3).
Resources may be usable by several tasks at a time, up to a given capacity limit (cumulative
resources, see Section 5.4).
If the enumeration is started with the function cp_schedule the solver will employ specialized search
strategies suitable for the corresponding (scheduling) problem type. It is possible to parameterize
these strategies or to define user search strategies for scheduling objects (see Section 5.7).
Alternatively, the standard optimization functions cp_minimize / cp_maximize may be used. In this
case the enumeration does not exploit the structural information provided by the scheduling objects
and works simply with decision variables.
The properties of scheduling objects (such as start time or duration of tasks) can be accessed and
employed, for instance, in the definition of constraints, thus giving the user the possibility to extend the
predefined standard problems with other types of constraints. For even greater flexibility Xpress Kalis
also enables the user to formulate his scheduling problems without the aggregate modeling objects,
using dedicated global constraints on decision variables of type cpvar. Most examples in this chapter
are therefore given with two different implementations, one using the scheduling objects and another
without these objects.
5.2 Precedences
Probably the most basic type of a scheduling problem is to plan a set of tasks that are linked by
precedence constraints.
The problem described in this section is taken from Section 7.1 ‘Construction of a stadium’ of the book
‘Applications of optimization with Xpress-MP’
A construction company has been awarded a contract to construct a stadium and wishes to complete
it within the shortest possible time. Table 5.1 lists the major tasks and their durations in weeks. Some
tasks can only start after the completion of certain other tasks, equally indicated in the table.
represented by a precedence graph with arcs (i, j) symbolizing that task i precedes task j.
The objective is to minimize the completion time of the project, that is the start time of the last,
fictitious task N. We thus obtain the following model where an upper bound HORIZON on the start
times is given by the sum of all task durations:
5.2.2 Implementation
The following model shows the implementation of this problem with Xpress Kalis. Since there are no
side-constraints, the earliest possible completion time of the schedule is the earliest start of the
fictitious task N. To trigger the propagation of task-related constraints we call the function
cp_propagate followed by a call to cp_shave that performs some additional tests to remove
infeasible values from the task variables’ domains. At this point, constraining the start of the fictitious
end task to its lower bound reduces all task start times to their feasible intervals through the effect of
constraint propagation. The start times of tasks on the critical path are fixed to a single value. The
subsequent call to minimization only serves for instantiating all variables with a single value so as to
enable the graphical representation of the solution.
declarations
N = 19 ! Number of tasks in the project
! (last = fictitious end task)
TASKS = 1..N
ARC: dynamic array(range,range) of integer ! Matrix of the adjacency graph
DUR: array(TASKS) of integer ! Duration of tasks
HORIZON : integer ! Time horizon
! For tasks on the critical path the start/completion times have been fixed
! by setting the bound on the last task. For all other tasks the range of
! possible start/completion times gets displayed.
forall(j in TASKS) writeln(j, ": ", getstart(task(j)))
Instead of indicating the predecessors of a task, we may just as well state the precedence constraints
by indicating the sets of successors for every task:
5.2.3 Results
The earliest completion time of the stadium construction is 64 weeks.
We therefore obtain the following model for our project scheduling problem:
minimize startN
∀j ∈ TASKS : startj ∈ {0, ..., HORIZON}
∀i, j ∈ TASKS, ∃ARCij : starti + DURi ≤ startj
The corresponding Mosel model is printed in full below. Notice that we have used explicit posting of
the precedence constraints—in the case of an infeasible data instance this may help tracing the cause
of the infeasibility.
declarations
N = 19 ! Number of tasks in the project
! (last = fictitious end task)
TASKS = 1..N
ARC: dynamic array(range,range) of integer ! Matrix of the adjacency graph
DUR: array(TASKS) of integer ! Duration of tasks
HORIZON : integer ! Time horizon
DUR ARC
end-initializations
forall(j in TASKS) do
0 <= start(j); start(j) <= HORIZON
end-do
! For tasks on the critical path the start/completion times have been fixed
! by setting the bound on the last task. For all other tasks the range of
! possible start/completion times gets displayed.
forall(j in TASKS) writeln(j, ": ", start(j))
end-model
Objective 1: The first objective is to minimize the makespan (completion time of the schedule) or,
equivalently, to minimize the completion time finish of the last job. The complete model is then given by
the following (where MAXTIME is a sufficiently large value, such as the sum of all release dates and all
durations):
resource res
tasks taskj (j ∈ JOBS)
minimize finish
finish = maximumj∈JOBS (taskj .end)
res.capacity = 1
∀j ∈ JOBS : taskj .end ∈ {0, ..., MAXTIME}
∀j ∈ JOBS : taskj .start ∈ {RELj , ..., MAXTIME}
∀j ∈ JOBS : taskj .duration = DURj
∀j ∈ JOBS : taskj .requirementres = 1
Objective 2: The formulation of the second objective (minimizing the average processing time or,
equivalently, minimizing the sum of the job completion times) remains unchanged from the first
model—we introduce an additional variable totComp representing the sum of the completion times of
all jobs.
minimize totComp
X
totComp = taskj .end
j∈JOBS
Objective 3: To formulate the objective of minimizing the total tardiness, we introduce new variables
latej to measure the amount of time that a job finishes after its due date. The value of these variables
corresponds to the difference between the completion time of a job j and its due date DUEj . If the job
finishes before its due date, the value must be zero. The objective now is to minimize the sum of these
tardiness variables:
minimize totLate
X
totLate = latej
j∈JOBS
∀j ∈ JOBS : latej ∈ {0, ..., MAXTIME}
∀j ∈ JOBS : latej ≥ taskj .end – DUEj
5.3.2 Implementation
The following implementation with Xpress Kalis (file b4seq3_ka.mos) shows how to set up the
necessary task and resource modeling objects. The resource capacity is set with procedure
set_resource_attributes (the resource is of the type KALIS_UNARY_RESOURCE meaning that it
processes at most one task at a time), for the tasks we use the procedure set_task_attributes.
The latter exists in several overloaded versions for different combinations of arguments (task
attributes)—the reader is referred to the Xpress Kalis Reference Manual for further detail.
For the formulation of the maximum constraint we use an (auxiliary) list of variables: Xpress Kalis does
not allow the user to employ the access functions to modeling objects (getstart, getduration,
etc.) in set expressions such as union(j in JOBS) getend(task(j)).
declarations
NJ = 7 ! Number of jobs
JOBS=1..NJ
forall(j in JOBS) do
0 <= getstart(task(j)); getstart(task(j)) <= MAXTIME
0 <= getend(task(j)); getend(task(j)) <= MAXTIME
end-do
! Start times
forall(j in JOBS) getstart(task(j)) >= REL(j)
forall(j in JOBS) do
0 <= late(j); late(j) <= MAXTIME
end-do
!-----------------------------------------------------------------
! Solution printing
procedure print_sol
writeln("Completion time: ", getsol(finish) ,
" average: ", getsol(sum(j in JOBS) getend(task(j))))
write("Rel\t")
forall(j in JOBS) write(strfmt(REL(j),4))
write("\nDur\t")
forall(j in JOBS) write(strfmt(DUR(j),4))
write("\nStart\t")
forall(j in JOBS) write(strfmt(getsol(getstart(task(j))),4))
write("\nEnd\t")
forall(j in JOBS) write(strfmt(getsol(getend(task(j))),4))
writeln
end-procedure
procedure print_sol3
write("Due\t")
forall(j in JOBS) write(strfmt(DUE(j),4))
write("\nLate\t")
forall(j in JOBS) write(strfmt(getsol(late(j)),4))
writeln
end-procedure
end-model
5.3.3 Results
This model produces similar results as those reported for the model versions in Section 3.5. Figure 5.1
shows a Gantt chart display of the solution. Above the Gantt chart we can see the resource usage
display: the machine is used without interruption by the tasks, that is, even if we relaxed the constraints
given by the release times and due dates it would not have been possible to generate a schedule
terminating earlier.
Our task is to save sixteen files of the following sizes: 46kb, 55kb, 62kb, 87kb, 108kb, 114kb, 137kb,
164kb, 253kb, 364kb, 372kb, 388kb, 406kb, 432kb, 461kb, and 851kb onto empty disks of 1.44Mb
capacity. How should the files be distributed in order to minimize the number of floppy disks used?
resource disks
tasks filesf (f ∈ FILES)
minimize diskuse
diskuse = maximumf∈FILES (filef .start)
disks.capacity = CAP
∀f ∈ FILES : filef .start ≥ 1
∀f ∈ FILES : filef .duration = 1
∀f ∈ FILES : filef .requirementdisks = SIZEf
5.4.2 Implementation
The implementation with Xpress Kalis is quite straightforward. We define a resource of the type
KALIS_DISCRETE_RESOURCE, indicating its total capacity. The definition of the tasks is similar to
what we have seen in the previous example.
declarations
ND: integer ! Number of floppy disks
FILES = 1..16 ! Set of files
DISKS: range ! Set of disks
DISKS:= 1..ND
! Solution printing
writeln("Number of disks used: ", getsol(diskuse))
forall(d in 1..getsol(diskuse)) do
write(d, ":")
forall(f in FILES) write( if(getsol(getstart(file(f)))=d , " "+SIZE(f), ""))
writeln(" space used: ",
sum(f in FILES | getsol(getstart(file(f)))=d) SIZE(f))
end-do
cp_show_stats
end-model
5.4.3 Results
Running the model results in the solution shown in Table 5.2, that is, 3 disks are needed for backing up
all the files.
Table 5.2: Distribution of files to disks
Disk File sizes (in kb) Used space (in Mb)
1 46 87 137 164 253 364 388 1.439
2 55 62 108 372 406 432 1.435
3 114 461 851 1.426
where UB stands for ‘upper bound’ and LB for ‘lower bound’ of a decision variable.
Let savef denote the disk used for saving a file f and usef the space used by the file (f ∈ FILES). As with
scheduling objects, the ‘start’ property of a task corresponds to the disk chosen for saving the file, and
the resource requirement of a task is the file size. Since we want to save every file onto a single disk,
the ‘duration’ durf is fixed to 1. The remaining task properties ‘end’ and ‘size’ (ef and sf ) that need to be
provided in the formulation of ‘cumulative’ constraints are not really required for our problem; their
values are determined by the other three properties.
5.4.5 Implementation
The following Mosel model implements the second model version using the cumulative constraint.
setparam("kalis_default_lb", 0)
declarations
ND: integer ! Number of floppy disks
FILES = 1..16 ! Set of files
DISKS: range ! Set of disks
forall(f in FILES) do
setdomain(save(f), DISKS) ! Every file onto a single disk
use(f) = SIZE(f)
dur(f) = 1
end-do
! Solution printing
end-model
The solution produced by the execution of this model has the same objective function value, but the
distribution of the files to the disks is not exactly the same: this problem has several different optimal
solutions, in particular those that may be obtained be interchanging the order numbers of the disks. To
shorten the search in such a case it may be useful to add some symmetry breaking constraints that
reduce the size of the search space by removing a part of the feasible solutions. In the present example
we may, for instance, assign the biggest file to the first disk and the second largest to one of the first
two disks, and so on, until we reach a lower bound on the number of disks required (a save lower bound
estimate is given by rounding up to the next larger integer the sum of files sizes divided by the disk
capacity).
resource res
tasks taskj (j ∈ JOBS)
X
maximize (PROFITj – COSTj ) × taskj .duration
j∈JOBS
res.capacity = 0
∀j ∈ JOBS : taskj .start, taskj .end ∈ {0, ..., HORIZON}
∀j ∈ JOBS : taskj .duration ∈ {MINDj , ..., MAXDj }
∀j ∈ FIRST : taskj .provisionres = RESAMTj
∀j ∈ FINAL : taskj .requirementres = RESAMTj
resource res
tasks taskj (j ∈ JOBS)
X
maximize (PROFITj – COSTj ) × taskj .duration
j∈JOBS
res.capacity = 0
∀j ∈ JOBS : taskj .start, taskj .end ∈ {0, ..., HORIZON}
∀j ∈ JOBS : taskj .duration ∈ {MINDj , ..., MAXDj }
∀j ∈ FIRST : taskj .productionres = RESAMTj
∀j ∈ FINAL : taskj .consumptionres = RESAMTj
However, this model does not entirely correspond to the problem description above since the
production of the intermediate product occurs at the start of a task. To remedy this problem we may
introduce an auxiliary task Endj for every job j in the first stage. The auxiliary job has duration 0, the
same completion time as the original job and produces the intermediate product in the place of the
original job.
5.5.2 Implementation
The following Mosel model implements case A. We use the default scheduling solver (function
cp_schedule) indicating by the value true for the optional second argument that we wish to
maximize the objective function.
declarations
FIRST = {'P1','P2'}
FINAL = {'P3','P4','P5'}
JOBS = FIRST+FINAL
totalProfit: cpfloatvar
task: array(JOBS) of cptask ! Task objects for jobs
intermProd: cpresource ! Non-renewable resource (intermediate prod.)
end-declarations
! Setting up resources
set_resource_attributes(intermProd, KALIS_DISCRETE_RESOURCE, CAP)
setname(intermProd, "IntP")
! Providing tasks
forall(j in FIRST) provides(task(j), RESAMT(j), intermProd)
! Requiring tasks
forall(j in FINAL) requires(task(j), RESAMT(j), intermProd)
! Solution printing
writeln("Total profit: ", getsol(totalProfit))
writeln("Job\tStart\tEnd\tDuration")
forall(j in JOBS)
writeln(j, "\t ", getsol(getstart(task(j))), "\t ", getsol(getend(task(j))),
"\t ", getsol(getduration(task(j))))
end-model
The model for case B adds the two auxiliary tasks (forming the set ENDFIRST) that mark the
completion of the jobs in the first stage. The only other difference are the task properties produces
and consumes that define the resource constraints. We only repeat the relevant part of the model:
declarations
FIRST = {'P1','P2'}
ENDFIRST = {'EndP1', 'EndP2'}
FINAL = {'P3','P4','P5'}
JOBS = FIRST+ENDFIRST+FINAL
totalProfit: cpfloatvar
task: array(JOBS) of cptask ! Task objects for jobs
intermProd: cpresource ! Non-renewable resource (intermediate prod.)
end-declarations
! Setting up resources
set_resource_attributes(intermProd, KALIS_DISCRETE_RESOURCE, CAP)
setname(intermProd, "IntP")
! Production tasks
forall(j in ENDFIRST) produces(task(j), RESAMT(j), intermProd)
forall(j in FIRST) getend(task(j)) = getend(task("End"+j))
! Consumer tasks
forall(j in FINAL) consumes(task(j), RESAMT(j), intermProd)
5.5.3 Results
The behavior of the (default) search and the results of the two models are considerably different. The
optimal solution with an objective of 344.9 for case B represented in Figure 5.3 is proven within a
fraction of a second. Finding a good solution for case A takes several seconds on a standard PC;
finding the optimal solution (see Figure 5.2) and proving its optimality requires several minutes of
running time. The main reason for this poor behavior of the search is our choice of the objective
function: the cost-based objective function does not propagate well and therefore does not help with
pruning the search tree. A better choice for objective functions in scheduling problems generally are
criteria involving the task decision variables (start, duration, or completion time, particularly the latter).
where UB stands for ‘upper bound’ and LB for ‘lower bound’ of a decision variable.
5.5.5 Implementation
The following Mosel model implements the second model version using the producer_consumer
constraint.
setparam("KALIS_DEFAULT_LB", 0)
declarations
FIRST = {'P1','P2'}
ENDFIRST = {'EndP1', 'EndP2'}
FINAL = {'P3','P4','P5'}
JOBS = FIRST+ENDFIRST+FINAL
PCJOBS = ENDFIRST+FINAL
totalProfit: cpfloatvar
fstart,fdur,fcomp: array(FIRST) of cpvar! Start, duration & completion of jobs
start,dur,comp: array(PCJOBS) of cpvar ! Start, duration & completion of jobs
produce,consume: array(PCJOBS) of cpvar ! Production/consumption per time unit
psize,csize: array(PCJOBS) of cpvar ! Cumulated production/consumption
end-declarations
! Production tasks
forall(j in ENDFIRST) do
produce(j) = RESAMT(j)
consume(j) = 0
end-do
forall(j in FIRST) fcomp(j) = comp("End"+j)
! Consumer tasks
forall(j in FINAL) do
consume(j) = RESAMT(j)
produce(j) = 0
end-do
! Resource constraint
producer_consumer(start, comp, dur, produce, psize, consume, csize)
end-model
This model generates the same solution as the previous model version with a slightly longer running
time (though still just a fraction of a second on a standard PC).
For every job j (j ∈ JOBS = {1, ..., NJ}), represented by a task object taskj , we are given its processing
duration DURj . We also have a matrix of cleaning times CLEAN with entries CLEANjk indicating the
duration of the cleaning operation if task k succedes task j. The machine processing the jobs is
modeled as a resource res of unitary capacity, thus stating the disjunctions between the jobs.
With the objective to minimize the makespan (completion time of the last batch) we obtain the
following model:
resource res
tasks taskj (j ∈ JOBS)
minimize finish
finish = maximumj∈JOBS (taskj .end)
res.capacity = 1
∀j ∈ JOBS : taskj .duration = DURj
∀j ∈ JOBS : taskj .requirementres = 1
∀j, k ∈ JOBS : setup(taskj , taskk ) = CLEANjk
The tricky bit in the formulation of the original problem is that we wish to minimize the cycle time, that
is, the completion of the last job plus the setup required between the last and the first jobs in the
sequence. Since our task-based model does not contain any information about the sequence or rank of
the jobs we introduce auxiliary variables firstjob and lastjob for the index values of the jobs in the first
and last positions of the production cycle, and a variable cleanlf for the duration of the setup operation
between the last and first tasks. The following constraints express the relations between these
variables and the task objects:
firstjob, lastjob ∈ JOBS
firstjob 6= lastjob
∀j ∈ JOBS : taskj .end = finish ⇔ lastjob = j
∀j ∈ JOBS : taskj .start = 1 ⇔ firstjob = j
cleanlf = CLEANlastjob,firstjob
Minimizing the cycle time then corresponds to minimizing the sum finish + cleanlf.
5.6.1.2 Implementation
The following Mosel model implements the task-based model formulated above. The setup times
between tasks are set with the procedure setsetuptimes indicating the two task objects and the
corresponding duration value.
declarations
NJ = 5 ! Number of paint batches (=jobs)
JOBS=1..NJ
firstjob,lastjob,cleanlf,finish: cpvar
L: cpvarlist
cycleTime: cpvar ! Objective variable
Strategy: array(range) of cpbranching
end-declarations
! Solution printing
declarations
SUCC: array(JOBS) of integer
end-declarations
forall(j in JOBS)
forall(k in JOBS)
if getsol(getstart(task(k))) = getsol(getend(task(j)))+CLEAN(j,k) then
SUCC(j):= k
break
end-if
writeln("Minimum cycle time: ", getsol(cycleTime))
writeln("Sequence of batches:\nBatch Start Duration Cleaning")
forall(k in JOBS)
writeln(" ", k, strfmt(getsol(getstart(task(k))),7), strfmt(DUR((k)),8),
strfmt(if(SUCC(k)>0, CLEAN(k,SUCC(k)), getsol(cleanlf)),9))
end-model
5.6.1.3 Results
The results are similar to those reported in Section 3.8. It should be noted here that this model
formulation is less efficient, in terms of search nodes and running times, than the previous model
versions, and in particular the ’cycle’ constraint version presented in Section 3.10. However, the
task-based formulation is more generic and easier to extend with additional features than the
problem-specific formulations in the previous model versions.
The graphical representation of the results looks as follows (Figure 5.4).
For each task j in the set of tasks TASKS we are given a fixed duration DURj and a machine-dependent
amount of resource REQjm that is required per time unit for the whole duration of the task. Both
machines have the same capacity CAP.
The resulting model looks as follows.
5.6.2.2 Implementation
uses "kalis"
setparam("KALIS_DEFAULT_LB", 0)
declarations
TASKS = {"a","b","c","d"} ! Index set of tasks
MACH = {"M1", "M2"} ! Index set of resources
USE: array(TASKS,MACH) of integer ! Machine-dependent res. requirement
DUR: array(TASKS) of integer ! Durations of tasks
DUR::(["a","b","c","d"])[7, 9, 8, 5]
USE::(["a","b","c","d"],["M1","M2"])[
4, 3,
2, 3,
2, 1,
4, 5]
cp_set_solution_callback("print_solution")
starttime:=timestamp
! Solution printing
forall(j in TASKS)
writeln(j, ": ", getsol(getstart(T(j))), " - ", getsol(getend(T(j))))
forall(t in 1..getsol(getmakespan)) do
write(strfmt(t-1,2), ": ")
! ⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Print solutions during enumeration at the node where they are found
public procedure print_solution
writeln(timestamp-starttime, "sec. Solution: ", getsol(getmakespan))
forall(m in MACH) do
writeln(m, ":")
forall(t in 0..getsol(getmakespan)-1) do
write(strfmt(t,2), ": ")
forall(j in TASKS | getrequirement(T(j), R(m), t)>0)
write(j, ":", getrequirement(T(j), R(m), t), " " )
writeln(" (total ", sum(j in TASKS) getrequirement(T(j), R(m), t), ")" )
end-do
end-do
end-procedure
end-model
A new feature introduced by this example are the resusage objects that are employed in the definition
of the resource requirement constraints for the tasks. A resusage consists of a resource object and a
resource usage (specified by a constant, lower and upper bounds, or a complete profile — see Section
5.6.3). In this example we use the simplest version with a fixed resource usage value. Optional third
and forth arguments to requires can be defined to specify the minimum and maximum number of
resources to be used by a task. The default version (employed in this example) will choose a single
resource from the set of alternative resources.
The implementation also shows how to retrieve solution information about resource assignments at a
solution node (that is, during the enumeration) and after the search. At the solution node the function
getrequirement returns the desired resource usage information; for a solution summary after
terminating the search we need to work with the decision variable obtained through getassignment.
The formulation of produces, consumes, and provides constraints with resusage objects is
analogous to what we have shown here for requires.
Note: when implementing alternative resources you always have to use the resource type
KALIS_DISCRETE_RESOURCES, setting a capacity limit of 1 for modeling disjunctive resources.
5.6.2.3 Results
Figure 5.5 shows two solutions generated by the execution of our model. The first solution uses a
lesser total amount resource but the second one is optimal with respect to the objective of minimizing
the makespan.
Note: leaving the decision about the resource choice open for the solver considerably increases the
complexity of a scheduling problem. We therefore recommend to use this feature sparingly.
Optimization problems involving resource assignment and sequencing aspects are often more easily
solved by a decompostion approach (see the Xpress Whitepaper Hybrid MIP/CP solving with Xpress
Optimizer and Xpress Kalis).
task the corresponding amount of resource. For example the four tasks in Figure 5.6 have the profiles
Profilea : (12, 12, 6, 2, 2, 2, 2), Profileb : (12, 12, 6, 2, 2, 2, 2, 2, 2), Profilec : (12, 12, 3, 3, 3, 3, 3, 3), and Profiled :
(6, 6, 6, 6, 6) respectively.
Let TASKS = {0 a0 ,0 b0 ,0 c0 ,0 d0 } be the set of tasks and res a resource of capacity CAP required by all tasks
for their execution. With the objective of minimizing the makespan of the schedule we may formulate
the model as follows.
resource res
tasks taskj (j ∈ TASKS)
minimize makespan
res.capacity = CAP
∀j ∈ TASKS : taskj .start, taskj .end ∈ {0, ..., HORIZON}
∀j ∈ TASKS : taskj .duration = DURj
∀j ∈ TASKS : taskj .requirementres = Profilej
This model formulation differs from a standard cumulative scheduling model (see Section 5.4 only in a
single point: the resource requirement Profilej of a task j is a list of values and not just a single constant.
5.6.3.2 Implementation
uses "kalis"
setparam("KALIS_DEFAULT_LB", 0)
declarations
TASKS = {"a","b","c","d"} ! Index set of tasks
Profile: array(TASKS) of list of integer ! Task profiles
DUR: array(TASKS) of integer ! Durations of tasks
DUR::(["a","b","c","d"])[7, 9, 8, 5]
Profile("a"):= [12, 12, 6, 2, 2, 2, 2]
Profile("b"):= [12, 12, 6, 2, 2, 2, 2, 2, 2]
Profile("c"):= [12, 12, 3, 3, 3, 3, 3, 3]
Profile("d"):= [6, 6, 6, 6, 6]
cp_set_solution_callback("print_solution")
starttime:=timestamp
! Solution printing
writeln("Schedule with makespan ", getsol(getmakespan), ":")
forall(t in TASKS)
writeln(t, ": ", getsol(getstart(T(t))), " - ", getsol(getend(T(t))))
! ⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Print solutions during enumeration at the node where they are found.
! 'getrequirement' can only be used here to access solution information,
! not after the enumeration (it returns a value corresponding to the
! current state, that is, 0 after the enumeration).
public procedure print_solution
writeln(timestamp-starttime, "sec. Solution: ", getsol(getmakespan))
forall(i in 0..getsol(getmakespan)-1) do
write(strfmt(i,2), ": ")
forall(t in TASKS | getrequirement(T(t), R, i)>0)
write(t, ":", getrequirement(T(t), R, i), " " )
writeln(" (total ", sum(t in TASKS) getrequirement(T(t), R, i), ")" )
end-do
end-procedure
end-model
The statement of the resource constraints (requires) uses an additional type of scheduling object for
the definition of the resource profiles, namely resusage. We have already encountered this object in
Section 5.6.2 where it was employed in the definition of sets of alternative resources. In the present
case there is only a single resource that must be used by all tasks, we now employ resusage to define
resource usage profiles.
5.6.3.3 Results
The chart in Figure 5.7 shows an optimal schedule of the four tasks on a resource with capacity 18. All
tasks are completed by time 11.
Let TASKS = {0 a0 ,0 b0 } be the set of tasks and res a resource of capacity CAP required by the tasks for
their execution. The set IDLESET contains the time periods during which the resource is unavailable
preempting the processing of tasks. With the objective of minimizing the makespan of the schedule we
may formulate the model as follows.
resource res
tasks taskj (j ∈ TASKS)
minimize makespan
res.capacity = CAP
res.idletimes = IDLESET
∀j ∈ TASKS : taskj .start, taskj .end ∈ {0, ..., HORIZON}
∀j ∈ TASKS : taskj .duration ≥ DURj
∀j ∈ TASKS : taskj .requirementres = Profilej
Please notice that we do not fix the durations of the tasks in this formulation: we may indicate (lower or
upper) bounds to restrict the total duration of a task, but if we want to leave room for preemptions the
bounds on the durations of tasks must include some slack.
5.6.4.2 Implementation
The following model shows how to implement our example problem with Mosel.
model "Preemption"
uses "kalis"
setparam("KALIS_DEFAULT_LB", 0)
declarations
aprofile,bprofile: list of integer
a,b: cptask
R: cpresource
end-declarations
b.duration >= 8
b.name:= "task_b"
bprofile:= [1,1,1,2,2,2,1,1]
cp_set_solution_callback("print_solution")
cp_show_sol
else
writeln("No solution")
end-if
! ⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
end-model
There are several equivalent ways of stating the constant resource usage of task a. Instead of defining
explicitly the complete profile we may simply write
requires(a, resusage(R,1))
or even shorter:
requires(a, 1, R)
5.6.4.3 Results
Solution: 12
Schedule: a: 0-5, b: 0-11
Resource usage:
0: Cap: 3, a:1, b:1
1: Cap: 3, a:0, b:0
2: Cap: 3, a:0, b:0
3: Cap: 3, a:1, b:1
4: Cap: 3, a:1, b:1
5: Cap: 3, a:1, b:2
6: Cap: 3, a:0, b:2
7: Cap: 3, a:0, b:0
8: Cap: 3, a:0, b:0
9: Cap: 3, a:0, b:2
10: Cap: 3, a:0, b:1
11: Cap: 3, a:0, b:1
setidletimes(R, (1..2)+(7..8))
results in the following solution where the tasks are placed in such a way that they do not overlap with
any ’hole’ in resource availability.
Solution: 17
Schedule: a: 3-6, b: 9-16
Resource usage:
0: Cap: 3, a:0, b:0
1: Cap: 0, a:0, b:0
2: Cap: 0, a:0, b:0
3: Cap: 3, a:1, b:0
4: Cap: 3, a:1, b:0
5: Cap: 3, a:1, b:0
6: Cap: 3, a:1, b:0
7: Cap: 0, a:0, b:0
8: Cap: 0, a:0, b:0
9: Cap: 3, a:0, b:1
10: Cap: 3, a:0, b:1
11: Cap: 3, a:0, b:1
12: Cap: 3, a:0, b:2
13: Cap: 3, a:0, b:2
14: Cap: 3, a:0, b:2
15: Cap: 3, a:0, b:1
16: Cap: 3, a:0, b:1
It is also possible to define both, preemptive and non-preemptive times of unavailability for the same
resource. For example, if times 1-2 correspond to a weekend where products may remain on the
machine R and times 7-8 are required for maintenance operations during which the machine must
imperatively be empty we would state in our model:
setidletimes(R, 1, 2, 0)
setcapacity(R, (7..8))
In this case the execution of task a may start at time 0, whereas task b will again be processed in times
9-16.
5.7 Enumeration
In the previous scheduling examples we have used the default enumeration for scheduling problems,
invoked by the optimization function cp_schedule. The built-in search strategies used by the solver in
this case are particularly suited if we wish to minimize the completion time of a schedule. With other
objectives the built-in strategies may not be a good choice, especially if the model contains decision
variables that are not part of scheduling objects (the built-in strategies always enumerate first the
scheduling objects) or if we wish to maximize an objective. Xpress Kalis makes it therefore possible to
use the standard optimization functions cp_minimize and cp_maximize in models that contain
scheduling objects, the default search strategies employed by these optimization functions being
different from the scheduling-specific ones. In addition, the user may also define his own
problem-specific enumeration as shown in the following examples.
User-defined enumeration strategies for scheduling problems may take two different forms:
variable-based and task-based. The former case is the subject of Chapter 4, and we only give a small
example here (Section 5.7.1). The latter will be explained with some more detail by the means of a
job-shop scheduling example.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 100
Scheduling
The only decisions to make in this problem are the assignments of files to disks, that is, choosing a
value for the start time variables of the tasks. The following lines of Mosel code order the tasks in
decreasing order of file sizes and define an enumeration strategy for their start times assigning each
the smallest possible disk number first. Notice that the sorting subroutine qsort is defined by the
module mmsystem that needs to be loaded with a uses statement at the beginning of the model.
declarations
Strategy: array(range) of cpbranching
FSORT: array(FILES) of integer
end-declarations
The choice of the variable selection criterion (first argument of assign_var) is not really important
here since every strategy Strategyf only applies to a single variable and hence no selection takes place.
Equivalently, we may have written
declarations
Strategy: cpbranching
FSORT: array(FILES) of integer
LS: cpvarlist
end-declarations
With this search strategy the first solution found uses 3 disks, that is, we immediately find an optimal
solution. The whole search terminates after 17 nodes and takes only a fraction of the time needed by
the default scheduling or minimization strategies.
The scheduling search consists of a pretreatment (shaving) phase and two main phases
(KALIS_INITIAL_SOLUTION and KALIS_OPTIMAL_SOLUTION) for which the user may specify
enumeration strategies by calling cp_set_schedule_search with the corresponding phase
selection. The ‘initial solution’ phase aims at providing quickly a good solution whereas the ‘optimal
solution’ phase proves optimality. Any search limits such as maximum number of nodes apply
separately to each phase, an overall time limit (parameter KALIS_MAX_COMPUTATION_TIME) only
applies to the last phase.
The definition of variable-based branching schemes for the scheduling search is done in exactly the
same way as what we have seen previously for standard search with cp_minimize or cp_maximize,
replacing cp_set_strategy by cp_set_schedule_strategy:
cp_set_schedule_branching(KALIS_INITIAL_SOLUTION, Strategy)
if cp_schedule(diskuse)=0 then writeln("Problem infeasible"); end-if
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 101
Scheduling
With this search strategy, the optimal solution is found in the ’initial solution’ phase after just 8 nodes
and the enumeration stops there since the pretreatment phase has proven a lower bound of 3 which is
just the value of the optimal solution.
NB: to obtain an output log from the different phases of the scheduling search set the control
parameter KALIS_VERBOSE_LEVEL to 2, that is, add the following line to your model before the start
of the solution algorithm.
setparam("KALIS_VERBOSE_LEVEL", 2)
Let JOBS denote the set of jobs and MACH (MACH = {1, ..., NM}) the set of machines. Every job j is
produced as a sequence of tasks taskjm where taskjm needs to be finished before taskj,m+1 can start. A
task taskjm is processed by the machine RESjm and has a fixed duration DURjm .
The following model formulates the job-shop scheduling problem.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 102
Scheduling
5.7.2.2 Implementation
The following Mosel model implements the job-shop scheduling problem and defines a task-based
branching strategy for solving it. We select the task that has the smallest remaining domain for its start
variable and enumerate the possible values for this variable starting with the smallest. A task-based
branching strategy is defined in Xpress Kalis with the function task_serialize, that takes as
arguments the user task selection, value selection strategies for the duration and start variables, and
the set of tasks it applies to. Such task-based branching strategies can be combined freely with any
variable-based branching strategies.
parameters
DATAFILE = "jobshop.dat"
NJ = 6 ! Number of jobs
NM = 6 ! Number of resources
end-parameters
declarations
JOBS = 1..NJ ! Set of jobs
MACH = 1..NM ! Set of resources
RES: array(JOBS,MACH) of integer ! Resource use of tasks
DUR: array(JOBS,MACH) of integer ! Durations of tasks
! Branching strategy
Strategy:=task_serialize("select_task", KALIS_MIN_TO_MAX,
KALIS_MIN_TO_MAX,
union(j in JOBS, m in MACH | exists(task(j,m))) {task(j,m)})
cp_set_branching(Strategy)
! Solution printing
cp_show_stats
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 103
Scheduling
!⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎⁎
! Task selection for branching
public function select_task(tlist: cptasklist): integer
declarations
Tset: set of integer
end-declarations
returned:= 0
end-function
end-model
5.7.2.3 Results
An optimal solution to this problem has a makespan of 55. In comparison with the default scheduling
strategy, our branching strategy reduces the number of nodes that are enumerated from over 300 to
just 105 nodes with a comparable model execution time (however, for larger instances the default
scheduling strategy is likely to outperform our branching strategy). The default minimization strategy
does not find any solution for this problem within several minutes running time.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 104
Scheduling
Similarly to what we have seen above, we may define a user task selection strategy for the scheduling
search. The only modifications required in our model are to replace cp_set_branching by
cp_set_schedule_strategy and cp_minimize by cp_schedule. The definition of the user task
choice function select_task remains unchanged.
! Branching strategy
Strategy:=task_serialize("select_task", KALIS_MIN_TO_MAX,
KALIS_MIN_TO_MAX,
union(j in JOBS, m in MACH | exists(task(j,m))) {task(j,m)})
cp_set_schedule_strategy(KALIS_INITIAL_SOLUTION, Strategy)
This strategy takes even fewer nodes for completing the enumeration than the standard search with
user task selection.
Instead of the user-defined task selection function select_task it is equally possible to use one of
the predefined task selection criteria:
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 105
Scheduling
For the present example, the best choice proves to be KALIS_SMALLEST_LCT (terminating the search
after approximately 60 nodes with both cp_schedule and cp_minimize):
Strategy:=task_serialize(KALIS_SMALLEST_LCT, KALIS_MIN_TO_MAX,
KALIS_MIN_TO_MAX,
union(j in JOBS, m in MACH | exists(task(j,m))) {task(j,m)})
Even more specialized task selection strategies can be defined with the help of group_serialize. In
this case you may decide freely which are the variables to enumerate for a task and in which order they
are to be considered. The scope of group_serialize is not limited to tasks, you may use this
subroutine to define search strategies involving other types of (user) objects.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 106
CHAPTER 6
Hybridization of CP and MP
This chapter introduces the concept of LP/MIP relaxations for problems formulated as CP models with
Xpress Kalis. By means of examples we show how to
Note: The functionality described in this chapter requires Xpress Optimizer to be installed and licensed
in addition to Xpress Kalis.
Constraint Programming and Mathematical Programming (here used for Linear and Mixed Integer
Programming, LP/MIP) are two different approaches to solving optimization problems that have a
number of complementary features. Some problems, or parts of problems, are better solved by CP,
others by LP/MIP techniques. Based on this observation it is possible to devise various approaches for
hybrid CP-MP problem solving. The FICO Xpress Optimization whitepaper ’Hybrid MIP/CP solving with
Xpress Optimizer and Xpress Kalis’ describes several decomposition schemes where coordination
between MIP and CP models is done on the Mosel level. Xpress Kalis now provides a built-in linear
programming relaxation functionality where communication and cooperation take place directly on the
solver level, with optional configuration and user interaction from the Mosel CP model.
In the context of Xpress Kalis we use the term linear relaxation for sets of linear (in)equality constraints
that are solved by an external LP/MIP solver. In the case of an automatic relaxation this will be a
reformulation of (a subset of) the constraints in the CP problem. A user-defined ’linear relaxation’,
however, may also include additional information, in which case this name choice might be somewhat
misleading.
Xpress Kalis can build automatically several linear (or mixed integer linear) relaxations of a CP problem
and use Xpress Optimizer to solve them. Xpress Kalis uses a double modeling approach (as in
[Hei99a]) by exchanging automatically and in a bidirectional way, information such as objective bounds,
infeasibility, optimal relaxed solutions and reduced costs between the CP solver and the linear
relaxations solver(s) during the search for a solution.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 107
Hybridization of CP and MP
These three ways of defining linear relaxations are presented in the following sections.
setparam("kalis_auto_relax",true)
whereby in many cases you will observe that the time to the first or optimal solution is shortened, or
that optimality is proven faster than with CP-only search.
maximize 5 · x1 + 8 · x2 + 4 · x3 + x4
s.t. xj ∈ {1, 3, 8, 9, 12} for j = 1, 2, 3, 4
3 · x1 + 5 · x2 + 2 · x3 ≤ 50
2 · x1 + x3 + 5 · x4 ≤ 75
all-different(x1 , x2 , x3 , x4 )
6.2.2 Implementation
The model implementation shown below is fairly straightforward, with the exception of the setting of
the control parameter KALIS_AUTO_RELAX.
declarations
VALUES = {1,3,8,9,12}
R = 1..4
x: array(R) of cpvar ! Decision variables
benefit: cpvar ! The objective to minimize
end-declarations
! Knapsack constraints
3⁎x(1) + 5⁎x(2) + 2⁎x(3) <= 50
2⁎x(1) + x(3) + 5⁎x(4) <= 75
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 108
Hybridization of CP and MP
all_different(union(i in R) {x(i)})
! Objective function
benefit = 5⁎x(1) + 8⁎x(2) + 4⁎x(3) + x(4)
! Initial propagation
res := cp_propagate
end-model
6.2.3 Results
Enabling the automatic relaxation for this problem reduces the number of nodes spent by the search
from 17 to 4. However, more time is spent in every node due to the overhead incurred by the LP solving.
As solving times in this small problem are negligible in all formulations time measurements are not
really meaningful; in larger problems the effect of using linear relaxations needs to be tested carefully.
linear constraints
all-different
occurrence
distribute
min/max
absolute value
distance
element
cycle
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 109
Hybridization of CP and MP
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 110
Hybridization of CP and MP
6.3.2 Implementation
The model shown below implements a similar relaxation as the automatic version in the previous
section. This model has two parts: in the beginning we define a standard CP model for our Knapsack
problem and trigger constraint propagation to display the initial bounds on the objective function. The
second part shows how to define and configure a linear relaxation solver. At first, the model generates
the automatic relaxation (and displays it on screen). The argument value 0 in cp_get_linrelax
indicates that we want an LP-oriented relaxation (use 1 for a MIP-oriented relaxation). The relaxation
solver defined with get_linrelax_solver is configured to maximize the objective benefit, solving
the problem as a MIP, and this only once, at the first node of the CP search. The linear relaxation solver
is added to the search process by a call to cp_add_linrelax_solver (there may be several solvers
in a search process). The model definition is completed by a ‘MIP style’ branching scheme that
branches first on the variables with largest reduced cost, and tests first the values nearest to the
optimal solution of the relaxation.
declarations
VALUES = {1,3,8,9,12}
R = 1..4
x: array(R) of cpvar ! Decision variables
benefit: cpvar ! The objective to minimize
end-declarations
! Knapsack constraints
3⁎x(1) + 5⁎x(2) + 2⁎x(3) <= 50
2⁎x(1) + x(3) + 5⁎x(4) <= 75
! Objective function
benefit = 5⁎x(1) + 8⁎x(2) + 4⁎x(3) + x(4)
! Initial propagation
res := cp_propagate
declarations
myrelaxall: cplinrelax
end-declarations
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 111
Hybridization of CP and MP
KALIS_SOLVE_AS_LP, KALIS_TREENODE_RELAX_SOLVER)
! Define a 'MIP' style branching scheme using the solution of the relaxation
cp_set_branching(assign_var(KALIS_LARGEST_REDUCED_COST(mysolver),
KALIS_NEAREST_RELAXED_VALUE(mysolver)))
end-model
6.3.3 Results
The formulation of a CP model for this problem is straightforward. However, solving this type of
problems with pure CP search methods tends to be very time consuming due to the poor quality of the
bounds on the objective variable derived through constraint propagation. Formulating a MIP model for
this problem is a perhaps somewhat more complicated task due to the need to linearize the
’all_different’ relation. Here the automatic reformulation provided by Xpress Kalis is certainly helpful.
The LP relaxation provides valuable bounds and insight on how to direct the search. Combining both
makes the CP search go straight to the optimal solution and optimality is proven immediatly by the
relaxation bounds. The configuration options allow you to tune the behavior of the combined solution
algorithm.
Try to experiment with other settings:
cp_get_linrelax(0, {KALIS_LINEAR_CONSTRAINTS})
Note: Instead of displaying the linear relaxation on screen you may also choose to export it to a file in
LP format using procedure export_prob, specifying a linear relaxation solver and a matrix filename
(the extension ".lp" will be appended to the name) as arguments.
export_prob(mysolver, "mymatfile")
As an alternative or in addition to the built-in relaxation definitions, linear relaxations (or parts thereof)
can be built up from scratch, similarly to the definition of linear constraints in a pure CP model. For the
representation of linear relaxations, Xpress Kalis provides the types cpauxvar and cpauxlinexp (for
variables and linear expressions that do not occur in the original CP model). The linearization of certain
global constraints requires a mapping from integer-valued CP decision variables to sets of 0-1 indicator
variables in the LP/MIP formulation, where each indicator variable is associated with a value in the
domain of the CP variable. These indicator variables (of type cpauxvar) are obtained with function
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 112
Hybridization of CP and MP
get_indicator. Besides these special variable types, linear relaxation constraints can also contain
variables of type cpvar or cpfloatvar.
6.4.1 Implementation
The implementations below replace the configured definition of a relaxation by a relaxation built up
’manually’ by stating the constraints one by one. Just like the previous model version, a relaxation
solver is defined with get_linrelax_solver. This linear relaxation solver is then added to the
search process by a call to cp_add_linrelax_solver and the model definition is completed by a
‘MIP style’ branching scheme that branches first on the variables with largest reduced cost, and tests
first the values nearest to the optimal solution of the relaxation.
This model extract selects one by one the constraints to be added to the relaxation:
declarations
myrelax: cplinrelax
end-declarations
! Output the relaxation to the screen (after creation of solver to see all!)
cp_show_relax(myrelax)
! Define a 'MIP' style branching scheme using the solution of the relaxation
cp_set_branching(assign_var(KALIS_LARGEST_REDUCED_COST(mysolver),
KALIS_NEAREST_RELAXED_VALUE(mysolver)))
When adding linear constraints to the relaxation we have the choice to add them directly, or by using
the function get_linrelax as in the model extract above. When using this function we can specify
through its second argument whether to disregard the integrality condition on any variables the linear
constraint.
If we do not want to use any of the built-in relaxation functionality, we can also state the relaxations
’manually’. In the example below the linear relaxation is formulated entirely with the binary indicators
for domain values, leaving out the variables xi that are present in all the automated relaxation versions
we have seen so far.
declarations
myrelax: cplinrelax
end-declarations
! Formulation of 'alldifferent':
forall(val in VALUES)
myrelax += sum(i in R | contains(x(i),val)) get_indicator(x(i), val) <= 1
forall(i in R)
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 113
Hybridization of CP and MP
6.4.2 Results
When comparing the program output obtained from the two formulations above, it appears that the
automatic formulation provides better guidance to the CP search (that is, less nodes are required to
terminate the enumeration) than the ’manual’ formulation that works exclusively with the binary
indicator variables. There often are different possibilities for formulating linear relaxations and some
experimentation is required to determine which is the best choice.
General recommendations for the formulation of linear relaxations are (a) to be careful to define a
relaxation that makes sense and (b) to make sure that the relaxation is connected to the original
problem (through variables shared by both formulations) so to obtain the desired benefits from
information exchange between the two views of the problem.
Note: Xpress Kalis also makes accessible additional configuration options for the underlying LP/MIP
solver; the interested reader is referred to the documentation of set_linrelax_solver_attribute
in the the Xpress Kalis Reference manual.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 114
Appendix
APPENDIX A
Trouble shooting
No license found: to work with Kalis for Mosel, the Xpress licensing system, and the Xpress Kalis
module must be installed. You need to copy the license file that you will receive from your
software vendor into the Xpress installation directory and set the environment variable
XPRESSDIR to point to this directory.
The Xpress Kalis module is not found: if the file kalis.dso is not installed in the directory dso
of the Mosel distribution, then the environment variable MOSEL_DSO must be defined with the
location of this file.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 116
APPENDIX B
Glossary of CP terms
Some terms commonly used in CP might require some explanation for readers with an Operations
Research background. The following list is an extract from [Hei99b].
Finite domain constraint problem/constraint satisfaction problem (CSP): defined by a finite set of
variables taking values from finite domains and a (conjunctive) set of constraints on these variables.
The objective may be either finding one solution (any or an optimal) or all solutions (consistent
assignment of values to the variables so that all the constraints are satisfied simultaneously) for the
given instance. The term constraint network is frequently employed to denote CP problems in allusion to
the graphical representation as a hyper graph (constraint graph), where nodes represent variables, and
constraints are (hyper) arcs linking several nodes. There is no standard problem representation in CP.
Model: a CP model specifies all variables, their domains and their declarative meaning and conceptual
constraints imposed on them (as opposed to actual constraints that are used to implement the
properties of the solution and the search process). In CP in general, a model preserves much
problem-specific knowledge about variables and the relations between them. This allows the
development and application of more efficient specialized solution strategies.
Variable: object that has a name and a domain (also referred to as decision variable).
Domain: the set of values (also: labels) a variable may take. In Xpress Kalis, it may consist of discrete
values, or intervals of integers. When solving CP problems active use of the domain concept is made.
At any stage, the domain of a variable is the set of values that cannot be proved to be inconsistent (with
the constraints on this variable) using the available consistency checking methods. Assigning or
restricting domains is often interpreted as unary constraints on the corresponding variables.
Instantiation of a set of variables is an assignment of a value to each variable from its domain, also
called labeling of each variable with a value from its domain.
Consistent instantiation of a constraint network is an instantiation of the variables such that the
constraints between variables are satisfied, also called admissible/satisfied instantiation, consistent
assignment of values, or consistent labeling. Solution is often used as a synonym for consistent
instantiation, but may also denote the result after applying any (local/partial) consistency algorithm.
Constraint: a relation over a set of variables limiting the combination of values that these variables can
take; constraints may also be interpreted as mappings from the domains of the variables onto the
Boolean values true and false. A (conceptual) constraint can sometimes be implemented in different
ways enforcing various levels of consistency (see below) with different computational overhead.
So-called global constraints subsume a set of other constraints (for instance an ‘all-different’ relation
on a set of variables replaces pair wise disequality constraints). Global constraints use specific
propagation/consistency algorithms that render them more efficient than the set of constraints they
replace.
Redundant constraints: a constraint is redundant with respect to a set of constraints, if it is satisfied
when the set of constraints is satisfied. Although redundant constraints do not change the set of
solutions (consistent instantiations) of a problem, in practice it may be useful to add redundant
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 117
Glossary of CP terms
constraints to the model formulation because they can help CP solution procedures, particularly by
achieving more powerful constraint propagation.
System of constraints: a conjunctive set of constraints, usually built up incrementally.
Constraint solving: deciding the consistency or satisfiability of a system of constraints.
Solution methods: finite domain CP problems are usually solved by tree search methods
(Branch-and-Bound for optimization, Branch-and-Prune for decision problems) that enumerate the
possible values of the variables coupled with consistency algorithms. In tree search methods with
consistency checking the local consistency algorithm is triggered by the propagation of the domain
changes of the branching variable. For optimization usually a cost constraint is introduced that
propagates to the variables. It is updated (in the case of minimization: bounded to be smaller than the
solution value) each time a new solution is found.
Consistency techniques and constraint propagation: Consistency algorithms remove inconsistent
values from the domains of variables. Informally speaking, a consistency algorithm is ‘stronger’ than
another one if it reduces the domains further, i.e., it establishes a higher level of consistency. In finite
domain CP, typically local consistency algorithms are used. Local or partial consistency signifies that
only subsets of the constraints of a system of constraints are simultaneously satisfied. A locally
consistent (according to some notion of consistency, such as arc-consistency) constraint network can
be obtained by propagating iteratively the effects of each constraint to all other constraints it is
connected to through its variables until a stable state is reached. This process is referred to as
constraint propagation. Propagation properties of constraints vary, e.g., due to their implementation, or
the types of variables used. Possible events triggering their evaluation may be variable instantiation,
modification of domain bounds, removing of value(s) from a domain, etc.
Backtrack search augmented by constraint propagation:
while not solved and not infeasible
check/establish (local) consistency
if a dead end is detected
then backtrack to the first open node
else
select a variable
select a value for the variable
Search algorithms/strategies: The values for variables come out of an enumeration process.
‘Intelligent’ enumeration strategies adapted to special types of constraints and variables are a central
issue in CP. The search is controlled by problem specific heuristics, strategies from Mathematical
Programming or the expert’s knowledge; fixing variables to trial values is possible. One can distinguish
variable and value selection heuristics. Due to the way the backtracking mechanism works, usually
depth-first search is used.
Constraint solver: (Also: constraint engine.) Distinction between exact and incomplete solvers. Exact
solvers guarantee the satisfiability of the system of constraints at any stage of the computations, they
usually work on rational numbers (trees of rationals and linear constraints). Incomplete solvers are
designed for more complex domains such as integers where checking and maintaining consistency of
the overall system is too expensive or not possible with presently known algorithms. These solvers
work with simplified calculations establishing some sort of partial (local) consistency among
constraints; usually simply stating constraints does not produce a solution, an enumeration phase
(searching for solutions) is necessary.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 118
Bibliography
[FM63] H. Fisher and J. F. Muth. Probalistic Learning Combinations of Local Job-Shop Scheduling Rules. In J. F. Muth and G. L.
Thompson, editors, Industrial Scheduling, pages 225—251, Englewood Cliffs, N. J., 1963. Prentice Hall.
[Hei99a] S. Heipcke. Combined Modelling and Problem Solving in Constraint Programming and Mathematical Programming. PhD
thesis, University of Buckingham, 1999.
[Hei99b] S. Heipcke. Comparing Constraint Programming and Mathematical Programming Approaches to Discrete
Optimisation. The Change Problem. Journal of the Operational Research Society, 50(6):581—595, 1999.
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 119
Index
Symbols cumulative, 17
+, 10 cycle, 17, 49, 53
;, 7 declaration, 19
definition, 6, 19
A disequality, 17
abs, 17, 26 disjunction, 33
all-different, 117 disjunctive, 17, 33
all-different constraint, 29 distance, 17, 26
all_different, 17, 25 distribute, 38, 39, 57
and, 36 element, 17, 37
array equivalence, 17, 42
definition, 6 explicit posting, 20, 76
assign_var, 15, 60 generic binary, 17, 50
assignment problem, 63 global, 1, 117
AUTO_PROPAGATE, 70 global cardinality, 39, 57
implication, 17, 42
B implicit, 73
backtrack search, 118 linear, 17
backtrackable number, 68 logic, 17, 36, 42
binary constraint, 50 maximum, 14, 17
binpacking, 81 minimum, 17
bottleneck machine, 29 name, 19
bottleneck problem, 63 nonlinear, 17
bound occurrence, 17, 36, 37, 39
default, 12 or, 33
lower, 66 propagation, 20
upper, 66 redundant, 117
Branch-and-Bound, 118 unary, 117
branching scheme, 15, 35, 46, 52, 60 constraint branching, 35
branching strategy, 2, 46, 52 constraint engine, 118
constraint network, 117
C Constraint Programming, 1
callback, 28, 62 constraint propagation, 2, 118
ceil, 37 constraint satisfaction problem, 117
choice point, 70 constraint solver, 118
comments, 7 constraint solving, 118
compile model, 7 contains, 67
condition continuous variable, 16
loop, 11 cost function, 2
conflict analysis, 69 CP, see Constraint Programming
consistency cp_infeas_analysis, 70
local, 118 cp_restore_state, 70
partial, 118 cp_save_state, 70
consistency algorithm, 117, 118 cp_set_schedule_search, 101
consistent instantiation, 2 cp_find_next_sol, 6, 20, 24
constraint, 2, 44, 117 cp_minimize, 20
absolute value, 17, 26 cp_post, 20, 76
actual, 117 cp_propagate, 20, 75
all-different, 17, 29, 42, 44, 117 cp_reset_search, 28, 62
automatical posting, 19 cp_schedule, 73
cardinality, 36 cp_shave, 75
conceptual, 117 cp_show_prob, 10
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 120
Index
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 121
Index
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 122
Index
T
target value, 61
task
predecessor, 76
resource profile, 94
successor, 76
task selection, 105
task-based
enumeration, 100
task_serialize, 60, 103
time limit, 28
U
upper bound, 66
Fair Isaac Corporation and Artelys SA Confidential and Proprietary Information 123