def evaluate(self, x_val): """ Evaluates the breaks-based PWL function at the point whose x-coordinate is `x_val`. Args: x_val: The x value for which we want to compute the value of the function. Returns: The value of the PWL function at point `x_val` A DOcplexException exception is raised when evaluating at a discontinuity of the PWL function. """ prev_break_index, index = -1, 0 while index < len(self.breaksxy): break_1, break_2, index = self._get_break_at_index(index) if break_1 is None: raise DOcplexException( "Invalid PWL definition: no break point is defined") if break_1[0] < x_val: prev_break_index = index else: if break_1[0] == x_val and break_2 is not None: raise DOcplexException( "Cannot evaluate PWL at a discontinuity") break index += 1 y_val, _, _ = self._get_y_value(x_val, prev_break_index) return y_val
def compile_naming_function(keys, user_name, dimension=1, key_format=None, _default_key_format='_%s', stringifier=str_flatten_tuple): # INTERNAL # builds a naming rule from an input , a dimension, and an optional meta-format # Makes sure the format string does contain the right number of format slots assert user_name is not None if is_string(user_name): if key_format is None: used_key_format = _default_key_format elif is_string(key_format): # -- make sure some %s is inside, otherwise add it if '%s' in key_format: used_key_format = key_format else: used_key_format = key_format + '%s' else: # pragma: no cover raise DOcplexException( "key format expects string format or None, got: {0!r}".format( key_format)) fixed_format_string = fix_format_string(user_name, dimension, used_key_format) if 1 == dimension: return lambda k: fixed_format_string % stringifier(k) else: # here keys are tuples of size >= 2 return lambda key_tuple: fixed_format_string % key_tuple elif is_function(user_name): return user_name elif is_iterable(user_name): # check that the iterable has same len as keys, # otherwise thereis no more default naming and None cannot appear in CPLEX name arrays list_names = list(user_name) if len(list_names) < len(keys): raise DOcplexException( "An array of names should have same len as keys, expecting: {0}, go: {1}" .format(len(keys), len(list_names))) key_to_names_dict = {k: nm for k, nm in izip(keys, list_names)} # use a closure return lambda k: key_to_names_dict[ k] # if k in key_to_names_dict else default_fn() else: raise DOcplexException( 'Cannot use this for naming variables: {0!r} - expecting string, function or iterable' .format(user_name))
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 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 __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 _get_y_value(self, x_coord, prev_break_index=-1): """ :param x_coord: :param prev_break_index: this parameter is mandatory if a breakxy tuple does exist before x_coord. Otherwise an exception is raised. :return: """ if prev_break_index < 0: break_1, break_2, last_ind = self._get_break_at_index(0) if break_1[0] < x_coord: raise DOcplexException( "Invalid arguments passed to PwlAsBreaks._get_y_value()" ) if break_1[0] == x_coord: y_coord_1 = break_1[1] y_coord_2 = None if break_2 is None else break_2[1] return y_coord_1, y_coord_2, last_ind y_coord_1 = break_1[1] - self.preslope * (break_1[0] - x_coord) return y_coord_1, None, -1 break_1, break_2, last_ind = self._get_break_at_index( prev_break_index) next_break_1, next_break_2, next_last_ind = self._get_break_at_index( last_ind + 1) if next_break_1 is None: # x-coord is after last break last_break = break_1 if break_2 is None else break_2 y_coord_1 = last_break[1] + self.postslope * (x_coord - last_break[0]) return y_coord_1, None, last_ind else: if x_coord == break_1[0]: # Here, one must have: x_coord > break_1[0] raise DOcplexException( "Invalid arguments passed to PwlAsBreaks._get_y_value()" ) if x_coord == next_break_1[0]: y_coord_1 = next_break_1[1] y_coord_2 = None if next_break_2 is None else next_break_2[ 1] return y_coord_1, y_coord_2, next_last_ind y_coord_prev = break_1[1] if break_2 is None else break_2[1] y_coord_next = next_break_1[1] slope = (y_coord_next - y_coord_prev) / (next_break_1[0] - break_1[0]) y_coord_1 = y_coord_prev + slope * (x_coord - break_1[0]) return y_coord_1, None, last_ind
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 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 __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 _resolve_cplex(self, env): # INTERNAL if env is None: raise DOcplexException("need an environment to resolve cplex, got None") if not self._is_cplex_resolved(): if env.has_cplex: from docplex.mp.cplex_engine import CplexEngine self._cplex_engine_type = CplexEngine self._engine_types_by_agent["cplex"] = CplexEngine else: self._cplex_engine_type = None
def translate(self, arg): """ Translate this PWL function by a number. This method creates a new PWL function instance for which all breakpoints have been moved along the horizontal axis by the amount specified by `arg`. Args: arg: The number that is used to translate all breakpoints. Returns: The translated PWL function. """ if is_number(arg): return PwlFunction(self.model, self.pwl_def.translate(arg)) else: raise DOcplexException("Invalid type for argument: {0!s}.".format(arg))
def _to_linear_operand(self, e, force_clone=False, msg=None): if isinstance(e, LinearOperand): if force_clone: return e.clone() else: return e elif is_number(e): return self.constant_expr(cst=e, safe_number=False) else: try: return e.to_linear_expr() except AttributeError: # delegate to the factory return self.linear_expr(e) except DocplexQuadToLinearException as qe: used_msg = msg.format(e) if msg else qe.message raise DOcplexException(used_msg)
def __add__(self, arg): if isinstance(arg, PwlFunction._PwlAsSlopes): all_x_coord = sorted({sbr[1] for sbr in self.slopebreaksx + arg.slopebreaksx}) all_slopebreaks_left = self._get_all_slopebreaks(all_x_coord) all_slopebreaks_right = arg._get_all_slopebreaks(all_x_coord) result_slopebreaksxy = [] # Both lists have same size, with same x-coord for slopebreaks # ==> perform the addition of slopes on each break for sbr_l, sbr_r in izip(all_slopebreaks_left, all_slopebreaks_right): if isinstance(sbr_l, tuple) and isinstance(sbr_r, tuple): result_slopebreaksxy.append((sbr_l[0] + sbr_r[0], sbr_l[1])) else: if isinstance(sbr_l, tuple): # sbr_r is a list containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0] + sbr_r[0][0], sbr_l[1])) result_slopebreaksxy.append((sbr_r[1][0], sbr_l[1])) elif isinstance(sbr_r, tuple): # sbr_l is a list containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0][0] + sbr_r[0], sbr_r[1])) result_slopebreaksxy.append((sbr_l[1][0], sbr_r[1])) else: # sbr_l and sbr_r are two lists, each containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0][0] + sbr_r[0][0], sbr_l[0][1])) result_slopebreaksxy.append((sbr_l[1][0] + sbr_r[1][0], sbr_l[0][1])) result_lastslope = self.lastslope + arg.lastslope if self.anchor[0] == arg.anchor[0]: result_anchor = (self.anchor[0], self.anchor[1] + arg.anchor[1]) else: # Compute a new anchor based on the last x-coord in the slopebreakx list + anchor point anchor_l = self._get_safe_xy_anchor() anchor_r = arg._get_safe_xy_anchor() delta = anchor_r[0] - anchor_l[0] if anchor_l[0] < anchor_r[0]: result_anchor = (anchor_r[0], anchor_l[1] + anchor_r[1] + delta * self.lastslope) else: result_anchor = (anchor_l[0], anchor_l[1] + anchor_r[1] - delta * arg.lastslope) return PwlFunction._PwlAsSlopes(*self._remove_useless_intermediate_slopes( result_slopebreaksxy, result_lastslope, result_anchor)) 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 multiply(self, arg): """ Multiplies this PWL function by a number. Note: This method does not create a new function but modifies the `self` instance. Args: arg: The number that is used to multiply `self`. Returns: The modified `self`. """ if 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 plot(self, lx=None, rx=None, k=1, **kwargs): # pragma: no cover """ This method displays the piecewise linear function using the matplotlib package, if found. :param lx: The value to show the `preslope` (must be before the first breakpoint x value). :param rx: The value to show the `postslope` (must be after the last breakpoint x value). :param k: Scaling factor to calculate default values for `rx` and/or `lx` if these arguments are not provided, based on mean interval length between the `x` values of breakpoints. :param kwargs: additional arguments to be passed to matplotlib plot() function """ try: import matplotlib.pyplot as plt except ImportError: raise DOcplexException('matplotlib is required for plot()') bks = self.pwl_def_as_breaks.breaksxy xs = [bk[0] for bk in bks] ys = [bk[1] for bk in bks] # compute mean delta_x first_x = xs[0] last_x = xs[-1] nb_intervals = self._pwl_def_as_breaks.get_nb_intervals() # k times the mean interval length is used for left/right extra points kdx_m = k * (last_x - first_x) / float(nb_intervals) if nb_intervals > 0 else 1 if lx is None: lx = first_x - kdx_m ly = ys[0] - self.pwl_def_as_breaks.preslope * (first_x - lx) xs.insert(0, lx) ys.insert(0, ly) if rx is None or rx <= last_x: rx = last_x + kdx_m ry = ys[-1] + self.pwl_def_as_breaks.postslope * (rx - last_x) xs.append(rx) ys.append(ry) if plt: plt.plot(xs, ys, **kwargs) if self.name: plt.title('pwl: {0}'.format(self.name)) plt.show()
def parse(cls, arg, default_level=INFO): # INTERNAL if not arg: return default_level elif isinstance(arg, cls): return arg elif is_string(arg): return cls._name2level_map().get(arg.lower(), default_level) elif is_int(arg): if arg < 10: # anything below 10 is INFO return cls.INFO elif arg < 100: return cls.WARNING elif arg < 1000: return cls.ERROR else: # level fatal prints nothing except fatal errors return cls.FATAL else: raise DOcplexException("Cannot convert this to InfoLevel: {0!r}".format(arg))
def terminate(self): raise DOcplexException("model has been terminated, no solve is possible...")
def set_lp_start(self, var_stats, ct_stats): raise DOcplexException('set_lp_start() requires CPLEX, not available for {0}'.format(self.name))
def get_cplex(self): raise DOcplexException("No CPLEX is available.") # pragma: no cover
def docplex_fatal(msg, *args): resolved_message = resolve_pattern(msg, args) docplex_error_stop_here() raise DOcplexException(resolved_message)
def fatal(self, msg, args=None): self._number_of_fatals += 1 resolved_message = resolve_pattern(msg, args) docplex_error_stop_here() raise DOcplexException(resolved_message)
def _simulate_error(): raise DOcplexException("simulate exception")
def get_cplex(self): raise DOcplexException( "{0} engine contains no instance of CPLEX".format(self.name))
def cannot_modify(self, expr): raise DOcplexException("{0}: {1}".format(self._msg, expr))