Source code for wolfhece.sigmoid.sigmoid


import numpy as np
from numba import njit, jit
from scipy.optimize import minimize
import logging

""" Using JIT to speed up the functions """
@njit
[docs] def sigmoid(x:np.float64, loc:np.float64, scale:np.float64) -> float: """ Sigmoid function """ return 1. / (1. + np.exp(-scale * (x - loc)))
@njit
[docs] def sigmoid_derivative(x:np.float64, loc:np.float64, scale:np.float64) -> float: """ Derivative of the sigmoid function """ s = sigmoid(x, loc, scale) return scale * s * (1. - s)
@njit
[docs] def sigmoid_second_derivative(x:np.float64, loc:np.float64, scale:np.float64) -> float: """ Second derivative of the sigmoid function """ s = sigmoid(x, loc, scale) return scale**2. * s * (1. - s) * (1. - 2. * s)
@njit
[docs] def one_minus_sigmoid(x:np.float64, loc:np.float64, scale:np.float64) -> float: """ 1 - Sigmoid function """ return 1. - sigmoid(x, loc, scale)
@njit @njit
[docs] def _piecewise_linear(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, slope_left:np.float64, slope_right:np.float64) -> np.ndarray: """ Piecewise linear function with continuous transition by sigmoids. In extrapolation mode, the function is y[0] for x < x[0] and linearly extrapoletd based on the last slope for x > x[-1]. :param x_candidate: np.ndarray, list or float, x values to evaluate the function :param x: np.ndarray, list of floats, x values of the points :param y: np.ndarray, list of floats, y values of the points :param scale: float, scale of the sigmoid functions :param slope_left: float, slope before the first segment - extrapolation mode :param slope_right: float, slope after the last segment - extrapolation mode """ # FIXME : Numba.JIT does not like np.concatenate, np.hstack... so we pre-allocate the arrays # Extend the x and y values to allow extrapolation based on slope_left and slope_right xx = np.zeros(x.shape[0]+1) # add the extrapolation points yy = np.zeros(y.shape[0]+1) # add the extrapolation points slopes = np.zeros(x.shape[0]+1) xx[0] = x[0] yy[0] = y[0] xx[1:] = x yy[1:] = y x = xx y = yy n = len(x) # number of intervals/segments taking into account the extrapolation results = np.zeros_like(x_candidate) # pre-allocate the results, force numpy type and not a list functions = np.zeros(n) # values of the linear functions for each segment sigmoids = np.ones(n) one_minus_sigmoids = np.ones(n) # local slopes of the segments -- must be a **function** (no vertical slope, x increasing...) slopes[0] = slope_left slopes[1:-1] = (y[2:] - y[1:-1]) / (x[2:] - x[1:-1]) slopes[-1] = slope_right for idx, cur_x in enumerate(x_candidate): # Copy the x value for each segment xvals = np.full(n, cur_x) # Compute the value of the linear function for each segment functions[:] = slopes[:] * (xvals[:] - x[:]) + y[:] # Compute the value of the sigmoid function for each segment (based on the start of the segment) sigmoids[1:] = sigmoid(xvals[1:], x[1:], scale) # Compute the value of 1 - sigmoid for each segment (based on the end of the segment) one_minus_sigmoids[:-1] = 1. - sigmoids[1:] """ Interpolation mode : x[0] <= x_candidate <= x[-1] ------------------ We will combine the results of each segment. For each segment, we use a door function that is 1 if we are in the segment and 0 otherwise. 1 ____________ | | | | | | 0______| |_______ x1 x2 The door function is the product of the sigmoid of the segment and the (1 - sigmoid) of the next segment. Door_i = sigmoid_i * (1 - sigmoid_{i+1}) So, for x, we can compute the value of the function as a sum of the value of the function for each segment multiplied by the door function. f(x) = sum_i (functions_i(x) * Door_i) """ results[idx] = np.sum(sigmoids * one_minus_sigmoids * functions) return results
@njit
[docs] def piecewise_linear(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, nb_elemt:np.int64, slope_left:np.float64, slope_right:np.float64) -> np.ndarray: """ Piecewise linear function with continuous transition by sigmoids :param x_candidate: np.ndarray, list or float, x values to evaluate the function :param x: np.ndarray, list of floats, x values of the points :param y: np.ndarray, list of floats, y values of the points :param scale: float, scale of the sigmoid functions :param nb_elemt: int, number of elements to consider around the x_candidate value """ results = np.zeros_like(x_candidate) if nb_elemt == -1: results[:] = _piecewise_linear(x_candidate, x, y, scale, slope_left, slope_right) else: xx, yy = extract_xy_binary_search(x_candidate, x, y, nb_elemt) for idx, cur_x in enumerate(x_candidate): results[idx] = _piecewise_linear(np.array([cur_x]), xx[idx], yy[idx], scale, slope_left, slope_right)[0] return results
@njit
[docs] def _gradient_piecewise_linear(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, slope_left:np.float64, slope_right:np.float64) -> np.ndarray: """ Gradient of the piecewise linear function """ xx = np.zeros(x.shape[0]+1) # add the extrapolation points yy = np.zeros(y.shape[0]+1) # add the extrapolation points slopes = np.zeros(x.shape[0]+1) xx[0] = x[0] yy[0] = y[0] xx[1:] = x yy[1:] = y x = xx y = yy n = len(x) results = np.zeros_like(x_candidate) functions = np.zeros(n) # values of the linear functions for each segment sigmoids = np.ones(n) one_minus_sigmoids = np.ones(n) slopes[0] = slope_left slopes[1:-1] = (y[2:] - y[1:-1]) / (x[2:] - x[1:-1]) slopes[-1] = slope_right for idx, cur_x in enumerate(x_candidate): # Copy the x value for each segment xvals = np.full(n, cur_x) # Compute the value of the linear function for each segment functions[:] = slopes[:] * (xvals[:] - x[:]) + y[:] # Compute the value of the sigmoid function for each segment (based on the start of the segment) sigmoids[1:] = sigmoid(xvals[1:], x[1:], scale) # Compute the value of 1 - sigmoid for each segment (based on the end of the segment) one_minus_sigmoids[:-1] = 1. - sigmoids[1:] def derivative_sigmoid(sig, scale): return scale * sig * (1. - sig) def derivative_oneminussigmoid(oneminussig, scale): return -derivative_sigmoid(oneminussig, scale) result = 0.0 for i in range(n): result += sigmoids[i] * one_minus_sigmoids[i] * slopes[i] result += (derivative_sigmoid(sigmoids[i], scale) * one_minus_sigmoids[i] + sigmoids[i] * derivative_oneminussigmoid(one_minus_sigmoids[i], scale)) * functions[i] results[idx] = result return results
@njit
[docs] def gradient_piecewise_linear(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, nb_elemt:np.int64, slope_left:np.float64, slope_right:np.float64) -> np.ndarray: """ Gradient of the piecewise linear function """ results = np.zeros_like(x_candidate) if nb_elemt == -1: results[:] = _gradient_piecewise_linear(x_candidate, x, y, scale, slope_left, slope_right) else: xx, yy = extract_xy_binary_search(x_candidate, x, y, nb_elemt) for idx, cur_x in enumerate(x_candidate): results[idx] = _gradient_piecewise_linear(np.array([cur_x]), xx[idx], yy[idx], scale, slope_left, slope_right)[0] return results
@njit
[docs] def _gradient_piecewise_linear_approx(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, slope_left:np.float64, slope_right:np.float64): """ Approximative gradient of the piecewise linear function. The derivative contribution of the sigmoids is ignored. """ x = np.hstack((x[0], x)) # add the extrapolation points y = np.hstack((y[0], y)) # add the extrapolation points n = len(x) results = np.zeros_like(x_candidate) functions = np.zeros(n) # values of the linear functions for each segment sigmoids = np.ones(n) one_minus_sigmoids = np.ones(n) slopes = np.concatenate([ [slope_left], (y[2:] - y[1:-1]) / (x[2:] - x[1:-1]), [slope_right]]) for idx, cur_x in enumerate(x_candidate): # Copy the x value for each segment xvals = np.full(n, cur_x) # Compute the value of the linear function for each segment functions[:] = slopes[:] * (xvals[:] - x[:]) + y[:] # Compute the value of the sigmoid function for each segment (based on the start of the segment) sigmoids[1:] = sigmoid(xvals[1:], x[1:], scale) # Compute the value of 1 - sigmoid for each segment (based on the end of the segment) one_minus_sigmoids[:-1] = 1. - sigmoids[1:] results[idx] = np.sum(sigmoids * one_minus_sigmoids * slopes) # result = 0.0 # for i in range(n): # result += sigmoids[i] * one_minus_sigmoids[i] * slopes[i] # results[idx] = result return results
@njit
[docs] def gradient_piecewise_linear_approx(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, scale:np.float64, nb_elemt:np.int64, slope_left:np.float64, slope_right:np.float64): """ Approximative gradient of the piecewise linear function. The derivative contribution of the sigmoids is ignored. """ results = np.zeros_like(x_candidate) if nb_elemt == -1: results[:] = _gradient_piecewise_linear_approx(x_candidate, x, y, scale, slope_left, slope_right) else: xx, yy = extract_xy_binary_search(x_candidate, x, y, nb_elemt) for idx, cur_x in enumerate(x_candidate): results[idx] = _gradient_piecewise_linear_approx(np.array([cur_x]), xx[idx], yy[idx], scale, slope_left, slope_right)[0] return results
[docs] class Piecewise_Linear_Sigmoid(): """ Piecewise linear function with smooth transitions using sigmoids """ def __init__(self, x, y, scale:float = 10., slope_left:float = 0., slope_right:float = 99999.): """ :param x: np.ndarray or list of floats, x values of the points :param y: np.ndarray or list of floats, y values of the points :param scale: float, scale of the sigmoid functions """ self.x:np.ndarray = np.asarray(x, dtype= np.float64).flatten() self.y:np.ndarray = np.asarray(y, dtype= np.float64).flatten() self.scale:np.float64 = np.float64(scale) self.slope_left:np.float64 = slope_left self.slope_right:np.float64 = slope_right self._checked_x = False self._checked_y = False @property
[docs] def slope_left(self): return self._slope_left
@property
[docs] def slope_right(self): return self._slope_right
@slope_left.setter def slope_left(self, value): self._slope_left = np.float64(value) if self._slope_left == 99999.: self._slope_left = (self.y[1] - self.y[0]) / (self.x[1] - self.x[0]) @slope_right.setter def slope_right(self, value): self._slope_right = np.float64(value) if self._slope_right == 99999.: self._slope_right = (self.y[-1] - self.y[-2]) / (self.x[-1] - self.x[-2])
[docs] def clip_around_x(self, x:np.ndarray, nb_elemt:int = -1) -> tuple[np.ndarray, np.ndarray]: """ Clip the input array x around the existing x values :param x: np.ndarray, list or float, x values to clip :param nb_elemt: int, number of elements to consider around each x value :return: tuple of two np.ndarrays, clipped x and y values """ if not self._checked_x: self.check_x() self._checked_x = True x_array = np.asarray(x, dtype=np.float64).flatten() return extract_xy_binary_search(x_array, self.x, self.y, np.int64(nb_elemt))
[docs] def clip_around_y(self, y:np.ndarray, nb_elemt:int = -1) -> tuple[np.ndarray, np.ndarray]: """ Clip the input array y around the existing y values :param y: np.ndarray, list or float, y values to clip :param nb_elemt: int, number of elements to consider around each y value :return: tuple of two np.ndarrays, clipped x and y values """ if not self._checked_y: self.check_y() self._checked_y = True y_array = np.asarray(y, dtype=np.float64).flatten() return extract_xy_binary_search(y_array, self.y, self.x, np.int64(nb_elemt))
[docs] def gradient(self, x, approximative:bool = False, nb_elemt:int = -1) -> np.ndarray: """ Gradient of the piecewise linear function with smooth transitions using sigmoids :param x: np.ndarray, list or float, x values to evaluate the gradient :param approximative: bool, if True, use an approximative gradient (ignoring derivative of the sigmoids) :param nb_elemt: int, number of elements to consider around the x_candidate value """ if not self._checked_x: self.check_x() self._checked_x = True if approximative: if isinstance(x, np.ndarray): return gradient_piecewise_linear_approx(x.astype(np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right) elif isinstance(x, list): return gradient_piecewise_linear_approx(np.array(x, dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right) elif isinstance(x, float): return gradient_piecewise_linear_approx(np.array([x], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right)[0] elif isinstance(x, int): return gradient_piecewise_linear_approx(np.array([float(x)], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right)[0] else: raise ValueError('x should be np.ndarray, list or float') else: if isinstance(x, np.ndarray): return gradient_piecewise_linear(x.astype(np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right) elif isinstance(x, list): return gradient_piecewise_linear(np.array(x, dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right) elif isinstance(x, float): return gradient_piecewise_linear(np.array([x], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right)[0] elif isinstance(x, int): return gradient_piecewise_linear(np.array([float(x)], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self.slope_left, self._slope_right)[0] else: raise ValueError('x should be np.ndarray, list or float')
[docs] def check_x(self): diff = np.diff(self.x) if np.any(diff <= 0): raise ValueError('x should be in increasing order')
[docs] def check_y(self): diff = np.diff(self.y) if np.any(diff <= 0): raise ValueError('y should be in increasing order')
[docs] def check(self): self.check_x() self.check_y() return True
@property
[docs] def n(self): """ Number of points """ return len(self.x)
@property
[docs] def size(self): return self.n
def __call__(self, x, nb_elemt:int = -1) -> np.ndarray: """ Evaluate the piecewise linear function at x :param x: np.ndarray, list or float, x values to evaluate the function :param nb_elemt: int, number of elements to consider around the x_candidate value """ if not self._checked_x: self.check_x() self._checked_x = True if isinstance(x, np.ndarray): return piecewise_linear(x.astype(np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self._slope_left, self._slope_right) elif isinstance(x, list): return piecewise_linear(np.array(x, dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self._slope_left, self._slope_right) elif isinstance(x, float): return piecewise_linear(np.array([x], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self._slope_left, self._slope_right)[0] elif isinstance(x, int): return piecewise_linear(np.array([float(x)], dtype=np.float64), self.x, self.y, self.scale, np.int64(nb_elemt), self._slope_left, self._slope_right)[0] else: raise ValueError('x should be np.ndarray, list or float')
[docs] def inverse(self, y, nb_elemt:int = -1) -> np.ndarray: """ Evaluate the inverse of the piecewise linear function at y """ if not self._checked_y: self.check_y() self._checked_y = True if isinstance(y, np.ndarray): return piecewise_linear(y.astype(np.float64), self.y, self.x, self.scale, np.int64(nb_elemt), 1./self._slope_left, 1./self._slope_right) elif isinstance(y, list): return piecewise_linear(np.array(y, dtype=np.float64), self.y, self.x, self.scale, np.int64(nb_elemt), 1./self._slope_left, 1./self._slope_right) elif isinstance(y, float): return piecewise_linear(np.array([y], dtype=np.float64), self.y, self.x, self.scale, np.int64(nb_elemt), 1./self._slope_left, 1./self._slope_right)[0] else: raise ValueError('y should be np.ndarray, list or float')
[docs] def get_y(self, x, nb_elemt:int = -1): """ Get the value of the piecewise linear function at x """ return self(x, nb_elemt)
[docs] def get_x(self, y, nb_elemt:int = -1): """ Get the inverse of the piecewise linear function at y """ return self.inverse(y, nb_elemt)
@njit
[docs] def _polynomial(coeffs:np.ndarray, x:np.float64): """ Polynomial function """ return np.sum(coeffs * np.power(x, np.arange(len(coeffs))), dtype=np.float64)
@njit
[docs] def piecewise_polynomial(x_candidate:np.ndarray, x:np.ndarray, y:np.ndarray, poly_coeff:np.ndarray, scale:np.float64, slope_left:np.float64, slope_right:np.float64) -> np.ndarray: """ Piecewise polynomial function. :param x_candidate: np.ndarray, list or float, x values to evaluate the function :param x: np.ndarray, list of floats, x values of the transition points between polyomials functions :param poly_coeff: np.ndarray, list of floats, coefficients of the polynomial functions """ c_left = np.zeros(poly_coeff.shape[1], dtype=np.float64) c_right = np.zeros(poly_coeff.shape[1], dtype=np.float64) c_left[0] = y[0] - slope_left * x[0] c_left[1] = slope_left c_right[0] = y[-1] - slope_right * x[-1] c_right[1] = slope_right shape_max = max([len(coeffs) for coeffs in poly_coeff]) polys = np.zeros((len(poly_coeff)+2, shape_max), dtype=np.float64) polys[0,:] = c_left polys[-1,:] = c_right polys[1:-1,:] = poly_coeff # Extend the x and y values to allow extrapolation based on slope_left and slope_right xx = np.zeros(x.shape[0]+1) # add the extrapolation points xx[0] = x[0] xx[1:] = x x = xx n = len(polys) results = np.zeros(len(x_candidate)) sigmoids = np.ones(n) one_minus_sigmoids = np.ones(n) for idx, cur_x in enumerate(x_candidate): xvals = np.full(n, cur_x) sigmoids[1:] = sigmoid(xvals[1:], x[1:], scale) one_minus_sigmoids[:-1] = 1. - sigmoids[1:] for i in range(n): results[idx] += sigmoids[i] * one_minus_sigmoids[i] * _polynomial(polys[i], cur_x) return results
[docs] class Piecewise_Polynomial_Sigmoid(): """ Polynomial function with smooth transitions using sigmoids """ def __init__(self, x, y, scale:float = 10., degree:int = 3, slope_left:float = 0., slope_right:float = 99999.): self.x:np.ndarray = np.asarray(x, dtype= np.float64).flatten() self.y:np.ndarray = np.asarray(y, dtype= np.float64).flatten() self.scale:np.float64 = np.float64(scale) self.degree:int = degree self.slope_left:np.float64 = slope_left self.slope_right:np.float64 = slope_right self._poly_coeff = None self._parts_x = None self._parts_y = None self._checked_x = False self._checked_y = False @property
[docs] def slope_left(self): return self._slope_left
@property
[docs] def slope_right(self): return self._slope_right
@slope_left.setter def slope_left(self, value): self._slope_left = np.float64(value) if self._slope_left == 99999.: self._slope_left = (self.y[1] - self.y[0]) / (self.x[1] - self.x[0]) @slope_right.setter def slope_right(self, value): self._slope_right = np.float64(value) if self._slope_right == 99999.: self._slope_right = (self.y[-1] - self.y[-2]) / (self.x[-1] - self.x[-2])
[docs] def fit(self, forced_passage:np.ndarray, method:str = 'Nelder-Mead') -> np.ndarray: """ Convert XY points into nbparts polynomial segments. The segments are combined with sigmoids to ensure a smooth transition. The routine must find the best transition points to minimize error. """ def error(coeffs, x, y, xx, yy, degree): nbparts = len(xx) - 1 loc_coeffs = np.reshape(coeffs, (nbparts, degree+1)) return np.sum((y - piecewise_polynomial(x, xx, yy, loc_coeffs, self.scale, self.slope_left, self.slope_right))**2) # Fit the polynomals coefficients nbparts = forced_passage.shape[0] - 1 coeffs = np.zeros((nbparts, self.degree+1), dtype=np.float64) coeffs[:,0] = 0. coeffs[:,1] = 1. self._parts_x = forced_passage[:,0] self._parts_y = forced_passage[:,1] ret = minimize(error, args=(self.x, self.y, forced_passage[:,0], forced_passage[:,1], self.degree), x0=coeffs.flatten(), method=method, tol= 1.e-14) self._poly_coeff = ret.x.reshape((nbparts, self.degree+1)) return self._poly_coeff
[docs] def fit_null_first_term(self, forced_passage:np.ndarray, method:str = 'Nelder-Mead') -> np.ndarray: """ Convert XY points into nbparts polynomial segments. The segments are combined with sigmoids to ensure a smooth transition. The routine must find the best transition points to minimize error. """ def error(coeffs, x, y, xx, yy, degree): nbparts = len(xx) - 1 loc_coeffs = np.zeros((nbparts, degree+1), dtype=np.float64).flatten() loc_coeffs[1:] = coeffs loc_coeffs = np.reshape(loc_coeffs, (nbparts, degree+1)) return np.sum((y - piecewise_polynomial(x, xx, yy, loc_coeffs, self.scale, self.slope_left, self.slope_right))**2) # Fit the polynomals coefficients nbparts = forced_passage.shape[0] - 1 coeffs = np.zeros((nbparts, self.degree+1), dtype=np.float64) coeffs[:,1] = (self.y[-1] - self.y[0]) / (self.x[-1] - self.x[0]) self._parts_x = forced_passage[:,0] self._parts_y = forced_passage[:,1] ret = minimize(error, args=(self.x, self.y, forced_passage[:,0], forced_passage[:,1], self.degree), x0=coeffs.flatten()[1:], method=method, tol= 1.e-14, options={'maxiter': 100000}) self._poly_coeff = np.zeros((nbparts, self.degree+1), dtype=np.float64).flatten() self._poly_coeff[1:] = ret.x self._poly_coeff = self._poly_coeff.reshape((nbparts, self.degree+1)) return self._poly_coeff
[docs] def check_x(self): diff = np.diff(self.x) if np.any(diff <= 0): raise ValueError('x should be in increasing order')
[docs] def check_y(self): diff = np.diff(self.y) if np.any(diff <= 0): raise ValueError('y should be in increasing order')
[docs] def check(self): self.check_x() self.check_y() return True
@property
[docs] def n(self): """ Number of points """ return len(self.x)
@property
[docs] def size(self): return self.n
def __call__(self, x, nb_elemt:int = -1) -> np.ndarray: """ Evaluate the piecewise linear function at x :param x: np.ndarray, list or float, x values to evaluate the function :param nb_elemt: int, number of elements to consider around the x_candidate value """ if not self._checked_x: self.check_x() self._checked_x = True if isinstance(x, np.ndarray): return piecewise_polynomial(x.astype(np.float64), self._parts_x, self._parts_y, self._poly_coeff, self.scale, self._slope_left, self._slope_right) elif isinstance(x, list): return piecewise_polynomial(np.array(x, dtype=np.float64), self._parts_x, self._parts_y, self._poly_coeff, self.scale, self._slope_left, self._slope_right) elif isinstance(x, float): return piecewise_polynomial(np.array([x], dtype=np.float64), self._parts_x, self._parts_y, self._poly_coeff, self.scale, self._slope_left, self._slope_right)[0] elif isinstance(x, int): return piecewise_polynomial(np.array([float(x)], dtype=np.float64), self._parts_x, self._parts_y, self._poly_coeff, self.scale, self._slope_left, self._slope_right)[0] else: raise ValueError('x should be np.ndarray, list or float')
[docs] def inverse(self, y, nb_elemt:int = -1) -> np.ndarray: """ Evaluate the inverse of the piecewise linear function at y """ logging.warning('Inverse function not implemented yet') pass
[docs] def get_y(self, x, nb_elemt:int = -1): """ Get the value of the piecewise linear function at x """ return self(x, nb_elemt)
[docs] def get_y_symmetry(self, x:np.ndarray, nb_elemt:int = -1): idx_sup = np.where(x > self.x[-1])[0] xloc = np.where(x <= self.x[-1], x, 2. * self.x[-1] - x) yloc = self(xloc, nb_elemt) if len(idx_sup) > 0: yloc[idx_sup] = 2. * self.y[-1] - yloc[idx_sup] return yloc
[docs] def get_x(self, y, nb_elemt:int = -1): """ Get the inverse of the piecewise linear function at y """ return self.inverse(y, nb_elemt)
if __name__ == '__main__': import matplotlib.pyplot as plt
[docs] test_sigmoid = sigmoid(0., 0, 10)
x = np.asarray([-2., 0, 1., 2., 4., 5.], dtype=np.float64) y = np.asarray([0., 0., 2., 8., -2., 5.], dtype=np.float64) x_test = np.linspace(-2, 6, 1000) x = np.arange(1000) y = np.sort(np.random.randn(1000)) newx, newy = extract_xy_binary_search(x_test, x, y, np.int64(100)) x_test = np.linspace(5, 1000, 100) fig, ax = plt.subplots() ax.plot(x, y, 'o') for scale in [0.01,.1,10.,100.]: # for scale in [100.]: xy = Piecewise_Linear_Sigmoid(x, y, scale) ax.plot(x_test, xy(x_test, nb_elemt= 5), label=f'scale={scale}') # ax.plot(x_test, xy.gradient(x_test), label=f'gradient={scale}') ax.plot(x_test, xy.gradient(x_test, True, nb_elemt= 5), label=f'gradient_approx={scale}') ax.legend() fig.show() plt.show() pass