Source code for aviary.utils.math

"""
Smooth functions and their derivatives.
"""

import numpy as np


[docs] def sigmoidX(x, x0, mu=1.0): """ Sigmoid used to smoothly transition between piecewise functions. Parameters ---------- x: float or array independent variable x0: float the center of symmetry. When x = x0, sigmoidX = 1/2. mu: float steepness parameter. Returns ------- float or array smoothed value from input parameter x. """ if mu == 0: raise ValueError('mu must be non-zero') if isinstance(x, np.ndarray): if np.isrealobj(x): dtype = float else: dtype = complex n_size = x.size y = np.zeros(n_size, dtype=dtype) # avoid overflow in squared term, underflow seems to be ok calc_idx = np.where((x.real - x0) / mu > -320) y[calc_idx] = 1 / (1 + np.exp(-(x[calc_idx] - x0) / mu)) else: if isinstance(x, float): dtype = float else: dtype = complex y = 0 if (x - x0) * mu > -320: y = 1 / (1 + np.exp(-(x - x0) / mu)) if dtype == float: y = y.real return y
[docs] def dSigmoidXdx(x, x0, mu=1.0): """ Derivative of sigmoid function. Parameters ---------- x: float or array independent variable x0: float the center of symmetry. When x = x0, sigmoidX = 1/2. mu: float steepness parameter. Returns ------- float or array smoothed derivative value from input parameter x. """ if mu == 0: raise ValueError('mu must be non-zero') if isinstance(x, np.ndarray): if np.isrealobj(x): dtype = float else: dtype = complex n_size = x.size y = np.zeros(n_size, dtype=dtype) term = np.zeros(n_size, dtype=dtype) term2 = np.zeros(n_size, dtype=dtype) # avoid overflow in squared term, underflow seems to be ok calc_idx = np.where((x.real - x0) / mu > -320) term[calc_idx] = np.exp(-(x[calc_idx] - x0) / mu) term2[calc_idx] = (1 + term[calc_idx]) * (1 + term[calc_idx]) y[calc_idx] = term[calc_idx] / mu / term2[calc_idx] else: y = 0 if (x - x0) * mu > -320: term = np.exp(-(x - x0) / mu) term2 = (1 + term) * (1 + term) y = term / mu / term2 if dtype == float: y = y.real return y
[docs] def smooth_min(x, b, mu=100.0): """ Smooth approximation of the min function using the log-sum-exp trick. Parameters: x (float or array-like): First value. b (float or array-like): Second value. mu (float): The smoothing factor. Higher values make it closer to the true minimum. Try between 75 and 275. Returns: float or array-like: The smooth approximation of min(x, b). """ sum_log_exp = np.log(np.exp(np.multiply(-mu, x)) + np.exp(np.multiply(-mu, b))) rv = -(1 / mu) * sum_log_exp return rv
[docs] def d_smooth_min(x, b, mu=100.0): """ Derivative of function smooth_min(x) Parameters: x (float or array-like): First value. b (float or array-like): Second value. mu (float): The smoothing factor. Higher values make it closer to the true minimum. Try between 75 and 275. Returns: float or array-like: The smooth approximation of derivative of min(x, b). """ d_sum_log_exp = np.exp(np.multiply(-mu, x)) / ( np.exp(np.multiply(-mu, x)) + np.exp(np.multiply(-mu, b)) ) return d_sum_log_exp
[docs] def smooth_max(x, b, mu=10.0): """ Smooth approximation of the min function using the log-sum-exp trick. Parameters: x (float or array-like): First value. b (float or array-like): Second value. mu (float): The smoothing factor. Higher values make it closer to the true maximum. Try between 75 and 275. Returns: float or array-like: The smooth approximation of max(x, b). """ mu_x = mu * x mu_b = mu * b m = np.maximum(mu_x, mu_b) sum_log_exp = (m + np.log(np.exp(mu_x - m) + np.exp(mu_b - m))) / mu return sum_log_exp
[docs] def d_smooth_max(x, b, mu=10.0): """ Derivative of function smooth_min(x) Parameters: x (float or array-like): First value. b (float or array-like): Second value. mu (float): The smoothing factor. Higher values make it closer to the true minimum. Try between 75 and 275. Returns: float or array-like: The smooth approximation of derivative of min(x, b). """ mu_x = mu * x mu_b = mu * b m = np.maximum(mu_x, mu_b) numerator = np.exp(mu_x - m) denominator = np.exp(mu_x - m) + np.exp(mu_b - m) d_sum_log_exp = mu * numerator / denominator return d_sum_log_exp
[docs] def sin_int4(val): """Define a smooth, differentialbe approximation to the 'int' function.""" return sin_int(sin_int(sin_int(sin_int(val)))) - 0.5
[docs] def dydx_sin_int4(val): """Define the derivative (dy/dx) of sin_int4, at x = val.""" y0 = sin_int(val) y1 = sin_int(y0) y2 = sin_int(y1) dydx3 = dydx_sin_int(y2) dydx2 = dydx_sin_int(y1) dydx1 = dydx_sin_int(y0) dydx0 = dydx_sin_int(val) dydx = dydx3 * dydx2 * dydx1 * dydx0 return dydx
# 'int' function can be approximated by recursively applying this sin function # which makes a smooth, differentialbe function (is there a good one?)
[docs] def sin_int(val): """ Define one step in approximating the 'int' function with a smooth, differentialbe function. """ int_val = val - np.sin(2 * np.pi * (val + 0.5)) / (2 * np.pi) return int_val
[docs] def dydx_sin_int(val): """Define the derivative (dy/dx) of sin_int, at x = val.""" dydx = 1.0 - np.cos(2 * np.pi * (val + 0.5)) return dydx
[docs] def smooth_int_tanh(x, mu=10.0): """ Smooth approximation of int(x) using tanh. """ f = np.floor(x) frac = x - f t = np.tanh(mu * (frac - 0.5)) s = 0.5 * (t + 1) y = f + s return y
[docs] def d_smooth_int_tanh(x, mu=10.0): """ Smooth approximation of int(x) using tanh. Returns (y, dy_dx). """ f = np.floor(x) frac = x - f t = np.tanh(mu * (frac - 0.5)) dy_dx = 0.5 * mu * (1 - t**2) return dy_dx