Using the Autodif module¶
The autodif module is designed to make gradient computation trivial for any mathematical function of an arbitrary number of parameters.
To use the autodif module from python, first we have to import it
import godot.core.autodif as ad
import numpy as np
We can construct an autodif scalar using only a floating point value
x = ad.Scalar( 1.5 )
print( x.value() )
print( x )
But to track the gradient with respect to this we need to set track to true
x = ad.Scalar( 1.5, True )
print( x )
or, alternatively give it a name
y = ad.Scalar( 1.5, "y" )
print( y )
In the print out, the value and the gradient with respect to tracked parameters is given.
The scalars support many operations. These operations compute the value and the gradients, keeping track of dependent parameters.
x*x
ad.sin(x)
We can do all kids of things, all the time tracking the partials. For example
def cheby(w):
t = [0]*10
t[0] = 1
t[1] = w
for i in range(2,10):
t[i] = 2 * w * t[i-1] - t[i-2]
return t
c = cheby(x)
for ci in c:
print(ci)
We can redefine x so that we see the partial name and then track more than one parameter
x = ad.Scalar( 2.5, "x")
z = y/x
c = cheby(z)
for ci in c:
print(ci)
We can retrieve the gradient from the autodif scalar
grad = z.gradient()
grad
or like this for a names list of parameters:
grad = z.at(['y','x'])
grad
And we can get a specific element as a numpy array as long as an autodif scalar is tracking only one parameter (leaf).
ad.getPartial( z, x.leaf() )
ad.getPartial( z*c[3], x.leaf() )
But this is not okay, because z has 2 elements of its gradient vector and therefore 2 leaves.
try:
ad.getPartial( z*c[3], z.leaf() )
except RuntimeError as err:
print("This is not okay ", err)
For that we can do this
z.at(x.leaf())
Vectors¶
We can use vectors too. For example:
y = ad.Vector( [ 1, 2, 3 ], "y" )
print( y.value() )
print( y )
many operations act like you would expect. Here are a few examples.
y[0]
y[1:]
y[1:2]
x = ad.Vector( [ 4, 5, 6 ], "x" )
z = ad.cross( x, y ) + y /3
print(z)
Numerical Integration¶
Suppose we have a a simple vector differential equation:
$$ \dot{\bf{x}} = f\left( \bf{x} \right) = \frac{\bf{x}}{\bf{x} \cdot \bf{x}} $$def f(t, x ):
return x / ( ad.dot(x,x) )
We use an Runge Kutte 4th order method to integrate this from an initial condition given as an autodif vector
def rk4(t, x, f, h):
k1 = f(t, x)
k2 = f(t+h/2, x+h*k1/2)
k3 = f(t+h/2, x+h*k2/2)
k4 = f(t+h, x+h*k3)
return h * (k1 + k2 + k3 + k4) / 6
h = 1
"initialise x"
x = ad.Vector( [ 4, 5, 6 ], "x0" )
for t in np.arange(1,10,h):
x = rk4( t, x, f, h)
print(x)
However, if we tried to integrate the same rhs with non-autodif variables we would run into problems because the rhs uses the ad.dot function.
To solve this issue, we introduced the bridge module:
import godot.core.autodif.bridge as br
This allows us to write functions which can be either numpy OR autodif
def f( t, x ):
return x / ( br.dot(x,x) )
h = 1
x = ad.Vector( [ 4, 5, 6 ], "x0" )
for t in np.arange(1,10,h):
x = rk4(t,x,f,h)
print(x)
h = 1
x = [ 4, 5, 6 ]
for t in np.arange(1,10,h):
x = rk4(t,x,f,h)
print(x)
Matrices¶
It is possible to create matrix parameters, each element is then tracked separately as a separate parameter.
Z = ad.Matrix( [[ 1, 2, 3 ],[4,5,6],[7,8,9] ], "Z" )
print( Z*y )
But you can also construct matrices from Scalars.
Suppose we have small rotations dx, dy and dzabout the x, y and z axes
dx = ad.Scalar(0.0,"dx")
dy = ad.Scalar(0.0,"dy")
dz = ad.Scalar(0.0,"dz")
We define the rotation matrices assuming the small angle approximation
import numpy as np
Rx = ad.Matrix(np.identity(3))
Rx[1,2] = -dx
Rx[2,1] = +dx
Ry = ad.Matrix(np.identity(3))
Ry[0,2] = +dy
Ry[2,0] = -dy
Rz = ad.Matrix(np.identity(3))
Rz[0,1] = +dz
Rz[1,0] = -dz
print(Rx)
print(Ry)
print(Rz)
and we can combine them to get a rotation matrix, tracking partials with respect to the small rotation angles.
R = Rz * Ry * Rx
print(R)