def __add__(self, arg): if isinstance(arg, PwlFunction._PwlAsBreaks): all_x_coord = sorted({br[0] for br in self.breaksxy + arg.breaksxy}) all_breaks_left = self._get_all_breaks(all_x_coord) all_breaks_right = arg._get_all_breaks(all_x_coord) result_breaksxy = [] # Both lists have same size, with same x-coord for breaks ==> perform the addition on each break for br_l, br_r in izip(all_breaks_left, all_breaks_right): if isinstance(br_l, tuple) and isinstance(br_r, tuple): result_breaksxy.append((br_l[0], br_l[1] + br_r[1])) else: if isinstance(br_l, tuple): # br_r is a list containing 2 tuple pairs result_breaksxy.append((br_l[0], br_l[1] + br_r[0][1])) result_breaksxy.append((br_l[0], br_l[1] + br_r[1][1])) elif isinstance(br_r, tuple): # br_l is a list containing 2 tuple pairs result_breaksxy.append((br_r[0], br_l[0][1] + br_r[1])) result_breaksxy.append((br_r[0], br_l[1][1] + br_r[1])) else: # br_l and br_r are two lists, each containing 2 tuple pairs result_breaksxy.append((br_l[0][0], br_l[0][1] + br_r[0][1])) result_breaksxy.append((br_l[0][0], br_l[1][1] + br_r[1][1])) result_preslope = self.preslope + arg.preslope result_postslope = self.postslope + arg.postslope return PwlFunction._PwlAsBreaks(*self._remove_useless_intermediate_breaks( result_preslope, result_breaksxy, result_postslope)) elif is_number(arg): return PwlFunction._PwlAsBreaks( self.preslope, [(br[0], br[1] + arg) for br in self.breaksxy], self.postslope) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def __mul__(self, arg): if is_number(arg): return PwlFunction._PwlAsSlopes(*self._remove_useless_intermediate_slopes( [(br[0] * arg, br[1]) for br in self.slopebreaksx], self.lastslope * arg, (self.anchor[0], self.anchor[1] * arg))) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def typecheck_optional_num_seq(cls, mdl, nums, accept_none=True, expected_size=None, caller=None): # INTERNAL # accepting either a num an iterable, or None (if accept_none if nums is None and accept_none: return nums elif is_iterable(nums, accept_string=False): nums_ = mdl._checker.typecheck_num_seq(nums, caller) lnums = list(nums_) if expected_size is not None: if len(lnums) != expected_size: caller_string = '' if not caller else '%s: ' % caller mdl.fatal( '{0}Expecting a sequence of numbers of size {1}, actual size is {2}', caller_string, expected_size, len(lnums)) return lnums elif is_number(nums): if expected_size is not None: return [nums] * expected_size else: return nums else: caller_string = '' if not caller else '%s: ' % caller msg = "{0}Expecting a number, a sequence of numbers{1}, {2!r} was passed" none_msg = ' or None' if accept_none else '' mdl.fatal(msg, caller_string, none_msg, nums)
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()
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 isinstance(e, Var): product = self.model._qfactory.new_var_product(e, self) elif isinstance(e, MonomialExpr): product = self.model._qfactory.new_monomial_product(self, e) elif isinstance(e, Expr) and e.is_quad_expr(): 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
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 _sum_with_iter(self, args): sum_of_nums = 0 lcc = self.counter_type() checker = self._checker qcc = None number_validation_fn = checker.get_number_validation_fn() for item in args: if isinstance(item, LinearOperand): for lv, lk in item.iter_terms(): update_dict_from_item_value(lcc, lv, lk) itc = item.get_constant() if itc: sum_of_nums += itc elif is_number(item): sum_of_nums += number_validation_fn(item) if number_validation_fn else item elif isinstance(item, QuadExpr): for lv, lk in item.linear_part.iter_terms(): update_dict_from_item_value(lcc, lv, lk) if qcc is None: qcc = self.counter_type() for qvp, qk in item.iter_quads(): update_dict_from_item_value(qcc, qvp, qk) sum_of_nums += item.get_constant() else: try: expr = item.to_linear_expr() sum_of_nums += expr.get_constant() for dv, k in expr.iter_terms(): update_dict_from_item_value(lcc, dv, k) except AttributeError: self._model.fatal("Model.sum() expects numbers/variables/expressions, got: {0!s}", item) return self._to_expr(qcc, lcc, sum_of_nums)
def scal_prod_triple(self, left_terms, right_terms, coefs): used_coefs = None checker = self._model._checker if is_iterable(coefs, accept_string=False): used_coefs = coefs elif is_number(coefs): if coefs: used_coefs = generate_constant(coefs, count_max=None) else: return self.new_zero_expr() else: self._model.fatal( "scal_prod_triple expects iterable or number as coefficients, got: {0!r}", coefs) if is_iterable(left_terms): used_left = checker.typecheck_var_seq(left_terms) else: checker.typecheck_var(left_terms) used_left = generate_constant(left_terms, count_max=None) if is_iterable(right_terms): used_right = checker.typecheck_var_seq(right_terms) else: checker.typecheck_var(right_terms) used_right = generate_constant(right_terms, count_max=None) if used_coefs is not coefs and used_left is not left_terms and used_right is not right_terms: # LOOK return left_terms * right_terms * coefs return self._scal_prod_triple(coefs=used_coefs, left_terms=used_left, right_terms=used_right)
def typecheck_int(cls, logger, arg, check_math, accept_negative=True, caller=None): if not is_number(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Expecting number, got: {1!r}", (caller_string, arg)) if check_math: if math.isnan(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}NaN value detected", (caller_string, )) elif math.isinf(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Infinite value detected", (caller_string, )) if not is_int(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Expecting integer, got: {1!r}", (caller_string, arg)) elif not accept_negative and arg < 0: caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Expecting positive integer, got: {1!r}", (caller_string, arg))
def static_validate_num2(e, infinity=1e+20, context_msg=None): # checks for number, nans, nath.inf, and truncates to 1e+20 if not is_number(e): docplex_fatal("Not a number: {}".format(e)) elif math.isnan(e): msg = "NaN value found in expression" if context_msg is not None: try: msg = "{0}: {1}".format(context_msg(), msg) except TypeError: msg = "{0}: {1}".format(context_msg, msg) docplex_fatal(msg) elif math.isinf(e): msg = "Infinite value forbidden in expression" if context_msg is not None: try: msg = "{0}: {1}".format(context_msg(), msg) except TypeError: msg = "{0}: {1}".format(context_msg, msg) docplex_fatal(msg) elif -infinity <= e <= infinity: return e elif e >= infinity: return infinity else: return -infinity
def subtract(self, arg): """ Subtracts an expression from this PWL function. Note: This method does not create a new function but modifies the `self` instance. Args: arg: The expression to be subtracted. Can be either a PWL function, or a number. Returns: The modified self. """ if isinstance(arg, PwlFunction): if (isinstance(self.pwl_def, PwlFunction._PwlAsBreaks) and isinstance(arg.pwl_def, PwlFunction._PwlAsBreaks)) or \ (isinstance(self.pwl_def, PwlFunction._PwlAsSlopes) and isinstance(arg.pwl_def, PwlFunction._PwlAsSlopes)): self._pwl_def = self.pwl_def - arg.pwl_def self._set_pwl_definition(self._pwl_def) else: # Use Breaks representation self._pwl_def = self.pwl_def_as_breaks - arg.pwl_def_as_breaks self._set_pwl_definition(self._pwl_def) elif is_number(arg): self._pwl_def = self.pwl_def - arg self._set_pwl_definition(self._pwl_def) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg)) return self
def translate(self, arg): if is_number(arg): return PwlFunction._PwlAsSlopes( [(br[0], br[1] + arg) for br in self.slopebreaksx], self.lastslope, (self.anchor[0] + arg, self.anchor[1])) else: raise DOcplexException("Invalid type for argument: {0!s}.".format(arg))
def _expand_bounds(self, keys, var_bound, default_bound, size, true_if_lb): ''' Converts raw bounds data (either LB or UB) to CPLEX-compatible bounds list. If lbs is None, this is the default, return []. If lbs is [] take the default again. If it is a number, build a list of size <size> with this number. If it is a list, use it if size ok (check numbers??), else try it as a function over keys. ''' if var_bound is None: # default lb is zero, default ub is infinity return [] elif is_number(var_bound): self._checker.typecheck_num(var_bound, caller='in variable bound') if true_if_lb: if var_bound == default_bound: return [] else: return [float(var_bound)] * size else: # ub if var_bound >= default_bound: return [] else: return [float(var_bound)] * size elif is_ordered_sequence(var_bound): nb_bounds = len(var_bound) if nb_bounds < size: # see how we can use defaults for those missing bounds self.fatal( "Variable bounds list is too small, expecting: %d, got: %d" % (size, nb_bounds)) else: return self._check_bounds(size, var_bound, default_bound, true_if_lb) elif is_iterator(var_bound): # unfold the iterator, as CPLEX needs a list return list(var_bound) elif isinstance(var_bound, dict): dict_bounds = [var_bound.get(k, default_bound) for k in keys] return self._check_bounds(size, dict_bounds, default_bound, true_if_lb) else: # try a function? try: fn_bounds = [var_bound(k) for k in keys] return self._check_bounds(size, fn_bounds, default_bound, true_if_lb) except TypeError: self._bad_bounds_fatal(var_bound) except Exception as e: # pragma: no cover self.fatal( "error calling function model bounds: {0!s}, error: {1!s}", var_bound, e)
def __sub__(self, arg): if isinstance(arg, PwlFunction._PwlAsSlopes): return self + arg * (-1) elif is_number(arg): return PwlFunction._PwlAsSlopes(copy.deepcopy(self.slopebreaksx), self.lastslope, (self.anchor[0], self.anchor[1] - arg)) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def __sub__(self, arg): if isinstance(arg, PwlFunction._PwlAsBreaks): return self + arg * (-1) elif is_number(arg): return PwlFunction._PwlAsBreaks( self.preslope, [(br[0], br[1] - arg) for br in self.breaksxy], self.postslope) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def typecheck_int(self, arg, accept_negative=True, caller=None): caller_string = "{0}: ".format(caller) if caller is not None else "" if not is_number(arg): self.fatal("{0}Expecting number, got: {1!r}", caller_string, arg) elif not is_int(arg): self.fatal("{0}Expecting integer, got: {1!r}", caller_string, arg) elif not accept_negative and arg < 0: self.fatal("{0}Expecting positive integer, got: {1!r}", caller_string, arg)
def typecheck_as_denominator(cls, mdl, denominator, numerator): if not is_number(denominator): cls.cannot_be_used_as_denominator_error(mdl, denominator, numerator) else: float_e = float(denominator) if 0 == float_e: mdl.fatal("Zero divide on {0!s}", numerator)
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 typecheck_num(self, arg, caller=None): caller_string = "{0}: ".format(caller) if caller is not None else "" if not is_number(arg): self.fatal("{0}Expecting number, got: {1!r}", caller_string, arg) elif math.isnan(arg): self.fatal("{0}NaN value detected", caller_string) elif math.isinf(arg): self.fatal("{0}Infinite value detected", caller_string)
def __init__(self, model, var_value_map=None, obj=None, blended_obj_by_priority=None, name=None, solved_by=None, keep_zeros=True): """ SolveSolution(model, var_value_map, obj, name) Creates a new solution object, associated to a a model. Args: model: The model to which the solution is associated. This model cannot be changed. obj: The value of the objective in the solution. A value of None means the objective is not defined at the time the solution is created, and will be set later. blended_obj_by_priority: For multi-objective models: the value of sub-problems' objectives (each sub-problem groups objectives having same priority). var_value_map: a Python dictionary containing associations of variables to values. name: a name for the solution. The default is None, in which case the solution is named after the model name. :return: A solution object. """ assert model is not None assert solved_by is None or is_string(solved_by) assert obj is None or is_number(obj) or is_indexable(obj) assert blended_obj_by_priority is None or is_indexable( blended_obj_by_priority) self._model = model self._name = name self._problem_objective_expr = model.objective_expr if model.has_objective( ) else None self._objective = self.NO_OBJECTIVE_VALUE if obj is None else obj self._blended_objective_by_priority = [self.NO_OBJECTIVE_VALUE] if blended_obj_by_priority is None else \ blended_obj_by_priority self._solved_by = solved_by self._var_value_map = {} # attributes self._reduced_costs = None self._dual_values = None self._slack_values = None self._infeasibilities = {} self._basis_statuses = None self._solve_status = None self._keep_zeros = keep_zeros self._solve_details = None if var_value_map is not None: self._store_var_value_map(var_value_map, keep_zeros=keep_zeros)
def static_validate_num(e, checked_num=False, infinity=1e+20): if not checked_num and not is_number(e): docplex_fatal("Expecting number, got: {0!r}".format(e)) elif -infinity <= e <= infinity: return e elif e >= infinity: return infinity else: return -infinity
def typecheck_num_nan_inf(cls, logger, arg, caller=None): # check for a "real" number, not a NaN, not infinity caller_string = "{0}: ".format(caller) if caller is not None else "" if not is_number(arg): logger.fatal("{0}Expecting number, got: {1!r}", caller_string, arg) elif math.isnan(arg): logger.fatal("{0}NaN value detected", caller_string) elif math.isinf(arg): logger.fatal("{0}Infinite value detected", caller_string)
def scal_prod_triple_vars(self, left_terms, right_terms, coefs): """ Creates a quadratic expression from two lists of variables and a sequence of coefficients. This method sums all quadratic terms built by multiplying the i_th coefficient by the product of the i_th expression in `left_terms` and the i_th expression in `right_terms` This method is faster than the standard generic scalar quadratic product method due to the fact that it takes only variables and does not take expressions as arguments. Example: `Model.scal_prod_vars_triple([x, y], [z, t], [2, 3])` returns the expression `2xz + 3yt`. :param left_terms: A list or an iterator on variables. :param right_terms: A list or an iterator on variables. :param coefs: A list or an iterator on numbers or a number. :returns: An instance of :class:`docplex.mp.quad.QuadExpr` or 0. Note: If either list or iterator is empty, this method returns zero. """ used_coefs = None checker = self._checker nb_non_iterables = 0 if is_iterable(coefs, accept_string=False): used_coefs = coefs elif is_number(coefs): if coefs: used_coefs = generate_constant(coefs, count_max=None) nb_non_iterables += 1 else: return self._aggregator.new_zero_expr() else: self.fatal( "scal_prod_triple expects iterable or number as coefficients, got: {0!r}", coefs) if is_iterable(left_terms): used_left = checker.typecheck_var_seq(left_terms) else: nb_non_iterables += 1 checker.typecheck_var(left_terms) used_left = generate_constant(left_terms, count_max=None) if is_iterable(right_terms): used_right = checker.typecheck_var_seq(right_terms) else: nb_non_iterables += 1 checker.typecheck_var(right_terms) used_right = generate_constant(right_terms, count_max=None) if nb_non_iterables >= 3: return left_terms * right_terms * coefs else: return self._aggregator._scal_prod_triple_vars( left_terms=used_left, right_terms=used_right, coefs=used_coefs)
def translate(self, arg): if is_number(arg): return PwlFunction._PwlAsBreaks(self.preslope, [(br[0] + arg, br[1]) for br in self.breaksxy], self.postslope) else: raise DOcplexException( "Invalid type for argument: {0!s}.".format(arg))
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: # 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 isinstance(e, Expr) and e.is_quad_expr(): 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 __init__(self, model, e=None, constant=0, name=None, safe=False, transient=False): ModelingObjectBase.__init__(self, model, name) 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 iteritems(e): 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 isinstance(e, Var): 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 create_cpx_bound_list(cls, bounds, size): if bounds is None: return [] elif is_number(bounds): return [bounds] * size elif is_ordered_sequence(bounds): assert size == len(bounds) return [float(bb) for bb in bounds] else: raise ValueError("Expecting number or sequence of numbers, {0!r} was passed".format(bounds))
def typecheck_num(cls, logger, arg, check_math, caller=None): if not is_number(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Expecting number, got: {1!r}", (caller_string, arg)) elif check_math: if math.isnan(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}NaN value detected", (caller_string,)) elif math.isinf(arg): caller_string = resolve_caller_as_string(caller) logger.fatal("{0}Infinite value detected", (caller_string,))
def __mul__(self, arg): if is_number(arg): return PwlFunction._PwlAsBreaks( *self._remove_useless_intermediate_breaks( self.preslope * arg, [(br[0], br[1] * arg) for br in self.breaksxy], self.postslope * arg)) else: raise DOcplexException( "Invalid type for right hand side operand: {0!s}.".format( arg))
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