[英]Why does this LinqPad program produce different results on the second run?
I am developing a simple PID controller in LinqPad:我正在 LinqPad 中开发一个简单的 PID 控制器:
class PidController
{
public float Proportional { get; set; }
public float Integral { get; set; }
public float Derivative { get; set; }
public float SetPoint { get; set; }
public float Kp { get; set; }
public float Ki { get; set; }
public float Kd { get; set; }
float _lastError;
DateTime _lastTime = DateTime.Now;
public PidController(float kp, float ki, float kd)
{
Kp = kp; Ki = ki; Kd = kd;
}
public float GetControlValue(float actual)
{
var currentTime = DateTime.Now;
var deltaTime = (float)(currentTime - _lastTime).TotalSeconds;
var error = SetPoint - actual;
Proportional = error;
Integral = Integral + error * deltaTime;
Derivative = (error - _lastError) / deltaTime;
_lastError = error;
return Kp * Proportional + Ki * Integral + Kd * Derivative;
}
}
For testing and tuning, the controller will control this simple process:为了测试和调整,控制器将控制这个简单的过程:
class SimpleProcess
{
private DateTime _lastTime = DateTime.Now;
private float _output;
public float Output { get { UpdateOutput(); return _output; }}
public float Input { get; set; }
private void UpdateOutput()
{
var deltaTime = (float)(DateTime.Now - _lastTime).TotalSeconds;
_output += Input * deltaTime;
}
}
...using this main loop: ...使用这个主循环:
void Main()
{
var pid = new PidController(1f, 0f, 0f) { SetPoint = 100f };
var proc = new SimpleProcess();
// pid.Dump();
// proc.Dump();
var values = new List<ProcessValue>();
for (int i = 0; i < 50; i++)
{
var actual = proc.Output;
var controlValue = pid.GetControlValue(actual);
proc.Input = controlValue;
var value = new ProcessValue
{
index = i,
timestamp = DateTime.Now.ToString("ss.fff"),
p = pid.Proportional,
i = pid.Integral,
d = pid.Derivative,
input = controlValue,
output = actual
};
values.Add(value);
Thread.Sleep(100);
}
values.Dump();
}
public class ProcessValue
{
public int index;
public string timestamp;
public float p, i, d, input, output;
}
Everything works as expected on the first run:第一次运行时一切都按预期工作:
index timestamp p i d input output
0 53.309 100 0.46 21490.59 100 0
1 53.411 89.69 10.06 -96.27 89.69 10.30
etc...
However, I started getting unexpected results on the second and subsequent runs after I commented out the line proc.Dump()
:但是,在我注释掉
proc.Dump()
行后,我开始在第二次和后续运行中得到意想不到的结果:
index timestamp p i d input output
0 10.199 100 0 ∞ NaN 0
1 10.299 NaN NaN NaN NaN NaN
2 10.399 NaN NaN NaN NaN NaN
etc...
Why is the second run (and subsequent runs) returning different results in my case?为什么第二次运行(和后续运行)在我的情况下返回不同的结果?
Any of the following actions will cause the next run to succeed:以下任何操作都将导致下一次运行成功:
The following makes the code run correctly every time:以下使代码每次都能正确运行:
proc.Dump()
proc.Dump()
This answer mentions that static variables will be cached between runs, but I have no static variables.这个答案提到静态变量将在运行之间缓存,但我没有静态变量。 I suspect the problem is related to the Application Domain Caching feature in LinqPad, but I'm trying to understand why I'm affected by this.
我怀疑问题是关系到LinqPad应用程序域缓存功能,但我想知道为什么我受此影响。
StriplingWarrior's answer is correct, my first derivative calculation resulted in Infinity when they system was performing well (ie after LinqPad had cached the first run), causing all subsequent calculations to fail. StriplingWarrior 的回答是正确的,我的一阶导数计算在系统运行良好时(即在 LinqPad 缓存第一次运行之后)导致 Infinity,导致所有后续计算失败。 Modifying my program in any way was invalidating this cache and caused the deltaTime to be large enough to avoid the error again on the next run.
以任何方式修改我的程序都会使这个缓存失效,并导致 deltaTime 足够大,以避免在下次运行时再次出现错误。
Since a derivative term makes no sense on the first interval, I decided to handle this by simply ignoring it:由于导数项在第一个区间没有意义,我决定通过简单地忽略它来处理这个问题:
var p = Kp * Proportional;
var i = Ki * Integral;
var d = float.IsInfinity(Derivative) ? 0 : Kd * Derivative;
return p + i + d;
You can test what Andrew theorizes in your comments above, by changing the first part of your main method thusly:您可以通过更改 main 方法的第一部分来测试 Andrew 在上面的评论中的理论化:
var sw = new Stopwatch();
sw.Start();
var pid = new PidController(1f, 0f, 0f) { SetPoint = 100f };
var proc = new SimpleProcess();
// pid.Dump();
// proc.Dump();
var values = new List<ProcessValue>();
for (int i = 0; i < 50; i++)
{
var actual = proc.Output;
var controlValue = pid.GetControlValue(actual);
if(sw.IsRunning){
sw.Stop();
sw.ElapsedTicks.Dump();
}
Running on my machine, I can see that the first run takes 10,000+ ticks, whereas the second run takes only 20 ticks.在我的机器上运行,我可以看到第一次运行需要 10,000+ 滴答,而第二次运行只需要 20 滴答。 I'm guessing this makes your calculations based on differences in DateTime.Now have very small delta values, and yield the differences you're seeing.
我猜这会使您根据 DateTime.Now 的差异进行计算。现在的增量值非常小,并产生您所看到的差异。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.