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:
133
pkg/interfaces/pid/pid.go
Normal file
133
pkg/interfaces/pid/pid.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Package pid defines interfaces for PID controller process variable sources.
|
||||
// This abstraction allows the PID controller to be used for any dynamic
|
||||
// adjustment scenario - rate limiting, PoW difficulty adjustment, etc.
|
||||
package pid
|
||||
|
||||
import "time"
|
||||
|
||||
// ProcessVariable represents a measurable quantity that the PID controller
|
||||
// regulates. Implementations provide the current value and optional metadata.
|
||||
type ProcessVariable interface {
|
||||
// Value returns the current process variable value.
|
||||
// The value should typically be normalized to a range where the setpoint
|
||||
// makes sense (e.g., 0.0-1.0 for percentage-based control, or absolute
|
||||
// values for things like hash rate or block time).
|
||||
Value() float64
|
||||
|
||||
// Timestamp returns when this measurement was taken.
|
||||
// This is used for derivative calculations and staleness detection.
|
||||
Timestamp() time.Time
|
||||
}
|
||||
|
||||
// Source provides process variable measurements to the PID controller.
|
||||
// Implementations are domain-specific (e.g., memory monitor, hash rate tracker).
|
||||
type Source interface {
|
||||
// Sample returns the current process variable measurement.
|
||||
// This should be efficient as it may be called frequently.
|
||||
Sample() ProcessVariable
|
||||
|
||||
// Name returns a human-readable name for this source (for logging/debugging).
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Output represents the result of a PID controller update.
|
||||
type Output interface {
|
||||
// Value returns the computed output value.
|
||||
// The interpretation depends on the application:
|
||||
// - For rate limiting: delay in seconds
|
||||
// - For PoW difficulty: difficulty adjustment factor
|
||||
// - For temperature control: heater power level
|
||||
Value() float64
|
||||
|
||||
// Clamped returns true if the output was clamped to limits.
|
||||
Clamped() bool
|
||||
|
||||
// Components returns the individual P, I, D contributions for debugging.
|
||||
Components() (p, i, d float64)
|
||||
}
|
||||
|
||||
// Controller defines the interface for a PID controller.
|
||||
// This allows for different controller implementations (standard PID,
|
||||
// PID with filtered derivative, adaptive PID, etc.).
|
||||
type Controller interface {
|
||||
// Update computes the controller output based on the current process variable.
|
||||
// Returns the computed output.
|
||||
Update(pv ProcessVariable) Output
|
||||
|
||||
// UpdateValue is a convenience method that takes a raw float64 value.
|
||||
// Uses the current time as the timestamp.
|
||||
UpdateValue(value float64) Output
|
||||
|
||||
// Reset clears all internal state (integral accumulator, previous values).
|
||||
Reset()
|
||||
|
||||
// SetSetpoint updates the target value.
|
||||
SetSetpoint(setpoint float64)
|
||||
|
||||
// Setpoint returns the current setpoint.
|
||||
Setpoint() float64
|
||||
|
||||
// SetGains updates the PID gains.
|
||||
SetGains(kp, ki, kd float64)
|
||||
|
||||
// Gains returns the current PID gains.
|
||||
Gains() (kp, ki, kd float64)
|
||||
}
|
||||
|
||||
// Tuning holds PID tuning parameters.
|
||||
// This can be used for configuration or auto-tuning.
|
||||
type Tuning struct {
|
||||
Kp float64 // Proportional gain
|
||||
Ki float64 // Integral gain
|
||||
Kd float64 // Derivative gain
|
||||
|
||||
Setpoint float64 // Target value
|
||||
|
||||
// Derivative filtering (0.0-1.0, lower = more filtering)
|
||||
DerivativeFilterAlpha float64
|
||||
|
||||
// Anti-windup limits for integral term
|
||||
IntegralMin float64
|
||||
IntegralMax float64
|
||||
|
||||
// Output limits
|
||||
OutputMin float64
|
||||
OutputMax float64
|
||||
}
|
||||
|
||||
// DefaultTuning returns sensible defaults for a normalized (0-1) process variable.
|
||||
func DefaultTuning() Tuning {
|
||||
return Tuning{
|
||||
Kp: 0.5,
|
||||
Ki: 0.1,
|
||||
Kd: 0.05,
|
||||
Setpoint: 0.5,
|
||||
DerivativeFilterAlpha: 0.2,
|
||||
IntegralMin: -10.0,
|
||||
IntegralMax: 10.0,
|
||||
OutputMin: 0.0,
|
||||
OutputMax: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// SimpleProcessVariable is a basic implementation of ProcessVariable.
|
||||
type SimpleProcessVariable struct {
|
||||
V float64
|
||||
T time.Time
|
||||
}
|
||||
|
||||
// Value returns the process variable value.
|
||||
func (p SimpleProcessVariable) Value() float64 { return p.V }
|
||||
|
||||
// Timestamp returns when this measurement was taken.
|
||||
func (p SimpleProcessVariable) Timestamp() time.Time { return p.T }
|
||||
|
||||
// NewProcessVariable creates a SimpleProcessVariable with the current time.
|
||||
func NewProcessVariable(value float64) SimpleProcessVariable {
|
||||
return SimpleProcessVariable{V: value, T: time.Now()}
|
||||
}
|
||||
|
||||
// NewProcessVariableAt creates a SimpleProcessVariable with a specific time.
|
||||
func NewProcessVariableAt(value float64, t time.Time) SimpleProcessVariable {
|
||||
return SimpleProcessVariable{V: value, T: t}
|
||||
}
|
||||
Reference in New Issue
Block a user