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:

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 class CpoParameters (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.