""" Archivo que contiene todas las rutinas necesarias para la funcionalidad de tunning de PID """
from collections import deque
from scipy import real
import controlmdf as ctrl
import numpy as np
import json
[documentos]def system_creator_tf(self, numerador, denominador):
"""
Funcion para la creacion del sistema a partir de los coeficientes del numerador y del denominador de la funcion de transferencia
:param numerador: Coeficientes del numerador
:type numerador: list
:param denominador: Coeficientes del denominador
:type denominador: list
"""
if not self.main.tfdiscretocheckBox2.isChecked(
) and self.main.tfdelaycheckBox2.isChecked():
delay = json.loads(self.main.tfdelayEdit2.text())
else:
delay = 0
system = ctrl.TransferFunction(numerador, denominador, delay=delay)
if self.main.kpCheckBox2.isChecked():
kp = self.main.kpHSlider2.value() / self.tfSliderValue
else:
kp = 0
if self.main.kiCheckBox2.isChecked():
ki = self.main.kiHSlider2.value() / self.tfSliderValue
else:
ki = 0
if self.main.kdCheckBox2.isChecked():
kd = self.main.kdHSlider2.value() / self.tfSliderValue
else:
kd = 0
t = self.main.pidTiempoSlider.value()
# En caso de que el sistema sea discreto
if self.main.tfdiscretocheckBox2.isChecked():
pid = ctrl.TransferFunction([
2*kd + 2 * self.dt * kp + ki * self.dt**2,
-2 * self.dt * kp - 4*kd + ki * self.dt**2,
2 * kd
], [2 * self.dt, -2 * self.dt, 0],
self.dt)
system = ctrl.sample_system(system, self.dt, self.main.tfcomboBox2.currentText())
if self.main.tfdelaycheckBox2.isChecked():
delayV = [0] * (int(json.loads(self.main.tfdelayEdit2.text()) / self.dt) + 1)
delayV[0] = 1
system_delay = system * ctrl.TransferFunction([1], delayV, self.dt)
system_delay = ctrl.feedback(pid * system_delay)
else:
system_delay = None
system = ctrl.feedback(pid * system)
else:
system_delay = system
try:
if ctrl.isdtime(system, strict=True):
T = np.arange(0, t+self.dt, self.dt)
else:
T = np.arange(0, t+0.05, 0.05)
except ValueError:
T = np.arange(0, 100, 0.05)
return system, T, system_delay, kp, ki, kd
[documentos]def system_creator_ss(self, A, B, C, D):
"""
Funcion para la creacion del sistema a partir de la matriz de estado, matriz de entrada, matriz de salida y
la matriz de transmision directa la ecuacion de espacio de estados
:param A: Matriz de estados
:type A: list
:param B: Matriz de entrada
:type B: list
:param C: Matriz de salida
:type C: list
:param D: Matriz de transmision directa
:type D: list
"""
if not self.main.ssdiscretocheckBox2.isChecked(
) and self.main.ssdelaycheckBox2.isChecked():
delay = json.loads(self.main.ssdelayEdit2.text())
else:
delay = 0
system = ctrl.StateSpace(A, B, C, D, delay=delay)
if self.main.kpCheckBox2.isChecked():
kp = self.main.kpHSlider2.value() / self.ssSliderValue
else:
kp = 0
if self.main.kiCheckBox2.isChecked():
ki = self.main.kiHSlider2.value() / self.ssSliderValue
else:
ki = 0
if self.main.kdCheckBox2.isChecked():
kd = self.main.kdHSlider2.value() / self.ssSliderValue
else:
kd = 0
t = self.main.pidTiempoSlider.value()
# En caso de que el sistema sea discreto
if self.main.ssdiscretocheckBox2.isChecked():
pid = ctrl.TransferFunction([
2*kd + 2 * self.dt * kp + ki * self.dt**2,
-2 * self.dt * kp - 4*kd + ki * self.dt**2,
2 * kd
], [2 * self.dt, -2 * self.dt, 0],
self.dt)
system = ctrl.sample_system(system, self.dt, self.main.sscomboBox2.currentText())
system_ss = system
system = ctrl.ss2tf(system)
if self.main.ssdelaycheckBox2.isChecked():
delayV = [0] * (int(json.loads(self.main.ssdelayEdit2.text()) / self.dt) + 1)
delayV[0] = 1
system_delay = system * ctrl.TransferFunction([1], delayV, self.dt)
system_delay = ctrl.feedback(pid * system_delay)
else:
system_delay = None
system = ctrl.feedback(pid * system)
else:
system_ss = system
system = ctrl.ss2tf(system)
system_delay = None
try:
if ctrl.isdtime(system, strict=True):
T = np.arange(0, t+self.dt, self.dt)
else:
T = np.arange(0, t+0.05, 0.05)
except ValueError:
T = np.arange(0, 100, 0.05)
return system, T, system_delay, system_ss, kp, ki, kd
[documentos]def system_creator_tf_tuning(self, numerador, denominador):
"""
Funcion para la creacion del sistema a partir de los coeficientes del numerador y del denominador de la funcion de transferencia, adicionalmente se realiza el auto tuning utilizando el metodo escojido por le usuario
:param A: Matriz de estados
:type A: list
:param B: Matriz de entrada
:type B: list
:param C: Matriz de salida
:type C: list
:param D: Matriz de transmision directa
:type D: list
"""
if not self.main.tfdiscretocheckBox2.isChecked(
) and self.main.tfdelaycheckBox2.isChecked():
delay = json.loads(self.main.tfdelayEdit2.text())
else:
delay = 0
system = ctrl.TransferFunction(numerador, denominador, delay=delay)
t = self.main.pidTiempoSlider.value()
T = np.arange(0, t, 0.05)
U = np.ones_like(T)
t_temp, y, _ = ctrl.forced_response(system, T, U)
dc_gain = ctrl.dcgain(system)
# Parametros del modelo
K_proceso, tau, alpha = model_method(self, t_temp, y, dc_gain)
# Auto tunning
try:
kp, ki, kd = auto_tuning_method(self, K_proceso, tau, alpha, self.main.tfAutoTuningcomboBox2.currentText())
except TypeError:
raise TypeError('Alfa es muy pequeño')
# En caso de que el sistema sea discreto
if self.main.tfdiscretocheckBox2.isChecked():
pid = ctrl.TransferFunction([
2*kd + 2 * self.dt * kp + ki * self.dt**2,
-2 * self.dt * kp - 4*kd + ki * self.dt**2,
2 * kd
], [2 * self.dt, -2 * self.dt, 0],
self.dt)
system = ctrl.sample_system(system, self.dt, self.main.tfcomboBox2.currentText())
if self.main.tfdelaycheckBox2.isChecked():
delayV = [0] * (int(json.loads(self.main.tfdelayEdit2.text()) / self.dt) + 1)
delayV[0] = 1
system_delay = system * ctrl.TransferFunction([1], delayV, self.dt)
system_delay = ctrl.feedback(pid * system_delay)
else:
system_delay = None
system = ctrl.feedback(pid * system)
else:
system_delay = system
try:
if ctrl.isdtime(system, strict=True):
T = np.arange(0, t+self.dt, self.dt)
else:
T = np.arange(0, t+0.05, 0.05)
except ValueError:
T = np.arange(0, 100, 0.05)
return system, T, system_delay, kp, ki, kd
[documentos]def system_creator_ss_tuning(self, A, B, C, D):
"""
Funcion para la creacion del sistema a partir de la matriz de estado, matriz de entrada, matriz de salida y la matriz de transmision directa la ecuacion de espacio de estados, adicionalmente se realiza el auto tuning utilizando el metodo escojido por le usuario
:param A: Matriz de estados
:type A: list
:param B: Matriz de entrada
:type B: list
:param C: Matriz de salida
:type C: list
:param D: Matriz de transmision directa
:type D: list
"""
if not self.main.ssdiscretocheckBox2.isChecked(
) and self.main.ssdelaycheckBox2.isChecked():
delay = json.loads(self.main.ssdelayEdit2.text())
else:
delay = 0
system = ctrl.StateSpace(A, B, C, D, delay=delay)
t = self.main.pidTiempoSlider.value()
T = np.arange(0, t, 0.05)
U = np.ones_like(T)
t_temp, y, _ = ctrl.forced_response(system, T, U)
dc_gain = ctrl.dcgain(system)
# Parametros del modelo
K_proceso, tau, alpha = model_method(self, t_temp, y, dc_gain)
# Auto tunning
try:
kp, ki, kd = auto_tuning_method(self, K_proceso, tau, alpha, self.main.ssAutoTuningcomboBox2.currentText())
except TypeError:
raise TypeError('Alfa es muy pequeño')
# En caso de que el sistema sea discreto
if self.main.ssdiscretocheckBox2.isChecked():
pid = ctrl.TransferFunction([
2*kd + 2 * self.dt * kp + ki * self.dt**2,
-2 * self.dt * kp - 4*kd + ki * self.dt**2,
2 * kd
], [2 * self.dt, -2 * self.dt, 0],
self.dt)
system = ctrl.sample_system(system, self.dt, self.main.sscomboBox2.currentText())
if self.main.ssdelaycheckBox2.isChecked():
delayV = [0] * (int(json.loads(self.main.ssdelayEdit2.text()) / self.dt) + 1)
delayV[0] = 1
system_delay = system * ctrl.TransferFunction([1], delayV, self.dt)
system_delay = ctrl.feedback(pid * system_delay)
else:
system_delay = None
system_ss = system
system = ctrl.feedback(pid * system)
else:
system_ss = system
system_delay = system
try:
if ctrl.isdtime(system, strict=True):
T = np.arange(0, t+self.dt, self.dt)
else:
T = np.arange(0, t+0.05, 0.05)
except ValueError:
T = np.arange(0, 100, 0.05)
return system, T, system_delay, system_ss, kp, ki, kd
[documentos]def model_method(self, t, y, dc_gain):
"""
Funcion para obtener los parametros del modelo de primer orden de un sistema a partir de su respuesta escalon
:param t: Vector de tiempo
:type t: numpyArray
:param y: Vector de respuesta
:type y: numpyArray
:param dc_gain: Ganancia DC del sistema
:type dc_gain: float
"""
i_max = np.argmax(np.abs(np.gradient(y)))
for index, i in enumerate(y):
if i >= 0.63 * dc_gain:
indexv = index
break
slop = (y[i_max] - y[i_max - 1]) / (t[i_max] - t[i_max - 1])
x1 = (0 - y[i_max]) / (slop) + t[i_max]
x2 = t[indexv]
y2 = slop * (x2 - t[i_max]) + y[i_max]
tau = x2 - x1
if self.main.tfdelaycheckBox2.isChecked():
alpha = x1 + json.loads(self.main.tfdelayEdit2.text())
else:
alpha = x1
K_proceso = y[-1] / 1
return K_proceso, tau, alpha
[documentos]def auto_tuning_method(self, k_proceso, tau, alpha, metodo):
"""
Funcion para obtener las ganancias del controlador PID a partir de los parametros del modelo de primer orden obtenidos de una respuesta escalon, las formulas son las dadas por Ziegler-Nichols y Cohen-Coon para una respuesta escalon en lazo abierto
:param k_proceso: Ganancia del proceso
:type k_proceso: float
:param tau: Constante de tiempo del proceso
:type tau: float
:param alpha: Tiempo muerto o delay del proceso
:type alpha: float
:param metodo: Metodo a utilizar
:type metodo: str
"""
if alpha <= 0.05:
print('Alfa es demasiado pequeño')
raise TypeError(' Alfa es demasiado pequeño')
if 'ZN' in metodo:
if 'P--' in metodo:
kp = (1/k_proceso) * (tau/alpha)
ti = np.infty
td = 0
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(False)
self.main.kdCheckBox2.setChecked(False)
if 'PI-' in metodo:
kp = (0.9/k_proceso) * (tau/alpha)
ti = alpha * 3.33
td = 0
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(True)
self.main.kdCheckBox2.setChecked(False)
if 'PID' in metodo:
kp = (1.2/k_proceso) * (tau/alpha)
ti = alpha * 2
td = alpha * 0.5
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(True)
self.main.kdCheckBox2.setChecked(True)
kp = kp
ki = kp / ti
kd = kp * td
if 'CC' in metodo:
if 'P--' in metodo:
kp = (1/k_proceso) * (tau/alpha) * (1 + (1/3) * (alpha/tau))
ti = np.infty
td = 0
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(False)
self.main.kdCheckBox2.setChecked(False)
if 'PI-' in metodo:
kp = (1/k_proceso) * (tau/alpha) * (0.9 + (1/12) * (alpha/tau))
ti = alpha * ((30 + 3 * (alpha/tau)) / (9 + 20 * (alpha/tau)))
td = 0
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(True)
self.main.kdCheckBox2.setChecked(False)
if 'PD-' in metodo:
kp = ((1/k_proceso) * (tau/alpha) * ((5/4) + (1/6) * (alpha/tau)))
ti = np.infty
td = alpha * ((6 - 2 * (alpha/tau)) / (22 + 3 * (alpha/tau)))
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(False)
self.main.kdCheckBox2.setChecked(True)
if 'PID' in metodo:
kp = ((1/k_proceso) * (tau/alpha) * ((4/3) + (1/4) * (alpha/tau)))
ti = alpha * ((32 + 6 * (alpha/tau)) / (13 + 8 * (alpha/tau)))
td = alpha * (4 / (11 + 2 * (alpha/tau)))
self.main.kpCheckBox2.setChecked(True)
self.main.kiCheckBox2.setChecked(True)
self.main.kdCheckBox2.setChecked(True)
kp = kp / 2
ki = kp / ti
kd = kp * td
return kp, ki, kd
[documentos]def rutina_step_plot(self, system, T, kp, ki, kd):
"""
Funcion para obtener la respuesta escalon del sistema en lazo cerrado en combinacion con un controlador PID y su respectiva graficacion
:param system: Representacion del sistema
:type system: LTI
:param T: Vector de tiempo
:type T: numpyArray
:param kp: Ganancia proporcional
:type kp: float
:param ki: Ganancia integral
:type ki: float
:param kd: Ganancia derivativa
:type kd: float
"""
U = np.ones_like(T)
# Discriminacion entre continue y discreto, con delay o sin delay, delay realizado con pade
if ctrl.isdtime(system, strict=True):
t, y, _ = ctrl.forced_response(system, T, U)
elif (
self.main.tfdelaycheckBox2.isChecked() and
self.main.PIDstackedWidget.currentIndex() == 0
):
pade = ctrl.TransferFunction(
*ctrl.pade(json.loads(self.main.tfdelayEdit2.text()), 10)
)
N = self.main.pidNSlider.value()
pid = ctrl.TransferFunction([N*kd+kp, N*kp+ki, N*ki], [1, N, 0])
system = ctrl.feedback(pid * system * pade)
t, y, _ = ctrl.forced_response(system, T, U)
elif (
self.main.ssdelaycheckBox2.isChecked() and
self.main.PIDstackedWidget.currentIndex() == 1
):
pade = ctrl.TransferFunction(
*ctrl.pade(json.loads(self.main.ssdelayEdit2.text()), 10)
)
N = self.main.pidNSlider.value()
pid = ctrl.TransferFunction([N*kd+kp, N*kp+ki, N*ki], [1, N, 0])
system = ctrl.feedback(pid * system * pade)
t, y, _ = ctrl.forced_response(system, T, U)
else:
N = self.main.pidNSlider.value()
pid = ctrl.TransferFunction([N*kd + kp, N*kp + ki, N * ki], [1, N, 0])
system = ctrl.feedback(pid * system)
t, y, _ = ctrl.forced_response(system, T, U)
if ctrl.isdtime(system, strict=True):
y = y[0]
y = np.clip(y, -1e12, 1e12)
self.main.stepGraphicsView2.curva.setData(t, y[:-1], stepMode=True)
else:
y = np.clip(y, -1e12, 1e12)
self.main.stepGraphicsView2.curva.setData(t, y, stepMode=False)
return t, y
[documentos]def rutina_system_info(self, system, T, y, kp=0, ki=0, kd=0, autotuning=False):
"""
Funcion para mostrar los resultados obtenidos de los calculos en un TextEdit
:param system: Representacion del sistema
:type system: LTI
:param T: Vector de tiempo
:type T: numpyArray
:param y: Vector de respuesta
:type y: numpyArray
:param kp: Ganancia proporcional, defaults to 0
:type kp: float, optional
:param ki: Ganancia integral, defaults to 0
:type ki: float, optional
:param kd: Ganancia derivativa, defaults to 0
:type kd: float, optional
:param autotuning: Bandera para señar si es o no una operacion con auto tunning, defaults to False
:type autotuning: bool, optional
"""
# Informacion del step
try:
info = ctrl.step_info(system, T=T, yout=y)
except:
info = {
'RiseTime':np.NaN,
'SettlingTime':np.NaN,
'SettlingMax': np.NaN,
'SettlingMin': np.NaN,
'Overshoot': np.NaN,
'Undershoot': np.NaN,
'Peak': np.NaN,
'PeakTime': np.NaN,
'SteadyStateValue': np.NaN
}
Datos = ""
Datos += str(system) + "\n"
if self.main.tfdelaycheckBox2.isChecked() and self.main.PIDstackedWidget.currentIndex(
) == 0:
delay = json.loads(self.main.tfdelayEdit2.text())
Datos += f"Delay: {delay}\n"
elif self.main.ssdelaycheckBox2.isChecked(
) and self.main.PIDstackedWidget.currentIndex() == 1:
delay = json.loads(self.main.ssdelayEdit2.text())
Datos += f"Delay: {delay}\n"
else:
delay = 0
Datos += "----------------------------------------------\n"
for k, v in info.items():
Datos += f"{k} : {v:5.3f}\n"
dcgain = ctrl.dcgain(system)
Datos += f"Ganancia DC: {real(dcgain):5.3f}\n"
if autotuning:
Datos += "----------------------------------------------\n"
Datos += f"Kp: {kp:.4f}\n"
Datos += f"Ki: {ki:.4f}\n"
Datos += f"Kd: {kd:.4f}\n"
if self.main.PIDstackedWidget.currentIndex() == 0:
self.main.tfdatosTextEdit2.setPlainText(Datos)
else:
self.main.ssdatosTextEdit2.setPlainText(Datos)