Source code for cplex.callbacks

# --------------------------------------------------------------------------
# File: callback.py
# ---------------------------------------------------------------------------
# Licensed Materials - Property of IBM
# 5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55 5655-Y21
# Copyright IBM Corporation 2008, 2024. All Rights Reserved.
#
# US Government Users Restricted Rights - Use, duplication or
# disclosure restricted by GSA ADP Schedule Contract with
# IBM Corp.
# --------------------------------------------------------------------------
"""Callback classes for the CPLEX Python API.

This module defines the `Context` class which provides methods to query
information and perform all actions that can be performed from a generic
callback. See `Cplex.set_callback()`.

See the following examples for more information on generic callbacks:

 * admipex8.py
 * admipex9.py
 * bendersatsp2.py

This module also defines a hierarchy of classes for legacy callbacks,
many of which can be subclassed to define alternative behavior for the
algorithms in CPLEX. The methods provided by the classes allow you to
query information about the optimization in progress and to control the
optimization algorithm.

To use a subclassable legacy callback class, define a subclass of it that
defines a __call__ method. Then pass that class name to the
`Cplex.register_callback` method. The `Cplex` class will instantiate the
legacy callback class and return the instance of the class that was
created. If you wish to associate additional data with your callback
class, you may do so after it has been registered with the `Cplex` object.

See the following examples for more information on legacy callbacks:

 * admipex1.py
 * admipex2.py
 * admipex3.py
 * admipex5.py
 * admipex6.py
 * bendersatsp.py
 * lpex4.py
 * mipex4.py

Note
  If you operate the CPLEX Python API in parallel mode with callbacks
  registered, there will be a single instance of each of your callback
  classes shared among all threads.
"""
import weakref

from . import _internal
from ._internal._callbackinfoenum import CallbackInfo
from ._internal import _constants as _const
from ._internal._solutionstrategyenum import SolutionStrategy
from ._internal import _procedural as _proc
from ._internal._aux_functions import (apply_freeform_two_args,
                                       apply_freeform_one_arg,
                                       init_list_args, convert, max_arg_length,
                                       validate_arg_lengths, unzip)
from ._internal._matrices import SparsePair, _HBMatrix, unpack_pair
from ._internal._subinterfaces import SolutionStatus
from ._internal import _pycplex
from .exceptions import (CplexError, CplexSolverError,
                         WrongNumberOfArgumentsError)
from .constant_class import ConstantClass
from .exceptions.error_codes import CPXERR_UNSUPPORTED_OPERATION

[docs] class Callback(): """Base class for Cplex callback classes. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" self._env = weakref.proxy(env) self._cb_type_string = None self._cb_set_function = None self._cbstruct = None self._env_lp_ptr = None self._status = 0
[docs] def __call__(self): """Method to be overridden by user-defined callback class. See the `Cplex.register_callback` method. """ raise CplexError("Callback.__call__ is a pure virtual method")
def _conv_col(self, name, cache=None): return convert(name, self._get_col_index, cache) def _get_col_index(self, name): """non-public""" status = _pycplex.cb_getcolindex( self._cbstruct, self._env_lp_ptr, name) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1] def _conv_row(self, name, cache=None): return convert(name, self._get_row_index, cache) def _get_row_index(self, name): """non-public""" status = _pycplex.cb_getrowindex( self._cbstruct, self._env_lp_ptr, name) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1] def _conv_quad(self, name, cache=None): return convert(name, self._get_quad_index, cache) def _get_quad_index(self, name): """non-public""" status = _pycplex.cb_getqconstrindex( self._cbstruct, self._env_lp_ptr, name) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1] def _conv_sos(self, name, cache=None): return convert(name, self._get_sos_index, cache) def _get_sos_index(self, name): """non-public""" status = _pycplex.cb_getsosindex( self._cbstruct, self._env_lp_ptr, name) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1]
[docs] def abort(self): """Terminates optimization. Note A call to abort should be followed immediately by a return statement. Calling other callback methods may override the effect of the abort statement. """ if hasattr(self, "_useraction"): self._useraction = _const.CPX_CALLBACK_FAIL self._status = 0 else: self._status = 1
[docs] def get_end_time(self): """Returns a time stamp for computing the time limit. Subtracting the return value of Callback.get_time() from the return value of this method yields the time remaining in seconds. The interpretation of this value as wall clock time or CPU time is controlled by the parameter clocktype. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_ENDTIME, CplexSolverError)
[docs] def get_end_dettime(self): """Returns a deterministic time stamp in ticks. Subtracting the return value of Callback.get_dettime() from the return value of this method yields the time remaining in deterministic ticks. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_ENDDETTIME, CplexSolverError)
[docs] def get_start_time(self): """Returns a time stamp specifying when the solving process started. To compute elapsed time in seconds, subtract the result of Callback.get_time() from the result of this method. This computation yields either wallclock time (also known as real time) or CPU time, depending on the clock set by the clocktype parameter. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_STARTTIME, CplexSolverError)
[docs] def get_start_dettime(self): """Returns a deterministic time stamp specifying when the solving process started. To compute elapsed deterministic time in ticks, subtract the result of Callback.get_dettime() from the result of this method. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_STARTDETTIME, CplexSolverError)
[docs] def get_time(self): """Returns a time stamp for the current time. Subtracting the return value of this method from the return value of Callback.get_end_time() yields the time remaining in seconds. The interpretation of this value as wall clock time or CPU time is controlled by the parameter clocktype. """ status = _pycplex.cb_gettime(self._cbstruct) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1]
[docs] def get_dettime(self): """Returns a deterministic time stamp for the current time. Subtracting the return value of this method from the return value of Callback.get_end_dettime() yields the time remaining in deterministic ticks. """ status = _pycplex.cb_getdettime(self._cbstruct) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1]
[docs] class OptimizationCallback(Callback): """Base class for Cplex optimization callback classes.""" def _wherefrom(self): """non-public""" return _proc.get_wherefrom(self._cbstruct)
[docs] def get_num_quadratic_constraints(self): """Returns the number of quadratic constraints in the problem.""" return _pycplex.cb_getnumqconstrs(self._cbstruct, self._env_lp_ptr)
[docs] def get_num_cols(self): """Returns the number of variables in the problem.""" return _pycplex.cb_getnumcols(self._cbstruct, self._env_lp_ptr)
[docs] def get_num_rows(self): """Returns the number of linear constraints in the problem.""" return _pycplex.cb_getnumrows(self._cbstruct, self._env_lp_ptr)
[docs] class ContinuousCallback(OptimizationCallback): """Subclassable class for Cplex continuous callbacks. When Cplex is solving a problem by a simplex or barrier method, this callback will be called after the simplex or barrier callback, if any such callbacks are registered. During concurrent optimization, CPLEX calls the user-written callback only in the main thread, not in other concurrent threads. :undocumented: __init__, _wherefrom """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "continuous" self._cb_set_function = _proc.setlpcallbackfunc
[docs] def get_dual_infeasibility(self): """Returns a measure of the dual infeasibility of the problem.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_DUAL_INFMEAS, CplexSolverError)
[docs] def get_primal_infeasibility(self): """Returns a measure of the primal infeasibility of the problem.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_PRIMAL_INFMEAS, CplexSolverError)
[docs] def get_num_iterations(self): """Returns the number of iterations completed.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_ITCOUNT_LONG, CplexSolverError)
[docs] def get_objective_value(self): """Returns the current value of the objective function.""" if self._wherefrom() == _const.CPX_CALLBACK_DUAL: return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_DUAL_OBJ, CplexSolverError) return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_PRIMAL_OBJ, CplexSolverError)
[docs] def is_dual_feasible(self): """Returns whether or not the current solution is dual feasible.""" if self._wherefrom() == _const.CPX_CALLBACK_PRIMAL: return self.get_dual_infeasibility() <= 0.0 return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_DUAL_FEAS, CplexSolverError)
[docs] def is_primal_feasible(self): """Returns whether or not the current solution is primal feasible.""" if self._wherefrom() == _const.CPX_CALLBACK_DUAL: return self.get_primal_infeasibility() <= 0.0 return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_PRIMAL_FEAS, CplexSolverError)
[docs] class SimplexCallback(ContinuousCallback): """Subclassable class for Cplex simplex callback classes. This callback will be used during execution of the primal simplex, dual simplex, or quadratic simplex algorithms. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "simplex"
[docs] class BarrierCallback(ContinuousCallback): """Subclassable class for Cplex barrier callback classes. This callback will be used during execution of the barrier or quadratic barrier algorithms. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "barrier"
[docs] def get_dual_objective_value(self): """Returns the current dual value of the objective function.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_DUAL_OBJ, CplexSolverError)
[docs] class CrossoverCallback(OptimizationCallback): """Subclassable class for Cplex crossover callback classes. This callback will be used during execution of a crossover algorithm. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "crossover" self._cb_set_function = _proc.setlpcallbackfunc
[docs] def get_num_dual_exchanges(self): """Returns the number of dual exchanges performed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CROSSOVER_DEXCH_LONG, CplexSolverError)
[docs] def get_num_dual_pushes(self): """Returns the number of dual pushes performed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CROSSOVER_DPUSH_LONG, CplexSolverError)
[docs] def get_num_primal_exchanges(self): """Returns the number of primal exchanges performed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CROSSOVER_PEXCH_LONG, CplexSolverError)
[docs] def get_num_primal_pushes(self): """Returns the number of primal pushes performed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CROSSOVER_PPUSH_LONG, CplexSolverError)
[docs] def get_num_superbasics(self): """Returns the number of superbasic variables in the basis.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CROSSOVER_SBCNT, CplexSolverError)
[docs] class QualityMetric(ConstantClass): """Constants defining metrics for the quality of the MIP solve.""" kappa_stable = _const.CPX_CALLBACK_INFO_KAPPA_STABLE kappa_suspicious = _const.CPX_CALLBACK_INFO_KAPPA_SUSPICIOUS kappa_unstable = _const.CPX_CALLBACK_INFO_KAPPA_UNSTABLE kappa_illposed = _const.CPX_CALLBACK_INFO_KAPPA_ILLPOSED kappa_max = _const.CPX_CALLBACK_INFO_KAPPA_MAX kappa_attention = _const.CPX_CALLBACK_INFO_KAPPA_ATTENTION
[docs] class CutType(ConstantClass): """Arguments to MIPInfoCallback.get_num_cuts().""" # NB: If you edit these, look at _subinterfaces.py:CutType too! # Also add the cut to the list of valid values get_num_cuts()! cover = _const.CPX_CALLBACK_INFO_COVER_COUNT GUB_cover = _const.CPX_CALLBACK_INFO_GUBCOVER_COUNT flow_cover = _const.CPX_CALLBACK_INFO_FLOWCOVER_COUNT clique = _const.CPX_CALLBACK_INFO_CLIQUE_COUNT fractional = _const.CPX_CALLBACK_INFO_FRACCUT_COUNT MIR = _const.CPX_CALLBACK_INFO_MIRCUT_COUNT flow_path = _const.CPX_CALLBACK_INFO_FLOWPATH_COUNT disjunctive = _const.CPX_CALLBACK_INFO_DISJCUT_COUNT implied_bound = _const.CPX_CALLBACK_INFO_IMPLBD_COUNT zero_half = _const.CPX_CALLBACK_INFO_ZEROHALFCUT_COUNT multi_commodity_flow = _const.CPX_CALLBACK_INFO_MCFCUT_COUNT lift_and_project = _const.CPX_CALLBACK_INFO_LANDPCUT_COUNT user = _const.CPX_CALLBACK_INFO_USERCUT_COUNT table = _const.CPX_CALLBACK_INFO_TABLECUT_COUNT solution_pool = _const.CPX_CALLBACK_INFO_SOLNPOOLCUT_COUNT benders = _const.CPX_CALLBACK_INFO_BENDERS_COUNT
# Not Implemented: # local_implied_bound # BQP # RLT
[docs] class MIPInfoCallback(OptimizationCallback): """Subclassable class for MIP informational callback classes. This callback will be used when CPLEX is solving a MIP problem. :undocumented: __init__ """ quality_metric = QualityMetric() """See `QualityMetric()`""" cut_type = CutType() """See `CutType()`"""
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "MIP_info" self._cb_set_function = _proc.setinfocallbackfunc
def _setup(self, e, lp): self.num_cols = _proc.getnumcols(e, lp) self.num_rows = _proc.getnumrows(e, lp) self.num_quad = _proc.getnumqconstrs(e, lp)
[docs] def get_num_cols(self): return self.num_cols
[docs] def get_num_rows(self): return self.num_rows
[docs] def get_num_quadratic_constraints(self): return self.num_quad
[docs] def get_num_cuts(self, cut_type): """Returns the number of cuts of type cut_type added so far.""" if cut_type not in (_const.CPX_CALLBACK_INFO_COVER_COUNT, _const.CPX_CALLBACK_INFO_GUBCOVER_COUNT, _const.CPX_CALLBACK_INFO_FLOWCOVER_COUNT, _const.CPX_CALLBACK_INFO_CLIQUE_COUNT, _const.CPX_CALLBACK_INFO_FRACCUT_COUNT, _const.CPX_CALLBACK_INFO_MIRCUT_COUNT, _const.CPX_CALLBACK_INFO_FLOWPATH_COUNT, _const.CPX_CALLBACK_INFO_DISJCUT_COUNT, _const.CPX_CALLBACK_INFO_IMPLBD_COUNT, _const.CPX_CALLBACK_INFO_ZEROHALFCUT_COUNT, _const.CPX_CALLBACK_INFO_MCFCUT_COUNT, _const.CPX_CALLBACK_INFO_LANDPCUT_COUNT, _const.CPX_CALLBACK_INFO_USERCUT_COUNT, _const.CPX_CALLBACK_INFO_TABLECUT_COUNT, _const.CPX_CALLBACK_INFO_SOLNPOOLCUT_COUNT, _const.CPX_CALLBACK_INFO_BENDERS_COUNT): raise ValueError("invalid value for cut_type ({0})".format(cut_type)) return _pycplex.fast_getcallbackinfo(self._cbstruct, cut_type, CplexSolverError)
[docs] def get_best_objective_value(self): """Returns the best objective value among unexplored nodes.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_BEST_REMAINING, CplexSolverError)
[docs] def get_cutoff(self): """Returns the current cutoff value.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_CUTOFF, CplexSolverError)
[docs] def get_incumbent_objective_value(self): """Returns the objective value of the incumbent solution.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_BEST_INTEGER, CplexSolverError)
[docs] def get_incumbent_linear_slacks(self, *args): """Returns a set of linear slacks for the incumbent solution. Can be called by four forms. self.get_incumbent_linear_slacks() return all linear slack values from the incumbent solution. self.get_incumbent_linear_slacks(i) i must be a linear constraint name or index. Returns the slack values associated with the linear constraint whose index or name is i. self.get_incumbent_linear_slacks(s) s must be a sequence of linear constraint names or indices. Returns the slack values associated with the linear constraints with indices the members of s. Equivalent to [self.get_incumbent_linear_slacks(i) for i in s] self.get_incumbent_linear_slacks(begin, end) begin and end must be linear constraint indices with begin <= end or linear constraint names whose indices respect this order. Returns the slack values associated with the linear constraints with indices between begin and end, inclusive of end. Equivalent to self.get_incumbent_linear_slacks(range(begin, end + 1)). """ values = [x for x in iter(self.get_incumbent_values())] status = _pycplex.cb_slackfromx(self._cbstruct, self._env_lp_ptr, values) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.num_rows - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_row, args)
[docs] def get_incumbent_quadratic_slacks(self, *args): """Returns a set of quadratic slacks for the incumbent solution. Can be called by four forms. self.get_incumbent_quadratic_slacks() return all quadratic slack values from the incumbent solution. self.get_incumbent_quadratic_slacks(i) i must be a quadratic constraint name or index. Returns the slack values associated with the quadratic constraint whose index or name is i. self.get_incumbent_quadratic_slacks(s) s must be a sequence of quadratic constraint names or indices. Returns the slack values associated with the quadratic constraints with indices the members of s. Equivalent to [self.get_incumbent_quadratic_slacks(i) for i in s] self.get_incumbent_quadratic_slacks(begin, end) begin and end must be quadratic constraint indices or quadratic constraint names. Returns the slack values associated with the quadratic constraints with indices between begin and end, inclusive of end. Equivalent to self.get_incumbent_quadratic_slacks(range(begin, end + 1)). """ values = [x for x in iter(self.get_incumbent_values())] status = _pycplex.cb_qconstrslackfromx(self._cbstruct, self._env_lp_ptr, values) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.num_quad - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_quad, args)
[docs] def get_incumbent_values(self, *args): """Returns the variable values of the incumbent solution. There are four forms by which get_incumbent_values may be called. self.get_incumbent_values() returns the entire incumbent solution self.get_incumbent_values(i) i must be a variable index or name. Returns the value of the variable with index i in the incumbent solution. self.get_incumbent_values(s) s must be a sequence of variables indices or names. Returns a list of the values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_incumbent_values(i) for i in s] self.get_incumbent_values(begin, end) begin and end must be variable indices or variable names. Returns a list of the values of the variables with indices between begin and end, inclusive of end. Equivalent to self.get_incumbent_values(range(begin, end + 1)) """ def getcallbackincumbent(begin, end=self.num_cols - 1): return _proc.getcallbackincumbent(self._cbstruct, begin, end) return apply_freeform_two_args( getcallbackincumbent, self._conv_col, args)
[docs] def get_MIP_relative_gap(self): """Returns the current relative MIP gap. Accesses the current relative gap, like the routine CPXgetmiprelgap in the Callable Library. See CPXgetcallbackinfo and CPXgetmiprelgap in the Callable Library Reference Manual for more detail. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_MIP_REL_GAP, CplexSolverError)
[docs] def get_num_iterations(self): """Returns the number of iterations performed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_MIP_ITERATIONS_LONG, CplexSolverError)
[docs] def get_num_nodes(self): """Returns the number of nodes processed so far.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_NODE_COUNT_LONG, CplexSolverError)
[docs] def get_num_remaining_nodes(self): """Returns the number of unexplored nodes remaining.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_NODES_LEFT_LONG, CplexSolverError)
[docs] def has_incumbent(self): """Returns whether or not an incumbent solution has been found.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_MIP_FEAS, CplexSolverError)
[docs] def get_float_quality(self, which): """Returns a measure of the quality of the MIP solution as a floating point value. The measure of the quality of a solution must be an attribute of self.quality_metric corresponding to a floating point quality metric. Such metrics include: self.quality_metric.kappa_stable self.quality_metric.kappa_suspicious self.quality_metric.kappa_unstable self.quality_metric.kappa_illposed self.quality_metric.kappa_max self.quality_metric.kappa_attention """ if which not in (_const.CPX_CALLBACK_INFO_KAPPA_STABLE, _const.CPX_CALLBACK_INFO_KAPPA_SUSPICIOUS, _const.CPX_CALLBACK_INFO_KAPPA_UNSTABLE, _const.CPX_CALLBACK_INFO_KAPPA_ILLPOSED, _const.CPX_CALLBACK_INFO_KAPPA_MAX, _const.CPX_CALLBACK_INFO_KAPPA_ATTENTION): raise ValueError("invalid value for which ({0})".format(which)) return _pycplex.fast_getcallbackinfo(self._cbstruct, which, CplexSolverError)
[docs] def get_thread_num(self): """Returns the identifier for the thread from which the callback was invoked. See CPX_CALLBACK_INFO_MY_THREAD_NUM in the Callable Library Reference Manual for more detail. """ return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_MY_THREAD_NUM, CplexSolverError)
[docs] class MIPCallback(MIPInfoCallback): """Subclassable class for MIP callback classes. This callback will be used when CPLEX is solving a MIP problem. :undocumented: __init__, _get_node_info, _get_seq_info """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "MIP" self._cb_set_function = _proc.setmipcallbackfunc
def _get_node_info(self, which_info, which_node): """non-public""" return _proc.getcallbacknodeinfo(self._cbstruct, which_node, which_info) def _get_seq_info(self, which_info, which_node): """non-public""" return _proc.getcallbackseqinfo(self._cbstruct, which_node, which_info)
[docs] def get_objective_coefficients(self, *args): """Returns the coefficients of the linear objective function. There are four forms by which get_objective_coefficients may be called. self.get_objective_coefficients() returns the entire objective function. self.get_objective_coefficients(i) i must be a variable index or name. Returns the objective coefficient of the variable with index i. self.get_objective_coefficients(s) s must be a sequence of variable indices or names. Returns a list of the objective coefficients of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_objective_coefficients(i) for i in s] self.get_objective_coefficients(begin, end) begin and end must be variable indices or variable names. Returns a list of the objective coefficients of variables with indices between begin and end, inclusive of end. Equivalent to self.get_objective_coefficients(range(begin, end + 1)) """ def getobj(begin, end=self.get_num_cols() - 1): status = _pycplex.cb_getobj(self._cbstruct, self._env_lp_ptr, begin, end) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1] return apply_freeform_two_args( getobj, self._conv_col, args)
[docs] def get_current_node_depth(self): """Returns the depth of the current node in the search tree. The root node has depth 0 (zero). The depth of other nodes is defined recursively as the depth of their parent node plus one. In other words, the depth of a node is its distance in terms of the number of branches from the root. """ # The function is called get_current_node_depth() rather than # get_depth() (as in the other APIs) for historical reasons: # when we introduced the function we already had a get_depth() # function in the node callback which had a different signature and # different semantics return _proc.getcallbacknodeinfo( self._cbstruct, 0, _const.CPX_CALLBACK_INFO_NODE_DEPTH_LONG)
[docs] class FeasibilityStatus(ConstantClass): """Feasibility status codes.""" feasible = _const.CPX_INTEGER_FEASIBLE implied_feasible = _const.CPX_IMPLIED_INTEGER_FEASIBLE infeasible = _const.CPX_INTEGER_INFEASIBLE
[docs] class ControlCallback(MIPCallback): """Base class for Cplex MIP control callback classes. :undocumented: __init__ """ feasibility_status = FeasibilityStatus() """See `FeasibilityStatus()`"""
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._useraction = _const.CPX_CALLBACK_DEFAULT
[docs] def get_pseudo_costs(self, *args): """Returns the current pseudo costs. There are four forms by which get_pseudo_costs may be called. self.get_pseudo_costs() returns a list of pairs with the pseudo costs for all the variables. self.get_pseudo_costs(i) i must be a variable index or name. Returns a pair (up, down), where up is the up pseudo cost and down is the down pseudo cost of branching on the variable i. self.get_pseudo_costs(s) s must be a sequence of variable indices or names. Returns a list of pairs (up, down) of pseudo costs of branching on the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_pseudo_costs(i) for i in s] self.get_pseudo_costs(begin, end) begin and end must be variable indices or variable names. Returns a list of pairs (up, down) of pseudo costs of branching on the variables with indices between begin and end, inclusive of end. Equivalent to self.get_pseudo_costs(range(begin, end + 1)) """ def getcallbackpseudocosts(begin, end=self.get_num_cols() - 1): return unzip(_proc.getcallbackpseudocosts(self._cbstruct, begin, end)) return apply_freeform_two_args( getcallbackpseudocosts, self._conv_col, args)
[docs] def get_feasibilities(self, *args): """Returns the current integer feasibility status. There are four forms by which get_feasibilities may be called. The return values are attributes of self.feasibility_status. self.get_feasibilities() returns a list with the feasibility status for all the variables. self.get_feasibilities(i) i must be a variable index or name. Returns the feasibility status of the variable with index i. self.get_feasibilities(s) s must be a sequence of variable indices or names. Returns a list of the feasibility statuses of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_feasibilities(i) for i in s] self.get_feasibilities(begin, end) begin and end must be variable indices or variable names. Returns a list of the feasibility statuses of the variables with indices between begin and end, inclusive of end. Equivalent to self.get_feasibilities(range(begin, end + 1)) Note Before you call this method from a solve callback, a solution must exist. That is, you must first create the solution by calling a CPLEX optimization method, and then you must verify that this optimization method generated a solution by checking its return value before you call the method get_feasibilities. """ def getcallbacknodeintfeas(begin, end=self.get_num_cols() - 1): return _proc.getcallbacknodeintfeas(self._cbstruct, begin, end) return apply_freeform_two_args( getcallbacknodeintfeas, self._conv_col, args)
[docs] def get_lower_bounds(self, *args): """Returns the lower bounds at the current node. There are four forms by which get_lower_bounds may be called. self.get_lower_bounds() returns a list with the lower bounds for all the variables. self.get_lower_bounds(i) i must be a variable index or name. Returns the lower bound of the variable with index i. self.get_lower_bounds(s) s must be a sequence of variable indices or names. Returns a list of the lower bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_lower_bounds(i) for i in s] self.get_lower_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the lower bounds of the variables with indices between begin and end, inclusive of end. Equivalent to self.get_lower_bounds(range(begin, end + 1)) """ def getcallbacknodelb(begin, end=self.get_num_cols() - 1): return _proc.getcallbacknodelb(self._cbstruct, begin, end) return apply_freeform_two_args( getcallbacknodelb, self._conv_col, args)
[docs] def get_upper_bounds(self, *args): """Returns the upper bounds at the current node. There are four forms by which get_upper_bounds may be called. self.get_upper_bounds() returns a list with the upper bounds for all the variables. self.get_upper_bounds(i) i must be a variable index or name. Returns the upper bound of the variable with index i. self.get_upper_bounds(s) s must be a sequence of variable indices or names. Returns a list of the upper bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_upper_bounds(i) for i in s] self.get_upper_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the upper bounds of the variables with indices between begin and end, inclusive of end. Equivalent to self.get_upper_bounds(range(begin, end + 1)) """ def getcallbacknodeub(begin, end=self.get_num_cols() - 1): return _proc.getcallbacknodeub(self._cbstruct, begin, end) return apply_freeform_two_args( getcallbacknodeub, self._conv_col, args)
[docs] def get_node_data(self): """Returns the user handle for the current node. Returns None if no handle is set for the node. """ return self._get_node_info( _const.CPX_CALLBACK_INFO_NODE_USERHANDLE, 0)
[docs] def set_node_data(self, data): """Set the user handle for the current node. Returns the user handle previously set for this node (or None if no handle was set). """ return _proc.callbacksetuserhandle(self._cbstruct, data)
[docs] def get_node_ID(self): """Return the sequence number of this node.""" return self._get_node_info( _const.CPX_CALLBACK_INFO_NODE_SEQNUM_LONG, 0)
[docs] def get_objective_value(self): """Returns the value of the objective function at the current node.""" return _proc.getcallbacknodeobjval(self._cbstruct)
[docs] def get_linear_slacks(self, *args): """Returns a set of linear slacks for the solution at the current node. Can be called by four forms. self.get_linear_slacks() return all linear slack values from the problem at the current node. self.get_linear_slacks(i) i must be a linear constraint name or index. Returns the slack values associated with the linear constraint whose index or name is i. self.get_linear_slacks(s) s must be a sequence of linear constraint names or indices. Returns the slack values associated with the linear constraints with indices the members of s. Equivalent to [self.get_linear_slacks(i) for i in s] self.get_linear_slacks(begin, end) begin and end must be linear constraint indices with begin <= end or linear constraint names whose indices respect this order. Returns the slack values associated with the linear constraints with indices between begin and end, inclusive of end. Equivalent to self.get_linear_slacks(range(begin, end + 1)). """ values = [x for x in iter(self.get_values())] status = _pycplex.cb_slackfromx( self._cbstruct, self._env_lp_ptr, values) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.get_num_rows() - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_row, args)
[docs] def get_quadratic_slacks(self, *args): """Returns a set of quadratic slacks for the solution at the current node. Can be called by four forms. self.get_quadratic_slacks() return all quadratic slack values from the problem at the current node. self.get_quadratic_slacks(i) i must be a quadratic constraint name or index. Returns the slack values associated with the quadratic constraint whose index or name is i. self.get_quadratic_slacks(s) s must be a sequence of quadratic constraint names or indices. Returns the slack values associated with the quadratic constraints with indices the members of s. Equivalent to [self.get_quadratic_slacks(i) for i in s] self.get_quadratic_slacks(begin, end) begin and end must be quadratic constraint indices or quadratic constraint names. Returns the slack values associated with the quadratic constraints with indices between begin and end, inclusive of end. Equivalent to self.get_quadratic_slacks(range(begin, end + 1)). """ values = [x for x in iter(self.get_values())] status = _pycplex.cb_qconstrslackfromx(self._cbstruct, self._env_lp_ptr, values) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.get_num_quadratic_constraints() - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_quad, args)
[docs] def get_values(self, *args): """Returns the solution values at the current node. In the case that the node LP is unbounded, this method returns a vector that corresponds to an unbounded direction, scaled so that at least one of its elements has magnitude cplex.infinity. Thus, often the vector can be used directly, for example to separate a lazy constraint. However, due to the presence of large values in the vector care must be taken to avoid potential numerical errors. If in doubt, rescale the vector, and use it as an unbounded ray rather than a primal vector. There are four forms by which get_values may be called. self.get_values() returns the entire primal solution vector. self.get_values(i) i must be a variable index or name. Returns the solution value of the variable with index i. self.get_values(s) s must be a sequence of variable indices or names. Returns a list of the solution values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_values(i) for i in s] self.get_values(begin, end) begin and end must be variable indices or variable names. Returns a list of the solution values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_values(range(begin, end + 1)) """ def getcallbacknodex(begin, end=self.get_num_cols() - 1): return _proc.getcallbacknodex(self._cbstruct, begin, end) return apply_freeform_two_args( getcallbacknodex, self._conv_col, args)
[docs] def get_SOS_feasibilities(self, *args): """Returns the current special ordered set feasibility status. There are four forms by which get_SOS_feasibilities may be called. Returns 1 if the SOS is feasible and 0 if it is not. self.get_SOS_feasibilties() Returns the feasibility statuses of all the special ordered sets. self.get_SOS_feasibilities(i) i must be a SOS index or name. Returns the feasibility status of the special ordered set with index i. self.get_SOS_feasibilities(s) s must be a sequence of SOS indices or names. Returns a list of the feasibility statuses of the special ordered sets with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_SOS_feasibilities(i) for i in s] self.get_SOS_feasibilities(begin, end) begin and end must be SOS indices or SOS names. Returns a list of the feasibility statuses of the special ordered sets with indices between begin and end, inclusive of end. Equivalent to self.get_SOS_feasibilities(range(begin, end + 1)) Note Before you call this method from a solve callback, a solution must exist. That is, you must first create the solution by calling a CPLEX optimization method, and then you must verify that this optimization method generated a solution by checking its return value before you call the method get_SOS_feasibilities. """ def is_sos_feasible(index): return _proc.getcallbacksosinfo( self._cbstruct, index, 0, _const.CPX_CALLBACK_INFO_SOS_IS_FEASIBLE) return apply_freeform_one_arg( is_sos_feasible, self._conv_sos, _pycplex.cb_getnumsos(self._cbstruct, self._env_lp_ptr), args)
[docs] class BranchType(ConstantClass): """Constants defining types of branches.""" any = _const.CPX_TYPE_ANY SOS1 = _const.CPX_TYPE_SOS1 SOS2 = _const.CPX_TYPE_SOS2 variable = _const.CPX_TYPE_VAR
[docs] class BranchCallback(ControlCallback): """Subclassable class for branch callback classes. The user must be careful when using this class. Pruning a valid node can prune the optimal solution. Also, choosing a different branching variable can result in placing an invalid bound on a variable, in case the variable was already restricted to other bounds before. In particular, the user must not branch on variables that are implied feasible. ImpliedFeasible specifies that the variable has been presolved out. It will be feasible when all other integer variables are integer feasible. Branching on such variables can cut off feasible solutions. If the user intends to branch on continuous variables, the user must disable dual presolve reductions. To disable dual presolve reductions, set the parameter to control primal and dual reduction type, parameters.preprocessing.reduce, either to the value 1 (one) (that is, CPX_PREREDUCE_PRIMALONLY) or to the value 0 (zero) (that is, CPX_PREREDUCE_NOPRIMALORDUAL). Also, if the user intends to branch on continuous variables, the user must turn off reductions that may interfere with crushing or uncrushing. This is parameters.preprocessing.reformulations. By design, the CPLEX branch callback calculates and provides the branching decisions that CPLEX would make in case the user does not create any branches in the callback. Depending on variable selection and other features of your model, the computation of these candidate branches can be time-consuming. Consequently, if you know that you will never use the branching candidates suggested by CPLEX, then you can save time by disabling such features as strong branching. This callback will be used prior to branching at a node in the branch and cut tree. :undocumented: __init__ """ branch_type = BranchType() """See `BranchType()`"""
[docs] def __init__(self, env): """non-public""" def fn(a, b): _proc.setbranchcallbackfunc(a, b) _proc.setbranchnosolncallbackfunc(a, b) super().__init__(env) self._cb_type_string = "branch" self._cb_set_function = fn self._node_count = 0 self._bound_count = 0 self._node_begin = [] self._index = [] self._bounds = [] self._lu = [] self._node_estimate = [] self._branch_type = None
[docs] def get_branch(self, which_branch): """Returns one of the candidate branches at the current node. which_branch must be an integer such that 0 <= which_branch < self.get_num_branches(). The return value of get_branch is a tuple t with two entries. The first entry is the node estimate for the specified branch. The second entry is a list of (var, dir, bnd) triples, where var is the index of a variable whose bound will change, bnd is the new bound, and dir is one of "L", "U", and "B", indicating that the branch will change the lower bound, the upper bound, or both, respectively. """ if which_branch < 0 or which_branch >= self._node_count: raise CplexError("BranchCallback.get_branch: Bad branch index") if which_branch == self._node_count - 1: end = self._bound_count else: end = self._node_begin[which_branch + 1] vars = self._index[self._node_begin[which_branch]: end] bnds = self._bounds[self._node_begin[which_branch]: end] dirs = self._lu[self._node_begin[which_branch]: end] return (self._node_estimate[which_branch], list(zip(vars, dirs, bnds)))
[docs] def get_branch_type(self): """Returns the type of the branch. One of the following: self.branch_type.any self.branch_type.SOS1 self.branch_type.SOS2 self.branch_type.variable """ return self._branch_type
[docs] def get_num_branches(self): """Return the number of nodes Cplex will create from this branch.""" return self._node_count
[docs] def is_integer_feasible(self): """Return whether or not the current node is integer feasible.""" return self.get_num_branches() == 0
[docs] def make_branch(self, objective_estimate, variables=None, constraints=None, node_data=None): """Makes a new branch with the specified data. objective_estimate is a float representing the estimated objective value resulting from the specified branch. variables is a sequence of (var, dir, bnd) triples specifying the variables on which to branch. var must be an index of a variable, dir must be one of "L" and "U", indicating that the bound is a lower or upper bound, respectively, and bnd is an integer specifying the new bound for the variable. constraints is a sequence of (vec, sense, rhs) triples specifying the constraints with which to branch. vec must be either an instance of SparsePair or a sequence with two entries, the first of which specifies the indices and the second of which specifies the values of the constraint. rhs must be a float determining the righthand side of the constraint. sense must be one of "L", "G", or "E", specifying whether the constraint is a less-than-or-equal-to (<=), greater-than-or-equal-to (>=), or equality constraint. node_data may be any object to be associated with the created node. It can be queried by the get_node_data methods of the IncumbentCallback class and the NodeCallback class. """ variables, constraints = init_list_args(variables, constraints) obje = objective_estimate if variables: a = unzip(variables) else: a = [[], [], []] vars = list(a[0]) dirs = ''.join(list(a[1])) bnds = list(a[2]) if constraints: a = unzip(constraints) else: a = [[], [], []] rmat = _HBMatrix(a[0]) sense = ''.join(list(a[1])) rhs = list(a[2]) seqnum = _proc.branchcallbackbranchgeneral( self._cbstruct, vars, dirs, bnds, rhs, sense, rmat.matbeg, rmat.matind, rmat.matval, obje, node_data) self._useraction = _const.CPX_CALLBACK_SET return (seqnum,)
[docs] def make_cplex_branch(self, which_branch, node_data=None): """Replicates a CPLEX branch. This method replicates the n-th branch that CPLEX would create at the current node. The purpose of this method is to branch exactly like CPLEX, but at the same time attach a user object to the newly created node. which_branch must be an integer such that 0 <= which_branch < self.get_num_branches(). node_data may be any object to be associated with the created node. It can be queried by the get_node_data methods of various callback classes. This method returns the sequence number of the newly created node. """ seqnum = _proc.branchcallbackbranchasCPLEX(self._cbstruct, which_branch, node_data) self._useraction = _const.CPX_CALLBACK_SET return seqnum
[docs] def prune(self): """Removes the current node from the search tree. Note Prune must not be called in combination with make_branch. Prune is not compatible with the populate_solution_pool method of the Cplex class because that method retains fathomed nodes for subsequent use. """ self._useraction = _const.CPX_CALLBACK_SET self._status = 0
[docs] class CutCallback(ControlCallback): """Base class for user cut and lazy constraint callback classes. :undocumented: add, add_local """ def _add(self, cut, sense, rhs, use_cut): """non-public""" indices, values = unpack_pair(cut) if use_cut is True: use_cut = 1 if use_cut is False: use_cut = 0 _proc.cutcallbackadd( self._cbstruct, rhs, sense, self._conv_col(indices), values, use_cut) def _add_local(self, cut, sense, rhs): """non-public""" indices, values = unpack_pair(cut) _proc.cutcallbackaddlocal( self._cbstruct, rhs, sense, self._conv_col(indices), values)
[docs] class UseConstraint(ConstantClass): """Constants to specify when to use the added constraint""" force = _const.CPX_USECUT_FORCE purge = _const.CPX_USECUT_PURGE
[docs] class LazyConstraintCallback(CutCallback): """Subclassable class for lazy constraint callback classes. This callback will be used when CPLEX finds a new integer feasible solution and when CPLEX finds that the LP relaxation at the current node is unbounded. Note: The lazy constraint callback may be invoked during MIP start processing. In that case get_solution_source returns mip_start_solution. When this value is returned some special considerations apply: - MIP start processing occurs very early in the solution process. At this point no search tree is setup yet and there are no search tree nodes yet. Consequently, a lot of the callback methods that require a node context will fail in this situation. - Lazy constraints separated when processing a MIP start will be discarded after that MIP start has been processed. This means that the callback may have to separate the same constraint again for the next MIP start or for a solution that is found later in the solution process. :undocumented: __init__ """ use_constraint = UseConstraint() """See `UseConstraint()`"""
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "lazycon" self._cb_set_function = _proc.setlazyconstraintcallbackfunc
[docs] def add(self, constraint, sense, rhs, use=use_constraint.force): """Adds a linear constraint to the current subproblem. constraint must be either a SparsePair instance or a list of two lists, the first of which specifies variables, the second of which specifies the values of the constraint. sense must be a single-character string; ("L", "G", "E") rhs is a float, specifying the righthand side of the constraint. use indicates under what circumstances the constraint should be used. It can take the following values: self.use_constraint.force (default) : force CPLEX to use the constraint self.use_constraint.purge : allow CPLEX to purge the constraint When you add a lazy constraint with the nondefault value purge, you authorize CPLEX to purge (that is, to eliminate) the lazy constraint under certain circumstances, for example, if the lazy constraint becomes slack. Consequently, in view of such purging, you must not assume that any previously added constraints are still in current relaxation. In other words, the purged lazy constraint may be violated in subsequent relaxations. """ self._add(constraint, sense, rhs, use)
[docs] def add_local(self, constraint, sense, rhs): """Adds a linear local constraint to the current subproblem. A local constraint is a constraint that will only be added to the problem at the current node and the subtree rooted by the current node. It will not be added to the constraint matrix of the original model. constraint must be either a SparsePair instance or a list of two lists, the first of which specifies variables, the second of which specifies the values of the constraint. sense must be a single-character string; ("L", "G", "E") rhs is a float, specifying the righthand side of the constraint. """ self._add_local(constraint, sense, rhs)
[docs] def is_unbounded_node(self): """Returns True if the current LP relaxation is unbounded, False otherwise.""" return self._wherefrom() == _const.CPX_CALLBACK_MIP_CUT_UNBD
[docs] def get_solution_source(self): """Returns the source of the solution for which the lazy constraint callback was invoked. The possible return values are: IncumbentCallback.solution_source.node_solution: The integral solution is the solution to the LP relaxation of a node in the MIP search tree. IncumbentCallback.solution_source.heuristic_solution: The integral solution has been found by a CPLEX internal heuristic. IncumbentCallback.solution_source.mipstart_solution: The integral solution has been found during MIP start processing. """ node_info = self._get_node_info(_const.CPX_CALLBACK_INFO_LAZY_SOURCE, 0) source = IncumbentCallback.solution_source switcher = { _const.CPX_LAZYCONSTRAINTCALLBACK_NODE: source.node_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_HEUR: source.heuristic_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_MIPSTART: source.mipstart_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_USER: source.user_solution } return switcher[node_info]
[docs] class UseCut(ConstantClass): """Constants to specify when to use the added cut.""" force = _const.CPX_USECUT_FORCE purge = _const.CPX_USECUT_PURGE filter = _const.CPX_USECUT_FILTER
[docs] class UserCutCallback(CutCallback): """Subclassable class for lazy constraint callback classes. This callback will be used within the cut loop that CPLEX calls at each node of the branch and cut algorithm. It will be called once after CPLEX has ended its own cut generation loop so that the user can specify additional cuts to be added to the cut pool. :undocumented: __init__ """ use_cut = UseCut() """See `UseCut()`"""
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "usercut" self._cb_set_function = _proc.setusercutcallbackfunc
[docs] def add(self, cut, sense, rhs, use=use_cut.force): """Adds a linear cut to to the current subproblem. cut must be either a SparsePair instance or a list of two lists, the first of which specifies variables, the second of which specifies the values of the cut. sense must be a single-character string; ("L", "G", "E") rhs is a float, specifying the righthand side of the cut. use indicates under what circumstances the cut should be used. It can take the following values self.use_cut.force (default) : force CPLEX to use the cut self.use_cut.purge : allow CPLEX to purge the cut self.use_cut.filter : treat as cuts CPLEX creates """ self._add(cut, sense, rhs, use)
[docs] def add_local(self, cut, sense, rhs): """Adds a linear local cut to the current subproblem. A local cut is a cut that is only valid at the current node and the subtree rooted by the current node. It does not need to be globally valid. cut must be either a SparsePair instance or a list of two lists, the first of which specifies variables, the second of which specifies the values of the cut. sense must be a single-character string; ("L", "G", "E") rhs is a float, specifying the righthand side of the cut. """ self._add_local(cut, sense, rhs)
[docs] def is_after_cut_loop(self): """Returns True if called after the cut loop, False otherwise.""" return self._wherefrom() == _const.CPX_CALLBACK_MIP_CUT_LAST
[docs] def abort_cut_loop(self): """Terminate the cut loop and proceed with branching.""" self._useraction = _const.CPX_CALLBACK_ABORT_CUT_LOOP
[docs] class MethodType(ConstantClass): """Constants defining methods for solving the node LP.""" primal = _const.CPX_ALG_PRIMAL dual = _const.CPX_ALG_DUAL barrier = _const.CPX_ALG_BARRIER network = _const.CPX_ALG_NET
[docs] class HSCallback(ControlCallback): """Base class for heuristic and solve callback classes.""" status = _internal._subinterfaces.SolutionStatus() """See `_internal._subinterfaces.SolutionStatus()` """ method = MethodType() """See `MethodType()`"""
[docs] def get_cplex_status(self): """Returns the solution status of the current subproblem. Returns an attribute of self.status. """ status = _pycplex.cb_getstat(self._cbstruct) _proc.check_status(self._cbstruct, status[0], from_cb=True) return status[1]
[docs] def is_dual_feasible(self): """Returns whether the solution of the current subproblem is dual feasible.""" status = _pycplex.cb_solninfo(self._cbstruct) _proc.check_status(self._cbstruct, status[0], from_cb=True) return bool(status[2])
[docs] def is_primal_feasible(self): """Returns whether the solution of the current subproblem is primal feasible.""" status = _pycplex.cb_solninfo(self._cbstruct) _proc.check_status(self._cbstruct, status[0], from_cb=True) return bool(status[1])
[docs] def solve(self, alg=_const.CPX_ALG_DUAL): """Solves the current subproblem. The value of alg, if specified, determines the algorithm to use to solve the current subproblem. self.method.primal : primal simplex self.method.dual : dual simplex self.method.barrier : barrier self.method.network : network If this method generates a feasible solution it returns True, otherwise it returns False. """ if alg == _const.CPX_ALG_PRIMAL: status = _pycplex.cb_primopt(self._cbstruct) elif alg in (_const.CPX_ALG_DUAL, _const.CPX_ALG_AUTOMATIC): status = _pycplex.cb_dualopt(self._cbstruct) elif alg == _const.CPX_ALG_BARRIER: status = _pycplex.cb_hybbaropt(self._cbstruct) elif alg == _const.CPX_ALG_NET: status = _pycplex.cb_hybnetopt(self._cbstruct) else: raise CplexError("HSCallback.solve: bad algorithm identifier") _proc.check_status(self._cbstruct, status, from_cb=True) return self.get_cplex_status() in (self.status.optimal, self.status.feasible, self.status.MIP_optimal, self.status.MIP_feasible)
[docs] class HeuristicCallback(HSCallback): """Subclassable class for heuristic callback classes. This callback will be used after solving each subproblem and at the root node before each round of cuts is added to the problem and resolved. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "heuristic" self._cb_set_function = _proc.setheuristiccallbackfunc self._x = []
[docs] def set_bounds(self, *args): """Sets the bounds for a set of variables. Can be called by two forms. self.set_bounds(i, lb, ub) i must be a variable name or index and lb and ub must be real numbers. Sets the lower bound and upper bound of the variable whose index or name is i to lb and ub, respectively. self.set_lower_bounds(seq_of_triples) seq_of_triples must be a list or tuple of tuples (i, lb, ub), each of which consists of a variable name or index and two real numbers. Sets the bounds of the specified variables to the corresponding values. Equivalent to [self.set_lower_bounds(triple[0], triple[1], triple[2]) for triple in seq_of_triples]. Note The variables specified must not have been removed by presolve. Note These bound changes affect only the problem at the current node. """ if len(args) == 1: vars, lb, ub = unzip(args) elif len(args) == 3: vars = [args[0]] lb = [args[1]] ub = [args[2]] else: raise WrongNumberOfArgumentsError() vars = self._conv_col(vars) status = _pycplex.cb_getprestat_c(self._cbstruct, self._env_lp_ptr) if status is not None: _proc.check_status(self._cbstruct, status[0], from_cb=True) pstat = status[1] for i in vars: if pstat[i] == 0: raise CplexError( "Variable removed by presolve: cannot change bounds") ind = [] lu = "" bd = [] for i, v in enumerate(vars): ind.append(v) if lb[i] == ub[i]: lu = ''.join([lu, "B"]) bd.append(lb[i]) else: ind.append(v) lu = ''.join([lu, "LU"]) bd.extend([lb[i], ub[i]]) status = _pycplex.cb_chgbds(self._cbstruct, ind, lu, bd) _proc.check_status(self._cbstruct, status, from_cb=True)
[docs] def set_solution(self, solution, objective_value=None): """Sets a solution to be used as the incumbent. solution is either an instance of SparsePair or a sequence of length two. If it is a sequence, the first entry is a sequence of variable indices or names whose values are to be changed and the second entry is a sequence of floats with the corresponding new solution values. Variables whose indices are not specified remain unchanged. If objective_value is specified, it is taken as the objective value of the new solution. Otherwise, the objective value is computed. Do not call this method multiple times. Calling it again will overwrite any previously specified solution. """ vars, vals = unpack_pair(solution) vars = self._conv_col(vars) for i, v in enumerate(vars): self._x[v] = vals[i] if objective_value is None: objective_value = 0.0 obj_coef = self.get_objective_coefficients() for i, v in enumerate(self._x): objective_value += v * obj_coef[i] self._objective_value = objective_value self._useraction = _const.CPX_CALLBACK_SET self._check_feasibility = 1 self._status = 0
[docs] class SolveCallback(HSCallback): """Subclassable class for solve callback classes. This callback can be used to solve node relaxations during branch and cut search. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "solve" self._cb_set_function = _proc.setsolvecallbackfunc
[docs] def set_start(self, primal=None, dual=None): """Sets the starting vectors for the next solve. The arguments primal and dual must either be instances of SparsePair or sequences of length two. If they are sequences, the first entry is a sequence of indices or names specifying the columns or rows whose values are to be set, and the second entry is a sequence of floats with the corresponding new values. If primal is specified but dual is not, no dual values will be stored. If dual is specified but primal is not, no primal values will be stored. Variables whose indices are not specified will be set to 0.0. Note If presolve is enabled, attempting to set dual values in this method will raise an exception. """ if primal is None: primal = SparsePair([], []) if dual is None: dual = SparsePair([], []) var, x = unpack_pair(primal) rng, pi = unpack_pair(dual) prim = [0.0] * self.get_num_cols() dual = [0.0] * self.get_num_rows() var = self._conv_col(var) rng = self._conv_row(rng) for i, val in enumerate(x): prim[var[i]] = val for i, val in enumerate(pi): dual[rng[i]] = val if var: status = _pycplex.cb_crushx(self._cbstruct, self._env_lp_ptr, prim) if status is not None: _proc.check_status(self._cbstruct, status[0], from_cb=True) prim = status[1] if rng: status = _pycplex.cb_crushpi(self._cbstruct, self._env_lp_ptr, dual) if status is not None: _proc.check_status(self._cbstruct, status, from_cb=True) raise CplexError( "Presolve must be disabled to set dual vectors in SolveCallback.set_start") status = _pycplex.cb_copystart(self._cbstruct, prim, dual) _proc.check_status(self._cbstruct, status, from_cb=True)
[docs] def use_solution(self): """Tell CPLEX to use the resident solution after calling solve.""" self._useraction = _const.CPX_CALLBACK_SET self._status = 0
[docs] class SolutionSource(ConstantClass): """Attributes defining possible solution sources.""" node_solution = _const.CPX_CALLBACK_MIP_INCUMBENT_NODESOLN heuristic_solution = _const.CPX_CALLBACK_MIP_INCUMBENT_HEURSOLN user_solution = _const.CPX_CALLBACK_MIP_INCUMBENT_USERSOLN mipstart_solution = _const.CPX_CALLBACK_MIP_INCUMBENT_MIPSTART
[docs] class IncumbentCallback(MIPCallback): """Subclassable class for incumbent callback classes. This callback will be used after each new potential incumbent is found. If the callback is used to reject incumbents, the user must set the parameter c.parameters.preprocessing.reduce either to the value 1 (one) to restrict presolve to primal reductions only or to 0 (zero) to disable all presolve reductions. This setting of the parameter is not necessary if the incumbent callback is used for other purposes. Note The incumbent callback may be invoked during MIP start processing. In that case get_solution_source will return mip_start_solution. In this situation the following special consideration applies: - MIP start processing occurs very early in the solution process. At this point no search tree is setup yet and there are no search tree nodes yet. Consequently, a lot of the callback methods that require a node context will fail in this situation. :undocumented: __init__ """ solution_source = SolutionSource() """See `SolutionSource()`"""
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "incumbent" self._cb_set_function = _proc.setincumbentcallbackfunc self._useraction = _const.CPX_CALLBACK_DEFAULT self._objective_value = 0.0 self._x = []
[docs] def get_node_data(self): """Returns the user handle for the current node. Returns None if no handle is set for the node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_USERHANDLE, 0)
[docs] def set_node_data(self, data): """Set the user handle for the current node. Returns the user handle previously set for this node (or None if no handle was set). """ return _proc.callbacksetuserhandle(self._cbstruct, data)
[docs] def get_node_ID(self): """Returns the sequence number of the current node.""" return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_SEQNUM_LONG, 0)
[docs] def get_objective_value(self): """Returns the objective value of the potential incumbent.""" return self._objective_value
[docs] def get_linear_slacks(self, *args): """Returns a set of linear slacks for the solution at the current node. Can be called by four forms. self.get_linear_slacks() return all linear slack values from the problem at the current node. self.get_linear_slacks(i) i must be a linear constraint name or index. Returns the slack values associated with the linear constraint whose index or name is i. self.get_linear_slacks(s) s must be a sequence of linear constraint names or indices. Returns the slack values associated with the linear constraints with indices the members of s. Equivalent to [self.get_linear_slacks(i) for i in s] self.get_linear_slacks(begin, end) begin and end must be linear constraint indices with begin <= end or linear constraint names whose indices respect this order. Returns the slack values associated with the linear constraints with indices between begin and end, inclusive of end. Equivalent to self.get_linear_slacks(range(begin, end + 1)). """ status = _pycplex.cb_slackfromx(self._cbstruct, self._env_lp_ptr, self._x) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.get_num_rows() - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_row, args)
[docs] def get_quadratic_slacks(self, *args): """Return a set of quadratic slacks for the solution at the current node. Can be called by four forms. self.get_quadratic_slacks() return all quadratic slack values from the problem at the current node. self.get_quadratic_slacks(i) i must be a quadratic constraint name or index. Returns the slack values associated with the quadratic constraint whose index or name is i. self.get_quadratic_slacks(s) s must be a sequence of quadratic constraint names or indices. Returns the slack values associated with the quadratic constraints with indices the members of s. Equivalent to [self.get_quadratic_slacks(i) for i in s] self.get_quadratic_slacks(begin, end) begin and end must be quadratic constraint indices or quadratic constraint names. Returns the slack values associated with the quadratic constraints with indices between begin and end, inclusive of end. Equivalent to self.get_quadratic_slacks(range(begin, end + 1)). """ status = _pycplex.cb_qconstrslackfromx( self._cbstruct, self._env_lp_ptr, self._x) _proc.check_status(self._cbstruct, status[0], from_cb=True) slacks = status[1] def getslack(a, b=self.get_num_quadratic_constraints() - 1): return slacks[a:b + 1] return apply_freeform_two_args( getslack, self._conv_quad, args)
[docs] def get_values(self, *args): """Return the potential incumbent solution values. There are four forms by which get_values may be called. self.get_values() returns the entire potential incumbent. self.get_values(i) i must be a variable index or name. Returns the potential incumbent value of the variable with index i. self.get_values(s) s must be a sequence of variable indices or names. Returns a list of the potential incumbent values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_values(i) for i in s] self.get_values(begin, end) begin and end must be variable indices or variable names. Returns a list of the potential incumbent values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_values(range(begin, end + 1)) """ def getx(begin, end=self.get_num_cols() - 1): return self._x[begin:end + 1] return apply_freeform_two_args( getx, self._conv_col, args)
[docs] def get_solution_source(self): """Returns the source of the solution for which the incumbent callback was invoked. The possible return values are: self.solution_source.node_solution: The integral solution is the solution to the LP relaxation of a node in the MIP search tree. self.solution_source.heuristic_solution: The integral solution has been found by a CPLEX internal heuristic. self.solution_source.user_solution: The integral solution has been found by the user in the heuristic callback. self.solution_source.mipstart_solution: The integral solution has been found during MIP start processing. """ wherefrom = self._wherefrom() source = self.solution_source switcher = { _const.CPX_CALLBACK_MIP_INCUMBENT_NODESOLN: source.node_solution, _const.CPX_CALLBACK_MIP_INCUMBENT_HEURSOLN: source.heuristic_solution, _const.CPX_CALLBACK_MIP_INCUMBENT_USERSOLN: source.user_solution, _const.CPX_CALLBACK_MIP_INCUMBENT_MIPSTART: source.mipstart_solution } return switcher[wherefrom]
[docs] def reject(self): """Tells Cplex not to use the potential incumbent.""" self._useraction = _const.CPX_CALLBACK_SET self._is_feasible = False
[docs] class NodeCallback(MIPCallback): """Subclassable class for node callback classes. This callback will be used before CPLEX enters a node, and can select a different node to be entered instead. :undocumented: __init__, __conditionally_convert """ def __conditionally_convert(self, which_node): if isinstance(which_node, type(())): return self.get_node_number(which_node) return which_node
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "node" self._cb_set_function = _proc.setnodecallbackfunc self._useraction = _const.CPX_CALLBACK_DEFAULT
[docs] def get_branch_variable(self, which_node): """Returns the index of the variable used to branch at node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_VAR, self.__conditionally_convert(which_node))
[docs] def get_depth(self, which_node): """Returns the depth in the search tree of node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_DEPTH_LONG, self.__conditionally_convert(which_node))
[docs] def get_current_node_depth(self): """Returns the depth of the current node in the search tree. This method always raises an exception since the node callback is not invoked in the context of any node. """ # Overrides MIPCallback.get_current_node_depth # Always throw an exception. # The intention is this: The super class has a getCurrentNodeDepth() # function as well. That would throw an exception because the callable # library returns a non-zero status. # However, by explicitly overriding this function, we can explicitly # document that this will fail. raise CplexSolverError('Not in a node context', None, CPXERR_UNSUPPORTED_OPERATION)
[docs] def get_estimated_objective_value(self, which_node): """Returns the estimated objective function value at node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_ESTIMATE, self.__conditionally_convert(which_node))
[docs] def get_infeasibility_sum(self, which_node): """Returns the sum of infeasibilities at node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_SIINF, self.__conditionally_convert(which_node))
[docs] def get_num_infeasibilities(self, which_node): """Returns the number of infeasibilities at node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_NIINF, self.__conditionally_convert(which_node))
[docs] def get_node_data(self, which_node): """Returns the handle set by the user for node which_node. Returns None if no handle was set when the node was created. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_USERHANDLE, self.__conditionally_convert(which_node))
[docs] def set_node_data(self, which_node, data): """Set the user handle for the specified node. Returns the user handle previously set for that node (or None if no handle was set). """ return _proc.callbacksetnodeuserhandle(self._cbstruct, which_node, data)
[docs] def get_node_ID(self, which_node): """Returns a one-tuple containing the sequence number of node which_node. which_node must be an integer specifying the index number of the desired node. """ return (self._get_node_info( _const.CPX_CALLBACK_INFO_NODE_SEQNUM_LONG, which_node),)
[docs] def get_node_number(self, which_node): """Returns the index number of node which_node. which_node must be a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_seq_info( _const.CPX_CALLBACK_INFO_NODE_NODENUM_LONG, which_node[0])
[docs] def get_objective_value(self, which_node): """Returns the objective function value for node which_node. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ return self._get_node_info(_const.CPX_CALLBACK_INFO_NODE_OBJVAL, self.__conditionally_convert(which_node))
[docs] def select_node(self, which_node): """Tells Cplex to enter node which_node next. which_node may either be an integer specifying the index number of the desired node, or a 1-tuple whose entry is an integer specifying the sequence number of the desired node. """ self._node_number = self.__conditionally_convert(which_node) self._useraction = _const.CPX_CALLBACK_SET self._status = 0
[docs] class TuningCallback(Callback): """Subclassable class for tuning callback classes. This callback will be used during tuning. For general information about tuning callbacks, see that topic in the CPLEX User's Manual. :undocumented: __init__ """
[docs] def __init__(self, env): """non-public""" super().__init__(env) self._cb_type_string = "tuning" self._cb_set_function = _proc.settuningcallbackfunc
[docs] def get_progress(self): """Returns the fraction of the tuning process that is done.""" return _pycplex.fast_getcallbackinfo( self._cbstruct, _const.CPX_CALLBACK_INFO_TUNING_PROGRESS, CplexSolverError)
[docs] class ContextType(ConstantClass): """The different contexts in which a generic callback can be invoked. The values defined here serve two purposes: They are returned from `Context.get_id()` to indicate in which context a particular callback invocation happened. The bit-wise OR of these values specifies to `Cplex.set_callback()` in which contexts CPLEX invokes the callback. See the reference manual of the CPLEX Callable Library (C API) for a more detailed description of the various contexts. """ thread_up = _const.CPX_CALLBACKCONTEXT_THREAD_UP """See `CPX_CALLBACKCONTEXT_THREAD_UP <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_THREAD_UP.html>`_ in the C API.""" thread_down = _const.CPX_CALLBACKCONTEXT_THREAD_DOWN """See `CPX_CALLBACKCONTEXT_THREAD_DOWN <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_THREAD_DOWN.html>`_ in the C API.""" local_progress = _const.CPX_CALLBACKCONTEXT_LOCAL_PROGRESS """See `CPX_CALLBACKCONTEXT_LOCAL_PROGRESS <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_LOCAL_PROGRESS.html>`_ in the C API.""" global_progress = _const.CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS """See `CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS.html>`_ in the C API.""" candidate = _const.CPX_CALLBACKCONTEXT_CANDIDATE """See `CPX_CALLBACKCONTEXT_CANDIDATE <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_CANDIDATE.html>`_ in the C API.""" relaxation = _const.CPX_CALLBACKCONTEXT_RELAXATION """See `CPX_CALLBACKCONTEXT_RELAXATION <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_RELAXATION.html>`_ in the C API.""" branching = _const.CPX_CALLBACKCONTEXT_BRANCHING """See `CPX_CALLBACKCONTEXT_BRANCHING <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_CALLBACKCONTEXT_BRANCHING.html>`_ in the C API."""
[docs] class RelaxationFlags(ConstantClass): """The flags that can be passed to `Context.get_relaxation_status()`. See the reference manual of the CPLEX Callable Library (C API) for a more detailed description of the various contexts. """ no_solve = _const.CPX_RELAXATION_FLAG_NOSOLVE """See `CPX_RELAXATION_FLAG_NOSOLVE <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/macros/CPX_RELAXATION_FLAG_NOSOLVE.html>`_ in the C API."""
[docs] class Context(): """Context for the generic callback. An instance of this class defines the context in which a generic callback was invoked. It provides methods to query information and perform all actions that can be performed from a generic callback. Note that an instance of this class is only valid during the execution of the callback into which it was passed. Don't store a reference to the context across callback invocations. See `Cplex.set_callback()`. :undocumented: __init__ """ info = CallbackInfo() """See `CallbackInfo`""" solution_strategy = SolutionStrategy() """See `SolutionStrategy`""" id = ContextType() """See `ContextType`""" relaxation_flags = RelaxationFlags() """See `RelaxationFlags`""" solution_status = SolutionStatus() """See `SolutionStatus`"""
[docs] def __init__(self, cpx, contextptr, contextid): """non-public""" self._cpx = cpx self._contextptr = contextptr self._contextid = contextid
def _get_column_index(self, name): """non-public""" # Adapted from Callback._get_col_index return _proc.getcolindex(env=self._cpx._env._e, lp=self._cpx._lp, colname=name) def _get_column_count(self): """non-public""" return _proc.getnumcols(self._cpx._env._e, self._cpx._lp) def _colname2idx(self, name, cache=None): """non-public""" # This is the same as Callback._conv_col! return convert(name, self._get_column_index, cache)
[docs] def get_id(self): """Returns the context in which the current callback was invoked. The return value will be one of the constants in `ContextType`. """ return self._contextid
[docs] def in_thread_up(self): """Returns True if the callback was invoked in context `ContextType.thread_up`. It is a shortcut for checking whether `get_id()` returns `ContextType.thread_up` or not. """ return self._contextid == self.id.thread_up
[docs] def in_thread_down(self): """Returns True if the callback was invoked in context `ContextType.thread_down`. It is a shortcut for checking whether `get_id()` returns `ContextType.thread_down` or not. """ return self._contextid == self.id.thread_down
[docs] def in_local_progress(self): """Returns True if the callback was invoked in context `ContextType.local_progress`. It is a shortcut for checking whether `get_id()` returns `ContextType.local_progress` or not. """ return self._contextid == self.id.local_progress
[docs] def in_global_progress(self): """Returns True if the callback was invoked in context `ContextType.global_progress`. It is a shortcut for checking whether `get_id()` returns `ContextType.global_progress` or not. """ return self._contextid == self.id.global_progress
[docs] def in_candidate(self): """Returns True if the callback was invoked in context `ContextType.candidate`. It is a shortcut for checking whether `get_id()` returns `ContextType.candidate` or not. """ return self._contextid == self.id.candidate
[docs] def in_relaxation(self): """Returns True if the callback was invoked in context `ContextType.relaxation`. It is a shortcut for checking whether `get_id()` returns `ContextType.relaxation` or not. """ return self._contextid == self.id.relaxation
[docs] def in_branching(self): """Returns True if the callback was invoked in context `ContextType.branching`. It is a shortcut for checking whether `get_id()` returns `ContextType.branching` or not. """ return self._contextid == self.id.branching
[docs] def get_int_info(self, what): """Returns a 32bit signed information value. Potential values are listed in `Context.info`. Note that in all contexts but `ContextType.global_progress` the information returned by the method is thread-local. See `CPXcallbackgetinfoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetinfoint.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetinfoint(self._contextptr, what)
[docs] def get_long_info(self, what): """Returns a 64bit signed information value. Potential values are listed in `Context.info`. Note that in all contexts but `ContextType.global_progress` the information returned by the method is thread-local. See `CPXcallbackgetinfolong <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetinfolong.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetinfolong(self._contextptr, what)
[docs] def get_double_info(self, what): """Returns a float information value. Potential values are listed in `Context.info`. Note that in all contexts but `ContextType.global_progress` the information returned by the method is thread-local. See `CPXcallbackgetinfodbl <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetinfodbl.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetinfodbl(self._contextptr, what)
[docs] def abort(self): """Aborts the optimization. If you call this method then CPLEX will abort optimization at the next opportunity. See `CPXcallbackabort <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackabort.html>`_ in the Callable Library Reference Manual for more detail. """ _proc.callbackabort(self._contextptr)
[docs] def get_relaxation_point(self, *args): """Returns the solution to the current relaxation. This method can only be invoked if `get_id()` returns `ContextType.relaxation` or `ContextType.branching`. If invoked in a different context it will raise an exception. This method returns the values in the solution for the current relaxation for the variables specified by the arguments. There are four forms by which get_relaxation_point may be called. self.get_relaxation_point() returns the full solution vector. self.get_relaxation_point(i) i must be a variable index or name. Returns the value of the variable with index or name i in the solution to the current relaxation. self.get_relaxation_point(s) s must be a sequence of variable indices or names. Returns a list of the values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_relaxation_point(i) for i in s] self.get_relaxation_point(begin, end) begin and end must be variable indices or variable names. Returns a list of solution values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_relaxation_point(range(begin, end + 1)). See `CPXcallbackgetrelaxationpoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetrelaxationpoint.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetrelaxationpoint(begin, end=self._get_column_count() - 1): return _proc.callbackgetrelaxationpoint(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetrelaxationpoint, self._colname2idx, args)
[docs] def get_relaxation_objective(self): """Returns the objective value of current relaxation. This method can only be invoked if `get_id()` returns `ContextType.relaxation` or `ContextType.branching`. If invoked in a different context it will raise an exception. See `CPXcallbackgetrelaxationpoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetrelaxationpoint.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetrelaxationpointobj(self._contextptr)
[docs] def get_relaxation_status(self, flags=0): """Returns the solution status of the relaxation LP. Returns the solution status of the LP relaxation at the current node. This method can only be invoked if `get_id()` returns `ContextType.relaxation` or `ContextType.branching`. If invoked in a different context it will raise an exception. See `CPXcallbackgetrelaxationstatus <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetrelaxationstatus.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetrelaxationstatus(self._contextptr, flags)
[docs] def make_branch(self, objective_estimate, variables=None, constraints=None): """Makes a new branch with the specified data. This method can only be invoked if `get_id()` returns `ContextType.branching`. If invoked in a different context it will raise an exception. objective_estimate is a float representing the estimated objective value resulting from the specified branch. variables is a sequence of (var, dir, bnd) tuples specifying the variables on which to branch. var must be an index of a variable, dir must be one of "L" and "U", indicating that the bound is a lower or upper bound, respectively, and bnd is an integer specifying the new bound for the variable. constraints is a sequence of (vec, sense, rhs) tuples specifying the constraints with which to branch. vec must be either an instance of `SparsePair` or a sequence with two entries, the first of which specifies the indices and the second of which specifies the values of the constraint. rhs must be a float determining the righthand side of the constraint. sense must be one of "L", "G", or "E", specifying whether the constraint is a less-than-or-equal-to (<=), greater-than-or-equal-to (>=), or equality constraint (=). The method returns an integer that uniquely identifies the newly created child node in the search tree. Note that the children will be dropped if you call `prune_current_node()` at the same node. See `CPXcallbackmakebranch <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackmakebranch.html>`_ in the Callable Library Reference Manual for more detail. """ variables, constraints = init_list_args(variables, constraints) if variables: a = unzip(variables) else: a = [[], [], []] vars = list(a[0]) dirs = ''.join(list(a[1])) bnds = list(a[2]) if constraints: a = unzip(constraints) else: a = [[], [], []] rmat = _HBMatrix(a[0]) sense = ''.join(list(a[1])) rhs = list(a[2]) return _proc.callbackmakebranch(self._contextptr, vars, dirs, bnds, rhs, sense, rmat.matbeg, rmat.matind, rmat.matval, objective_estimate)
[docs] def prune_current_node(self): """Ask CPLEX to prune the current node from the search tree. This method can only be invoked if `get_id()` returns `ContextType.relaxation` or `ContextType.branching`. If invoked in a different context it will raise an exception. The node is marked for pruning. As soon as the callback returns, CPLEX stops processing the node. In particular, no child nodes will be created from that node, even if you called `make_branch()` to explicitly create new nodes. See `CPXcallbackprunenode <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackprunenode.html>`_ in the Callable Library Reference Manual for more detail. """ _proc.callbackprunenode(self._contextptr)
[docs] def exit_cut_loop(self): """Ask CPLEX to stop cutting plane separatation at the current node. This method can only be invoked if `get_id()` returns `ContextType.relaxation`. If invoked in a different context it will raise an exception. See `CPXcallbackexitcutloop <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackexitcutloop.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackexitcutloop(self._contextptr)
[docs] def get_incumbent(self, *args): """Returns the current incumbent solution. The method returns the values in the current incumbent solution for the variables specified by the arguments. There are four forms by which get_incumbent may be called. self.get_incumbent() returns the full incumbent vector. self.get_incumbent(i) i must be a variable index or name. Returns the value of the variable with index or name i in the current incumbent solution. self.get_incumbent(s) s must be a sequence of variable indices or names. Returns a list of the values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_incumbent(i) for i in s] self.get_incumbent(begin, end) begin and end must be variable indices or variable names. Returns a list of solution values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_incumbent(range(begin, end + 1)). See `CPXcallbackgetincumbent <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetincumbent.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetincumbent(begin, end=self._get_column_count() - 1): return _proc.callbackgetincumbent(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetincumbent, self._colname2idx, args)
[docs] def get_incumbent_objective(self): """Returns the objective value of the current incumbent. The returned value may be a huge value (such as 1e75) to indicate that no incumbent was found yet. Consider using `get_int_info()` with `CallbackInfo.feasible` first to check whether there is an incumbent. See `CPXcallbackgetincumbent <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetincumbent.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetincumbentobj(self._contextptr)
[docs] def is_candidate_point(self): """Test if the callback was invoked for a candidate feasible point. This method can only be invoked if `get_id()` returns `ContextType.candidate`. If invoked in a different context it will raise an exception. This method returns true if the callback was invoked for a candidate feasible point. In that case the candidate feasible point can be examined using `get_candidate_point()` and `get_candidate_objective()`. See `CPXcallbackcandidateispoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackcandidateispoint.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackcandidateispoint(self._contextptr)
[docs] def get_candidate_source(self): """Get the source from which the current candidate solution originated. The value returned is from IncumbentCallback.solution_source. """ # FIXME: Share this code with above source = IncumbentCallback.solution_source switcher = { _const.CPX_LAZYCONSTRAINTCALLBACK_NODE: source.node_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_HEUR: source.heuristic_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_MIPSTART: source.mipstart_solution, _const.CPX_LAZYCONSTRAINTCALLBACK_USER: source.user_solution } return switcher[self.get_int_info(self.info.candidate_source)]
[docs] def get_candidate_point(self, *args): """Returns the current candidate solution. This method can only be invoked if `get_id()` returns `ContextType.candidate` and `is_candidate_point()` returns true. If invoked in a different context it will raise an exception. This method returns the values in the current candidate solution for the variables specified by the arguments. There are four forms by which get_candidate_point may be called. self.get_candidate_point() returns the full solution vector. self.get_candidate_point(i) i must be a variable index or name. Returns the value of the variable with index or name i in the current candidate solution. self.get_candidate_point(s) s must be a sequence of variable indices or names. Returns a list of the values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_candidate_point(i) for i in s] self.get_candidate_point(begin, end) begin and end must be variable indices or variable names. Returns a list of solution values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_candidate_point(range(begin, end + 1)) See `CPXcallbackgetcandidatepoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetcandidatepoint.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetcandidatepoint(begin, end=self._get_column_count() - 1): return _proc.callbackgetcandidatepoint(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetcandidatepoint, self._colname2idx, args)
[docs] def get_candidate_objective(self): """Returns the objective value of current candidate solution. This method can only be invoked if `get_id()` returns `ContextType.candidate` and `is_candidate_point()` returns true. It will raise an exception if invoked in a different context. See `CPXcallbackgetcandidatepoint <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetcandidatepoint.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackgetcandidateobj(self._contextptr)
[docs] def is_candidate_ray(self): """Test if the callback was invoked for an unbounded ray. This method can only be invoked if `get_id()` returns `ContextType.candidate`. If invoked in a different context it will raise an exception. This method returns true if the callback was invoked for an unbounded relaxation. In that case the unbounded ray can be obtained using `get_candidate_ray()` and. See `CPXcallbackcandidateisray <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackcandidateisray.html>`_ in the Callable Library Reference Manual for more detail. """ return _proc.callbackcandidateisray(self._contextptr)
[docs] def get_candidate_ray(self, *args): """Returns the current unbounded ray. This method can only be invoked if `get_id()` returns `ContextType.candidate` and `is_candidate_ray()` returns true. If invoked in a different context it will raise an exception. The method returns the values for in the unbounded ray for the variables specified by the arguments. There are four forms by which get_candidate_ray may be called. self.get_candidate_ray() returns the full ray vector. self.get_candidate_ray(i) i must be a variable index or name. Returns the value of the variable with index or name i in the unbounded ray. self.get_candidate_ray(s) s must be a sequence of variable indices or names. Returns a list of the values of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_candidate_ray(i) for i in s] self.get_candidate_ray(begin, end) begin and end must be variable indices or variable names. Returns a list of unbounded reay values of variables with indices between begin and end, inclusive of end. Equivalent to self.get_candidate_ray(range(begin, end + 1)). See `CPXcallbackgetcandidateray <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackgetcandidateray.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetcandidateray(begin, end=self._get_column_count() - 1): return _proc.callbackgetcandidateray(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetcandidateray, self._colname2idx, args)
[docs] def get_local_lower_bounds(self, *args): """Returns the current local lower bounds. This method can only be invoked if `get_id()` returns `ContextType.relaxation`. If invoked in a different context it will raise an exception. There are four forms by which get_local_lower_bounds may be called. self.get_local_lower_bounds() returns local lower bounds for all variables. self.get_local_lower_bounds(i) i must be a variable index or name. Returns the local lower bound of the variable with index or name i. self.get_local_lower_bounds(s) s must be a sequence of variable indices or names. Returns a list of the local lower bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_local_lower_bounds(i) for i in s] self.get_local_lower_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the local lower bounds of variables with indices between begin and end, inclusive of end. Equivalent to self.get_local_lower_bounds(range(begin, end + 1)). See `CPXcallbackgetlocallb <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/mipapi/callbackgetlocallb.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetlocallb(begin, end=self._get_column_count() - 1): return _proc.callbackgetlocallb(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetlocallb, self._colname2idx, args)
[docs] def get_local_upper_bounds(self, *args): """Returns the current local upper bounds. This method can only be invoked if `get_id()` returns `ContextType.relaxation`. If invoked in a different context it will raise an exception. There are four forms by which get_local_upper_bounds may be called. self.get_local_upper_bounds() returns local upper bounds for all variables. self.get_local_upper_bounds(i) i must be a variable index or name. Returns the local upper bound of the variable with index or name i. self.get_local_upper_bounds(s) s must be a sequence of variable indices or names. Returns a list of the local upper bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_local_upper_bounds(i) for i in s] self.get_local_upper_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the local upper bounds of variables with indices between begin and end, inclusive of end. Equivalent to self.get_local_upper_bounds(range(begin, end + 1)). See `CPXcallbackgetlocalub <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/mipapi/callbackgetlocalub.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetlocalub(begin, end=self._get_column_count() - 1): return _proc.callbackgetlocalub(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetlocalub, self._colname2idx, args)
[docs] def get_global_lower_bounds(self, *args): """Returns the current globally valid lower bounds. This method cannot be invoked if `get_id()` returns `ContextType.thread_up` or `ContextType.thread_down`. There are four forms by which get_global_lower_bounds may be called. self.get_global_lower_bounds() returns global lower bounds for all variables. self.get_global_lower_bounds(i) i must be a variable index or name. Returns the global lower bound of the variable with index or name i. self.get_global_lower_bounds(s) s must be a sequence of variable indices or names. Returns a list of the global lower bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_global_lower_bounds(i) for i in s] self.get_global_lower_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the global lower bounds of variables with indices between begin and end, inclusive of end. Equivalent to self.get_global_lower_bounds(range(begin, end + 1)). See `CPXcallbackgetgloballb <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/mipapi/callbackgetgloballb.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetgloballb(begin, end=self._get_column_count() - 1): return _proc.callbackgetgloballb(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetgloballb, self._colname2idx, args)
[docs] def get_global_upper_bounds(self, *args): """Returns the current globally valid upper bounds. This method cannot be invoked if `get_id()` returns `ContextType.thread_up` or `ContextType.thread_down`. There are four forms by which get_global_upper_bounds may be called. self.get_global_upper_bounds() returns global upper bounds for all variables. self.get_global_upper_bounds(i) i must be a variable index or name. Returns the global upper bound of the variable with index or name i. self.get_global_upper_bounds(s) s must be a sequence of variable indices or names. Returns a list of the global upper bounds of the variables with indices the members of s, in the same order as they appear in s. Equivalent to [self.get_global_upper_bounds(i) for i in s] self.get_global_upper_bounds(begin, end) begin and end must be variable indices or variable names. Returns a list of the global upper bounds of variables with indices between begin and end, inclusive of end. Equivalent to self.get_global_upper_bounds(range(begin, end + 1)). See `CPXcallbackgetglobalub <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/mipapi/callbackgetglobalub.html>`_ in the Callable Library Reference Manual for more detail. """ def callbackgetglobalub(begin, end=self._get_column_count() - 1): return _proc.callbackgetglobalub(self._contextptr, begin, end) return apply_freeform_two_args( callbackgetglobalub, self._colname2idx, args)
[docs] def post_heuristic_solution(self, x, obj, strategy): """Posts a feasible solution vector to CPLEX. This method posts a (possibly partial) feasible solution to CPLEX. CPLEX may use this vector to find a new incumbent solution. x is either a `SparsePair` instance or a list of two lists, the first of which specifies the variables (by index or name) and the second of which specifies the values. obj is an estimate for the objective function value of the solution provided by x. strategy specifies how CPLEX should complete partial solutions. See `SolutionStrategy` for further details. See `CPXcallbackpostheursoln <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackpostheursoln.html>`_ in the Callable Library Reference Manual for more detail. """ indices, values = unpack_pair(x) _proc.callbackpostheursoln(self._contextptr, len(indices), self._colname2idx(indices), values, obj, strategy)
[docs] def add_user_cuts(self, cuts, senses, rhs, cutmanagement, local): """Adds user cuts. This method can only be invoked if `get_id()` returns `ContextType.relaxation`. If invoked in a different context it will raise an exception. This method submits the specified user cuts to CPLEX. cuts, senses, rhs, cutmanagement, local must all be lists of compatible dimensions. The first three specify the cuts to be added. cuts must be either a list of `SparsePair` instances or a list of lists of two lists, the first of which specifies variables, the second of which specifies the values of the constraint. senses must be list of single-character strings; ("L", "G", "E") It may also be one single string (the concatenation of the single character strings). rhs is a list of floats, specifying the righthand side of the constraints. cutmanagement must be a list of integer values specifying how CPLEX should treat each cut (see `UseCut` constants for further details). local must be a list of boolean values and specifies for each cut whether it is only locally valid (True) or globally valid (False). See `CPXcallbackaddusercuts <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackaddusercuts.html>`_ in the Callable Library Reference Manual for more detail. """ if not isinstance(senses, str): senses = "".join(senses) arg_list = [rhs, senses, cuts, cutmanagement, local] ncuts = max_arg_length(arg_list) validate_arg_lengths( arg_list, extra_msg=": cuts, senses, rhs, cutmanagement, local" ) if ncuts > 0: with _proc.chbmatrix(cuts, self._cpx._env_lp_ptr, 0) as (rmat, nnz): _proc.callbackaddusercuts(self._contextptr, ncuts, nnz, rhs, senses, rmat, cutmanagement, local)
[docs] def add_user_cut(self, cut, sense, rhs, cutmanagement, local): """Convenience wrapper for `add_user_cuts()` that only adds a single cut. """ self.add_user_cuts([cut], [sense], [rhs], [cutmanagement], [local])
[docs] def reject_candidate(self, constraints=None, senses=None, rhs=None): """Rejects the current candidate solution. This method can only be invoked if `get_id()` returns `ContextType.candidate`. If invoked in a different context it will raise an exception. This method marks the current candidate solution as infeasible, potentially specifying additional constraints that cut it off. If constraints, senses, and rhs are all None then the current candidate solution is just rejected. If any of the three is not None then all must be not None and all must have compatible dimensions. In that case the three arguments specify a set of constraints that cut off the current candidate solution. CPLEX may use this information to tighten the problem formulation and to avoid finding the same solution again. There is however no guarantee that CPLEX will actually use those additional constraints. constraints must be either a list of `SparsePair` instances or a list of lists of two lists, the first of which specifies variables, the second of which specifies the values of the constraint. senses must be list of single-character strings; ("L", "G", "E") It may also be one single string (the concatenation of the single character strings). rhs is a list of floats, specifying the righthand side of the constraints. See `CPXcallbackrejectcandidate <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackrejectcandidate.html>`_ in the Callable Library Reference Manual for more detail. """ constraints, senses, rhs = init_list_args(constraints, senses, rhs) if not isinstance(senses, str): senses = "".join(senses) arg_list = [rhs, senses, constraints] nconstraints = max_arg_length(arg_list) validate_arg_lengths( arg_list, extra_msg=": constraints, senses, rhs" ) with _proc.chbmatrix(constraints, self._cpx._env_lp_ptr, 0) as (rmat, nnz): _proc.callbackrejectcandidate(self._contextptr, nconstraints, nnz, rhs, senses, rmat)
[docs] def reject_candidate_local(self, constraints=None, senses=None, rhs=None): """Rejects the current candidate solution. This method can only be invoked if `get_id()` returns `ContextType.candidate` and if the candidate was invoked for an integral node. If invoked in a different context it will raise an exception. This method marks the current candidate solution as infeasible, potentially specifying additional constraints that cut it off. The specified constraints are not required to be globally valid. They are only required to be valid in the subtree in which the callback was invoked. If constraints, senses, and rhs are all None then the current candidate solution is just rejected. If any of the three is not None then all must be not None and all must have compatible dimensions. In that case the three arguments specify a set of constraints that cut off the current candidate solution. CPLEX may use this information to tighten the problem formulation and to avoid finding the same solution again. There is however no guarantee that CPLEX will actually use those additional constraints. constraints must be either a list of `SparsePair` instances or a list of lists of two lists, the first of which specifies variables, the second of which specifies the values of the constraint. senses must be list of single-character strings; ("L", "G", "E") It may also be one single string (the concatenation of the single character strings). rhs is a list of floats, specifying the righthand side of the constraints. See `CPXcallbackrejectcandidatelocal <https://www.ibm.com/docs/en/SSSA5P_22.1.2/ilog.odms.cplex.help/refcallablelibrary/cpxapi/callbackrejectcandidatelocal.html>`_ in the Callable Library Reference Manual for more detail. """ constraints, senses, rhs = init_list_args(constraints, senses, rhs) if not isinstance(senses, str): senses = "".join(senses) arg_list = [rhs, senses, constraints] nconstraints = max_arg_length(arg_list) validate_arg_lengths(arg_list) with _proc.chbmatrix(constraints, self._cpx._env_lp_ptr, 0) as (rmat, nnz): _proc.callbackrejectcandidatelocal(self._contextptr, nconstraints, nnz, rhs, senses, rmat)