src/classes/Matrix.ps1

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
 
 
  *** 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)
  .EXAMPLE
  $Matrix = 1..9 | matrix 3,3
  #>

  [CmdletBinding()]
  [Alias('matrix')]
  [OutputType([Matrix])]
  Param(
    [Parameter(ValueFromPipeline=$true)]
    [Array] $Values,
    [Parameter(Position=0)]
    [Array] $Size = @(2,2)
  )
  Begin {
    $Matrix = [Matrix]::New($Size[0], $Size[1])
    if ($Values.Count -gt 0) {
      $Matrix.Rows = $Values | Invoke-Flatten
    }
  }
  End {
    if ($Input.Count -gt 0) {
      $Matrix.Rows = $Input | Invoke-Flatten
    }
    $Matrix
  }
}
function Test-DiagonalMatrix {
  <#
  .SYNOPSIS
  Return true if passed value is a "diagonal" matrix
  .DESCRIPTION
  A diagonal matrix is a matrix in which the entries outside the main diagonal are all zero.
  Example:
    1 0
    0 1
 
  The primary purpose of this function is to be used as a Matrix object type extension.
  #>

  [CmdletBinding()]
  [OutputType([Bool])]
  Param(
    [Parameter(Position=0, ValueFromPipeline=$true)]
    [Matrix] $Value
  )
  Process {
    $Value.Indexes() | ForEach-Object {
      $Row, $Col = $_
      ($Row -eq $Col) -or ($Value.Rows[$Row][$Col] -eq 0)
    } | Invoke-Reduce -Every
  }
}
function Test-SquareMatrix {
  <#
  .SYNOPSIS
  Return true if passed value is a "square" matrix
  .DESCRIPTION
  A square matrix is a matrix that has the same number of rows as columns.
  Example:
    1 1
    1 1
 
  The primary purpose of this function is to be used as a Matrix object type extension.
  #>

  [CmdletBinding()]
  [OutputType([Bool])]
  Param(
    [Parameter(Position=0, ValueFromPipeline=$true)]
    [Matrix] $Value
  )
  Process {
    $Rows, $Columns = $Value.Size
    $Rows -eq $Columns
  }
}
function Test-SymmetricMatrix {
  <#
  .SYNOPSIS
  Return true if passed value is a "symmetric" matrix
  .DESCRIPTION
  A symmetric matrix is a matrix for which every element of the matrix (a_ij -eq a_ji) is true
  Example:
    1 2 3
    2 1 4
    3 4 1
 
  The primary purpose of this function is to be used as a Matrix object type extension.
  #>

  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Result')]
  [CmdletBinding()]
  [OutputType([Bool])]
  Param(
    [Parameter(Position=0, ValueFromPipeline=$true)]
    [Matrix] $Value
  )
  Process {
    0..($Value.Size[0] - 1) | ForEach-Object {
      $Row = $_
      0..$Row | ForEach-Object { $Value.Rows[$Row][$_] -eq $Value.Rows[$_][$Row] }
    } | Invoke-Reduce -Every
  }
}
# Need to parameterize class with "id" in order to re-load class during local testing
$Id = if ($Env:ProjectName -eq 'pwsh-prelude' -and $Env:BuildSystem -eq 'Unknown') { 'Test' } else { '' }
$TypeDefinition = @"
    using System;
    using System.Collections.Generic;
    using System.Linq;
 
    public class Matrix${Id} {
 
        public int[] Size {
            get;
            private set;
        }
        private double[][] _Rows;
        public double[][] Rows {
            get {
                return _Rows;
            }
            set {
                int rows = this.Size[0], cols = this.Size[1];
                if (value.Length > rows) {
                    var limit = Math.Min(value.Length,(rows * cols));
                    for (var i = 0; i < limit; ++i) {
                        int row = (int)(Math.Floor((double)(i / cols)));
                        int col = i % cols;
                        _Rows[row][col] = value[i][0];
                    }
                } else {
                    double[][] temp = Matrix${Id}.Create(rows,cols);
                    for (var row = 0; row < rows; ++row)
                        temp[row] = (double[])value[row].Take(cols).ToArray();
                    _Rows = temp;
                }
            }
        }
        public Matrix${Id}(int n) {
            this.Size = new int[] { n,n };
            this.Rows = Matrix${Id}.Create(n,n);
        }
        public Matrix${Id}(int rows,int cols) {
            this.Size = new int[] { rows,cols };
            this.Rows = Matrix${Id}.Create(rows,cols);
        }
        public static double[][] Create(int rows,int cols) {
            double[][] result = new double[rows][];
            for (int i = 0; i < rows; ++i)
                result[i] = new double[cols];
            return result;
        }
        public static Matrix${Id} Unit(int n) {
            var temp = new Matrix${Id}(n);
            foreach (var index in temp.Indexes()) {
                int i = index[0], j = index[1];
                temp.Rows[i][j] = 1;
            }
            return temp;
        }
        public static Matrix${Id} Identity(int n) {
            var temp = new Matrix${Id}(n);
            for (int i = 0; i < n; ++i)
                temp.Rows[i][i] = 1;
            return temp;
        }
        public static Matrix${Id} Transpose(Matrix${Id} a) {
            var clone = a.Clone();
            foreach (var index in clone.Indexes()) {
                int i = index[0], j = index[1];
                clone.Rows[i][j] = a.Rows[j][i];
            }
            return clone;
        }
        public static Matrix${Id} Add(params Matrix${Id}[] addends) {
            var size = addends[0].Size;
            var sum = new Matrix${Id}(size[0],size[1]);
            foreach (Matrix${Id} matrix in addends)
                foreach (var index in matrix.Indexes()) {
                    int i = index[0], j = index[1];
                    sum.Rows[i][j] += matrix.Rows[i][j];
                }
            return sum;
        }
        public static Matrix${Id} Adj(Matrix${Id} a) {
            Matrix${Id} temp = a.Clone();
            foreach (var index in temp.Indexes()) {
                int i = index[0], j = index[1];
                temp.Rows[i][j] = a.Cofactor(i,j);
            }
            return Matrix${Id}.Transpose(temp);
        }
        public static double Det(Matrix${Id} a) {
            int rows = a.Size[0];
            switch (rows) {
                case 1:
                    return a.Rows[0][0];
                case 2:
                    return (a.Rows[0][0] * a.Rows[1][1]) - (a.Rows[0][1] * a.Rows[1][0]);
                default:
                    double sum = 0;
                    for (int i = 0; i < rows; ++i)
                        sum += (a.Rows[0][i] * a.Cofactor(0,i));
                    return sum;
            }
        }
        public static Matrix${Id} Dot(Matrix${Id} a,Matrix${Id} b) {
            int m = a.Size[0], p = a.Size[1], n = b.Size[1];
            var product = new Matrix${Id}(m,n);
            foreach (var index in product.Indexes()) {
                int i = index[0], j = index[1];
                double sum = 0;
                for (int k = 0; k < p; ++k) {
                    sum += (a.Rows[i][k] * b.Rows[k][j]);
                }
                product.Rows[i][j] = sum;
            }
            return product;
        }
        public static Matrix${Id} Invert(Matrix${Id} a) {
            Matrix${Id} adjugate = Matrix${Id}.Adj(a);
            double det = Matrix${Id}.Det(a);
            return Matrix${Id}.Multiply(adjugate,(1 / det));
        }
        public static Matrix${Id} Multiply(Matrix${Id} a,double k) {
            Matrix${Id} clone = a.Clone();
            foreach (var index in clone.Indexes()) {
                int i = index[0], j = index[1];
                clone.Rows[i][j] *= k;
            }
            return clone;
        }
        public Matrix${Id} Clone() {
            Matrix${Id} original = this;
            int rows = original.Size[0], cols = original.Size[1];
            Matrix${Id} clone = new Matrix${Id}(rows,cols);
            foreach (var index in clone.Indexes()) {
                int i = index[0], j = index[1];
                clone.Rows[i][j] = original.Rows[i][j];
            }
            return clone;
        }
        public double Cofactor(int i = 0,int j = 0) {
            return (Math.Pow(-1,i + j) * Matrix${Id}.Det(this.RemoveRow(i).RemoveColumn(j)));
        }
        public List<int[]> Indexes(int offset = 0) {
            int rows = this.Size[0], cols = this.Size[1];
            List<int[]> pairs = new List<int[]>();
            for (var i = 0; i < rows; ++i)
                for (var j = 0; j < cols; ++j) {
                    int[] pair = { i + offset,j + offset };
                    pairs.Add(pair);
                }
            return pairs;
        }
        public Matrix${Id} RemoveColumn(int index) {
            Matrix${Id} original = this.Clone();
            int rows = original.Size[0], cols = original.Size[1];
            if (index < 0 || index >= cols) {
                return original;
            } else {
                var temp = new Matrix${id}(rows,cols - 1);
                for (var i = 0; i < rows; ++i)
                    for (var j = 0; j < index; ++j)
                        temp.Rows[i][j] = original.Rows[i][j];
                for (var i = 0; i < rows; ++i)
                    for (var j = index; j < cols - 1; ++j)
                        temp.Rows[i][j] = original.Rows[i][j + 1];
                return temp;
            }
        }
        public Matrix${Id} RemoveRow(int index) {
            Matrix${Id} original = this.Clone();
            int rows = original.Size[0], cols = original.Size[1];
            if (index < 0 || index >= rows) {
                return original;
            } else {
                var temp = new Matrix${id}(rows - 1,cols);
                for (var i = 0; i < index; ++i)
                    for (var j = 0; j < cols; ++j)
                        temp.Rows[i][j] = original.Rows[i][j];
                for (var i = index; i < rows - 1; ++i)
                    for (var j = 0; j < cols; ++j)
                        temp.Rows[i][j] = original.Rows[i + 1][j];
                return temp;
            }
        }
        public override string ToString() {
            Matrix${Id} matrix = this;
            int rank = matrix.Size[0];
            var rows = new string[rank];
            for (var i = 0; i < rank; ++i)
                rows[i] = string.Join(",",matrix.Rows[i]);
            return string.Join("\r\n",rows);
        }
    }
"@

if ("Matrix${Id}" -as [Type]) {
  return
} else {
  Add-Type -TypeDefinition $TypeDefinition
  if ($Env:BuildSystem -eq 'Travis CI') {
    $Accelerators = [PowerShell].Assembly.GetType('System.Management.Automation.TypeAccelerators')
    $Accelerators::Add('MatrixTest', 'Matrix')
  }
}