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

使用嵌套 Lambda 函数实现数值算法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (8投票s)

2010 年 7 月 19 日

CPOL

4分钟阅读

viewsIcon

34667

downloadIcon

514

对于数值计算,使用 Lambda 函数可以弥补 C# 中缺少嵌套方法的不足。

引言

大约二十七年前,我曾用 Pascal 编写过一些数值方法。许多算法都使用了嵌套函数。本文介绍了如何使用嵌套 Lambda 函数,将其中许多函数成功迁移到 C# 4.0。

我当时正在设计一艘游艇,问题是如何不仅要得到一个光滑的曲面,还要计算排水量、浮心和湿表面积等由船体表面定义产生的其他数据。当时还没有可以在我的 BBC 电脑上运行的 CAD 程序。它配备了一个 8 位 6502 处理器,运行速度为 2 MHz,内存为 32 KB。实际上,当时船体线图都是按全尺寸绘制的,并通过柔性木条或样条进行平滑处理。测量是从放样线或比例图上获得的,并使用辛普森法则通过手工计算来确定排水量、浮心以及由船体形状产生的其他数据。

当时,BBC 电脑有一个 Pascal 编译器。我还能够编写一个指定船体的函数。然后,通过使用数值计算,我就可以生成所需的船体数据。此外,我还能够迭代地修改指定船体的参数,除了其他理想的功能外,还能在满足所需倾侧力矩的情况下优化湿表面积。不可否认,当时的图形功能非常基础,但通过使用一般的圆锥曲线作为光滑曲线,可以获得一个光滑的船体。

这艘 12 米的游艇“阿波罗尼乌斯”(注意那些圆锥曲线),是一艘巡航赛艇,已经成功完成了许多远洋比赛。如今,我更喜欢在巴斯海峡(塔斯马尼亚岛和澳大利亚大陆之间一段湍急的水域)的岛屿间巡游。

背景

言归正传。上周,我在整理一些旧纸板文件夹时,偶然发现了我的旧 Pascal 代码,这些代码是用一种早已过时的点阵打印机打印出来的,几乎还能辨认。内容大致如下:

program boatHull(input, output);
type posint = 1..maxint;
{Much code omitted - for brevity}

    function integral(function f(x : real) : real; a, b : real; n : posint) : real;
    type
    {types} ;
    var
    {vars};
    begin
        {chosen algorithm}
    end {integral};

   function doubleIntegral (function f(x, y : real) : real;
                   function g(y : real): real; function h(y : real): real; a,
                    b : real; m, n: posint ) : real;
                    {end of parameters}
       function area(y: real): real; {
            first nested function; note g, h, and m are in scope}

            function argument(x : real) : real; {
                second nested function; note y is in scope}
            begin {argument}
                argument := f(x,y)
            end {argument};

        begin {area}
            area := integral(argument, g(y), h(y), m)
        end {area};

    begin {doubleIntegral}
        doubleIntegral:= integral(area, a, b, n)
    end {doubleIntegral};

begin {demo}
   {input, output code goes here}
end. {boatHull}

我们可以看到,函数 integraldoubleIntegral 的作用域使它们对程序 boatHull 可用。但是,函数 doubleIntegral 能够调用函数 integral。函数 area 嵌套在 doubleIntegral 中,并且只能供 doubleIntegral 使用。同样,函数参数也只能供 area 函数使用。

封装是使用 Pascal 函数 doubleIntegral 中的嵌套函数的决定性原因。嵌套函数 area 在 doubleIntegral 之外没有任何意义,函数参数也没有理由存在于 area 函数之外。

迁移到 C# 4.0

在我退休后,我为教育环境设计和实现在线 SQL 数据库应用程序。我在 .aspx 页面的代码隐藏中使用 C#。自然,我想知道如何使用与上述类似的编码原则,通过嵌套函数来实现双重积分。

在 C# 中,Pascal 函数对应于方法。但是,无法直接翻译代码,因为 C# 虽然支持嵌套类,但不支持嵌套方法。然而,对于 C# 来说相对较新的 Lambda 函数可以被嵌套。于是,我下载了 Visual C# 2010 Express(.NET 4.0 的一个消费者),准备尝试一下。

此时,如果读者不熟悉 Lambda 符号和委托,那么他们应该查阅相关的参考资料,因为这类资料非常多。

首先,在初步介绍之后,是一些委托

using System;
using System.Linq;
 
namespace Computationals
{
    // Delegates
 
    delegate double Function(double x);
    delegate double Functionxy(double x, double y);
    delegate double Integral(Function f, double a, double b, int n);
    // for integration of function f over n subintervals from a to b
 
    public class LambdaNumericals
    {

接下来,是一些积分方法:首先是辛普森法

static Integral SimpInt()
{
// Simpson's rule 
// integration of function f, over n (even) subintervals in the interval [a,b]
Integral integral = (f, a, b, n) =>                
  {
// n, not even, throw exception code should go here
  double sum = 0;
  double h = (b - a) / n;
  for (int i = 0; i < n; i = i + 2)
     sum = sum + (f(a + i * h) + 4 * f(a + (i + 1) * h) + f(a + (i + 2) * h)) * h / 3;
  return sum;
  };
return integral;
}

其次,是高斯-勒让德 4 点算法

 static Integral GaussInt()
{           
// 4-point Gaussian rule integration of the function f, 
// over n subintervals in the interval (a,b)
 Integral integral = (f, a, b, n) =>
    {  
     double[] weight = new double[4];
     weight[2] = 0.652145154862546;
     weight[1] = weight[2];
     weight[3] = 0.347854845137454;
     weight[0] = weight[3];
 
     double[] point = new double[4];
     point[3] = 0.861136311594053;
     point[0] = -point[3];
     point[2] = 0.339981043584856;
     point[1] = -point[2];
 
     double sum = 0;
     double h = (b - a) / n;
 
     double xr, xl, m, c, s;
 
     for (int i = 0; i < n; i++)
        {
         s = 0;
 
         xl = a + i * h;
         xr = xl + h;
         m = (xr - xl) / 2;
         c = (xr + xl) / 2;
 
         for (int j = 0; j < 4; j++)
           s = s + weight[j] * f(m * point[j] + c) * m;
 
         sum = sum + s;
        }
     return sum;
    };
 return integral;
}

现在,我们可以将以上内容用于

 // Double integration using nested lambda functions
 // Inner integral dx, from x=g(y) to x=h(y), over m subintervals, using algorithm inInt
 // Outer integral dy, from y=a to y=b, over n subintervals, using algorithm outInt
 
static double dubInt(Functionxy fxy, Function g, Function h, double a, double b, 
        int m, int n, Integral inInt, Integral outInt)
   {
      return outInt((double y) => inInt((double x) => fxy(x, y), g(y), h(y), m), a, b, n);
  }

inInt 和 outInt 是接受积分函数(SimpInt() 或 GaussInt())的参数。
Pascal 的 doubleIntegral 函数变为 outInt(area, a ,b, n)。
Pascal 代码中的 area 函数对应于片段 (double y) => inInt(argument, g(y), h(y), m)。
最后,argument 函数对应于 (double x) => fxy(x, y)。

为了测试我们的代码,我们可以定义以下测试函数作为参数

public static double p(double x, double y)
        {
            return x * y;
        }
 
        public static double f1(double x)
        {
            return x;
        }
 
        public static double f2(double x)
        {
            return 2 * x;
        }
 
        public static double sine(double x)
        {
            return Math.Sin(x);
        }

然后,我们可以使用以下方式调用我们的积分方法

public static void Main()
  {
   Integral integrate1, integrate2;
 
   integrate1 = SimpInt();
   Console.WriteLine(integrate1(sine, 0, Math.PI, 60));
   // result 2.00000008353986
 
   integrate2 = GaussInt();
   Console.WriteLine(integrate2(sine, 0, Math.PI, 15));
   // result 2
 
   Console.WriteLine(integrate2(Math.Sin, 0, Math.PI, 15));
   // result 2
 
   // analytically, exactly 2 for the above integrals – 
   // note the superior convergence of Gauss 4-point algorithm
 
   // Now to test our double integration functtion
   Console.WriteLine(dubInt(p, f1, f2, 1, 2, 4, 4, integrate1, integrate2));
   // result 5.625
   // analytically 5 5/8
  }
}

看点

接着,通过类似的策略,我已经将 Lambda 方法论应用于了微分、二次微分、曲线长度、偏导数、表面积和曲率。包含示例的代码在下载文件中。

所演示的技术可以轻松地扩展到极坐标积分、三重积分以及圆柱坐标和球坐标积分。参数函数、样条和曲面以及微分方程都是 Lambda 函数的潜在应用。毫无疑问,这仅仅是冰山一角。

代码应用限制

总之,我必须强调,本文关注的是 Lambda 函数和嵌套 Lambda 函数应用于数值计算的技术。在库的上下文中,实际的数值算法当然会包含错误规范。此外,某些常量的最佳值依赖于机器。我对上述方法适用于特定应用的适用性不做任何建议。

尽管如此,一旦程序员熟悉了 Lambda 函数的语法,它们就有可能为非平凡的数值计算提供非常简洁和高效的入口。

© . All rights reserved.