def validate(self, equal_slopes_tolerance=1e-6): """ Validate this piecewise linear function by verifying various properties of the breakpoints and values lists (e.g., that the list of breakpoints is nondecreasing). Args: equal_slopes_tolerance (float): Tolerance used check if consecutive slopes are nearly equal. If any are found, validation will fail. Default is 1e-6. Returns: int: a function characterization code (see \ :func:`util.characterize_function`) Raises: PiecewiseValidationError: if validation fails """ breakpoints = [_value(x) for x in self._breakpoints] values = [_value(x) for x in self._values] if not is_nondecreasing(breakpoints): raise PiecewiseValidationError( "The list of breakpoints is not nondecreasing: %s" % (str(breakpoints))) ftype, slopes = characterize_function(breakpoints, values) for i in range(1, len(slopes)): if (slopes[i-1] is not None) and \ (slopes[i] is not None) and \ (abs(slopes[i-1] - slopes[i]) <= equal_slopes_tolerance): raise PiecewiseValidationError( "Piecewise function validation detected slopes " "of consecutive line segments to be within %s " "of one another. This may cause numerical issues. " "To avoid this error, set the 'equal_slopes_tolerance' " "keyword to a smaller value or disable validation." % (equal_slopes_tolerance)) return ftype
def __call__(self, x): """Evaluates the piecewise linear function at the given point using interpolation. Note that step functions are assumed lower-semicontinuous.""" i = bisect.bisect_left(_shadow_list(self.breakpoints), x) if i == 0: xP = _value(self.breakpoints[i]) if xP == x: return float(_value(self.values[i])) elif i != len(self.breakpoints): xL = _value(self.breakpoints[i - 1]) xU = _value(self.breakpoints[i]) assert xL <= xU if (xL <= x) and (x <= xU): yL = _value(self.values[i - 1]) yU = _value(self.values[i]) return yL + (float(yU - yL) / (xU - xL)) * (x - xL) raise ValueError( "The point %s is outside of the " "function domain: [%s,%s]." % (x, _value(self.breakpoints[0]), _value(self.breakpoints[-1])))
def __getitem__(self, i): return _value(self._x.__getitem__(i))
def validate(self, equal_slopes_tolerance=1e-6, require_bounded_input_variable=True, require_variable_domain_coverage=True): """ Validate this piecewise linear function by verifying various properties of the breakpoints, values, and input variable (e.g., that the list of breakpoints is nondecreasing). Args: equal_slopes_tolerance (float): Tolerance used check if consecutive slopes are nearly equal. If any are found, validation will fail. Default is 1e-6. require_bounded_input_variable (bool): Indicates if the input variable is required to have finite upper and lower bounds. Default is :const:`True`. Setting this keyword to :const:`False` can be used to allow general expressions to be used as the input in place of a variable. require_variable_domain_coverage (bool): Indicates if the function domain (defined by the endpoints of the breakpoints list) needs to cover the entire domain of the input variable. Default is :const:`True`. Ignored for any bounds of variables that are not finite, or when the input is not assigned a variable. Returns: int: a function characterization code (see \ :func:`util.characterize_function`) Raises: PiecewiseValidationError: if validation fails """ ftype = self._f.validate(equal_slopes_tolerance=equal_slopes_tolerance) assert ftype in (1, 2, 3, 4, 5) input_var = self.input.expr if not isinstance(input_var, IVariable): input_var = None if require_bounded_input_variable and \ ((input_var is None) or \ (not input_var.has_lb()) or \ (not input_var.has_ub())): raise PiecewiseValidationError( "Piecewise function input is not a " "variable with finite upper and lower " "bounds: %s. To avoid this error, set the " "'require_bounded_input_variable' keyword " "to False or disable validation." % (str(input_var))) if require_variable_domain_coverage and \ (input_var is not None): domain_lb = _value(self.breakpoints[0]) domain_ub = _value(self.breakpoints[-1]) if input_var.has_lb() and \ _value(input_var.lb) < domain_lb: raise PiecewiseValidationError( "Piecewise function domain does not include " "the lower bound of the input variable: " "%s.ub = %s > %s. To avoid this error, set " "the 'require_variable_domain_coverage' " "keyword to False or disable validation." % (input_var.name, _value(input_var.lb), domain_lb)) if input_var.has_ub() and \ _value(input_var.ub) > domain_ub: raise PiecewiseValidationError( "Piecewise function domain does not include " "the upper bound of the input variable: " "%s.ub = %s > %s. To avoid this error, set " "the 'require_variable_domain_coverage' " "keyword to False or disable validation." % (input_var.name, _value(input_var.ub), domain_ub)) return ftype