使用 ILNumerics 进行矩阵运算

ILNumerics 可用于在 .NET 程序中进行矩阵运算,就像 MATLAB 一样便捷。

可以使用 VS 的 NuGet 程序包管理器 来安装此库。

下面使用一个简单的控制台应用程序来介绍其用法。

TOC

用法初探

using System;
using ILNumerics;

namespace ILNumericsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ILArray A = ILMath.array(new[]
            {
                1.0, 2.0, 3.0,
                4.0, 5.0, 6.0,
                7.0, 8.0, 9.0
            }, 3, 3);
            Console.WriteLine("A = {0}", A);
            Console.WriteLine("rank(A) = {0}", ILMath.rank(A));
            Console.WriteLine("eye = {0}", ILMath.eye(4, 5));
            Console.WriteLine("rand = {0}", ILMath.rand(4, 5));
            Console.WriteLine("randn = {0}", ILMath.randn(4, 5));
            A.Dispose();
        }
    }
}
A = <Double> [3,3]
         1          4          7
         2          5          8
         3          6          9
rank(A) = <Double>          2
eye = <Double> [4,5]
         1          0          0          0          0
         0          1          0          0          0
         0          0          1          0          0
         0          0          0          1          0
rand = <Double> [4,5]
   0.07688    0.05419    0.42710    0.59218    0.21221
   0.49736    0.82061    0.28775    0.24880    0.22409
   0.75808    0.00029    0.96645    0.25829    0.87084
   0.89038    0.66059    0.01196    0.79845    0.43328
randn = <Double> [4,5]
  -0.00510   -1.69297    0.79831   -0.18837   -0.61365
  -0.81711   -0.58147   -0.64654    1.04699    1.87848
   0.42923   -1.48460   -0.65863   -1.22596   -0.51767
   0.28376    0.54479   -0.68697   -0.96019    0.41380
请按任意键继续. . .

这里的`ILArray<>`就是一个普通的矩阵类了。由此可见,ILNumerics 的操作和 MATLAB 有一定的相似性。

`ILArray<>`实现了`IDispose`接口,所以一定要保证所有的`ILArray<>`在使用完毕后都会被调用`Dispose`函数。

当然,更好的办法是使用`using`语句,其可以在离开`using`块的时候自动调用`Dispose`函数:

            ILArray<double> A = ILMath.array(new[]
            {
                1.0, 2.0, 3.0,
                4.0, 5.0, 6.0,
                7.0, 8.0, 9.0
            }, 3, 3);
            using (A)
            {
                Console.WriteLine("A = {0}", A);
            }

还有一点,在局部类型推理如此常见的今天,我们还需要手动指定变量`A`的类型?也许你想这样写:var A = ILMath.array(new[] … ,这固然没错。

我们来看看`ILMath.array`的定义

    /// <summary>
    /// Create array, given elements and size
    /// </summary>
    /// <returns>
    /// Newly created array
    /// </returns>
    public static ILRetArray<T> array<T>(T[] elements, params int[] size);

很明显,这个函数的返回类型是`ILRetArray<>`。是的,在 ILArray<double> A = ILMath.array(new[] … 一行中发生了隐式数据类型转换。在转换的过程中,`ILRetArray<>`对应的表达式被计算,并存入`A`中。希望大家能够注意到这个细节。

在 ILNumerics 的一般“使用规则”中,有明确提到,不要使用局部变量的类型自动推理,也就是说,不要显式(使用`ILRetArray<>`)或者隐式(使用`var`推理)定义`ILRetArray<>`类型的局部变量。否则在使用过程中,可能会出现`NullReferenceException`异常。

实际上,建议大家在使用此库之前,仔细阅读General Rules – ILNumerics – Numerical Math Library for C# and .NET一页的全部内容。大体而言,使用规则可以归纳为

    • 在使用矩阵变量时不要使用局部类型自动推理。(当然,这并不妨碍你定义诸如var i = 10; /* i is int */ 这样的局部变量,因为他们和 ILNumerics 没有任何关系。)
    • 不要对矩阵使用复合赋值运算符(诸如 +=、-=、*=、/=)
    • 如果当前正在编写的函数的输入/输出参数有矩阵,那么需要遵循以下规则

      Essential Function Rules of ILNumerics: The first rule declares specific array types for input parameters and return values in function declarations. The second rule creates artificial scopes around the function body and the third rule handles assignments to output parameters.

内存管理

与MATLAB不同,C#不是一门动态语言,因此所有的变量都必须显式声明出来。此外,由于,`ILArray<>`实现了`IDispose`,因此在计算结束后必须调用其`Dispose`函数。

using System;
using ILNumerics;

namespace ILNumericsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ILArray<double> A = ILMath.array(new[]
            {
                1.0, 2.0, 3.0,
                4.0, 5.0, 6.0,
                7.0, 8.0, 10.0
            }, 3, 3))
            using (ILArray<double> B = ILMath.array(new[] { 1.0, 5.0, 10.0 }, 3, 1))
            {
                Console.WriteLine("A = {0}", A);
                Console.WriteLine("b = {0}", B);
                Console.WriteLine("x = {0}", ILMath.linsolve(A, B));
            }
        }
    }
}
A = <Double> [3,3]
         1          4          7
         2          5          8
         3          6         10
b = <Double> [3,1]
         1
         5
        10
x = <Double> [3,1]
         6
        -3
         1
请按任意键继续. . .

这仅仅是一个简单的线性方程求解程序。如果我们需要使用大量的中间矩阵变量,那么势必会造成`Dispose()`(或者`using`)满天飞的情况。好在 ILNumerics 提供了 `ILScope` 机制,可以解决这一问题。

using System;
using ILNumerics;

namespace ILNumericsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ILScope.Enter())
            {
                var A = ILMath.array(new[]
                {
                    1.0, 2.0, 3.0,
                    4.0, 5.0, 6.0,
                    7.0, 8.0, 10.0
                }, 3, 3);
                var B = ILMath.array(new[] { 1.0, 5.0, 10.0 }, 3, 1);
                Console.WriteLine("A = {0}", A);
                Console.WriteLine("b = {0}", B);
                Console.WriteLine("x = {0}", ILMath.linsolve(A, B));
            }
        }
    }
}

在离开`using`块时,`using`块中定义的所有的矩阵变量均会被`Dispose`。注意,这是`ILScope`的行为,而不是`using`块的行为。

关于`ILScope`的详细信息,请参阅 Optimizing Performance – ILNumerics – Numerical Math Library for .NET and C#

用法示例

下面的代码简要介绍了矩阵的常用功能。大部分的操作可以和MATLAB相类比。

using System;
using System.Linq;
using ILNumerics;

namespace ILNumericsTest
{
    class Program
    {
        static ILArray<double> staticArray;
        static void Main(string[] args)
        {
            using (ILScope.Enter())
            {
                //10行 10列 的矩阵。
                ILArray<double> A = ILMath.zeros(10, 10);
                //获取矩阵的长度和维度。
                //长度是行和列中较大的那个。一般用于获取向量的长度。
                Console.WriteLine("A.Length = {0}, A.Size = {1}", A.Length, A.Size);
                //双下标索引。
                //注意所有的下标均从 0 开始,
                //也就是说,A[1, 1] 是第二行、第二列。
                //MATLAB 是以 1 为下标的。
                A[1, 0] = 1;
                //支持单下标索引。
                for (int i = 0; i < 15; i++) A[i] = i;
                //分片操作
                //例如,给第一行赋随机数。
                A[0, ":"] = ILMath.rand(1, 10);
                //将一小片子方阵赋为单位阵。
                A["1:4", "1:4"] = ILMath.eye(4, 4);
                //将右下角的 5x5 方阵赋为单位阵。
                A["5:end", "5:end"] = ILMath.eye(5, 5);
                //当然,可以随时使用 A.ToString() 来获得矩阵的字符串表示形式。
                //此处使用了控制台提供的复合格式设置。
                Console.WriteLine("A = {0}", A);
                //取左上角 4x4 子阵。注意索引的下标是从0开始的。
                ILArray<double> APrime = A["0:3", "0:3"];
                Console.WriteLine("A' = {0}", APrime);
                //基本运算
                Console.WriteLine("A' .* A' = {0}", APrime * APrime);
                Console.WriteLine("A' * A' = {0}", ILMath.multiply(APrime, APrime));
                //对矩阵求逆。这里使用的是求解线性方程组的方法。
                //注意 ILMath.invert(A) 是将每个元素取相反数,与 -A 效果相同。
                ILArray<double> InvAPrime = ILMath.linsolve(ILMath.eye(4, 4), APrime);
                Console.WriteLine("inv(A) = {0}", InvAPrime);
            }
        }
    }
}
A.Length = 10, A.Size = [10,10]
A = <Double> [10,10]
   0.99374    0.38286    0.28393    0.25934    0.45773    0.62894    0.44294    0.52775    0.87408    0.51702
         1          1          0          0          0          0          0          0          0          0
         2          0          1          0          0          0          0          0          0          0
         3          0          0          1          0          0          0          0          0          0
         4          0          0          0          1          0          0          0          0          0
         5          0          0          0          0          1          0          0          0          0
         6          0          0          0          0          0          1          0          0          0
         7          0          0          0          0          0          0          1          0          0
         8          0          0          0          0          0          0          0          1          0
         9          0          0          0          0          0          0          0          0          1
A' = <Double> [4,4]
   0.99374    0.38286    0.28393    0.25934
         1          1          0          0
         2          0          1          0
         3          0          0          1
A' .* A' = <Double> [4,4]
   0.98753    0.14658    0.08062    0.06726
         1          1          0          0
         4          0          1          0
         9          0          0          1
A' * A' = <Double> [4,4]
   2.71627    0.76331    0.56608    0.51707
   1.99374    1.38286    0.28393    0.25934
   3.98749    0.76571    1.56786    0.51869
   5.98123    1.14857    0.85179    1.77803
inv(A) = <Double> [4,4]
   0.99374    0.38286    0.28393    0.25934
         1          1          0          0
         2          0          1          0
         3          0          0          1
请按任意键继续. . .

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Content is available under CC BY-SA 3.0 unless otherwise noted.