Source code for opengnc.classical_control.pid

"""
Generic PID controller implementation with anti-windup logic.
"""

from __future__ import annotations


[docs] class PID: r""" Generic PID controller with anti-windup. Control Law: $u(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{de(t)}{dt}$ Parameters ---------- kp : float Proportional gain. ki : float Integral gain. kd : float Derivative gain. output_limits : tuple[float, float] | None, optional (min, max) saturation limits. anti_windup_method : str, optional Method (e.g., "clamping"). Default "clamping". """ def __init__( self, kp: float, ki: float, kd: float, output_limits: tuple[float, float] | None = None, anti_windup_method: str = "clamping", ) -> None: """Initialize the PID controller instance.""" self.kp = kp self.ki = ki self.kd = kd self.output_limits = output_limits self.anti_windup_method = anti_windup_method self.integral_error = 0.0 self.previous_error = 0.0 self.reset()
[docs] def reset(self) -> None: """Reset the internal integrator and error states to zero.""" self.integral_error = 0.0 self.previous_error = 0.0
[docs] def update(self, error: float, dt: float) -> float: """ Update the PID control calculation for a single time step. Parameters ---------- error : float The current error signal (setpoint - measured). dt : float Time step since the last update (s). Returns ------- float The computed control output signal. """ if dt <= 0: return 0.0 # Proportional term p_term = self.kp * error # Integral term calculation (tentative) self.integral_error += error * dt i_term = self.ki * self.integral_error # Derivative term using backward difference derivative = (error - self.previous_error) / dt d_term = self.kd * derivative # Calculate raw output output = p_term + i_term + d_term # Apply output limits and anti-windup if self.output_limits is not None: min_limit, max_limit = self.output_limits # Saturation check if output > max_limit: output_clamped = max_limit elif output < min_limit: output_clamped = min_limit else: output_clamped = output # Anti-windup (Clamping method) # If the output is saturated AND the error has the same sign as the # control signal (driving it further into saturation), stop integrating. if self.anti_windup_method == "clamping": if output != output_clamped: if (output > max_limit and error > 0) or (output < min_limit and error < 0): # Revert the integration step self.integral_error -= error * dt output = output_clamped self.previous_error = error return output