first commit

This commit is contained in:
2026-09-02 15:13:13 +08:00
commit cb00322a50
107 changed files with 10918 additions and 0 deletions
@@ -0,0 +1,95 @@
"""位置式 PID 控制器(纯算法构件)。
对应 C++ 参考实现 reference-c++/visual_track_control/src/pid_controller.cpp
(规格:docs/物理追踪思路.md §6.2 距离控制律、§6.4 Coast 清积分)。
anti-windup(条件积分):输出饱和且本拍积分增量(error·dt)仍在推深饱和
时冻结本步积分累加;判据刻意基于"增量方向"而非"积分项与输出同向"——
后者在误差反号后仍会冻结回卷,导致饱和残留。
"""
from dataclasses import dataclass
from .rate_limiter import clamp
@dataclass
class PidParams:
"""PID 参数(全部来自配置,代码不写死任何数值)。"""
kp: float = 0.0
ki: float = 0.0
kd: float = 0.0
integral_limit: float = 0.0 # 积分项限幅(<=0 表示不限幅)
derivative_filter_coef: float = 0.0 # 微分一阶低通系数 ∈ [0,1):0 = 不滤波
output_limit: float = 0.0 # 输出限幅(<=0 表示不限幅)
class PidController:
def __init__(self, params: PidParams) -> None:
self._params = params
self._integral = 0.0
self._prev_error = 0.0
self._filtered_derivative = 0.0
self._last_output = 0.0
self._has_prev_error = False
def update(self, error: float, dt_s: float) -> float:
"""单步更新:输入误差与采样间隔(秒),返回限幅后的输出。
dt_s <= 0 时保持上次输出(异常节拍保护)。
"""
if dt_s <= 0.0:
return self._last_output
# 积分:候选值先钳位(积分限幅)
integral_candidate = self._integral + error * dt_s
if self._params.integral_limit > 0.0:
integral_candidate = clamp(integral_candidate,
-self._params.integral_limit,
self._params.integral_limit)
# 微分:一阶低通滤波(系数 ∈ [0,1),0 = 不滤波)
derivative = 0.0
if self._has_prev_error:
derivative = (error - self._prev_error) / dt_s
coef = self._params.derivative_filter_coef
derivative = coef * self._filtered_derivative + (1.0 - coef) * derivative
self._filtered_derivative = derivative
self._prev_error = error
self._has_prev_error = True
# 位置式合成
integral_term = self._params.ki * integral_candidate
raw = self._params.kp * error + integral_term + self._params.kd * derivative
# anti-windup(条件积分):输出饱和且本拍积分增量仍在推深饱和时冻结
saturated = (self._params.output_limit > 0.0
and abs(raw) > self._params.output_limit)
increment_pushes_deeper = (error * dt_s) * raw > 0.0
if not (saturated and increment_pushes_deeper):
self._integral = integral_candidate
# 输出限幅
if self._params.output_limit > 0.0:
self._last_output = clamp(raw, -self._params.output_limit,
self._params.output_limit)
else:
self._last_output = raw
return self._last_output
def reset_integral(self) -> None:
"""清空积分项(§6.4:Coast 时防止积分饱和);保留微分历史与上次输出。"""
self._integral = 0.0
def reset(self) -> None:
"""清空全部内部状态(积分、微分历史、上次输出)。"""
self._integral = 0.0
self._prev_error = 0.0
self._filtered_derivative = 0.0
self._last_output = 0.0
self._has_prev_error = False
@property
def last_output(self) -> float:
return self._last_output