You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
4.2 KiB
176 lines
4.2 KiB
package ratelimit |
|
|
|
import ( |
|
"testing" |
|
"time" |
|
) |
|
|
|
func TestPIDController_BasicOperation(t *testing.T) { |
|
pid := DefaultPIDControllerForWrites() |
|
|
|
// First call should return 0 (initialization) |
|
delay := pid.Update(0.5) |
|
if delay != 0 { |
|
t.Errorf("expected 0 delay on first call, got %v", delay) |
|
} |
|
|
|
// Sleep a bit to ensure dt > 0 |
|
time.Sleep(10 * time.Millisecond) |
|
|
|
// Process variable below setpoint (0.5 < 0.85) should return 0 delay |
|
delay = pid.Update(0.5) |
|
if delay != 0 { |
|
t.Errorf("expected 0 delay when below setpoint, got %v", delay) |
|
} |
|
|
|
// Process variable above setpoint should return positive delay |
|
time.Sleep(10 * time.Millisecond) |
|
delay = pid.Update(0.95) // 0.95 > 0.85 setpoint |
|
if delay <= 0 { |
|
t.Errorf("expected positive delay when above setpoint, got %v", delay) |
|
} |
|
} |
|
|
|
func TestPIDController_IntegralAccumulation(t *testing.T) { |
|
pid := NewPIDController( |
|
0.5, 0.5, 0.0, // High Ki, no Kd |
|
0.5, // setpoint |
|
0.2, // filter alpha |
|
-10, 10, // integral bounds |
|
0, 1.0, // output bounds |
|
) |
|
|
|
// Initialize |
|
pid.Update(0.5) |
|
time.Sleep(10 * time.Millisecond) |
|
|
|
// Continuously above setpoint should accumulate integral |
|
for i := 0; i < 10; i++ { |
|
time.Sleep(10 * time.Millisecond) |
|
pid.Update(0.8) // 0.3 above setpoint |
|
} |
|
|
|
integral, _, _ := pid.GetState() |
|
if integral <= 0 { |
|
t.Errorf("expected positive integral after sustained error, got %v", integral) |
|
} |
|
} |
|
|
|
func TestPIDController_FilteredDerivative(t *testing.T) { |
|
pid := NewPIDController( |
|
0.0, 0.0, 1.0, // Only Kd |
|
0.5, // setpoint |
|
0.5, // 50% filtering |
|
-10, 10, |
|
0, 1.0, |
|
) |
|
|
|
// Initialize with low value |
|
pid.Update(0.5) |
|
time.Sleep(10 * time.Millisecond) |
|
|
|
// Second call with same value - derivative should be near zero |
|
pid.Update(0.5) |
|
_, _, prevFiltered := pid.GetState() |
|
|
|
time.Sleep(10 * time.Millisecond) |
|
|
|
// Big jump - filtered derivative should be dampened |
|
delay := pid.Update(1.0) |
|
|
|
// The filtered derivative should cause some response, but dampened |
|
// Since we only have Kd=1.0 and alpha=0.5, the response should be modest |
|
if delay < 0 { |
|
t.Errorf("expected non-negative delay, got %v", delay) |
|
} |
|
|
|
_, _, newFiltered := pid.GetState() |
|
// Filtered error should have moved toward the new error but not fully |
|
if newFiltered <= prevFiltered { |
|
t.Errorf("filtered error should increase with rising process variable") |
|
} |
|
} |
|
|
|
func TestPIDController_AntiWindup(t *testing.T) { |
|
pid := NewPIDController( |
|
0.0, 1.0, 0.0, // Only Ki |
|
0.5, // setpoint |
|
0.2, // filter alpha |
|
-1.0, 1.0, // tight integral bounds |
|
0, 10.0, // wide output bounds |
|
) |
|
|
|
// Initialize |
|
pid.Update(0.5) |
|
|
|
// Drive the integral to its limit |
|
for i := 0; i < 100; i++ { |
|
time.Sleep(1 * time.Millisecond) |
|
pid.Update(1.0) // Large positive error |
|
} |
|
|
|
integral, _, _ := pid.GetState() |
|
if integral > 1.0 { |
|
t.Errorf("integral should be clamped at 1.0, got %v", integral) |
|
} |
|
} |
|
|
|
func TestPIDController_Reset(t *testing.T) { |
|
pid := DefaultPIDControllerForWrites() |
|
|
|
// Build up some state |
|
pid.Update(0.5) |
|
time.Sleep(10 * time.Millisecond) |
|
pid.Update(0.9) |
|
time.Sleep(10 * time.Millisecond) |
|
pid.Update(0.95) |
|
|
|
// Reset |
|
pid.Reset() |
|
|
|
integral, prevErr, prevFiltered := pid.GetState() |
|
if integral != 0 || prevErr != 0 || prevFiltered != 0 { |
|
t.Errorf("expected all state to be zero after reset") |
|
} |
|
|
|
// Next call should behave like first call |
|
delay := pid.Update(0.9) |
|
if delay != 0 { |
|
t.Errorf("expected 0 delay on first call after reset, got %v", delay) |
|
} |
|
} |
|
|
|
func TestPIDController_SetGains(t *testing.T) { |
|
pid := DefaultPIDControllerForWrites() |
|
|
|
// Change gains |
|
pid.SetGains(1.0, 0.5, 0.1) |
|
|
|
if pid.Kp != 1.0 || pid.Ki != 0.5 || pid.Kd != 0.1 { |
|
t.Errorf("gains not updated correctly") |
|
} |
|
} |
|
|
|
func TestPIDController_SetSetpoint(t *testing.T) { |
|
pid := DefaultPIDControllerForWrites() |
|
|
|
pid.SetSetpoint(0.7) |
|
|
|
if pid.Setpoint != 0.7 { |
|
t.Errorf("setpoint not updated, got %v", pid.Setpoint) |
|
} |
|
} |
|
|
|
func TestDefaultControllers(t *testing.T) { |
|
writePID := DefaultPIDControllerForWrites() |
|
readPID := DefaultPIDControllerForReads() |
|
|
|
// Write controller should have higher gains and lower setpoint |
|
if writePID.Kp <= readPID.Kp { |
|
t.Errorf("write Kp should be higher than read Kp") |
|
} |
|
|
|
if writePID.Setpoint >= readPID.Setpoint { |
|
t.Errorf("write setpoint should be lower than read setpoint") |
|
} |
|
}
|
|
|