65.9K
CodeProject 正在变化。 阅读更多。
Home

高斯-牛顿法用于齐纳二极管

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2019年7月2日

CPOL

5分钟阅读

viewsIcon

11277

downloadIcon

339

使用高斯-牛顿法(非线性最小二乘法)计算齐纳二极管指数模型的参数

引言

该代码实现了高斯-牛顿法来求解非线性最小二乘问题,并将其应用于齐纳二极管的 IZ(VZ) 模型。

背景

最近,我一直在考虑一个意想不到的效应,即齐纳二极管电流对微控制器的输入级 (ADC) 的吸收;齐纳二极管本应只用于钳位可能的过高电压,而不应像实际上那样明显地改变输入信号。不幸的是,二极管的数据手册没有提供关于 IZ(VZ) 的详细信息,所以我不得不对其进行测量,然后,无可救药地被吸引,尝试对其进行建模。

本文描述了这个过程。

免责声明

  • 本文不试图解释甚至介绍高斯-牛顿算法(例如,它在维基百科上已经得到了很好的详细介绍)。它只是将其用于一个目的。
  • 使用高斯-牛顿方法是因为它很契合(抱歉用这个双关语)。虽然我确信有更高级的方法来计算非线性最小二乘,但我并不关心。我编写代码的过程很有趣。

实验数据

实验数据是使用图 1 中所示的简单电路收集的:一个可调电压发生器(实验室电源)与一个 992 欧姆的电阻和一个 3V3 的齐纳二极管串联。电压发生器为电路供电,为齐纳二极管提供正向和反向电压。

The circuit showing the mesurement points

图 1:显示测量点的电路。

在这种电路中,电阻和齐纳二极管流过的电流是相同的

IZ = IR = VR/R

因此,测量 VZ 和 VR 就可以获得所有需要的数据。

然而,额外测量 VTOT 并非仅仅是浪费(我的)时间,因为它为收集到的 {VZ, IZ} 对提供了“冗余检查”。

数据文件

共收集了 123 个数据点,VTOT 的范围从约 -21 V 到(约)18 V

对应的值可在 data.csv 文件中找到。图 2 显示了数据的部分视图。

A partial view of the collected data values

图 2:收集到的数据部分视图。

实验曲线

收集到的数据用图 3 绘制的曲线表示。

The graph of the collected data points

图 2:实验数据点。

显示了齐纳二极管的特性行为:正向电压(本例中约为 0.7 V)下指数级增长的电流,反向击穿电压(约为标称值 -3.3 V)下指数级增长(但斜率非常不同,更平缓)的(负)电流。

在中区,电流非常小。

模型

选择的 IZ(VZ) 模型是“指数二极管”。

The exponential diode model

在本文中,我只考虑方程 (1),该方程应仅在 V > -VB 范围内有效,其中 VB 是(齐纳)二极管的“击穿电压”

图 0,位于页面顶部,实际上显示了该方程在无效范围(V <= -VB)下的“灾难性失效”。

由于该齐纳二极管是 3V3 的,其标称 VB3.3 V。然而,本次测试的目的是“实验性地”确定方程 (1) 的有效范围。

计划

该计划包括迭代应用高斯-牛顿方法(非线性最小二乘)到实验数据的子范围(尾部),直到获得令人满意的拟合,以确定齐纳二极管的“实验击穿电压”(VBE)。

(这个结果将是另一篇文章的起点,在其中需要找到 V < -VBE 范围内的方程 (2) 的拟合。)

细节

  • 在第 0 步,考虑所有实验数据来拟合模型(VStart = Vmin = V0)。计算相应的方差。
  • 在第 1 步,排除第一个实验点(VStart = V1),对剩余数据尾部进行拟合,计算相应的方差。
  • 在第 2 步,排除第一个和第二个实验点(VStart = V2),对剩余数据尾部进行拟合和方差计算。
  • ...
  • 在第 83 步,停止所有操作,因为现在数据尾部相当小了。

最后,绘制方差(Vstart)图以估算 VBE

高斯-牛顿算法

高斯-牛顿算法是一种求解非线性最小二乘问题的迭代方法(详情请参阅维基百科页面:高斯-牛顿算法[^])。

迭代步骤 n+1,即在步骤 n+1 时模型估计参数的值是

The iteration step of the Gauss Newton method

在我们的例子中,在将方程 (1) 重写为如下形式之后

The rewritten equation 1

我们有模型函数

The model function

以及迭代步骤分量

The iteration step components

其中明确显示了参数(2 个分量)和残差(N 个分量)的矢量性质,以及雅可比矩阵(N x 2 个元素)的矩阵性质。

幸运的是,JTJ 乘积是一个 2x2 矩阵:其逆矩阵的计算是微不足道的。

代码

代码位于单个源文件 gn2.cpp 中 - 它使用 g++-7 编译,但任何现代 C++ 编译器都应该可以完成。

它包含一个名为 GN 的类,该类在 (main 函数中) 使用所有实验数据值(以及参数的初始估计)进行构造。

main 首先“任意地”设置 20 次迭代步骤,然后通过 offset 参数定义的尾部数据,迭代调用 GN::compute 方法。

 for ( size_t offset = 0; offset <= (N - 40); ++offset)
  {
    cout << "----------------------\n";
    cout << "starting with item:\n" << offset << endl << endl;
    auto  [ best_pe, best_var] = gn.compute(steps, offset);
    cout << "best pe:\n" << best_pe << endl;
    cout << "best variance:\n" << best_var << endl << endl;
  }
  cout << "done." << endl << endl;

对于每个范围,都会报告

  • 在标准输出上,最佳计算出的方差以及相应的参数估计。
  • 在一个名为 gn_<offset>.log 的日志文件中(例如 gn_009.log),记录了计算的所有细节(是的,大约会生成 80 个日志文件 :-))。

控制台输出摘录

...
----------------------
starting with item:
9

best pe:
2.10572e-17
43.5169

best variance:
0.000195843
...

相应日志文件 gn_009.log 的摘录

experimental data
ex:
-3.773
-3.753
[...]
ey:
-0.0174294
-0.0164012
[...]
parameter estimates starting value
epe
5.18e-20
51.2

####################
step 0
r:
0
0
[...]
variance:
0.000197485

j:
0 0
0 0
[...]
jt:
0 0 0 0 [...]
[...]
jtr:
6.88719e+13
1.81736e-06

jtj:
7.32544e+35 2.96725e+16
2.96725e+16 0.00120204

inv_jtj:
1.30385e-32 -3.21856e-13
-3.21856e-13 7.94587e+06

identity:
1 0
0 1

dpe:
3.13055e-19
-7.72628

new parameter estimates:
3.64855e-19
43.4737
[...]

其中 identity = (JTJ)-1 · (JTJ) 是一个“一致性检查”,而 dpe 是参数估计值的增量(delta)。

方差图

在图上收集并绘制了针对不同范围计算出的方差。

The graph of the variance computed on different ranges

图 3:在不同范围的实验数据上计算出的拟合方差。

该图再次证实了方程 (1) 对于电压 V < -VB 的不适用性。

它还指示了击穿电压的实验值 VBE。事实上,模型在以下情况下“表现良好”:

V > -VBE ≅ -2.8 V

关注点

我到处都使用 std::vector,但在这种特殊应用中,我发现 std::array 结合模板为矩阵计算提供了强大而简洁的工具,例如,考虑

template <size_t R, size_t C>
  array< array < double, R>, C > transpose
       ( const array < array< double, C >, R > & m, size_t offset)
{
  array< array < double, R >, C > t{0}; // the transposed matrix
  for (size_t r = offset; r<R; ++r)
    for ( size_t c = 0; c < C; ++c)
      t[c][r] = m[r][c];
  return t;
}

在我看来,这是一种几乎可以自文档化的转置操作实现。

您可以通过丢弃 offset 参数来实现更简洁的实现,我在这里使用它是为了提高性能而牺牲了通用性。

历史

  • 2019年6月30日 - 首次发布
© . All rights reserved.