Generalize PID controller as reusable library with abstract interfaces

- Create pkg/interfaces/pid for generic PID controller interfaces:
  - ProcessVariable: abstract input (value + timestamp)
  - Source: provides process variable samples
  - Output: controller output with P/I/D components and clamping info
  - Controller: generic PID interface with setpoint/gains
  - Tuning: configuration struct for all PID parameters

- Create pkg/pid as standalone PID controller implementation:
  - Thread-safe with mutex protection
  - Low-pass filtered derivative to suppress high-frequency noise
  - Anti-windup on integral term
  - Configurable output clamping
  - Presets for common use cases: rate limiting, PoW difficulty,
    temperature control, motor speed

- Update pkg/ratelimit to use generic pkg/pid.Controller:
  - Limiter now uses pidif.Controller interface
  - Type assertions for monitoring/debugging state access
  - Maintains backward compatibility with existing API

The generic PID package can now be used for any dynamic adjustment
scenario beyond rate limiting, such as blockchain PoW difficulty
adjustment, temperature regulation, or motor speed control.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-11 22:53:04 +01:00
parent 88b0509ad8
commit 28b41847a6
5 changed files with 990 additions and 37 deletions

127
pkg/pid/presets.go Normal file
View File

@@ -0,0 +1,127 @@
package pid
import (
pidif "next.orly.dev/pkg/interfaces/pid"
)
// Presets for common PID controller use cases.
// These provide good starting points that can be fine-tuned for specific applications.
// RateLimitWriteTuning returns tuning optimized for write rate limiting.
// - Aggressive response to prevent memory exhaustion
// - Moderate integral for sustained load handling
// - Small derivative with strong filtering
func RateLimitWriteTuning() pidif.Tuning {
return pidif.Tuning{
Kp: 0.5,
Ki: 0.1,
Kd: 0.05,
Setpoint: 0.85, // Target 85% of limit
DerivativeFilterAlpha: 0.2, // Strong filtering
IntegralMin: -2.0,
IntegralMax: 10.0,
OutputMin: 0.0,
OutputMax: 1.0, // Max 1 second delay
}
}
// RateLimitReadTuning returns tuning optimized for read rate limiting.
// - Less aggressive than writes (reads are more latency-sensitive)
// - Lower gains to avoid over-throttling queries
func RateLimitReadTuning() pidif.Tuning {
return pidif.Tuning{
Kp: 0.3,
Ki: 0.05,
Kd: 0.02,
Setpoint: 0.90, // Target 90% of limit
DerivativeFilterAlpha: 0.15, // Very strong filtering
IntegralMin: -1.0,
IntegralMax: 5.0,
OutputMin: 0.0,
OutputMax: 0.5, // Max 500ms delay
}
}
// DifficultyAdjustmentTuning returns tuning for PoW difficulty adjustment.
// Designed for block time targeting where:
// - Process variable: actual_block_time / target_block_time (1.0 = on target)
// - Output: difficulty multiplier (1.0 = no change, >1 = harder, <1 = easier)
//
// This uses:
// - Low Kp to avoid overreacting to individual blocks
// - Moderate Ki to converge on target over time
// - Small Kd with strong filtering to anticipate trends
func DifficultyAdjustmentTuning() pidif.Tuning {
return pidif.Tuning{
Kp: 0.1, // Low proportional (blocks are noisy)
Ki: 0.05, // Moderate integral for convergence
Kd: 0.02, // Small derivative
Setpoint: 1.0, // Target: actual == expected block time
DerivativeFilterAlpha: 0.1, // Very strong filtering (blocks are noisy)
IntegralMin: -0.5, // Limit integral windup
IntegralMax: 0.5,
OutputMin: 0.5, // Min 50% difficulty change
OutputMax: 2.0, // Max 200% difficulty change
}
}
// TemperatureControlTuning returns tuning for temperature regulation.
// Suitable for heating/cooling systems where:
// - Process variable: current temperature
// - Setpoint: target temperature
// - Output: heater/cooler power level (0-1)
func TemperatureControlTuning(targetTemp float64) pidif.Tuning {
return pidif.Tuning{
Kp: 0.1, // Moderate response
Ki: 0.01, // Slow integral (thermal inertia)
Kd: 0.05, // Some anticipation
Setpoint: targetTemp,
DerivativeFilterAlpha: 0.3, // Moderate filtering
IntegralMin: -100.0,
IntegralMax: 100.0,
OutputMin: 0.0,
OutputMax: 1.0,
}
}
// MotorSpeedTuning returns tuning for motor speed control.
// - Process variable: actual RPM / target RPM
// - Output: motor power level
func MotorSpeedTuning() pidif.Tuning {
return pidif.Tuning{
Kp: 0.5, // Quick response
Ki: 0.2, // Eliminate steady-state error
Kd: 0.1, // Dampen oscillations
Setpoint: 1.0, // Target: actual == desired speed
DerivativeFilterAlpha: 0.4, // Moderate filtering
IntegralMin: -1.0,
IntegralMax: 1.0,
OutputMin: 0.0,
OutputMax: 1.0,
}
}
// NewRateLimitWriteController creates a controller for write rate limiting.
func NewRateLimitWriteController() *Controller {
return New(RateLimitWriteTuning())
}
// NewRateLimitReadController creates a controller for read rate limiting.
func NewRateLimitReadController() *Controller {
return New(RateLimitReadTuning())
}
// NewDifficultyAdjustmentController creates a controller for PoW difficulty.
func NewDifficultyAdjustmentController() *Controller {
return New(DifficultyAdjustmentTuning())
}
// NewTemperatureController creates a controller for temperature regulation.
func NewTemperatureController(targetTemp float64) *Controller {
return New(TemperatureControlTuning(targetTemp))
}
// NewMotorSpeedController creates a controller for motor speed control.
func NewMotorSpeedController() *Controller {
return New(MotorSpeedTuning())
}