# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2022
# --------------------------------------------------------------------------
# pylint: disable=too-many-lines
from docplex.mp.constants import ComparisonType, UpdateEvent
from docplex.mp.basic import Expr, ModelingObjectBase, _SubscriptionMixin
from docplex.mp.operand import LinearOperand
from docplex.mp.utils import is_int, is_number, iter_emptyset, is_quad_expr
from docplex.mp.dvar import is_var
# ----------------------------
# kept for compatibility
from docplex.mp.dvar import Var
# ------------------
from docplex.mp.sttck import StaticTypeChecker
[docs]class DOCplexQuadraticArithException(Exception):
# INTERNAL
pass
# noinspection PyAbstractClass
[docs]class AbstractLinearExpr(LinearOperand, Expr):
__slots__ = ('_discrete_locked',)
[docs] def get_coef(self, dvar):
""" Returns the coefficient of a variable in the expression.
Note:
If the variable is not present in the expression, the function returns 0.
:param dvar: The variable for which the coefficient is being queried.
:return: A floating-point number.
"""
self.model._typecheck_var(dvar)
return self.unchecked_get_coef(dvar)
def __getitem__(self, dvar):
# direct access to a variable coef x[var]
return self.unchecked_get_coef(dvar)
def __iter__(self):
# INTERNAL: this is necessary to prevent expr from being an iterable.
# as it follows getitem protocol, it can mistakenly be interpreted as an iterable
# but this would make sum loop forever.
raise TypeError
def lock_discrete(self):
# intern al: used for any expression used in linear constraints inside equivalences
self._discrete_locked = True
def is_discrete_locked(self):
return getattr(self, '_discrete_locked', False)
def check_discrete_lock_frozen(self, item=None):
self.get_linear_factory().check_expr_discrete_lock(self, item)
def relaxed_copy(self, relaxed_model, var_map):
return self.copy(relaxed_model, var_map)
def set_coefficients(self, var_coef_seq):
for dv, k in var_coef_seq:
self.set_coefficient(dv, k)
[docs]class MonomialExpr(_SubscriptionMixin, AbstractLinearExpr):
# INTERNAL
def _raw_solution_value(self, s=None):
return self.coef * self._dvar._raw_solution_value(s)
# INTERNAL class
__slots__ = ('_dvar', '_coef', '_subscribers')
# noinspection PyMissingConstructor
def __init__(self, model, dvar, coeff, safe=False):
self._model = model # faster than to call recursively init methods...
self._name = None
self._dvar = dvar
self._subscribers = []
if safe:
self._coef = coeff
else:
validfn = model._checker.get_number_validation_fn()
self._coef = validfn(coeff) if validfn else coeff
[docs] def number_of_variables(self):
return 1
def __hash__(self):
# py3 requires this function
return id(self)
@property
def var(self):
return self._dvar
@property
def coef(self):
return self._coef
@property
def constant(self):
# for compatibility
return 0
def as_variable(self):
# INTERNAL
return self._dvar if 1 == self._coef else None
def clone(self):
return self.__class__(self.model, self._dvar, self._coef, safe=True)
def copy(self, target_model, var_mapping):
copy_var = var_mapping[self._dvar]
return MonomialExpr(target_model, dvar=copy_var, coeff=self._coef, safe=True)
def iter_terms(self):
yield self._dvar, self._coef
iter_sorted_terms = iter_terms
def unchecked_get_coef(self, dvar):
return self._coef if dvar is self._dvar else 0
[docs] def contains_var(self, dvar):
return self._dvar is dvar
def is_normalized(self):
# INTERNAL
return self._coef != 0 # pragma: no cover
def is_discrete(self):
return self._dvar.is_discrete() and is_int(self._coef)
# arithmetics
def negate(self):
self._coef = - self._coef
self.notify_modified(event=UpdateEvent.LinExprCoef)
return self
def plus(self, e):
if isinstance(e, LinearOperand) or is_number(e):
return self.to_linear_expr().add(e)
else:
return e.plus(self)
def minus(self, e):
if isinstance(e, LinearOperand) or is_number(e):
expr = self.to_linear_expr()
expr.subtract(e)
return expr
else:
return e.rminus(self)
def times(self, e):
if is_number(e):
if 0 == e:
return self.get_linear_factory().new_zero_expr()
else:
# return a fresh instance
return MonomialExpr(self._model, self._dvar, self._coef * e, safe=True)
elif isinstance(e, LinearExpr):
return e.times(self)
elif is_var(e):
return self.model._qfactory.new_var_product(e, self)
elif isinstance(e, MonomialExpr):
return self.model._qfactory.new_monomial_product(self, e)
else:
expr = self.to_linear_expr()
return expr.multiply(e)
def square(self):
return self.model._qfactory.new_monomial_product(self, self)
def quotient(self, e):
# returns a new instance
self._model._typecheck_as_denominator(e, self)
inverse = 1.0 / float(e)
return MonomialExpr(self._model, self._dvar, self._coef * inverse, safe=True)
def __add__(self, e):
return self.plus(e)
def __radd__(self, e):
return self.__add__(e)
def __sub__(self, e):
return self.minus(e)
def __rsub__(self, e):
return self.get_linear_factory()._to_linear_operand(e, force_clone=True).minus(self)
def __neg__(self):
opposite = self.clone()
return opposite.negate()
def __mul__(self, e):
return self.times(e)
def __rmul__(self, e):
return self.times(e)
def __div__(self, e):
return self.quotient(e)
def __truediv__(self, e):
# for py3
# INTERNAL
return self.__div__(e) # pragma: no cover
def __rtruediv__(self, e):
# for py3
self.model.cannot_be_used_as_denominator_error(self, e) # pragma: no cover
def __rdiv__(self, e):
self.model.cannot_be_used_as_denominator_error(self, e) # pragma: no cover
# changing a coef
def set_coefficient(self, dvar, coef):
m = self._model
m._typecheck_var(dvar)
m._typecheck_num(coef, 'Expr.set_coefficient()')
return self._set_coefficient(dvar, coef)
set_coef = set_coefficient
def _set_coefficient(self, dvar, coef):
self.check_discrete_lock_frozen(item=coef)
if dvar is self._dvar:
self._coef = coef
self.notify_modified(event=UpdateEvent.LinExprCoef)
if not coef:
self.notify_replaced(new_expr=self.lfactory.new_zero_expr())
elif coef:
# monomail is extended to a linear expr
new_self = self.to_linear_expr()
new_self._add_term(dvar, coef)
# beware self is modified here
self.notify_replaced(new_self)
# noinspection PyMethodFirstArgAssignment
self = new_self
return self
# -- arithmetic to self
def __iadd__(self, other):
return self._add_to_self(other)
def _add_to_self(self, other):
self.check_discrete_lock_frozen(item=other)
if isinstance(other, LinearOperand) or is_number(other):
added = self.to_linear_expr().add(other)
else:
added = other.plus(self)
self.notify_replaced(added)
return added
def add(self, other):
return self._add_to_self(other)
def __isub__(self, other):
return self._sub_to_self(other)
def _sub_to_self(self, other):
# INTERNAL
self.check_discrete_lock_frozen(item=other)
if isinstance(other, LinearOperand) or is_number(other):
expr = self.to_linear_expr()
expr.subtract(other)
subtracted = expr
else:
subtracted = other.rminus(self)
self.notify_replaced(subtracted)
return subtracted
def subtract(self, other):
return self._sub_to_self(other)
def __imul__(self, e):
return self.multiply(e)
def multiply(self, e):
self.check_discrete_lock_frozen(e)
if is_number(e):
if 0 == e:
product = self.get_linear_factory().new_zero_expr()
else:
self._coef *= e
self.notify_modified(event=UpdateEvent.LinExprCoef)
product = self
elif isinstance(e, LinearExpr):
product = e.times(self)
elif is_var(e):
product = self.model._qfactory.new_var_product(e, self)
elif isinstance(e, MonomialExpr):
product = self.model._qfactory.new_monomial_product(self, e)
elif is_quad_expr(e):
if e.has_quadratic_term():
StaticTypeChecker.mul_quad_lin_error(self._model, self, e)
else:
product = self.model._qfactory.new_monomial_product(self, e.linear_part)
else:
product = self.to_linear_expr().multiply(e)
self.notify_replaced(product)
return product
mul = multiply
def __idiv__(self, other):
return self.divide(other) # pragma: no cover
def __itruediv__(self, other): # pragma: no cover
# for py3
return self.divide(other)
def divide(self, other):
self._model._typecheck_as_denominator(other, self)
inverse = 1.0 / float(other)
self.check_discrete_lock_frozen(inverse)
self._coef *= inverse
self.notify_modified(event=UpdateEvent.LinExprCoef)
return self
def equals(self, other):
return isinstance(other, LinearOperand) and \
other.get_constant() == 0 and \
other.number_of_terms() == 1 and \
other.unchecked_get_coef(self._dvar) == self._coef
# conversion
def to_linear_expr(self):
e = LinearExpr(self._model, e=(self._dvar, self._coef), safe=True, transient=True)
return e
def to_stringio(self, oss, nb_digits, use_space, var_namer=lambda v: v.lp_name):
self_coef = self._coef
if self_coef != 1:
if self_coef < 0:
oss.write(u'-')
self_coef = - self_coef
if self_coef != 1:
self._num_to_stringio(oss, num=self_coef, ndigits=nb_digits)
if use_space:
oss.write(u' ')
oss.write(str(var_namer(self._dvar)))
def __repr__(self):
return "docplex.mp.MonomialExpr(%s)" % self.to_string()
# from private.debug_deco import count_instances
#
# @count_instances
[docs]class LinearExpr(_SubscriptionMixin, AbstractLinearExpr):
"""LinearExpr()
This class models linear expressions.
This class is not intended to be instantiated. Expressions are built
either using operators or using `Model.linear_expr()`.
"""
@staticmethod
def _new_terms_dict(model, *args, **kwargs):
return model._lfactory.term_dict_type(*args, **kwargs)
# @staticmethod
# def _new_empty_terms_dict(model):
# return model._lfactory.term_dict_type()
def to_linear_expr(self):
return self
def _get_terms_dict(self):
# INTERNAL
return self._terms
__slots__ = ('_constant', '_terms', '_transient', '_subscribers')
def __hash__(self):
# py3 requires this function
return id(self)
def __init__(self, model, e=None, constant=0, safe=False, transient=False):
ModelingObjectBase.__init__(self, model)
if not safe and constant:
model._typecheck_num(constant, 'LinearExpr()')
self._constant = constant
self._transient = transient
self._subscribers = []
if isinstance(e, dict):
if safe:
self._terms = e
else:
self_terms = model._lfactory.term_dict_type()
for (v, k) in e.items():
model._typecheck_var(v)
model._typecheck_num(k, 'LinearExpr')
if k != 0:
self_terms[v] = k
self._terms = self_terms
return
else:
self._terms = model._lfactory._new_term_dict()
if e is None:
pass
elif is_var(e):
self._terms[e] = 1
elif is_number(e):
self._constant += e
elif isinstance(e, MonomialExpr):
# TODO: simplify by self_terms[e.var] = e.coef
self._add_term(e.var, e.coef)
elif isinstance(e, LinearExpr):
# note that transient is not kept.
self._constant = e.get_constant()
self._terms = self._new_terms_dict(model, e._get_terms_dict()) # make a copy
elif isinstance(e, tuple):
v, k = e
self._terms[v] = k
else:
self.fatal("Cannot convert {0!r} to docplex.mp.LinearExpr, type is {1}", e, type(e))
def keep(self):
self._transient = False
return self
def is_kept(self):
# INTERNAL
return not self._transient
def is_transient(self): # pragma: no cover
# INTERNAL
return self._transient
def clone_if_necessary(self):
# INTERNAL
if self._transient and not self._model._keep_all_exprs and not self.is_in_use():
return self
else:
return self.clone()
# def set_name(self, name):
# Expr.set_name(self, name)
# # an expression with a name is not transient any more
# if name:
# self.keep()
#
# def _get_name(self):
# return self._name
#
# name = property(_get_name, set_name)
# from private.debug_deco import count_calls
# @count_calls
[docs] def clone(self):
"""
Returns:
A copy of the expression on the same model.
"""
cloned_terms = self._new_terms_dict(self._model, self._terms) # faster than copy() on OrderedDict()
cloned = LinearExpr(model=self._model, e=cloned_terms, constant=self._constant, safe=True)
return cloned
def copy(self, target_model, var_mapping):
# INTERNAL
copied_terms = self._new_terms_dict(target_model)
for v, k in self.iter_sorted_terms():
copied_terms[var_mapping[v]] = k
copied_expr = LinearExpr(model=target_model, e=copied_terms, constant=self.constant, safe=True)
return copied_expr
[docs] def negate(self):
""" Takes the negation of an expression.
Changes the expression by replacing each variable coefficient and the constant term
by its opposite.
Note:
This method does not create any new expression but modifies the `self` instance.
Returns:
The modified self.
"""
self._constant = - self._constant
self_terms = self._terms
for v, k in self_terms.items():
self_terms[v] = -k
self.notify_modified(event=UpdateEvent.LinExprGlobal)
return self
def _clear(self):
""" Clears the expression.
All variables and coefficients are removed and the constant term is set to zero.
"""
self._constant = 0
self._terms.clear()
[docs] def equals_constant(self, scalar):
""" Checks if the expression equals a constant term.
Args:
scalar (float): A floating-point number.
Returns:
Boolean: True if the expression equals this constant term.
"""
return self.is_constant() and (scalar == self._constant)
def is_zero(self):
return self.equals_constant(0)
[docs] def is_constant(self):
"""
Checks if the expression is a constant.
Returns:
Boolean: True if the expression consists of only a constant term.
"""
return not self._terms
def as_variable(self):
# INTERNAL: returns True if expression is in fact a variable (1*x)
if 1 == len(self._terms) and not self._constant:
for v, k in self.iter_terms():
if k == 1:
return v
return None
def is_normalized(self):
# INTERNAL
return all(k for _, k in self.iter_terms() )
def normalize(self):
doomed = [dv for dv, k in self.iter_terms() if not k]
lterms = self._terms
for d in doomed:
del lterms[d]
[docs] def number_of_variables(self):
return len(self._terms)
def unchecked_get_coef(self, dvar):
# INTERNAL
return self._terms.get(dvar, 0)
[docs] def add_term(self, dvar, coeff):
"""
Adds a term (variable and coefficient) to the expression.
Args:
dvar (:class:`Var`): A decision variable.
coeff (float): A floating-point number.
Returns:
The modified expression itself.
"""
if coeff:
self._model._typecheck_var(dvar)
self._model._typecheck_num(coeff)
self._add_term(dvar, coeff)
self.notify_modified(event=UpdateEvent.LinExprCoef)
return self
def _add_term(self, dvar, coef=1):
# INTERNAL
self_terms = self._terms
new_coef = self_terms.get(dvar, 0) + coef
if new_coef:
self_terms[dvar] = new_coef
else:
try:
del self_terms[dvar]
except KeyError:
pass
def set_coefficient(self, dvar, coeff):
self._model._typecheck_var(dvar)
self._model._typecheck_num(coeff)
self._set_coefficient(dvar, coeff)
set_coef = set_coefficient
def _set_coefficient_internal(self, dvar, coeff):
self_terms = self._terms
if coeff or dvar in self_terms:
self_terms[dvar] = coeff
return True
else:
return False
def _set_coefficient(self, dvar, coeff):
self.check_discrete_lock_frozen(coeff)
if self._set_coefficient_internal(dvar, coeff):
self.notify_modified(event=UpdateEvent.LinExprCoef)
if not coeff:
self.normalize()
def set_coefficients(self, var_coef_seq):
# TODO: typecheck
self._set_coefficients(var_coef_seq)
set_coefs = set_coefficients
def _set_coefficients(self, var_coef_seq):
self.check_discrete_lock_frozen()
nb_changes = 0
nb_nulls = 0
for dv, k in var_coef_seq:
if self._set_coefficient_internal(dv, k):
nb_changes += 1
if not k:
nb_nulls += 1
if nb_changes:
self.notify_modified(event=UpdateEvent.LinExprCoef)
if nb_nulls:
self.normalize()
[docs] def remove_term(self, dvar):
""" Removes a term associated with a variable from the expression.
Args:
dvar (:class:`Var`): A decision variable.
Returns:
The modified expression.
"""
self.set_coefficient(dvar, 0)
@property
def constant(self):
"""
This property is used to get or set the constant term of the expression.
"""
return self._constant
@constant.setter
def constant(self, new_constant):
self._set_constant(new_constant)
def get_constant(self):
return self._constant
def _set_constant(self, new_constant):
if new_constant != self._constant:
self.check_discrete_lock_frozen(new_constant)
self._constant = new_constant
self.notify_modified(event=UpdateEvent.ExprConstant)
[docs] def contains_var(self, dvar):
""" Checks whether a decision variable is part of an expression.
Args:
dvar (:class:`Var`): A decision variable.
Returns:
Boolean: True if `dvar` is mentioned in the expression with a nonzero coefficient.
"""
return dvar in self._terms
[docs] def equals(self, other):
"""
This method is used to test equality between expressions.
Because of the overloading of operator `==` through the redefinition of
the `__eq__` method, you cannot use `==` to test for equality.
The `equals` method to test whether a given expression is equivalent to a variable.
Two linear expressions are equivalent if they have the same coefficient for all
variables.
Args:
other: a number or any expression.
Returns:
A boolean value, True if the passed expression is equivalent, else False.
Note:
A constant expression is considered equivalent to its constant number.
m.linear_expression(3).equals(3) returns True
"""
if is_number(other):
return self.is_constant() and other == self.constant
else:
if not isinstance(other, LinearOperand):
return False
if self.constant != other.get_constant():
return False
if self.number_of_terms() != other.number_of_terms():
return False
for dv, k in self.iter_terms():
if k != other.unchecked_get_coef(dv):
return False
return True
# noinspection PyPep8
def to_stringio(self, oss, nb_digits, use_space, var_namer=lambda v: v.lp_name):
# INTERNAL
# Writes unicode representation of self
c = 0
# noinspection PyPep8Naming
SP = u' '
for v, coeff in self.iter_sorted_terms():
if not coeff:
continue # pragma: no cover
# 1 separator
if use_space and c > 0:
oss.write(SP)
# ---
# sign is printed if non-first OR negative
# at the end of this block coeff is positive
if coeff < 0 or c > 0:
oss.write(u'-' if coeff < 0 else u'+')
if coeff < 0:
coeff = -coeff
if use_space and c > 0:
oss.write(SP)
# ---
if 1 != coeff:
self._num_to_stringio(oss, coeff, nb_digits)
if use_space:
oss.write(SP)
varname = var_namer(v)
oss.write(str(varname))
c += 1
k = self.constant
if c == 0:
self._num_to_stringio(oss, k, nb_digits)
elif k != 0:
if k < 0:
sign = u'-'
k = -k
else:
sign = u'+'
if use_space:
oss.write(SP)
oss.write(sign)
if use_space:
oss.write(SP)
self._num_to_stringio(oss, k, nb_digits)
def _add_expr(self, other_expr):
# INTERNAL
self._constant += other_expr.get_constant()
# merge term dictionaries
for v, k in other_expr.iter_terms():
# use unchecked version
self._add_term(v, k)
def _add_expr_scaled(self, expr, factor):
# INTERNAL: used by quadratic
if factor:
self._constant += expr.get_constant() * factor
for v, k in expr.iter_terms():
# use unchecked version
self._add_term(v, k * factor)
# --- algebra methods always modify self.
[docs] def add(self, e):
""" Adds an expression to self.
Note:
This method does not create an new expression but modifies the `self` instance.
Args:
e: The expression to be added. Can be a variable, an expression, or a number.
Returns:
The modified self.
See Also:
The method :func:`plus` to compute a sum without modifying the self instance.
"""
event = UpdateEvent.LinExprGlobal
if is_var(e):
self._add_term(e, coef=1)
elif isinstance(e, LinearOperand):
self._add_expr(e)
if isinstance(e, ZeroExpr):
event = None
elif is_number(e):
validfn = self._model._checker.get_number_validation_fn()
valid_e = validfn(e) if validfn else e
self._constant += valid_e
event = UpdateEvent.ExprConstant
elif is_quad_expr(e):
raise DOCplexQuadraticArithException
else:
try:
self.add(e.to_linear_expr())
except AttributeError:
self._unsupported_binary_operation(self, "+", e)
self.notify_modified(event=event)
return self
[docs] def iter_terms(self):
""" Iterates over the terms in the expression.
Returns:
An iterator over the (variable, coefficient) pairs in the expression.
"""
return self._terms.items()
def number_of_terms(self):
return len(self._terms)
@property
def size(self):
return len(self._terms) + bool(self._constant)
[docs] def subtract(self, e):
""" Subtracts an expression from this expression.
Note:
This method does not create a new expression but modifies the `self` instance.
Args:
e: The expression to be subtracted. Can be either a variable, an expression, or a number.
Returns:
The modified self.
See Also:
The method :func:`minus` to compute a difference without modifying the `self` instance.
"""
event = UpdateEvent.LinExprCoef
if is_var(e):
self._add_term(e, -1)
elif is_number(e):
self._constant -= e
event = UpdateEvent.ExprConstant
elif isinstance(e, LinearExpr):
if e.is_constant() and 0 == e.get_constant():
return self
else:
# 1. decr constant
self.constant -= e.constant
# merge term dictionaries
for v, k in e.iter_terms():
self._add_term(v, -k)
elif isinstance(e, MonomialExpr):
self._add_term(e.var, -e.coef)
elif isinstance(e, ZeroExpr):
event = None
elif is_quad_expr(e):
#
raise DOCplexQuadraticArithException
else:
try:
self.subtract(e.to_linear_expr())
except AttributeError:
self._unsupported_binary_operation(self, "-", e)
self.notify_modified(event)
return self
def _scale(self, factor):
# INTERNAL: used my multiply
# this method modifies self.
if 0 == factor:
self._clear()
elif factor != 1:
self._constant *= factor
self_terms = self._terms
for v, k in self_terms.items():
self_terms[v] = k * factor
[docs] def multiply(self, e):
""" Multiplies this expression by an expression.
Note:
This method does not create a new expression but modifies the `self` instance.
Args:
e: The expression that is used to multiply `self`.
Returns:
The modified `self`.
See Also:
The method :func:`times` to compute a multiplication without modifying the `self` instance.
"""
mul_res = self
event = UpdateEvent.LinExprGlobal
self_constant = self.get_constant()
if is_number(e):
self._scale(factor=e)
elif isinstance(e, LinearOperand):
if e.is_constant():
# simple scaling
self._scale(factor=e.get_constant())
elif self.is_constant():
# self is constant: import other terms , scaled.
# set constant to zero.
if self_constant:
for lv, lk in e.iter_terms():
self.set_coefficient(dvar=lv, coeff=lk * self_constant)
self._constant *= e.get_constant()
else:
self._scale(factor=0)
else:
# yields a quadratic
mul_res = self.model._qfactory.new_linexpr_product(self, e)
event = UpdateEvent.LinExprPromotedToQuad
# elif isinstance(e, ZeroExpr):
# self._scale(factor=0)
elif is_quad_expr(e):
if not e.number_of_quadratic_terms:
return self.multiply(e.linear_part)
elif self.is_constant():
return e.multiply(self.get_constant())
else:
StaticTypeChecker.mul_quad_lin_error(self._model, self, e)
else:
self.fatal("Multiply expects variable, expr or number, {0!r} was passed (type is {1})", e, type(e))
self.notify_modified(event=event)
return mul_res
def square(self):
return self.model._qfactory.new_linexpr_product(self, self)
[docs] def divide(self, e):
""" Divides this expression by an operand.
Args:
e: The operand by which the self expression is divided. Only nonzero numbers are permitted.
Note:
This method does not create a new expression but modifies the `self` instance.
Returns:
The modified `self`.
"""
self.model._typecheck_as_denominator(e, numerator=self)
inverse = 1.0 / float(e)
return self.multiply(inverse)
# operator-based API
def opposite(self):
cloned = self.clone_if_necessary()
cloned.negate()
return cloned
[docs] def plus(self, e):
""" Computes the sum of the expression and some operand.
Args:
e: the expression to add to self. Can be either a variable, an expression or a number.
Returns:
a new expression equal to the sum of the self expression and `e`
Note:
This method doe snot modify self.
"""
cloned = self.clone_if_necessary()
try:
return cloned.add(e)
except DOCplexQuadraticArithException:
return e.plus(self)
def minus(self, e):
cloned = self.clone_if_necessary()
try:
return cloned.subtract(e)
except DOCplexQuadraticArithException:
return e.rminus(self)
[docs] def times(self, e):
""" Computes the multiplication of this expression with an operand.
Note:
This method does not modify the `self` instance but returns a new expression instance.
Args:
e: The expression that is used to multiply `self`.
Returns:
A new instance of expression.
"""
cloned = self.clone_if_necessary()
return cloned.multiply(e)
[docs] def quotient(self, e):
""" Computes the division of this expression with an operand.
Note:
This method does not modify the `self` instance but returns a new expression instance.
Args:
e: The expression that is used to modify `self`. Only nonzero numbers are permitted.
Returns:
A new instance of expression.
"""
cloned = self.clone_if_necessary()
cloned.divide(e)
return cloned
def __add__(self, e):
return self.plus(e)
def __radd__(self, e):
return self.plus(e)
def __iadd__(self, e):
try:
self.add(e)
return self
except DOCplexQuadraticArithException:
r = e + self
self.notify_replaced(new_expr=r)
return r
def __sub__(self, e):
return self.minus(e)
def __rsub__(self, e):
cloned = self.clone_if_necessary()
cloned.subtract(e)
cloned.negate()
return cloned
def __isub__(self, e):
try:
return self.subtract(e)
except DOCplexQuadraticArithException:
r = -e + self
return r
def __neg__(self):
return self.opposite()
def __mul__(self, e):
return self.times(e)
def __rmul__(self, e):
return self.times(e)
def __imul__(self, e):
return self.multiply(e)
def __div__(self, e):
return self.quotient(e)
def __idiv__(self, other):
return self.divide(other) # pragma: no cover
def __itruediv__(self, other):
# this is for Python 3.z
return self.divide(other) # pragma: no cover
def __truediv__(self, e):
return self.__div__(e) # pragma: no cover
def __rtruediv__(self, e):
self.fatal("Expression {0!s} cannot be used as divider of {1!s}", self, e) # 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.
"""
return super().solution_value
def _raw_solution_value(self, s=None):
# INTERNAL: no checks
val = self._constant
sol = s or self._model.solution
for var, koef in self.iter_terms():
val += koef * sol._get_var_value(var)
return val
[docs] def is_discrete(self):
""" Checks if the expression contains only discrete variables and coefficients.
Example:
If X is an integer variable, X, X+1, 2X+3 are discrete
but X+0.3, 1.5X, 2X + 0.7 are not.
Returns:
Boolean: True if the expression contains only discrete variables and coefficients.
"""
self_cst = self._constant
if self_cst != int(self_cst):
# a float constant with integer value is OK
return False
for v, k in self.iter_terms():
if not v.is_discrete() or not is_int(k):
return False
return True
def __repr__(self):
return "docplex.mp.LinearExpr({0})".format(self.repr_str())
def _iter_sorted_terms(self):
# internal
self_terms = self._terms
for dv in sorted(self_terms.keys(), key=lambda v: v._index):
yield dv, self_terms[dv]
def iter_sorted_terms(self):
if self._model.keep_ordering:
return self.iter_terms()
else:
return self._iter_sorted_terms()
LinearConstraintType = ComparisonType
[docs]class ZeroExpr(_SubscriptionMixin, AbstractLinearExpr):
def _raw_solution_value(self, s=None):
return 0
def is_zero(self):
return True
# INTERNAL
__slots__ = ('_subscribers',)
def __hash__(self):
return id(self)
def __init__(self, model):
ModelingObjectBase.__init__(self, model)
self._subscribers = []
def clone(self):
return self # this is not cloned.
def copy(self, target_model, var_mapping):
return ZeroExpr(target_model)
def to_linear_expr(self):
return self # this is a linear expr.
[docs] def number_of_variables(self):
return 0
def number_of_terms(self):
return 0
def iter_terms(self):
return iter_emptyset()
def is_constant(self):
return True
def is_discrete(self):
return True
def unchecked_get_coef(self, dvar):
return 0
[docs] def contains_var(self, dvar):
return False
@property
def constant(self):
# for compatibility
return 0
@constant.setter
def constant(self, newk):
if newk:
cexpr = self.get_linear_factory().constant_expr(newk, safe_number=False)
self.notify_replaced(cexpr)
def negate(self):
return self
# noinspection PyMethodMayBeStatic
def plus(self, e):
return e
def times(self, _):
return self
# noinspection PyMethodMayBeStatic
def minus(self, e):
return -e
def to_string(self, nb_digits=None, use_space=False):
return '0'
def to_stringio(self, oss, nb_digits, use_space, var_namer=lambda v: v.name):
oss.write(self.to_string())
# arithmetic
def __sub__(self, e):
return self.minus(e)
def __rsub__(self, e):
# e - 0 = e !
return e
def __neg__(self):
return self
def __add__(self, other):
return other
def __radd__(self, other):
return other
def __mul__(self, other):
return self
def __rmul__(self, other):
return self
def __div__(self, other):
return self._divide(other)
def __truediv__(self, e):
# for py3
# INTERNAL
return self.__div__(e) # pragma: no cover
def _divide(self, other):
self.model._typecheck_as_denominator(numerator=self, denominator=other)
return self
def __repr__(self):
return "docplex.mp.ZeroExpr()"
def equals(self, other):
return (isinstance(other, LinearOperand) and
(0 == other.get_constant() and (0 == other.number_of_terms()))) or \
(is_number(other) and other == 0)
def square(self):
return self
# arithmetic to self
add = plus
subtract = minus
multiply = times
def __iadd__(self, other):
linear_other = self.get_linear_factory()._to_linear_operand(other, force_clone=False)
self.notify_replaced(linear_other)
return linear_other
def __isub__(self, other):
linear_other = self.get_linear_factory()._to_linear_operand(other, force_clone=True)
negated = linear_other.negate()
self.notify_replaced(negated)
return negated
[docs]class ConstantExpr(_SubscriptionMixin, AbstractLinearExpr):
__slots__ = ('_constant', '_subscribers')
def __init__(self, model, cst):
ModelingObjectBase.__init__(self, model=model, name=None)
# assume constant is a number (to be checked upfront)
self._constant = cst
self._subscribers = []
@property
def size(self):
return 1 if self._constant else 0
# INTERNAL
def _make_new_constant(self, new_value):
return ConstantExpr(self._model, new_value)
def _raw_solution_value(self, s=None):
return self._constant
def is_zero(self):
return 0 == self._constant
def clone(self):
return self.__class__(self._model, self._constant)
def copy(self, target_model, var_mapping):
return self.__class__(target_model, self._constant)
def to_linear_expr(self):
return self # this is a linear expr.
[docs] def number_of_variables(self):
return 0
[docs] def iter_variables(self):
return iter_emptyset()
def iter_terms(self):
return iter_emptyset()
def is_constant(self):
return True
def is_discrete(self):
return is_int(self._constant)
def unchecked_get_coef(self, dvar):
return 0
[docs] def contains_var(self, dvar):
return False
def set_coefficients(self, var_coef_seq):
pass
@property
def constant(self):
return self._constant
@constant.setter
def constant(self, new_constant):
self._set_constant(new_constant)
def get_constant(self):
return self._constant
def _set_constant(self, new_constant):
if new_constant != self._constant:
self.check_discrete_lock_frozen(new_constant)
self._constant = new_constant
self.notify_modified(event=UpdateEvent.ExprConstant)
def negate(self):
return self._make_new_constant(- self._constant)
def _apply_op(self, pyop, arg):
if is_number(arg):
return self._make_new_constant(pyop(self.constant, arg))
else:
return pyop(arg, self._constant)
# noinspection PyMethodMayBeStatic
def plus(self, e):
import operator
return self._apply_op(operator.add, e)
def times(self, e):
if is_number(e):
return self.__class__(self._model, e * self._constant)
else:
return e * self._constant
# noinspection PyMethodMayBeStatic
def minus(self, e):
return self + (-e)
def to_string(self, nb_digits=None, use_space=False):
return '{0}'.format(self._constant)
def to_stringio(self, oss, nb_digits, use_space, var_namer=lambda v: v.name):
self._num_to_stringio(oss, self._constant, nb_digits)
# arithmetic
def __sub__(self, e):
return self.minus(e)
def __rsub__(self, e):
# e - k = e !
return e - self._constant
def __neg__(self):
return self._make_new_constant(- self._constant)
def __add__(self, other):
return self.plus(other)
def __radd__(self, other):
return self.plus(other)
def __mul__(self, other):
return self.times(other)
def __rmul__(self, other):
return self.times(other)
def __div__(self, other):
return self._divide(other)
def __truediv__(self, e):
# for py3
# INTERNAL
return self.__div__(e) # pragma: no cover
def _divide(self, other):
self.model._typecheck_as_denominator(numerator=self, denominator=other)
return self._make_new_constant(self._constant / other)
def __repr__(self):
return 'docplex.mp.linear.ConstantExpr({0})'.format(self._constant)
def equals_expr(self, other):
return isinstance(other, ConstantExpr) and self._constant == other.constant
def square(self):
return self._make_new_constant(self._constant ** 2)
# arithmetci to self
def _scale(self, factor):
return self._make_new_constant(self._constant * factor)
def equals(self, other):
if is_number(other):
return self._constant == other
else:
return isinstance(other, LinearOperand) \
and other.is_constant() and \
self._constant == other.get_constant()
# arithmetic to self
def __iadd__(self, other):
return self.add(other)
def add(self, other):
if is_number(other):
self._constant += other
self.notify_modified(UpdateEvent.ExprConstant)
return self
elif isinstance(other, LinearOperand) and other.is_constant():
self._constant += other.get_constant()
self.notify_modified(UpdateEvent.ExprConstant)
return self
else:
# replace self by other + self.
added = other.plus(self._constant)
self.notify_replaced(added)
return added
def subtract(self, other):
if is_number(other):
self._constant -= other
self.notify_modified(UpdateEvent.ExprConstant)
return self
elif isinstance(other, LinearOperand) and other.is_constant():
self._constant -= other.get_constant()
self.notify_modified(UpdateEvent.ExprConstant)
return self
else:
# replace self by (-other) + self.K
subtracted = other.negate().plus(self._constant)
self.notify_replaced(subtracted)
return subtracted
def __isub__(self, other):
return self.subtract(other)
def multiply(self, other):
if is_number(other):
self._constant *= other
self.notify_modified(UpdateEvent.ExprConstant)
return self
elif isinstance(other, LinearOperand) and other.is_constant():
self._constant *= other.get_constant()
self.notify_modified(UpdateEvent.ExprConstant)
return self
else:
# replace self by (-other) + self.K
multiplied = other * self._constant
self.notify_replaced(multiplied)
return multiplied
def __imul__(self, other):
return self.multiply(other)