El algoritmo PID en diferentes lenguajes de programación
¡Hola a todos los entusiastas de la programación y la automatización! Hoy tengo algo realmente emocionante que compartir con vosotros. Ya sabéis que tengo una especie de obsesión (¡pero de las buenas!) con el algoritmo PID. Es esa maravilla de la ingeniería que mantiene todo en equilibrio, desde nuestros termostatos hasta los drones. Bueno, pues cada vez que encuentro un momento libre, me zambullo de cabeza en el mundo de PID, experimentando con pequeñas maquetas y desarrollos para ver qué puedo crear.
Pero esta vez, he decidido llevar mi pasión un paso más allá. ¿Habéis oído hablar del ranking PYPL (PopularitY of Programming Language Index)? Es como la lista de éxitos para los lenguajes de programación, mostrándonos cuáles son los más utilizados en este loco mundo de la tecnología. Así que, ¿qué mejor manera de celebrar mi amor por el PID y la programación que combinándolos?
He preparado algo especial: una implementación básica del algoritmo PID en los diez primeros lenguajes de programación del ranking PYPL. ¡Sí, habéis leído bien! Desde Python hasta JavaScript, pasando por Swift y muchos más. Voy a desglosar cómo se puede implementar este algoritmo en cada uno de estos lenguajes populares, mostrando sus peculiaridades y sus puntos fuertes.
Así que, si eres un fanático del control automático o simplemente un curioso de la programación, te invito a seguirme en esta aventura. Vamos a explorar juntos cómo el algoritmo PID puede cobrar vida de formas nuevas y emocionantes. ¡Prepárate para una mezcla de código, creatividad y, por supuesto, un poco de diversión técnica!
Python
PIDController <- setRefClass( "PIDController", fields = list( kp = "numeric", ki = "numeric", kd = "numeric", sample_time = "numeric", integral_limit = "numeric", last_error = "numeric", integral = "numeric", last_update_time = "numeric" ), methods = list( initialize = function(kp, ki, kd, sample_time, integral_limit) { kp <<- kp ki <<- ki kd <<- kd sample_time <<- sample_time integral_limit <<- integral_limit last_error <<- 0 integral <<- 0 last_update_time <<- Sys.time() }, update = function(setpoint, current_value) { now <- Sys.time() delta_time <- difftime(now, last_update_time, units = "secs") if (as.numeric(delta_time) >= sample_time) { last_update_time <<- now error <- setpoint - current_value # Término proporcional P <- kp * error # Término integral integral <<- integral + error * as.numeric(delta_time) integral <<- min(max(integral, -integral_limit), integral_limit) I <- ki * integral # Término derivativo derivative <- (error - last_error) / as.numeric(delta_time) D <- kd * derivative # Actualizar último error last_error <<- error return(P + I + D) } else { return(0) # No hay cambio en la salida si no ha pasado el tiempo de muestreo } } ) ) # Ejemplo de uso del controlador PID pid <- PIDController$new(kp = 0.1, ki = 0.01, kd = 0.1, sample_time = 1, integral_limit = 10) setpoint <- 100 current_value <- 90 # Función de bucle para simular el control loop <- function() { control <- pid$update(setpoint, current_value) print(paste("Control output:", control)) Sys.sleep(1) # Espera un segundo antes de la próxima actualización } # Ejecutar el bucle (detenerlo manualmente) while(TRUE) { loop() }
Java
public class PIDController { private double kp; // Coeficiente proporcional private double ki; // Coeficiente integral private double kd; // Coeficiente derivativo private long sampleTime; // Tiempo de muestreo en milisegundos private double integralLimit; // Límite para prevenir el windup private double lastError; private double integral; private long lastUpdateTime; public PIDController(double kp, double ki, double kd, long sampleTime, double integralLimit) { this.kp = kp; this.ki = ki; this.kd = kd; this.sampleTime = sampleTime; this.integralLimit = integralLimit; this.lastError = 0; this.integral = 0; this.lastUpdateTime = System.currentTimeMillis(); } public double update(double setpoint, double currentValue) { long now = System.currentTimeMillis(); long timeChange = now - lastUpdateTime; if (timeChange >= sampleTime) { lastUpdateTime = now; double error = setpoint - currentValue; // Término proporcional double P = kp * error; // Término integral integral += error * (timeChange / 1000.0); // Convertir a segundos integral = Math.max(Math.min(integral, integralLimit), -integralLimit); double I = ki * integral; // Término derivativo double derivative = (error - lastError) / (timeChange / 1000.0); double D = kd * derivative; // Actualizar último error lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } // Métodos getters y setters para kp, ki, kd, etc., si son necesarios } public class Main { public static void main(String[] args) { PIDController pid = new PIDController(0.1, 0.01, 0.1, 1000, 10.0); // Ejemplo de parámetros double setpoint = 100; double currentValue = 90; while (true) { double control = pid.update(setpoint, currentValue); System.out.println("Control output: " + control); // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador try { Thread.sleep(100); // Espera un poco antes de la próxima actualización } catch (InterruptedException e) { e.printStackTrace(); } } } }
Javascript
class PIDController { constructor(kp, ki, kd, sampleTime, integralLimit) { this.kp = kp; // Coeficiente proporcional this.ki = ki; // Coeficiente integral this.kd = kd; // Coeficiente derivativo this.sampleTime = sampleTime; // Tiempo de muestreo en segundos this.integralLimit = integralLimit; // Límite para prevenir el windup this.currentValue = 0; this.lastError = 0; this.integral = 0; this.lastUpdateTime = Date.now(); } update(setpoint, currentValue) { const now = Date.now(); const deltaTime = (now - this.lastUpdateTime) / 1000; // Convertir a segundos this.lastUpdateTime = now; // Asegurarse de que estamos actualizando en el tiempo de muestreo establecido if (deltaTime >= this.sampleTime) { const error = setpoint - currentValue; // Término proporcional const P = this.kp * error; // Término integral this.integral += error * deltaTime; this.integral = Math.max(Math.min(this.integral, this.integralLimit), -this.integralLimit); // Limitar el término integral const I = this.ki * this.integral; // Término derivativo const derivative = (error - this.lastError) / deltaTime; const D = this.kd * derivative; // Actualizar último error this.lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } } // Ejemplo de uso del controlador PID const pid = new PIDController(0.1, 0.01, 0.1, 1, 10); // Ejemplo de parámetros const setpoint = 100; let currentValue = 90; // Suponer que esta función se llama repetidamente, por ejemplo, en un bucle o con setInterval function loop() { const control = pid.update(setpoint, currentValue); console.log("Control output:", control); // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador } // Suponiendo un bucle de control que se actualiza cada segundo setInterval(loop, 1000);
C++
#include <iostream> #include <chrono> #include <thread> class PIDController { private: double kp; // Coeficiente proporcional double ki; // Coeficiente integral double kd; // Coeficiente derivativo double sampleTime; // Tiempo de muestreo en segundos double integralLimit; // Límite para prevenir el windup double lastError; double integral; std::chrono::steady_clock::time_point lastUpdateTime; public: PIDController(double kp, double ki, double kd, double sampleTime, double integralLimit) : kp(kp), ki(ki), kd(kd), sampleTime(sampleTime), integralLimit(integralLimit), lastError(0), integral(0) { lastUpdateTime = std::chrono::steady_clock::now(); } double update(double setpoint, double currentValue) { auto now = std::chrono::steady_clock::now(); std::chrono::duration<double> elapsed = now - lastUpdateTime; if (elapsed.count() >= sampleTime) { lastUpdateTime = now; double error = setpoint - currentValue; // Término proporcional double P = kp * error; // Término integral integral += error * elapsed.count(); integral = std::max(std::min(integral, integralLimit), -integralLimit); double I = ki * integral; // Término derivativo double derivative = (error - lastError) / elapsed.count(); double D = kd * derivative; // Actualizar último error lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } }; int main() { PIDController pid(0.1, 0.01, 0.1, 1.0, 10.0); // Ejemplo de parámetros double setpoint = 100; double currentValue = 90; while (true) { double control = pid.update(setpoint, currentValue); std::cout << "Control output: " << control << std::endl; // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador std::this_thread::sleep_for(std::chrono::seconds(1)); // Espera un segundo antes de la próxima actualización } return 0; }
C#
using System; using System.Threading; public class PIDController { private double kp; // Coeficiente proporcional private double ki; // Coeficiente integral private double kd; // Coeficiente derivativo private int sampleTime; // Tiempo de muestreo en milisegundos private double integralLimit; // Límite para prevenir el windup private double lastError; private double integral; private DateTime lastUpdateTime; public PIDController(double kp, double ki, double kd, int sampleTime, double integralLimit) { this.kp = kp; this.ki = ki; this.kd = kd; this.sampleTime = sampleTime; this.integralLimit = integralLimit; this.lastError = 0; this.integral = 0; this.lastUpdateTime = DateTime.Now; } public double Update(double setpoint, double currentValue) { var now = DateTime.Now; var timeChange = (now - lastUpdateTime).TotalMilliseconds; if (timeChange >= sampleTime) { lastUpdateTime = now; double error = setpoint - currentValue; // Término proporcional double P = kp * error; // Término integral integral += error * (timeChange / 1000.0); // Convertir a segundos integral = Math.Max(Math.Min(integral, integralLimit), -integralLimit); double I = ki * integral; // Término derivativo double derivative = (error - lastError) / (timeChange / 1000.0); double D = kd * derivative; // Actualizar último error lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } } class Program { static void Main() { PIDController pid = new PIDController(0.1, 0.01, 0.1, 1000, 10.0); // Ejemplo de parámetros double setpoint = 100; double currentValue = 90; while (true) { double control = pid.Update(setpoint, currentValue); Console.WriteLine("Control output: " + control); // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador Thread.Sleep(100); // Espera un poco antes de la próxima actualización } } }
PHP
<?php class PIDController { private $kp; // Coeficiente proporcional private $ki; // Coeficiente integral private $kd; // Coeficiente derivativo private $sampleTime; // Tiempo de muestreo en segundos private $integralLimit; // Límite para prevenir el windup private $lastError; private $integral; private $lastUpdateTime; public function __construct($kp, $ki, $kd, $sampleTime, $integralLimit) { $this->kp = $kp; $this->ki = $ki; $this->kd = $kd; $this->sampleTime = $sampleTime; $this->integralLimit = $integralLimit; $this->lastError = 0; $this->integral = 0; $this->lastUpdateTime = microtime(true); } public function update($setpoint, $currentValue) { $now = microtime(true); $timeChange = $now - $this->lastUpdateTime; if ($timeChange >= $this->sampleTime) { $this->lastUpdateTime = $now; $error = $setpoint - $currentValue; // Término proporcional $P = $this->kp * $error; // Término integral $this->integral += $error * $timeChange; $this->integral = max(min($this->integral, $this->integralLimit), -$this->integralLimit); $I = $this->ki * $this->integral; // Término derivativo $derivative = ($error - $this->lastError) / $timeChange; $D = $this->kd * $derivative; // Actualizar último error $this->lastError = $error; return $P + $I + $D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } } // Ejemplo de uso del controlador PID $pid = new PIDController(0.1, 0.01, 0.1, 1, 10); $setpoint = 100; $currentValue = 90; // En un entorno de script PHP, el siguiente bucle no sería práctico. // En un entorno de servidor web, la lógica de actualización debería ser gestionada de manera diferente. while (true) { $control = $pid->update($setpoint, $currentValue); echo "Control output: " . $control . "\n"; usleep(100000); // Espera 0.1 segundos (100000 microsegundos) }
R
#include <iostream> #include <chrono> #include <thread> class PIDController { private: double kp; // Coeficiente proporcional double ki; // Coeficiente integral double kd; // Coeficiente derivativo double sampleTime; // Tiempo de muestreo en segundos double integralLimit; // Límite para prevenir el windup double lastError; double integral; std::chrono::steady_clock::time_point lastUpdateTime; public: PIDController(double kp, double ki, double kd, double sampleTime, double integralLimit) : kp(kp), ki(ki), kd(kd), sampleTime(sampleTime), integralLimit(integralLimit), lastError(0), integral(0) { lastUpdateTime = std::chrono::steady_clock::now(); } double update(double setpoint, double currentValue) { auto now = std::chrono::steady_clock::now(); std::chrono::duration<double> elapsed = now - lastUpdateTime; if (elapsed.count() >= sampleTime) { lastUpdateTime = now; double error = setpoint - currentValue; // Término proporcional double P = kp * error; // Término integral integral += error * elapsed.count(); integral = std::max(std::min(integral, integralLimit), -integralLimit); double I = ki * integral; // Término derivativo double derivative = (error - lastError) / elapsed.count(); double D = kd * derivative; // Actualizar último error lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } }; int main() { PIDController pid(0.1, 0.01, 0.1, 1.0, 10.0); // Ejemplo de parámetros double setpoint = 100; double currentValue = 90; while (true) { double control = pid.update(setpoint, currentValue); std::cout << "Control output: " << control << std::endl; // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador std::this_thread::sleep_for(std::chrono::seconds(1)); // Espera un segundo antes de la próxima actualización } return 0; }
Typescript
class PIDController { private kp: number; // Coeficiente proporcional private ki: number; // Coeficiente integral private kd: number; // Coeficiente derivativo private sampleTime: number; // Tiempo de muestreo en milisegundos private integralLimit: number; // Límite para prevenir el windup private lastError: number; private integral: number; private lastUpdateTime: number; constructor(kp: number, ki: number, kd: number, sampleTime: number, integralLimit: number) { this.kp = kp; this.ki = ki; this.kd = kd; this.sampleTime = sampleTime; this.integralLimit = integralLimit; this.lastError = 0; this.integral = 0; this.lastUpdateTime = Date.now(); } public update(setpoint: number, currentValue: number): number { const now = Date.now(); const deltaTime = now - this.lastUpdateTime; if (deltaTime >= this.sampleTime) { this.lastUpdateTime = now; const error = setpoint - currentValue; // Término proporcional const P = this.kp * error; // Término integral this.integral += error * (deltaTime / 1000); // Convertir a segundos this.integral = Math.max(Math.min(this.integral, this.integralLimit), -this.integralLimit); const I = this.ki * this.integral; // Término derivativo const derivative = (error - this.lastError) / (deltaTime / 1000); const D = this.kd * derivative; // Actualizar último error this.lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } } // Ejemplo de uso const pid = new PIDController(0.1, 0.01, 0.1, 1000, 10.0); const setpoint = 100; let currentValue = 90; // Simulación de un bucle de control setInterval(() => { const control = pid.update(setpoint, currentValue); console.log("Control output:", control); // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador }, 100);
Swift
import Foundation class PIDController { private var kp: Double // Coeficiente proporcional private var ki: Double // Coeficiente integral private var kd: Double // Coeficiente derivativo private var sampleTime: TimeInterval // Tiempo de muestreo en segundos private var integralLimit: Double // Límite para prevenir el windup private var lastError: Double private var integral: Double private var lastUpdateTime: Date init(kp: Double, ki: Double, kd: Double, sampleTime: TimeInterval, integralLimit: Double) { self.kp = kp self.ki = ki self.kd = kd self.sampleTime = sampleTime self.integralLimit = integralLimit self.lastError = 0 self.integral = 0 self.lastUpdateTime = Date() } func update(setpoint: Double, currentValue: Double) -> Double { let now = Date() let timeChange = now.timeIntervalSince(lastUpdateTime) if timeChange >= sampleTime { lastUpdateTime = now let error = setpoint - currentValue // Término proporcional let P = kp * error // Término integral integral += error * timeChange integral = max(min(integral, integralLimit), -integralLimit) let I = ki * integral // Término derivativo let derivative = (error - lastError) / timeChange let D = kd * derivative // Actualizar último error lastError = error return P + I + D } else { return 0 // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } } // Ejemplo de uso let pid = PIDController(kp: 0.1, ki: 0.01, kd: 0.1, sampleTime: 1.0, integralLimit: 10.0) let setpoint = 100.0 var currentValue = 90.0 // Bucle de control (simulación) Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in let control = pid.update(setpoint: setpoint, currentValue: currentValue) print("Control output: \(control)") // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador }
Objetive-C
#import <Foundation/Foundation.h> @interface PIDController : NSObject { double kp; // Coeficiente proporcional double ki; // Coeficiente integral double kd; // Coeficiente derivativo NSTimeInterval sampleTime; // Tiempo de muestreo en segundos double integralLimit; // Límite para prevenir el windup double lastError; double integral; NSDate *lastUpdateTime; } - (id)initWithKp:(double)kp ki:(double)ki kd:(double)kd sampleTime:(NSTimeInterval)sampleTime integralLimit:(double)integralLimit; - (double)updateWithSetpoint:(double)setpoint currentValue:(double)currentValue; @end @implementation PIDController - (id)initWithKp:(double)kpValue ki:(double)kiValue kd:(double)kdValue sampleTime:(NSTimeInterval)sampleTimeValue integralLimit:(double)integralLimitValue { self = [super init]; if (self) { kp = kpValue; ki = kiValue; kd = kdValue; sampleTime = sampleTimeValue; integralLimit = integralLimitValue; lastError = 0; integral = 0; lastUpdateTime = [NSDate date]; } return self; } - (double)updateWithSetpoint:(double)setpoint currentValue:(double)currentValue { NSDate *now = [NSDate date]; NSTimeInterval timeChange = [now timeIntervalSinceDate:lastUpdateTime]; if (timeChange >= sampleTime) { lastUpdateTime = now; double error = setpoint - currentValue; // Término proporcional double P = kp * error; // Término integral integral += error * timeChange; integral = MAX(MIN(integral, integralLimit), -integralLimit); double I = ki * integral; // Término derivativo double derivative = (error - lastError) / timeChange; double D = kd * derivative; // Actualizar último error lastError = error; return P + I + D; } else { return 0; // No hay cambio en la salida si no ha pasado el tiempo de muestreo } } @end int main(int argc, const char * argv[]) { @autoreleasepool { PIDController *pid = [[PIDController alloc] initWithKp:0.1 ki:0.01 kd:0.1 sampleTime:1.0 integralLimit:10.0]; double setpoint = 100.0; double currentValue = 90.0; // Bucle de control (simulación) while (true) { double control = [pid updateWithSetpoint:setpoint currentValue:currentValue]; NSLog(@"Control output: %f", control); [NSThread sleepForTimeInterval:0.1]; // Espera un poco antes de la próxima actualización // Aquí iría la lógica para actualizar el 'currentValue' basado en la salida del controlador } } return 0; }
La imagen de portada de la entrada ha sido generada con ChatGPT.