深入解析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控制算法:从理论到实践的完整指南
前言 大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论…...
linux--关于GCC、动态库静态库
gcc和g的异同 他们是不同的编译器, 在linux中,生成可执行文件不像和windows一样。 linux中是以**.out作为可执行文件**的 无论是什么系统,生成可执行文件分为4步: 预处理–>编译–>汇编–>链接。 从.c/.cpp–>.i文件…...
matlab汽车动力学半车垂向振动模型
1、内容简介 matlab141-半车垂向振动模型 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...
Pygame中自定义事件处理的方法2-2
在《Pygame中自定义事件处理的方法2-1》中提到了处理自定义事件的方法。通过处理自定义事件,可以实现动画等效果。 1 弹跳小球程序 通过处理自定义事件,可以实现弹跳小球程序,如图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.什么是服务雪崩? 定义:在微服务中,假如一个或者多个服务出现故障,如果这时候,依赖的服务还在不断发起请求,或者重试,那么这些请求的压力会不断在下游堆积,导致下游服务的负载急剧…...
从驾驶员到智能驾驶:汽车智能化进程中的控制与仿真技术
在汽车技术持续演进的历程中,人类驾驶员始终是一个极具研究价值的智能控制系统“原型”。驾驶员通过视觉感知、行为决策与操作执行的闭环控制,将复杂的驾驶任务转化为车辆的实际动作,同时动态适应道路环境的变化。这一过程不仅体现了高度的自…...
mysql和minio
在现代应用架构中,Word 文档、PPT 等文件通常存储在对象存储服务(如 MinIO)中,而不是直接存储在关系型数据库(如 MySQL)中。以下是具体的分工和原因: 为什么选择对象存储(如 MinIO&a…...
java练习(24)
PS:练习来自力扣 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意&am…...
Android的Activity生命周期知识点总结,详情
一. Activity生命周期 1.1 返回栈知识点 二. Activity状态 2.1 启动状态 2.2 运行状态 2.3 暂停状态 2.4 停止状态 2.5 销毁状态 三. Activity生存期 3.1 回调方法 3.2 生存期 四. 体验Activity的生命周期 五. Activity被回收办法 引言: 掌握Acti…...
STM32——HAL库开发笔记19(串口中断接收实验)(参考来源:b站铁头山羊)
本实验,我们以中断的方式使得串口发送数据控制LED的闪烁速度,发送1,慢闪;发送2,速度正常;发送3,快闪。 一、电路连接图 二、实现思路&CubeMx配置 1、实现控制LED的闪烁速度 uint32_t bli…...
基于腾讯云TI-ONE 训练平台快速部署和体验 DeepSeek 系列模型
引言 在前两篇文章中,我们通过腾讯云的HAI部署了DeepSeek-R1,并基于此进行了一系列实践。 腾讯云HAI DeepSeek 腾讯云AI代码助手 :零门槛打造AI代码审计环境 基于腾讯云HAI DeepSeek 快速开发中医辅助问诊系统 这些尝试不仅帮助我们理解…...
python的类装饰器
装饰器不仅可以用于函数,还能作用于类。将装饰器应用于类时,其核心原理与作用于函数类似,都是通过接收一个类作为输入,然后返回一个新的类或者修改后的原类,以此来为类添加额外的功能 简单的类装饰器 def add_method…...
C++17中的LegacyContiguousIterator(连续迭代器)
文章目录 特点内存连续性与指针的兼容性更高的性能 适用场景与C接口交互高性能计算 支持连续迭代器的容器示例代码性能优势缓存局部性指针算术优化 注意事项总结 在C17标准里,LegacyContiguousIterator(连续迭代器)是一类特殊的迭代器。它不仅…...
Linux-文件IO
1.open函数 【1】基本概念和使用 #include <fcntl.h> int open(const char *pathname,int flags); int open(const char *pathname,int flags,mode_t mode); 功能: 打开或创建文件 参数: pathname //打开的文件名 f…...
DeepSeek-R1 + Cherry Studio 本地部署打造个人 AI 知识库
ChatGPT 爆火的时候,我心里就燃起了一个想法:打造一个专属于自己的AI知识库,它就像我的第二大脑一样,能记住我生活里的点点滴滴。 我随口一问“去年5月我做了什么”,它不仅能精准找到记录,还能帮我回忆起那…...
《红色警戒:兵临城下》 游戏软件安装步骤与百度网盘链接
软件简介: 《红色警戒:兵临城下》(Command & Conquer: Red Alert)是一款经典的即时战略游戏,由Westwood Studios开发,于1996年首次发行。它是《命令与征服》系列的衍生作品,以其独特的世界…...
25/2/16 <算法笔记> DirectPose
DirectPose 是一种直接从图像中预测物体的 6DoF(位姿:6 Degrees of Freedom)姿态 的方法,包括平移和平面旋转。它在目标检测、机器人视觉、增强现实(AR)和自动驾驶等领域中具有广泛应用。相比于传统的位姿估…...
第32周:文献阅读
目录 摘要 Abstract 文献阅读 问题引入 研究问题 研究意义 研究方法 集成方法 随机森林(RF) 支持向量机(SVM) 简单循环神经网络(SimpleRNN) 长短期记忆网络(LSTM) 创…...
Ollama 开发指南
文章来源:开发指南 - Ollama中文文档|Ollama官方文档 安装先决条件: GOC/C 编译器,例如 macOS 上的 Clang、TDM-GCC (Windows amd64) 或 llvm-mingw (Windows arm64)、Linux 上的 GCC/Clang。…...
【deepseek与chatGPT辩论】辩论题: “人工智能是否应当具备自主决策能力?”
探讨辩论题 这个提案涉及创建一个精确的辩论题目,旨在测试deepseek的应答能力。 创建辩论题目 提议设计一个辩论题目以测试deepseek的应答能力。希望这个题目具有挑战性并能够测量其回应质量。 好的,来一道适合深度学习的辩论题: 辩论题&…...
神经网络常见激活函数 9-CELU函数
文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元:CELU(Continuously Differentiable Exponential Linear Unit),是一种连续可导的激活函数,结合了 ELU 和 …...
JavaScript系列(74)--反射API详解
JavaScript反射API详解 🔍 JavaScript的反射API提供了强大的运行时检查和操作对象的能力。本文将深入探讨Reflect API的原理、应用场景和最佳实践。 反射基础 🌟 💡 小知识:反射是指程序在运行时能够检查、修改自身结构和行为的…...
轻量级分组加密算法RECTANGLE
轻量级分组加密算法RECTANGLE RECTANGLE轻量级分组密码算法是Wentao Zhang,Zhenzhen Bao,Dongdai Lin等学者于2014年提出的,该算法是SPN结构的,采用了线性移位的置换层以及44bit的S盒。RECTANGLE是一个迭代分组密码,分组长度为64…...
智能设备监控:AI 与 Python 助力设备管理的未来
智能设备监控:AI 与 Python 助力设备管理的未来 引言 随着物联网(IoT)和智能设备的广泛应用,我们的日常生活逐渐离不开这些高科技产品。从智能家居到工业控制,智能设备已经渗透到各个领域。然而,随着设备种类和数量的增加,如何高效地监控这些设备,确保它们的稳定性和…...
python语言进阶之函数
目录 前言 函数的创建和调用 函数创建 调用函数 参数传递 形式参数和实际参数 位置参数 数量必须与定义时一致 位置必须与定义时一致 关键字参数 为参数设置默认值 可变参数 **parameter 返回值 变量的作用域 局部变量 全局变量 匿名函数 前言 提到函数&…...
Golang Model 字段自动化校验设计
背景 在我们日常开发中,不可避免的总要去进行各种参数校验,但是如果在某个场景中,要校验的字段非常多,并且在其中还有耦合关系,那么我们手写校验逻辑就变得非常的低效且难以维护。本篇文档就基于 DDD 领域模型设计的思…...
Hot100 堆
215. 数组中的第K个最大元素 - 力扣(LeetCode) 堆排序 我们可以借助一个小顶堆来维护当前堆内元素的最小值,同时保证堆的大小为 k: 遍历数组将元素入堆; 如果当前堆内元素超过 k 了,我们就把堆顶元素去除…...
AIGC图生视频保姆级教程
一、AI文生图高阶技巧 推荐工具 ▸ MidJourney(艺术感最强) ▸ DALLE 3(与ChatGPT深度联动) ▸ Leonardo.ai(精细化参数控制) 核心策略 提示词架构: [主体描述][环境氛围][镜头语言][风格参数…...
Qt QDateTimeEdit总结
1. 概述 QDateTimeEdit 是 Qt 提供的用于编辑日期和时间的控件,支持直接输入或通过弹出日历/时间选择器调整值。继承自 QAbstractSpinBox,是 QDateEdit 和 QTimeEdit 的父类,可同时处理日期和时间。默认显示格式为系统本地化的日期时间格式&…...
【吾爱出品】 视频批量分段工具
视频批量分段工具 链接:https://pan.xunlei.com/s/VOJDvtHQE7GOiJ84WNea5Ay1A1?pwd5nta# 选择视频文件 启动程序后,点击 "文件" 菜单下的 "选择视频文件" 按钮,或者直接将视频文件拖放到程序窗口中的视频列表区域。支…...
SHEIN的迁移与无奈
日前,因杭州宇树科技、DeepSeek的“六小龙”企业崛起,不少地方开始反思,为什么本地没有留住创始人,或者发展出类似的企业。例如DeepSeek创始人梁文锋和Kimi创始人杨植麟都是广东人,但都在其他地区创业成功。而还有媒体…...
TCP/UDP 简介,三次握手与四次挥手
一、TCP 三次握手 目的:为了解决在不可靠的信道上建立可靠的网络连接 三次握手是连接请求的过程: A 发送连接请求的数据给 B(发送 SYN 包) B 同意连接,返回数据给 A(返回 SYNACK 包) A 收到后回…...
Windows Defender Control--禁用Windows安全中心
Windows Defender Control--禁用Windows安全中心 链接:https://pan.xunlei.com/s/VOJDuy2ZEqswU4sEgf12JthZA1?pwdtre6#...
数据仓库与数据湖的协同工作:智慧数据管理的双引擎
数据仓库与数据湖的协同工作:智慧数据管理的双引擎 引言 在数据驱动的今天,企业和组织收集和存储的数据量正以惊人的速度增长。如何高效管理和利用这些数据,成为了决策者和技术专家的共同难题。为了解决这一问题,数据仓库(Data Warehouse)和数据湖(Data Lake)这两种技…...
50. c++多维数组
在‘19 数组’中描述了原生数组的本质和其索引的原理,一维数组是连续的一个内存块,本质就是指针,指向这个内存块的起始位置,索引的原理就是对该指针的操作。通常对数组的操作一种策略就是使用指针,二维数组可以说是数组…...
用大模型学大模型05-线性回归
deepseek.com:多元线性回归的目标函数,损失函数,梯度下降 标量和矩阵形式的数学推导,pytorch真实能跑的代码案例以及模型,数据,预测结果的可视化展示, 模型应用场景和优缺点,及如何改进解决及改进方法数据推…...
苹果CMS站群插件的自动生成功能:提升网站流量的秘诀
引言 在数字营销的浪潮中,站群技术因其强大的流量引导能力而备受青睐。苹果CMS作为一款优秀的内容管理系统,凭借其灵活性和可扩展性,成为了站群管理的理想选择。本文将详细介绍苹果CMS站群插件的自动生成功能,探讨如何通过这一功…...
大语言模型中one-hot编码和embedding之间的区别?
1. 维度与稀疏性 One-Hot编码 定义:每个词被表示为一个高维稀疏向量,维度等于词汇表大小。例如,词汇表有10,000个词,每个词对应一个10,000维的向量,其中仅有一个位置为1(表示当前词)࿰…...
【Bluedroid】 BLE连接源码分析(一)
BLE链接过程分析见【Bluedroid】BLE连接过程详解-CSDN博客,本篇主要围绕HCI_LE_Create_Connection展开。基于Android14源码进行分析。在蓝牙低功耗技术中,设备之间建立连接是进行数据传输等操作的前提。HCI LE Extended Create Connection Command 提供了一种更灵活、功能更丰…...
【C语言】移除元素
移除元素 给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后…...
SQL与数据库程序设计
1.1986年,10月美国国家标准局颁布了SQL语言的美国标准,称为SQL86 2.SQL(Structured Query Language)又称为结构化查询语言 3.建立索引的主要目的是加快查找的速度 4.在基本表上建立一个或者多个索引 5. 一个基本表是最多只能建立一个聚簇索引 6.CAL…...
基于Java企业项目管理系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
Blazor-设置组件焦点
在Razor中设置焦点我们需要用到ElementReference类型的变量,使用ref指令引用到设置焦点HTML的元素。 在Blazor中,ElementReference类型的作用是提供对HTML DOM元素的引用,以便在C#代码中通过JavaScript互操作(JS Interopÿ…...
信用违约掉期(Credit Default Swap, CDS):金融市场的“保险”还是“定时炸弹”?(中英双语)
信用违约掉期(CDS):金融市场的“保险”还是“定时炸弹”? 引言 信用违约掉期(Credit Default Swap, CDS) 是金融市场中一种重要的衍生品,它最初被设计为债务违约的保险工具,但在实…...
Deepseek R1模型本地化部署与API实战指南:释放企业级AI生产力
摘要 本文深入解析Deepseek R1开源大模型的本地化部署流程与API集成方案,涵盖从硬件选型、Docker环境搭建到模型微调及RESTful接口封装的完整企业级解决方案。通过电商评论分析和智能客服搭建等案例,展示如何将前沿AI技术转化为实际生产力。教程支持Lin…...
核货宝多语言订货系统:打破语言障碍,拓展全球市场
在经济全球化的大背景下,企业的业务版图不断向全球扩张,国际贸易活动日益频繁。对于众多从事跨境贸易、跨国批发零售以及拥有广泛海外客户群体的企业而言,一款能够跨越语言障碍的多语言订货系统,已成为其在全球市场竞争中脱颖而出…...
【prompt示例】智能客服+智能质检业务模版
本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权&am…...
在linux系统中安装Anaconda,并使用conda
系统 : ubuntu20.04 显卡:NVIDIA GTX1650 目录 安装Anaconda第一步:下载合适版本的Anconda1. 查看自己Linux的操作系统及架构命令:uname -a2. 下载合适版本的Anconda 第二步:安装Aanconda1. 为.sh文件设置权限2. 执行.sh文件2.1 .…...
基于 openEuler 构建 LVS-DR 群集
1、环境准备 准备好下面四台台服务器: 主机名IP角色openEuler-1192.168.121.11Director ServeropenEuler-2192.168.121.12Real Server1openEuler-3192.168.121.13Real Server2Rocky8192.168.121.51Client 2、Web服务器配置 在两台RS上安装并配置nginx服务&#…...