这个答案与问题有些正交,因为它完全避免了 Fortran,但鉴于 C# 和 MINPACK 搜索结果中问题的普遍性,值得一提的是,MINPACK 的开源 C/C++ 端口可用,这可以简化使某些东西可以从 .NET 调用的过程。
要使用这个特定的库,
- 下载源代码的发布版本,撰写本文时最新的版本是 1.6.3。
- 打开
cminpack.sln
包含在其中的 Visual C++ 解决方案 ,并构建它以获取库cminpack_dll.dll
.
- 添加
cminpack_dll.dll
到您的 .NET 解决方案并确保在构建时将其复制到输出目录。
- 此时,可以使用P/Invoke 调用该库。
下面的示例显示了如何将二次函数拟合到给定的数据点集合:
class Program
{
static void Main()
{
// Define some test data by 5i + 3i^2. The plan is to let cminpack figure out
// the values 5 and 3.
var data = Enumerable.Range(0, 20)
.Select(i => 5 * i + 3 * Math.Pow(i, 2))
.ToList();
CminpackFuncMn residuals = (p, m, n, x, fvec, iflag) =>
{
unsafe
{
// Update fvec with the values of the residuals x[0]*i + x[1]*i^2 - data[i].
var fvecPtr = (double*)fvec;
var xPtr = (double*)x;
for (var i = 0; i < m; i++)
*(fvecPtr + i) = *xPtr * i + *(xPtr + 1) * Math.Pow(i, 2) - data[i];
}
return 0;
};
// Define an initial (bad) guess for the value of the parameters x.
double[] parameters = { 2d, 2d };
var numParameters = parameters.Length;
var numResiduals = data.Count;
var lwa = numResiduals * numParameters + 5 * numParameters + numResiduals;
// Call cminpack
var info = lmdif1(
fcn: residuals,
p: IntPtr.Zero,
m: numResiduals,
n: numParameters,
x: parameters,
fvec: new double[numResiduals],
tol: 0.00001,
iwa: new int[numParameters],
wa: new double[lwa],
lwa: lwa);
// parameters now contains { 5, 3 }.
Console.WriteLine($"Return value: {info}, x: {string.Join(", ", parameters)}");
}
[DllImport("cminpack_dll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int lmdif1(CminpackFuncMn fcn, IntPtr p, int m, int n, double[] x,
double[] fvec, double tol, int[] iwa, double[] wa, int lwa);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int CminpackFuncMn(IntPtr p, int m, int n, IntPtr x, IntPtr fvec, int iflag);
}
这里通过查看cminpack中的相关头文件可以推断出lmdif1
和的签名。CminpackFuncMn