NeuralODE

Neural ODE (astroNN.neuralODE; Neural Ordinary Differential Equation) module provides numerical integrator implemented in Tensorflow for solutions of an ODE system, and can calculate gradient.

Numerical Integrator

astroNN implemented numerical integrator in Tensorflow

An example integration an ODE for sin(x)

 1import time
 2import matplotlib.pyplot as plt
 3import numpy as np
 4import tensorflow as tf
 5from astroNN.shared.nn_tools import cpu_fallback
 6from astroNN.neuralode import odeint
 7
 8cpu_fallback()
 9
10# time array
11t = tf.constant(np.linspace(0, 100, 10000))
12# initial condition
13true_y0 = tf.constant([0., 1.])
14# analytical ODE system for sine wave [x, t] -> [v, a]
15ode_func = lambda y, t: tf.stack([tf.cos(t), tf.sin(t)])
16
17start_t = time.time()
18true_y = odeint(ode_func, true_y0, t, method='dop853')
19print(time.time() - start_t)  # approx. 4.3 seconds on i7-9750H GTX1650
20
21# plot the solution and compare
22plt.figure(dpi=300)
23plt.title("sine(x)")
24plt.plot(t, np.sin(t), label='Analytical')
25plt.plot(t, true_y[:, 0], ls='--', label='astroNN odeint')
26plt.legend(loc='best')
27plt.xlabel("t")
28plt.ylabel("y")
29plt.show()
../_images/odeint_sine.png

Moreover odeint supports numerically integration in parallel, the example below integration the sin(x) for 50 initial conditions. You can see the execution time is the same!!

1start_t = time.time()
2# initial conditions, 50 of them instead of a single initial condition
3true_y0sss = tf.random.normal((50, 2), 0, 1)
4# time array, 50 of them instead of the same time array for every initial condition
5tsss = tf.random.normal((50, 10000), 0, 1)
6true_y = odeint(ode_func, true_y0sss, tsss, method='dop853')
7print(time.time() - start_t)  # also approx. 4.3 seconds on i7-9750H GTX1650

Neural Network model with Numerical Integrator

You can use odeint along with neural network model, below is an example

 1import numpy as np
 2import tensorflow as tf
 3from astroNN.shared.nn_tools import cpu_fallback
 4from astroNN.neuralode import odeint
 5
 6cpu_fallback()
 7
 8t = tf.constant(np.linspace(0, 1, 20))
 9# initial condition
10true_y0 = tf.constant([0., 1.])
11
12class MyModel(tf.keras.Model):
13    def __init__(self):
14        super(MyModel, self).__init__()
15        self.dense1 = tf.keras.layers.Dense(2, activation=tf.nn.relu)
16        self.dense2 = tf.keras.layers.Dense(16, activation=tf.nn.relu)
17        self.dense3 = tf.keras.layers.Dense(2)
18
19    def call(self, inputs, t, *args):
20        inputs = tf.expand_dims(inputs, axis=0)
21        x = self.dense2(self.dense1(inputs))
22        return tf.squeeze(self.dense3(x))
23
24model = MyModel()
25
26with tf.GradientTape() as g:
27    g.watch(true_y0)
28    y = odeint(model, true_y0, t)
29# gradient of the result w.r.t. model's weights
30g.gradient(y, model.trainable_variables)  # well define, no None, no inf or no NaN