A 3D printer's hot end is a small block of aluminium with a heater cartridge of up to u_max watts and a temperature sensor. Its firmware runs a PID controller every dt seconds. After a failed print the maker wants to check the firmware's arithmetic, so you are given its log: the setpoints and the sensor readings (°C), one pair per sample, and you must recompute the heater commands.
The controller keeps two numbers between samples: the integral term I (in watts, already multiplied by Ki) and the filtered derivative D (°C per second). Both start at 0. For each sample, in exactly this order:
e = r - y (error, °C)
I = I + Ki * e * dt, then limited to 0..u_max (clamped integral: no windup beyond the heater's range)
raw = -(y - y_previous) / dt (derivative on the measurement; 0 for the first sample)
D = D + dt / (Tf + dt) * (raw - D) (low-pass filtered, time constant Tf seconds)
u = Kp * e + I + Kd * D, then limited to 0..u_max (watts)
Write pid_log(setpoints, readings, dt, Kp, Ki, Kd, Tf, u_max) that returns the list of commands u, one per sample.
Examples
Input: setpoints = [200, 200, 200, 200], readings = [195.0, 196.0, 197.5, 198.5],
dt = 0.5, Kp = 2, Ki = 0.4, Kd = 2, Tf = 0.5, u_max = 40
Output: [11.0, 7.8, 3.3, 1.6]
Explanation: sample 1: e = 5, I = 0.4 * 5 * 0.5 = 1, raw = 0, u = 10 + 1 = 11 W.
Sample 2: e = 4, I = 1.8, raw = -2 °C/s, D = 0 + 0.5 * (-2 - 0) = -1, u = 8 + 1.8 - 2 = 7.8 W.
Input: setpoints = [210, 210, 210], readings = [25.0, 30.0, 36.0],
dt = 0.5, Kp = 2, Ki = 0.4, Kd = 5, Tf = 0.5, u_max = 40
Output: [40.0, 40.0, 40.0]
Explanation: heating from cold the command is far above 40 W and is limited; the integral
term is held at 40 W too, so it cannot pile up while the heater is flat out.
Constraints
len(setpoints) == len(readings)- answers are compared with a tolerance of
1e-6; do not round
Goals
- Implement a complete discrete PID update in a stated order
- Clamp the integral term and the output to what the heater can deliver
- Combine a derivative on the measurement with a low-pass filter