I'm trying to compute the Lyapunov exponent for a smooth continuous time dynamical system(say, $\dot{\bar{x}} = f(\bar x)$). I using the QR decomposition method. Here are the steps that I follow.
- Choose some initial condition in the basin of the attractor. Call this $\bar v_0$. And have a blob (hypersphere, $U$) of unit radii around $\bar v_{0}$.
- Iterate one time-step to get $\bar v_{1}$. The blob evolves as $U_{1} = D\bar f(\bar v_{0})\cdot U$. $D\bar{f}(\bar v_{0})$ being the Jacobian matrix.
- Do a QR decomposition of $U_{1}$. $R$'s diag elements gives me the expansion/contraction rate. Store these lengths (in say J). And $Q$ is my new $U$.
- Continue this process for say $n$ times.
The Lyapunov exponent(s) is given by, $\lambda = \frac1{k \cdot dt} \sum_{i=1}^{n} \log |J_{ii}|$. $dt$ is the time-step of the integrator.
I'm using this procedure to compute the Lyapunov exponent for the Lorenz system as a check. But unortunately this is not working. Is there anything wrong with the steps of the algorithm?
For example, for the canonical Lorenz system with the parameters, $\sigma = 10, r = 28, b = \frac83$, and starting with the initial condition $(1, 1, 1)$, I get all positive Lyapunov exponents! Them being, $25.6336, 20.1935, 16.76311$. Temporally they fluctuate for some time, and then settle on these values.
Here is the code that I'm using:
import numpy as np
from scipy.integrate import solve_ivp
#ODE system
def func(t, v, sigma, r, b):
x, y, z = v #unpack the variables
return [ sigma * (y - x), r * x - y - x * z, x * y - b * z ]
#Jacobian matrix
def JM(v, sigma, r, b):
x, y, z = [k for k in v]
return np.array([[-sigma, sigma, 0], [r - z, -1, -x], [y, x, -b]])
#initial parameters
sigma = 10
r = 28
b = 8/3
U = np.eye(3) #unit blob
v0 = np.ones(3) #initial condition
lyap = [] #empty list to store the lengths of the orthogonal axes
iters=10**3
dt=0.1
tf=iters * dt
#integrate the ODE system -- hopefully falls into an attractor
sol = solve_ivp(func, [0, tf], v0, t_eval=np.linspace(0, tf, iters), args=(sigma, r, b))
v_n = sol.y.T #transpose the solution
#do this for each iteration
for k in range(0, iters):
v0 = v_n[k] #new v0 after iteration
U_n = np.matmul(np.eye(3) + JM(v0, sigma, r, b) * dt, U)
#do a Gram-Schmidt Orthogonalisation (GSO)
Q, R = np.linalg.qr(U_n)
lyap.append(np.log(abs(R.diagonal())))
U = Q #new axes after iteration
[sum([lyap[k][j] for k in range(iters)]) / (dt * iters) for j in range(3)]
Note: The funution $\text{JM}$ gives the Jacobian. $\text{funs}$ gives the system of ODE (Lorenz system here).
Update: Updated code. As per Lutz Lehmann's answer.