Model Predictive Temperature Control port for Klipper

In the Klipper development goals for 2024 one of the mentioned goals is

Heater modeling improvements.

There has been some interesting work in “feed forward” control systems recently (eg, “Marlin’s MPC” system). Something to “keep an eye on”.

The marlin documentation explains quite well what MPC is and why it is better than PID, Model Predictive Temperature Control | Marlin Firmware.

In the last few months I have been working on porting marlin’s MPC over to klipper.
The code is available here: GitHub - Luro02/klipper-mpc: Implementation of Model Predictive Temperature Control for klipper

What is working?

  • the mpc algorithm will settle at the target temperature
  • if the calibrated values are wrong, it will apply corrections to the ambient_temp (therefore even with wrong values, it should settle after some time, but this could take forever)
  • it is able to detect when it has settled at the target temperature
  • the calibration code correctly finds the initial values (those are later replaced by better ones, but that part is not working)
  • it is able to

What is not working?

  • It is unable to find the right ambient_xfer_coeff_fan0 / ambient_xfer_coeff_fan255 or there is something wrong when recalculating the model constants with the new ambient_xfer_coeff
  • differential tuning has not been tested and might therefore be broken (on some heaters, analytic tuning will not work, on those differential tuning is necessary)
  • heaters that do not heat up to ~230°C do not work yet (therefore no mpc for the bed)
  • different target temperature (not 200°C)

How to use it?

The README.md explains everything necessary for getting started. It is a bit outdated, because at the time it automatically moved the extruder to the correct position (which it does not do anymore).

Before starting the calibration, you should move the extruder to the middle of the bed with a z height of <= 1mm.

:warning: Do not leave your printer unattended while MPC is enabled, it might destroy your 3d printer. This is not yet stable enough to be trusted. For example: While developing, I made a mistake which caused my code to crash. This resulted in mainsail no longer updating the printer temperature and my printer continued heating at 100% power. Klipper has a built-in safety feature called verify_heater which should shutdown the printer if it heats too much, but because of the crash it did not trigger. I was there, so I made an emergency shutdown.The temperature was very close to the maximum.

I added some extra code to make the printer shutdown if it crashes (klipper-mpc/src/mpc_calibrate.py at 9d4567c66827466402843e48a4e408b2e681f358 · Luro02/klipper-mpc · GitHub), so this shouldn’t be an issue anymore.

When will it be working?

I don’t know. Right now I do not have the time to continue working on it. Around April, I will have time again. The biggest blocker is the wrong ambient_xfer_coeffs.

I welcome any pull request, especially if someone is able to reliably calibrate the ambient_xfer_coeffs.

Here is what I came up with for this problem:

After the initial calibration at target ~200°C it will let the MPC algorithm heat to some higher temperature (for example 216°C). The algorithm will then adjust the ambient_temp, until the algorithm stabilizes at the higher temperature.

The formula for the ambient_xfer_coeff is (see the marlin docs):
ambient_xfer_coeff = Power Consumption / (asymptotic temperature - ambient_temp)

The asymptotic temperatue is the target temperature, so the only two variables are the ambient_temp and the power consumption.

Marlin implementation

The original marlin implementation measures how much power is consumed in a fixed-time frame (i think in 20s):

self.total_energy_fan0 += self.mpc.data.heater_power * heater_pwm * time_diff + (self.last_temp - temp) * self.mpc.data.block_heat_capacity
self.fan0_measurements += time_diff

and then later calculates the average power consumption:

self.power_fan0 = self.total_energy_fan0 / self.fan0_measurements
// ...
self.mpc.data.ambient_xfer_coeff_fan0 = self.power_fan0 / (target_temp - self.fan0_ambient_temp)

fan255 is calculated in the same way, by turning on the fan (letting the algorithm stabilize again) and then measuring how much power is consumed.

Calculating it from the power or adjusted ambient temperature

I assume the algorithm adjusts the ambient temperature until the power consumption is correct for the target temperature. The idea is to find an ambient_xfer_coeff for which the power consumption is the same, but with an ambient_temperature that is closer to the real world value, so the algorithm does not have to adjust so much.

By rearranging the formula from above

ambient_xfer_coeff = Power Consumption / (target_temp  - ambient_temp)
<=> Power Consumption = ambient_xfer_coeff  *  (target_temp  - ambient_temp)

then setting the old equation equal to the new one

 ambient_xfer_coeff_original  *  (target_temp - ambient_temp_adjusted) = ambient_xfer_coeff_new  *  (target_temp - ambient_temp_real)
<=> ambient_xfer_coeff_new = ambient_xfer_coeff_original  *  (target_temp - ambient_temp_adjusted) / (target_temp - ambient_temp_real)

one gets a different formula for finding the ambient_xfer_coeff.

Will it be merged into mainline?

Maybe? I think this works quite well as a standalone extension. Klipper for some reason still supports Python 2, so my code will not work without rewriting it (it is written for a modern version of Python 3). I really like how it uses dataclasses for reading and writing config values, which is why I would rather wait for klipper to migrate to Python 3.

By the way, this would be a very nice improvement for reading config values. Extensions could then declare them like this:

@klipperconfig # custom annotation like the dataclass one, which will add a from_config and save method
class MyConfigValues:
    heater_power: float = cfield(default = 40.0, above = 0.0, below = 3000.0)
    sensor_responsiveness: float = 0.0
    ambient_xfer_coeff_fan255: Optional[float] = None
6 Likes

Will this implementation of MPC work with PTC style heaters like the ones on Revo or Rapido? I know PTC heaters have unique characteristics of their available heat output relative to temperature which might not be covered in the standard MPC parameters?

If it works with marlin, it will work as well. The implementation is an almost 1:1 port, but like mentioned above, it currently is not working.