El algoritmo PID en diferentes lenguajes de programación

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.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Información básica sobre protección de datos
Responsable Garikoitz Martínez Moreno +info...
Finalidad Gestionar y moderar tus comentarios. +info...
Legitimación Consentimiento del interesado. +info...
Destinatarios Automattic Inc., EEUU para filtrar el spam. +info...
Derechos Acceder, rectificar y cancelar los datos, así como otros derechos. +info...
Información adicional Puedes consultar la información adicional y detallada sobre protección de datos en nuestra página de política de privacidad.