为什么这个 LinqPad 程序在第二个 运行 上产生不同的结果?
Why does this LinqPad program produce different results on the second run?
我正在 LinqPad 中开发一个简单的 PID 控制器:
PID Class
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;
}
}
为了测试和调整,控制器将控制这个简单的过程:
受控过程
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;
}
}
...使用这个主循环:
主程序循环
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;
}
第一个 运行:
一切正常
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...
然而,在我注释掉行 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...
为什么第二个 运行(以及后续的 运行)在我的案例中返回不同的结果?
以下任何操作都会导致下一个 运行 成功:
- 修改代码(即使只是adding/removing一个空格)
- 按 [CTRL]+[SHIFT]+[F5]
以下使代码 运行 每次都正确:
- 取消注释行
proc.Dump()
This answer提到静态变量会在运行之间缓存,但是我没有静态变量。我怀疑问题 是 与 LinqPad 中的 Application Domain Caching 功能有关,但我试图理解为什么我会受此影响。
更新
StriplingWarrior 的回答是正确的,我的一阶导数计算在系统运行良好时(即在 LinqPad 缓存第一个 运行 之后)导致所有后续计算失败。以任何方式修改我的程序都会使此缓存无效并导致 deltaTime 足够大以避免在下一个 运行.
再次出现错误
由于导数项在第一个区间没有意义,我决定通过简单地忽略它来处理这个问题:
var p = Kp * Proportional;
var i = Ki * Integral;
var d = float.IsInfinity(Derivative) ? 0 : Kd * Derivative;
return p + i + d;
您可以通过更改 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();
}
运行 在我的机器上,我可以看到第一个 运行 需要 10,000+ 个滴答,而第二个 运行 只需要 20 个滴答。我猜这会使您基于 DateTime.Now 的差异进行的计算具有非常小的增量值,并产生您所看到的差异。
我正在 LinqPad 中开发一个简单的 PID 控制器:
PID Class
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;
}
}
为了测试和调整,控制器将控制这个简单的过程:
受控过程
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;
}
}
...使用这个主循环:
主程序循环
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;
}
第一个 运行:
一切正常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...
然而,在我注释掉行 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...
为什么第二个 运行(以及后续的 运行)在我的案例中返回不同的结果?
以下任何操作都会导致下一个 运行 成功:
- 修改代码(即使只是adding/removing一个空格)
- 按 [CTRL]+[SHIFT]+[F5]
以下使代码 运行 每次都正确:
- 取消注释行
proc.Dump()
This answer提到静态变量会在运行之间缓存,但是我没有静态变量。我怀疑问题 是 与 LinqPad 中的 Application Domain Caching 功能有关,但我试图理解为什么我会受此影响。
更新
StriplingWarrior 的回答是正确的,我的一阶导数计算在系统运行良好时(即在 LinqPad 缓存第一个 运行 之后)导致所有后续计算失败。以任何方式修改我的程序都会使此缓存无效并导致 deltaTime 足够大以避免在下一个 运行.
再次出现错误由于导数项在第一个区间没有意义,我决定通过简单地忽略它来处理这个问题:
var p = Kp * Proportional;
var i = Ki * Integral;
var d = float.IsInfinity(Derivative) ? 0 : Kd * Derivative;
return p + i + d;
您可以通过更改 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();
}
运行 在我的机器上,我可以看到第一个 运行 需要 10,000+ 个滴答,而第二个 运行 只需要 20 个滴答。我猜这会使您基于 DateTime.Now 的差异进行的计算具有非常小的增量值,并产生您所看到的差异。