"""
Non-autonomous dynamic systems.
Chaotic nonlinear ODEs
    + https://en.wikipedia.org/wiki/List_of_chaotic_maps
"""
import numpy as np
import inspect, sys
import warnings
warnings.filterwarnings("ignore")
from scipy.integrate import odeint
from psl.emulator import EmulatorBase
from psl.perturb import Steps, Step, SplineSignal, Periodic, RandomWalk
[docs]class ODE_NonAutonomous(EmulatorBase):
    """
    base class autonomous ODE
    """
[docs]    def simulate(self, U=None, ninit=None, nsim=None, ts=None, x0=None):
        """
        :param nsim: (int) Number of steps for open loop response
        :param ninit: (float) initial simulation time
        :param ts: (float) step size, sampling time
        :param x0: (float) state initial conditions
        :return: X, Y, U, D
        """
        if ninit is None:
            ninit = self.ninit
        if nsim is None:
            nsim = self.nsim
        if ts is None:
            ts = self.ts
        if U is None:
            U = self.U
        if x0 is None:
            x = self.x0
        else:
            assert x0.shape[0] == self.nx, "Mismatch in x0 size"
            x = x0
        t = np.arange(0, nsim+1) * ts + ninit
        X = [x]
        N = 0
        for u in U:
            dT = [t[N], t[N + 1]]
            xdot = odeint(self.equations, x, dT, args=(u,))
            x = xdot[-1]
            X.append(x)
            N += 1
            if N == nsim-1:
                break
        Yout = np.asarray(X).reshape(nsim, -1)
        Uout = np.asarray(U).reshape(nsim, -1)
        return {'Y': Yout, 'U': Uout, 'X': np.asarray(X)}  
[docs]class LorenzControl(ODE_NonAutonomous):
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.sigma = 10.
        self.beta = 2.66667
        self.rho = 28
        self.x0 = np.asarray([-8., 8., 27.])
        self.nx = 3
        self.nu = 2
        self.ts = 0.01
        self.ninit = 0.
        self.nsim = 1000
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        t = np.random.uniform(low=0, high=np.pi)
        self.ninit = t
        T = np.arange(t, t + self.ts * nsim, self.ts)
        return self.u_fun(T).T[:nsim] 
[docs]    def equations(self, x, t, u):
        return [
            self.sigma * (x[1] - x[0]) + u[0],
            x[0] * (self.rho - x[2]) - x[1],
            x[0] * x[1] - self.beta * x[2] - u[1],
        ] 
[docs]    def u_fun(self, t):
        return np.array([np.sin(2 * t), np.sin(8 * t)])  
[docs]class SEIR_population(ODE_NonAutonomous):
    """
    Susceptible, Exposed, Infected, and Recovered (SEIR) population population model. Used to model COVID-19 spread.
    Source of the model:
    + https://apmonitor.com/do/index.php/Main/COVID-19Response
    states:
    + Susceptible (s): population fraction that is susceptible to the virus
    + Exposed (e): population fraction is infected with the virus but does not transmit to others
    + Infectious (i): population fraction that is infected and can infect others
    + Recovered (r): population fraction recovered from infection and is immune from further infection
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.N = 10000 # population
        # initial number of infected and recovered individuals
        self.e_0 = 1 / self.N
        self.i_0 = 0.00
        self.r_0 = 0.00
        self.s_0 = 1 - self.e_0 - self.i_0 - self.r_0
        self.x0 = np.asarray([self.s_0, self.e_0, self.i_0, self.r_0])
        self.nx = 4
        self.nu = 1
        self.t_incubation = 5.1
        self.t_infective = 3.3
        self.R0 = 2.4
        self.alpha = 1 / self.t_incubation
        self.gamma = 1 / self.t_infective
        self.beta = self.R0 * self.gamma
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        return Steps(nx=self.nu, nsim=nsim, values=None,
                     randsteps=int(np.ceil(self.nsim / 100)), xmax=1, xmin=0) 
[docs]    def equations(self, x, t, u):
        """
        +  Inputs (1): social distancing (u=0 (none), u=1 (total isolation))
        +  States (4):
        +  Susceptible (s): population fraction that is susceptible to the virus
        +  Exposed (e): population fraction is infected with the virus but does not transmit to others
        +  Infectious (i): population fraction that is infected and can infect others
        +  Recovered (r): population fraction recovered from infection and is immune from further infection
        """
        s = x[0]
        e = x[1]
        i = x[2]
        u = u[0]
        sdt = -(1 - u) * self.beta * s * i
        edt = (1 - u) * self.beta * s * i - self.alpha * e
        idt = self.alpha * e - self.gamma * i
        rdt = self.gamma * i
        dx = [sdt, edt, idt, rdt]
        return dx  
[docs]class Tank(ODE_NonAutonomous):
    """
    Single Tank model
    Original code obtained from APMonitor:
    + https://apmonitor.com/pdc/index.php/Main/TankLevel
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.rho = 1000.0  # water density (kg/m^3)
        self.A = 1.0  # tank area (m^2)
        self.x0 = 0
        self.U = self.get_U(self.nsim)
        self.nu = 2
        self.nx = 1
[docs]    def get_U(self, nsim):
        c = Steps(nx=1, nsim=self.nsim, values=None,
                  randsteps=int(np.ceil(nsim / 400)), xmax=55, xmin=45)
        valve = Steps(nx=1, nsim=nsim, values=None,
                      randsteps=int(np.ceil(nsim / 400)), xmax=100, xmin=0)
        return np.vstack([c.T, valve.T]).T 
[docs]    def equations(self, x, t, u):
        """
        + States (1): level in the tanks
        + Inputs u(1): c - valve coefficient (kg/s / %open)
        + Inputs u(2): valve in % [0-100]
        """
        c = u[0]
        valve = u[1]
        dx_dt = (c / (self.rho*self.A)) * valve
        if x >= 1.0 and dx_dt > 0.0:
            dx_dt = 0
        return dx_dt  
[docs]class TwoTank(ODE_NonAutonomous):
    """
    Two Tank model
    Original code obtained from APMonitor:
    + https://apmonitor.com/do/index.php/Main/LevelControl
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.ts = 1
        self.c1 = 0.08  # inlet valve coefficient
        self.c2 = 0.04  # tank outlet coefficient
        self.x0 = np.asarray([0, 0])
        self.U = self.get_U(self.nsim)
        self.nu = 2
        self.nx = 2
[docs]    def get_U(self, nsim):
        pump = Steps(nx=1, nsim=nsim, values=None,
                     randsteps=int(np.ceil(nsim / 200)), xmax=0.5, xmin=0)
        valve = Steps(nx=1, nsim=nsim, values=None,
                      randsteps=int(np.ceil(nsim / 300)), xmax=0.4, xmin=0)
        return np.vstack([pump.T, valve.T]).T 
[docs]    def equations(self, x, t, u):
        # States (2): level in the tanks
        h1 = x[0]
        h2 = x[1]
        # Inputs (2): pump and valve
        pump = u[0]
        valve = u[1]
        dhdt1 = self.c1 * (1.0 - valve) * pump - self.c2 * np.sqrt(h1)
        dhdt2 = self.c1 * valve * pump + self.c2 * np.sqrt(h1) - self.c2 * np.sqrt(h2)
        if h1 >= 1.0 and dhdt1 > 0.0:
            dhdt1 = 0
        if h2 >= 1.0 and dhdt2 > 0.0:
            dhdt2 = 0
        dhdt = [dhdt1, dhdt2]
        return dhdt  
[docs]class CSTR(ODE_NonAutonomous):
    """
    CSTR model
    Original code obtained from APMonitor:
    + http://apmonitor.com/do/index.php/Main/NonlinearControl
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        # Volumetric Flowrate (m^3/sec)
        self.q = 100
        # Volume of CSTR (m^3)
        self.V = 100
        # Density of A-B Mixture (kg/m^3)
        self.rho = 1000
        # Heat capacity of A-B Mixture (J/kg-K)
        self.Cp = 0.239
        # Heat of reaction for A->B (J/mol)
        self.mdelH = 5e4
        # E - Activation energy in the Arrhenius Equation (J/mol)
        # R - Universal Gas Constant = 8.31451 J/mol-K
        self.EoverR = 8750
        # Pre-exponential factor (1/sec)
        self.k0 = 7.2e10
        # U - Overall Heat Transfer Coefficient (W/m^2-K)
        # A - Area - this value is specific for the U calculation (m^2)
        self.UA = 5e4
        # Steady State Initial Conditions for the States
        self.Ca_ss = 0.87725294608097
        self.T_ss = 324.475443431599
        self.x0 = np.empty(2)
        self.x0[0] = self.Ca_ss
        self.x0[1] = self.T_ss
        # Steady State Initial Condition for the Uncontrolled Inputs
        self.u_ss = 300.0  # cooling jacket Temperature (K)
        self.Tf = 350  # Feed Temperature (K)
        self.Caf = 1  # Feed Concentration (mol/m^3)
        self.nx = 2
        self.nu = 1
        self.nd = 2
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        return self.u_ss + Steps(nx=1, nsim=nsim, values=None,
                                 randsteps=int(np.ceil(nsim / 100)), xmax=6, xmin=-6) 
[docs]    def equations(self, x, t, u):
        # Inputs (1):
        # Temperature of cooling jacket (K)
        Tc = u
        # Disturbances (2):
        # Tf = Feed Temperature (K)
        # Caf = Feed Concentration (mol/m^3)
        # States (2):
        # Concentration of A in CSTR (mol/m^3)
        Ca = x[0]
        # Temperature in CSTR (K)
        T = x[1]
        # reaction rate
        rA = self.k0 * np.exp(-self.EoverR / T) * Ca
        # Calculate concentration derivative
        dCadt = self.q / self.V * (self.Caf - Ca) - rA
        # Calculate temperature derivative
        dTdt = self.q / self.V * (self.Tf - T) \
               
+ self.mdelH / (self.rho * self.Cp) * rA \
               
+ self.UA / self.V / self.rho / self.Cp * (Tc - T)
        xdot = np.zeros(2)
        xdot[0] = dCadt
        xdot[1] = dTdt
        return xdot  
[docs]class InvPendulum(ODE_NonAutonomous):
    """
    Inverted Pendulum dynamics
    states: x = [\theta \dot{\theta}]; \theta is angle from upright equilibrium
    input: u = input torque
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 2  # Number of states
        self.nu = 1  # Number of control inputs
        self.g = 9.81  # Acceleration due to gravity (m/s^2)
        self.L = 0.5  # length of the pole in m
        self.m = 0.15  # ball mass in kg
        self.b = 0.1  # friction
        self.x0 = [0.5, 0.0]
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        return np.zeros(nsim) 
[docs]    def equations(self, x, t, u):
        y = [x[1],
            (self.m * self.g * self.L * np.sin(x[0]) - self.b * x[1]) / (self.m * self.L ** 2)]
        y[1] = y[1] + (u / (self.m * self.L ** 2))
        return y  
[docs]class UAV3D_kin(ODE_NonAutonomous):
    """
    Dubins 3D model -- UAV kinematic model with no wind
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 4    # Number of states
        self.nu = 3    # Number of control inputs
        self.g = 9.81  # Acceleration due to gravity (m/s^2)
        self.vmin = 9.5   # Minimum airspeed for stable flight (m/s)
        self.h = 10    # Minimum altitude to avoid crash (m)
        self.ts = 0.1
        # Initial Conditions for the States
        self.x0 = np.array([5, 10, 50, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = 1
        headVec = np.multiply([0.0, -120.0, 0.0, 45.0, -90.0, 90.0, -175.0, 25.0, -90.0, 40.0, -20.0],
                              np.pi / 180.0)
        gammVec = np.multiply([0.0, 5.0, 10.0, -5.0, 0.0, 9.0, -1.0, 0.0, 3.0, -1.0, 0.0, 3.0, -1.0], np.pi / 180.0)
        velVec = [9.5, 16.0, 10.0, 11.0, 10.0, 11.5, 9.5, 15.0, 16.0, 13.0, 12.0, 12.0, 9.0]
        self.V = SplineSignal(nsim=nsim, values=velVec, xmin=9, xmax=15, rseed=seed)
        heading = SplineSignal(nsim=nsim, values=headVec, xmin=-20 * np.pi / 180, xmax=20 * np.pi / 180,
                               rseed=seed)
        self.phi = np.append([0.0], np.diff(heading) / self.ts)
        self.gamma = SplineSignal(nsim=nsim, values=gammVec, xmin=-10 * np.pi / 180, xmax=10 * np.pi / 180,
                                  rseed=seed)
        U1 = np.multiply(self.V, np.cos(self.gamma))
        U2 = np.multiply(self.V, np.sin(self.gamma))
        U3 = self.g * np.divide(np.tan(self.phi), self.V)
        return np.vstack([U1, U2, U3]).T 
[docs]    def equations(self, x, t, u):
        """
        + States (4): [x, y, z]
        + Inputs (3): [V, phi, gamma]
        + Transformed Inputs (3): [U1, U2, U3]
        """
        U1 = u[0]
        U2 = u[1]
        U3 = u[2]
        dx_dt = np.zeros(4)
        dx_dt[0] = U1 * np.cos(x[3])
        dx_dt[1] = U1 * np.sin(x[3])
        dx_dt[2] = U2
        if x[2] <= self.h:
            dx_dt[2] = 0.0
        dx_dt[3] = U3
        return dx_dt  
[docs]class UAV2D_kin(ODE_NonAutonomous):
    """
    Dubins 2D model -- UAV kinematic model with no wind
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 4    # Number of states
        self.nu = 1    # Number of control inputs
        self.g = 9.81  # Acceleration due to gravity (m/s^2)
        self.vmin = 9.5   # Minimum airspeed for stable flight (m/s)
        self.h = 10    # Minimum altitude to avoid crash (m)
        self.ts = 0.1
        self.V = 10   # Constant velocity  (m/s^2)
        self.x0 = np.array([5, 10, 10, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = 3
        self.phi = SplineSignal(nsim, values=None, xmin=-45*np.pi/180, xmax=45*np.pi/180, rseed=seed)
        return self.phi 
[docs]    def equations(self, x, t, u):
        """
        + States (3): [x, y, z, psi]
        + Inputs (1): [phi]
        """
        V = self.V
        phi = u
        dx_dt = np.zeros(4)
        dx_dt[0] = V * np.cos(x[3])
        dx_dt[1] = V * np.sin(x[3])
        dx_dt[2] = 0.0
        dx_dt[3] = (self.g/V) * (np.tan(phi))
        return dx_dt  
[docs]class UAV3D_reduced(ODE_NonAutonomous):
    """
    Reduced Dubins 3D model -- UAV kinematic model with transformed inputs
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 3    # Number of states
        self.nu = 3    # Number of control inputs
        self.g = 9.81  # Acceleration due to gravity (m/s^2)
        self.vmin = 9.5   # Minimum airspeed for stable flight (m/s)
        self.h = 10    # Minimum altitude to avoid crash (m)
        self.ts = 0.1
        self.x0 = np.array([5, 10, 50])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = 2
        headVec = np.multiply([0.0, -120.0, 0.0, 45.0, -90.0, 90.0, -175.0, 25.0, -90.0, 40.0, -20.0], np.pi/180.0)
        gammVec = np.multiply([0.0, 5.0, 10.0, -5.0, 0.0, 9.0, -1.0, 0.0, 3.0, -1.0, 0.0], np.pi/180.0)
        self.V = SplineSignal(nsim=nsim, values=None, xmin=9, xmax=15, rseed=seed)
        self.phi = SplineSignal(nsim=nsim, values=None, xmin=-20*np.pi/180, xmax=20*np.pi/180, rseed=seed)
        self.gamma = SplineSignal(nsim=nsim, values=gammVec, xmin=-10*np.pi/180, xmax=10*np.pi/180, rseed=seed)
        U1 = np.multiply(self.V, np.cos(self.gamma))
        U2 = np.multiply(self.V, np.sin(self.gamma))
        U3 = SplineSignal(nsim=nsim, values=headVec * 3, xmin=-20 * np.pi / 180, xmax=20 * np.pi / 180, rseed=seed)
        return np.vstack([U1, U2, U3]).T 
[docs]    def equations(self, x, t, u):
        """
        + States (4): [x, y, z]
        + Inputs (3): [V, phi, gamma]
        + Transformed Inputs (3): [U1, U2, U3]
        """
        U1 = u[0]
        U2 = u[1]
        U3 = u[2]
        dx_dt = np.zeros(3)
        dx_dt[0] = U1 * np.cos(U3)
        dx_dt[1] = U1 * np.sin(U3)
        dx_dt[2] = U2
        if x[2] <= self.h:
            dx_dt[2] = 0.0
        return dx_dt  
      
[docs]class UAV3D_dyn(ODE_NonAutonomous):
    """
    UAV dynamic guidance model with no wind
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 6    # Number of states
        self.nu = 3    # Number of control inputs
        self.g = 9.81  # Acceleration due to gravity (m/s^2)
        self.W = 10.0  # Weight of the aircraft (kg)
        self.rho = 1.2 # Air density at sea level (kg/m^3); varies with altitude
        self.S = 10.0  # Wing area (m^2)
        self.lenF = 2  # Fuselage length
        self.b = 7.0  # Wingspan (m)
        self.AR = self.b**2 / self.S  # Aspect Ratio of the wing
        self.eps = 0.92  # Oswald efficiency factor (from Atlantik Solar UAV)
        self.K = 1 / (self.eps * np.pi * self.AR)  # aerodynamic coefficient
        self.ts = 0.1
        self.x0 = np.array([5, 10, 15, 0, np.pi/18, 9])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = 2
        loadVec = [1.0, 1.5, 1.0, 0.5, 0.1, 1.5, 1.0, 1.25, 1.0, 0.9, 1.0]
        T = SplineSignal(nsim=nsim, values=None, xmin=100, xmax=500, rseed=seed)
        phi = SplineSignal(nsim=nsim, values=None, xmin=-10 * np.pi / 180, xmax=10 * np.pi / 180, rseed=seed)
        load = SplineSignal(nsim=nsim, values=loadVec, xmin=0, xmax=3, rseed=seed)
        return np.vstack([T, phi, load]).T 
[docs]    def equations(self, x, t, U):
        """
        + States (6): [x, y, z, psi, gamma, V]
        + Inputs (3): [T, phi, load]
        + load = Lift force / Weight
        """
        V = x[5]
        T = U[0]
        phi = U[1]
        load = 1.0
        CD0 = 0.015  # 0.074 * Re ** (-0.2)  # parasitic drag
        CL = 2 * load * self.W / self.rho * V ** 2 * self.S  # Lift coefficient
        CD = CD0 + self.K * CL**2     # Drag coefficient
        drag = self.rho * V ** 2 * self.S * CD   # Total drag
        dx_dt = np.zeros(6)
        dx_dt[0] = V * np.cos(x[3]) * np.cos(x[4])
        dx_dt[1] = V * np.sin(x[3]) * np.cos(x[4])
        dx_dt[2] = V * np.sin(x[4])
        dx_dt[3] = (self.g/V) * (load * np.sin(phi)) / np.cos(x[4])
        dx_dt[4] = (self.g/V) * (load * np.cos(phi) - np.cos(x[4]))
        dx_dt[5] = self.g * ((T - drag)/self.W - np.sin(x[4]))
        return dx_dt  
      
"""
Chaotic nonlinear ODEs 
https://en.wikipedia.org/wiki/List_of_chaotic_maps
"""
[docs]class HindmarshRose(ODE_NonAutonomous):
    """
    Hindmarsh–Rose model of neuronal activity
    + https://en.wikipedia.org/wiki/Hindmarsh%E2%80%93Rose_model
    + https://demonstrations.wolfram.com/HindmarshRoseNeuronModel/
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=59):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.a = 1
        self.b = 2.6
        self.c = 1
        self.d = 5
        self.s = 4
        self.xR = -8/5
        self.r = 0.01
        self.umin = -10
        self.umax = 10
        self.x0 = np.asarray([-5,-10,0])
        self.U = self.get_U(self.nsim)
        self.nu = 1
        self.nx = 3
[docs]    def get_U(self, nsim):
        return 3 * np.asarray([np.ones((nsim))]).T 
[docs]    def equations(self, x, t, u):
        theta = -self.a*x[0]**3 + self.b*x[0]**2
        phi = self.c -self.d*x[0]**2
        dx1 = x[1] + theta - x[2] + u
        dx2 = phi - x[1]
        dx3 = self.r*(self.s*(x[0]-self.xR)-x[2])
        dx = [dx1, dx2, dx3]
        return dx  
[docs]class Iver_kin_reduced(ODE_NonAutonomous):
    """
    Kinetic model of Unmanned Underwater Vehicle (Yan et al 2020) -- UAV kinematic model with **no roll**
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 5
        self.nu = 5
        self.ts = 0.1
        self.K_alpha = 100.0 # Barrier function parameter
        self.x0 = np.array([0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = self.seed
        u = Steps(nsim=nsim, values=None, randsteps=20, xmin=1.0, xmax=3.0, rseed=seed).T
        v = Steps(nsim=nsim, values=None, randsteps=20, xmin=-0.5, xmax=0.5, rseed=2 * seed).T
        w = Steps(nsim=nsim, values=None, randsteps=20, xmin=-0.5, xmax=0.5, rseed=3 * seed).T
        q = Steps(nsim=nsim, values=None, randsteps=20, xmin=-0.5, xmax=0.5, rseed=4 * seed).T
        r = Steps(nsim=nsim, values=None, randsteps=20, xmin=-0.5, xmax=0.5, rseed=5 * seed).T
        return np.vstack( [u, v, w, q, r] ).T 
[docs]    def equations(self, x, t, u):
        """
        + States (5): [xi, eta, zeta, theta, psi]
        + Inputs (5): [u, v, w, q, r]
        """
        theta = x[3]
        psi = x[4]
        # Control
        uu = u[0]
        v = u[1]
        w = u[2]
        q = u[3]
        r = u[4]
        # Barrier function to avoid singularities
        h = (np.pi/2.0 - 0.01)**2 - theta**2
        z = -2.0*theta*q + self.K_alpha*h
        dx_dt = np.zeros(5)
        dx_dt[0] = np.cos( psi )*np.cos( theta )*uu - np.sin( psi )*v + np.sin( theta )*np.cos( psi )*w
        dx_dt[1] =  np.sin( psi )*np.cos( theta )*uu + np.cos( psi )*v + ( np.sin( theta )*np.sin( psi ) )*w
        dx_dt[2] = -np.sin( theta )*uu + np.cos( theta )*w
        dx_dt[3] = q - z
        dx_dt[4] = r/(np.cos( theta ))
        return dx_dt  
[docs]class Iver_kin(ODE_NonAutonomous):
    """
    Kinetic model of Unmanned Underwater Vehicle (Fossen) -- Full UAV kinematic model
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 6
        self.nu = 6
        self.ts = 0.1
        self.x0 = np.array([1.0, 0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = self.seed
        u = SplineSignal(nsim=nsim, values=None, xmin=1.0, xmax=3.0, rseed=seed)
        v = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        w = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        p = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        q = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        r = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        return np.vstack( [u, v, w, p, q, r] ).T 
[docs]    def equations(self, x, t, u):
        """
        + States (5): [n, e, d, phi, theta, psi]
        + Inputs (5): [u, v, w, p, q, r]
        """
        phi = x[3]
        theta = x[4]
        psi = x[5]
        # Control
        uu = u[0]
        v = u[1]
        w = u[2]
        p = u[3]
        q = u[4]
        r = u[5]
        dx_dt = np.zeros(6)
        dx_dt[0] = uu*np.cos( psi )*np.cos( theta ) + v*( np.cos( psi )*np.sin( theta )*np.sin( phi ) - np.sin( psi )*np.cos( phi ) ) + w*( np.sin( psi )*np.sin( phi ) + np.cos( psi )*np.cos( phi )*np.sin( theta ) )
        dx_dt[1] = uu*( np.sin( psi )*np.cos( theta ) ) + v*( np.cos( psi )*np.cos( phi ) + np.sin( phi )*np.sin( theta )*np.sin( psi ) ) + w*( np.sin( theta )*np.sin( psi )*np.cos( phi ) - np.cos( psi )*np.sin( phi ) )
        dx_dt[2] = -uu*np.sin( theta ) + v*np.cos( theta )*np.sin( phi ) + w*np.cos( theta )*np.cos( phi )
        dx_dt[3] = p + q*np.sin ( phi )*np.tan( theta ) + r*np.cos( phi )*np.tan( theta )
        dx_dt[4] = q*np.cos( phi ) - r*np.sin( phi )
        dx_dt[5] = q*( np.sin( phi )/np.cos( theta ) ) + r*( np.cos( phi )/np.cos( theta ) )
        return dx_dt  
[docs]class Iver_dyn(ODE_NonAutonomous):
    """
    Dynamic model of Unmanned Underwater Vehicle (Fossen) -- Excludes hydrostatic/dynamic terms and ocean current
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 6    # Number of states
        self.nu = 6    # Number of control inputs
        self.ts = 0.1
        self.m = 1.0        # Mass of of the vehicle (kg)
        self.Ixx = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Iyy = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Izz = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Ixy = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Ixz = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Iyz = 0.001    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.xg = 0.0       # Center of mass w.r.t x axis, written in body frame (m)
        self.yg = 0.0       # Center of mass w.r.t y axis, written in body frame (m)
        self.zg = 0.0       # Center of mass w.r.t z axis, written in body frame (m)
        self.x0 = np.array([1.0, 0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = self.seed
        tau_X = SplineSignal(nsim=nsim, values=None, xmin=1.0, xmax=3.0, rseed=seed)
        tau_Y = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        tau_Z = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        tau_K = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        tau_M = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        tau_N = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        return np.vstack( [tau_X, tau_Y, tau_Z, tau_K, tau_M, tau_N] ).T 
[docs]    def equations(self, x, t, u):
        """
        + States (6): [u, v, w, p, q, r]
        + Inputs (6): [tau_X, tau_Y, tau_Z, tau_K, tau_M, tau_N]
        """
        # States
        uu = x[0]
        v = x[1]
        w = x[2]
        p = x[3]
        q = x[4]
        r = x[5]
        nu = np.array(x)
        nu1 = np.array([uu, v, w])
        nu2 = np.array([p, q, r])
        rbg = np.array([self.xg, self.yg, self.zg ])
        Io = np.array([ [self.Ixx, -self.Ixy, -self.Ixz], [-self.Ixy, self.Iyy, -self.Iyz], [-self.Ixz, -self.Iyz, self.Izz] ])
        M_rb = np.block([ [self.m*np.eye(3), -self.m*self.Cross(rbg) ], [self.m*self.Cross(rbg), Io ] ])
        C_rb = np.block([ [np.zeros((3,3)), -self.m*self.Cross(nu1) - self.m*np.multiply( self.Cross(nu2), self.Cross(rbg)) ], [-self.m*self.Cross(nu1) + self.m*np.multiply( self.Cross(rbg), self.Cross(nu2) ), -self.Cross( Io.dot(nu2) )  ] ])
        dx_dt = np.linalg.inv(M_rb).dot( -C_rb.dot(nu) + u  )
        return dx_dt 
[docs]    def Cross(self,x):
        """
        + Input: 3d array x
        + Output: cross product matrix (skew symmetric)
        """
        return np.array([[0, -x[2], x[1]], [x[2], 0, -x[0]], [-x[1], x[0], 0] ])  
[docs]class Iver_dyn_reduced(ODE_NonAutonomous):
    """
    Dynamic model of Unmanned Underwater Vehicle (Yan et al) -- Excludes rolling, includes hydrostate/dynamic terms, no currents
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 10    # Number of states
        self.nu = 5    # Number of control inputs
        self.ts = 0.1
        self.m = 100.0        # Mass of of the vehicle (kg)
        self.Iyy = 0.1    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Izz = 0.1    # Moment of inertia about resp. axes, written w.r.t body frame (kg m^2)
        self.Xu_dot = 0.0001  # Hydrodynamic coefficient
        self.Yv_dot = 0.0001  # Hydrodynamic coefficient
        self.Zw_dot = 0.0001  # Hydrodynamic coefficient
        self.Mq_dot = 0.0001  # Hydrodynamic coefficient
        self.Nr_dot = 0.0001  # Hydrodynamic coefficient
        self.Xu = 0.0001      # Hydrodynamic coefficient
        self.Yv = 0.0001      # Hydrodynamic coefficient
        self.Zw = 0.0001      # Hydrodynamic coefficient
        self.Mq = 0.0001      # Hydrodynamic coefficient
        self.Nr = 0.0001      # Hydrodynamic coefficient
        self.Xuu = 0.0001     # Hydrodynamic coefficient
        self.Yvv = 0.0001     # Hydrodynamic coefficient
        self.Zww = 0.0001     # Hydrodynamic coefficient
        self.Mqq = 0.0001     # Hydrodynamic coefficient
        self.Nrr = 0.0001     # Hydrodynamic coefficient
        self.V = 0.01       # Volume of water displaced by the vehicle
        self.rho = 1000    # Density of the water (kg/m^3)
        self.g = 9.81       # Acceleration due to gravity (m/s^2)
        self.GML = 0.01     # Vertical metacentric height (m)
        self.x0 = np.array([0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = self.seed
        tau_X = SplineSignal(nsim=nsim, values=None, xmin=1.0, xmax=3.0, rseed=seed)
        tau_Y = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        tau_Z = SplineSignal(nsim=nsim, values=None, xmin=-0.1, xmax=0.1, rseed=seed)
        tau_M = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        tau_N = SplineSignal(nsim=nsim, values=None, xmin=-0.01, xmax=0.01, rseed=seed)
        return np.vstack( [tau_X, tau_Y, tau_Z, tau_M, tau_N] ).T 
[docs]    def equations(self, x, t, u):
        """
        + States (10): [n, e, d, theta, psi, u, v, w, p, q, r]
        + Inputs (5): [tau_X, tau_Y, tau_Z, tau_M, tau_N]
        """
        n = x[0]
        e = x[1]
        d = x[2]
        theta = x[3]
        psi = x[4]
        uu = x[5]
        v = x[6]
        w = x[7]
        q = x[8]
        r = x[9]
        nu = np.array([ uu, v, w, q, r])
        # Kinematics: dot(eta) = J nu
        dx_dt = np.zeros(10)
        dx_dt[0] = np.cos(x[4]) * np.cos(x[3]) * nu[0] - np.sin(x[4]) * nu[1] + np.sin(x[3]) * np.cos(x[4]) * nu[2]
        dx_dt[1] = (np.sin(x[4]) * np.cos(x[3]) * np.cos(x[4]) * np.sin(x[3]) * np.sin(x[4]) - np.sin(x[3])) * nu[0]
        dx_dt[2] = np.cos(x[3]) * nu[2]
        dx_dt[3] = nu[3]
        dx_dt[4] = nu[4] / (np.cos(x[3]))
        # Construct dynamics in matrix form: dot(nu) = inv(M) ( -C nu - D nu - g + tau)
        M_rb = np.diag([ self.m, self.m, self.m, self.Iyy, self.Izz ])
        M_a = np.diag([ -self.Xu_dot, -self.Yv_dot, -self.Zw_dot, -self.Mq_dot, -self.Nr_dot ])
        M = M_rb + M_a
        C_rb = np.array([ [0, 0, 0, self.m*w, -self.m*v], [0, 0, 0, 0, self.m*uu], [0, 0, 0, -self.m*uu, 0], [-self.m*w, 0, self.m*uu, 0, 0], [self.m*v, -self.m*uu, 0, 0, 0] ])
        C_a = np.array([ [0, 0, 0, -self.Zw_dot*w, self.Yv_dot*v], [0, 0, 0, 0, -self.Xu_dot*uu], [0, 0, 0, self.Xu_dot*uu, 0],[self.Zw_dot*w, 0, -self.Xu_dot*uu, 0, 0], [-self.Yv_dot*v, self.Xu_dot*uu, 0, 0, 0] ])
        C = C_rb + C_a
        D = np.diag([ self.Xu, self.Yv, self.Zw, self.Mq, self.Nr ]) + np.diag([ self.Xuu, self.Yvv, self.Zww, self.Mqq, self.Nrr ])
        g = np.array([ 0, 0, 0, self.rho*self.V*self.GML*np.sin(theta), 0])
        dx_dt[5:] = np.linalg.inv(M).dot( -C.dot(nu) -D.dot(nu) - g + u  )
        return dx_dt  
[docs]class Iver_dyn_simplified(ODE_NonAutonomous):
    """
    Dynamic model of Unmanned Underwater Vehicle (modified from Stankiewicz et al) -- Excludes rolling, sway, currents, Includes: hydrostate/dynamic terms, control surface deflections/propeller thrust, and actuator dynamics
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.nx = 12    # Number of states (including actuator dynamics)
        self.nu = 3    # Number of control inputs
        self.ts = 0.1
        self.Mq = -0.748        # Hydrodynamic coefficient (1/s)
        self.Nur = -0.441       # Hydrodynamic coefficient (1/m)
        self.Xuu = -0.179       # Hydrodynamic coefficient (1/m)
        self.Zww = 0.098        # Hydrodynamic coefficient (1/m)
        self.Muq = -3.519       # Hydrodynamic coefficient (1/m)
        self.WB = 0.0           # Out-of-ballast term based on weight and buoyancy ratio (m/s^2) (Differs from Stankiewicz, here set to zero for neutral buoyancy)
        self.Bz = 8.947         # Bouyancy term that accounts for the center of bouyancy vertical offset from the center of gravity (1/s^2)
        self.k = 0.519          # Hydrodynamic coefficient (m/s^2)
        self.b = 3.096          # Hydrodynamic coefficient (1/m^2)
        self.c = 0.065          # Hydrodynamic coefficient (1/m^2)
        self.K_delta_u = -10.0   # Thruster dynamic coefficient
        self.K_delta_q = -10.0   # Elevator deflection dynamic coefficient
        self.K_delta_r = -10.0   # Rudder deflection dynamic coefficient
        self.x0 = np.array([0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        seed = self.seed
        delta_u = Steps(nsim=nsim, values=None, randsteps=100, xmin=0.0, xmax=1.0, rseed=seed).T
        delta_q = Steps(nsim=nsim, values=None, randsteps=100, xmin=-1.0, xmax=1.0, rseed=2 * seed).T
        delta_r = Steps(nsim=nsim, values=None, randsteps=100, xmin=-1.0, xmax=1.0, rseed=3 * seed).T
        return np.vstack( [delta_u, delta_q, delta_r] ).T 
[docs]    def equations(self, x, t, u):
        """
        + States (12): [px, py, pz, theta, psi, uu, w, q, r, delta_u, delta_q, delta_r]
        + Inputs (3): [delta_uc, delta_qc, delta_rc] (thrust speed/deflections, normalized)
        """
        theta = x[3]
        psi = x[4]
        uu = x[5]
        w = x[6]
        q = x[7]
        r = x[8]
        delta_u = x[9]
        delta_q = x[10]
        delta_r = x[11]
        delta_uc = u[0]
        delta_qc = u[1]
        delta_rc = u[2]
        # Kinematics:
        dx_dt = np.zeros(12)
        dx_dt[0] = uu*np.cos(psi)*np.cos(theta) + w*np.cos(psi)*np.sin(theta)
        dx_dt[1] = uu*np.sin(psi)*np.cos(theta) + w*np.sin(psi)*np.sin(theta)
        dx_dt[2] = w*np.cos(theta) - uu*np.sin(theta)
        dx_dt[3] = q
        dx_dt[4] = r / (np.cos(theta))
        # Dynamics
        dx_dt[5] = self.Xuu*(uu**2) + self.k*delta_u
        dx_dt[6] = self.Zww*w*np.absolute(w) + self.WB*np.cos(theta)
        dx_dt[7] = self.Muq*uu*q + self.Mq*q - self.Bz*np.sin(theta) + self.b*(uu**2)*delta_q
        dx_dt[8] = self.Nur*uu*r + self.c*(uu**2)*delta_r
        # Actuator dynamics:
        dx_dt[9] = self.K_delta_u*( delta_u - delta_uc )
        dx_dt[10] = self.K_delta_q*( delta_q - delta_qc )
        dx_dt[11] = self.K_delta_r*( delta_r - delta_rc )
        return dx_dt  
[docs]class Iver_dyn_simplified_output(ODE_NonAutonomous):
    """
    Dynamic model of Unmanned Underwater Vehicle (modified from Stankiewicz et al) -- Excludes rolling, sway, currents, Includes: hydrostate/dynamic terms, control surface deflections/propeller thrust, and actuator dynamics
    with non-kinematic output
    """
    # parameters of the dynamical system
[docs]    def parameters(self):
        self.nx = 9    # Number of states (including actuator dynamics)
        self.nu = 3    # Number of control inputs
        self.ts = 0.1
        # Model parameters
        self.Mq = -0.748        # Hydrodynamic coefficient (1/s)
        self.Nur = -0.441       # Hydrodynamic coefficient (1/m)
        self.Xuu = -0.179       # Hydrodynamic coefficient (1/m)
        self.Zww = 0.098        # Hydrodynamic coefficient (1/m)
        self.Muq = -3.519       # Hydrodynamic coefficient (1/m)
        self.WB = 0.0           # Out-of-ballast term based on weight and buoyancy ratio (m/s^2) (Differs from Stankiewicz, here set to zero for neutral buoyancy)
        self.Bz = 8.947         # Bouyancy term that accounts for the center of bouyancy vertical offset from the center of gravity (1/s^2)
        self.k = 0.519          # Hydrodynamic coefficient (m/s^2)
        self.b = 3.096          # Hydrodynamic coefficient (1/m^2)
        self.c = 0.065          # Hydrodynamic coefficient (1/m^2)
        self.K_delta_u = -10.0   # Thruster dynamic coefficient
        self.K_delta_q = -10.0   # Elevator deflection dynamic coefficient
        self.K_delta_r = -10.0   # Rudder deflection dynamic coefficient
        # Initial Conditions for the States
        self.x0 = np.array([0, 0, 0.01, 0, 0, 0, 0, 0, 0])
        self.U = self.get_U(self.nsim) 
[docs]    def get_U(self, nsim):
        seed = 3
        #delta_u = SplineSignal(nsim=self.nsim, values=None, xmin= 0.0, xmax=1.0, rseed=seed)
        #delta_q = SplineSignal(nsim=self.nsim, values=None, xmin=-1.0, xmax=1.0, rseed=2*seed)
        #delta_r = SplineSignal(nsim=self.nsim, values=None, xmin=-1.0, xmax=1.0, rseed=3*seed)
        delta_u = Steps(nsim=nsim, values=None, randsteps=100, xmin=0.0, xmax=1.0, rseed=seed).T
        delta_q = Steps(nsim=nsim, values=None, randsteps=100, xmin=-1.0, xmax=1.0, rseed=2 * seed).T
        delta_r = Steps(nsim=nsim, values=None, randsteps=100, xmin=-1.0, xmax=1.0, rseed=3 * seed).T
        return np.vstack( [delta_u, delta_q, delta_r] ).T 
    # equations defining the dynamical system
[docs]    def equations(self, x, t, u):
        """
        + States (9): [theta, psi, uu, w, q, r, delta_u, delta_q, delta_r]
        + Inputs (3): [delta_uc, delta_qc, delta_rc] (thrust speed/deflections, normalized)
        """
        # States
        theta = x[0]
        psi = x[1]
        uu = x[2]
        w = x[3]
        q = x[4]
        r = x[5]
        delta_u = x[6]
        delta_q = x[7]
        delta_r = x[8]
        # Control
        delta_uc = u[0]
        delta_qc = u[1]
        delta_rc = u[2]
        # Kinematics:
        dx_dt = np.zeros(9)
        dx_dt[0] = q
        dx_dt[1] = r / (np.cos(theta))
        # Dynamics
        dx_dt[2] = self.Xuu*(uu**2) + self.k*delta_u
        dx_dt[3] = self.Zww*w*np.absolute(w) + self.WB*np.cos(theta)
        dx_dt[4] = self.Muq*uu*q + self.Mq*q - self.Bz*np.sin(theta) + self.b*(uu**2)*delta_q
        dx_dt[5] = self.Nur*uu*r + self.c*(uu**2)*delta_r
        # Actuator dynamics:
        dx_dt[6] = self.K_delta_u*( delta_u - delta_uc )
        dx_dt[7] = self.K_delta_q*( delta_q - delta_qc )
        dx_dt[8] = self.K_delta_r*( delta_r - delta_rc )
        return dx_dt  
[docs]class SwingEquation(ODE_NonAutonomous):
    """
    Power Grid Swing Equation.
    + https://en.wikipedia.org/wiki/Swing_equation
    + The second-order swing equation is converted to
    + two first-order ODEs
    """
    def __init__(self, nsim=1001, ninit=0, ts=0.1, seed=3):
        super().__init__(nsim=nsim, ninit=ninit, ts=ts, seed=seed)
        self.Pm = 0.8  # Mechanical power
        self.Pmax = 5.0  # Maximum electrical output
        self.H = 500.0  # Inertia constant
        self.D = 5.0  # Damping coefficient
        self.freq = 60.0  # Base frequency
        self.ws = 2 * np.pi * self.freq  # Base angular speed
        self.M = 2 * self.H / self.ws  # scaled inertia constant
        self.nx = 2  # number of variables
        self.mode = 1  # 0 (Constant mechanical power and Pmax)
        # 1 (Noisy mechanical power with constant Pmax)
        # 2 (Constant mechanical power with fault on-off Pmax (square wave))
        if (self.mode == 0):
            self.delta0 = 0.0
        else:
            self.delta0 = np.arcsin(self.Pm / self.Pmax)  # initial condition for machine angle \delta
        self.dw0 = 0.0  # initial condition for machine speed deviation d\omega
        self.x0 = [self.delta0, self.dw0]  # initial condition for the model
        self.ts = 0.01  # Time-step
        self.ninit = 0.0  # Simulation start at t=0
        self.nsim = 1000  # 1000 steps = 10 sec. horizon
        self.tfaulton = 0.05
        self.tfaultoff = 0.15
        self.U = self.get_U(self.nsim)
[docs]    def get_U(self, nsim):
        if (self.mode == 0):
            # 0 (Constant mechanical power and Pmax)
            Pmech = Step(nx=1, nsim=nsim, tstep=100, xmax=self.Pm, xmin=self.Pm, rseed=1)
            Pmax = Step(nx=1, nsim=nsim, tstep=100, xmax=self.Pmax, xmin=self.Pmax, rseed=1)
            self.U = np.vstack([Pmech.T, Pmax.T]).T
            self.nu = 2
        elif (self.mode == 1):
            # 1 (Noisy mechanical power with constant Pmax)
            Pmech = RandomWalk(nx=1, nsim=nsim, xmax=self.Pm * 1.02, xmin=self.Pm * 0.98, sigma=0.1, rseed=1)
            self.U = Pmech
            self.nu = 1
        else:
            # 2 (Constant mechanical power with fault on-off Pmax (square wave))
            Signal = []
            signal = []
            for tstep in range(0, nsim):
                t = tstep * self.ts
                if (t < self.tfaulton):  # Pre-fault
                    signal.append(self.Pmax)
                elif (t >= self.tfaulton and t <= self.tfaultoff):  # Fault-on
                    signal.append(0)
                else:  # Post-fault
                    signal.append(self.Pmax)
            Signal.append(signal)
            Pmax = np.asarray(Signal).T
            self.U = Pmax
            self.nu = 1
        return self.U 
[docs]    def equations(self, x, t, u):
        delta = x[0]
        domega = x[1]
        if (self.mode == 0):
            Pm = u[0]
            Pmax = u[1]
        elif (self.mode == 1):
            Pm = u[0]
            Pmax = self.Pmax
        else:
            Pm = self.Pm
            Pmax = u[0]
        dx_dt = [self.ws * domega, (Pm - Pmax * np.sin(delta) - self.D * domega) / self.M]
        return dx_dt  
systems = dict(inspect.getmembers(sys.modules[__name__], lambda x: inspect.isclass(x)))
systems = {k: v for k, v in systems.items() if issubclass(v, ODE_NonAutonomous) and v is not ODE_NonAutonomous}