Source code for do_dpc.control_utils.pid

"""
Implements a discrete-time PID controller.

This module provides a `PIDController` class for computing PID control actions
and a `PIDGains` dataclass for storing PID gain parameters.
"""

from dataclasses import dataclass
from typing import List

import numpy as np


[docs] @dataclass class PIDGains: """ A dataclass to store PID gains. Attributes: Kp (float): Proportional gain. Ki (float): Integral gain. Kd (float): Derivative gain. """ Kp: float Ki: float Kd: float
[docs] class PIDController: r""" A discrete-time PID controller. This class implements a discrete-time PID (Proportional-Integral-Derivative) controller. It computes the control action based on the error between the desired setpoint and the current process variable, using the PID control law: .. math:: u(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{d}{dt} e(t) where: - :math:`u(t)` is the control output - :math:`e(t)` is the error between the setpoint and the process variable - :math:`K_p`, :math:`K_i`, and :math:`K_d` are the proportional, integral, and derivative gains, respectively. Attributes: gains (PIDGains): An instance of PIDGains containing the PID constants: `Kp`, `Ki`, and `Kd`. dt (float): Sampling time (time step between updates). alpha (float): Smoothing factor for the derivative term (low-pass filter for noise reduction). integral (float): The accumulated integral term. prev_error (float): The previous error used for the derivative calculation. filtered_derivative (float): The filtered derivative term. Args: gains (PIDGains): An instance of PIDGains containing `Kp`, `Ki`, and `Kd`. dt (float): Sampling time. alpha (float, optional): Smoothing factor for the derivative term. Defaults to 0.1. Raises: ValueError: If `dt` is not positive. """ def __init__(self, gains: PIDGains, dt: float, alpha: float = 0.1): """ Initializes the discrete-time PID controller. Args: gains (PIDGains): An instance of PIDGains containing Kp, Ki, and Kd. dt (float): Sampling time. alpha (float, optional): Smoothing factor for the derivative term. Defaults to 0.1. Raises: ValueError: If dt is not positive. """ if dt <= 0: raise ValueError("Time step dt must be positive.") self.gains = gains self.dt = dt self.alpha = alpha self.integral = 0.0 self.prev_error = 0.0 self.filtered_derivative = 0.0 def compute(self, error: float) -> float: """ Computes the discrete PID control output. Args: error (float): The error signal (difference between setpoint and measurement). Returns: float: The computed control signal. """ P = self.gains.Kp * error self.integral += error * self.dt I = self.gains.Ki * self.integral raw_derivative = (error - self.prev_error) / self.dt self.filtered_derivative = self.alpha * raw_derivative + (1 - self.alpha) * self.filtered_derivative D = self.gains.Kd * self.filtered_derivative self.prev_error = error return P + I + D def compute_with_derivative(self, error: float, error_derivative: float) -> float: """ Computes the discrete PID control output with an externally provided error derivative. Args: error (float): The error signal (difference between setpoint and measurement). error_derivative (float): The derivative of the error signal. Returns: float: The computed control signal. """ P = self.gains.Kp * error self.integral += error * self.dt I = self.gains.Ki * self.integral D = self.gains.Kd * error_derivative self.prev_error = error return P + I + D
[docs] class MIMOPIDController: """ A class to manage multiple PID controllers for a decoupled MIMO system. This class holds multiple PIDController instances and computes the control actions for each one. Attributes: pid_controllers (List[PIDController]): A list of PIDController instances. """ def __init__(self, pid_controllers: List[PIDController]): """ Initializes the MIMO_PIDController with a list of PIDController instances. Args: pid_controllers (List[PIDController]): A list of PIDController instances. """ self.pid_controllers = pid_controllers def compute(self, errors: np.ndarray) -> np.ndarray: """ Computes the control actions for all PID controllers. Args: errors (np.ndarray): An array of error signals (differences between setpoints and measurements). Returns: np.ndarray: An array of computed control signals. """ return np.array([pid.compute(error) for pid, error in zip(self.pid_controllers, errors)]) def compute_with_derivative(self, errors: np.ndarray, error_derivatives: np.ndarray) -> np.ndarray: """ Computes the control actions for all PID controllers using provided error derivatives. Args: errors (np.ndarray): An array of error signals (differences between setpoints and measurements). error_derivatives (np.ndarray): An array of error derivatives. Returns: np.ndarray: An array of computed control signals. """ return np.array( [ pid.compute_with_derivative(error, error_derivative) for pid, error, error_derivative in zip(self.pid_controllers, errors, error_derivatives) ] )