use crate::error::ControlError;
use crate::scalar::Numeric;
use crate::signal_processing::OnePoleLowPass;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pid<T: Numeric = f64> {
proportional_gain: T,
integral_gain: T,
derivative_gain: T,
dt: T,
output_minimum: T,
output_maximum: T,
integral: T,
derivative_filter: OnePoleLowPass<T>,
previous_measurement: T,
previous_error: T,
has_previous_measurement: bool,
}
impl<T: Numeric> Pid<T> {
pub fn new(
proportional_gain: T,
integral_gain: T,
derivative_gain: T,
dt: T,
) -> Result<Self, ControlError> {
if !proportional_gain.is_finite()
|| !integral_gain.is_finite()
|| !derivative_gain.is_finite()
|| !dt.is_finite()
{
return Err(ControlError::NonFinite);
}
if dt <= T::ZERO {
return Err(ControlError::NonPositiveTimestep);
}
Ok(Self {
proportional_gain,
integral_gain,
derivative_gain,
dt,
output_minimum: T::NEG_INFINITY,
output_maximum: T::INFINITY,
integral: T::ZERO,
derivative_filter: OnePoleLowPass::new(T::ONE)?,
previous_measurement: T::ZERO,
previous_error: T::ZERO,
has_previous_measurement: false,
})
}
pub fn with_output_limits(mut self, minimum: T, maximum: T) -> Result<Self, ControlError> {
if minimum.is_nan() || maximum.is_nan() {
return Err(ControlError::NonFinite);
}
if minimum > maximum {
return Err(ControlError::InvalidOutputLimits);
}
self.output_minimum = minimum;
self.output_maximum = maximum;
Ok(self)
}
pub fn with_derivative_filter(mut self, smoothing: T) -> Result<Self, ControlError> {
self.derivative_filter = OnePoleLowPass::new(smoothing)?;
Ok(self)
}
pub fn set_gains(
&mut self,
proportional_gain: T,
integral_gain: T,
derivative_gain: T,
) -> Result<(), ControlError> {
if !proportional_gain.is_finite()
|| !integral_gain.is_finite()
|| !derivative_gain.is_finite()
{
return Err(ControlError::NonFinite);
}
if self.has_previous_measurement {
let filtered_derivative = self.derivative_filter.value();
self.integral = self.integral
+ (self.proportional_gain - proportional_gain) * self.previous_error
+ (self.derivative_gain - derivative_gain) * filtered_derivative;
}
self.proportional_gain = proportional_gain;
self.integral_gain = integral_gain;
self.derivative_gain = derivative_gain;
Ok(())
}
pub fn resume_from(
&mut self,
output: T,
setpoint: T,
measurement: T,
) -> Result<(), ControlError> {
if !output.is_finite() || !setpoint.is_finite() || !measurement.is_finite() {
return Err(ControlError::NonFinite);
}
let error = setpoint - measurement;
self.integral =
output - self.proportional_gain * error - self.integral_gain * error * self.dt;
self.derivative_filter.reset();
self.previous_measurement = measurement;
self.previous_error = error;
self.has_previous_measurement = true;
Ok(())
}
#[must_use]
pub fn update(&mut self, setpoint: T, measurement: T) -> T {
let error = setpoint - measurement;
let proportional_term = self.proportional_gain * error;
let raw_derivative = if self.has_previous_measurement {
(self.previous_measurement - measurement) / self.dt
} else {
T::ZERO
};
let derivative_term = self.derivative_gain * self.derivative_filter.filter(raw_derivative);
self.previous_measurement = measurement;
self.previous_error = error;
self.has_previous_measurement = true;
let candidate_integral = self.integral + self.integral_gain * error * self.dt;
let unsaturated = proportional_term + candidate_integral + derivative_term;
let output = unsaturated
.max(self.output_minimum)
.min(self.output_maximum);
let saturated_high = unsaturated > self.output_maximum;
let saturated_low = unsaturated < self.output_minimum;
let pushing_deeper =
(saturated_high && error > T::ZERO) || (saturated_low && error < T::ZERO);
if !pushing_deeper {
self.integral = candidate_integral;
}
output
}
pub fn reset(&mut self) {
self.integral = T::ZERO;
self.has_previous_measurement = false;
self.previous_measurement = T::ZERO;
self.previous_error = T::ZERO;
self.derivative_filter.reset();
}
#[inline]
#[must_use]
pub fn integral(&self) -> T {
self.integral
}
}