Skip to content

Commit

Permalink
Capialize first letter in comments to match PEP-8. Some other style f…
Browse files Browse the repository at this point in the history
…ixes.
  • Loading branch information
m-lundberg committed Mar 20, 2021
1 parent 1517f0b commit faeb98c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 50 deletions.
6 changes: 3 additions & 3 deletions examples/water_boiler/water_boiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def __init__(self):

def update(self, boiler_power, dt):
if boiler_power > 0:
# boiler can only produce heat, not cold
# Boiler can only produce heat, not cold
self.water_temp += 1 * boiler_power * dt

# some heat dissipation
# Some heat dissipation
self.water_temp -= 0.02 * dt
return self.water_temp

Expand All @@ -34,7 +34,7 @@ def update(self, boiler_power, dt):
start_time = time.time()
last_time = start_time

# keep track of values for plotting
# Keep track of values for plotting
setpoint, y, x = [], [], []

while time.time() - start_time < 10:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
include_package_data=True,
zip_safe=False,
extras_require={
'docs': ['m2r', 'sphinx-rtd-theme']
'docs': ['m2r', 'sphinx-rtd-theme'],
},
project_urls={
'Documentation': 'https://simple-pid.readthedocs.io/',
Expand Down
30 changes: 14 additions & 16 deletions simple_pid/PID.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def _clamp(value, limits):


try:
# get monotonic time to ensure that time deltas are always positive
# Get monotonic time to ensure that time deltas are always positive
_current_time = time.monotonic
except AttributeError:
# time.monotonic() not available (using python < 3.3), fallback to time.time()
Expand All @@ -35,7 +35,7 @@ def __init__(
output_limits=(None, None),
auto_mode=True,
proportional_on_measurement=False,
error_map=None
error_map=None,
):
"""
Initialize a new PID controller.
Expand Down Expand Up @@ -100,39 +100,37 @@ def __call__(self, input_, dt=None):
elif dt <= 0:
raise ValueError('dt has negative value {}, must be positive'.format(dt))

if (self.sample_time is not None) \
and (dt < self.sample_time) \
and (self._last_output is not None):
# only update every sample_time seconds
if self.sample_time is not None and dt < self.sample_time and self._last_output is not None:
# Only update every sample_time seconds
return self._last_output

# compute error terms
# Compute error terms
error = self.setpoint - input_
d_input = input_ - (self._last_input if (self._last_input is not None) else input_)

# check if must map the error
# Check if must map the error
if self.error_map is not None:
error = self.error_map(error)

# compute the proportional term
# Compute the proportional term
if not self.proportional_on_measurement:
# regular proportional-on-error, simply set the proportional term
# Regular proportional-on-error, simply set the proportional term
self._proportional = self.Kp * error
else:
# add the proportional error on measurement to error_sum
# Add the proportional error on measurement to error_sum
self._proportional -= self.Kp * d_input

# compute integral and derivative terms
# Compute integral and derivative terms
self._integral += self.Ki * error * dt
self._integral = _clamp(self._integral, self.output_limits) # avoid integral windup
self._integral = _clamp(self._integral, self.output_limits) # Avoid integral windup

self._derivative = -self.Kd * d_input / dt

# compute final output
# Compute final output
output = self._proportional + self._integral + self._derivative
output = _clamp(output, self.output_limits)

# keep track of state
# Keep track of state
self._last_output = output
self._last_input = input_
self._last_time = now
Expand Down Expand Up @@ -193,7 +191,7 @@ def set_auto_mode(self, enabled, last_output=None):
auto mode.
"""
if enabled and not self._auto_mode:
# switching from manual mode to auto, reset
# Switching from manual mode to auto, reset
self.reset()

self._integral = last_output if (last_output is not None) else 0
Expand Down
59 changes: 29 additions & 30 deletions tests/test_pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_I():
pid = PID(0, 10, 0, setpoint=10, sample_time=0.1)
time.sleep(0.1)

assert round(pid(0)) == 10.0 # make sure we are close to expected value
assert round(pid(0)) == 10.0 # Make sure we are close to expected value
time.sleep(0.1)

assert round(pid(0)) == 20.0
Expand All @@ -46,11 +46,11 @@ def test_I_negative_setpoint():
def test_D():
pid = PID(0, 0, 0.1, setpoint=10, sample_time=0.1)

# should not compute derivative when there is no previous input (don't assume 0 as first input)
# Should not compute derivative when there is no previous input (don't assume 0 as first input)
assert pid(0) == 0
time.sleep(0.1)

# derivative is 0 when input is the same
# Derivative is 0 when input is the same
assert pid(0) == 0
assert pid(0) == 0
time.sleep(0.1)
Expand All @@ -64,11 +64,11 @@ def test_D_negative_setpoint():
pid = PID(0, 0, 0.1, setpoint=-10, sample_time=0.1)
time.sleep(0.1)

# should not compute derivative when there is no previous input (don't assume 0 as first input)
# Should not compute derivative when there is no previous input (don't assume 0 as first input)
assert pid(0) == 0
time.sleep(0.1)

# derivative is 0 when input is the same
# Derivative is 0 when input is the same
assert pid(0) == 0
assert pid(0) == 0
time.sleep(0.1)
Expand All @@ -83,7 +83,7 @@ def test_D_negative_setpoint():
def test_desired_state():
pid = PID(10, 5, 2, setpoint=10, sample_time=None)

# should not make any adjustment when setpoint is achieved
# Should not make any adjustment when setpoint is achieved
assert pid(10) == 0


Expand All @@ -102,7 +102,7 @@ def test_sample_time():

control = pid(0)

# last value should be returned again
# Last value should be returned again
assert pid(100) == control


Expand All @@ -118,30 +118,30 @@ def test_monotonic():
def test_auto_mode():
pid = PID(1, 0, 0, setpoint=10, sample_time=None)

# ensure updates happen by default
# Ensure updates happen by default
assert pid(0) == 10
assert pid(5) == 5

# ensure no new updates happen when auto mode is off
# Ensure no new updates happen when auto mode is off
pid.auto_mode = False
assert pid(1) == 5
assert pid(7) == 5

# should reset when reactivating
# Should reset when reactivating
pid.auto_mode = True
assert pid._last_input is None
assert pid._integral == 0
assert pid(8) == 2

# last update time should be reset to avoid huge dt
# Last update time should be reset to avoid huge dt
from simple_pid.PID import _current_time

pid.auto_mode = False
time.sleep(1)
pid.auto_mode = True
assert _current_time() - pid._last_time < 0.01

# check that setting last_output works
# Check that setting last_output works
pid.auto_mode = False
pid.set_auto_mode(True, last_output=10)
assert pid._integral == 10
Expand All @@ -164,22 +164,22 @@ def test_clamp():
assert _clamp(None, (None, None)) is None
assert _clamp(None, (-10, 10)) is None

# no limits
# No limits
assert _clamp(0, (None, None)) == 0
assert _clamp(100, (None, None)) == 100
assert _clamp(-100, (None, None)) == -100

# only lower limit
# Only lower limit
assert _clamp(0, (0, None)) == 0
assert _clamp(100, (0, None)) == 100
assert _clamp(-100, (0, None)) == 0

# only upper limit
# Only upper limit
assert _clamp(0, (None, 0)) == 0
assert _clamp(100, (None, 0)) == 0
assert _clamp(-100, (None, 0)) == -100

# both limits
# Both limits
assert _clamp(0, (-10, 10)) == 0
assert _clamp(-10, (-10, 10)) == -10
assert _clamp(10, (-10, 10)) == 10
Expand All @@ -199,27 +199,26 @@ def test_repr():

def test_converge_system():
pid = PID(1, 0.8, 0.04, setpoint=5, output_limits=(-5, 5))
PV = 0 # process variable
pv = 0 # Process variable

def update_system(C, dt):
# calculate a simple system model
return PV + C * dt - 1 * dt
def update_system(c, dt):
# Calculate a simple system model
return pv + c * dt - 1 * dt

start_time = time.time()
last_time = start_time

while time.time() - start_time < 120:
C = pid(PV)
PV = update_system(C, time.time() - last_time)
c = pid(pv)
pv = update_system(c, time.time() - last_time)

last_time = time.time()

# check if system has converged
assert abs(PV - 5) < 0.1
# Check if system has converged
assert abs(pv - 5) < 0.1


def test_error_map():
# error map function
import math

def pi_clip(angle):
Expand All @@ -232,9 +231,9 @@ def pi_clip(angle):
return angle + 2 * math.pi
return angle

sp = 0. # setpoint
pid = PID(1, 0, 0, setpoint=sp, sample_time=0.1, error_map=pi_clip) # include error mapping
PV = 5. # process variable
sp = 0.0 # Setpoint
pv = 5.0 # Process variable
pid = PID(1, 0, 0, setpoint=0.0, sample_time=0.1, error_map=pi_clip)

# check if error value is mapped by the function
assert pid(PV) == pi_clip(sp - PV) # clip the error
# Check if error value is mapped by the function
assert pid(pv) == pi_clip(sp - pv)

0 comments on commit faeb98c

Please sign in to comment.