Recapitulando

Em Koopman — Introdução, EDMD estima \(K\) tal que \(\phi(x_{k+1}) \approx K\, \phi(x_k)\). Aqui, um sistema não linear de propósito, cujo espaço de observáveis invariante é conhecido — para conferir que EDMD recupera \(K\) exatamente.

Sistema de Exemplo

\[ x_{1,k+1} = \lambda_1 x_{1,k} \qquad x_{2,k+1} = \lambda_2 x_{2,k} + b\, x_{1,k}^2 \]

Não linear em \((x_1,x_2)\), mas linear no observável estendido \(z=(x_1,x_2,x_1^2)\), já que \(x_{1,k+1}^2=\lambda_1^2 x_{1,k}^2\):

\[ K = \begin{bmatrix} \lambda_1 & 0 & 0 \\ 0 & \lambda_2 & b \\ 0 & 0 & \lambda_1^2 \end{bmatrix} \]

Python — Dados e Observáveis

import numpy as np
lam1, lam2, bcoef = 0.9, 0.5, 1.0

def step(x):
    x1, x2 = x
    return np.array([lam1*x1, lam2*x2 + bcoef*x1**2])

def phi(x):
    x1, x2 = x
    return np.array([x1, x2, x1**2])

rng = np.random.default_rng(0)
X = rng.uniform(-1, 1, size=(300, 2))
Xnext = np.array([step(x) for x in X])

Python — Estimando \(K\)

Phi = np.array([phi(x) for x in X])
PhiNext = np.array([phi(x) for x in Xnext])
K = np.linalg.lstsq(Phi, PhiNext, rcond=None)[0].T

K_exact = np.array([[lam1,0,0], [0,lam2,bcoef], [0,0,lam1**2]])
print("K estimado:\n", np.round(K, 3))
K estimado:
 [[ 0.9  -0.   -0.  ]
 [-0.    0.5   1.  ]
 [-0.   -0.    0.81]]

Nota

Sem ruído, \(K\) recupera \(K_{\text{exato}}\) até erro de ponto flutuante.

Resultado — Predição da Trajetória

import matplotlib.pyplot as plt
x0, steps = np.array([0.5, -0.3]), 15

z = [phi(x0)]
for _ in range(steps): z.append(K @ z[-1])
z = np.array(z)

x_true = [x0]
for _ in range(steps): x_true.append(step(x_true[-1]))
x_true = np.array(x_true)

fig, ax = plt.subplots()
_ = ax.plot(x_true[:,1], "o-", label="$x_2$ verdadeiro")
_ = ax.plot(z[:,1], "x--", label="$x_2$ predito")
_ = ax.legend()
plt.tight_layout(); plt.show()

Por Que o Dicionário Importa

Sem \(x_1^2\) (DMD comum, \(\phi(x)=(x_1,x_2)\)):

Klin = np.linalg.lstsq(X, Xnext, rcond=None)[0].T
x_lin = [x0]
for _ in range(steps): x_lin.append(Klin @ x_lin[-1])
x_lin = np.array(x_lin)

print("erro com x1²:", np.max(np.abs(z[:,1] - x_true[:,1])))
erro com x1²: 2.220446049250313e-16
print("erro sem x1²:", np.max(np.abs(x_lin[:,1] - x_true[:,1])))
erro sem x1²: 0.24072030365909527

Importante

Sem \(x_1^2\), o subespaço deixa de ser invariante: \(K_{\text{lin}}\) é a melhor aproximação linear, mas erra sistematicamente.

Escolhendo Observáveis

Referências

Brunton, Steven L., Marko Budišić, Eurika Kaiser, e J. Nathan Kutz. 2022. «Modern Koopman Theory for Dynamical Systems». SIAM Review 64 (2): 229–340. https://doi.org/10.1137/21M1401243.
Mauroy, Alexandre, Igor Mezić, e Yoshihiko Susuki, eds. 2020. The Koopman Operator in Systems and Control. Springer. https://doi.org/10.1007/978-3-030-35713-9.
Williams, Matthew O., Clarence W. Rowley, e Ioannis G. Kevrekidis. 2015. «A Kernel-Based Method for Data-Driven Koopman Spectral Analysis». Journal of Computational Dynamics 2 (2): 247–65. https://doi.org/10.3934/jcd.2015005.