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).

These classes should not be used directly to create expression objects. Instead, the constructor functions should be used, which create, for example, an array or a dictionary of variables in one call:

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.

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 its Python source is used several times. Consequently, if a Python list is used in several modeling expressions, it will always the same value in the model even if the list content is changed between the construction of the different expressions.

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, and sum are overwritten in a safe way that calls the built-in implementation if none of the parameters are expressions.

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.

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.

Import necessary modules

Following is a condensed example of the NQueen problem that uses the default import policy. More comments are available in the files in the directory docplex/cp/examples.

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. This is convenient to assure model writing compatibility with docplex.mp models that require to use model object as factory for every expression creation.

import docplex.cp.model as cp
NB_QUEEN = 8
mdl = cp.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)]))

Solving a model

Solving a model can be done in multiple ways:

  • Automatically submit the model to the DOcplexcloud solving service. 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 drop it manually on the DropSolve application. A description of how a model in CPO file format can be generated from the model is described in section Generate CPO file.

Solve a model with DOcplexcloud

A model can be solved directly and transparently on the cloud, if credentials were initialized properly as described in the installation topic.

mdl = cp.CpoModel()
. . . . .
<Construction of the model>
. . . . .
print("Solving model....")
msol = mdl.solve()
msol.print_solution()

In case you need to explicitly give DOcplexcloud credentials, they can be passed as arguments of the solve() method as follows:

msol = mdl.solve(url=..., key=...)

The 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 print_solution() 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:

  • url=<DOcplexcloud server url>. Overwrites the default configuration parameter context.solver.docloud.url that is used to handle the URL of the DOcplexcloud service.
  • key=<DOcplexcloud authentication key>. Overwrites the default configuration parameter context.solver.docloud.key that is used to handle the DOcplexcloud service access key.
  • params=<solving parameters>: Object of class CpoParameters that contains a complete set of solving parameters (see the section “Solving parameters” for more details).

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.py and its specializations.

If the attribute is not found as an existing attribute, it is then used as a solving parameter set in the params subcontext.

See the section Advanced configuration for more details on configuration.

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, and
    • CpoStateFunction 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).

Note: The attributes parameters and infos are not yet available in responses that come from DOcplexcloud.

Generate CPO file

The generation of the CPO file corresponding to a model is made available by calling the method export_as_cpo(), as demonstrated in the following example:

mdl = CpoModel()
. . . . .
<Construction of the model>
. . . . .
mdl.export_as_cpo()

Following is the CPO format generated from the NQueen example:

///////////////////////////////////////////////////////////////////////////////
// CPO file generated from:
// C:\CPO\Tests\Workspace\CpoPython\Examples\NQueen.py
///////////////////////////////////////////////////////////////////////////////

//--- Variables ---
X7 = int_var(0..7);
X6 = int_var(0..7);
X5 = int_var(0..7);
X4 = int_var(0..7);
X3 = int_var(0..7);
X2 = int_var(0..7);
X1 = int_var(0..7);
X0 = int_var(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

There are two optional parameters to the method export_as_cpo():

  • out: To specify an output file or stream instead of stdout.
  • srcloc: To indicate to add source location information in the generated file.

This last option adds location comments to the generated file, as follows:

//--- Expressions ---
#line 32 "C:\CPO\Tests\Workspace\CpoPython\Examples\NQueen.py"
alldiff([X0, X1]);
#line 35
alldiff([X0 + 0, X1 + 1]);

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().

Advanced configuration

The configuration parameters of DOcplex.CP are based on the class Context, which allows the user to set and retrieve any number of values, that may be atomic or other subcontexts organized as a tree.

An object of the class Context has the following characteristics:

  • It can contain an unlimited number of attributes that can be easily addressed directly with the Python ‘.’ operator.
  • 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.

API configuration

The configuration of DOcplex.CP is described by using a Context that is comprised of the following tree of contexts:

context            # General parameters
   model           # Parameters related to model file generation
   params          # Solving parameters (of type *CpoParameters*, subclass of *Context*)
   solver          # Parameters of the solving in general
      docloud      # Parameters related to solving on DOcplexcloud
      angel        # Parameters related to local solve with CP Optimizer Interactive

The default context of DOcplex.CP is accessible by the variable context, which is initialized in the module config.py.

This module should not be edited directly. Use the procedure in the next section to change configuration parameters.

Change the default configuration

To change configuration values, set them in one of the following Python modules that you can create at any place visible in your PYTHONPATH (for example with your modeling sources):

  • cpo_config.py
  • cpo_config_<your hostname>.py
  • docloud_config.py (also used in DOcplex.MP config, to share the same DOcplexcloud parameters)

If present, these modules are loaded in the order above to overwrite the default configuration values.

To change the value of an attribute of the default configuration, set it using its full path:

context.solver.docloud.url = "..."
context.solver.docloud.key = "..."
context.solver.trace_cpo = True

Note that these files are evaluated inside the config.py context. You must not add any import statement to docplex.cp.config inside them. This is not the case if you want to change a context value outside a configuration file, for example directly in your modeling source code.

Cloud or Local solving

If you have a local installation of CPLEX Optimization Studio with a version greater or equal to 12.7, you can use the service of CP Optimizer Interactive program to solve your model locally instead of using the default solve on DOcplexcloud.

Local solving configuration assumes that the installation 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.

If the CP Optimizer Interactive process is detected visible from the system PATH, the configuration is then automatically switched to local solve by default.

Otherwise, to force local solving, edit the file cpo_config.py to add as first line:

set_default(LOCAL_CONTEXT)

To force remote solving on DOcplexcloud, replace symbol LOCAL_CONTEXT by DOCLOUD_CONTEXT.

If you have some other local settings, you can keep them after this line. They will affect local configuration instead of the ‘cloud’ one.

Display current configuration

To display the configuration that includes all the updates that made by using specialized modules, run the module config.py. 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"
   length_for_alias = 15
   trace_cpo = False
params =
   TimeLimit = 100
   Workers = 4
solver =
   add_log_to_solution = True
   agent = docloud
   enable_undocumented_params = False
   log_prefix = "[Solver] "
   angel =
      class_name = "docplex.cp.solver.solver_angel.CpoSolverAngel"
      execfile = "cpoptimizer.exe"
      log_prefix = "[Angel] "
      parameters = ['-angel']
   docloud =
      always_close_connection = False
      clean_job_after_solve = True
      key = "Put your key here"
      log_prefix = "[DOcloud] "
      request_timeout = 30
      result_wait_extra_time = 60
      secret = None
      url = "https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/"
      verify_ssl = True

Default configuration attributes

The following list gives a detailed definition of the most important configuration parameters. The complete list of parameters is given in the module config.py, which should not be changed directly.

General parameters

context.log_output = sys.stdout

This parameter contains the default log stream. By default it is set to the standard output. A value of None can be used to disable all logs.

context.model.add_source_location = True

This parameter 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. If any error is raised by the solver during the solve, this information is provided in the error description, which allows for easier debugging.

context.model.length_for_alias = 15

This parameter enables the replacement of very long variable names by a shorter alias in the generated CPO format, allowing to reduce the size of the generated CPO file. By default, the value is 15. A value of None would indicate to always keep original variable names.

context.model.dump_directory = None

This parameter gives the name of a directory where the CPO files that are generated for solving models are stored for logging purpose.

If not None, the directory is created and generated models are stored in files named <model_name>.cpo.

context.params.*

The parameter context.params is an instance of the class CpoParameters (in parameters.py), which describes all of the public solver parameters as properties and is an extension of the base class Context.

The default configuration limits the solving time to 100 seconds by using following settings:

context.params.TimeMode = "ElapsedTime"
context.params.TimeLimit = 100

Configuration of the model solving

context.solver.trace_cpo = False

This parameter indicates to trace the CPO model that is generated before submitting it for solving. The model is printed on the context.log_output stream, if given.

context.solver.trace_log = False

This parameter indicates to trace the log generated by the solver when solving the CPO model. The log is printed on the context.log_output stream, if given.

The default value of this parameter is False for a solve on the cloud, and True for a local solve.

context.solver.add_log_to_solution = True

This parameter indicates to add the solver log content to the solution object. By default, this parameter is True but it can be set to False if the log is very big or of no interest.

context.solver.agent = ‘docloud’

This parameter specifies the name of the solver agent that is used to solve the model. The value of this parameter is the name of a child context of context.solver, which contains necessary attributes that allow to create and run the required agent.

There are two different agents described in the default configuration file:
  • docloud, the default agent, for solving a CPO model using the DOcplexcloud service.
  • angel, the agent allowing to solve models locally using the CP Optimizer Interactive coming with versions of COS greater or equal to 12.7.0.

Configuration of the docloud solving agent

context.solver.docloud.url = “https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/”

This parameter is used to specify the URL of the DOcplexcloud service.

context.solver.docloud.key = “‘Set your key in docloud_config.py’”

This parameter contains the personal key for authorizing access to the DOcplexcloud service.

Access credentials (base URL and access key) can be retrieved after registration from http://developer.ibm.com/docloud/docs/api-key/.

These two parameters can be set in the docloud_config.py file.

context.solver.docloud.request_timeout = 30

This parameter contains the maximum time, in seconds, that a response is waited for after a unitary request to DOcplexcloud server.

context.solver.docloud.result_wait_extra_time = 60

This parameter is a time in seconds added to the expected solve time to compute the total result waiting timeout.

context.solver.docloud.clean_job_after_solve = True

This parameter indicates whether the job is automatically cleaned after the model is solved. If not set to True, the model stays on the DOcplexcloud server and is visible from its DropSolve interface. Note that the server may block future solving requests if there are too many jobs waiting.