```python
import sys
sys.path.append('../../')
import os
import dill
import numpy as np
import scipy as sc
import random as rand
from sklearn import preprocessing, linear_model
import matplotlib.pyplot as plt

from core.controllers import PDController
from core.dynamics import LinearSystemDynamics, ConfigurationDynamics

from koopman_core.controllers import OpenLoopController, MPCController,BilinearFBLinController, PerturbedController, LinearLiftedController
from koopman_core.dynamics import LinearLiftedDynamics, BilinearLiftedDynamics
from koopman_core.learning import Edmd, BilinearEdmd, KoopDnn
from koopman_core.basis_functions import PlanarQuadBasis
from koopman_core.learning.utils import differentiate_vec
from koopman_core.systems import PlanarQuadrotorForceInput

class QuadrotorPdOutput(ConfigurationDynamics):
    def __init__(self, dynamics, xd, t_d, n, m):
        ConfigurationDynamics.__init__(self, dynamics, 1)
        self.xd = xd
        self.t_d = t_d
        self.xd_dot = differentiate_vec(self.xd, self.t_d)
        self.n = n
        self.m = m

    def proportional(self, x, t):
        q, q_dot = x[:int(n/2)], x[int(n/2):]
        return self.y(q) - self.y_d(t)

    def derivative(self, x, t):
        q, q_dot = x[:int(n/2)], x[int(n/2):]
        return self.dydq(q)@q_dot - self.y_d_dot(t)

    def y(self, q):
        return q

    def dydq(self, q):
        return np.eye(int(self.n/2))

    def d2ydq2(self, q):
        return np.zeros((int(self.n/2), int(self.n/2), int(self.n/2)))

    def y_d(self, t):
        return self.desired_state_(t)[:int(self.n/2)]

    def y_d_dot(self, t):
        return self.desired_state_(t)[int(self.n/2):]

    def y_d_ddot(self, t):
        return self.desired_state_dot_(t)[int(self.n/2):]

    def desired_state_(self, t):
        return [np.interp(t, self.t_d.flatten(),self.xd[:,ii].flatten()) for ii in range(self.xd.shape[1])]

    def desired_state_dot_(self, t):
        return [np.interp(t, self.t_d.flatten(),self.xd_dot[:,ii].flatten()) for ii in range(self.xd_dot.shape[1])]

class PlanarQuadrotorForceInputDiscrete(PlanarQuadrotorForceInput):
    def __init__(self, mass, inertia, prop_arm, g=9.81, dt=1e-2):
        PlanarQuadrotorForceInput.__init__(self, mass, inertia, prop_arm, g=g)
        self.dt=dt

    def eval_dot(self, x, u, t):
        return x + self.dt*self.drift(x, t) + self.dt*np.dot(self.act(x, t),u)

    def get_linearization(self, x0, x1, u0, t):
        m, J, b, g = self.params
        A_lin = np.eye(self.n) + self.dt*np.array([[0, 0, 0, 1, 0, 0],
                                                   [0, 0, 0, 0, 1, 0],
                                                   [0, 0, 0, 0, 0, 1],
                                                   [0, 0, -(1/m)*np.cos(x0[2])*u0[0] -(1/m)*np.cos(x0[2])*u0[1], 0, 0, 0],
                                                   [0, 0, -(1/m)*np.sin(x0[2])*u0[0] -(1/m)*np.sin(x0[2])*u0[1], 0, 0, 0],
                                                   [0, 0, 0, 0, 0, 0],])

        B_lin = self.dt*np.array([[0, 0],
                                  [0, 0],
                                  [0, 0],
                                  [-(1/m)*np.sin(x0[2]), -(1/m)*np.sin(x0[2])],
                                  [(1/m)*np.cos(x0[2]), (1/m)*np.cos(x0[2])],
                                  [-b/J, b/J]])

        if x1 is None:
            x1 = A_lin@x0 + B_lin@u0

        f_d = self.eval_dot(x0,u0,t)
        r_lin = f_d - x1

        return A_lin, B_lin, r_lin

```

## Planar Quadrotor Example

Consider a planar quadrotor with states $\mathbf{x} = [y \, z \, \theta \, \dot{y} \, \dot{z} \, \dot{\theta}]^T$ and continuous-time dynamics

\begin{equation}
    \begin{bmatrix} \ddot{y} \\ \ddot{z} \\ \ddot{\theta} \end{bmatrix}
    = \begin{bmatrix}
    0\\-g\\0
    \end{bmatrix} +
    \begin{bmatrix}
    -\frac{1}{m}\text{sin}\theta & -\frac{1}{m}\text{sin}\theta\\
    \frac{1}{m}\text{cos}\theta & \frac{1}{m}\text{cos}\theta\\
    -\frac{l_{arm}}{I_{xx}} & \frac{l_{arm}}{I_{xx}}
    \end{bmatrix}
    \begin{bmatrix}
    T_1 \\ T_2
    \end{bmatrix}
\end{equation}

where $y,z$ describe the position of the vehicle in a fixed reference frame, $\theta$ is the orientation of the vehicle,
$T_1, T_2$ are the thrust from each of the propellers, $g$ is the gravitational acceleration, $m$ is the vehicle mass,
$l_{arm}$ is the distance from the vehicle's center of mass to the center of the propeller, and $I_{xx}$ is the inertia
around the x-axis.

```python
mass = 2.
inertia = 1.
prop_arm = 0.2
gravity = 9.81
sys_name = 'planar_quad'
system = PlanarQuadrotorForceInput(mass, inertia, prop_arm, g=gravity)

# Linearized system specification:
n, m = 6, 2                                                         # Number of states, number of control inputs
A_nom = np.array([[0., 0., 0., 1., 0., 0.],                         # Linearization of the true system around the origin
                  [0., 0., 0., 0., 1., 0.],
                  [0., 0., 0., 0., 0., 1.],
                  [0., 0., -gravity, 0., 0., 0.],
                  [0., 0., 0., 0., 0., 0.],
                  [0., 0., 0., 0., 0., 0.]])
B_nom = np.array([[0., 0.],                                         # Linearization of the true system around the origin
                  [0., 0.],
                  [0., 0.],
                  [0., 0.],
                  [1./mass, 1./mass],
                  [-prop_arm/inertia, prop_arm/inertia]])

hover_thrust = mass*gravity/m
```

### Collect data for learning

To collect data, a nominal controller is designed with LQR on the dynamics's linearization around hover. However, any
controller can be used and the method does not require the knowledge of model's linearization. In addition, a
exploratory white noise is added to the controller to ensure that the data is sufficiently excited. Note that the system
is underactuated and that trajectory optimization is necessary to control the position of the vehicle. We use a
simplified trajectory generator based on a model predictive controller for the linearized dynamics. More careful design
of the desired trajectory may be necessary for more demanding applications and this is readily compatible with our method.

```python
q_dc, r_dc = 5e2, 1                                                 # State and actuation penalty values, data collection
Q_dc = q_dc * np.identity(n)                                        # State penalty matrix, data collection
R_dc = r_dc*np.identity(m)                                          # Actuation penalty matrix, data collection
P_dc = sc.linalg.solve_continuous_are(A_nom, B_nom, Q_dc, R_dc)     # Algebraic Ricatti equation solution, data collection
K_dc = np.linalg.inv(R_dc)@B_nom.T@P_dc                             # LQR feedback gain matrix, data collection
K_dc_p = K_dc[:,:int(n/2)]                                          # Proportional control gains, data collection
K_dc_d = K_dc[:,int(n/2):]                                          # Derivative control gains, data collection
nominal_sys = LinearLiftedDynamics(A_nom, B_nom, np.eye(n), lambda x: x)

# Data collection parameters:
collect_data = False
dt = 1.0e-2                                                         # Time step length
traj_length_dc = 2.                                                 # Trajectory length, data collection
n_pred_dc = int(traj_length_dc/dt)                                  # Number of time steps, data collection
t_train = dt * np.arange(n_pred_dc + 1)                             # Simulation time points
n_traj_train = 500                                                  # Number of trajectories to execute, data collection
n_traj_val = int(0.25*n_traj_train)
noise_var = 2.                                                      # Exploration noise to perturb controller, data collection

xmax = np.array([2, 2, np.pi/3, 2.,2.,2.])                          # State constraints, trajectory generation
xmin = -xmax
umax = np.array([2*hover_thrust, 2*hover_thrust]) - hover_thrust    # Actuation constraint, trajectory generation
umin = np.array([0., 0.]) - hover_thrust
x0_max = np.array([xmax[0], xmax[1], xmax[2], 1., 1., 1.])          # Initial value limits
Q_trajgen = sc.sparse.diags([0,0,0,0,0,0])                          # State penalty matrix, trajectory generation
QN_trajgen = sc.sparse.diags([1e2,1e2,1e2,1e1,1e1,1e1])             # Final state penalty matrix, trajectory generation
R_trajgen = sc.sparse.eye(m)                                        # Actuation penalty matrix, trajectory generation
sub_sample_rate = 1                                                 # Rate to subsample data for training
model_fname = 'examples/planar_quad_models'                         # Path to save learned models
n_cols = 10                                                         # Number of columns in training data plot
save_figures = True
directory = os.path.abspath("")                                     # Path to save learned models
#dropbox_folder = '/Users/carlaxelfolkestad/Dropbox/Apps/Overleaf/Koopman NMPC (ICRA21)/'
```

```python
if collect_data:
    xd = np.empty((n_traj_train + n_traj_val, n_pred_dc + 1, n))
    xs = np.empty((n_traj_train + n_traj_val, n_pred_dc + 1, n))
    us = np.empty((n_traj_train + n_traj_val, n_pred_dc, m))

    plt.figure(figsize=(12, 12 * (n_traj_train + n_traj_val) / (n_cols ** 2)))
    for ii in range(n_traj_train+n_traj_val):
        x0 = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
        set_pt_dc = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
        mpc_trajgen = MPCController(nominal_sys, n_pred_dc, dt, umin, umax, xmin, xmax, QN_trajgen, R_trajgen,
                                    QN_trajgen, set_pt_dc)
        mpc_trajgen.eval(x0, 0)
        xd[ii, :, :] = mpc_trajgen.parse_result().T
        while abs(x0[0]) + abs(x0[1]) < 1 or np.any(np.isnan(xd[ii, :, :])):
            x0 = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
            set_pt_dc = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
            mpc_trajgen = MPCController(nominal_sys, n_pred_dc, dt, umin, umax, xmin, xmax, QN_trajgen, R_trajgen,
                                        QN_trajgen, set_pt_dc)
            mpc_trajgen.eval(x0, 0)
            xd[ii, :, :] = mpc_trajgen.parse_result().T

        output = QuadrotorPdOutput(system, xd[ii, :, :], t_train, n, m)
        pd_controller = PDController(output, K_dc_p, K_dc_d)
        perturbed_pd_controller = PerturbedController(system, pd_controller, noise_var, const_offset=hover_thrust)
        xs[ii, :, :], us[ii, :, :] = system.simulate(x0, perturbed_pd_controller, t_train)

        plt.subplot(int(np.ceil((n_traj_train + n_traj_val) / n_cols)), n_cols, ii + 1)
        plt.plot(t_train, xs[ii, :, 0], 'b', label='$y$')
        plt.plot(t_train, xs[ii, :, 1], 'g', label='$z$')
        plt.plot(t_train, xs[ii, :, 2], 'r', label='$\\theta$')
        plt.plot(t_train, xd[ii, :, 0], '--b', label='$y_d$')
        plt.plot(t_train, xd[ii, :, 1], '--g', label='$z_d$')
        plt.plot(t_train, xd[ii, :, 2], '--r', label='$\\theta_d$')

    plt.suptitle(
        'Training data \nx-axis: time (sec), y-axis: state value, $x$ - blue, $xd$ - dotted blue, $\\theta$ - red, $\\theta_d$ - dotted red',
        y=0.94)
    plt.show()

    xs_train, us_train = xs[:n_traj_train,:,:], us[:n_traj_train, :, :]
    xs_val, us_val = xs[n_traj_train:,:,:], us[n_traj_train:, :, :]

    data_list = [xs_train, us_train, t_train, n_traj_train, xs_val, us_val, t_train, n_traj_val]
    outfile = open(directory + '/data/' + sys_name + '_data.pickle', 'wb')
    dill.dump(data_list, outfile)
    outfile.close()
else:
    infile = open(directory + '/data/' + sys_name + '_data.pickle', 'rb')
    xs_train, us_train, t_train, n_traj_train, xs_val, us_val, t_val, n_traj_val = dill.load(infile)
    infile.close()
```

### Learn a linear model with dynamic mode decomposition (DMD)

To compare our method with existing techniques, we first learn a linear state space model from data. This is dubbed
dynamic mode decomposition. I.e. we use linear regression with LASSO regularization to learn an approximate linear model
with model structure

\begin{equation}
    \mathbf{\dot{x}} = A_{dmd}\mathbf{x} + B_{dmd}\mathbf{u}
\end{equation}

```python
#DMD parameters:
alpha_dmd = 9.8e-5                                                  # Regularization strength (LASSO) DMD
tune_mdl_dmd = False
```

```python
basis = lambda x: x
C_dmd = np.eye(n)

optimizer_dmd = linear_model.MultiTaskLasso(alpha=alpha_dmd, fit_intercept=False, selection='random')
cv_dmd = linear_model.MultiTaskLassoCV(fit_intercept=False, n_jobs=-1, cv=3, selection='random')
standardizer_dmd = preprocessing.StandardScaler(with_mean=False)

model_dmd = Edmd(n, m, basis, n, n_traj_train, optimizer_dmd, cv=cv_dmd, standardizer=standardizer_dmd, C=C_dmd,
                 first_obs_const=False, continuous_mdl=False, dt=dt)
xdmd, y_dmd = model_dmd.process(xs_train, us_train-hover_thrust, np.tile(t_train,(n_traj_train,1)),
                                downsample_rate=sub_sample_rate)
model_dmd.fit(xdmd, y_dmd, cv=tune_mdl_dmd, override_kinematics=True)
sys_dmd = LinearLiftedDynamics(model_dmd.A, model_dmd.B, model_dmd.C, model_dmd.basis, continuous_mdl=False, dt=dt)
if tune_mdl_dmd:
    print('$\\alpha$ DMD: ',model_dmd.cv.alpha_)
```

### Learn a lifted linear model with extended dynamic mode decomposition (EDMD)

In addition, we compare our method with the current state of the art of Koopman based learning, the extended dynamic mode
decomposition. We use a dictionary of nonlinear functions $\boldsymbol{\phi(x)}$ to lift the state variables and learn a lifted state space model
of the dynamics. I.e. we first lift and then use linear regression with LASSO regularization to learn an approximate
lifted linear model with model structure

\begin{equation}
    \mathbf{\dot{z}} = A_{edmd}\mathbf{z} + B_{edmd}\mathbf{u}, \qquad \mathbf{z} = \boldsymbol{\phi(x)}
\end{equation}

```python
#EDMD parameters:
alpha_edmd = 2.22e-4                                                 # Regularization strength (LASSO) EDMD
tune_mdl_edmd = False
```

```python
basis = PlanarQuadBasis(n, poly_deg=3)
basis.construct_basis()
planar_quad_features = preprocessing.FunctionTransformer(basis.basis)
planar_quad_features.fit(np.zeros((1,n)))
n_lift_edmd = planar_quad_features.transform((np.zeros((1,n)))).shape[1]
C_edmd = np.zeros((n,n_lift_edmd))
C_edmd[:,1:n+1] = np.eye(n)

optimizer_edmd = linear_model.MultiTaskLasso(alpha=alpha_edmd, fit_intercept=False, selection='random',
                                             max_iter=2000)
cv_edmd = linear_model.MultiTaskLassoCV(fit_intercept=False, n_jobs=-1, cv=3, selection='random', max_iter=2000)
standardizer_edmd = preprocessing.StandardScaler(with_mean=False)

model_edmd = Edmd(n, m, basis.basis, n_lift_edmd, n_traj_train, optimizer_edmd, cv=cv_edmd,
                  standardizer=standardizer_edmd, C=C_edmd, continuous_mdl=False, dt=dt)
X_edmd, y_edmd = model_edmd.process(xs_train, us_train-hover_thrust, np.tile(t_train,(n_traj_train,1)),
                                    downsample_rate=sub_sample_rate)
model_edmd.fit(X_edmd, y_edmd, cv=tune_mdl_edmd, override_kinematics=True)
model_edmd.reduce_mdl()
sys_edmd = LinearLiftedDynamics(model_edmd.A, model_edmd.B, model_edmd.C, model_edmd.basis_reduced,
                                continuous_mdl=False, dt=dt)
if tune_mdl_edmd:
    print('$\\alpha$ EDMD: ',model_edmd.cv.alpha_)

```

### Learn a lifted bilinear model with bilinear extended mode decomposition (bEDMD)

Finally, we use the method developed in the paper to learn a lifted bilinear model of the dynamics, dubbed bilinear
extended mode decomposition (bEDMD). I.e. we first lift and then use linear regression with LASSO regularization to learn an approximate
lifted linear model with model structure

\begin{equation}
    \mathbf{\dot{z}}=F\mathbf{z}+\sum_{i=1}^m G_i\mathbf{z}\mathbf{u}_i, \qquad \mathbf{z} = \boldsymbol{\phi(x)}
\end{equation}

```python
#Bilinear EDMD parameters:
alpha_bedmd = 6.9e-5  # Regularization strength (LASSO) bEDMD
tune_mdl_bedmd = False
```

```python
n_lift_bedmd = n_lift_edmd
C_bedmd = np.zeros((n,n_lift_bedmd))
C_bedmd[:,1:n+1] = np.eye(n)

basis_bedmd = lambda x: planar_quad_features.transform(x)
optimizer_bedmd = linear_model.MultiTaskLasso(alpha=alpha_bedmd, fit_intercept=False, selection='random')
cv_bedmd = linear_model.MultiTaskLassoCV(fit_intercept=False, n_jobs=-1, cv=3, selection='random')
standardizer_bedmd = preprocessing.StandardScaler(with_mean=False)

model_bedmd = BilinearEdmd(n, m, basis_bedmd, n_lift_bedmd, n_traj_train, optimizer_bedmd, cv=cv_bedmd,
                           standardizer=standardizer_bedmd, C=C_bedmd, continuous_mdl=False, dt=dt)
X_bedmd, y_bedmd = model_bedmd.process(xs_train, us_train-hover_thrust, np.tile(t_train,(n_traj_train,1)),
                                       downsample_rate=sub_sample_rate)
model_bedmd.fit(X_bedmd, y_bedmd, cv=tune_mdl_bedmd, override_kinematics=True)
sys_bedmd = BilinearLiftedDynamics(model_bedmd.n_lift, m, model_bedmd.A, model_bedmd.B, model_bedmd.C,
                                   model_bedmd.basis, continuous_mdl=False, dt=dt)
if tune_mdl_bedmd:
    print('$\\alpha$ bilinear EDMD: ', model_bedmd.cv.alpha_)

```

    /Users/carlaxelfolkestad/.conda/envs/keedmd/lib/python3.7/site-packages/sklearn/linear_model/_coordinate_descent.py:1790: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 0.2162272752800316, tolerance: 0.07207116984129382
      check_random_state(self.random_state), random)

### Learn a lifted bilinear model with Koopman NN

```python
import dill, os, torch

load_tuned_params = False

if load_tuned_params:
    infile = open(os.path.abspath('') + '/data/planar_quad_best_params.pickle', 'rb')
    net_params_lst, val_loss, test_loss, open_loop_mse, open_loop_std = dill.load(infile)
    infile.close()
    net_params = net_params_lst[-3]

    print(open_loop_mse)
    plt.figure()
    plt.plot(val_loss, label='Validation loss')
    plt.plot(test_loss, label='Test loss')
    plt.plot(open_loop_mse, label='Open loop mse')
    plt.legend()
    plt.show()

else:
    net_params = {}
    net_params['state_dim'] = n
    net_params['ctrl_dim'] = m
    net_params['encoder_hidden_width'] = 100
    net_params['encoder_hidden_depth'] = 1
    net_params['encoder_output_dim'] = 10
    net_params['optimizer'] = 'adam'
    net_params['activation_type'] = 'tanh'
    net_params['lr'] = 1e-3
    net_params['epochs'] = 500
    net_params['batch_size'] = 256
    net_params['lin_loss_penalty'] = 0.9
    net_params['l2_reg'] = 1e-6
    net_params['l1_reg'] = 1e-6
    net_params['first_obs_const'] = True
    net_params['override_kinematics'] = True
    net_params['override_C'] = True
    net_params['dt'] = dt

print(net_params)
```

    {'state_dim': 6, 'ctrl_dim': 2, 'encoder_hidden_width': 100, 'encoder_hidden_depth': 1, 'encoder_output_dim': 10, 'optimizer': 'adam', 'activation_type': 'tanh', 'lr': 0.001, 'epochs': 500, 'batch_size': 256, 'lin_loss_penalty': 0.9, 'l2_reg': 1e-06, 'l1_reg': 1e-06, 'first_obs_const': True, 'override_kinematics': True, 'override_C': True, 'dt': 0.01}

```python
from koopman_core.learning import KoopDnn, KoopmanNetCtrl
from koopman_core.util import fit_standardizer

standardizer_x_kdnn = fit_standardizer(xs_train, preprocessing.StandardScaler())
standardizer_u_kdnn = fit_standardizer(us_train-hover_thrust, preprocessing.StandardScaler(with_mean=False))

net = KoopmanNetCtrl(net_params, standardizer_x=standardizer_x_kdnn, standardizer_u=standardizer_u_kdnn)
model_koop_dnn = KoopDnn(net)
model_koop_dnn.set_datasets(xs_train, t_train, u_train=us_train-hover_thrust, x_val=xs_val, u_val=us_val-hover_thrust, t_val=t_train)
model_koop_dnn.model_pipeline(net_params)
model_koop_dnn.construct_koopman_model()
sys_koop_dnn = BilinearLiftedDynamics(model_koop_dnn.net.n_tot, m, model_koop_dnn.A, model_koop_dnn.B, model_koop_dnn.C, model_koop_dnn.basis_encode,
                                    continuous_mdl=False, dt=dt, standardizer_x=standardizer_x_kdnn,
                                      standardizer_u=standardizer_u_kdnn)
```

    Epoch   1: train loss: 0.13335793, validation loss: 0.01259141
    Epoch   2: train loss: 0.00980584, validation loss: 0.00925152
    Epoch   3: train loss: 0.00708126, validation loss: 0.00650281
    Epoch   4: train loss: 0.00487909, validation loss: 0.00445679
    Epoch   5: train loss: 0.00338148, validation loss: 0.00324028
    Epoch   6: train loss: 0.00252589, validation loss: 0.00255369
    Epoch   7: train loss: 0.00201190, validation loss: 0.00207453
    Epoch   8: train loss: 0.00166438, validation loss: 0.00175000
    Epoch   9: train loss: 0.00143117, validation loss: 0.00146351
    Epoch  10: train loss: 0.00123746, validation loss: 0.00127742
    Epoch  11: train loss: 0.00108055, validation loss: 0.00110991
    Epoch  12: train loss: 0.00095070, validation loss: 0.00103909
    Epoch  13: train loss: 0.00084477, validation loss: 0.00088243
    Epoch  14: train loss: 0.00073658, validation loss: 0.00075431
    Epoch  15: train loss: 0.00065887, validation loss: 0.00070848
    Epoch  16: train loss: 0.00057808, validation loss: 0.00059338
    Epoch  17: train loss: 0.00049205, validation loss: 0.00051975
    Epoch  18: train loss: 0.00040318, validation loss: 0.00040563
    Epoch  19: train loss: 0.00033975, validation loss: 0.00032654
    Epoch  20: train loss: 0.00027919, validation loss: 0.00031570
    Epoch  21: train loss: 0.00024492, validation loss: 0.00026025
    Epoch  22: train loss: 0.00022293, validation loss: 0.00024281
    Epoch  23: train loss: 0.00020665, validation loss: 0.00023658
    Epoch  24: train loss: 0.00019459, validation loss: 0.00027384
    Epoch  25: train loss: 0.00018757, validation loss: 0.00021020
    Epoch  26: train loss: 0.00018173, validation loss: 0.00020509
    Epoch  27: train loss: 0.00017220, validation loss: 0.00018541
    Epoch  28: train loss: 0.00016644, validation loss: 0.00018737
    Epoch  29: train loss: 0.00016619, validation loss: 0.00021608
    Epoch  30: train loss: 0.00016767, validation loss: 0.00017637
    Epoch  31: train loss: 0.00016301, validation loss: 0.00018202
    Epoch  32: train loss: 0.00015333, validation loss: 0.00016452
    Epoch  33: train loss: 0.00015089, validation loss: 0.00022004
    Epoch  34: train loss: 0.00015143, validation loss: 0.00017123
    Epoch  35: train loss: 0.00014858, validation loss: 0.00014595
    Epoch  36: train loss: 0.00014403, validation loss: 0.00016093
    Epoch  37: train loss: 0.00014309, validation loss: 0.00014229
    Epoch  38: train loss: 0.00013826, validation loss: 0.00014408
    Epoch  39: train loss: 0.00015511, validation loss: 0.00018782
    Epoch  40: train loss: 0.00013804, validation loss: 0.00016002
    Epoch  41: train loss: 0.00013083, validation loss: 0.00013592
    Epoch  42: train loss: 0.00013968, validation loss: 0.00014806
    Epoch  43: train loss: 0.00013058, validation loss: 0.00013850
    Epoch  44: train loss: 0.00012960, validation loss: 0.00012260
    Epoch  45: train loss: 0.00013349, validation loss: 0.00014441
    Epoch  46: train loss: 0.00012562, validation loss: 0.00013348
    Epoch  47: train loss: 0.00012949, validation loss: 0.00013667
    Epoch  48: train loss: 0.00012440, validation loss: 0.00012097
    Epoch  49: train loss: 0.00012106, validation loss: 0.00012483
    Epoch  50: train loss: 0.00012923, validation loss: 0.00011606
    Epoch  51: train loss: 0.00012038, validation loss: 0.00013174
    Epoch  52: train loss: 0.00012460, validation loss: 0.00013850
    Epoch  53: train loss: 0.00011376, validation loss: 0.00013763
    Epoch  54: train loss: 0.00012096, validation loss: 0.00011819
    Epoch  55: train loss: 0.00011723, validation loss: 0.00012226
    Epoch  56: train loss: 0.00012001, validation loss: 0.00011488
    Epoch  57: train loss: 0.00011696, validation loss: 0.00010966
    Epoch  58: train loss: 0.00011915, validation loss: 0.00011605
    Epoch  59: train loss: 0.00011243, validation loss: 0.00011147
    Epoch  60: train loss: 0.00011988, validation loss: 0.00013710
    Epoch  61: train loss: 0.00011297, validation loss: 0.00011108
    Epoch  62: train loss: 0.00011228, validation loss: 0.00014417
    Epoch  63: train loss: 0.00011456, validation loss: 0.00013470
    Epoch  64: train loss: 0.00010719, validation loss: 0.00012642
    Epoch  65: train loss: 0.00011188, validation loss: 0.00010301
    Epoch  66: train loss: 0.00011393, validation loss: 0.00012076
    Epoch  67: train loss: 0.00010534, validation loss: 0.00010095
    Epoch  68: train loss: 0.00011051, validation loss: 0.00011773
    Epoch  69: train loss: 0.00010356, validation loss: 0.00010625
    Epoch  70: train loss: 0.00010998, validation loss: 0.00010925
    Epoch  71: train loss: 0.00010949, validation loss: 0.00010974
    Epoch  72: train loss: 0.00010572, validation loss: 0.00011731
    Epoch  73: train loss: 0.00010455, validation loss: 0.00009910
    Epoch  74: train loss: 0.00010513, validation loss: 0.00010713
    Epoch  75: train loss: 0.00010690, validation loss: 0.00010887
    Epoch  76: train loss: 0.00010288, validation loss: 0.00010014
    Epoch  77: train loss: 0.00010999, validation loss: 0.00009746
    Epoch  78: train loss: 0.00009998, validation loss: 0.00011726
    Epoch  79: train loss: 0.00010329, validation loss: 0.00013377
    Epoch  80: train loss: 0.00010556, validation loss: 0.00012139
    Epoch  81: train loss: 0.00010348, validation loss: 0.00009419
    Epoch  82: train loss: 0.00010024, validation loss: 0.00011079
    Epoch  83: train loss: 0.00010331, validation loss: 0.00010027
    Epoch  84: train loss: 0.00009969, validation loss: 0.00013045
    Epoch  85: train loss: 0.00009958, validation loss: 0.00012181
    Epoch  86: train loss: 0.00009829, validation loss: 0.00013662
    Epoch  87: train loss: 0.00010857, validation loss: 0.00012934
    Epoch  88: train loss: 0.00009494, validation loss: 0.00010114
    Epoch  89: train loss: 0.00010002, validation loss: 0.00010033
    Epoch  90: train loss: 0.00009737, validation loss: 0.00009978
    Epoch  91: train loss: 0.00010228, validation loss: 0.00010606
    Epoch  92: train loss: 0.00009784, validation loss: 0.00009611
    Epoch  93: train loss: 0.00009615, validation loss: 0.00009792
    Epoch  94: train loss: 0.00009705, validation loss: 0.00011246
    Epoch  95: train loss: 0.00009856, validation loss: 0.00010465
    Epoch  96: train loss: 0.00009369, validation loss: 0.00013292
    Epoch  97: train loss: 0.00009896, validation loss: 0.00009271
    Epoch  98: train loss: 0.00009707, validation loss: 0.00010063
    Epoch  99: train loss: 0.00009298, validation loss: 0.00009459
    Epoch 100: train loss: 0.00009404, validation loss: 0.00009378
    Epoch 101: train loss: 0.00009864, validation loss: 0.00011218
    Epoch 102: train loss: 0.00009256, validation loss: 0.00014778
    Epoch 103: train loss: 0.00009470, validation loss: 0.00009583
    Epoch 104: train loss: 0.00009493, validation loss: 0.00008993
    Epoch 105: train loss: 0.00009355, validation loss: 0.00009187
    Epoch 106: train loss: 0.00009565, validation loss: 0.00009570
    Epoch 107: train loss: 0.00009119, validation loss: 0.00009249
    Epoch 108: train loss: 0.00009374, validation loss: 0.00009906
    Epoch 109: train loss: 0.00009557, validation loss: 0.00009893
    Epoch 110: train loss: 0.00009091, validation loss: 0.00008595
    Epoch 111: train loss: 0.00009430, validation loss: 0.00008995
    Epoch 112: train loss: 0.00008841, validation loss: 0.00009309
    Epoch 113: train loss: 0.00009218, validation loss: 0.00014056
    Epoch 114: train loss: 0.00009624, validation loss: 0.00009929
    Epoch 115: train loss: 0.00008600, validation loss: 0.00010353
    Epoch 116: train loss: 0.00009133, validation loss: 0.00009968
    Epoch 117: train loss: 0.00009323, validation loss: 0.00009132
    Epoch 118: train loss: 0.00008855, validation loss: 0.00008663
    Epoch 119: train loss: 0.00009226, validation loss: 0.00008946
    Epoch 120: train loss: 0.00008738, validation loss: 0.00010490
    Epoch 121: train loss: 0.00008923, validation loss: 0.00008706
    Epoch 122: train loss: 0.00009051, validation loss: 0.00009610
    Epoch 123: train loss: 0.00008727, validation loss: 0.00008729
    Epoch 124: train loss: 0.00009010, validation loss: 0.00008735
    Epoch 125: train loss: 0.00008957, validation loss: 0.00009811
    Epoch 126: train loss: 0.00008868, validation loss: 0.00014087
    Epoch 127: train loss: 0.00008714, validation loss: 0.00011667
    Epoch 128: train loss: 0.00008892, validation loss: 0.00010810
    Epoch 129: train loss: 0.00008666, validation loss: 0.00008622
    Epoch 130: train loss: 0.00008787, validation loss: 0.00009818
    Epoch 131: train loss: 0.00009216, validation loss: 0.00008179
    Epoch 132: train loss: 0.00008670, validation loss: 0.00012902
    Epoch 133: train loss: 0.00008873, validation loss: 0.00007947
    Epoch 134: train loss: 0.00008358, validation loss: 0.00009510
    Epoch 135: train loss: 0.00008236, validation loss: 0.00007979
    Epoch 136: train loss: 0.00008507, validation loss: 0.00017276
    Epoch 137: train loss: 0.00008986, validation loss: 0.00007677
    Epoch 138: train loss: 0.00008366, validation loss: 0.00008809
    Epoch 139: train loss: 0.00008140, validation loss: 0.00009492
    Epoch 140: train loss: 0.00008604, validation loss: 0.00010583
    Epoch 141: train loss: 0.00008429, validation loss: 0.00009016
    Epoch 142: train loss: 0.00008682, validation loss: 0.00008156
    Epoch 143: train loss: 0.00008244, validation loss: 0.00008430
    Epoch 144: train loss: 0.00008463, validation loss: 0.00008326
    Epoch 145: train loss: 0.00008523, validation loss: 0.00007978
    Epoch 146: train loss: 0.00008347, validation loss: 0.00007852
    Epoch 147: train loss: 0.00008265, validation loss: 0.00008784
    Epoch 148: train loss: 0.00008158, validation loss: 0.00009124
    Epoch 149: train loss: 0.00008347, validation loss: 0.00007871
    Epoch 150: train loss: 0.00008232, validation loss: 0.00007529
    Epoch 151: train loss: 0.00008307, validation loss: 0.00007886
    Epoch 152: train loss: 0.00008040, validation loss: 0.00009226
    Epoch 153: train loss: 0.00008667, validation loss: 0.00013052
    Epoch 154: train loss: 0.00007779, validation loss: 0.00007389
    Epoch 155: train loss: 0.00008175, validation loss: 0.00007882
    Epoch 156: train loss: 0.00007913, validation loss: 0.00007582
    Epoch 157: train loss: 0.00008288, validation loss: 0.00010252
    Epoch 158: train loss: 0.00008047, validation loss: 0.00008519
    Epoch 159: train loss: 0.00008141, validation loss: 0.00009514
    Epoch 160: train loss: 0.00007879, validation loss: 0.00008634
    Epoch 161: train loss: 0.00007859, validation loss: 0.00007733
    Epoch 162: train loss: 0.00008286, validation loss: 0.00010578
    Epoch 163: train loss: 0.00007702, validation loss: 0.00007529
    Epoch 164: train loss: 0.00008246, validation loss: 0.00008364
    Epoch 165: train loss: 0.00007804, validation loss: 0.00007760
    Epoch 166: train loss: 0.00007644, validation loss: 0.00010714
    Epoch 167: train loss: 0.00008207, validation loss: 0.00013193
    Epoch 168: train loss: 0.00007721, validation loss: 0.00007662
    Epoch 169: train loss: 0.00007527, validation loss: 0.00007743
    Epoch 170: train loss: 0.00008007, validation loss: 0.00008246
    Epoch 171: train loss: 0.00007895, validation loss: 0.00008035
    Epoch 172: train loss: 0.00008336, validation loss: 0.00008668
    Epoch 173: train loss: 0.00007171, validation loss: 0.00007851
    Epoch 174: train loss: 0.00007771, validation loss: 0.00007316
    Epoch 175: train loss: 0.00008534, validation loss: 0.00007442
    Epoch 176: train loss: 0.00007259, validation loss: 0.00008666
    Epoch 177: train loss: 0.00007467, validation loss: 0.00007702
    Epoch 178: train loss: 0.00007903, validation loss: 0.00007540
    Epoch 179: train loss: 0.00007389, validation loss: 0.00008146
    Epoch 180: train loss: 0.00007452, validation loss: 0.00006959
    Epoch 181: train loss: 0.00008204, validation loss: 0.00007471
    Epoch 182: train loss: 0.00007463, validation loss: 0.00006906
    Epoch 183: train loss: 0.00007446, validation loss: 0.00007285
    Epoch 184: train loss: 0.00007633, validation loss: 0.00007766
    Epoch 185: train loss: 0.00007242, validation loss: 0.00009174
    Epoch 186: train loss: 0.00007723, validation loss: 0.00007087
    Epoch 187: train loss: 0.00007391, validation loss: 0.00007122
    Epoch 188: train loss: 0.00007654, validation loss: 0.00008344
    Epoch 189: train loss: 0.00007203, validation loss: 0.00007201
    Epoch 190: train loss: 0.00007986, validation loss: 0.00008100
    Epoch 191: train loss: 0.00006904, validation loss: 0.00007543
    Epoch 192: train loss: 0.00007676, validation loss: 0.00008999
    Epoch 193: train loss: 0.00007385, validation loss: 0.00008038
    Epoch 194: train loss: 0.00007063, validation loss: 0.00006804
    Epoch 195: train loss: 0.00007643, validation loss: 0.00007156
    Epoch 196: train loss: 0.00007282, validation loss: 0.00008031
    Epoch 197: train loss: 0.00007240, validation loss: 0.00007355
    Epoch 198: train loss: 0.00007217, validation loss: 0.00006870
    Epoch 199: train loss: 0.00007305, validation loss: 0.00007806
    Epoch 200: train loss: 0.00007428, validation loss: 0.00007967
    Epoch 201: train loss: 0.00007226, validation loss: 0.00008789
    Epoch 202: train loss: 0.00007516, validation loss: 0.00008142
    Epoch 203: train loss: 0.00006931, validation loss: 0.00008010
    Epoch 204: train loss: 0.00007243, validation loss: 0.00007445
    Epoch 205: train loss: 0.00007279, validation loss: 0.00007292
    Epoch 206: train loss: 0.00006882, validation loss: 0.00010166
    Epoch 207: train loss: 0.00007386, validation loss: 0.00006929
    Epoch 208: train loss: 0.00007460, validation loss: 0.00006869
    Epoch 209: train loss: 0.00006916, validation loss: 0.00008162
    Epoch 210: train loss: 0.00007398, validation loss: 0.00007247
    Epoch 211: train loss: 0.00007647, validation loss: 0.00008496
    Epoch 212: train loss: 0.00006909, validation loss: 0.00007063
    Epoch 213: train loss: 0.00007185, validation loss: 0.00010159
    Epoch 214: train loss: 0.00006829, validation loss: 0.00007857
    Epoch 215: train loss: 0.00007091, validation loss: 0.00007790
    Epoch 216: train loss: 0.00007271, validation loss: 0.00006495
    Epoch 217: train loss: 0.00006719, validation loss: 0.00007311
    Epoch 218: train loss: 0.00007934, validation loss: 0.00007556
    Epoch 219: train loss: 0.00006523, validation loss: 0.00006507
    Epoch 220: train loss: 0.00007132, validation loss: 0.00008135
    Epoch 221: train loss: 0.00006586, validation loss: 0.00010232
    Epoch 222: train loss: 0.00007421, validation loss: 0.00009046
    Epoch 223: train loss: 0.00006959, validation loss: 0.00013701
    Epoch 224: train loss: 0.00006806, validation loss: 0.00006875
    Epoch 225: train loss: 0.00007143, validation loss: 0.00006606
    Epoch 226: train loss: 0.00006863, validation loss: 0.00007703
    Epoch 227: train loss: 0.00006764, validation loss: 0.00006767
    Epoch 228: train loss: 0.00007225, validation loss: 0.00006291
    Epoch 229: train loss: 0.00006791, validation loss: 0.00008491
    Epoch 230: train loss: 0.00007043, validation loss: 0.00006459
    Epoch 231: train loss: 0.00006747, validation loss: 0.00006877
    Epoch 232: train loss: 0.00007363, validation loss: 0.00008738
    Epoch 233: train loss: 0.00006533, validation loss: 0.00006643
    Epoch 234: train loss: 0.00006968, validation loss: 0.00010832
    Epoch 235: train loss: 0.00007003, validation loss: 0.00007099
    Epoch 236: train loss: 0.00006911, validation loss: 0.00006954
    Epoch 237: train loss: 0.00006653, validation loss: 0.00007388
    Epoch 238: train loss: 0.00006880, validation loss: 0.00007265
    Epoch 239: train loss: 0.00006901, validation loss: 0.00007223
    Epoch 240: train loss: 0.00006540, validation loss: 0.00006560
    Epoch 241: train loss: 0.00006773, validation loss: 0.00006374
    Epoch 242: train loss: 0.00006891, validation loss: 0.00006646
    Epoch 243: train loss: 0.00006707, validation loss: 0.00006804
    Epoch 244: train loss: 0.00006612, validation loss: 0.00006841
    Epoch 245: train loss: 0.00007120, validation loss: 0.00006527
    Epoch 246: train loss: 0.00006624, validation loss: 0.00006201
    Epoch 247: train loss: 0.00006739, validation loss: 0.00009579
    Epoch 248: train loss: 0.00006762, validation loss: 0.00006463
    Epoch 249: train loss: 0.00006661, validation loss: 0.00006489
    Epoch 250: train loss: 0.00007292, validation loss: 0.00007228
    Epoch 251: train loss: 0.00006406, validation loss: 0.00008743
    Epoch 252: train loss: 0.00006472, validation loss: 0.00006269
    Epoch 253: train loss: 0.00006730, validation loss: 0.00007998
    Epoch 254: train loss: 0.00006582, validation loss: 0.00006766
    Epoch 255: train loss: 0.00006505, validation loss: 0.00006431
    Epoch 256: train loss: 0.00006747, validation loss: 0.00006542
    Epoch 257: train loss: 0.00007616, validation loss: 0.00007213
    Epoch 258: train loss: 0.00005949, validation loss: 0.00008809
    Epoch 259: train loss: 0.00006774, validation loss: 0.00007903
    Epoch 260: train loss: 0.00006603, validation loss: 0.00006271
    Epoch 261: train loss: 0.00006506, validation loss: 0.00007099
    Epoch 262: train loss: 0.00006990, validation loss: 0.00008036
    Epoch 263: train loss: 0.00006182, validation loss: 0.00006986
    Epoch 264: train loss: 0.00006889, validation loss: 0.00006196
    Epoch 265: train loss: 0.00006503, validation loss: 0.00015469
    Epoch 266: train loss: 0.00006760, validation loss: 0.00007373
    Epoch 267: train loss: 0.00006351, validation loss: 0.00006734
    Epoch 268: train loss: 0.00006320, validation loss: 0.00005871
    Epoch 269: train loss: 0.00006584, validation loss: 0.00007053
    Epoch 270: train loss: 0.00006529, validation loss: 0.00006371
    Epoch 271: train loss: 0.00007020, validation loss: 0.00006470
    Epoch 272: train loss: 0.00006313, validation loss: 0.00007151
    Epoch 273: train loss: 0.00006407, validation loss: 0.00006717
    Epoch 274: train loss: 0.00006424, validation loss: 0.00006734
    Epoch 275: train loss: 0.00006532, validation loss: 0.00006293
    Epoch 276: train loss: 0.00006180, validation loss: 0.00006276
    Epoch 277: train loss: 0.00006659, validation loss: 0.00006266
    Epoch 278: train loss: 0.00006578, validation loss: 0.00006309
    Epoch 279: train loss: 0.00006429, validation loss: 0.00007638
    Epoch 280: train loss: 0.00006247, validation loss: 0.00007270
    Epoch 281: train loss: 0.00006923, validation loss: 0.00006427
    Epoch 282: train loss: 0.00006237, validation loss: 0.00006689
    Epoch 283: train loss: 0.00006544, validation loss: 0.00006649
    Epoch 284: train loss: 0.00006322, validation loss: 0.00009068
    Epoch 285: train loss: 0.00006525, validation loss: 0.00006191
    Epoch 286: train loss: 0.00006212, validation loss: 0.00006064
    Epoch 287: train loss: 0.00006506, validation loss: 0.00005949
    Epoch 288: train loss: 0.00006361, validation loss: 0.00006334
    Epoch 289: train loss: 0.00006232, validation loss: 0.00008718
    Epoch 290: train loss: 0.00006632, validation loss: 0.00006155
    Epoch 291: train loss: 0.00006372, validation loss: 0.00010483
    Epoch 292: train loss: 0.00006038, validation loss: 0.00006227
    Epoch 293: train loss: 0.00006261, validation loss: 0.00005975
    Epoch 294: train loss: 0.00006295, validation loss: 0.00015820
    Epoch 295: train loss: 0.00006522, validation loss: 0.00007121
    Epoch 296: train loss: 0.00006313, validation loss: 0.00007562
    Epoch 297: train loss: 0.00006127, validation loss: 0.00007077
    Epoch 298: train loss: 0.00006200, validation loss: 0.00006121
    Epoch 299: train loss: 0.00006563, validation loss: 0.00006256
    Epoch 300: train loss: 0.00006017, validation loss: 0.00007976
    Epoch 301: train loss: 0.00006289, validation loss: 0.00006095
    Epoch 302: train loss: 0.00006538, validation loss: 0.00006948
    Epoch 303: train loss: 0.00006106, validation loss: 0.00006070
    Epoch 304: train loss: 0.00006044, validation loss: 0.00007261
    Epoch 305: train loss: 0.00006229, validation loss: 0.00006064
    Epoch 306: train loss: 0.00006476, validation loss: 0.00005677
    Epoch 307: train loss: 0.00005986, validation loss: 0.00013190
    Epoch 308: train loss: 0.00006276, validation loss: 0.00006016
    Epoch 309: train loss: 0.00006300, validation loss: 0.00005974
    Epoch 310: train loss: 0.00006288, validation loss: 0.00006690
    Epoch 311: train loss: 0.00005941, validation loss: 0.00007541
    Epoch 312: train loss: 0.00006362, validation loss: 0.00007254
    Epoch 313: train loss: 0.00006234, validation loss: 0.00011238
    Epoch 314: train loss: 0.00006139, validation loss: 0.00006189
    Epoch 315: train loss: 0.00006189, validation loss: 0.00006186
    Epoch 316: train loss: 0.00006193, validation loss: 0.00006171
    Epoch 317: train loss: 0.00005919, validation loss: 0.00006407
    Epoch 318: train loss: 0.00005981, validation loss: 0.00008356
    Epoch 319: train loss: 0.00006302, validation loss: 0.00008587
    Epoch 320: train loss: 0.00006468, validation loss: 0.00009131
    Epoch 321: train loss: 0.00006142, validation loss: 0.00006125
    Epoch 322: train loss: 0.00006390, validation loss: 0.00009942
    Epoch 323: train loss: 0.00005858, validation loss: 0.00005782
    Epoch 324: train loss: 0.00005828, validation loss: 0.00006196
    Epoch 325: train loss: 0.00006318, validation loss: 0.00007381
    Epoch 326: train loss: 0.00005796, validation loss: 0.00006066
    Epoch 327: train loss: 0.00006487, validation loss: 0.00005656
    Epoch 328: train loss: 0.00005786, validation loss: 0.00006427
    Epoch 329: train loss: 0.00005930, validation loss: 0.00008200
    Epoch 330: train loss: 0.00005995, validation loss: 0.00006033
    Epoch 331: train loss: 0.00006182, validation loss: 0.00006558
    Epoch 332: train loss: 0.00005970, validation loss: 0.00008166
    Epoch 333: train loss: 0.00006210, validation loss: 0.00007263
    Epoch 334: train loss: 0.00005809, validation loss: 0.00007149
    Epoch 335: train loss: 0.00006208, validation loss: 0.00005538
    Epoch 336: train loss: 0.00005979, validation loss: 0.00006755
    Epoch 337: train loss: 0.00006166, validation loss: 0.00005789
    Epoch 338: train loss: 0.00005783, validation loss: 0.00006792
    Epoch 339: train loss: 0.00005920, validation loss: 0.00009126
    Epoch 340: train loss: 0.00006086, validation loss: 0.00006238
    Epoch 341: train loss: 0.00005934, validation loss: 0.00006087
    Epoch 342: train loss: 0.00006015, validation loss: 0.00005936
    Epoch 343: train loss: 0.00006163, validation loss: 0.00006678
    Epoch 344: train loss: 0.00005785, validation loss: 0.00007088
    Epoch 345: train loss: 0.00006009, validation loss: 0.00006072
    Epoch 346: train loss: 0.00005790, validation loss: 0.00007035
    Epoch 347: train loss: 0.00005913, validation loss: 0.00005674
    Epoch 348: train loss: 0.00005925, validation loss: 0.00005754
    Epoch 349: train loss: 0.00005781, validation loss: 0.00005585
    Epoch 350: train loss: 0.00006159, validation loss: 0.00005798
    Epoch 351: train loss: 0.00006081, validation loss: 0.00006660
    Epoch 352: train loss: 0.00006098, validation loss: 0.00005763
    Epoch 353: train loss: 0.00006359, validation loss: 0.00005675
    Epoch 354: train loss: 0.00005379, validation loss: 0.00005865
    Epoch 355: train loss: 0.00005854, validation loss: 0.00005921
    Epoch 356: train loss: 0.00005951, validation loss: 0.00006382
    Epoch 357: train loss: 0.00005963, validation loss: 0.00007154
    Epoch 358: train loss: 0.00005826, validation loss: 0.00008945
    Epoch 359: train loss: 0.00005663, validation loss: 0.00005428
    Epoch 360: train loss: 0.00005908, validation loss: 0.00005757
    Epoch 361: train loss: 0.00005770, validation loss: 0.00005347
    Epoch 362: train loss: 0.00005728, validation loss: 0.00005789
    Epoch 363: train loss: 0.00006001, validation loss: 0.00006603
    Epoch 364: train loss: 0.00005678, validation loss: 0.00005970
    Epoch 365: train loss: 0.00005830, validation loss: 0.00005694
    Epoch 366: train loss: 0.00006127, validation loss: 0.00009064
    Epoch 367: train loss: 0.00005462, validation loss: 0.00008087
    Epoch 368: train loss: 0.00005710, validation loss: 0.00007372
    Epoch 369: train loss: 0.00006040, validation loss: 0.00006238
    Epoch 370: train loss: 0.00005461, validation loss: 0.00005893
    Epoch 371: train loss: 0.00005926, validation loss: 0.00006033
    Epoch 372: train loss: 0.00005589, validation loss: 0.00005803
    Epoch 373: train loss: 0.00006113, validation loss: 0.00005931
    Epoch 374: train loss: 0.00005752, validation loss: 0.00006023
    Epoch 375: train loss: 0.00005491, validation loss: 0.00006436
    Epoch 376: train loss: 0.00005678, validation loss: 0.00005862
    Epoch 377: train loss: 0.00005761, validation loss: 0.00006065
    Epoch 378: train loss: 0.00005713, validation loss: 0.00006463
    Epoch 379: train loss: 0.00005621, validation loss: 0.00007610
    Epoch 380: train loss: 0.00005875, validation loss: 0.00006151
    Epoch 381: train loss: 0.00005855, validation loss: 0.00005386
    Epoch 382: train loss: 0.00005956, validation loss: 0.00005603
    Epoch 383: train loss: 0.00005312, validation loss: 0.00005215
    Epoch 384: train loss: 0.00005632, validation loss: 0.00005459
    Epoch 385: train loss: 0.00005723, validation loss: 0.00005815
    Epoch 386: train loss: 0.00005588, validation loss: 0.00007636
    Epoch 387: train loss: 0.00005846, validation loss: 0.00005829
    Epoch 388: train loss: 0.00005529, validation loss: 0.00006401
    Epoch 389: train loss: 0.00005892, validation loss: 0.00006015
    Epoch 390: train loss: 0.00005370, validation loss: 0.00006119
    Epoch 391: train loss: 0.00005703, validation loss: 0.00006107
    Epoch 392: train loss: 0.00005682, validation loss: 0.00005693
    Epoch 393: train loss: 0.00005375, validation loss: 0.00005170
    Epoch 394: train loss: 0.00005542, validation loss: 0.00005993
    Epoch 395: train loss: 0.00005696, validation loss: 0.00005270
    Epoch 396: train loss: 0.00005677, validation loss: 0.00005856
    Epoch 397: train loss: 0.00005648, validation loss: 0.00006086
    Epoch 398: train loss: 0.00005602, validation loss: 0.00005412
    Epoch 399: train loss: 0.00005388, validation loss: 0.00007282
    Epoch 400: train loss: 0.00005741, validation loss: 0.00005851
    Epoch 401: train loss: 0.00005433, validation loss: 0.00005426
    Epoch 402: train loss: 0.00005514, validation loss: 0.00007449
    Epoch 403: train loss: 0.00005708, validation loss: 0.00005441
    Epoch 404: train loss: 0.00005605, validation loss: 0.00005172
    Epoch 405: train loss: 0.00005462, validation loss: 0.00005738
    Epoch 406: train loss: 0.00005696, validation loss: 0.00009459
    Epoch 407: train loss: 0.00005267, validation loss: 0.00006220
    Epoch 408: train loss: 0.00005679, validation loss: 0.00006109
    Epoch 409: train loss: 0.00005455, validation loss: 0.00006360
    Epoch 410: train loss: 0.00005370, validation loss: 0.00007543
    Epoch 411: train loss: 0.00005990, validation loss: 0.00005131
    Epoch 412: train loss: 0.00005319, validation loss: 0.00005521
    Epoch 413: train loss: 0.00005797, validation loss: 0.00005482
    Epoch 414: train loss: 0.00005241, validation loss: 0.00005494
    Epoch 415: train loss: 0.00005554, validation loss: 0.00005634
    Epoch 416: train loss: 0.00005270, validation loss: 0.00005138
    Epoch 417: train loss: 0.00005650, validation loss: 0.00005590
    Epoch 418: train loss: 0.00005242, validation loss: 0.00005418
    Epoch 419: train loss: 0.00005686, validation loss: 0.00006355
    Epoch 420: train loss: 0.00005252, validation loss: 0.00005776
    Epoch 421: train loss: 0.00005553, validation loss: 0.00007072
    Epoch 422: train loss: 0.00005622, validation loss: 0.00005530
    Epoch 423: train loss: 0.00005513, validation loss: 0.00006311
    Epoch 424: train loss: 0.00005373, validation loss: 0.00006040
    Epoch 425: train loss: 0.00005527, validation loss: 0.00004993
    Epoch 426: train loss: 0.00005473, validation loss: 0.00009682
    Epoch 427: train loss: 0.00005598, validation loss: 0.00005652
    Epoch 428: train loss: 0.00005647, validation loss: 0.00005035
    Epoch 429: train loss: 0.00005219, validation loss: 0.00006149
    Epoch 430: train loss: 0.00005203, validation loss: 0.00005497
    Epoch 431: train loss: 0.00005555, validation loss: 0.00005579
    Epoch 432: train loss: 0.00005479, validation loss: 0.00005520
    Epoch 433: train loss: 0.00005387, validation loss: 0.00006519
    Epoch 434: train loss: 0.00005500, validation loss: 0.00005729
    Epoch 435: train loss: 0.00005421, validation loss: 0.00008082
    Epoch 436: train loss: 0.00005103, validation loss: 0.00007005
    Epoch 437: train loss: 0.00005473, validation loss: 0.00005617
    Epoch 438: train loss: 0.00005583, validation loss: 0.00011018
    Epoch 439: train loss: 0.00005302, validation loss: 0.00005055
    Epoch 440: train loss: 0.00005361, validation loss: 0.00005613
    Epoch 441: train loss: 0.00005970, validation loss: 0.00005385
    Epoch 442: train loss: 0.00005043, validation loss: 0.00006868
    Epoch 443: train loss: 0.00005404, validation loss: 0.00007294
    Epoch 444: train loss: 0.00005500, validation loss: 0.00004950
    Epoch 445: train loss: 0.00005123, validation loss: 0.00005034
    Epoch 446: train loss: 0.00005286, validation loss: 0.00005913
    Epoch 447: train loss: 0.00005428, validation loss: 0.00005976
    Epoch 448: train loss: 0.00005288, validation loss: 0.00007149
    Epoch 449: train loss: 0.00005391, validation loss: 0.00005094
    Epoch 450: train loss: 0.00005345, validation loss: 0.00008920
    Epoch 451: train loss: 0.00005326, validation loss: 0.00008170
    Epoch 452: train loss: 0.00005034, validation loss: 0.00007440
    Epoch 453: train loss: 0.00005383, validation loss: 0.00005073
    Epoch 454: train loss: 0.00004770, validation loss: 0.00006633
    Epoch 455: train loss: 0.00004906, validation loss: 0.00005097
    Epoch 456: train loss: 0.00004978, validation loss: 0.00005103
    Epoch 457: train loss: 0.00004983, validation loss: 0.00005776
    Epoch 458: train loss: 0.00004880, validation loss: 0.00005366
    Epoch 459: train loss: 0.00005121, validation loss: 0.00004951
    Epoch 460: train loss: 0.00004797, validation loss: 0.00005497
    Epoch 461: train loss: 0.00004987, validation loss: 0.00005716
    Epoch 462: train loss: 0.00005170, validation loss: 0.00005642
    Epoch 463: train loss: 0.00004833, validation loss: 0.00005023
    Epoch 464: train loss: 0.00005100, validation loss: 0.00004843
    Epoch 465: train loss: 0.00004760, validation loss: 0.00004853
    Epoch 466: train loss: 0.00004958, validation loss: 0.00004684
    Epoch 467: train loss: 0.00004978, validation loss: 0.00005770
    Epoch 468: train loss: 0.00004957, validation loss: 0.00005328
    Epoch 469: train loss: 0.00004828, validation loss: 0.00005073
    Epoch 470: train loss: 0.00004958, validation loss: 0.00004787
    Epoch 471: train loss: 0.00004851, validation loss: 0.00005324
    Epoch 472: train loss: 0.00005225, validation loss: 0.00004883
    Epoch 473: train loss: 0.00004748, validation loss: 0.00004854
    Epoch 474: train loss: 0.00004787, validation loss: 0.00005506
    Epoch 475: train loss: 0.00004815, validation loss: 0.00004892
    Epoch 476: train loss: 0.00005019, validation loss: 0.00005634
    Epoch 477: train loss: 0.00004919, validation loss: 0.00004979
    Epoch 478: train loss: 0.00005453, validation loss: 0.00004978
    Epoch 479: train loss: 0.00004470, validation loss: 0.00005008
    Epoch 480: train loss: 0.00004836, validation loss: 0.00004949
    Epoch 481: train loss: 0.00004856, validation loss: 0.00004901
    Epoch 482: train loss: 0.00004934, validation loss: 0.00004965
    Epoch 483: train loss: 0.00004682, validation loss: 0.00004773
    Epoch 484: train loss: 0.00004921, validation loss: 0.00004867
    Epoch 485: train loss: 0.00004868, validation loss: 0.00004915
    Epoch 486: train loss: 0.00004750, validation loss: 0.00005710
    Epoch 487: train loss: 0.00004991, validation loss: 0.00005168
    Epoch 488: train loss: 0.00004759, validation loss: 0.00005039
    Epoch 489: train loss: 0.00004918, validation loss: 0.00004848
    Epoch 490: train loss: 0.00004927, validation loss: 0.00004970
    Epoch 491: train loss: 0.00004732, validation loss: 0.00005370
    Epoch 492: train loss: 0.00005182, validation loss: 0.00010300
    Epoch 493: train loss: 0.00004733, validation loss: 0.00004850
    Epoch 494: train loss: 0.00004691, validation loss: 0.00004782
    Epoch 495: train loss: 0.00004937, validation loss: 0.00004878
    Epoch 496: train loss: 0.00004785, validation loss: 0.00004802
    Epoch 497: train loss: 0.00004777, validation loss: 0.00004799
    Epoch 498: train loss: 0.00004710, validation loss: 0.00004870
    Epoch 499: train loss: 0.00004707, validation loss: 0.00004547
    Epoch 500: train loss: 0.00004846, validation loss: 0.00005256
    Finished Training

```python
print(model_koop_dnn.net.loss_scaler_x, model_koop_dnn.net.loss_scaler_z)
print(model_koop_dnn.A)
print(model_koop_dnn.C)
```

    tensor([0.0111, 0.0094, 0.0271, 0.0226, 0.0197, 0.0299]) 0.021373708962906513
    [[ 1.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  1.0000000e+00  0.0000000e+00  0.0000000e+00
       1.1081556e-02  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  1.0000000e+00  0.0000000e+00
       0.0000000e+00  9.7555937e-03  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  1.0000000e+00
       0.0000000e+00  0.0000000e+00  2.6306523e-02  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [-6.6681497e-04  0.0000000e+00  0.0000000e+00 -5.1866095e-03
       1.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       1.8332787e-02 -9.0039354e-03 -3.6473172e-03  1.8041149e-02
       1.3419050e-02 -3.2375276e-02  0.0000000e+00  2.9435270e-03
       0.0000000e+00]
     [-2.0867020e-03  0.0000000e+00  0.0000000e+00  1.6804270e-03
       0.0000000e+00  1.0000237e+00 -1.2601082e-05  0.0000000e+00
       8.8700578e-03 -9.9829072e-03  5.3750058e-03  3.2783080e-02
      -1.1894170e-02  2.1156453e-02  0.0000000e+00  2.7434222e-04
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  1.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  3.0274916e-04
       0.0000000e+00  0.0000000e+00  0.0000000e+00  1.0000000e+00
       9.3666660e-03  0.0000000e+00  0.0000000e+00 -1.7628893e-03
       0.0000000e+00  0.0000000e+00  0.0000000e+00 -1.1416033e-02
       1.6665140e-02]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       1.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00 -1.4796966e-02  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  1.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  6.4123021e-03  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  1.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       3.2557022e-05  0.0000000e+00  0.0000000e+00 -1.2240004e-02
       0.0000000e+00  0.0000000e+00  0.0000000e+00  1.0000000e+00
       0.0000000e+00  0.0000000e+00 -4.3127295e-02  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00 -1.7055733e-03  2.0373490e-02
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       1.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  5.0006160e-03 -3.4201939e-02
       0.0000000e+00  0.0000000e+00  0.0000000e+00  3.2124397e-05
       0.0000000e+00  1.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       7.2754063e-03  0.0000000e+00  0.0000000e+00 -1.1449737e-03
       0.0000000e+00  0.0000000e+00  1.0000000e+00 -1.0738813e-02
       2.0538816e-02]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00 -5.2747841e-04  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00 -4.6560308e-05  0.0000000e+00  1.0000000e+00
       0.0000000e+00]
     [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00
       1.0000000e+00]]
    tensor([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

```python
train_loss = [l[0] for l in model_koop_dnn.train_loss_hist]
train_pred_loss = [l[1] for l in model_koop_dnn.train_loss_hist]
train_lin_loss = [l[2] for l in model_koop_dnn.train_loss_hist]
val_loss = [l[0] for l in model_koop_dnn.val_loss_hist]
val_pred_loss = [l[1] for l in model_koop_dnn.val_loss_hist]
val_lin_loss = [l[2] for l in model_koop_dnn.val_loss_hist]
epochs = np.arange(0, net_params['epochs'])

plt.figure(figsize=(15,8))
plt.plot(epochs, train_loss, color='tab:orange', label='Training loss')
plt.plot(epochs, train_pred_loss, '--', color='tab:orange', label='Training prediction loss')
plt.plot(epochs, train_lin_loss, ':', color='tab:orange', label='Training linear loss')
plt.plot(epochs, val_loss, color='tab:blue', label='Validation loss')
plt.plot(epochs, val_pred_loss, '--', color='tab:blue', label='Validation prediction loss')
plt.plot(epochs, val_lin_loss, ':', color='tab:blue', label='Validation linear loss')
plt.legend()
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.yscale('log')
plt.show()
```

### Evaluate open loop prediction performance

We first evaluate the open loop prediction performance of the proposed method.
This is done by generating a new data set in the same way as the training set, predicting the evolution of the system
with the control sequence of each trajectory executed in the data set with each of the models, and finally comparing
the mean and standard deviation of the error between the true and predicted evolution over the trajectories. The
experimental results support what is to be expected from the theory as the error in the $y$ and $z$ terms are
significantly lower for the bEDMD method than both DMD and EDMD. The reason for this
improvement is that the bEDMD method can capture the nonlinearities present in the actuation matrix of the
$(y,z)$-dynamics.

```python
# Prediction performance evaluation parameters:
folder_plots = 'figures/'                                          # Path to save plots
n_traj_ol = 50                                                     # Number of trajectories to execute, open loop
t_eval = t_train
```

```python
from koopman_core.util import evaluate_ol_pred
from tabulate import tabulate

xs_test = np.empty((n_traj_ol, t_eval.shape[0], n))
us_test = np.empty((n_traj_ol, t_eval.shape[0]-1, m))

for ii in range(n_traj_ol):
    x0 = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
    set_pt_dc = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
    mpc_trajgen = MPCController(nominal_sys, n_pred_dc, dt, umin, umax, xmin, xmax, QN_trajgen, R_trajgen,
                                QN_trajgen, set_pt_dc)
    mpc_trajgen.eval(x0, 0)
    xd = mpc_trajgen.parse_result().T

    while xd[0,0] is None:
        x0 = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
        set_pt_dc = np.asarray([rand.uniform(l, u) for l, u in zip(-x0_max, x0_max)])
        mpc_trajgen = MPCController(nominal_sys, n_pred_dc, dt, umin, umax, xmin, xmax, QN_trajgen, R_trajgen,
                                    QN_trajgen, set_pt_dc)
        mpc_trajgen.eval(x0, 0)
        xd = mpc_trajgen.parse_result().T

    output = QuadrotorPdOutput(system, xd, t_eval, n, m)
    pd_controller = PDController(output, K_dc_p, K_dc_d)
    perturbed_pd_controller = PerturbedController(system, pd_controller, noise_var, const_offset=hover_thrust)

    xs_test[ii,:,:], us_test[ii,:,:] = system.simulate(x0, perturbed_pd_controller, t_eval)

#    mdl_lst = [sys_dmd, sys_edmd, sys_bedmd, sys_koop_dnn]
mdl_lst = [sys_bedmd, sys_koop_dnn]
#mdl_names = ['DMD', 'EDMD', 'bEDMD', 'Koop DNN']
mdl_names = ['bEDMD', 'Koop DNN']
error, mse, std = [], [], []

for sys in mdl_lst:
    err_tmp, mse_tmp, std_tmp = evaluate_ol_pred(sys, xs_test, t_eval, us=us_test-hover_thrust)
    error.append(err_tmp)
    mse.append(mse_tmp)
    std.append(std_tmp)

print('\nOpen loop performance statistics:')
table_data = []
for name, mse_mdl, std_mdl in zip(mdl_names, mse, std):
    table_data.append([name, "{:.5f}".format(mse_mdl), "{:.5f}".format(std_mdl)])

print(tabulate(table_data,
               headers=['Mean squared error', 'Standard deviation']))
```

    Open loop performance statistics:
                Mean squared error    Standard deviation
    --------  --------------------  --------------------
    bEDMD                  0.00574               0.07568
    Koop DNN               0.0041                0.063

```python
import matplotlib.pyplot as plt
import matplotlib

figwidth = 16
lw = 2
fs = 14
y_lim_gain = 1.2
row = 2
col = n/row

#Plot open loop results:
plt.figure(figsize=(figwidth,4))
axs = [plt.subplot(row,col,jj+1) for jj in range(n)]

for ii, err in enumerate(error):
    err_mean = np.mean(err, axis=0)
    err_std = np.std(err, axis=0)

    for jj in range(n):
        axs[jj].plot(t_eval[1:], err_mean[:,jj], label=mdl_names[ii])
        axs[jj].fill_between(t_eval[1:], err_mean[:,jj]-err_std[:,jj], err_mean[:,jj]+err_std[:,jj], alpha=0.1)

for jj in range(n):
    axs[jj].set_xlabel('Time (sec)', fontsize=fs)
    axs[jj].set_ylabel('$x_'+ str(jj+1) + '$', fontsize=fs)

plt.legend(frameon=False, fontsize=fs)
stitle=plt.suptitle('Open loop prediction performance of learned models', fontsize=fs+2)
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
plt.savefig(folder_plots + 'koop_sys_prediction.pdf', format='pdf', dpi=2400, bbox_extra_artists=(stitle,), bbox_inches="tight")
plt.show()
```

```python
ii = 1
x0 = xs_test[ii, 0, :].reshape(1,-1)
ctrl = OpenLoopController(sys, standardizer_u_kdnn.transform(us_test[ii, :, :]-hover_thrust), t_train[:-1])
z0 = sys.basis(np.atleast_2d(x0)).squeeze()
zs_tmp, _ = sys.simulate(z0, ctrl, t_train)
xs_pred = standardizer_x_kdnn.inverse_transform(np.dot(sys.C, zs_tmp.T).T)

plt.figure(figsize=(15,6))
for ss in range(3):
    plt.subplot(1,3,ss+1)
    plt.plot(t_train, xs_test[ii, :, ss], '--', color='tab:orange', label='True')
    plt.plot(t_train, xs_pred[:, ss], '--', color='tab:blue', label='Learned')
    plt.plot(t_train, xs_test[ii, :, ss+3], color='tab:orange', label='True')
    plt.plot(t_train, xs_pred[:, ss+3], color='tab:blue', label='Learned')
plt.legend()
plt.show()
```

# Closed loop performance evaluation parameters:
traj_length=250
t_eval = dt * np.arange(traj_length+1)                       # Simulation time points, closed loop
Q_mpc = sc.sparse.diags([0,0,0,0,0,0])                       # State penalty matrix, trajectory generation
#QN_mpc = sc.sparse.diags([1e5,1e5,1e5,1e5,1e5,1e5])         # Final state penalty matrix, trajectory generation
QN_mpc = Q_mpc
R_mpc = sc.sparse.eye(m)                                     # Actuation penalty matrix, trajectory generation

ctrl_offset = np.array([hover_thrust, hover_thrust])

# Design trajectory:
x0_cl = np.array([0., 0., 0., 0., 0., 0.])                   # Initial value, closed loop trajectory
set_pt_cl = np.array([1.5, 0.5, 0., 0., 0., 0.])              # Desired final value, closed loop trajectory
xmax = np.array([2, 2, np.pi/3, 1.,1.,1.])                          # State constraints, trajectory generation
xmin = -xmax
term_constraint=True

# Define initial solution for SQP algorithm:
x_init = np.linspace(x0_cl, set_pt_cl, int(traj_length)+1)
u_init = hover_thrust*np.ones((m,traj_length)).T

#### Design controllers for learned DMD, EDMD, and bEDMD models

```python
from koopman_core.controllers import MPCController, NonlinearMPCController, BilinearMPCController

# Define DMD-based controller:
controller_dmd = MPCController(sys_dmd, traj_length, dt, umin, umax, xmin, xmax, Q_mpc, R_mpc, QN_mpc, set_pt_cl, terminal_constraint=term_constraint, const_offset=ctrl_offset)

# Define EDMD-based controller:
controller_edmd = MPCController(sys_edmd, traj_length, dt, umin, umax, xmin, xmax, Q_mpc, R_mpc, QN_mpc, set_pt_cl, terminal_constraint=term_constraint, const_offset=ctrl_offset)

# Define bEDMD-based controller:
controller_bedmd = BilinearMPCController(sys_bedmd, traj_length, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc, R_mpc, QN_mpc, set_pt_cl, terminal_constraint=term_constraint)
z0_cl = sys_bedmd.basis(x0_cl.reshape((1,-1))).squeeze()
z_init = sys_bedmd.basis(x_init)
controller_bedmd.construct_controller(z_init, u_init)

# Define KDNN-based controller:
controller_kdnn = BilinearMPCController(sys_koop_dnn, traj_length, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc, R_mpc, QN_mpc, set_pt_cl,
                                        terminal_constraint=term_constraint, standardizer_x=standardizer_x_kdnn, standardizer_u=standardizer_u_kdnn)
z0_cl_kdnn = sys_koop_dnn.basis(x0_cl.reshape((1,-1))).squeeze()
z_init_kdnn = sys_koop_dnn.basis(x_init)
u_init_kdnn = standardizer_u_kdnn.transform(u_init)
controller_kdnn.construct_controller(z_init_kdnn, u_init_kdnn)
```

#### Design controller using full knowledge of nonlinear controller

```python
system_d = PlanarQuadrotorForceInputDiscrete(mass, inertia, prop_arm, g=gravity, dt=dt)
controller_nmpc = NonlinearMPCController(system_d, traj_length, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc, R_mpc, QN_mpc, set_pt_cl, terminal_constraint=term_constraint)
controller_nmpc.construct_controller(x_init, u_init+hover_thrust)
```

#### Design trajectories with the contructed MPCs

```python
max_iter = 50

controller_dmd.eval(x0_cl, 0)
xr_dmd = controller_dmd.parse_result()
ur_dmd = controller_dmd.get_control_prediction() + hover_thrust

controller_edmd.eval(x0_cl, 0)
xr_edmd = sys_edmd.C@controller_edmd.parse_result()
ur_edmd = controller_edmd.get_control_prediction() + hover_thrust

controller_bedmd.solve_to_convergence(z0_cl, 0., z_init, u_init, max_iter=max_iter)
xr_bedmd = sys_bedmd.C@controller_bedmd.get_state_prediction().T
ur_bedmd = controller_bedmd.get_control_prediction().T

controller_kdnn.solve_to_convergence(z0_cl_kdnn, 0., z_init_kdnn, u_init_kdnn, max_iter=max_iter)
xr_kdnn = standardizer_x_kdnn.inverse_transform((sys_koop_dnn.C@controller_kdnn.get_state_prediction().T).T).T
ur_kdnn = controller_kdnn.get_control_prediction().T
```

```python
controller_nmpc.solve_to_convergence(x0_cl, 0., x_init, u_init + ctrl_offset.reshape(1,-1), max_iter=max_iter)
xr_nmpc = controller_nmpc.get_state_prediction().T
ur_nmpc = controller_nmpc.get_control_prediction().T
```

#### Simulate designed trajectories open loop

```python
ol_controller_dmd = OpenLoopController(system, ur_dmd.T, t_eval[:-1])
xs_dmd, us_dmd = system.simulate(x0_cl, ol_controller_dmd, t_eval)
xs_dmd, us_dmd = xs_dmd.T, us_dmd.T

ol_controller_edmd = OpenLoopController(system, ur_edmd.T, t_eval[:-1])
xs_edmd, us_edmd = system.simulate(x0_cl, ol_controller_edmd, t_eval)
xs_edmd, us_edmd = xs_edmd.T, us_edmd.T

ol_controller_bedmd = OpenLoopController(system, ur_bedmd.T, t_eval[:-1])
xs_bedmd, us_bedmd = system.simulate(x0_cl, ol_controller_bedmd, t_eval)
xs_bedmd, us_bedmd = xs_bedmd.T, us_bedmd.T

ol_controller_kdnn = OpenLoopController(system, ur_kdnn.T, t_eval[:-1])
xs_kdnn, us_kdnn = system.simulate(x0_cl, ol_controller_kdnn, t_eval)
xs_kdnn, us_kdnn = xs_kdnn.T, us_kdnn.T

ol_controller_nmpc = OpenLoopController(system, ur_nmpc.T, t_eval[:-1])
xs_nmpc, us_nmpc = system.simulate(x0_cl, ol_controller_nmpc, t_eval)
xs_nmpc, us_nmpc = xs_nmpc.T, us_nmpc.T
```

##### Compare performance

```python
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator

plot_inds = [0, 1, 2, 3, 4, 5, 0, 1]
subplot_inds = [1, 2, 3, 5, 6, 7, 4, 8]
labels = ['$y$ (m)', '$z$ (m)', '$\\theta$ (rad)', '$\\dot{y}$ (m/s)','$\\dot{z}$ (m/s)', '$\\dot{\\theta}$', '$T_1$ (N)','$T_2$ (N)']
titles = ['y-coordinates', 'z-coordinates', '$\\theta$-coordinates', 'Control inputs']
colors = ['tab:blue', 'tab:orange', 'tab:brown', 'tab:green', 'tab:cyan']

plt.figure(figsize=(12,4))
#plt.suptitle('Trajectory designed with model predictive controllers\nsolid lines - designed trajectory | dashed lines - open loop simulated trajectory | black dotted lines - state/actuation bounds')
for ii in range(8):
    ind = plot_inds[ii]
    if ii < 6:
        ax = plt.subplot(2,4,subplot_inds[ii])
        plt.plot(t_eval, xr_dmd[ind,:], colors[0], label='DMD MPC')
        plt.plot(t_eval, xr_edmd[ind, :], colors[1], label='EDMD MPC')
        plt.plot(t_eval, xr_bedmd[ind, :], colors[2], label='K-MPC')
        plt.plot(t_eval, xr_kdnn[ind, :], colors[3], label='KDNN-MPC')
        plt.plot(t_eval, xr_nmpc[ind,:], colors[4], label='NMPC')

        plt.plot(t_eval, xs_dmd[ind,:], '--', color=colors[0], linewidth=1)
        plt.plot(t_eval, xs_edmd[ind, :], '--', color=colors[1], linewidth=1)
        plt.plot(t_eval, xs_bedmd[ind, :], '--', color=colors[2], linewidth=1)
        plt.plot(t_eval, xs_kdnn[ind, :], '--',  colors[3], linewidth=1)
        plt.plot(t_eval, xs_nmpc[ind,:], '--', color=colors[4], linewidth=1)

        plt.scatter(t_eval[0], x0_cl[ind], color='g')
        plt.scatter(t_eval[-1], set_pt_cl[ind], color='r')
        plt.ylabel(labels[ind])
        ax.yaxis.set_major_locator(MaxNLocator(integer=True))
        if ii >= 3:
            plt.plot([0, t_eval[-1]], [xmax[ind], xmax[ind]], ':k')
            plt.plot([0, t_eval[-1]], [xmin[ind], xmin[ind]], ':k')
            #plt.ylim(xmin[ind]-0.1,xmax[ind]+0.1)
        if subplot_inds[ii]==1:
            plt.legend(loc='upper left', frameon=False)
    elif ii < 8:
        ax = plt.subplot(2,4,subplot_inds[ii])
        plt.plot(t_eval[:-1],ur_dmd[ind,:], color=colors[0], label='DMD MPC')
        plt.plot(t_eval[:-1], ur_edmd[ind, :], color=colors[1], label='EDMD MPC')
        plt.plot(t_eval[:-1], ur_bedmd[ind, :], color=colors[2], label='K-NMPC')
        plt.plot(t_eval[:-1], ur_kdnn[ind, :], color=colors[3], label='KDNN-NMPC')
        plt.plot(t_eval[:-1],ur_nmpc[ind,:], color=colors[4], label='NMPC')
        plt.plot([0, t_eval[-1]], [umax[ind]+hover_thrust, umax[ind]+hover_thrust], ':k')
        plt.plot([0, t_eval[-1]], [umin[ind]+hover_thrust, umin[ind]+hover_thrust], ':k')
        plt.ylabel(labels[ii])
        ax.yaxis.set_major_locator(MaxNLocator(integer=True))

    if subplot_inds[ii] > 4:
        plt.xlabel('Time (sec)')
    else:
        plt.title(titles[subplot_inds[ii]-1])

if save_figures:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42
    plt.tight_layout()
    #plt.savefig(dropbox_folder + 'planar_quad_trajectory.pdf', format='pdf', dpi=2400)
plt.show()

cost_ref_dmd = (xr_dmd[:,-1]-set_pt_cl).T@QN_mpc@(xr_dmd[:,-1]-set_pt_cl) + np.sum(np.diag(ur_dmd.T@R_mpc@ur_dmd))
cost_ref_edmd = (xr_edmd[:,-1]-set_pt_cl).T@QN_mpc@(xr_edmd[:,-1]-set_pt_cl) + np.sum(np.diag(ur_edmd.T@R_mpc@ur_edmd))
cost_ref_bedmd = (xr_bedmd[:,-1]-set_pt_cl).T@QN_mpc@(xr_bedmd[:,-1]-set_pt_cl) + np.sum(np.diag(ur_bedmd.T@R_mpc@ur_bedmd))
cost_ref_kdnn = (xr_kdnn[:,-1]-set_pt_cl).T@QN_mpc@(xr_kdnn[:,-1]-set_pt_cl) + np.sum(np.diag(ur_kdnn.T@R_mpc@ur_kdnn))
cost_ref_nmpc = (xr_nmpc[:,-1]-set_pt_cl).T@QN_mpc@(xr_nmpc[:,-1]-set_pt_cl) + np.sum(np.diag(ur_nmpc.T@R_mpc@ur_nmpc))

dist_ol_dmd = np.linalg.norm(xs_dmd[:,-1] - set_pt_cl)
dist_ol_edmd = np.linalg.norm(xs_edmd[:,-1] - set_pt_cl)
dist_ol_bedmd = np.linalg.norm(xs_bedmd[:,-1] - set_pt_cl)
dist_ol_kdnn = np.linalg.norm(xs_kdnn[:,-1] - set_pt_cl)
dist_ol_nmpc = np.linalg.norm(xs_nmpc[:,-1] - set_pt_cl)

print('Solution statistics:\n')
print(tabulate([['DMD MPC', "{:.4f}".format(cost_ref_dmd/cost_ref_nmpc), "{:.4f}".format(dist_ol_dmd), '-','-',sum(controller_dmd.comp_time)],
                ['EDMD MPC', "{:.4f}".format(cost_ref_edmd/cost_ref_nmpc), "{:.4f}".format(dist_ol_edmd),'-','-',sum(controller_edmd.comp_time)],
                ['bEDMD MPC', "{:.4f}".format(cost_ref_bedmd/cost_ref_nmpc), "{:.4f}".format(dist_ol_bedmd), len(controller_bedmd.x_iter), "{:.4f}".format(np.mean(controller_bedmd.comp_time)), sum(controller_bedmd.comp_time)],
                ['KDNN MPC', "{:.4f}".format(cost_ref_kdnn/cost_ref_nmpc), "{:.4f}".format(dist_ol_kdnn), len(controller_kdnn.x_iter), "{:.4f}".format(np.mean(controller_kdnn.comp_time)), sum(controller_kdnn.comp_time)],
                ['NMPC (benchmark)', 1, "{:.4f}".format(dist_ol_nmpc), len(controller_nmpc.x_iter), "{:.4f}".format(np.mean(controller_nmpc.comp_time)), sum(controller_nmpc.comp_time)]],
               headers=['Normalized cost,\ndesigned trajectory', 'Realized terminal,\nerror', '# of SQP\niterations','Mean comp. time\nper iteration (secs)', 'Total comp.\ntime (secs)']))
```

#### Study evolution of the solution after each iteration of the SQP-algorithm

```python
n_iter = min(len(controller_nmpc.x_iter),len(controller_bedmd.x_iter))

# Calculate cost after each iteration:
iter_cost_bedmd, iter_cost_nmpc = [], []

ol_controller_init = OpenLoopController(system, u_init, t_eval[:-1])
xs_init, _ = system.simulate(x0_cl, ol_controller_init, t_eval)
xs_init, us_init = xs_init.T, u_init.T
init_cost = (xs_init[:,-1]-set_pt_cl).T@QN_mpc@(xs_init[:,-1]-set_pt_cl) + np.sum(np.diag(us_init.T@R_mpc@us_init))
iter_cost_bedmd = [init_cost]
iter_cost_kdnn = [init_cost]
iter_cost_nmpc = [init_cost]
iter_norm_dist_bedmd = [np.linalg.norm(xs_init[:,-1]-set_pt_cl)]
iter_norm_dist_kdnn = [np.linalg.norm(xs_init[:,-1]-set_pt_cl)]
iter_norm_dist_nmpc = [np.linalg.norm(xs_init[:,-1]-set_pt_cl)]

for ii in range(len(controller_bedmd.x_iter)):
    ur_bedmd_iter = controller_bedmd.u_iter[ii].T
    ol_controller_bedmd_iter = OpenLoopController(system, ur_bedmd_iter, t_eval[:-1])
    xs_bedmd_iter, _ = system.simulate(x0_cl, ol_controller_bedmd_iter, t_eval)
    xs_bedmd_iter, us_bedmd_iter = xs_bedmd_iter.T, ur_bedmd_iter.T
    iter_cost_bedmd.append((xs_bedmd_iter[:,-1]-set_pt_cl).T@QN_mpc@(xs_bedmd_iter[:,-1]-set_pt_cl) + np.sum(np.diag(us_bedmd_iter.T@R_mpc@us_bedmd_iter)))
    iter_norm_dist_bedmd.append(np.linalg.norm(xs_bedmd_iter[:,-1]-set_pt_cl))

for ii in range(len(controller_kdnn.x_iter)):
    ur_kdnn_iter = standardizer_u_kdnn.inverse_transform(controller_kdnn.u_iter[ii].T)
    ol_controller_kdnn_iter = OpenLoopController(system, ur_kdnn_iter, t_eval[:-1])
    xs_kdnn_iter, _ = system.simulate(x0_cl, ol_controller_kdnn_iter, t_eval)
    xs_kdnn_iter, us_kdnn_iter = xs_kdnn_iter.T, ur_kdnn_iter.T
    iter_cost_kdnn.append((xs_kdnn_iter[:,-1]-set_pt_cl).T@QN_mpc@(xs_kdnn_iter[:,-1]-set_pt_cl) + np.sum(np.diag(us_kdnn_iter.T@R_mpc@us_kdnn_iter)))
    iter_norm_dist_kdnn.append(np.linalg.norm(xs_kdnn_iter[:,-1]-set_pt_cl))

for ii in range(len(controller_nmpc.x_iter)):
    ur_nmpc_iter = controller_nmpc.u_iter[ii].T
    ol_controller_nmpc_iter = OpenLoopController(system, ur_nmpc_iter, t_eval[:-1])
    xs_nmpc_iter, _ = system.simulate(x0_cl, ol_controller_nmpc_iter, t_eval)
    xs_nmpc_iter, us_nmpc_iter = xs_nmpc_iter.T, ur_nmpc_iter.T
    iter_cost_nmpc.append((xs_nmpc_iter[:,-1]-set_pt_cl).T@QN_mpc@(xs_nmpc_iter[:,-1]-set_pt_cl) + np.sum(np.diag(us_nmpc_iter.T@R_mpc@us_nmpc_iter)))
    iter_norm_dist_nmpc.append(np.linalg.norm(xs_nmpc_iter[:,-1]-set_pt_cl))
```

```python
plt.figure(figsize=(6,4))
#plt.suptitle('Control solution after each iteration of the SQP-algorithm for NMPC and K-NMPC')
plt.subplot(2,1,1)
plt.plot(np.arange(n_iter), iter_cost_bedmd[:n_iter]/iter_cost_nmpc[-1], color=colors[2], label='K-NMPC')
plt.plot(np.arange(n_iter), iter_cost_kdnn[:n_iter]/iter_cost_nmpc[-1], color=colors[3], label='KDNN-NMPC')
plt.plot(np.arange(n_iter), iter_cost_nmpc[:n_iter]/iter_cost_nmpc[-1], color=colors[4], label='NMPC')
plt.title('Control effort')
plt.ylabel('$||u||$')
plt.legend(loc='upper right', frameon=False)
plt.xlabel('SQP iteration')

plt.subplot(2,1,2)
plt.plot(np.arange(n_iter), iter_norm_dist_bedmd[:n_iter], color=colors[2], label=labels[2])
plt.plot(np.arange(n_iter), iter_norm_dist_kdnn[:n_iter], color=colors[3], label=labels[3])
plt.plot(np.arange(n_iter), iter_norm_dist_nmpc[:n_iter], color=colors[4], label=labels[4])
plt.ylim(0,5)
plt.title('Realized terminal distance from setpoint')
plt.ylabel('$||x_N - x_d||$')
plt.xlabel('SQP iteration')

if save_figures:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42
    plt.tight_layout()
    #plt.savefig(dropbox_folder + 'planar_quad_sqp_iterations.pdf', format='pdf', dpi=2400)

plt.show()

print('Solution statistics\n')
print(tabulate([['Nonlinear MPC', len(controller_nmpc.x_iter), np.mean(controller_nmpc.comp_time), np.std(controller_nmpc.comp_time), sum(controller_nmpc.comp_time)],
                ['Koopman bilinear MPC', len(controller_bedmd.x_iter), np.mean(controller_bedmd.comp_time), np.std(controller_bedmd.comp_time), sum(controller_bedmd.comp_time)],
                ['Koopman DNN bilinear MPC', len(controller_kdnn.x_iter), np.mean(controller_kdnn.comp_time), np.std(controller_kdnn.comp_time), sum(controller_kdnn.comp_time)]],
               headers=['Number of SQP\niterations','Mean comp. time per\niteration (secs)', 'Std comp. time per\niteration (secs)', 'Total comp.\ntime (secs)']))
```

# Evaluate performance of controllers for closed-loop control

#### Design finite horizon controllers

```python
from koopman_core.controllers import PerturbedController

Q_mpc_cl = 5e2*np.diag([1, 1, 1, 2e-1, 2e-1, 2e-1])
QN_mpc_cl = Q_mpc_cl
R_mpc_cl = np.eye(m)
traj_duration = 1.
N_cl = int(traj_duration/dt)
t_eval_cl=np.arange(300)*dt

# Solver settings closed loop:
polish_osqp = False
max_iter_osqp = 10
linsys_solver_osqp = 'qdldl'
warm_start_osqp = True

controller_dmd_cl = MPCController(sys_dmd, N_cl, dt, umin, umax, xmin, xmax, Q_mpc_cl, R_mpc_cl, QN_mpc_cl, set_pt_cl, add_slack=True)
controller_dmd_cl = PerturbedController(sys_dmd,controller_dmd_cl,0.,const_offset=hover_thrust, umin=umin, umax=umax)

controller_edmd_cl = MPCController(sys_edmd, N_cl, dt, umin, umax, xmin, xmax, Q_mpc_cl, R_mpc_cl, QN_mpc_cl, set_pt_cl, add_slack=True)
controller_edmd_cl = PerturbedController(sys_edmd,controller_edmd_cl,0.,const_offset=hover_thrust, umin=umin, umax=umax)

controller_bedmd_cl = BilinearMPCController(sys_bedmd, N_cl, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc_cl, R_mpc_cl, QN_mpc_cl, set_pt_cl, add_slack=True)
controller_bedmd_cl.construct_controller(controller_bedmd.cur_z[:N_cl+1,:], controller_bedmd.cur_u[:N_cl,:])
controller_bedmd_cl.solve_to_convergence(z0_cl, 0., controller_bedmd.cur_z[:N_cl+1,:], controller_bedmd.cur_u[:N_cl,:], max_iter=max_iter)
#controller_bedmd_cl = PerturbedController(sys_bedmd,controller_bedmd_cl,0.,const_offset=hover_thrust, umin=umin, umax=umax)
controller_bedmd_cl.update_solver_settings(polish=polish_osqp, max_iter=max_iter_osqp, linsys_solver=linsys_solver_osqp, warm_start=warm_start_osqp)

controller_kdnn_cl = BilinearMPCController(sys_koop_dnn, N_cl, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc_cl, R_mpc_cl, QN_mpc_cl, set_pt_cl,
                                        add_slack=True, standardizer_x=standardizer_x_kdnn, standardizer_u=standardizer_u_kdnn)
controller_kdnn_cl.construct_controller(controller_kdnn.cur_z[:N_cl+1,:], controller_kdnn.cur_u[:N_cl,:])
controller_kdnn_cl.solve_to_convergence(z0_cl_kdnn, 0., controller_kdnn.cur_z[:N_cl+1,:], controller_kdnn.cur_u[:N_cl,:], max_iter=max_iter)
controller_kdnn_cl.update_solver_settings(polish=polish_osqp, max_iter=max_iter_osqp, linsys_solver=linsys_solver_osqp, warm_start=warm_start_osqp)

controller_nmpc_cl = NonlinearMPCController(system_d, N_cl, dt, umin+hover_thrust, umax+hover_thrust, xmin, xmax, Q_mpc_cl, R_mpc_cl, QN_mpc_cl, set_pt_cl, add_slack=True)
controller_nmpc_cl.construct_controller(controller_nmpc.cur_z[:N_cl+1,:], controller_nmpc.cur_u[:N_cl,:])
controller_nmpc_cl.solve_to_convergence(x0_cl, 0., controller_nmpc.cur_z[:N_cl+1,:], controller_nmpc.cur_u[:N_cl,:], max_iter=max_iter)
controller_nmpc_cl.update_solver_settings(polish=polish_osqp, max_iter=max_iter_osqp, linsys_solver=linsys_solver_osqp, warm_start=warm_start_osqp)
```

#### Simulate designed trajectories closed-loop

```python
xs_dmd_cl, us_dmd_cl = system.simulate(x0_cl, controller_dmd_cl, t_eval_cl)
xs_dmd_cl, us_dmd_cl = xs_dmd_cl.T, us_dmd_cl.T

xs_edmd_cl, us_edmd_cl = system.simulate(x0_cl, controller_edmd_cl, t_eval_cl)
xs_edmd_cl, us_edmd_cl = xs_edmd_cl.T, us_edmd_cl.T

controller_bedmd_cl.comp_time = []
xs_bedmd_cl, us_bedmd_cl = system.simulate(x0_cl, controller_bedmd_cl, t_eval_cl)
xs_bedmd_cl, us_bedmd_cl = xs_bedmd_cl.T, us_bedmd_cl.T

controller_kdnn_cl.comp_time = []
xs_kdnn_cl, us_kdnn_cl = system.simulate(x0_cl, controller_kdnn_cl, t_eval_cl)
xs_kdnn_cl, us_kdnn_cl = xs_kdnn_cl.T, us_kdnn_cl.T

controller_nmpc_cl.comp_time = []
xs_nmpc_cl, us_nmpc_cl = system.simulate(x0_cl, controller_nmpc_cl, t_eval_cl)
xs_nmpc_cl, us_nmpc_cl = xs_nmpc_cl.T, us_nmpc_cl.T
```

#### Plot/analyze the results

```python
plot_inds = [0, 1, 2, 0, 1]
subplot_inds = [1, 2, 3, 4, 8]

plt.figure(figsize=(12,2.5))
for ii in range(5):
    ind = plot_inds[ii]
    if ii < 3:
        ax = plt.subplot(1,4,subplot_inds[ii])
        plt.plot(t_eval_cl, xs_dmd_cl[ind,:], colors[0], label='DMD MPC')
        plt.plot(t_eval_cl, xs_edmd_cl[ind, :], colors[1], label='EDMD MPC')
        plt.plot(t_eval_cl, xs_bedmd_cl[ind, :], colors[2], label='K-NMPC')
        plt.plot(t_eval_cl, xs_kdnn_cl[ind, :], colors[3], label='KDNN-NMPC')
        plt.plot(t_eval_cl, xs_nmpc_cl[ind,:], colors[4], label='NMPC')

        plt.scatter(t_eval_cl[0], x0_cl[ind], color='g')
        plt.scatter(t_eval_cl[-1], set_pt_cl[ind], color='r')
        plt.ylabel(labels[ind])
        ax.yaxis.set_major_locator(MaxNLocator(integer=True))
        plt.title(titles[subplot_inds[ii]-1])
        plt.xlabel('Time (sec)')
        if subplot_inds[ii]==1:
            plt.legend(loc='upper left', frameon=False)
            plt.ylim(-0.15,2)
    else:
        bx = plt.subplot(2,4,subplot_inds[ii])
        plt.plot(t_eval_cl[:-1],us_dmd_cl[ind,:], color=colors[0], label='DMD MPC')
        plt.plot(t_eval_cl[:-1], us_edmd_cl[ind, :], color=colors[1], label='EDMD MPC')
        plt.plot(t_eval_cl[:-1], us_bedmd_cl[ind, :], color=colors[2], label='K-NMPC')
        plt.plot(t_eval_cl[:-1], us_kdnn_cl[ind, :], color=colors[3], label='KDNN-NMPC')
        plt.plot(t_eval_cl[:-1],us_nmpc_cl[ind,:], color=colors[4], label='NMPC')
        plt.plot([0, t_eval_cl[-1]], [umax[ind]+hover_thrust, umax[ind]+hover_thrust], ':k')
        plt.plot([0, t_eval_cl[-1]], [umin[ind]+hover_thrust, umin[ind]+hover_thrust], ':k')
        plt.ylabel(labels[ii+3])
        ax.yaxis.set_major_locator(MaxNLocator(integer=True))
        if subplot_inds[ii] == 4:
            plt.title('Control inputs')
        else:
            plt.xlabel('Time (sec)')
if save_figures:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42
    plt.tight_layout()
    #plt.savefig(dropbox_folder + 'planar_quad_closed_loop.pdf', format='pdf', dpi=2400)
plt.show()

cost_cl_dmd = np.sum(np.diag((xs_dmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)).T@Q_mpc_cl@(xs_dmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)))) + (xs_dmd_cl[:,-1]-set_pt_cl).T@QN_mpc_cl@(xs_dmd_cl[:,-1]-set_pt_cl) + np.sum(np.diag(us_dmd_cl.T@R_mpc_cl@us_dmd_cl))
cost_cl_edmd = np.sum(np.diag((xs_edmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)).T@Q_mpc_cl@(xs_edmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)))) + (xs_edmd_cl[:,-1]-set_pt_cl).T@QN_mpc_cl@(xs_edmd_cl[:,-1]-set_pt_cl) + np.sum(np.diag(us_edmd_cl.T@R_mpc_cl@us_edmd_cl))
cost_cl_bedmd = np.sum(np.diag((xs_bedmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)).T@Q_mpc_cl@(xs_bedmd_cl[:,:-1]-set_pt_cl.reshape(-1,1)))) + (xs_bedmd_cl[:,-1]-set_pt_cl).T@QN_mpc_cl@(xs_bedmd_cl[:,-1]-set_pt_cl) + np.sum(np.diag(us_bedmd_cl.T@R_mpc_cl@us_bedmd_cl))
cost_cl_kdnn = np.sum(np.diag((xs_kdnn_cl[:,:-1]-set_pt_cl.reshape(-1,1)).T@Q_mpc_cl@(xs_kdnn_cl[:,:-1]-set_pt_cl.reshape(-1,1)))) + (xs_kdnn_cl[:,-1]-set_pt_cl).T@QN_mpc_cl@(xs_kdnn_cl[:,-1]-set_pt_cl) + np.sum(np.diag(us_kdnn_cl.T@R_mpc_cl@us_kdnn_cl))
cost_cl_nmpc = np.sum(np.diag((xs_nmpc_cl[:,:-1]-set_pt_cl.reshape(-1,1)).T@Q_mpc_cl@(xs_nmpc_cl[:,:-1]-set_pt_cl.reshape(-1,1)))) + (xs_nmpc_cl[:,-1]-set_pt_cl).T@QN_mpc_cl@(xs_nmpc_cl[:,-1]-set_pt_cl) + np.sum(np.diag(us_nmpc_cl.T@R_mpc_cl@us_nmpc_cl))

print('Solution statistics:\n')
print(tabulate([['DMD MPC', "{:.4f}".format(cost_cl_dmd/cost_cl_nmpc), np.mean(controller_dmd_cl.nom_controller.comp_time), np.std(controller_dmd_cl.nom_controller.comp_time)],
                ['EDMD MPC', "{:.4f}".format(cost_cl_edmd/cost_cl_nmpc),np.mean(controller_edmd_cl.nom_controller.comp_time), np.std(controller_edmd_cl.nom_controller.comp_time)],
                ['bEDMD MPC', "{:.4f}".format(cost_cl_bedmd/cost_cl_nmpc), np.mean(controller_bedmd_cl.comp_time), np.std(controller_bedmd_cl.comp_time)],
                ['DNN bEDMD MPC', "{:.4f}".format(cost_cl_kdnn/cost_cl_nmpc), np.mean(controller_kdnn_cl.comp_time), np.std(controller_kdnn_cl.comp_time)],
                ['NMPC (benchmark, known model)',1, np.mean(controller_nmpc_cl.comp_time), np.std(controller_nmpc_cl.comp_time)]],
               headers=['Normalized cost,\nrealized trajectory', 'Mean comp. time (secs)', 'std comp. time (secs)']))
```