Source code for pathsim.blocks.math

#########################################################################################
##
##                                      MATH BLOCKS 
##                                    (blocks/math.py)
##
##                  definitions of elementary math and function blocks
##
#########################################################################################

# IMPORTS ===============================================================================

import numpy as np

from ._block import Block

from ..optim.operator import Operator


# BASE MATH BLOCK =======================================================================

[docs] class Math(Block): """Base math block. Note ---- This block doesnt implement any functionality itself. Its intended to be used as a base for the elementary math blocks. Its **not** intended to be used directly! """ def __len__(self): """Purely algebraic block""" return 1
[docs] def update(self, t): """update algebraic component of system equation Parameters ---------- t : float evaluation time """ u = self.inputs.to_array() y = self.op_alg(u) self.outputs.update_from_array(y)
# BLOCKS ================================================================================
[docs] class Sin(Math): """Sine operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\sin(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.sin, jac=lambda x: np.diag(np.cos(x)) )
[docs] class Cos(Math): """Cosine operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\cos(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.cos, jac=lambda x: -np.diag(np.sin(x)) )
[docs] class Sqrt(Math): """Square root operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\sqrt{|\\vec{u}|} Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=lambda x: np.sqrt(abs(x)), jac=lambda x: np.diag(1/np.sqrt(abs(x))) )
[docs] class Abs(Math): """Absolute value operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\vert| \\vec{u} \\vert| Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=lambda x: abs(x), jac=lambda x: np.diag(np.sign(x)) )
[docs] class Pow(Math): """Raise to power operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\vec{u}^{p} Parameters ---------- exponent : float, array_like exponent to raise the input to the power of Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self, exponent=2): super().__init__() self.exponent = exponent #create internal algebraic operator self.op_alg = Operator( func=lambda x: np.power(x, self.exponent), jac=lambda x: np.diag(self.exponent * np.power(x, self.exponent - 1)) )
[docs] class PowProd(Math): """Power-Product operator block. This block raises each input to a power and then multiplies all results together: .. math:: y = \\prod_i u_i^{p_i} Parameters ---------- exponents : float, array_like exponent(s) to raise the inputs to the power of. If scalar, applies same exponent to all inputs. Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self, exponents=2): super().__init__() self.exponents = exponents def _jac(x): if np.isscalar(self.exponents): exps = np.full_like(x, self.exponents) else: exps = np.array(self.exponents) product = np.prod(np.power(x, exps)) # Jacobian is a row vector since output is scalar jac = np.zeros((1, len(x))) for j in range(len(x)): if x[j] != 0: jac[0, j] = product * exps[j] / x[j] else: jac[0, j] = 0 return jac #create internal algebraic operator self.op_alg = Operator( func=lambda x: np.prod(np.power(x, self.exponents)), jac=_jac )
[docs] class Exp(Math): """Exponential operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = e^{\\vec{u}} Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.exp, jac=lambda x: np.diag(np.exp(x)) )
[docs] class Log(Math): """Natural logarithm operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\ln(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.log, jac=lambda x: np.diag(1/x) )
[docs] class Log10(Math): """Base-10 logarithm operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\log_{10}(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.log10, jac=lambda x: np.diag(1/(x * np.log(10))) )
[docs] class Tan(Math): """Tangent operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\tan(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.tan, jac=lambda x: np.diag(1/np.cos(x)**2) )
[docs] class Sinh(Math): """Hyperbolic sine operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\sinh(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.sinh, jac=lambda x: np.diag(np.cosh(x)) )
[docs] class Cosh(Math): """Hyperbolic cosine operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\cosh(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.cosh, jac=lambda x: np.diag(np.sinh(x)) )
[docs] class Tanh(Math): """Hyperbolic tangent operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\tanh(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.tanh, jac=lambda x: np.diag(1 - np.tanh(x)**2) )
[docs] class Atan(Math): """Arctangent operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\arctan(\\vec{u}) Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.arctan, jac=lambda x: np.diag(1/(1 + x**2)) )
[docs] class Norm(Math): """Vector norm operator block. This block computes the Euclidean norm of the input vector: .. math:: y = \\|\\vec{u}\\|_2 = \\sqrt{\\sum_i u_i^2} Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self): super().__init__() #create internal algebraic operator self.op_alg = Operator( func=np.linalg.norm, jac=lambda x: x/np.linalg.norm(x) )
[docs] class Mod(Math): """Modulo operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\vec{u} \\bmod m Note ---- modulo is not differentiable at discontinuities Parameters ---------- modulus : float modulus value Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self, modulus=1.0): super().__init__() self.modulus = modulus #create internal algebraic operator self.op_alg = Operator( func=lambda x: np.mod(x, self.modulus), jac=lambda x: np.diag(np.ones_like(x)) )
[docs] class Clip(Math): """Clipping/saturation operator block. This block supports vector inputs. This is the operation it does: .. math:: \\vec{y} = \\text{clip}(\\vec{u}, u_{min}, u_{max}) Parameters ---------- min_val : float, array_like minimum clipping value max_val : float, array_like maximum clipping value Attributes ---------- op_alg : Operator internal algebraic operator """ def __init__(self, min_val=-1.0, max_val=1.0): super().__init__() self.min_val = min_val self.max_val = max_val #create internal algebraic operator def _clip_jac(x): """Jacobian is 1 where not clipped, 0 where clipped""" mask = (x >= self.min_val) & (x <= self.max_val) return np.diag(mask.astype(float)) self.op_alg = Operator( func=lambda x: np.clip(x, self.min_val, self.max_val), jac=_clip_jac )