Source code for direct_data_driven_mpc.utilities.models.nonlinear_model

"""
Classes for modeling nonlinear systems.

This module provides a class for representing and simulating discrete-time
nonlinear systems using a state-space representation.
"""

from typing import Callable

import numpy as np


[docs] class NonlinearSystem: """ A class representing a nonlinear dynamical system. The system is defined by its dynamics and output functions, in the form: .. math:: x(k+1) = f(x(k), u(k)) = f_0(x(k)) + B * u(k) y(k) = h(x(k), u(k)) = h_0(x(k)) + D * u(k) Attributes: f (Callable[[np.ndarray, np.ndarray], np.ndarray]): The function representing the system's dynamics. h (Callable[[np.ndarray, np.ndarray], np.ndarray]): The function representing the system's output. n (int): The number of system states. m (int): The number of inputs to the system. p (int): The number of outputs of the system. eps_max (float): The upper bound of the system measurement noise. x (np.ndarray): The internal state vector of the system. """
[docs] def __init__( self, f: Callable[[np.ndarray, np.ndarray], np.ndarray], h: Callable[[np.ndarray, np.ndarray], np.ndarray], n: int, m: int, p: int, eps_max: float = 0.0, ): """ Initialize a nonlinear dynamical system with a dynamics function `f` and an output function `h`. Args: f (Callable[[np.ndarray, np.ndarray], np.ndarray]): The function representing the system's dynamics. h (Callable[[np.ndarray, np.ndarray], np.ndarray]): The function representing the system's output. n (int): The number of system states. m (int): The number of inputs to the system. p (int): The number of outputs of the system. eps_max (float): The upper bound of the system measurement noise. Defaults to 0.0. """ self.f = f self.h = h self.n = n self.m = m self.p = p self.eps_max = eps_max # System state self.x = np.zeros(self.n)
[docs] def simulate_step(self, u: np.ndarray, w: np.ndarray) -> np.ndarray: """ Simulate a single time step of the nonlinear system with a given input `u`. The system simulation follows the state-space equations: .. math:: x(k+1) = f(x(k), u(k)) = f_0(x(k)) + B * u(k) y(k) = h(x(k), u(k)) = h_0(x(k)) + D * u(k) Args: u (np.ndarray): The input vector of shape `(m,)` at the current time step, where `m` is the number of inputs. w (np.ndarray): The measurement noise vector of shape `(p,)` at the current time step, where `p` is the number of outputs. Returns: np.ndarray: The output vector `y` of shape `(p,)` at the current time step, where `p` is the number of outputs. Note: This method updates the `x` attribute, which represents the internal state vector of the system, after simulation. """ # Compute output using the output function y = self.h(self.x, u) + w # Update state based on the dynamics function self.x = self.f(self.x, u) return y
[docs] def simulate(self, U: np.ndarray, W: np.ndarray, steps: int) -> np.ndarray: """ Simulate the nonlinear system over multiple time steps. Args: U (np.ndarray): An input matrix of shape `(steps, m)` where `steps` is the number of time steps and `m` is the number of inputs. W (np.ndarray): A noise matrix of shape `(steps, p)` where `steps` is the number of time steps and `p` is the number of outputs. steps (int): The number of simulation steps. Returns: np.ndarray: The output matrix `Y` of shape `(steps, p)` containing the simulated system outputs at each time step. Note: This method updates the `x` attribute, which represents the internal state vector of the system, after each simulation step. """ # Initialize system output Y = np.zeros((steps, self.p)) for k in range(steps): Y[k, :] = self.simulate_step(U[k, :], W[k, :]) return Y