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);
            }
        }
        [Theory]
        [InlineData(1)]
        [InlineData(2)]
        [InlineData(3)]
        public void Can_Create_Integer_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[,] {
                { 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[,] {
                { 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[,] {
                { 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, 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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[,] {
                { 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));
        }
    }
}