For example : The forward differential in the a-direction $ \partial_a $ applied to a function $f$ over a linear manifold is defined by
\begin{align} \partial_a f &\equiv \frac{f(x+\tau a )- f(x)}{\tau} \\ \end{align}
Consider defining the backward differential with a reverse symmetry,
\begin{align}
\ f \partial_a &\equiv \frac{f(x)-f(x-\tau a )}{\tau} \\
\end{align}
The central differential is then seen as an inner product
\begin{align} \partial_a \cdot f \equiv \frac{1}{2}(\partial_a f + f\partial_a ) =\frac{ f(x+\tau a ) - f(x-\tau a )}{2\tau} \end{align} lets look at an implementation
Implementation¶
In [1]:
class Operator:
def __init__(self, right, left, *args, **kw):
"""
A non-commutative operator that can be applied to functions
Summary of operations
- self * func: applies operator on the right
- func * self: applies operator on the left
- self ^ func: 1/2 * (right - left)
- self | func: 1/2 * (right + left)
Args:
right: A function that takes a function f and returns the operator applied to f on the right
left: A function that takes a function f and returns the operator applied to f on the left
*args, **kw: Additional arguments to be passed to the right and left functions
"""
self._right = right
self._left = left
self.right = lambda f: right(f, *args, **kw)
self.left = lambda f: left(f, *args, **kw)
def __call__(self, *args, **kw):
"""return a new Operator with the same left and right but updated args/kw"""
return type(self)(self._right, self._left,*args, **kw)
def __add__(self, op):
"""operator * func: applies operator on the right"""
raise NotImplementedError("Addition of Operators is not implemented yet.")
def __mul__(self, f):
"""operator * func: applies operator on the right"""
return self.right(f)
def __rmul__(self, f):
"""func * operator: applies operator on the left"""
return self.left(f)
def __xor__(self, f):
"""operator ^ func: antisymmetric combination = 1/2 * (self * f - f * self)"""
return lambda *args, **kwargs: 0.5 * ((self*f)(*args, **kwargs) - (f*self)(*args, **kwargs))
def __rxor__(self, f):
"""operator ^ func: antisymmetric combination = 1/2 * (f*self - self * f)"""
return -self.__xor__(f) # subtraction is antisymmetric
def __or__(self, f):
"""operator | func: symmetric combination = 1/2 * (self * f + f * self)"""
return lambda *args, **kwargs: 0.5 * ((self*f)(*args, **kwargs) + (f*self)(*args, **kwargs))
def __ror__(self, f):
"""operator | func: symmetric combination = 1/2 * (f*self + self * f)"""
return self.__or__(f) # addition is symmetric
Test¶
In [2]:
from kingdon import calculus as calc
from kingdon import Algebra
sta = Algebra(1,3,start_index=0)
locals().update(sta.blades)
da = Operator(right = lambda f,a: calc.da(f, a, direction='forward'),
left = lambda f,a: calc.da(f, a, direction='backward'))
f = lambda x: 2*x**20 # function with high curvature to see differences in derivative approximations
x = sta.random_vector() # location at which to evaluate the derivative
a = sta.random_vector() # direction for the derivative
((da(a=a)|f)(x), # d_a*f +f*d_a
calc.da(f=f, a=a, direction='central')(x),
calc.da(f=f, a=a, direction='forward')(x))
Out[2]:
(1.54 + 5.65e-08 𝐞₀₁ + -2.77e-08 𝐞₀₂ + -7.1e-10 𝐞₀₃ + -5.1e-08 𝐞₁₂ + -6.01e-08 𝐞₁₃ + -5.96e-09 𝐞₂₃ + -1.35e-24 𝐞₀₁₂₃, 1.54 + 5.65e-08 𝐞₀₁ + -2.77e-08 𝐞₀₂ + -7.1e-10 𝐞₀₃ + -5.1e-08 𝐞₁₂ + -6.01e-08 𝐞₁₃ + -5.96e-09 𝐞₂₃ + -1.35e-24 𝐞₀₁₂₃, 1.54 + 4.84e-08 𝐞₀₁ + -1.5e-07 𝐞₀₂ + 3.49e-08 𝐞₀₃ + -2.9e-08 𝐞₁₂ + -4.32e-08 𝐞₁₃ + 1.51e-08 𝐞₂₃ + 3.12e-23 𝐞₀₁₂₃)
In [3]:
((da(a=a)*f)*da(a=a))(x)
Out[3]:
-3.89e+02 + -16.2 𝐞₀₁ + -2.44e+02 𝐞₀₂ + 71.2 𝐞₀₃ + 43.9 𝐞₁₂ + 33.9 𝐞₁₃ + 42.0 𝐞₂₃ + 6.51e-14 𝐞₀₁₂₃
In [ ]: