Skip to content

Commit 3b03e67

Browse files
usbalbinOwez
authored andcommitted
Allow signed integers to be used.
This commit relaxes the bounds of the generics used for the controller so signed integers (e.g. i8, i64, etc.) can be used as well as floats. This has been done by adding a new trait which also allows user-defined numbers to be easily implemented. Resolves: #12
1 parent ee0fe7a commit 3b03e67

File tree

1 file changed

+60
-22
lines changed

1 file changed

+60
-22
lines changed

src/lib.rs

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,27 @@
4444
//! ```
4545
#![no_std]
4646

47-
use num_traits::float::FloatCore;
4847
#[cfg(feature = "serde")]
4948
use serde::{Deserialize, Serialize};
5049

50+
/// A trait for any numeric type usable in the PID controller
51+
///
52+
/// This trait is automatically implemented for all types that satisfy `PartialOrd + num_traits::Signed + Copy`. This includes all of the signed float types and builtin integer except for [isize]:
53+
/// - [i8]
54+
/// - [i16]
55+
/// - [i32]
56+
/// - [i64]
57+
/// - [i128]
58+
/// - [f32]
59+
/// - [f64]
60+
///
61+
/// As well as any user type that matches the requirements
62+
pub trait Number: PartialOrd + num_traits::Signed + Copy {}
63+
64+
// Implement `Number` for all types that
65+
// satisfy `PartialOrd + num_traits::Signed + Copy`.
66+
impl<T: PartialOrd + num_traits::Signed + Copy> Number for T {}
67+
5168
/// Adjustable proportional-integral-derivative (PID) controller.
5269
///
5370
/// # Examples
@@ -81,9 +98,13 @@ use serde::{Deserialize, Serialize};
8198
/// 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.
8299
///
83100
/// 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)]
101+
///
102+
/// # Type Warning
103+
///
104+
/// [Number] is abstract and can be used with anything from a [i32] to an [i128] (as well as user-defined types). Because of this, very small types might overflow during calculation in [`next_control_output`](Self::next_control_output). You probably don't want to use [i8] or user-defined types around that size so keep that in mind when designing your controller.
105+
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
85106
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
86-
pub struct Pid<T: FloatCore> {
107+
pub struct Pid<T: Number> {
87108
/// Ideal setpoint to strive for.
88109
pub setpoint: T,
89110
/// Defines the overall output filter limit.
@@ -124,7 +145,7 @@ pub struct Pid<T: FloatCore> {
124145
/// println!("P: {}\nI: {}\nD: {}\nFinal Output: {}", output.p, output.i, output.d, output.output);
125146
/// ```
126147
#[derive(Debug, PartialEq, Eq)]
127-
pub struct ControlOutput<T: FloatCore> {
148+
pub struct ControlOutput<T: Number> {
128149
/// Contribution of the P term to the output.
129150
pub p: T,
130151
/// Contribution of the I term to the output.
@@ -139,7 +160,7 @@ pub struct ControlOutput<T: FloatCore> {
139160

140161
impl<T> Pid<T>
141162
where
142-
T: FloatCore,
163+
T: Number,
143164
{
144165
/// Creates a new controller with the target setpoint and the output limit
145166
///
@@ -245,8 +266,8 @@ where
245266
}
246267

247268
/// Saturating the input `value` according the absolute `limit` (`-limit <= output <= limit`).
248-
fn apply_limit<T: FloatCore>(limit: T, value: T) -> T {
249-
limit.min(value.abs()) * value.signum()
269+
fn apply_limit<T: Number>(limit: T, value: T) -> T {
270+
num_traits::clamp(value, -limit, limit)
250271
}
251272

252273
#[cfg(test)]
@@ -361,23 +382,40 @@ mod tests {
361382
assert_eq!(out.output, 2.4);
362383
}
363384

364-
/// Full PID operation with mixed f32/f64 checking to make sure they're equal
385+
// NOTE: use for new test in future: /// Full PID operation with mixed float checking to make sure they're equal
386+
/// PID operation with zero'd values, checking to see if different floats equal each other
365387
#[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);
388+
fn floats_zeros() {
389+
let mut pid_f32 = Pid::new(10.0f32, 100.0);
390+
pid_f32.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
391+
392+
let mut pid_f64 = Pid::new(10.0, 100.0f64);
393+
pid_f64.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
394+
395+
for _ in 0..5 {
396+
assert_eq!(
397+
pid_f32.next_control_output(0.0).output,
398+
pid_f64.next_control_output(0.0).output as f32
399+
);
400+
}
401+
}
372402

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-
);
403+
// NOTE: use for new test in future: /// Full PID operation with mixed signed integer checking to make sure they're equal
404+
/// PID operation with zero'd values, checking to see if different floats equal each other
405+
#[test]
406+
fn signed_integers_zeros() {
407+
let mut pid_i8 = Pid::new(10i8, 100);
408+
pid_i8.p(0, 100).i(0, 100).d(0, 100);
409+
410+
let mut pid_i32 = Pid::new(10i32, 100);
411+
pid_i32.p(0, 100).i(0, 100).d(0, 100);
412+
413+
for _ in 0..5 {
414+
assert_eq!(
415+
pid_i32.next_control_output(0).output,
416+
pid_i8.next_control_output(0i8).output as i32
417+
);
418+
}
381419
}
382420

383421
/// See if the controller can properly target to the setpoint after 2 output iterations

0 commit comments

Comments
 (0)