src/matrix.ps1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'New-ComplexValue')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'New-Matrix')] Param() function Format-ComplexValue { <# .SYNOPSIS Utility method for rendering readable output for complex numbers .PARAMETER WithColor When -WithColor is used, the output will include color templates to add color (see Write-Label) #> [CmdletBinding()] [OutputType([String])] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [System.Numerics.Complex] $Value, [Switch] $WithColor ) $Real = $Value.Real $Imaginary = $Value.Imaginary if ($Real -eq 0 -and $Imaginary -eq 0) { '0' } else { $Re = if ($Real -eq 0) { '' } else { $Real } $Sign = if ([Math]::Sign($Imaginary) -lt 0) { '-' } else { '+' } $Op = if ($Re.Length -gt 0 -and $Imaginary -ne 0) { " $Sign " } else { '' } $Minus = if ($Imaginary -lt 0 -and $Re.Length -eq 0) { '-' } else { '' } $Im = if ($Imaginary -eq 0) { '' } else { [Math]::Abs($Imaginary) } $I = if ($Imaginary -ne 0) { 'i' } else { '' } if ($WithColor) { $WithI = if ($Imaginary -ne 0) { "{{#cyan $I}}" } else { '' } "${Re}${Op}${Minus}${Im}${WithI}" } else { "${Re}${Op}${Minus}${Im}${I}" } } } function Invoke-MatrixMap { <# .SYNOPSIS Apply passed function to each element of passed matrix and return new matrix with results. .EXAMPLE $A = 1..4 | matrix $AddOne = { Param($X) $X + 1 } $B = $A | matmap $AddOne #> [CmdletBinding()] [Alias('matmap')] [OutputType([Matrix])] Param( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [Matrix] $InputMatrix, [Parameter(Position = 0)] [ScriptBlock] $Expression = { }, [Switch] $Strict ) Process { $Parameters = ($Expression | Get-ParameterList).Name $Morphism = switch ($Parameters.Count) { 1 { [System.Func[Complex, Complex]]$Expression } 3 { [System.Func[Complex, Int, Int, Complex]]$Expression } 4 { [System.Func[Complex, Int, Int, Matrix, Complex]]$Expression } Default { if ($Strict) { throw 'Expression has wrong number of parameters' } $Identity = { Param($X) $X } [System.Func[Complex, Complex]]$Identity } } $InputMatrix.Map($Morphism) } } function New-ComplexValue { <# .SYNOPSIS Utility method for creating complex values .PARAMETER Random Return a complex value with random real and imaginary parts .PARAMETER Bounds Minimum and maximum values for random real and imaginary parts .EXAMPLE $C = New-ComplexValue 2 3 .EXAMPLE $C = 4, -1.5 | complex #> [CmdletBinding(DefaultParameterSetName = 'normal')] [Alias('complex')] [OutputType([System.Numerics.Complex])] Param( [Parameter(ValueFromPipeline = $True)] [Array] $Parts, [Parameter(Position = 0)] [Alias('Re')] [Int] $Real = 0, [Parameter(Position = 1)] [Alias('Im')] [Int] $Imaginary = 0, [Parameter(ParameterSetName = 'random')] [Switch] $Random, [Parameter(ParameterSetName = 'random')] [ValidateCount(2, 2)] [Double[]] $Bounds = @(-10.0, 10.0) ) End { $Re, $Im = if ($Input.Count -ge 2) { $Input[0, 1] } else { if ($Random) { $Minimum, $Maximum = $Bounds $Parameters = @{ Minimum = $Minimum; Maximum = $Maximum } (Get-Random @Parameters), (Get-Random @Parameters) } else { $Real, $Imaginary } } [System.Numerics.Complex]::New($Re, $Im) } } function New-Matrix { <# .SYNOPSIS Utility wrapper function for creating matrices .DESCRIPTION New-Matrix is a wrapper function for the [Matrix] class and is intended to reduce the effort required to create [Matrix] objects. Use "New-Matrix | Get-Member" to see available methods: Adj: Return matrix adjugate ("classical adjoint") > Note: Available as a class method - [Matrix]::Adj Det: Return matrix determinant (even matrices larger than 3x3 > Note: Available as a class method - [Matrix]::Det Dot: Return dot product between matrix and one other matrix (with compatible size) > Note: Available as a class method - [Matrix]::Dot Clone: Return new matrix with identical values as original matrix Cofactor: Return cofactor for given row and column index pair > Example: $Matrix.Cofactor(0, 1) Indexes: Return list of "ij" pairs (useful for iterating through matrix values) > Example: (New-Matrix).Indexes() | ForEach-Object { "(i,j) = ($($_[0]),$($_[1]))" } Inverse: Return matrix inverse (Note: Det() must return non-zero value) > Note: Available as a class method - [Matrix]::Invert Multiply: Return result of multiplying matrix by scalar value (ex: 42) > Note: Available as a class method - [Matrix]::Multiply RemoveColumn: Return matrix with selected column removed RemoveRow: Return matrix with selected column removed Transpose: Return matrix transpose > Note: Available as a class method - [Matrix]::Transpose Trace: Return matrix trace (sum of diagonal elements) > Note: Available as a class method - [Matrix]::Trace *** All methods that return a [Matrix] object provide a "fluent" interface and can be chained *** *** All methods are "non destructive" and will return a clone of the original matrix (when applicable) *** .PARAMETER Size Size = @(number of rows, number of columns) .PARAMETER Diagonal Add values to matrix along diagonal .PARAMETER Unit Create unit matrix with size, -Size .PARAMETER Random Create random matrix with size, -Size .PARAMETER Bounds Minimum and maximum values for matrix random complex values .EXAMPLE $Matrix = 1..9 | matrix 3,3 #> [CmdletBinding(DefaultParameterSetName = 'normal')] [Alias('matrix')] [OutputType([Matrix])] Param( [Parameter(ValueFromPipeline = $True)] [Array] $Values, [Parameter(Position = 0)] [Array] $Size = @(2, 2), [Switch] $Diagonal, [Switch] $Identity, [Switch] $Unit, [Switch] $Custom, [Parameter(ParameterSetName = 'random')] [Switch] $Random, [Parameter(ParameterSetName = 'random')] [ValidateCount(2, 2)] [Double[]] $Bounds = @(-10.0, 10.0) ) Begin { function Update-Matrix { Param( [Matrix] $Matrix, [String] $MatrixType, [Array] $Values ) switch ($MatrixType) { 'Diagonal' { $Values = $Values | Invoke-Flatten $Index = 0 foreach ($Pair in $Matrix.Indexes()) { $Row, $Column = $Pair if ($Row -eq $Column) { $Matrix[$Row][$Column] = $Values[$Index] $Index++ } } break } 'Custom' { $Matrix.Rows = $Values | Invoke-Flatten } Default { # Do nothing } } } $M, $N = if ($Size.Count -eq 1) { $Size * 2 } else { $Size } $Matrix = New-Object 'Matrix' @($M, $N) $MatrixType = Find-FirstTrueVariable 'Custom', 'Diagonal', 'Identity', 'Unit', 'Random' if ($Values.Count -gt 0) { Update-Matrix -Values $Values -Matrix $Matrix -MatrixType $MatrixType } } End { $Values = $Input if ($Values.Count -gt 0) { Update-Matrix -Values $Values -Matrix $Matrix -MatrixType $MatrixType } else { switch ($MatrixType) { 'Unit' { $Matrix = [Matrix]::Unit($M, $N) break } 'Identity' { $Matrix = [Matrix]::Identity($M) break } 'Random' { $Matrix.Rows = 1..($M * $N) | ForEach-Object { New-ComplexValue -Random -Bounds $Bounds } break } Default { # Do nothing } } } $Matrix } } function Test-Matrix { <# .SYNOPSIS Test if a matrix is one or more of the following: - Diagonal - Square - Symmetric .EXAMPLE 1..4 | New-Matrix 2,2 | Test-Matrix -Square # True .EXAMPLE 1..4 | New-Matrix 2,2 | Test-Matrix -Square -Diagonal # False #> [CmdletBinding()] [OutputType([Bool])] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] $Value, [Switch] $Diagonal, [Switch] $Hermitian, [Switch] $Square, [Switch] $Symmetric ) if ($Value.GetType().Name -eq 'Matrix') { $Result = $True if ($Diagonal) { $Result = $Result -and $Value.IsDiagonal() } if ($Hermitian) { $Result = $Result -and $Value.IsHermitian() } if ($Square) { $Result = $Result -and $Value.IsSquare() } if ($Symmetric) { $Result = $Result -and $Value.IsSymmetric() } return $Result } $False } |