Creating a constraint programming model¶
Building a model requires:
- building unitary model expressions and
- assembling them into a model.
The examples can be used as starting point to create a new model.
The detailed documentation of the Python API for docplex.cp is available here.
Build model expressions¶
The CP Optimizer expression elements are implemented in the Python modules located in docplex/cp
.
Basic elements are described in the module expression.py, which contains:
- the class CpoExpr that is the root class for all CP Optimizer expressions,
- one class for each type of variable (CpoIntVar, CpoIntervalVar, CpoSequenceVar), and
- one class per each other object type (CpoTupleSet, CpoTransitionMatrix).
An auxiliary module, function.py, contains classes that represent functions (CpoStepFunction, CpoSegmentFunction) that are used in scheduling.
These classes should not be used directly to create expression objects. Instead, the constructor functions defined in expression.py should be used.
Function Creates integer_var() Single integer variable integer_var_list() List of integer variables integer_var_dict() Dictionary of integer variables binary_var() Single binary variable (integer variable with domain = {0, 1}) binary_var_list() List of binary variables binary_var_dict() Dictionary of binary variables interval_var() Single interval variable interval_var_list() Array of interval variables interval_var_dict() Dictionary of interval variables sequence_var() Sequence variable transition_matrix() Empty transition matrix tuple_set() Tuple set state_function() State function
The CP Optimizer functions for building operations between expressions are available in the module modeler.py, which contains more than 100 specialized functions. See details of these functions in the API documentation.
We will see later that all the modeling functions can also be invoked as member of the class
CpoModel
.
However, there documentation should be see in the modules where they are actually implemented.
Mapping of Python objects to CP Optimizer objects¶
The following Python objects are accepted by the modeling API:
bool objects are converted into boolean constants.
int objects are converted into integer constants.
float objects are converted into float constants.
if numpy is installed:
- numpy bool_ and bool_ are converted into boolean constants.
- numpy int_, intc, intp, int8, int16, int32, int64, uint8, uint16, uint32 and uint64 are converted into integer constants.
- numpy float_, float16, float32 and float64 are converted into float constants.
tuple and list objects are converted into array of expressions.
if numpy is installed, numpy.ndarray objects are also converted into array of expressions.
if panda is available, panda.Series objects are also converted into array of expressions.
Note that all objects that are convertible into arrays are duplicated when the expression is constructed. An internal cache mechanism is used to retrieve the same CP modeling object when same Python source object is used several times.
Special function renaming¶
The modeling functions are identified with the same names in the Python API as in the CPO file format, the C++ API, and the Java API. However, there is an exception for the Boolean operators and, or, and not that are predefined symbols in Python and cannot be overloaded or used as function names. They are renamed as follows:
CPO Python and logical_and or logical_or not logical_not
Functions that correspond to built-in Python functions abs, min, max, range, round, sum, any and all are overwritten in a safe way that calls the built-in implementation if none of the parameters are model expressions. This allows in particular to import all modeling functions at root level, which shorten model writing. However, this approach of modeling is not recommended. Calling modeling functions on the model object is preferable.
Operator overloading¶
To simplify the writing of a model, Python operators are overloaded to match CP Optimizer operator creation functions. The following table indicates which function is assigned to which operator.
Operation plus + minus - unary_minus - times * mod % int_div // float_div / power ** equal == diff != greater_or_equal >= less_or_equal <= greater > less < logical_or | logical_and & logical_not ~
Caution: Logical operations and, or, and not are overloaded by using Python binary operators. These operators have a different priority than usual logical operators. Make sure to always use parenthesis to avoid any ambiguity.
Caution: If one number is negative, the modulo operation in CPO has the same behavior than usual programming languages like C++ or Java, which is not the case in Python. For example, in Java, 5 % -3 = 2, and -5 % 3 = -2. In Python, 5 % -3 = -1 and -5 % 3 = 1. See Wikipedia - Modulo operation for details.
Build a model¶
The model is represented by the class CpoModel implemented in the module model.py.
An expression is added to the model by calling the method add() with the expression as the parameter.
Quickest modeling approach¶
Following is a condensed example of the NQueen problem that uses the shortest way of modeling.
from docplex.cp.model import *
NB_QUEEN = 8
mdl = CpoModel()
x = integer_var_list(NB_QUEEN, 0, NB_QUEEN - 1, "X")
mdl.add(all_diff(x))
mdl.add(all_diff([x[i] + i for i in range(NB_QUEEN)]))
mdl.add(all_diff([x[i] - i for i in range(NB_QUEEN)]))
Note that importing model.py also indirectly imports expression.py, modeler.py, function.py and parameters.py.
Use module as a factory¶
To avoid possible conflict with other modules that may propose similar function names, il is possible to use the module as a factory by writing:
import docplex.cp.model as cp
NB_QUEEN = 8
mdl = cp.CpoModel()
x = cp.integer_var_list(NB_QUEEN, 0, NB_QUEEN - 1, "X")
mdl.add(cp.all_diff(x))
mdl.add(cp.all_diff([x[i] + i for i in range(NB_QUEEN)]))
mdl.add(cp.all_diff([x[i] - i for i in range(NB_QUEEN)]))
Use model object as a factory¶
All functions creating model expressions that are made available by module model.py (that itself embed functions from expression.py and modeler.py) are also available directly at CpoModel object level.
from docplex.cp.model import CpoModel
NB_QUEEN = 8
mdl = CpoModel()
x = mdl.integer_var_list(NB_QUEEN, 0, NB_QUEEN - 1, "X")
mdl.add(mdl.all_diff(x))
mdl.add(mdl.all_diff([x[i] + i for i in range(NB_QUEEN)]))
mdl.add(mdl.all_diff([x[i] - i for i in range(NB_QUEEN)]))
This is the most convenient way of modeling because:
- it is compatible with docplex.mp modeling that imposes to use model object as factory for every expression creation.
- it eliminates collisions between modeling and builtin functions that have the same name.
Solving a model¶
Solving a model can be done in multiple ways:
- Using a local solver (default) that is made available by installing IBM ILOG CPLEX Optimization Studio (see here), or its free Community Edition (see here). In this case, the transformation into CPO file format, the submission for solving, and the retrieval of results is done automatically.
- Transform the model into CPO file format to solve it using another application, such as CP Optimizer Interactive that is provided with CPLEX Optimization Studio. A description of how a model in CPO file format can be generated from the model is described in the section Generate CPO file.
Solve a model with local solver¶
The default local solving assumes that the installation of CPLEX Optimization Studio with a version
greater or equal to 12.7 has been completed correctly and that the CP Optimizer Interactive process
is already put in the system path.
You can check this by invoking cpoptimizer(.exe)
in a command prompt.
Building and solving a model is done as follows:
mdl = CpoModel()
. . . . .
<Construction of the model>
. . . . .
print("Solving model....")
msol = mdl.solve()
msol.print_solution()
The method solve()
method returns an object of class
CpoSolveResult
that contains the result of solving.
This object is described in the section “Retrieve results”.
The method write()
prints a default view of the status of the
solve and the values of all variables.
The object CpoSolveResult
contains all the necessary accessors to create a customized solution output.
Solving options¶
The method solve()
accepts a variable list of optional named arguments for configuring the solving.
For example:
- context=<solving context>: Complete solving configuration. If not given, solving context is the default one that is defined in the module
config
.- params=<solving parameters>: Object of class
CpoParameters
that contains a complete set of solving parameters (see the section “Solving parameters” for more details).- <config attribute>=<value>: Set a particular configuration (context) attribute to the given value
- <solving parameters>=<value>: Set a particular solving parameter to the given value
The list of arguments is not limited. Each named argument is used to replace the leaf attribute that has the same name
in the global configuration initialized in the module config
and its specializations.
If the attribute is not found as an existing attribute, it is then used as a solving parameter that is set in
the params subcontext.
Solving parameters¶
Solving parameters can be passed to the solve method either:
- with an object of class
CpoParameters
assigned to the argument params as indicated in the previous section, or- directly as individual parameters. The names of the parameters are the same as the property exposed in the class
CpoParameters
declared in the module parameters.py.
For example:
msol = mdl.solve(TimeLimit=80, LogPeriod=5000)
is equivalent to:
params = CpoParameters()
params.TimeLimit = 80
params.LogPeriod = 5000
msol = mdl.solve(params=params)
Retrieve results¶
Results from the solve are returned in a data structure of the class CpoSolveResult
,
implemented in the module solution.py.
This object contains:
global model information, such as status of the search, value of the objective(s), and solve time, and
the value of each variable, by using dedicated objects classes:
CpoIntVarSolution
for single integer variables,CpoIntervalVarSolution
for interval variables,CpoSequenceVarSolution
for sequence variables, andCpoStateFunctionSolution
for state functions.
Look at solution.py in the reference manual for details on what methods are available for each object.
Many shortcuts are available to write simpler code.
The model solution object implements the method __nonzero__() and __bool__() (for Python 2 and 3) which makes it possible to test directly if a solution is present.
A simplified Python value for each object is directly accessible by using square brackets (msol[vname]). The result is:
- an integer for integer variables,
- a tuple (start, end, size) for interval variables or empty tuple () if it is absent,
- a list of variable solutions for a sequence variable (each element is a CpoIntervalVarSolution), and
- a list of tuples (start, end, value) for state functions.
The following code is an example of solution printing for the NQueen example:
from sys import stdout
if msol:
stdout.write("Solution:")
for v in x:
stdout.write(" " + str(msol[v]))
stdout.write("\n")
else:
stdout.write("Solve status: " + msol.get_solve_status() + "\n")
The different solution elements can be accessed directly by using the following attributes instead of the dedicated methods:
- vars: Dictionary of solutions of all variables. Key is a variable name, and value is either CpoIntVarSolution, CpoIntervalVarSolution, CpoSequenceVarSolution, or CpoStateFunctionSolution.
- parameters: Map of solving parameters. Key is a parameter name, and value is a string, int, or float (not available when solving on DOcplexcloud).
- infos: Map of solving information. Key is an attribute name, and value is a string, int, or float. (not available when solving on DOcplexcloud).
Generate CPO file¶
The generation of the CPO file corresponding to a model is made available by calling the method
export_model()
, as demonstrated in the following example:
mdl = CpoModel()
. . . . .
<Construction of the model>
. . . . .
mdl.export_model()
Following is the CPO format generated from the NQueen example:
///////////////////////////////////////////////////////////////////////////////
// CPO file generated from:
// C:\CPO\Tests\Workspace\CpoPython\Examples\NQueen.py
///////////////////////////////////////////////////////////////////////////////
//--- Variables ---
X0 = intVar(0..7);
X1 = intVar(0..7);
X2 = intVar(0..7);
X3 = intVar(0..7);
X4 = intVar(0..7);
X5 = intVar(0..7);
X6 = intVar(0..7);
X7 = intVar(0..7);
//--- Expressions ---
alldiff([X0, X1, X2, X3, X4, X5, X6, X7]);
alldiff([X0 + 0, X1 + 1, X2 + 2, X3 + 3, X4 + 4, X5 + 5, X6 + 6, X7 + 7]);
alldiff([X0 - 0, X1 - 1, X2 - 2, X3 - 3, X4 - 4, X5 - 5, X6 - 6, X7 - 7]);
//--- Parameters ---
// None
Generation options¶
The method export_model()
uses the following optional arguments :
- out: To specify an output file or stream instead of stdout.
- all other arguments allowing to change the configuration, as for the method
solve()
.
For example, the argument short_output=True
generates a condensed form of the model, as follows:
X_0 = intVar(0..7);
X_1 = intVar(0..7);
X_2 = intVar(0..7);
X_3 = intVar(0..7);
X_4 = intVar(0..7);
X_5 = intVar(0..7);
X_6 = intVar(0..7);
X_7 = intVar(0..7);
alldiff([X_0, X_1, X_2, X_3, X_4, X_5, X_6, X_7]);
alldiff([X_0 + 0, X_1 + 1, X_2 + 2, X_3 + 3, X_4 + 4, X_5 + 5, X_6 + 6, X_7 + 7]);
alldiff([X_0 - 0, X_1 - 1, X_2 - 2, X_3 - 3, X_4 - 4, X_5 - 5, X_6 - 6, X_7 - 7]);
Get the CP Optimizer model as a string¶
The CPO formatted model can also be retrieved as a string by using the method
get_cpo_string()
.
As for method export_model()
, same arguments than in the
method solve()
can be used.
Advanced configuration¶
The configuration of docplex.cp is based on the object class Context
that allows to
group attributes in a hierarchical way.
An object of the class Context
has the following characteristics:
- It can contain an unlimited number of named attributes that can be easily addressed directly with the Python ‘.’ operator.
- An attribute can itself be a
Context
, enabling a hierarchical representation of attributes.- When an attribute does not exist, it is inherited from the parent
Context
, if any.- If an attribute is not found, the value None is returned by default.
API configuration¶
The default configuration of docplex.cp is accessible using the variable context, which is initialized
in the module docplex.cp.config
.
This module should not be edited directly. Use the procedure in the next section to change configuration parameters.
The default configuration context is organized as follows :
context # General parameters
model # Parameters related to model file generation
params # Solving parameters (of type :class:`~docplex.cp.parameters.CpoParameters`, subclass of :class:`~docplex.cp.utils.Context`)
solver # Parameters of the solving in general
local # Parameters related to local solve with CP Optimizer Interactive
Change the default configuration¶
The default configuration can be easily customized in multiple ways:
- by setting a configuration attribute directly with Python code,
- when calling some methods such as
solve()
(see above),- statically, by creating customized configuration file(s).
Setting an attribute with Python code is very simple, as in the following example:
from docplex.cp.config import context
context.params.TimeLimit = 100
To change the configuration in a persistent way, following customized configuration files can be created at any place
that is visible in the PYTHONPATH
(for example with the modeling sources):
- cpo_config.py
- cpo_config_<your hostname>.py if you want changes to be done only on specific host(s).
If present, these modules are loaded in this order to overwrite the default configuration values.
In these files, changing a configuration attribute is done as in normal Python source, except that the
instruction from docplex.cp.config import context
is already done.
context.solver.trace_cpo = True
context.solver.trace_log = False
context.params.LogPeriod = 5000
These files are evaluated inside the module config.py. You must then not add any import statement to docplex.cp.config inside them.
Display current configuration¶
To display the configuration that includes all the updates that are made using specialized modules,
directly run the module docplex.cp.config
.
The configuration details are printed on standard output.
Example:
> python config.py
log_output = <open file '<stdout>', mode 'w' at 0x00000000023B20C0>
visu_enabled = True
model =
add_source_location = True
dump_directory = "/Tmp/cpo"
trace_cpo = False
params =
TimeLimit = 100
Workers = 4
solver =
add_log_to_solution = True
agent = local
enable_undocumented_params = False
log_prefix = "[Solver] "
local =
class_name = "docplex.cp.solver.solver_local.CpoSolverLocal"
execfile = "cpoptimizer.exe"
log_prefix = "[Local] "
parameters = ['-angel']
. . . . .
Configuration attributes¶
The following is a summary of the most representative configuration parameters.
The complete list of parameters with their detailed description is given in the module docplex.cp.config
context.log_output
Default log stream.
context.model.version
Version of the CPO format that is used to transmit the model to the solver. This attribute is used only if no explicit version is given when building the model. By default, its value is None, meaning that latest format is used, without specifying it explicitly.
context.model.add_source_location
Indicates that when the model is transformed into CPO format, additional information is added to correlate expressions with the Python file and line where it has been generated.
context.model.dump_directory
Name of a directory where the CPO files that are generated for solving models are stored for logging purpose.
context.params.*
Instance of the classCpoParameters
(in parameters.py), which describes all of the public solver parameters as properties.
context.solver.trace_cpo
Indicates to trace the CPO model that is generated before submitting it for solving.
context.solver.trace_log
Indicates to trace the log generated by the solver when solving the CPO model.
context.solver.add_log_to_solution
Indicates to include the solver log content to the solution object.
context.solver.agent
Name of the solving agent to be used for solving the model (local by default).
context.solver.local.execfile
Name or full path of the CP Optimizer Interactive executable file. By default, it is set to cpoptimizer(.exe), which supposes that the program is searched dynamically when needed, if visible from the system path. In this case, when found in the path, this attribute value is replaced by the full path of the executable.