mod fixtures;
use fixtures::test_pid;
use discrete_pid::pid::{IntegratorActivity, PidConfig, PidConfigBuilder, PidConfigError};
use discrete_pid::time::Millis;
use std::time::Duration;
mod test_pid_config {
use core::f64;
use super::test_pid::make_controller;
use super::*;
const NEW_KP: f64 = 10.0;
const INVALID_KP_VALUES: &[f64; 4] = &[0.0, -1.0, f64::INFINITY, f64::NAN];
#[test]
fn test_get_and_set_kp() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.kp(), 1.0);
assert!(config.set_kp(NEW_KP).is_ok());
assert_eq!(config.kp(), NEW_KP);
for it in INVALID_KP_VALUES {
assert_eq!(
config.set_kp(*it),
Err(PidConfigError::InvalidProportionalGain)
);
assert_eq!(config.kp(), NEW_KP);
}
}
#[test]
fn test_build_kp() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config.set_kp(NEW_KP).is_ok());
let built_config = PidConfigBuilder::default().kp(NEW_KP).build();
assert!(built_config.is_ok());
assert_eq!(built_config.unwrap().kp(), default_init_config.kp());
for it in INVALID_KP_VALUES {
assert_eq!(
PidConfigBuilder::default().kp(*it).build().map(|_| ()),
Err(PidConfigError::InvalidProportionalGain)
);
}
}
const NEW_KI: f64 = 10.0;
const INVALID_KI_VALUES: &[f64; 3] = &[-1.0, f64::INFINITY, f64::NAN];
#[test]
fn test_get_and_set_ki() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.ki(), 1.0);
assert!(config.set_ki(NEW_KI).is_ok());
assert_eq!(config.ki(), NEW_KI);
assert!(config.set_sample_time(Duration::from_millis(150)).is_ok());
assert_eq!(config.ki(), NEW_KI);
for it in INVALID_KI_VALUES {
assert_eq!(config.set_ki(*it), Err(PidConfigError::InvalidIntegralGain));
assert_eq!(config.ki(), NEW_KI);
}
assert!(config.set_ki(0.0).is_ok());
assert_eq!(config.ki(), 0.0);
}
#[test]
fn test_build_ki() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config.set_ki(NEW_KI).is_ok());
let built_config = PidConfigBuilder::default().ki(NEW_KI).build();
assert!(built_config.is_ok());
assert_eq!(built_config.unwrap().ki(), default_init_config.ki());
let built_config_2 = PidConfigBuilder::default()
.ki(10.0)
.sample_time(Duration::from_millis(200))
.build();
assert!(built_config_2.is_ok());
assert_eq!(built_config_2.unwrap().ki(), default_init_config.ki());
for it in INVALID_KI_VALUES {
assert_eq!(
PidConfigBuilder::default().ki(*it).build().map(|_| ()),
Err(PidConfigError::InvalidIntegralGain)
);
}
}
const NEW_KD: f64 = 10.0;
const INVALID_KD_VALUES: &[f64; 3] = &[-1.0, f64::INFINITY, f64::NAN];
#[test]
fn test_get_and_set_kd() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.kd(), 0.0);
assert!(config.set_kd(NEW_KD).is_ok());
assert_eq!(config.kd(), NEW_KD);
assert!(config.set_sample_time(Duration::from_millis(150)).is_ok());
assert_eq!(config.kd(), NEW_KD);
for it in INVALID_KD_VALUES {
assert_eq!(
config.set_kd(*it),
Err(PidConfigError::InvalidDerivativeGain)
);
assert_eq!(config.kd(), NEW_KD);
}
assert!(config.set_kd(0.0).is_ok());
assert_eq!(config.kd(), 0.0);
}
#[test]
fn test_build_kd() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config.set_kd(NEW_KD).is_ok());
let built_config = PidConfigBuilder::default().kd(NEW_KD).build();
assert!(built_config.is_ok());
assert_eq!(built_config.unwrap().kd(), default_init_config.kd());
let built_config_2 = PidConfigBuilder::default()
.kd(10.0)
.sample_time(Duration::from_millis(200))
.build();
assert!(built_config_2.is_ok());
assert_eq!(built_config_2.unwrap().kd(), default_init_config.kd());
for it in INVALID_KD_VALUES {
assert_eq!(
PidConfigBuilder::default().kd(*it).build().map(|_| ()),
Err(PidConfigError::InvalidDerivativeGain)
);
}
}
const NEW_TC: f64 = 10.0;
const INVALID_FILTER_TC_VALUES: &[f64; 4] = &[-1.0, 0.0, f64::NAN, f64::INFINITY];
#[test]
fn test_get_and_set_filter_tc() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.filter_tc(), 0.01);
assert!(config.set_filter_tc(NEW_TC).is_ok());
assert_eq!(config.filter_tc(), NEW_TC);
for it in INVALID_FILTER_TC_VALUES {
assert_eq!(
config.set_filter_tc(*it),
Err(PidConfigError::InvalidFilterTimeConstant)
);
assert_eq!(config.filter_tc(), NEW_TC);
}
}
#[test]
fn test_build_filter_tc() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config.set_filter_tc(NEW_TC).is_ok());
let built_config = PidConfigBuilder::default().filter_tc(NEW_TC).build();
assert!(built_config.is_ok());
assert_eq!(
built_config.unwrap().filter_tc(),
default_init_config.filter_tc()
);
for it in INVALID_FILTER_TC_VALUES {
assert_eq!(
PidConfigBuilder::default()
.filter_tc(*it)
.build()
.map(|_| ()),
Err(PidConfigError::InvalidFilterTimeConstant)
);
}
}
const NEW_SAMPLE_TIME: Duration = Duration::from_millis(100);
const INVALID_SAMPLE_TIME_VALUES: &[Duration; 2] = &[Duration::ZERO, Duration::MAX];
#[test]
fn test_get_and_set_sample_time() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.sample_time(), Duration::from_millis(10));
let gains = (config.kp(), config.ki(), config.kd());
assert!(config.set_sample_time(NEW_SAMPLE_TIME).is_ok());
assert_eq!(config.sample_time(), NEW_SAMPLE_TIME);
let gains_round_trip = (config.kp(), config.ki(), config.kd());
assert_eq!(gains, gains_round_trip);
for it in INVALID_SAMPLE_TIME_VALUES {
assert_eq!(
config.set_sample_time(*it),
Err(PidConfigError::InvalidSampleTime)
);
assert_eq!(config.sample_time(), NEW_SAMPLE_TIME);
}
}
#[test]
fn test_build_sample_time() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config.set_sample_time(NEW_SAMPLE_TIME).is_ok());
let built_config = PidConfigBuilder::<f64>::default()
.sample_time(NEW_SAMPLE_TIME)
.build();
assert!(built_config.is_ok());
assert_eq!(
built_config.unwrap().sample_time(),
default_init_config.sample_time()
);
for it in INVALID_SAMPLE_TIME_VALUES {
assert_eq!(
PidConfigBuilder::<f64>::default()
.sample_time(*it)
.build()
.map(|_| ()),
Err(PidConfigError::InvalidSampleTime)
);
}
}
const NEW_OUTPUT_MIN: f64 = -10.0;
const NEW_OUTPUT_MAX: f64 = 10.0;
const INVALID_OUTPUT_LIMITS: &[(f64, f64); 5] = &[
(2.0, -2.0),
(0.0, 0.0),
(f64::NAN, 0.0),
(0.0, f64::NAN),
(f64::NAN, f64::NAN),
];
#[test]
fn test_get_and_set_output_limits() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert_eq!(config.output_min(), -f64::INFINITY);
assert_eq!(config.output_max(), f64::INFINITY);
assert!(config
.set_output_limits(NEW_OUTPUT_MIN, NEW_OUTPUT_MAX)
.is_ok());
assert_eq!(config.output_min(), NEW_OUTPUT_MIN);
assert_eq!(config.output_max(), NEW_OUTPUT_MAX);
for (lb, ub) in INVALID_OUTPUT_LIMITS {
assert_eq!(
config.set_output_limits(*lb, *ub),
Err(PidConfigError::InvalidOutputLimits)
);
assert_eq!(config.output_min(), NEW_OUTPUT_MIN);
assert_eq!(config.output_max(), NEW_OUTPUT_MAX);
}
}
#[test]
fn test_build_output_limits() {
let mut default_init_config = PidConfig::<f64>::default();
assert!(default_init_config
.set_output_limits(NEW_OUTPUT_MIN, NEW_OUTPUT_MAX)
.is_ok());
let built_config = PidConfigBuilder::default()
.output_limits(NEW_OUTPUT_MIN, NEW_OUTPUT_MAX)
.build();
assert!(built_config.is_ok());
assert_eq!(
built_config.unwrap().output_min(),
default_init_config.output_min()
);
assert_eq!(
built_config.unwrap().output_max(),
default_init_config.output_max()
);
for (lb, ub) in INVALID_OUTPUT_LIMITS {
assert_eq!(
PidConfigBuilder::default()
.output_limits(*lb, *ub)
.build()
.map(|_| ()),
Err(PidConfigError::InvalidOutputLimits)
);
}
}
#[test]
fn test_get_and_set_flags() {
let (mut pid, _) = make_controller();
let config = pid.config_mut();
assert!(!config.use_derivative_on_measurement());
assert!(!config.use_strict_causal_integrator());
config.set_use_derivative_on_measurement(true);
config.set_use_strict_causal_integrator(true);
assert!(config.use_derivative_on_measurement());
assert!(config.use_strict_causal_integrator());
}
#[test]
fn test_build_flags() {
let mut default_init_config = PidConfig::<f64>::default();
default_init_config.set_use_derivative_on_measurement(true);
default_init_config.set_use_strict_causal_integrator(true);
let built_config = PidConfigBuilder::<f64>::default()
.use_derivative_on_measurement(true)
.use_strict_causal_integrator(true)
.build();
assert!(built_config.is_ok());
assert_eq!(
built_config.unwrap().use_derivative_on_measurement(),
default_init_config.use_derivative_on_measurement()
);
assert_eq!(
built_config.unwrap().use_strict_causal_integrator(),
default_init_config.use_strict_causal_integrator()
);
}
}
mod test_pid_qualitative_performance {
use discrete_pid::pid::{FuncPidController, PidContext};
use super::test_pid::make_controller;
use super::*;
pub fn get_next_timestamp(
pid: &FuncPidController<f64>,
ctx: &PidContext<Millis, f64>,
) -> Millis {
let now = ctx.last_time().unwrap_or(Millis(0));
now + pid.config().sample_time()
}
mod p_control {
use super::*;
#[test]
fn test_pure_proportional_control() {
let (mut pid, ctx) = make_controller();
let config = pid.config_mut();
assert!(config.set_ki(0.0).is_ok());
assert!(config.set_kd(0.0).is_ok());
let (_, ctx) = pid.compute(ctx, 0.5, 1.0, get_next_timestamp(&pid, &ctx), None);
let output = ctx.output();
assert_eq!(output, 0.5); }
}
mod i_control {
use super::*;
const N_STEPS: usize = 5;
const BASE_ERROR: f64 = 10.0;
#[test]
fn test_integral_accumulation() {
let (pid, mut ctx) = make_controller();
let mut output;
let mut outputs = vec![];
for _ in 0..10 {
(output, ctx) = pid.compute(ctx, 0.0, 1.0, get_next_timestamp(&pid, &ctx), None);
outputs.push(output);
}
assert!(outputs.windows(2).all(|w| w[1] >= w[0])); }
#[test]
fn test_integral_windup_and_recovery() {
let (mut pid, mut ctx) = make_controller();
let mut output: f64;
let limit: f64 =
BASE_ERROR * (1.0 + N_STEPS as f64 * pid.config().sample_time().as_secs_f64());
assert!(pid.config_mut().set_output_limits(-limit, limit).is_ok());
for i in 0..10 {
(output, ctx) =
pid.compute(ctx, 0.0, BASE_ERROR, get_next_timestamp(&pid, &ctx), None);
if i >= N_STEPS - 1 {
assert_eq!(output, limit);
} else {
assert!(output < limit);
}
}
(output, _) = pid.compute(ctx, 0.0, -0.1, get_next_timestamp(&pid, &ctx), None);
assert!(output < limit, "Expected reversal due to anti-windup");
}
#[test]
fn test_integral_deactivation_resets_iterm() {
let (mut pid, mut ctx) = make_controller();
let mut output: f64;
let limit: f64 =
BASE_ERROR * (1.0 + N_STEPS as f64 * pid.config().sample_time().as_secs_f64());
assert!(pid.config_mut().set_output_limits(-limit, limit).is_ok());
const N_CYCLES: usize = 2;
for i in 0..(N_CYCLES * N_STEPS - 1) {
if i == N_STEPS - 1 {
ctx.set_integrator_activity(IntegratorActivity::Inactive);
assert!(ctx.integrator_activity() == IntegratorActivity::Inactive);
}
(output, ctx) =
pid.compute(ctx, 0.0, BASE_ERROR, get_next_timestamp(&pid, &ctx), None);
if i == N_STEPS - 1 {
ctx.set_integrator_activity(IntegratorActivity::Active);
assert!(ctx.integrator_activity() == IntegratorActivity::Active);
}
assert!(output < limit);
}
}
#[test]
fn test_hold_and_inactivate_integrator() {
let (pid, mut ctx) = make_controller();
let mut output: f64;
(_, ctx) = pid.compute(ctx, 0.0, 8.0, get_next_timestamp(&pid, &ctx), None);
let error = 8.0;
let last_i_term = error * pid.config().ki() * pid.config().sample_time().as_secs_f64();
ctx.set_integrator_activity(IntegratorActivity::HoldIntegration);
const SETPOINTS: [f64; 5] = [-5.0, 1.0, 0.0, 1.0, 5.0];
for setpoint in SETPOINTS {
(output, ctx) =
pid.compute(ctx, 0.0, setpoint, get_next_timestamp(&pid, &ctx), None);
assert_eq!(output, setpoint + last_i_term);
}
}
}
mod d_control {
use super::*;
#[test]
fn test_derivative_boosting_and_damping() {
let (mut pid, mut ctx) = make_controller();
assert!(pid.config_mut().set_kd(1.0).is_ok());
(_, ctx) = pid.compute(ctx, 0.0, 5.0, get_next_timestamp(&pid, &ctx), None);
const NEW_SETPOINT: f64 = 10.0;
pid.config_mut().set_use_derivative_on_measurement(false);
let (output_no_derivative_on_meas, _) =
pid.compute(ctx, 1.0, NEW_SETPOINT, get_next_timestamp(&pid, &ctx), None);
pid.config_mut().set_use_derivative_on_measurement(true);
let (output_with_derivative_on_meas, _) =
pid.compute(ctx, 1.0, NEW_SETPOINT, get_next_timestamp(&pid, &ctx), None);
assert!(pid.config_mut().set_kd(0.0).is_ok());
let (output_no_derivative, _) =
pid.compute(ctx, 1.0, NEW_SETPOINT, get_next_timestamp(&pid, &ctx), None);
assert!(output_no_derivative_on_meas > output_no_derivative);
assert!(output_with_derivative_on_meas < output_no_derivative);
}
#[test]
fn test_derivative_kick_mitigation() {
let (mut pid, mut ctx) = make_controller();
assert!(pid.config_mut().set_kd(1.0).is_ok());
(_, ctx) = pid.compute(ctx, 0.0, 5.0, get_next_timestamp(&pid, &ctx), None);
const NEW_SETPOINT: f64 = 50.0;
pid.config_mut().set_use_derivative_on_measurement(false);
let (output_no_derivative_on_meas, _) =
pid.compute(ctx, 1.0, NEW_SETPOINT, get_next_timestamp(&pid, &ctx), None);
pid.config_mut().set_use_derivative_on_measurement(true);
let (output_with_derivative_on_meas, _) =
pid.compute(ctx, 1.0, NEW_SETPOINT, get_next_timestamp(&pid, &ctx), None);
assert!(output_with_derivative_on_meas < output_no_derivative_on_meas);
}
}
mod safety_and_lifecycle {
use super::*;
#[test]
fn test_result_queries() {
let (pid, mut ctx) = make_controller();
for (input, setpoint) in [
(0.0, 1.5),
(1.5, 1.0),
(0.2, -1.0),
(-1.0, 0.2),
(-1.0, -2.2),
(-2.3, -2.0),
] {
let expected: f64;
(expected, ctx) =
pid.compute(ctx, input, setpoint, get_next_timestamp(&pid, &ctx), None);
assert_eq!(ctx.output(), expected);
}
}
#[test]
fn test_noop_until_sample_time_elapsed() {
const N_STEPS: u64 = 10;
let (pid, mut ctx) = make_controller();
let mut output: f64;
let expected: f64;
(expected, ctx) = pid.compute(ctx, 0.0, 1.5, get_next_timestamp(&pid, &ctx), None);
let init_time = ctx.last_time().unwrap();
for i in 0..=N_STEPS {
let small = init_time
+ pid
.config()
.sample_time()
.mul_f64(i as f64 / N_STEPS as f64);
(output, ctx) = pid.compute(ctx, 0.0, 1.5, small, None);
if i < N_STEPS {
assert_eq!(output, expected);
} else {
assert_ne!(output, expected);
}
}
}
#[test]
fn test_output_within_limits() {
let (pid, mut ctx) = make_controller();
let mut output;
for _ in 0..10 {
(output, ctx) = pid.compute(ctx, 100.0, 0.0, get_next_timestamp(&pid, &ctx), None);
assert!(output >= pid.config().output_min());
assert!(output <= pid.config().output_max());
}
}
#[test]
fn test_activation_and_initialization() {
let (pid, mut ctx) = make_controller();
let mut output: f64;
(output, ctx) = pid.compute(ctx, 0.0, 1.5, get_next_timestamp(&pid, &ctx), None);
let last_output = output;
ctx.deactivate();
const SETPOINTS: [f64; 5] = [-5.0, 1.0, 0.0, 1.0, 5.0];
const INPUTS: [f64; 5] = [0.0, 1.0, 2.0, 3.0, 4.0];
for (input, setpoint) in INPUTS.into_iter().zip(SETPOINTS) {
(output, ctx) =
pid.compute(ctx, input, setpoint, get_next_timestamp(&pid, &ctx), None);
assert_eq!(output, last_output);
}
ctx.activate();
(output, ctx) = pid.compute(ctx, 5.0, 7.0, get_next_timestamp(&pid, &ctx), None);
assert!(ctx.is_initialized());
assert_ne!(output, last_output);
ctx.deactivate();
assert!(!ctx.is_active());
for _ in 0..2 {
ctx.activate();
assert!(ctx.is_active());
assert!(!ctx.is_initialized());
}
}
#[test]
fn test_deactivation_before_first_computation() {
let (pid, mut ctx) = make_controller();
let mut output;
ctx.deactivate();
(output, ctx) = pid.compute(ctx, 0.0, 1.5, get_next_timestamp(&pid, &ctx), None);
assert!(!ctx.is_active());
assert!(!ctx.is_initialized());
assert_eq!(output, 0.0);
ctx.activate();
(output, ctx) = pid.compute(ctx, 0.0, 1.5, get_next_timestamp(&pid, &ctx), None);
assert!(ctx.is_active());
assert!(ctx.is_initialized());
assert_eq!(output, 1.5 + 1.5 * pid.config().sample_time().as_secs_f64());
}
#[test]
fn test_initialized_start() {
const STEADY_STATE_MEASURED: f64 = 5.0;
const STEADY_STATE_DISTURBANCE: f64 = 1.5;
let ctx = PidContext::new(Millis(0), STEADY_STATE_MEASURED, STEADY_STATE_DISTURBANCE);
let pid = FuncPidController::new(PidConfig::default());
const EXPECTED_ERROR: f64 = 1.5;
let (output, _) = pid.compute(
ctx,
STEADY_STATE_MEASURED,
STEADY_STATE_MEASURED + EXPECTED_ERROR,
get_next_timestamp(&pid, &ctx),
None,
);
let expected_i_term = STEADY_STATE_DISTURBANCE
+ EXPECTED_ERROR * pid.config().sample_time().as_secs_f64();
assert_eq!(output, EXPECTED_ERROR + expected_i_term);
}
}
}
mod test_stateful_pid {
use discrete_pid::pid::PidController;
use super::test_pid::*;
use super::*;
pub fn get_next_timestamp_stateful(pid: &PidController<Millis, f64>) -> Millis {
pid.last_time().unwrap_or(Millis(0)) + pid.config().sample_time()
}
#[test]
fn test_noop_until_sample_time_elapsed() {
const N_STEPS: u64 = 10;
let mut pid = make_stateful_controller();
let mut output: f64;
let expected = pid.compute(0.0, 1.5, get_next_timestamp_stateful(&pid), None);
let init_time = pid.last_time().unwrap();
for i in 0..=N_STEPS {
let small = init_time
+ pid
.config()
.sample_time()
.mul_f64(i as f64 / N_STEPS as f64);
output = pid.compute(0.0, 1.5, small, None);
if i < N_STEPS {
assert_eq!(output, expected);
} else {
assert_ne!(output, expected);
}
}
}
#[test]
fn test_result_queries() {
let mut pid = make_stateful_controller();
for (input, setpoint) in [
(0.0, 1.5),
(1.5, 1.0),
(0.2, -1.0),
(-1.0, 0.2),
(-1.0, -2.2),
(-2.3, -2.0),
] {
let expected = pid.compute(input, setpoint, get_next_timestamp_stateful(&pid), None);
assert_eq!(pid.output(), expected);
}
}
#[test]
fn test_lazy_initialization_and_reset() {
let mut pid = make_stateful_controller();
assert!(!pid.is_initialized());
let timestamp = Millis(0);
let _ = pid.compute(0.0, 1.0, timestamp, None);
assert!(pid.is_initialized());
assert!(pid.is_active());
let expected = pid.output();
pid.deactivate();
assert!(!pid.is_active());
let result = pid.compute(0.0, 1.0, timestamp, None);
assert_eq!(result, expected);
for _ in 0..2 {
pid.activate();
assert!(pid.is_active());
assert!(!pid.is_initialized());
}
}
#[test]
fn test_integrator_control_flow() {
let mut pid = make_stateful_controller();
let base_error = 2.0;
let _ = pid.compute(0.0, base_error, Millis(0), None);
pid.set_integrator_activity(IntegratorActivity::HoldIntegration);
assert!(pid.integrator_activity() == IntegratorActivity::HoldIntegration);
const NUM_ITERS: usize = 10;
let held_output = (0..NUM_ITERS).fold(0.0, |_, _| {
pid.compute(0.0, base_error, get_next_timestamp_stateful(&pid), None)
});
pid.set_integrator_activity(IntegratorActivity::Inactive);
assert!(pid.integrator_activity() == IntegratorActivity::Inactive);
let inactive_output = (0..NUM_ITERS).fold(0.0, |_, _| {
pid.compute(0.0, base_error, get_next_timestamp_stateful(&pid), None)
});
pid.set_integrator_activity(IntegratorActivity::Active);
assert!(pid.integrator_activity() == IntegratorActivity::Active);
let active_output = (0..NUM_ITERS).fold(0.0, |_, _| {
pid.compute(0.0, base_error, get_next_timestamp_stateful(&pid), None)
});
pid.reset_integral();
let resetted_output = pid.compute(0.0, base_error, get_next_timestamp_stateful(&pid), None);
assert!(active_output > inactive_output, "Expected active output ({active_output}) to be greater than inactive output ({inactive_output})");
assert!(held_output > inactive_output, "Expected held output ({held_output}) to be greater than inactive output ({inactive_output})");
assert_eq!(
resetted_output,
base_error + base_error * pid.config().sample_time().as_secs_f64()
);
}
#[test]
fn test_derivative_kick_mitigation() {
let mut pid = make_stateful_controller();
assert!(pid.config_mut().set_kd(1.0).is_ok());
pid.compute(0.0, 5.0, get_next_timestamp_stateful(&pid), None);
const NEW_SETPOINT: f64 = 50.0;
pid.config_mut().set_use_derivative_on_measurement(false);
let output_no_derivative_on_meas =
pid.compute(1.0, NEW_SETPOINT, get_next_timestamp_stateful(&pid), None);
pid.config_mut().set_use_derivative_on_measurement(true);
let output_with_derivative_on_meas =
pid.compute(1.0, NEW_SETPOINT, get_next_timestamp_stateful(&pid), None);
assert!(output_with_derivative_on_meas < output_no_derivative_on_meas);
}
#[test]
fn test_initialized_start() {
const STEADY_STATE_MEASURED: f64 = 5.0;
const STEADY_STATE_DISTURBANCE: f64 = 1.5;
let mut pid = PidController::new(
PidConfig::default(),
Millis(0),
STEADY_STATE_MEASURED,
STEADY_STATE_DISTURBANCE,
);
const EXPECTED_ERROR: f64 = 1.5;
let output = pid.compute(
STEADY_STATE_MEASURED,
STEADY_STATE_MEASURED + EXPECTED_ERROR,
get_next_timestamp_stateful(&pid),
None,
);
let expected_i_term =
STEADY_STATE_DISTURBANCE + EXPECTED_ERROR * pid.config().sample_time().as_secs_f64();
assert_eq!(output, EXPECTED_ERROR + expected_i_term);
}
}