src/cs/Tests/Matrix.Tests.cs
using Xunit; using FsCheck; using FsCheck.Xunit; using System; using Prelude; namespace MatrixTests { public class UnitTests { [Property] public Property NxN_Matrix_Has_N_Rows_and_N_Columns(PositiveInt n) { var size = n.Get; var matrix = new Matrix(size); return (matrix.Size[0] == matrix.Size[1]).Label("NxN matrix has square shape") .And(matrix.Rows.Length == matrix.Rows[0].Length).Label("NxN matrix has same number of rows and columns") .And(matrix.Size[0] == matrix.Rows.Length).Label("NxN is characterized by N rows and N columns"); } [Property] public Property MxN_Matrix_Has_M_Rows_and_N_Columns(PositiveInt m, PositiveInt n) { var rows = m.Get; var cols = n.Get; var matrix = new Matrix(rows, cols); return (matrix.Size[0] == rows && (matrix.Rows.Length == rows)).Label("MxN matrix has M rows") .And(matrix.Size[1] == cols && (matrix.Rows[0].Length == cols)).Label("MxN matrix has N columns"); } [Property] public Property Identity_Matrix_is_Square(PositiveInt n) { var size = n.Get; var matrix = new Matrix(size); var rows = matrix.Size[0]; var cols = matrix.Size[1]; return (rows == size && rows == cols).Label("Identity matrix has same number of rows and columns"); } [Property] [Trait("Category", "Determinant")] public Property Multiplying_Row_By_K_Multiplies_Det_By_K(PositiveInt k, PositiveInt a, PositiveInt b, PositiveInt c, PositiveInt d) { var A = new Matrix(2); var B = new Matrix(2); A.Rows[0] = new double[] { a.Get, b.Get }; A.Rows[1] = new double[] { c.Get, d.Get }; B.Rows[0] = new double[] { (k.Get * a.Get), (k.Get * b.Get) }; B.Rows[1] = new double[] { c.Get, d.Get }; return (Matrix.Det(B) == (k.Get * Matrix.Det(A))).Label("Multiply row in A by k ==> k * Det(A)"); } [Property] [Trait("Category", "Determinant")] public Property Determinants_Are_Invariant_Under_Matrix_Transposition(PositiveInt a, PositiveInt b, PositiveInt c, PositiveInt d) { var A = new Matrix(2); A.Rows[0] = new double[] { a.Get, b.Get }; A.Rows[1] = new double[] { c.Get, d.Get }; return (Matrix.Det(A) == Matrix.Det(Matrix.Transpose(A))).Label("Determinant is invariant under matrix transpose"); } [Property] [Trait("Category", "Determinant")] public Property Two_Identical_Rows_Makes_Determinant_Zero(PositiveInt a, PositiveInt b, PositiveInt c, PositiveInt d, PositiveInt e, PositiveInt f) { var A = new Matrix(3); A.Rows[0] = new double[] { a.Get, b.Get, c.Get }; A.Rows[1] = new double[] { d.Get, e.Get, f.Get }; A.Rows[2] = new double[] { a.Get, b.Get, c.Get }; return (Matrix.Det(A) == 0).Label("A has two identical rows ==> Det(A) == 0"); } [Theory] [InlineData(1)] [InlineData(2)] [InlineData(3)] public void Can_Create_Unit_Matrix(int N) { var unit = Matrix.Unit(N); Assert.Equal(new int[] { N,N }, unit.Size); var expected = new double[N]; Array.Fill(expected,1); foreach (double[] Row in unit.Rows) { Assert.Equal(expected, Row); } } [Fact] public void Can_Create_Identity_Matrix() { var identity2 = Matrix.Identity(2); Assert.Equal(new double[] { 1,0 }, identity2.Rows[0]); Assert.Equal(new double[] { 0,1 }, identity2.Rows[1]); var identity4 = Matrix.Identity(4); Assert.Equal(new double[] { 1,0,0,0 }, identity4.Rows[0]); Assert.Equal(new double[] { 0,1,0,0 }, identity4.Rows[1]); Assert.Equal(new double[] { 0,0,1,0 }, identity4.Rows[2]); Assert.Equal(new double[] { 0,0,0,1 }, identity4.Rows[3]); } [Fact] public void Can_Transpose_NxN_Matrices() { var A = new Matrix(3); double[,] rows = new double[3,3] { { 1,2,3 }, { 4,5,6 }, { 7,8,9 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var T = Matrix.Transpose(A); Assert.Equal(new double[] { 1,4,7 }, T.Rows[0]); Assert.Equal(new double[] { 2,5,8 }, T.Rows[1]); Assert.Equal(new double[] { 3,6,9 }, T.Rows[2]); var B = Matrix.Transpose(T); Assert.Equal(new double[] { 1, 2, 3 }, B.Rows[0]); Assert.Equal(new double[] { 4, 5, 6 }, B.Rows[1]); Assert.Equal(new double[] { 7, 8, 9 }, B.Rows[2]); } [Fact] public void Can_Transpose_MxN_Matrices() { var A = new Matrix(2,3); double[,] rows = new double[2,3] { { 1,2,3 },{ 4,5,6 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var T = Matrix.Transpose(A); Assert.Equal(new double[] { 1,4 }, T.Rows[0]); Assert.Equal(new double[] { 2,5 }, T.Rows[1]); Assert.Equal(new double[] { 3,6 }, T.Rows[2]); var B = Matrix.Transpose(T); Assert.Equal(new double[] { 1, 2, 3 }, B.Rows[0]); Assert.Equal(new double[] { 4, 5, 6 }, B.Rows[1]); } [Theory] [InlineData(2)] [InlineData(3)] [InlineData(4)] public void Can_Add_Matrices(int N) { var sum = new Matrix(N); var unit = Matrix.Unit(N); for (int i = 0; i < N; ++i) { sum = Matrix.Add(sum,unit); } var expected = new double[N]; Array.Fill(expected,N); foreach (var Row in sum.Rows) Assert.Equal(expected, Row); } [Fact] public void Can_Calculate_Dot_Product_of_Two_NxN_Matrices() { var A = Matrix.Identity(2); A.Rows[1][1] = 0; var B = Matrix.Identity(2); B.Rows[0][0] = 0; var product = Matrix.Dot(A, B); Assert.Equal(new int[] { 2,2 }, product.Size); Assert.Equal(new double[] { 0,0 }, product.Rows[0]); Assert.Equal(new double[] { 0,0 }, product.Rows[1]); double[,] rows = new double[2,2]; rows = new double[,] { { 1,2 }, { 3,4 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } rows = new double[,] { { 1,1 }, { 0,2 } }; foreach (var Index in B.Indexes()) { int i = Index[0], j = Index[1]; B.Rows[i][j] = rows[i,j]; } product = Matrix.Dot(A, B); Assert.Equal(new double[] { 1,5 }, product.Rows[0]); Assert.Equal(new double[] { 3,11 }, product.Rows[1]); product = Matrix.Dot(B, A); Assert.Equal(new double[] { 4,6 }, product.Rows[0]); Assert.Equal(new double[] { 6,8 }, product.Rows[1]); } [Fact] public void Can_Calculate_Dot_Product_of_Two_MxN_Matrices() { var A = new Matrix(1,2); var B = new Matrix(2,3); double[,] rowsA = new double[1,2]; rowsA = new double[,] { { 2,1 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rowsA[i,j]; } double[,] rowsB = new double[2,3]; rowsB = new double[,] { { 1,-2,0 }, { 4,5,-3 } }; foreach (var Index in B.Indexes()) { int i = Index[0], j = Index[1]; B.Rows[i][j] = rowsB[i,j]; } var product = Matrix.Dot(A, B); Assert.Equal(new int[] { 1,3 }, product.Size); Assert.Equal(new double[] { 6,1,-3 }, product.Rows[0]); } [Fact] public void Can_Verify_the_Dot_Product_of_a_Matrix_and_its_Inverse() { var A = new Matrix(2); var B = new Matrix(2); double[,] rowsA = new double[2,2]; rowsA = new double[,] { { 2,5 }, { 1,3 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rowsA[i,j]; } double[,] rowsB = new double[2,2]; rowsB = new double[,] { { 3,-5 }, { -1,2 } }; foreach (var Index in B.Indexes()) { int i = Index[0], j = Index[1]; B.Rows[i][j] = rowsB[i,j]; } var product = Matrix.Dot(A, B); Assert.Equal(new int[] { 2,2 }, product.Size); Assert.Equal(Matrix.Identity(2).Rows, product.Rows); } [Theory] [InlineData(1)] [InlineData(7)] public void Can_Multiply_Matrix_by_Scalar_Constant(int k) { var sum = new Matrix(2); var identity = Matrix.Identity(2); for (int i = 0; i < k; ++i) { sum = Matrix.Add(sum,identity); } var A = Matrix.Identity(2); var product = Matrix.Multiply(A, k); Assert.Equal(sum.Rows, product.Rows); Assert.Equal(new double[] { k,0 }, product.Rows[0]); Assert.Equal(new double[] { 0,k }, product.Rows[1]); } [Fact] public void Can_Calculate_the_Inverse_of_a_Matrix() { var A = new Matrix(3); double[,] rows = new double[3,3]; rows = new double[,] { { 1,2,3 }, { 2,3,4 }, { 1,5,7 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var inverse = Matrix.Invert(A); Assert.Equal(new double[] { 0.5,0.5,-0.5 }, inverse.Rows[0]); Assert.Equal(new double[] { -5,2,1 }, inverse.Rows[1]); Assert.Equal(new double[] { 3.5,-1.5,-0.5 }, inverse.Rows[2]); Assert.Equal(Matrix.Identity(3).Rows, Matrix.Dot(A, inverse).Rows); } [Fact] public void Can_Calculate_Matrix_Trace() { var A = new Matrix(3); double[,] rows = new double[3,3]; rows = new double[,] { { 1,2,3 }, { 4,5,6 }, { 7,8,9 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } Assert.Equal(15, Matrix.Trace(A)); } [Fact] [Trait("Category", "Instance")] public void Can_Create_Clones() { var A = new Matrix(2); double[,] rows = new double[2,2]; rows = new double[,] { { 1,2 }, { 3,4 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var B = A.Clone(); Assert.Equal(new double[] { 1,2 }, B.Rows[0]); Assert.Equal(new double[] { 3,4 }, B.Rows[1]); } [Fact] [Trait("Category", "Instance")] public void Can_Remove_Rows() { var A = new Matrix(3); double[,] rows = new double[3,3]; rows = new double[,] { { 1,2,3 }, { 4,5,6 }, { 7,8,9 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var edited = A.RemoveRow(0); Assert.Equal(new int[] { 2,3 }, edited.Size); Assert.Equal(new double[] { 4,5,6 }, edited.Rows[0]); Assert.Equal(new double[] { 7,8,9 }, edited.Rows[1]); edited = A.RemoveRow(1); Assert.Equal(new int[] { 2,3 }, edited.Size); Assert.Equal(new double[] { 1,2,3 }, edited.Rows[0]); Assert.Equal(new double[] { 7,8,9 }, edited.Rows[1]); edited = A.RemoveRow(2); Assert.Equal(new int[] { 2,3 }, edited.Size); Assert.Equal(new double[] { 1,2,3 }, edited.Rows[0]); Assert.Equal(new double[] { 4,5,6 }, edited.Rows[1]); edited = A.RemoveRow(2).RemoveColumn(0); Assert.Equal(new int[] { 2,2 }, edited.Size); Assert.Equal(new double[] { 2,3 }, edited.Rows[0]); Assert.Equal(new double[] { 5,6 }, edited.Rows[1]); } [Fact] [Trait("Category", "Instance")] public void Can_Remove_Columns() { var A = new Matrix(3); double[,] rows = new double[3,3]; rows = new double[,] { { 1,2,3 }, { 4,5,6 }, { 7,8,9 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } var edited = A.RemoveColumn(0); Assert.Equal(new int[] { 3,2 }, edited.Size); Assert.Equal(new double[] { 2,3 }, edited.Rows[0]); Assert.Equal(new double[] { 5,6 }, edited.Rows[1]); Assert.Equal(new double[] { 8,9 }, edited.Rows[2]); edited = A.RemoveColumn(1); Assert.Equal(new int[] { 3,2 }, edited.Size); Assert.Equal(new double[] { 1,3 }, edited.Rows[0]); Assert.Equal(new double[] { 4,6 }, edited.Rows[1]); Assert.Equal(new double[] { 7,9 }, edited.Rows[2]); edited = A.RemoveColumn(2); Assert.Equal(new int[] { 3,2 }, edited.Size); Assert.Equal(new double[] { 1,2 }, edited.Rows[0]); Assert.Equal(new double[] { 4,5 }, edited.Rows[1]); Assert.Equal(new double[] { 7,8 }, edited.Rows[2]); edited = A.RemoveColumn(0).RemoveRow(0); Assert.Equal(new int[] { 2,2 }, edited.Size); Assert.Equal(new double[] { 5,6 }, edited.Rows[0]); Assert.Equal(new double[] { 8,9 }, edited.Rows[1]); } [Fact] [Trait("Category", "Instance")] public void Can_Be_Converted_to_String() { var A = new Matrix(2); double[,] rows = new double[2,2]; rows = new double[,] { { 1,2 }, { 3,4 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } string output = A.ToString(); Assert.Equal("1,2\r\n3,4", output); } [Fact] [Trait("Category", "Determinant")] public void Can_Calculate_Determinant_for_2x2_Matrices() { Assert.Equal(0, Matrix.Det(Matrix.Unit(2))); var A = new Matrix(2); double[,] rows = new double[2, 2] { { 1,2 }, { 3,4 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i, j]; } Assert.Equal(-2, Matrix.Det(A)); } [Theory] [InlineData(3)] [Trait("Category", "Determinant")] public void Can_Calculate_Determinant_for_3x3_Matrices(int N) { Assert.Equal(0, Matrix.Det(Matrix.Unit(N))); Assert.Equal(1, Matrix.Det(Matrix.Identity(N))); var A = new Matrix(N); double[,] rows = new double[N, N]; rows = new double[,] { { 2,3,-4 }, { 0,-4,2 }, { 1,-1,5 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i, j]; } Assert.Equal(-46, Matrix.Det(A)); A = new Matrix(N); rows = new double[,] { { 1,2,3 }, { 4,-2,3 }, { 2,5,-1 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i, j]; } Assert.Equal(79, Matrix.Det(A)); } [Theory] [InlineData(4)] [Trait("Category", "Determinant")] public void Can_Calculate_Determinant_for_4x4_Matrices(int N) { Assert.Equal(0, Matrix.Det(Matrix.Unit(N))); Assert.Equal(1, Matrix.Det(Matrix.Identity(N))); var A = new Matrix(N); double[,] rows = new double[N,N]; rows = new double[,] { { 3,-2,-5,4 }, { -5,2,8,-5 }, { -2,4,7,-3 }, { 2,-3,-5,8 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } Assert.Equal(-54, Matrix.Det(A)); A = new Matrix(N); rows = new double[,] { { 5,4,2,1 }, { 2,3,1,-2 }, { -5,-7,-3,9 }, { 1,-2,-1,4 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i,j]; } Assert.Equal(38, Matrix.Det(A)); } [Theory] [InlineData(6)] [Trait("Category", "Determinant")] [Trait("Category", "LongDuration")] public void Can_Calculate_Determinant_for_Matrices_Larger_than_4x4(int N) { Assert.Equal(0, Matrix.Det(Matrix.Unit(10))); Assert.Equal(1, Matrix.Det(Matrix.Identity(10))); var A = new Matrix(N); double[,] rows = new double[N, N]; rows = new double[,] { { 12,22,14,17,20,10 }, { 16,-4,7,1,-2,15 }, { 10,-3,-2,3,-2,8 }, { 7,12,8,9,11,6 }, { 11,2,4,-8,1,9 }, { 24,6,6,3,4,22 } }; foreach (var Index in A.Indexes()) { int i = Index[0], j = Index[1]; A.Rows[i][j] = rows[i, j]; } Assert.Equal(12228, Matrix.Det(A)); } } } |