pid/
lib.rs

1//! A proportional-integral-derivative (PID) controller library.
2//!
3//! See [Pid] for the adjustable controller itself, as well as [ControlOutput] for the outputs and weights which you can use after setting up your controller. Follow the complete example below to setup your first controller!
4//!
5//! # Example
6//!
7//! ```rust
8//! use pid::Pid;
9//!
10//! // Create a new proportional-only PID controller with a setpoint of 15
11//! let mut pid = Pid::new(15.0, 100.0);
12//! pid.p(10.0, 100.0);
13//!
14//! // Input a measurement with an error of 5.0 from our setpoint
15//! let output = pid.next_control_output(10.0);
16//!
17//! // Show that the error is correct by multiplying by our kp
18//! assert_eq!(output.output, 50.0); // <--
19//! assert_eq!(output.p, 50.0);
20//!
21//! // It won't change on repeat; the controller is proportional-only
22//! let output = pid.next_control_output(10.0);
23//! assert_eq!(output.output, 50.0); // <--
24//! assert_eq!(output.p, 50.0);
25//!
26//! // Add a new integral term to the controller and input again
27//! pid.i(1.0, 100.0);
28//! let output = pid.next_control_output(10.0);
29//!
30//! // Now that the integral makes the controller stateful, it will change
31//! assert_eq!(output.output, 55.0); // <--
32//! assert_eq!(output.p, 50.0);
33//! assert_eq!(output.i, 5.0);
34//!
35//! // Add our final derivative term and match our setpoint target
36//! pid.d(2.0, 100.0);
37//! let output = pid.next_control_output(15.0);
38//!
39//! // The output will now say to go down due to the derivative
40//! assert_eq!(output.output, -5.0); // <--
41//! assert_eq!(output.p, 0.0);
42//! assert_eq!(output.i, 5.0);
43//! assert_eq!(output.d, -10.0);
44//! ```
45#![no_std]
46
47use num_traits::float::FloatCore;
48#[cfg(feature = "serde")]
49use serde::{Deserialize, Serialize};
50
51/// Adjustable proportional-integral-derivative (PID) controller.
52///
53/// # Examples
54///
55/// This controller provides a builder pattern interface which allows you to pick-and-choose which PID inputs you'd like to use during operation. Here's what a basic proportional-only controller could look like:
56///
57/// ```rust
58/// use pid::Pid;
59///
60/// // Create limited controller
61/// let mut p_controller = Pid::new(15.0, 100.0);
62/// p_controller.p(10.0, 100.0);
63///
64/// // Get first output
65/// let p_output = p_controller.next_control_output(400.0);
66/// ```
67///
68/// This controller would give you set a proportional controller to `10.0` with a target of `15.0` and an output limit of `100.0` per [output](Self::next_control_output) iteration. The same controller with a full PID system built in looks like:
69///
70/// ```rust
71/// use pid::Pid;
72///
73/// // Create full PID controller
74/// let mut full_controller = Pid::new(15.0, 100.0);
75/// full_controller.p(10.0, 100.0).i(4.5, 100.0).d(0.25, 100.0);
76///
77/// // Get first output
78/// let full_output = full_controller.next_control_output(400.0);
79/// ```
80///
81/// This [`next_control_output`](Self::next_control_output) method is what's used to input new values into the controller to tell it what the current state of the system is. In the examples above it's only being used once, but realistically this will be a hot method. Please see [ControlOutput] for examples of how to handle these outputs; it's quite straight forward and mirrors the values of this structure in some ways.
82///
83/// The last item of note is that these [`p`](Self::p()), [`i`](Self::i()), and [`d`](Self::d()) methods can be used *during* operation which lets you add and/or modify these controller values if need be.
84#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
85#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
86pub struct Pid<T: FloatCore> {
87    /// Ideal setpoint to strive for.
88    pub setpoint: T,
89    /// Defines the overall output filter limit.
90    pub output_limit: T,
91    /// Proportional gain.
92    pub kp: T,
93    /// Integral gain.
94    pub ki: T,
95    /// Derivative gain.
96    pub kd: T,
97    /// Limiter for the proportional term: `-p_limit <= P <= p_limit`.
98    pub p_limit: T,
99    /// Limiter for the integral term: `-i_limit <= I <= i_limit`.
100    pub i_limit: T,
101    /// Limiter for the derivative term: `-d_limit <= D <= d_limit`.
102    pub d_limit: T,
103    /// Last calculated integral value if [Pid::ki] is used.
104    integral_term: T,
105    /// Previously found measurement whilst using the [Pid::next_control_output] method.
106    prev_measurement: Option<T>,
107}
108
109/// Output of [controller iterations](Pid::next_control_output) with weights
110///
111/// # Example
112///
113/// This structure is simple to use and features three weights: [p](Self::p), [i](Self::i), and [d](Self::d). These can be used to figure out how much each term from [Pid] contributed to the final [output](Self::output) value which should be taken as the final controller output for this iteration:
114///
115/// ```rust
116/// use pid::{Pid, ControlOutput};
117///
118/// // Setup controller
119/// let mut pid = Pid::new(15.0, 100.0);
120/// pid.p(10.0, 100.0).i(1.0, 100.0).d(2.0, 100.0);
121///
122/// // Input an example value and get a report for an output iteration
123/// let output = pid.next_control_output(26.2456);
124/// println!("P: {}\nI: {}\nD: {}\nFinal Output: {}", output.p, output.i, output.d, output.output);
125/// ```
126#[derive(Debug, PartialEq, Eq)]
127pub struct ControlOutput<T: FloatCore> {
128    /// Contribution of the P term to the output.
129    pub p: T,
130    /// Contribution of the I term to the output.
131    ///
132    /// This integral term is equal to `sum[error(t) * ki(t)] (for all t)`
133    pub i: T,
134    /// Contribution of the D term to the output.
135    pub d: T,
136    /// Output of the PID controller.
137    pub output: T,
138}
139
140impl<T> Pid<T>
141where
142    T: FloatCore,
143{
144    /// Creates a new controller with the target setpoint and the output limit
145    ///
146    /// To set your P, I, and D terms into this controller, please use the following builder methods:
147    /// - [Self::p()]: Proportional term setting
148    /// - [Self::i()]: Integral term setting
149    /// - [Self::d()]: Derivative term setting
150    pub fn new(setpoint: impl Into<T>, output_limit: impl Into<T>) -> Self {
151        Self {
152            setpoint: setpoint.into(),
153            output_limit: output_limit.into(),
154            kp: T::zero(),
155            ki: T::zero(),
156            kd: T::zero(),
157            p_limit: T::zero(),
158            i_limit: T::zero(),
159            d_limit: T::zero(),
160            integral_term: T::zero(),
161            prev_measurement: None,
162        }
163    }
164
165    /// Sets the [Self::p] term for this controller.
166    pub fn p(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
167        self.kp = gain.into();
168        self.p_limit = limit.into();
169        self
170    }
171
172    /// Sets the [Self::i] term for this controller.
173    pub fn i(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
174        self.ki = gain.into();
175        self.i_limit = limit.into();
176        self
177    }
178
179    /// Sets the [Self::d] term for this controller.
180    pub fn d(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
181        self.kd = gain.into();
182        self.d_limit = limit.into();
183        self
184    }
185
186    /// Sets the [Pid::setpoint] to target for this controller.
187    pub fn setpoint(&mut self, setpoint: impl Into<T>) -> &mut Self {
188        self.setpoint = setpoint.into();
189        self
190    }
191
192    /// Given a new measurement, calculates the next [control output](ControlOutput).
193    ///
194    /// # Panics
195    ///
196    /// - If a setpoint has not been set via `update_setpoint()`.
197    pub fn next_control_output(&mut self, measurement: T) -> ControlOutput<T> {
198        // Calculate the error between the ideal setpoint and the current
199        // measurement to compare against
200        let error = self.setpoint - measurement;
201
202        // Calculate the proportional term and limit to it's individual limit
203        let p_unbounded = error * self.kp;
204        let p = apply_limit(self.p_limit, p_unbounded);
205
206        // Mitigate output jumps when ki(t) != ki(t-1).
207        // While it's standard to use an error_integral that's a running sum of
208        // just the error (no ki), because we support ki changing dynamically,
209        // we store the entire term so that we don't need to remember previous
210        // ki values.
211        self.integral_term = self.integral_term + error * self.ki;
212
213        // Mitigate integral windup: Don't want to keep building up error
214        // beyond what i_limit will allow.
215        self.integral_term = apply_limit(self.i_limit, self.integral_term);
216
217        // Mitigate derivative kick: Use the derivative of the measurement
218        // rather than the derivative of the error.
219        let d_unbounded = -match self.prev_measurement.as_ref() {
220            Some(prev_measurement) => measurement - *prev_measurement,
221            None => T::zero(),
222        } * self.kd;
223        self.prev_measurement = Some(measurement);
224        let d = apply_limit(self.d_limit, d_unbounded);
225
226        // Calculate the final output by adding together the PID terms, then
227        // apply the final defined output limit
228        let output = p + self.integral_term + d;
229        let output = apply_limit(self.output_limit, output);
230
231        // Return the individual term's contributions and the final output
232        ControlOutput {
233            p,
234            i: self.integral_term,
235            d,
236            output: output,
237        }
238    }
239
240    /// Resets the integral term back to zero, this may drastically change the
241    /// control output.
242    pub fn reset_integral_term(&mut self) {
243        self.integral_term = T::zero();
244    }
245}
246
247/// Saturating the input `value` according the absolute `limit` (`-limit <= output <= limit`).
248fn apply_limit<T: FloatCore>(limit: T, value: T) -> T {
249    limit.min(value.abs()) * value.signum()
250}
251
252#[cfg(test)]
253mod tests {
254    use super::Pid;
255    use crate::ControlOutput;
256
257    /// Proportional-only controller operation and limits
258    #[test]
259    fn proportional() {
260        let mut pid = Pid::new(10.0, 100.0);
261        pid.p(2.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
262        assert_eq!(pid.setpoint, 10.0);
263
264        // Test simple proportional
265        assert_eq!(pid.next_control_output(0.0).output, 20.0);
266
267        // Test proportional limit
268        pid.p_limit = 10.0;
269        assert_eq!(pid.next_control_output(0.0).output, 10.0);
270    }
271
272    /// Derivative-only controller operation and limits
273    #[test]
274    fn derivative() {
275        let mut pid = Pid::new(10.0, 100.0);
276        pid.p(0.0, 100.0).i(0.0, 100.0).d(2.0, 100.0);
277
278        // Test that there's no derivative since it's the first measurement
279        assert_eq!(pid.next_control_output(0.0).output, 0.0);
280
281        // Test that there's now a derivative
282        assert_eq!(pid.next_control_output(5.0).output, -10.0);
283
284        // Test derivative limit
285        pid.d_limit = 5.0;
286        assert_eq!(pid.next_control_output(10.0).output, -5.0);
287    }
288
289    /// Integral-only controller operation and limits
290    #[test]
291    fn integral() {
292        let mut pid = Pid::new(10.0, 100.0);
293        pid.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
294
295        // Test basic integration
296        assert_eq!(pid.next_control_output(0.0).output, 20.0);
297        assert_eq!(pid.next_control_output(0.0).output, 40.0);
298        assert_eq!(pid.next_control_output(5.0).output, 50.0);
299
300        // Test limit
301        pid.i_limit = 50.0;
302        assert_eq!(pid.next_control_output(5.0).output, 50.0);
303        // Test that limit doesn't impede reversal of error integral
304        assert_eq!(pid.next_control_output(15.0).output, 40.0);
305
306        // Test that error integral accumulates negative values
307        let mut pid2 = Pid::new(-10.0, 100.0);
308        pid2.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
309        assert_eq!(pid2.next_control_output(0.0).output, -20.0);
310        assert_eq!(pid2.next_control_output(0.0).output, -40.0);
311
312        pid2.i_limit = 50.0;
313        assert_eq!(pid2.next_control_output(-5.0).output, -50.0);
314        // Test that limit doesn't impede reversal of error integral
315        assert_eq!(pid2.next_control_output(-15.0).output, -40.0);
316    }
317
318    /// Checks that a full PID controller's limits work properly through multiple output iterations
319    #[test]
320    fn output_limit() {
321        let mut pid = Pid::new(10.0, 1.0);
322        pid.p(1.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
323
324        let out = pid.next_control_output(0.0);
325        assert_eq!(out.p, 10.0); // 1.0 * 10.0
326        assert_eq!(out.output, 1.0);
327
328        let out = pid.next_control_output(20.0);
329        assert_eq!(out.p, -10.0); // 1.0 * (10.0 - 20.0)
330        assert_eq!(out.output, -1.0);
331    }
332
333    /// Combined PID operation
334    #[test]
335    fn pid() {
336        let mut pid = Pid::new(10.0, 100.0);
337        pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
338
339        let out = pid.next_control_output(0.0);
340        assert_eq!(out.p, 10.0); // 1.0 * 10.0
341        assert_eq!(out.i, 1.0); // 0.1 * 10.0
342        assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
343        assert_eq!(out.output, 11.0);
344
345        let out = pid.next_control_output(5.0);
346        assert_eq!(out.p, 5.0); // 1.0 * 5.0
347        assert_eq!(out.i, 1.5); // 0.1 * (10.0 + 5.0)
348        assert_eq!(out.d, -5.0); // -(1.0 * 5.0)
349        assert_eq!(out.output, 1.5);
350
351        let out = pid.next_control_output(11.0);
352        assert_eq!(out.p, -1.0); // 1.0 * -1.0
353        assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1)
354        assert_eq!(out.d, -6.0); // -(1.0 * 6.0)
355        assert_eq!(out.output, -5.6);
356
357        let out = pid.next_control_output(10.0);
358        assert_eq!(out.p, 0.0); // 1.0 * 0.0
359        assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1.0 + 0.0)
360        assert_eq!(out.d, 1.0); // -(1.0 * -1.0)
361        assert_eq!(out.output, 2.4);
362    }
363
364    /// Full PID operation with mixed f32/f64 checking to make sure they're equal
365    #[test]
366    fn f32_and_f64() {
367        let mut pid32 = Pid::new(10.0f32, 100.0);
368        pid32.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
369
370        let mut pid64 = Pid::new(10.0, 100.0f64);
371        pid64.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
372
373        assert_eq!(
374            pid32.next_control_output(0.0).output,
375            pid64.next_control_output(0.0).output as f32
376        );
377        assert_eq!(
378            pid32.next_control_output(0.0).output as f64,
379            pid64.next_control_output(0.0).output
380        );
381    }
382
383    /// See if the controller can properly target to the setpoint after 2 output iterations
384    #[test]
385    fn setpoint() {
386        let mut pid = Pid::new(10.0, 100.0);
387        pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
388
389        let out = pid.next_control_output(0.0);
390        assert_eq!(out.p, 10.0); // 1.0 * 10.0
391        assert_eq!(out.i, 1.0); // 0.1 * 10.0
392        assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
393        assert_eq!(out.output, 11.0);
394
395        pid.setpoint(0.0);
396
397        assert_eq!(
398            pid.next_control_output(0.0),
399            ControlOutput {
400                p: 0.0,
401                i: 1.0,
402                d: -0.0,
403                output: 1.0
404            }
405        );
406    }
407}