Source code for docplex.mp.dvar


from docplex.mp.constants import CplexScope

from docplex.mp.basic import IndexableObject, _AbstractBendersAnnotated, _AbstractValuable
from docplex.mp.operand import LinearOperand
from docplex.mp.utils import is_number, is_quad_expr

from docplex.mp.sttck import StaticTypeChecker


[docs]class Var(IndexableObject, LinearOperand, _AbstractBendersAnnotated, _AbstractValuable): """Var() This class models decision variables. Decision variables are instantiated by :class:`docplex.mp.model.Model` methods such as :func:`docplex.mp.model.Model.var`. """ __slots__ = ('_vartype', '_lb', '_ub') def __init__(self, model, vartype, name, lb=None, ub=None, _safe_lb=False, _safe_ub=False): IndexableObject.__init__(self, model, name) self._vartype = vartype if _safe_lb: #assert lb is not None self._lb = lb else: self._lb = vartype._compute_lb(lb, model) if _safe_ub: #assert ub is not None self._ub = ub else: self._ub = vartype._compute_ub(ub, model) @property def cplex_scope(self): return CplexScope.VAR_SCOPE # noinspection PyUnusedLocal def copy(self, new_model, var_mapping): return var_mapping[self] relaxed_copy = copy # linear operand api def as_variable(self): return self def iter_terms(self): yield self, 1 def clone(self): return self def negate(self): return self.lfactory._new_monomial_expr(self, -1, safe=True) iter_sorted_terms = iter_terms def number_of_variables(self): return 1 def number_of_terms(self): return 1 def unchecked_get_coef(self, dvar): return 1 if dvar is self else 0 def contains_var(self, dvar): return self is dvar def accepts_value(self, candidate_value, tolerance=1e-6): # INTERNAL return self.vartype.accept_domain_value(candidate_value, lb=self._lb, ub=self._ub, tolerance=tolerance) def check_name(self, new_name): self.check_lp_name(qualifier='variable', new_name=new_name, accept_empty=False, accept_none=False) def __hash__(self): return self._index def set_name(self, new_name): # INTERNAL self.check_name(new_name) self.model.set_var_name(self, new_name) @property def name(self): return self._name @name.setter def name(self, new_name): self.set_name(new_name) @property def lb(self): """ This property is used to get or set the lower bound of the variable. Possible values for the lower bound depend on the variable type. Binary variables accept only 0 or 1 as bounds. An integer variable will convert the lower bound value to the ceiling integer value of the argument. """ return self._lb @lb.setter def lb(self, new_lb): self.set_lb(new_lb) def set_lb(self, lb): if lb != self._lb: self._model.set_var_lb(self, lb) return self._lb def _internal_set_lb(self, lb): # Internal, used only by the model self._lb = lb def _internal_set_ub(self, ub): # INTERNAL self._ub = ub @property def ub(self): """ This property is used to get or set the upper bound of the variable. Possible values for the upper bound depend on the variable type. Binary variables accept only 0 or 1 as bounds. An integer variable will convert the upper bound value to the floor integer value of the argument. To reset the upper bound to its default infinity value, use :func:`docplex.mp.model.Model.infinity`. """ return self._ub @ub.setter def ub(self, new_ub): self.set_ub(new_ub) def set_ub(self, ub): if ub != self._ub: self._model.set_var_ub(self, ub) return self._ub def has_free_lb(self): return self.lb <= - self._model.infinity def has_free_ub(self): return self.ub >= self._model.infinity def is_free(self): return self.has_free_lb() and self.has_free_ub() def _reset_bounds(self): vartype = self._vartype vtype_lb, vtype_ub = vartype.default_lb, vartype.default_ub self.set_lb(vtype_lb) self.set_ub(vtype_ub) @property def vartype(self): """ This property returns the variable type, an instance of :class:`VarType`. """ return self._vartype def set_vartype(self, new_vartype): # INTERNAL self._model._set_var_type(self, new_vartype) def _set_vartype_internal(self, new_vartype): # INTERNAL self._vartype = new_vartype def _has_type(self, vartype_code): # internal return self.cplex_typecode == vartype_code
[docs] def is_binary(self): """ Checks if the variable is binary. Returns: Boolean: True if the variable is of type Binary. """ return self._has_type('B')
[docs] def is_integer(self): """ Checks if the variable is integer. Returns: Boolean: True if the variable is of type Integer. """ return self._has_type('I')
[docs] def is_continuous(self): """ Checks if the variable is continuous. Returns: Boolean: True if the variable is of type Continuous. """ return self._has_type('C')
[docs] def is_discrete(self): """ Checks if the variable is discrete. Returns: Boolean: True if the variable is of type Binary or Integer. """ return self._vartype.is_discrete()
@property def float_precision(self): return 0 if self.is_discrete() else self._model.float_precision def get_value(self): # for compatibility only: use solution_value instead print("* get_value() is deprecated, use property solution_value instead") # pragma: no cover return self.solution_value # pragma: no cover @property def solution_value(self): """ This property returns the solution value of the variable. Raises: DOCplexException if the model has not been solved succesfully. """ self.model._check_has_solution() return self._raw_solution_value() @property def sv(self): """ Same as `solution_value` but shorter""" return super().sv def _raw_solution_value(self, s=None): sol = s or self.model._get_solution() return sol._get_var_value(self)
[docs] def get_key(self): """ Returns the key used to create the variable, or None. When the variable is part of a list or dictionary of variables created from a sequence of keys, returns the key associated with the variable. Example: xs = m.continuous_var_dict(keys=['a', 'b', 'c']) xa = xs['a'] assert xa.get_key() == 'a' :return: a Python object, possibly None. """ container = self.container return container.get_var_key(self) if container else None
def __mul__(self, e): return self.times(e) @classmethod def is_zero_op(cls, other): try: return other.is_zero() except AttributeError: return False def times(self, e): if is_number(e): return self.lfactory._new_monomial_expr(dvar=self, coeff=e, safe=False) elif self.is_zero_op(e): return self.lfactory.new_zero_expr() elif isinstance(e, LinearOperand): return self._model._qfactory.new_var_product(self, e) else: return self.to_linear_expr().multiply(e) def __rmul__(self, e): return self.times(e) def __add__(self, e): return self.plus(e) @staticmethod def _extract_calling_ct_xhs(): _searched_patterns = [("lhs", 0), ("left_expr", 0), ("rhs", 1), ("right_expr", 1)] import inspect # need to get 2 steps higher to find caller to add/sub frame = inspect.stack()[2] code_context = frame.code_context # if PY3 else frame[4] def find_in_line(line_): for xhs_s, xhs_p in _searched_patterns: if xhs_s in line_: return line_.find(xhs_s), xhs_p return -1, -1 if code_context: line = code_context[0] if line: spos, lr = find_in_line(line) if spos > 1: assert lr >= 0 # strip whitespace before code... ct_varname = line[:spos - 1].lstrip() # evaluate ct in caller locals dict subframe = frame.frame # if PY3 else frame[0] ct_object = subframe.f_locals.get(ct_varname) # returns a constraint (or None if that fails), plus 0-1 (0 for lhs, 1 for rhs) return ct_object, lr return None, -1 # def _perform_arithmetic_to_self(self, self_arithmetic_method, e): # # INTERNAL # res = self_arithmetic_method(e) # ct, xhs_pos = self._extract_calling_ct_xhs() # if ct is not None: # self.get_linear_factory().set_linear_constraint_expr_from_pos(ct, xhs_pos, res) # return res def add(self, e): res = self.plus(e) ct, xhs_pos = self._extract_calling_ct_xhs() if ct is not None: self.lfactory.set_linear_constraint_expr_from_pos(ct, xhs_pos, res) return res def subtract(self, e): res = self.minus(e) ct, xhs_pos = self._extract_calling_ct_xhs() if ct is not None: self.lfactory.set_linear_constraint_expr_from_pos(ct, xhs_pos, res) return res def plus(self, e): if isinstance(e, Var): expr = self._make_linear_expr() expr._add_term(e) return expr elif is_number(e): return self._make_linear_expr(constant=e, safe=False) elif is_quad_expr(e): return e.plus(self) else: return self.to_linear_expr().add(e) def to_linear_expr(self): # INTERNAL return self._make_linear_expr() def _make_linear_expr(self, constant=0, safe=True): return self.lfactory.linear_expr(self, constant, safe=safe, transient=True) def __radd__(self, e): return self.plus(e) def __sub__(self, e): return self.minus(e) def minus(self, e): if isinstance(e, LinearOperand): return self.to_linear_expr().subtract(e) elif is_number(e): # v -k -> expression(v,-1) -k return self._make_linear_expr(constant=-e, safe=False) elif is_quad_expr(e): return e.rminus(self) else: return self.to_linear_expr().subtract(e) def __rsub__(self, e): expr = self.lfactory._to_linear_operand(e, force_clone=True) # makes a clone. return expr.subtract(self) def divide(self, e): return self.to_linear_expr().divide(e) def __div__(self, e): return self.divide(e) def __truediv__(self, e): # for py3 # INTERNAL return self.divide(e) # pragma: no cover def __rtruediv__(self, e): # for py3 self.fatal("Variable {0!s} cannot be used as denominator of {1!s}", self, e) # pragma: no cover def __rdiv__(self, e): self.fatal("Variable {0!s} cannot be used as denominator of {1!s}", self, e) def __pos__(self): # the "+e" unary plus is syntactic sugar return self def __neg__(self): # the "-e" unary minus returns a linear expression return self.negate() def __pow__(self, power): # INTERNAL if 0 == power: return 1 elif 1 == power: return self elif 2 == power: return self.square() else: self.model.unsupported_power_error(self, power) def __rshift__(self, other): """ Redefines the right-shift operator to create indicators. This operator allows to create indicators with the `>>` operator. It expects a linear constraint as second argument. :param other: a linear constraint used to create the indicator :return: an instance of IndicatorConstraint, that is not added to the model. Use `Model.add()` to add it to the model. Note: The variable must be binary, otherwise an exception is raised. Example: >>> m.add(b >> (x >=3) creates an indicator which links the satisfaction of the constraint (x >= 3) to the value of binary variable b. """ return self._model.indicator_constraint(self, other) def square(self): return self._model._qfactory.new_var_square(self) def __int__(self): """ Converts a decision variable to a integer number. This is only possible for discrete variables, and when the model has been solved successfully. If the model has been solved, returns the variable's solution value. Returns: int: The variable's solution value. Raises: DOCplexException if the model has not been solved successfully. DOCplexException if the variable is not discrete. """ if self.is_continuous(): self.fatal("Cannot convert continuous variable value to int: {0!s}", self) return int(self.solution_value) def __float__(self): """ Converts a decision variable to a floating-point number. This is only possible when the model has been solved successfully, otherwise an exception is raised. If the model has been solved, it returns the variable's solution value. Returns: float: The variable's solution value. Raises: DOCplexException if the model has not been solved successfully. """ return float(self.solution_value)
[docs] def to_bool(self, precision=1e-6): """ Converts a variable value to True or False. Assuming the variable is discrete (integer or binary), returns True if the variable value is set a non-zero integer, taking into account precision. For binary variables, returns TRue if the variable value equals 1, taking into account precision. Raises: DOCplexException if the model has not been solved successfully. DOCplexException if the variable is not discrete Returns: Boolean: True if the variable value is nonzero, else False. """ if not self.is_discrete(): self.fatal("boolean conversion only for discrete variables, type is {0!s}", self.vartype) value = self.solution_value # this property checks for a solution. return abs(value) >= precision
def __str__(self): """ Returns: string: A string representation of the variable. """ return self.to_string() def to_string(self, use_space=False): return self.lp_name @property def lp_name(self): return self._name or "x%s" % self.index1 @property def lpt_name(self): # 'c1', 'b2', 'i3' ... first letter of cplex code + index cpx_typecode = self.cplex_typecode.lower() radix = 'x' if cpx_typecode == 'c' else cpx_typecode[0] return "%s%d" % (radix, self.index1) @property def cplex_typecode(self): return self._vartype.cplex_typecode def _must_print_lb(self): return self.cplex_typecode not in 'SN' and self.lb == self._vartype.default_lb def __repr__(self): self_vartype, self_lb, self_ub = self._vartype, self.lb, self.ub # print lb for semi-xx if self._must_print_lb(): repr_lb = '' else: repr_lb = ',lb={0:g}'.format(self_lb) if self_vartype.default_ub == self_ub: repr_ub = '' else: repr_ub = ',ub={0:g}'.format(self_ub) if self.has_name(): repr_name = ",name='{0}'".format(self.name) else: repr_name = '' cpxc = self.cplex_typecode return "docplex.mp.Var(type={0}{1}{2}{3})". \ format(cpxc, repr_name, repr_lb, repr_ub) @property def reduced_cost(self): """ Returns the reduced cost of the variable. This method will raise an exception if the model has not been solved as a LP. Note: For a large number of variables (> 100), using the `Model.reduced_costs()` method can be much faster. Returns: The reduced cost of the variable (a float value). See Also: :func:`docplex.mp.model.Model.reduced_costs` """ return self._model._reduced_cost1(self) @property def basis_status(self): """ This property returns the basis status of the variable, if any. The variable must be continuous, otherwise an exception is raised. Returns: An enumerated value from the enumerated type `docplex.constants.BasisStatus`. Note: for the model to hold basis information, the model must have been solved as a LP problem. In some cases, a model which failed to solve may still have a basis available. Use `Model.has_basis()` to check whether the model has basis information or not. See Also: :func:`docplex.mp.model.Model.has_basis` :class:`docplex.mp.constants.BasisStatus` """ if not self.is_continuous(): self.fatal("Basis status is for continuous variables, {0!s} has type {1!s}", self, self.vartype.short_name) return self._model._var_basis_status1(self) @property def benders_annotation(self): """ This property is used to get or set the Benders annotation of a variable. The value of the annotation must be a positive integer """ return self.get_benders_annotation() @benders_annotation.setter def benders_annotation(self, new_anno): self.set_benders_annotation(new_anno)
[docs] def iter_constraints(self): """ Returns an iterator traversing all constraints in which the variable is used. :return: An iterator. """ for ct in self._model.iter_constraints(): for ctv in ct.iter_variables(): if ctv is self: yield ct break
[docs] def equals(self, other): """ This method is used to test equality to an expression. Because of the overloading of operator `==` through the redefinition of the `__eq__` method, you cannot use `==` to test for equality. In order to test that two decision variables ar ethe same, use th` Python `is` operator; use the `equals` method to test whether a given expression is equivalent to a variable: for example, calling `equals` with a linear expression which consists of this variable only, with a coefficient of 1, returns True. Args: other: an expression or a variable. :return: A boolean value, True if the passed variable is this very variable, or if the passed expression is equivalent to the variable, else False. """ # noinspection PyPep8 return self is other or \ (isinstance(other, LinearOperand) and other.get_constant() == 0 and other.number_of_terms() == 1 and other.unchecked_get_coef(self) == 1)
def as_logical_operand(self): # INTERNAL return self if self.is_binary() else None def _check_binary_variable_for_logical_op(self, op_name): if not self.is_binary(): self.fatal("Logical {0} is available only for binary variables, {1} has type {2}", op_name, self, self.vartype.short_name) def logical_and(self, other): self._check_binary_variable_for_logical_op(op_name="and") StaticTypeChecker.typecheck_logical_op(self, other, caller="Var.logical_and") return self.lfactory.new_logical_and_expr([self, other]) def logical_or(self, other): self._check_binary_variable_for_logical_op(op_name="or") StaticTypeChecker.typecheck_logical_op(self, other, caller="Var.logical_or") return self.lfactory.new_logical_or_expr([self, other]) def logical_not(self): self._check_binary_variable_for_logical_op(op_name="not") return self.lfactory.new_logical_not_expr(self) def __and__(self, other): return self.logical_and(other) def __or__(self, other): return self.logical_or(other)
# no unary not in magic methods... def is_var(x): return isinstance(x, Var)