Files
Python-DT_Slot_3/Regler/Regler_class_file.py
2023-02-06 09:57:23 +01:00

184 lines
6.5 KiB
Python

import numpy as np
#based on https://en.wikipedia.org/wiki/PID_controller#Discrete_implementation
# performance parameters for controllers
def trap_int(vec,timestep):
# numerical integration via the trapeziod rule to calculate the performance parameters
l = np.size(vec)
int = 0
for i in range(l-1):
int = int + (vec[i]+vec[i+1])/2*timestep
return int
def ISE_fun(error_history,timestep):
# calcuate the integral of square error
e = np.array(error_history)
dt = timestep
ise = trap_int(e**2,dt)
return ise
def IAE_fun(error_history,timestep):
# calcuate the integral of absolute error
e = np.array(error_history)
dt = timestep
iae = trap_int(np.abs(e),dt)
return iae
def ITSE_fun(error_history,timestep):
# calcuate the integral of time multiply square error
e = np.array(error_history)
dt = timestep
n = np.size(e)
t = np.arange(0,n)*dt
itse = trap_int(t*e**2,dt)
return itse
def ITAE_fun(error_history,timestep):
# calcuate the integral of time multiply absolute error
e = np.array(error_history)
dt = timestep
n = np.size(e)
t = np.arange(0,n)*dt
itae = trap_int(np.abs(e),dt)
return itae
# P controller
class P_controller_class:
# def __init__(self,setpoint,proportionality_constant):
# self.SP = setpoint
# self.Kp = proportionality_constant
# self.error_history = []
# self.control_variable = 0.1
# self.lower_limit = -0.1 # default
# self.upper_limit = +0.1 # default
# def set_control_variable_limits(self,lower_limit,upper_limit):
# self.lower_limit = lower_limit
# self.upper_limit = upper_limit
# def calculate_error(self,process_variable):
# self.error = self.SP-process_variable
# self.error_history.append(self.error)
# def get_control_variable(self):
# new_control = self.control_variable+self.Kp*(self.error_history[-1]-self.error_history[-2])
# if new_control < self.lower_limit:
# new_control = self.lower_limit
# if new_control > self.upper_limit:
# new_control = self.upper_limit
# self.control_variable = new_control
# # print(new_control)
# return new_control
def __init__(self):
pass
# PI controller
class PI_controller_class:
# init
def __init__(self,setpoint,deadband,proportionality_constant,Ti,timestep,lower_limit=0.,upper_limit=1.):
self.SP = setpoint
self.db = deadband
self.Kp = proportionality_constant
self.Ti = Ti # ~integration time
self.dt = timestep
# use a list to be able to append more easily - will get converted to np.array when needed
self.error_history = [0]
self.cv_lower_limit = lower_limit # limits for the controll variable
self.cv_upper_limit = upper_limit # limits for the controll variable
# setter
def set_setpoint(self,setpoint):
self.SP = setpoint
def set_control_variable(self,control_variable, display_warning=True):
if display_warning == True:
print('WARNING! You are setting the control variable of the PI controller manually! \
Consider using the .update_controll_variable() method instead.')
self.control_variable = control_variable
# getter
def get_current_control_variable(self):
return self.control_variable
def get_error_history(self):
return self.error_history[1:]
def get_performance_indicators(self,ISE=True,IAE=True,ITSE=True,ITAE=True):
# calculate and return the performance indicators of the error history
ise = np.nan
iae = np.nan
itse = np.nan
itae = np.nan
# self.error_history[1:] because the first value of the error history is set to [0]
# to avoid special case handling in the calculation of the control variable
if ISE == True:
ise = ISE_fun(self.error_history[1:],self.dt)
if IAE == True:
iae = IAE_fun(self.error_history[1:],self.dt)
if ITSE == True:
itse = ITSE_fun(self.error_history[1:],self.dt)
if ITAE == True:
itae = ITAE_fun(self.error_history[1:],self.dt)
return ise,iae,itse,itae
def get_info(self):
new_line = '\n'
# :<10 pads the self.value to be 10 characters wide
print_str = (f"Controller has the following attributes: {new_line}"
f"----------------------------- {new_line}"
f"Type = PI Controller {new_line}"
f"Setpoint = {self.SP:<10} {new_line}"
f"Deadband = {self.db:<10} {new_line}"
f"Proportionality constant = {self.Kp:<10} {new_line}"
f"Integration time = {self.Ti:<10} [s] {new_line}"
f"Current control variable = {round(self.control_variable,3):<10} {new_line}"
f"Lower limit CV = {self.cv_lower_limit:<10} {new_line}"
f"Upper limit CV = {self.cv_upper_limit:<10} {new_line}"
f"Simulation timestep = {self.dt:<10} [s] {new_line}"
f"----------------------------- {new_line}")
print(print_str)
# methods
def calculate_error(self,process_variable):
# calculate the error and expand the err history
self.error = process_variable-self.SP
self.error_history.append(self.error)
def update_control_variable(self,process_variable):
# calculate the current control variable and make sure it does not exceed the limits
self.calculate_error(process_variable)
# initialize some variables
cv = self.control_variable
Kp = self.Kp
Ti = self.Ti
dt = self.dt
e0 = self.error_history[-1]
e1 = self.error_history[-2]
# test if the error exceeds the deadband range
# only if that is the case, change control variable
if abs(self.error) > self.db:
new_control = cv+Kp*(e0-e1)+dt/Ti*e0
# ensure that the controll variable stays within the predefined limits
if new_control < self.cv_lower_limit:
new_control = self.cv_lower_limit
if new_control > self.cv_upper_limit:
new_control = self.cv_upper_limit
else:
new_control = cv
# set the control variable attribute
self.set_control_variable(new_control,display_warning=False)