当前位置: 首页 > news >正文

深入解析PID控制算法:从理论到实践的完整指南

前言

大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论学习打下基础。 在很多的自动化控制领域。都会遇到PID控制算法,这种算法具有很好的控制模式,可以让系统具有很好的鲁棒性。

基本介绍

PID 深入理解

(1)闭环控制系统:讲解 PID 之前,我们先解释什么是闭环控制系统。简单说就是一个有输入有输出的系统,输入能影响输出。一般情况下,人们也称输出为反馈,因此也叫闭环反馈控制系统。比如恒温水池,输入就是加热功率,输出就是水温度;比如冷库,输入是空调功率,输出是内部温度。

(2)什么是PID:英文分解开就是:比例(proportional)、积分(integral)、微分(derivative),其根据系统反馈,通过比例,积分和微分三个部分的计算,动态调整系统输入,确保被控量稳定在人们设定的目标值附近。PID 是目前最常见的应用于闭环反馈控制系统的算法,三个部分可以只用一个(P,I,D),也可以只用两个(PI,PD),也可以三个一起用(PID),非常灵活。

(3)PID控制原理图与表达式:

上面的控制原理图与下面的数学表达式是相互对应的。

setpoint 为设定值,也叫目标值;output(t) 是系统反馈值,随时间变化;e(t) 是设定值与反馈值的差值,由于反馈总是作为被减数,因此也称为负反馈控制算法;Kp 是比例系数,Kp * e(t) 就是 PID 的比例部分;Ki 是积分系数,Ki 乘以 e(t) 对时间的积分,就是 PID 的积分部分;Kd 是微分系数,Kd 乘以 e(t) 对时间的微分,就是 PID 的微分部分。通常情况下,三个系数都是正数,但三个部分正负号并不一定相同,相互之间有抵消和补偿。三个部分之和,就是系统输入值 input(t)。整个控制系统的目标就是让差值 e(t) 稳定到 0。

(4)我们以恒温水池为例,讲解 PID 的三个部分:其中 input(t) 为加热功率,output(t) 为水池温度,setpoint 假设为 36 度, e(t) 为 setpoint 与当前温度的差值 。

比例部分:比例部分最直观,也比较容易理解,举例而言:假设当前水温为 20 度,差值 e 为 36 - 20 = 16 度,乘上比例系数 Kp ,得到加热功率,于是温度就会慢慢上涨;如果水温超过了设定温度,比如 40 度,差值 e 为 36 - 40 = -4 度,则停止加热,让热量耗散,温度就会慢慢下降。

微分部分:只有比例部分,我们可以想象出水池温度的变化通常会比较大,而且很难恒定,这样的水池不能算是恒温水池。解决办法是引入差值 e(t) 的微分,也就是 e(t) 对时间的导数。通过数学计算,可得导数为水池温度的斜率负数:

根据求导结果,我们分两种情况讨论微分部分对比例部分的作用:当差值 e(t) 扩大时:微分部分将与比例部分同正负号,对比例部分进行补偿,更好的抑制差值扩大;当差值 e(t) 缩小时:微分部分将与比例部分异号,对比例部分进行抵消,防止系统输出过冲。综合两种情况,可以认为微分部分提供了一种预测性的调控作用,通过考虑差值 e(t) 的未来走势,更精细地调整系统输入,从而让系统输出逐渐收敛到目标值。

积分部分:只有比例和微分部分,在某些场景下会失灵。举例而言,假如我们只使用 PD 算法。此时水池的室外温度非常低,热量散失非常快。当加热到某个温度的时候(比如 30 度),温度可能再也无法上涨。这种情况,称之为系统的稳态误差。我们分两部分解释原因:比例部分:由于差值 e(t) 不那么大了,比例部分会比较小,每次增加的热量正好被耗散掉,因此温度不会继续上升;微分部分:由于温度基本恒定,微分部分将约为零,也无法对比例部分进行补偿。解决办法是引入差值 e(t) 的积分,也就是 e(t) 乘以单位时间并不断累加,数学表达式如下:

假设温度停在了 30 度,不再上升,此时,积分部分会随着时间的推移而不断增加,相当于对比例部分进行补偿,从而增加加热功率,最终温度将继续上升。下面的动图比较形象地展示了三个参数对系统输出的影响:

(5)PID 为什么被称为启发式控制算法:

第一,PID 的三个参数并非基于严格的数学计算得到,而是靠工程师的直觉和经验。第二,PID 算法调参的目标是可用,只要实际效果不错就行,并不追求最优解。第三,PID 不依赖精确的数学模型,就能进行有效的控制。因此看起来更像是一种基于实践和实际效果的启发式方法,而不是一个理论上推导出来的控制策略。(6)介绍一种 PID 调参方法:Ziegler-Nichols(齐格勒-尼科尔斯)最终值振荡法第一,将微分系数 Kd 和积分系数 Kp 都设置为 0,只保留比例系数。第二,不断增加比例系数,直到达到无衰减的持续振荡,此时的比例系数称为 Ku ,此时的振荡周期为 Tu。第三,使用临界系数和振荡周期设置 PID 参数:

比例系数:Kp = 0.60 * Ku积分系数:Ki = 2 * Kp / Tu微分系数:Kd = Kp * Tu / 8

PID 编码实现

这部分我们主要参考 Arduino 的 PID 库 Arduino-PID-Library,分八步实现一个实际可用的 PID 算法库。接下来的每一步都需要大家认真的阅读,因为涉及到很多的细节。

特别提示:由于本节讲解 PID 的实现,我们将以 PID 作为第一视角,如果提到 input ,指的是 PID 算法输入,相当于上节中的系统输出 output(t),即恒温水池的温度;如果提到 ouput,指的是 PID 算法输出,相当于上节中的系统输入 input(t),即加热功率。

初始版本

代码实现 PID 算法,面临最大的困惑是如何实现积分和微分。正如上一节所说,积分可转化为差值 e(t) 乘以采样间隔并不断累加;微分可转换为求两次采样的差值 e(t) 的斜率。于是有了如下代码,请读者关注代码注释(可以直接拿去跑)。

#include <iostream>#include <chrono>#include <thread>

class PIDController {public:explicit PIDController() {InitTime();}

PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();
}void InitTime() {last_time_ = GetMillis();
}double Compute(double setpoint, double input) {uint64_t now = GetMillis();double time_change = static_cast<double>(now - last_time_);double error = setpoint - input;printf("error: %f\n", error);err_sum_ += error * time_change;double derivative = (error - last_error_) / time_change;double output = kp_ * error + ki_ * err_sum_ + kd_ * derivative;last_error_ = error;last_time_ = now;return output;
}void set_tunings(double kp_para, double ki_para, double kd_para) {kp_ = kp_para;ki_ = ki_para;kd_ = kd_para;
}

private:double kp_;double ki_;double kd_;

double last_error_ = 0;
double err_sum_ = 0;    
uint64_t last_time_ = 0; uint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

};

int main() {PIDController pid;pid.set_tunings(10, 0.01, 0.01);

double setpoint = 36;
double temperature = 20;std::this_thread::sleep_for(std::chrono::seconds(1));for (int i = 0; i < 100; ++i) {double control_signal = pid.Compute(setpoint, temperature);temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "Temperature: " << temperature << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));
}return 0;

}

固定采样间隔

初始版本的 PID 的采样间隔是由外部循环控制的,会导致两个问题:第一,无法获取一致的 PID 行为,因为外部有可能调用,也有可能不调用;第二,每次都要根据采样间隔计算微分和积分部分,这涉及到浮点运算。效率比较低。好的办法是固定采用间隔,两个问题都能解决,看下面的代码以及注释(可以直接拿去跑)。

#include <iostream>#include <chrono>#include <thread>

class PIDController {public:explicit PIDController() {InitTime();}

PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();
}void InitTime() {last_time_ = GetMillis();
}void set_tunings(double kp_para, double ki_para, double kd_para) {double sample_time_in_sec = static_cast<double>(sample_time_) / 1000.0;kp_ = kp_para;// sum = ki * (error(0) * dt + error(1) * dt + ... + error(n) * dt) = (ki * dt) * (error(0) + error(1) + ... + error(n))ki_ = ki_para * sample_time_in_sec;// derivative = kd * (error(n) - error(n-1)) / dt = (kd / dt) * (error(n) - error(n-1))kd_ = kd_para / sample_time_in_sec;
}void set_sample_time(uint64_t new_sample_time) {if (new_sample_time > 0) {double ratio = static_cast<double>(new_sample_time) / static_cast<double>(sample_time_);ki_ = ki_ * ratio;kd_ = kd_ / ratio;sample_time_ = new_sample_time;}
}double Compute(double setpoint, double input) {uint64_t now = GetMillis();uint64_t time_change = now - last_time_;if (time_change < sample_time_) {return last_output_;}double error = setpoint - input;printf("error: %f\n", error);err_sum_ += error;double derivative = error - last_error_;double output = kp_ * error + ki_ * err_sum_ + kd_ * derivative;last_error_ = error;last_time_ = now;last_output_ = output;return output;
}

private:double kp_;double ki_;double kd_;

double last_error_ = 0.0;
double err_sum_ = 0.0;
uint64_t last_time_ = 0UL;double last_output_ = 0.0;
uint64_t sample_time_ = 1000UL; // 1 seconduint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

};

int main() {PIDController pid;pid.set_tunings(1, 0.2, 0.02);pid.set_sample_time(1000); // Set sample time to 1 second

double setpoint = 36;
double temperature = 20;std::this_thread::sleep_for(std::chrono::seconds(1));for (int i = 0; i < 1000; ++i) {double control_signal = pid.Compute(setpoint, temperature);temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "Temperature: " << temperature << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(200));
}return 0;

}

消除 spike

spike 的英文含义是尖刺,这里指的是当系统运行过程中,突然改变 setpoint 时, PID 的微分部分会因 setpoint 的突然切换而生成一个极大的导数,导致算法输出值 output 将产生一次急剧变化,这就是 spike。比如恒温水池的初始 setpoint 是 36 度,运行过程中,突然改为 50 度。相当于在一个采样周期内,差值 error 突然增加了 14 ,再除以采样周期,数值将会非常大,如下图所示。

解决办法是将 setpoint 从 PID 的微分部分请出去,理论依据是:差值 error 的导数也是算法输入(恒温水池的温度)的斜率负数:

代码实现如下:

#include <iostream>#include <chrono>#include <thread>

class PIDController {public:explicit PIDController() {InitTime();}PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();}

void InitTime() {last_time_ = GetMillis();
}void set_tunings(double kp_para, double ki_para, double kd_para) {double sample_time_in_sec = static_cast<double>(sample_time_) / 1000.0;kp_ = kp_para;ki_ = ki_para * sample_time_in_sec;kd_ = kd_para / sample_time_in_sec;
}void set_sample_time(uint64_t new_sample_time) {if (new_sample_time > 0) {double ratio = static_cast<double>(new_sample_time) / static_cast<double>(sample_time_);ki_ = ki_ * ratio;kd_ = kd_ / ratio;sample_time_ = new_sample_time;}
}double Compute(double setpoint, double input) {uint64_t now = GetMillis();uint64_t time_change = now - last_time_;if (time_change < sample_time_) {return last_output_;}double error = setpoint - input;printf("error: %f\n", error);err_sum_ += error;double derivative = input - last_input_;double output = kp_ * error + ki_ * err_sum_ - kd_ * derivative;last_input_ = input;last_time_ = now;last_output_ = output;return output;
}

private:double kp_;double ki_;double kd_;

double last_input_ = 0.0;
double err_sum_ = 0.0;
uint64_t last_time_ = 0UL;double last_output_ = 0.0;
uint64_t sample_time_ = 1000UL; // 1 seconduint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

};

int main() {PIDController pid;pid.set_tunings(1, 0.2, 0.02);pid.set_sample_time(1000);

double setpoint = 36;
double temperature = 20;std::this_thread::sleep_for(std::chrono::seconds(1));for (int i = 0; i < 1000; ++i) {double control_signal = pid.Compute(setpoint, temperature);temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "Temperature: " << temperature << std::endl;if (i == 200) {setpoint = 50; std::cout << "Setpoint changed to 50" << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(200));
}return 0;

}

动态改参

好的 PID 算法,允许在系统运行过程中,调整 PID 参数。问题的关键是,运行中途修改 PID 参数,如何保持算法输出仍然平稳,对系统状态不产生额外冲击。仔细分析 PID 的三个部分,当对应的参数改变时,影响最大的是积分部分,比例和微分两部分都只影响当前值,而积分部分将会更改历史值。

解决办法是放弃先计算积分和,最后乘以积分系数的做法,而是让积分系数参与每一次积分运算并累加起来:

如此一来,即使更新了积分参数,也只影响当前值,历史值由于被存储起来,因此不会改变,代码实现如下 。

#include <iostream>#include <chrono>#include <thread>

class PIDController {public:explicit PIDController() {InitTime();}PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();}

void InitTime() {last_time_ = GetMillis();
}void set_tunings(double kp_para, double ki_para, double kd_para) {double sample_time_in_sec = static_cast<double>(sample_time_) / 1000.0;kp_ = kp_para;ki_ = ki_para * sample_time_in_sec;kd_ = kd_para / sample_time_in_sec;
}void set_sample_time(uint64_t new_sample_time) {if (new_sample_time > 0) {double ratio = static_cast<double>(new_sample_time) / static_cast<double>(sample_time_);ki_ = ki_ * ratio;kd_ = kd_ / ratio;sample_time_ = new_sample_time;}
}double Compute(double setpoint, double input) {uint64_t now = GetMillis();uint64_t time_change = now - last_time_;if (time_change < sample_time_) {return last_output_;}double error = setpoint - input;printf("error: %f\n", error);err_item_sum_ += ki_ * error;double derivative = input - last_input_;double output = kp_ * error + err_item_sum_ - kd_ * derivative;last_input_ = input;last_time_ = now;last_output_ = output;return output;
}

private:double kp_;double ki_;double kd_;

double last_input_ = 0.0;
double err_item_sum_ = 0.0;
uint64_t last_time_ = 0UL;double last_output_ = 0.0;
uint64_t sample_time_ = 1000UL; // 1 seconduint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

};

int main() {PIDController pid;pid.set_tunings(1, 0.2, 0.02);pid.set_sample_time(1000);

double setpoint = 36;
double temperature = 20;std::this_thread::sleep_for(std::chrono::seconds(1));for (int i = 0; i < 1000; ++i) {double control_signal = pid.Compute(setpoint, temperature);temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "Temperature: " << temperature << std::endl;if (i == 200) {pid.set_tunings(1, 0.5, 0.02);std::cout << "PID coefficients changed, 1, 0.2, 0.02 ->1, 0.5, 0.02" << std::endl;}    std::this_thread::sleep_for(std::chrono::milliseconds(200));
}return 0;

}

设置算法输出限制

通常情况下,PID 算法输出是有一定限制的,比如恒温水池的加热功率不可能无限大,更不可能小于零。当 PID 的算法输出为负数时,实际是停止加热,也就是功率为零。因此需要给 PID 算法添加限制范围,代码实现如下。补充:为了看到输出限制的作用,这次我们把目标温度定为 90 度。

#include <iostream>#include <chrono>#include <thread>

class PIDController {public:explicit PIDController() {InitTime();}PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();}

void InitTime() {last_time_ = GetMillis();
}void set_tunings(double kp_para, double ki_para, double kd_para) {double sample_time_in_sec = static_cast<double>(sample_time_) / 1000.0;kp_ = kp_para;ki_ = ki_para * sample_time_in_sec;kd_ = kd_para / sample_time_in_sec;
}void set_sample_time(uint64_t new_sample_time) {if (new_sample_time > 0) {double ratio = static_cast<double>(new_sample_time) / static_cast<double>(sample_time_);ki_ = ki_ * ratio;kd_ = kd_ / ratio;sample_time_ = new_sample_time;}
}void set_output_limits(double min, double max) {if (min > max) {return;}out_min_ = min;out_max_ = max;SetLimits(last_output_);SetLimits(err_item_sum_);
}double Compute(double setpoint, double input) {uint64_t now = GetMillis();uint64_t time_change = now - last_time_;if (time_change < sample_time_) {return last_output_;}double error = setpoint - input;printf("error: %f\n", error);err_item_sum_ += ki_ * error;SetLimits(err_item_sum_);double derivative = input - last_input_;double output = kp_ * error + err_item_sum_ - kd_ * derivative;SetLimits(output);last_input_ = input;last_time_ = now;last_output_ = output;return output;
}

private:double kp_;double ki_;double kd_;

double last_input_ = 0.0;
double last_output_ = 0.0;
double err_item_sum_ = 0.0;double out_min_ = 0.0;
double out_max_ = 0.0;uint64_t last_time_ = 0UL;
uint64_t sample_time_ = 1000UL; // 1 seconduint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}void SetLimits(double& val) {if (val > out_max_) {printf("val: %f > out_max_: %f\n", val, out_max_);val = out_max_;} else if (val < out_min_) {printf("val: %f > out_min_: %f\n", val, out_min_);val = out_min_;} else {; // Do nothing}
}    

};

int main() {PIDController pid;pid.set_tunings(1, 0.5, 0.05);pid.set_sample_time(1000);pid.set_output_limits(0, 100);

double setpoint = 90;
double temperature = 20;std::this_thread::sleep_for(std::chrono::seconds(1));for (int i = 0; i < 1000; ++i) {double control_signal = pid.Compute(setpoint, temperature);temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "Temperature: " << temperature << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(200));
}return 0;

}

添加开关控制

好的 PID 算法应允许使用者动态启停,比如恒温水池运行过程中,由于某种原因,管理人员需要停掉自动控制,改为手动控制,操作结束后,重新启动自动控制。实现动态停止并不复杂,只要 PID 内部加一个开关标识,当关闭时,PID 算法内部不执行计算,外部直接使用人工操作值替代算法输出值进行控制。但问题的关键是,当从手动模式重新改为自动模式时,需要保证恒温水池温度不出现大的抖动,即 PID 算法能接续人类的控制状态,实现平滑过渡。解决办法是重新初始化:当从手动切换到自动时,将水池温度和人工操作值传给 PID ,更新 PID 内部的历史输入值和历史积分值。如此一来,当 PID 重新启动时,就能接续人类的控制结果,平滑启动,如图所示。

#include <iostream>#include <chrono>#include <thread>

enum PID_MODE: uint8_t {PID_MODE_MANUAL = 0,PID_MODE_AUTOMATIC = 1};

class PIDController {public:explicit PIDController() {InitTime();}

PIDController(double kp_para, double ki_para, double kd_para) : kp_(kp_para), ki_(ki_para), kd_(kd_para) {InitTime();
}void InitTime() {last_time_ = GetMillis();
}void set_tunings(double kp_para, double ki_para, double kd_para) {double sample_time_in_sec = static_cast<double>(sample_time_) / 1000.0;kp_ = kp_para;ki_ = ki_para * sample_time_in_sec;kd_ = kd_para / sample_time_in_sec;
}void set_sample_time(uint64_t new_sample_time) {if (new_sample_time > 0) {double ratio = static_cast<double>(new_sample_time) / static_cast<double>(sample_time_);ki_ = ki_ * ratio;kd_ = kd_ / ratio;sample_time_ = new_sample_time;}
}void set_output_limits(double min, double max) {if (min > max) {return;}out_min_ = min;out_max_ = max;SetLimits(last_output_);SetLimits(err_item_sum_);
}void InitInnaState(double input, double output) {last_input_ = input;err_item_sum_ = output;SetLimits(err_item_sum_);
}void set_auto_mode(PID_MODE mode, double input = 0.0, double output = 0.0) {bool new_auto = (mode == PID_MODE_AUTOMATIC);if (new_auto == true && in_auto_ == false) {InitInnaState(input, output);}in_auto_ = new_auto;std::cout << "PID mode: " << (in_auto_ ? "Automatic" : "Manual") << std::endl;
}double Compute(double setpoint, double input) {if (in_auto_ == false) {return last_output_;}uint64_t now = GetMillis();uint64_t time_change = now - last_time_;if (time_change < sample_time_) {return last_output_;}double error = setpoint - input;printf("error: %f\n", error);err_item_sum_ += ki_ * error;SetLimits(err_item_sum_);double derivative = input - last_input_;double output = kp_ * error + err_item_sum_ - kd_ * derivative;SetLimits(output);last_input_ = input;last_time_ = now;last_output_ = output;return output;
}

private:double kp_;double ki_;double kd_;

double last_input_ = 0.0;
double last_output_ = 0.0;
double err_item_sum_ = 0.0;double out_min_ = 0.0;
double out_max_ = 0.0;uint64_t last_time_ = 0UL;
uint64_t sample_time_ = 1000UL; // 1 second// PID 内部状态控制量:false 表示手动模式,true 表示自动模式
bool in_auto_ = false;uint64_t GetMillis() {return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}void SetLimits(double& val) {if (val > out_max_) {printf("val: %f > out_max_: %f\n", val, out_max_);val = out_max_;} else if (val < out_min_) {val = out_min_;} else {; // Do nothing}
}

};

int main() {PIDController pid;pid.set_tunings(1, 0.2, 0.02);pid.set_sample_time(1000);pid.set_output_limits(0, 100);

double setpoint = 36.0;
double temperature = 20.0;std::this_thread::sleep_for(std::chrono::seconds(1));pid.set_auto_mode(PID_MODE_AUTOMATIC);for (int i = 0; i < 1000; ++i) {if (i == 200) {pid.set_auto_mode(PID_MODE_MANUAL);std::cout << "---->>> Switch to manual mode" << std::endl;}double control_signal = pid.Compute(setpoint, temperature);if (i >= 200 && i < 250) {control_signal = 3;}if (i >= 250 && i <= 300) {control_signal = 4;}std::cout << "--> Control signal: " << control_signal << std::endl;temperature += control_signal * 0.1;temperature *= 0.99;std::cout << "<-- Temperature: " << temperature << std::endl;if (i == 300) {pid.set_auto_mode(PID_MODE_AUTOMATIC, temperature, control_signal);std::cout << "---->>> Switch back to automatic mode" << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(200));
}return 0;

}

相关文章:

深入解析PID控制算法:从理论到实践的完整指南

前言 大家好&#xff0c;今天我们介绍一下经典控制理论中的PID控制算法&#xff0c;并着重讲解该算法的编码实现&#xff0c;为实现后续的倒立摆样例内容做准备。 众所周知&#xff0c;掌握了 PID &#xff0c;就相当于进入了控制工程的大门&#xff0c;也能为更高阶的控制理论…...

linux--关于GCC、动态库静态库

gcc和g的异同 他们是不同的编译器&#xff0c; 在linux中&#xff0c;生成可执行文件不像和windows一样。 linux中是以**.out作为可执行文件**的 无论是什么系统&#xff0c;生成可执行文件分为4步&#xff1a; 预处理–>编译–>汇编–>链接。 从.c/.cpp–>.i文件…...

matlab汽车动力学半车垂向振动模型

1、内容简介 matlab141-半车垂向振动模型 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...

Pygame中自定义事件处理的方法2-2

在《Pygame中自定义事件处理的方法2-1》中提到了处理自定义事件的方法。通过处理自定义事件&#xff0c;可以实现动画等效果。 1 弹跳小球程序 通过处理自定义事件&#xff0c;可以实现弹跳小球程序&#xff0c;如图1所示。 图1 弹跳小球程序 2 弹跳小球程序原理 实现弹跳小…...

B. Longest Divisors Interval

time limit per test 2 seconds memory limit per test 256 megabytes Given a positive integer nn, find the maximum size of an interval [l,r][l,r] of positive integers such that, for every ii in the interval (i.e., l≤i≤rl≤i≤r), nn is a multiple of ii. …...

什么是服务的雪崩、熔断、降级的解释以及Hystrix和Sentinel服务熔断器的解释、比较

1.什么是服务雪崩&#xff1f; 定义&#xff1a;在微服务中&#xff0c;假如一个或者多个服务出现故障&#xff0c;如果这时候&#xff0c;依赖的服务还在不断发起请求&#xff0c;或者重试&#xff0c;那么这些请求的压力会不断在下游堆积&#xff0c;导致下游服务的负载急剧…...

从驾驶员到智能驾驶:汽车智能化进程中的控制与仿真技术

在汽车技术持续演进的历程中&#xff0c;人类驾驶员始终是一个极具研究价值的智能控制系统“原型”。驾驶员通过视觉感知、行为决策与操作执行的闭环控制&#xff0c;将复杂的驾驶任务转化为车辆的实际动作&#xff0c;同时动态适应道路环境的变化。这一过程不仅体现了高度的自…...

mysql和minio

在现代应用架构中&#xff0c;Word 文档、PPT 等文件通常存储在对象存储服务&#xff08;如 MinIO&#xff09;中&#xff0c;而不是直接存储在关系型数据库&#xff08;如 MySQL&#xff09;中。以下是具体的分工和原因&#xff1a; 为什么选择对象存储&#xff08;如 MinIO&a…...

java练习(24)

PS:练习来自力扣 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&am…...

Android的Activity生命周期知识点总结,详情

一. Activity生命周期 1.1 返回栈知识点 二. Activity状态 2.1 启动状态 2.2 运行状态 2.3 暂停状态 2.4 停止状态 2.5 销毁状态 三. Activity生存期 3.1 回调方法 3.2 生存期 四. 体验Activity的生命周期 五. Activity被回收办法 引言&#xff1a; 掌握Acti…...

STM32——HAL库开发笔记19(串口中断接收实验)(参考来源:b站铁头山羊)

本实验&#xff0c;我们以中断的方式使得串口发送数据控制LED的闪烁速度&#xff0c;发送1&#xff0c;慢闪&#xff1b;发送2&#xff0c;速度正常&#xff1b;发送3&#xff0c;快闪。 一、电路连接图 二、实现思路&CubeMx配置 1、实现控制LED的闪烁速度 uint32_t bli…...

基于腾讯云TI-ONE 训练平台快速部署和体验 DeepSeek 系列模型

引言 在前两篇文章中&#xff0c;我们通过腾讯云的HAI部署了DeepSeek-R1&#xff0c;并基于此进行了一系列实践。 腾讯云HAI DeepSeek 腾讯云AI代码助手 &#xff1a;零门槛打造AI代码审计环境 基于腾讯云HAI DeepSeek 快速开发中医辅助问诊系统 这些尝试不仅帮助我们理解…...

python的类装饰器

装饰器不仅可以用于函数&#xff0c;还能作用于类。将装饰器应用于类时&#xff0c;其核心原理与作用于函数类似&#xff0c;都是通过接收一个类作为输入&#xff0c;然后返回一个新的类或者修改后的原类&#xff0c;以此来为类添加额外的功能 简单的类装饰器 def add_method…...

C++17中的LegacyContiguousIterator(连续迭代器)

文章目录 特点内存连续性与指针的兼容性更高的性能 适用场景与C接口交互高性能计算 支持连续迭代器的容器示例代码性能优势缓存局部性指针算术优化 注意事项总结 在C17标准里&#xff0c;LegacyContiguousIterator&#xff08;连续迭代器&#xff09;是一类特殊的迭代器。它不仅…...

Linux-文件IO

1.open函数 【1】基本概念和使用 #include <fcntl.h> int open(const char *pathname&#xff0c;int flags); int open(const char *pathname&#xff0c;int flags&#xff0c;mode_t mode); 功能: 打开或创建文件 参数: pathname //打开的文件名 f…...

DeepSeek-R1 + Cherry Studio 本地部署打造个人 AI 知识库

ChatGPT 爆火的时候&#xff0c;我心里就燃起了一个想法&#xff1a;打造一个专属于自己的AI知识库&#xff0c;它就像我的第二大脑一样&#xff0c;能记住我生活里的点点滴滴。 我随口一问“去年5月我做了什么”&#xff0c;它不仅能精准找到记录&#xff0c;还能帮我回忆起那…...

《红色警戒:兵临城下》 游戏软件安装步骤与百度网盘链接

软件简介&#xff1a; 《红色警戒&#xff1a;兵临城下》&#xff08;Command & Conquer: Red Alert&#xff09;是一款经典的即时战略游戏&#xff0c;由Westwood Studios开发&#xff0c;于1996年首次发行。它是《命令与征服》系列的衍生作品&#xff0c;以其独特的世界…...

25/2/16 <算法笔记> DirectPose

DirectPose 是一种直接从图像中预测物体的 6DoF&#xff08;位姿&#xff1a;6 Degrees of Freedom&#xff09;姿态 的方法&#xff0c;包括平移和平面旋转。它在目标检测、机器人视觉、增强现实&#xff08;AR&#xff09;和自动驾驶等领域中具有广泛应用。相比于传统的位姿估…...

第32周:文献阅读

目录 摘要 Abstract 文献阅读 问题引入 研究问题 研究意义 研究方法 集成方法 随机森林&#xff08;RF&#xff09; 支持向量机&#xff08;SVM&#xff09; 简单循环神经网络&#xff08;SimpleRNN&#xff09; 长短期记忆网络&#xff08;LSTM&#xff09; 创…...

Ollama 开发指南

文章来源&#xff1a;开发指南 - Ollama中文文档|Ollama官方文档 安装先决条件&#xff1a; GOC/C 编译器&#xff0c;例如 macOS 上的 Clang、TDM-GCC &#xff08;Windows amd64&#xff09; 或 llvm-mingw &#xff08;Windows arm64&#xff09;、Linux 上的 GCC/Clang。…...

【deepseek与chatGPT辩论】辩论题: “人工智能是否应当具备自主决策能力?”

探讨辩论题 这个提案涉及创建一个精确的辩论题目&#xff0c;旨在测试deepseek的应答能力。 创建辩论题目 提议设计一个辩论题目以测试deepseek的应答能力。希望这个题目具有挑战性并能够测量其回应质量。 好的&#xff0c;来一道适合深度学习的辩论题&#xff1a; 辩论题&…...

神经网络常见激活函数 9-CELU函数

文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元&#xff1a;CELU&#xff08;Continuously Differentiable Exponential Linear Unit&#xff09;,是一种连续可导的激活函数&#xff0c;结合了 ELU 和 …...

JavaScript系列(74)--反射API详解

JavaScript反射API详解 &#x1f50d; JavaScript的反射API提供了强大的运行时检查和操作对象的能力。本文将深入探讨Reflect API的原理、应用场景和最佳实践。 反射基础 &#x1f31f; &#x1f4a1; 小知识&#xff1a;反射是指程序在运行时能够检查、修改自身结构和行为的…...

轻量级分组加密算法RECTANGLE

轻量级分组加密算法RECTANGLE RECTANGLE轻量级分组密码算法是Wentao Zhang&#xff0c;Zhenzhen Bao,Dongdai Lin等学者于2014年提出的&#xff0c;该算法是SPN结构的&#xff0c;采用了线性移位的置换层以及44bit的S盒。RECTANGLE是一个迭代分组密码&#xff0c;分组长度为64…...

智能设备监控:AI 与 Python 助力设备管理的未来

智能设备监控:AI 与 Python 助力设备管理的未来 引言 随着物联网(IoT)和智能设备的广泛应用,我们的日常生活逐渐离不开这些高科技产品。从智能家居到工业控制,智能设备已经渗透到各个领域。然而,随着设备种类和数量的增加,如何高效地监控这些设备,确保它们的稳定性和…...

python语言进阶之函数

目录 前言 函数的创建和调用 函数创建 调用函数 参数传递 形式参数和实际参数 位置参数 数量必须与定义时一致 位置必须与定义时一致 关键字参数 为参数设置默认值 可变参数 **parameter 返回值 变量的作用域 局部变量 全局变量 匿名函数 前言 提到函数&…...

Golang Model 字段自动化校验设计

背景 在我们日常开发中&#xff0c;不可避免的总要去进行各种参数校验&#xff0c;但是如果在某个场景中&#xff0c;要校验的字段非常多&#xff0c;并且在其中还有耦合关系&#xff0c;那么我们手写校验逻辑就变得非常的低效且难以维护。本篇文档就基于 DDD 领域模型设计的思…...

Hot100 堆

215. 数组中的第K个最大元素 - 力扣&#xff08;LeetCode&#xff09; 堆排序 我们可以借助一个小顶堆来维护当前堆内元素的最小值&#xff0c;同时保证堆的大小为 k&#xff1a; 遍历数组将元素入堆&#xff1b; 如果当前堆内元素超过 k 了&#xff0c;我们就把堆顶元素去除…...

AIGC图生视频保姆级教程

一、AI文生图高阶技巧 推荐工具 ▸ MidJourney&#xff08;艺术感最强&#xff09; ▸ DALLE 3&#xff08;与ChatGPT深度联动&#xff09; ▸ Leonardo.ai&#xff08;精细化参数控制&#xff09; 核心策略 提示词架构&#xff1a; [主体描述][环境氛围][镜头语言][风格参数…...

Qt QDateTimeEdit总结

1. 概述 QDateTimeEdit 是 Qt 提供的用于编辑日期和时间的控件&#xff0c;支持直接输入或通过弹出日历/时间选择器调整值。继承自 QAbstractSpinBox&#xff0c;是 QDateEdit 和 QTimeEdit 的父类&#xff0c;可同时处理日期和时间。默认显示格式为系统本地化的日期时间格式&…...

【吾爱出品】 视频批量分段工具

视频批量分段工具 链接&#xff1a;https://pan.xunlei.com/s/VOJDvtHQE7GOiJ84WNea5Ay1A1?pwd5nta# 选择视频文件 启动程序后&#xff0c;点击 "文件" 菜单下的 "选择视频文件" 按钮&#xff0c;或者直接将视频文件拖放到程序窗口中的视频列表区域。支…...

SHEIN的迁移与无奈

日前&#xff0c;因杭州宇树科技、DeepSeek的“六小龙”企业崛起&#xff0c;不少地方开始反思&#xff0c;为什么本地没有留住创始人&#xff0c;或者发展出类似的企业。例如DeepSeek创始人梁文锋和Kimi创始人杨植麟都是广东人&#xff0c;但都在其他地区创业成功。而还有媒体…...

TCP/UDP 简介,三次握手与四次挥手

一、TCP 三次握手 目的&#xff1a;为了解决在不可靠的信道上建立可靠的网络连接 三次握手是连接请求的过程&#xff1a; A 发送连接请求的数据给 B&#xff08;发送 SYN 包&#xff09; B 同意连接&#xff0c;返回数据给 A&#xff08;返回 SYNACK 包&#xff09; A 收到后回…...

Windows Defender Control--禁用Windows安全中心

Windows Defender Control--禁用Windows安全中心 链接&#xff1a;https://pan.xunlei.com/s/VOJDuy2ZEqswU4sEgf12JthZA1?pwdtre6#...

数据仓库与数据湖的协同工作:智慧数据管理的双引擎

数据仓库与数据湖的协同工作:智慧数据管理的双引擎 引言 在数据驱动的今天,企业和组织收集和存储的数据量正以惊人的速度增长。如何高效管理和利用这些数据,成为了决策者和技术专家的共同难题。为了解决这一问题,数据仓库(Data Warehouse)和数据湖(Data Lake)这两种技…...

50. c++多维数组

在‘19 数组’中描述了原生数组的本质和其索引的原理&#xff0c;一维数组是连续的一个内存块&#xff0c;本质就是指针&#xff0c;指向这个内存块的起始位置&#xff0c;索引的原理就是对该指针的操作。通常对数组的操作一种策略就是使用指针&#xff0c;二维数组可以说是数组…...

用大模型学大模型05-线性回归

deepseek.com:多元线性回归的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c;预测结果的可视化展示&#xff0c; 模型应用场景和优缺点&#xff0c;及如何改进解决及改进方法数据推…...

苹果CMS站群插件的自动生成功能:提升网站流量的秘诀

引言 在数字营销的浪潮中&#xff0c;站群技术因其强大的流量引导能力而备受青睐。苹果CMS作为一款优秀的内容管理系统&#xff0c;凭借其灵活性和可扩展性&#xff0c;成为了站群管理的理想选择。本文将详细介绍苹果CMS站群插件的自动生成功能&#xff0c;探讨如何通过这一功…...

大语言模型中one-hot编码和embedding之间的区别?

1. 维度与稀疏性 One-Hot编码 定义&#xff1a;每个词被表示为一个高维稀疏向量&#xff0c;维度等于词汇表大小。例如&#xff0c;词汇表有10,000个词&#xff0c;每个词对应一个10,000维的向量&#xff0c;其中仅有一个位置为1&#xff08;表示当前词&#xff09;&#xff0…...

【Bluedroid】 BLE连接源码分析(一)

BLE链接过程分析见【Bluedroid】BLE连接过程详解-CSDN博客,本篇主要围绕HCI_LE_Create_Connection展开。基于Android14源码进行分析。在蓝牙低功耗技术中,设备之间建立连接是进行数据传输等操作的前提。HCI LE Extended Create Connection Command 提供了一种更灵活、功能更丰…...

【C语言】移除元素

移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后…...

SQL与数据库程序设计

1.1986年&#xff0c;10月美国国家标准局颁布了SQL语言的美国标准&#xff0c;称为SQL86 2.SQL(Structured Query Language)又称为结构化查询语言 3.建立索引的主要目的是加快查找的速度 4.在基本表上建立一个或者多个索引 5. 一个基本表是最多只能建立一个聚簇索引 6.CAL…...

基于Java企业项目管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

Blazor-设置组件焦点

在Razor中设置焦点我们需要用到ElementReference类型的变量&#xff0c;使用ref指令引用到设置焦点HTML的元素。 在Blazor中&#xff0c;ElementReference类型的作用是提供对HTML DOM元素的引用&#xff0c;以便在C#代码中通过JavaScript互操作&#xff08;JS Interop&#xff…...

信用违约掉期(Credit Default Swap, CDS):金融市场的“保险”还是“定时炸弹”?(中英双语)

信用违约掉期&#xff08;CDS&#xff09;&#xff1a;金融市场的“保险”还是“定时炸弹”&#xff1f; 引言 信用违约掉期&#xff08;Credit Default Swap, CDS&#xff09; 是金融市场中一种重要的衍生品&#xff0c;它最初被设计为债务违约的保险工具&#xff0c;但在实…...

Deepseek R1模型本地化部署与API实战指南:释放企业级AI生产力

摘要 本文深入解析Deepseek R1开源大模型的本地化部署流程与API集成方案&#xff0c;涵盖从硬件选型、Docker环境搭建到模型微调及RESTful接口封装的完整企业级解决方案。通过电商评论分析和智能客服搭建等案例&#xff0c;展示如何将前沿AI技术转化为实际生产力。教程支持Lin…...

核货宝多语言订货系统:打破语言障碍,拓展全球市场

在经济全球化的大背景下&#xff0c;企业的业务版图不断向全球扩张&#xff0c;国际贸易活动日益频繁。对于众多从事跨境贸易、跨国批发零售以及拥有广泛海外客户群体的企业而言&#xff0c;一款能够跨越语言障碍的多语言订货系统&#xff0c;已成为其在全球市场竞争中脱颖而出…...

【prompt示例】智能客服+智能质检业务模版

本文原创作者&#xff1a;姚瑞南 AI-agent 大模型运营专家&#xff0c;先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗&#xff1b;多年人工智能行业智能产品运营及大模型落地经验&#xff0c;拥有AI外呼方向国家专利与PMP项目管理证书。&#xff08;转载需经授权&am…...

在linux系统中安装Anaconda,并使用conda

系统 : ubuntu20.04 显卡&#xff1a;NVIDIA GTX1650 目录 安装Anaconda第一步&#xff1a;下载合适版本的Anconda1. 查看自己Linux的操作系统及架构命令&#xff1a;uname -a2. 下载合适版本的Anconda 第二步&#xff1a;安装Aanconda1. 为.sh文件设置权限2. 执行.sh文件2.1 .…...

基于 openEuler 构建 LVS-DR 群集

1、环境准备 准备好下面四台台服务器&#xff1a; 主机名IP角色openEuler-1192.168.121.11Director ServeropenEuler-2192.168.121.12Real Server1openEuler-3192.168.121.13Real Server2Rocky8192.168.121.51Client 2、Web服务器配置 在两台RS上安装并配置nginx服务&#…...