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 没有任何关系。)
- 不要对矩阵使用复合赋值运算符(诸如 +=、-=、*=、/=)
- 如果当前正在编写的函数的输入/输出参数有矩阵,那么需要遵循以下规则
内存管理
与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 请按任意键继续. . .