# My Dumb PID Simulator

An exercise into creating a PID regulator model and hooking it up on my simulator to see how it behaves while trying to control the voltage across the resistor on a RL circuit.

After working a few days into building my own Python simulator, a few days ago I finished its “0.0.0.0.0.1” release (yeah, it’s pre-pre-pre-alpha). You can read more about my simulator here or here.

With it, I was able to simulate a few specific models (mostly, electric circuits). With that in place, my first thought was to add some more interesting models to it. What I had in mind was to add models for certain kinds of machines (valves, compressors, turbines, etc.). But then I thought, “well, if I can simulate electric circuits, how cool wouldn’t it be to simulate a **regulator** too!”. And that’s what I did today. I did a really dumb model of a really-really simple PID regulator.

# Numerical analysis

But to start with, I had to create two classes to run the numerical integration and numerical differentiation. Again, the methods selected were the most basic ones (no wonder I called the classes *DumbIntegrator* and *DumbDifferentiator*. These two are shown below.

`class DumbIntegrator:`

""" The dumbest possible numerical integrator """

def __init__(self, m0 = 0):

self.m0 = m0

self.y0 = None

def calculate(self, dt, y):

y0 = self.y0 if self.y0 else y

new_area = dt*((y0 + y)/2)

total_area = self.m0 + new_area

# update state

self.y0 = y

self.m0 = total_area

return total_area

class DumbDifferentiator:

""" The dumbest possible numerical derivator """

def __init__(self):

self.y0 = None

def calculate(self, dt, y):

y0 = self.y0 if self.y0 else y

rate = (y - y0)/dt

# update state

self.y0 = y

return rate

# PID regulator

I wanted the PID implementation to be as simple as possible so I can compare it with better PID implementations later and even with different regulator approaches (model based, optimal control, adaptive control, etc.). So, this is officially **the worst PID implementation** I could think of. There is no windup protection (even because, theoretically, my models don’t have physical limitations yet), differentiation is done on the error instead of on the process value, there is no filter on the derivative gain, no deadband feature, and no limits on the integral gain. So, don’t use this script on production!!!

But this bad implementation is exactly what I wanted so it can serve as a baseline to compare with better implementations later.

`from .base import BaseModel`

from ..utils.numerical_analysis import DumbDifferentiator, DumbIntegrator

class PID(BaseModel):

""" A class used to model a dumb PID regulator """

def __init__(self, dt, Kp, Ti, Td, error = 0):

# constants

self.dt = dt

self.Kp = [Kp]

self.Ti = [Ti]

self.Td = [Td]

# state variable

self.error = [error]

# inputs

self.PV = [None]

self.SP = [None]

# other

self.I = DumbIntegrator()

self.D = DumbDifferentiator()

self.PG = [0]

self.IG = [0]

self.DG = [0]

# output

self.MV = [0]

def calculate(self, SP, PV, FWD):

""" Calculates the next value and adds it to the memory """

Kp = self.Kp[-1]

Ti = self.Ti[-1]

Td = self.Td[-1]

# error

error = SP - PV

# gains

PG = Kp*error

IG = self.I.calculate(self.dt, error)*Kp/Ti

DG = self.D.calculate(self.dt, error)*Kp*Td

# output

MV = PG + IG + DG + FWD

# update attributes

self.update_attributes(**{

"error": error,

"PG": PG,

"IG": IG,

"DG": DG,

"MV": MV

})

return MV

# Simulation

With this PID implementation, I hooked it up to my simulator and I ran some trials on a RL (resistor-inductor) circuit. My goal here was to use a PID regulator to control the voltage on the resistor of the RL circuit.

*Electric circuit representation*

Circuit drawn usingcircuitlab

*Control loop representation*

# Results

P.S.: I have not done any optimization work on the regulator settings. For now my focus was only to see if the model and simulator were functioning properly.

Since using a constant set point ( `SP`

) is too boring, I used my *SignalGenerator* class to create a crazy `SP`

(with some steps and ramps; it's shown in red below).

As it can be seen above, the process variable ( `PV`

; the voltage on the resistor) do track the `SP`

. Hence, it seems my dumb PID regulator isn't so dumb at the end. After all, it kind of works. It's not optimal though.

For example, what the heck is that pulse on the manipulated value ( `MV`

; the regulator voltage applied at the input of the RL circuit)?! That's is a (often) bad derivative gain implementation. Since my derivative gain is based on the error, whenever there is a pulse on the `SP`

, and therefore a pulse on the calculated error, my derivative gain skyrockets! That's clearly depicted below where I have plotted the different (P-I-D) gains of the controller.

As it can be seen from the picture above, the derivative gain goes crazy whenever the SP steps. It then doesn’t go to zero. It just looks like it is zero because the derivative time ( `Td`

) I used was quite small. As it can be seen, even with small derivative times, it's possible to have spikes on the derivative response if it's not implemented correctly. And the faster the regulator scan time, the higher these spikes are. That's why the derivative gain is often calculated based on the `PV`

and not on the calculated error ( `SP`

- `PV`

).

To see the difference, I ran the same simulation but this time without a derivative gain (p.s.: *the graphic below has a different y-axis range*).

This time the spike on the `MV`

is almost two times smaller than while using the derivative gain. The spike still exists because of the proportional gain reacting on the `SP`

step.

# What’s next

My next step is going to be to improve the PID regulator script to try to improve it. Something else I want to do is to tune the regulator as well and see how its performance changes.

I have also simulated a PID regulator windup effect. You can read more about this story here.

The whole code used for these experiments can be found here.

*Originally published at **https://fabiomolinar.com** on August 1, 2019.*