tests/Matrix.Tests.ps1

& (Join-Path $PSScriptRoot '_setup.ps1') 'classes'

Describe 'Matrix class static methods' {
    It 'can create an NxN multi-dimensional array' {
        $N = 5
        $Matrix = [MatrixTest]::New($N)
        $Matrix.Values.Count | Should -Be $N
        $Matrix.Values[0].Count | Should -Be $N
    }
    It 'can create an MxN multi-dimensional array' {
        $M = 8
        $N = 6
        $Matrix = [MatrixTest]::New($M,$N)
        $Matrix.Values.Count | Should -Be $M
        $Matrix.Values[0].Count | Should -Be $N
    }
    It 'can create unit matrices' {
        $Unit = [MatrixTest]::Unit(1)
        $Unit.Order | Should -Be 1,1
        $Unit.Values[0] | Should -Be 1
        $Unit = [MatrixTest]::Unit(2)
        $Unit.Order | Should -Be 2,2
        $Unit.Values[0] | Should -Be 1,1
        $Unit.Values[1] | Should -Be 1,1
        $Unit = [MatrixTest]::Unit(3)
        $Unit.Order | Should -Be 3,3
        $Unit.Values[0] | Should -Be 1,1,1
        $Unit.Values[1] | Should -Be 1,1,1
        $Unit.Values[2] | Should -Be 1,1,1
    }
    It 'can create identity matrices' {
        $Identity = [MatrixTest]::Identity(2)
        $Identity.Order | Should -Be 2,2
        $Identity.Values[0] | Should -Be 1,0
        $Identity.Values[1] | Should -Be 0,1
        $Identity = [MatrixTest]::Identity(4)
        $Identity.Order | Should -Be 4,4
        $Identity.Values[0] | Should -Be 1,0,0,0
        $Identity.Values[1] | Should -Be 0,1,0,0
        $Identity.Values[2] | Should -Be 0,0,1,0
        $Identity.Values[3] | Should -Be 0,0,0,1
    }
    It 'can transpose matrices' {
        $Matrix = [MatrixTest]::New(3)
        $Matrix.Values[0][0] = 1
        $Matrix.Values[0][1] = 2
        $Matrix.Values[0][2] = 3
        $Matrix.Values[1][0] = 4
        $Matrix.Values[1][1] = 5
        $Matrix.Values[1][2] = 6
        $Matrix.Values[2][0] = 7
        $Matrix.Values[2][1] = 8
        $Matrix.Values[2][2] = 9
        $Matrix.Values[0] | Should -Be 1,2,3
        $Matrix.Values[1] | Should -Be 4,5,6
        $Matrix.Values[2] | Should -Be 7,8,9
        $Transposed = [MatrixTest]::Transpose($Matrix)
        $Transposed.Values[0] | Should -Be 1,4,7
        $Transposed.Values[1] | Should -Be 2,5,8
        $Transposed.Values[2] | Should -Be 3,6,9
        $Original = [MatrixTest]::Transpose($Transposed)
        $Original.Values[0] | Should -Be 1,2,3
        $Original.Values[1] | Should -Be 4,5,6
        $Original.Values[2] | Should -Be 7,8,9
    }
    It 'can add two or more Matrices' {
        $A = [MatrixTest]::Identity(2)
        $Sum = [MatrixTest]::Add($A,$A)
        $Sum.Values[0] | Should -Be 2,0
        $Sum.Values[1] | Should -Be 0,2
        $Sum = [MatrixTest]::Add($A,$A,$A)
        $Sum.Values[0] | Should -Be 3,0
        $Sum.Values[1] | Should -Be 0,3
    }
    It 'can calculate the determinant for 2x2 matrices' {
        [MatrixTest]::Det([MatrixTest]::Unit(2)) | Should -Be 0
        [MatrixTest]::Det([MatrixTest]::Identity(2)) | Should -Be 1
        $A = [MatrixTest]::New(2)
        $A.Values[0][0] = 1
        $A.Values[0][1] = 2
        $A.Values[1][0] = 3
        $A.Values[1][1] = 4
        [MatrixTest]::Det($A) | Should -Be -2
    }
    It 'can calculate the determinant for 3x3 matrices' {
        [MatrixTest]::Det([MatrixTest]::Unit(3)) | Should -Be 0
        [MatrixTest]::Det([MatrixTest]::Identity(3)) | Should -Be 1
        $A = [MatrixTest]::New(3)
        $A.Values[0][0] = 2
        $A.Values[0][1] = 3
        $A.Values[0][2] = -4
        $A.Values[1][0] = 0
        $A.Values[1][1] = -4
        $A.Values[1][2] = 2
        $A.Values[2][0] = 1
        $A.Values[2][1] = -1
        $A.Values[2][2] = 5
        [MatrixTest]::Det($A) | Should -Be -46
        $A.Det() | Should -Be -46
        $A = [MatrixTest]::New(3)
        $A.Values[0][0] = 1
        $A.Values[0][1] = 2
        $A.Values[0][2] = 3
        $A.Values[1][0] = 4
        $A.Values[1][1] = -2
        $A.Values[1][2] = 3
        $A.Values[2][0] = 2
        $A.Values[2][1] = 5
        $A.Values[2][2] = -1
        [MatrixTest]::Det($A) | Should -Be 79
        $A.Det() | Should -Be 79
    }
    It 'can calculate the determinant for 4x4 matrices' {
        [MatrixTest]::Det([MatrixTest]::Unit(4)) | Should -Be 0
        [MatrixTest]::Det([MatrixTest]::Identity(4)) | Should -Be 1
        $A = [MatrixTest]::New(4)
        $A.Values[0][0] = 3
        $A.Values[0][1] = -2
        $A.Values[0][2] = -5
        $A.Values[0][3] = 4
        $A.Values[1][0] = -5
        $A.Values[1][1] = 2
        $A.Values[1][2] = 8
        $A.Values[1][3] = -5
        $A.Values[2][0] = -2
        $A.Values[2][1] = 4
        $A.Values[2][2] = 7
        $A.Values[2][3] = -3
        $A.Values[3][0] = 2
        $A.Values[3][1] = -3
        $A.Values[3][2] = -5
        $A.Values[3][3] = 8
        [MatrixTest]::Det($A) | Should -Be -54
        $A.Det() | Should -Be -54
        $A = [MatrixTest]::New(4)
        $A.Values[0][0] = 5
        $A.Values[0][1] = 4
        $A.Values[0][2] = 2
        $A.Values[0][3] = 1
        $A.Values[1][0] = 2
        $A.Values[1][1] = 3
        $A.Values[1][2] = 1
        $A.Values[1][3] = -2
        $A.Values[2][0] = -5
        $A.Values[2][1] = -7
        $A.Values[2][2] = -3
        $A.Values[2][3] = 9
        $A.Values[3][0] = 1
        $A.Values[3][1] = -2
        $A.Values[3][2] = -1
        $A.Values[3][3] = 4
        [MatrixTest]::Det($A) | Should -Be 38
        $A.Det() | Should -Be 38
    }
    It 'can produce the dot product of two matrices' {
        $Identity = [MatrixTest]::Identity(2)
        $A = $Identity.Clone()
        $A.Values[1][1] = 0
        $B = $Identity.Clone()
        $B.Values[0][0] = 0
        $Product = [MatrixTest]::Dot($A,$B)
        $Product.Order | Should -Be 2,2
        $Product.Values[0] | Should -Be 0,0 -Because 'the dot product of orthogonal matrices should be zero'
        $Product.Values[1] | Should -Be 0,0 -Because 'the dot product of orthogonal matrices should be zero'
        $A.Values[0][0] = 1
        $A.Values[0][1] = 2
        $A.Values[1][0] = 3
        $A.Values[1][1] = 4
        $B.Values[0][0] = 1
        $B.Values[0][1] = 1
        $B.Values[1][0] = 0
        $B.Values[1][1] = 2
        $Product = [MatrixTest]::Dot($A,$B)
        $Product.Values[0] | Should -Be 1,5
        $Product.Values[1] | Should -Be 3,11
        $Product = [MatrixTest]::Dot($B,$A)
        $Product.Values[0] | Should -Be 4,6 -Because 'the dot product is not commutative'
        $Product.Values[1] | Should -Be 6,8 -Because 'the dot product is not commutative'
        $A = [MatrixTest]::New(1,2)
        $A.Values[0][0] = 2
        $A.Values[0][1] = 1
        $B = [MatrixTest]::New(2,3)
        $B.Values[0][0] = 1
        $B.Values[0][1] = -2
        $B.Values[0][2] = 0
        $B.Values[1][0] = 4
        $B.Values[1][1] = 5
        $B.Values[1][2] = -3
        $Product = [MatrixTest]::Dot($A,$B)
        $Product.Order | Should -Be 1,3 -Because 'dot product supports matrices of different sizes'
        $Product.Values[0] | Should -Be 6,1,-3
        $Product = $A.Dot($B)
        $Product.Order | Should -Be 1,3 -Because 'dot product supports matrices of different sizes'
        $Product.Values[0] | Should -Be 6,1,-3
        $A = [MatrixTest]::New(2)
        $A.Values[0][0] = 2
        $A.Values[0][1] = 5
        $A.Values[1][0] = 1
        $A.Values[1][1] = 3
        $B = [MatrixTest]::New(2)
        $B.Values[0][0] = 3
        $B.Values[0][1] = -5
        $B.Values[1][0] = -1
        $B.Values[1][1] = 2
        [MatrixTest]::Dot($A,$B) | Test-Equal $Identity | Should -BeTrue -Because '$A and $B are invertible'
    }
}
Describe 'Matrix class instance' {
    It 'can create clones' {
        $Matrix = [MatrixTest]::New(2)
        $Matrix.Values[0][0] = 1
        $Matrix.Values[0][1] = 2
        $Matrix.Values[1][0] = 3
        $Matrix.Values[1][1] = 4
        $Clone = $Matrix.Clone()
        $Clone.Values[0] | Should -Be 1,2
        $Clone.Values[1] | Should -Be 3,4
    }
    It 'can be multiplied by a scalar constant' {
        $A = [MatrixTest]::Identity(2)
        [MatrixTest]::Add($A,$A,$A) | Test-Equal $A.Multiply(3) | Should -BeTrue
        $Product = $A.Multiply(7)
        $Product.Values[0] | Should -Be 7,0
        $Product.Values[1] | Should -Be 0,7
    }
    It 'can remove rows' {
        $A = [MatrixTest]::New(3)
        $A.Values[0][0] = 1
        $A.Values[0][1] = 2
        $A.Values[0][2] = 3
        $A.Values[1][0] = 4
        $A.Values[1][1] = 5
        $A.Values[1][2] = 6
        $A.Values[2][0] = 7
        $A.Values[2][1] = 8
        $A.Values[2][2] = 9
        $Edited = $A.RemoveRow(0)
        $Edited.Order | Should -Be 2,3
        $Edited.Values[0] | Should -Be 4,5,6
        $Edited.Values[1] | Should -Be 7,8,9
        $Edited = $A.RemoveRow(1)
        $Edited.Order | Should -Be 2,3
        $Edited.Values[0] | Should -Be 1,2,3
        $Edited.Values[1] | Should -Be 7,8,9
        $Edited = $A.RemoveRow(2)
        $Edited.Order | Should -Be 2,3
        $Edited.Values[0] | Should -Be 1,2,3
        $Edited.Values[1] | Should -Be 4,5,6
        $Edited = $A.RemoveRow(2).RemoveColumn(0)
    }
    It 'can remove columns' {
        $A = [MatrixTest]::New(3)
        $A.Values[0][0] = 1
        $A.Values[0][1] = 2
        $A.Values[0][2] = 3
        $A.Values[1][0] = 4
        $A.Values[1][1] = 5
        $A.Values[1][2] = 6
        $A.Values[2][0] = 7
        $A.Values[2][1] = 8
        $A.Values[2][2] = 9
        $Edited = $A.RemoveColumn(0)
        $Edited.Order | Should -Be 3,2
        $Edited.Values[0] | Should -Be 2,3
        $Edited.Values[1] | Should -Be 5,6
        $Edited.Values[2] | Should -Be 8,9
        $Edited = $A.RemoveColumn(1)
        $Edited.Order | Should -Be 3,2
        $Edited.Values[0] | Should -Be 1,3
        $Edited.Values[1] | Should -Be 4,6
        $Edited.Values[2] | Should -Be 7,9
        $Edited = $A.RemoveColumn(2)
        $Edited.Order | Should -Be 3,2
        $Edited.Values[0] | Should -Be 1,2
        $Edited.Values[1] | Should -Be 4,5
        $Edited.Values[2] | Should -Be 7,8
        $Edited = $A.RemoveColumn(0).RemoveRow(0)
        $Edited.Order | Should -Be 2,2
        $Edited.Values[0] | Should -Be 5,6
        $Edited.Values[1] | Should -Be 8,9
    }
    It 'can be converted to string output' {
        $Matrix = [MatrixTest]::New(2)
        $Matrix.Values[0][0] = 1
        $Matrix.Values[0][1] = 2
        $Matrix.Values[1][0] = 3
        $Matrix.Values[1][1] = 4
        $Matrix.ToString() | Should -Be '1,2;3,4'
        [MatrixTest]::Unit(3).ToString() | Should -Be '1,1,1;1,1,1;1,1,1'
    }
}