375 lines
14 KiB
C#
375 lines
14 KiB
C#
using System.Buffers;
|
|
|
|
namespace Nerd_STF.Mathematics.Algebra;
|
|
|
|
public class Matrix : IMatrix<Matrix, Matrix>
|
|
{
|
|
public static Matrix Identity(Int2 size)
|
|
{
|
|
if (size.x != size.y) throw new InvalidSizeException("Can only create an identity matrix of a square matrix." +
|
|
" You may want to use " + nameof(IdentityIsh) + " instead.");
|
|
Matrix m = Zero(size);
|
|
for (int i = 0; i < size.x; i++) m[i, i] = 1;
|
|
return m;
|
|
}
|
|
public static Matrix IdentityIsh(Int2 size)
|
|
{
|
|
Matrix m = Zero(size);
|
|
int max = Mathf.Min(size.x, size.y);
|
|
for (int i = 0; i < max; i++) m[i, i] = 1;
|
|
return m;
|
|
}
|
|
public static Matrix One(Int2 size) => new(size, 1);
|
|
public static Matrix SignGrid(Int2 size) => new(size, Equations.SgnFill);
|
|
public static Matrix Zero(Int2 size) => new(size);
|
|
|
|
public bool HasMinors => Size.x > 1 && Size.y > 1;
|
|
public bool IsSquare => Size.x == Size.y;
|
|
|
|
public Int2 Size { get; private init; }
|
|
|
|
private readonly float[,] array;
|
|
|
|
public Matrix() : this(Int2.Zero) { }
|
|
public Matrix(Int2 size, float all = 0)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
|
|
if (all == 0) return;
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = all;
|
|
}
|
|
public Matrix(Int2 size, float[] vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
|
|
if (vals.Length < size.x * size.y)
|
|
throw new InvalidSizeException("Array must contain enough values to fill the matrix.");
|
|
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[c + r * size.y];
|
|
}
|
|
public Matrix(Int2 size, int[] vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
|
|
if (vals.Length < size.x * size.y)
|
|
throw new InvalidSizeException("Array must contain enough values to fill the matrix.");
|
|
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[c + r * size.y];
|
|
}
|
|
public Matrix(Int2 size, Fill<float> vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(c + r * size.y);
|
|
}
|
|
public Matrix(Int2 size, Fill<int> vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(c + r * size.y);
|
|
}
|
|
public Matrix(Int2 size, float[,] vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
|
|
}
|
|
public Matrix(Int2 size, int[,] vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals[r, c];
|
|
}
|
|
public Matrix(Int2 size, Fill2D<float> vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
|
|
}
|
|
public Matrix(Int2 size, Fill2D<int> vals)
|
|
{
|
|
Size = size;
|
|
array = new float[size.x, size.y];
|
|
for (int r = 0; r < size.x; r++) for (int c = 0; c < size.y; c++) array[r, c] = vals(r, c);
|
|
}
|
|
|
|
public float this[int r, int c]
|
|
{
|
|
get => array[r, c];
|
|
set => array[r, c] = value;
|
|
}
|
|
public float this[Int2 index]
|
|
{
|
|
get => this[index.x, index.y];
|
|
set => this[index.x, index.y] = value;
|
|
}
|
|
public float this[Index r, Index c]
|
|
{
|
|
get
|
|
{
|
|
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
|
|
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
|
|
return array[row, col];
|
|
}
|
|
set
|
|
{
|
|
int row = r.IsFromEnd ? Size.x - r.Value : r.Value,
|
|
col = c.IsFromEnd ? Size.y - c.Value : c.Value;
|
|
array[row, col] = value;
|
|
}
|
|
}
|
|
public Matrix this[Range rs, Range cs]
|
|
{
|
|
get
|
|
{
|
|
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
|
|
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
|
|
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
|
|
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
|
|
|
|
Matrix newMatrix = new((rowEnd - rowStart - 1, colEnd - colStart - 1));
|
|
for (int r = rowStart; r < rowEnd; r++)
|
|
for (int c = colStart; c < colEnd; c++)
|
|
newMatrix[r, c] = array[r, c];
|
|
return newMatrix;
|
|
}
|
|
set
|
|
{
|
|
int rowStart = rs.Start.IsFromEnd ? Size.x - rs.Start.Value : rs.Start.Value,
|
|
rowEnd = rs.End.IsFromEnd ? Size.x - rs.End.Value : rs.End.Value,
|
|
colStart = cs.Start.IsFromEnd ? Size.y - cs.Start.Value : cs.Start.Value,
|
|
colEnd = cs.End.IsFromEnd ? Size.y - cs.End.Value : cs.End.Value;
|
|
|
|
if (value.Size != (rowEnd - rowStart - 1, colEnd - colStart - 1))
|
|
throw new InvalidSizeException("Matrix has invalid size.");
|
|
|
|
for (int r = rowStart; r < rowEnd; r++)
|
|
for (int c = colStart; c < colEnd; c++)
|
|
array[r, c] = value[r, c];
|
|
}
|
|
}
|
|
|
|
public static Matrix Absolute(Matrix val) => new(val.Size, (r, c) => Mathf.Absolute(val[r, c]));
|
|
public static Matrix Ceiling(Matrix val) => new(val.Size, (r, c) => Mathf.Ceiling(val[r, c]));
|
|
public static Matrix Clamp(Matrix val, Matrix min, Matrix max) =>
|
|
new(val.Size, (r, c) => Mathf.Clamp(val[r, c], min[r, c], max[r, c]));
|
|
public static Matrix Divide(Matrix num, params Matrix[] vals)
|
|
{
|
|
foreach (Matrix m in vals) num /= m;
|
|
return num;
|
|
}
|
|
public static Matrix Floor(Matrix val) => new(val.Size, (r, c) => Mathf.Floor(val[r, c]));
|
|
public static Matrix Lerp(Matrix a, Matrix b, float t, bool clamp = true) =>
|
|
new(a.Size, (r, c) => Mathf.Lerp(a[r, c], b[r, c], t, clamp));
|
|
public static Matrix Product(params Matrix[] vals)
|
|
{
|
|
if (vals.Length < 1) throw new InvalidSizeException("Array must contain at least one matrix.");
|
|
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
|
|
Matrix val = Identity(vals[0].Size);
|
|
foreach (Matrix m in vals) val *= m;
|
|
return val;
|
|
}
|
|
public static Matrix Round(Matrix val) => new(val.Size, (r, c) => Mathf.Round(val[r, c]));
|
|
public static Matrix Subtract(Matrix num, params Matrix[] vals)
|
|
{
|
|
foreach (Matrix m in vals) num -= m;
|
|
return num;
|
|
}
|
|
public static Matrix Sum(params Matrix[] vals)
|
|
{
|
|
if (!CheckSize(vals)) throw new InvalidSizeException("All matricies must be the same size.");
|
|
Matrix val = Zero(vals[0].Size);
|
|
foreach (Matrix m in vals) val += m;
|
|
return val;
|
|
}
|
|
|
|
public void Apply(Modifier2D modifier)
|
|
{
|
|
for (int r = 0; r < Size.x; r++) for (int c = 0; c < Size.y; c++)
|
|
array[r, c] = modifier(new(r, c), array[r, c]);
|
|
}
|
|
|
|
public float[] GetColumn(int column)
|
|
{
|
|
float[] vals = new float[Size.x];
|
|
for (int i = 0; i < Size.x; i++) vals[i] = array[i, column];
|
|
return vals;
|
|
}
|
|
public float[] GetRow(int row)
|
|
{
|
|
float[] vals = new float[Size.y];
|
|
for (int i = 0; i < Size.y; i++) vals[i] = array[row, i];
|
|
return vals;
|
|
}
|
|
|
|
public void SetColumn(int column, float[] vals)
|
|
{
|
|
if (vals.Length < Size.x)
|
|
throw new InvalidSizeException("Array must contain enough values to fill the column.");
|
|
for (int i = 0; i < Size.x; i++) array[i, column] = vals[i];
|
|
}
|
|
public void SetRow(int row, float[] vals)
|
|
{
|
|
if (vals.Length < Size.y)
|
|
throw new InvalidSizeException("Array must contain enough values to fill the row.");
|
|
for (int i = 0; i < Size.y; i++) array[row, i] = vals[i];
|
|
}
|
|
|
|
public Matrix Adjugate() => Cofactor().Transpose();
|
|
public Matrix Cofactor()
|
|
{
|
|
Matrix dets = new(Size);
|
|
Matrix[,] minors = Minors();
|
|
for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) dets[c, r] = minors[c, r].Determinant();
|
|
return dets ^ SignGrid(Size);
|
|
}
|
|
public float Determinant()
|
|
{
|
|
if (!IsSquare) throw new InvalidSizeException("Matrix must be square to calculate determinant.");
|
|
if (Size.x <= 0) return 0;
|
|
if (Size.x == 1) return array[0, 0];
|
|
|
|
Matrix[] minors = Minors().GetRow(0, Size.x);
|
|
float det = 0;
|
|
for (int i = 0; i < minors.Length; i++) det += array[0, i] * minors[i].Determinant() * (i % 2 == 0 ? 1 : -1);
|
|
|
|
return det;
|
|
}
|
|
public Matrix? Inverse()
|
|
{
|
|
float d = Determinant();
|
|
if (d == 0) return null;
|
|
|
|
return Adjugate() / d;
|
|
}
|
|
public Matrix[,] Minors()
|
|
{
|
|
if (!HasMinors) return new Matrix[0,0];
|
|
Matrix[,] minors = new Matrix[Size.x, Size.y];
|
|
for (int r = 0; r < Size.x; r++) for (int c = 0; c < Size.y; c++) minors[r, c] = MinorOf(new(r, c));
|
|
return minors;
|
|
}
|
|
public Matrix Transpose()
|
|
{
|
|
Matrix @this = this;
|
|
return new(Size, (r, c) => @this[c, r]);
|
|
}
|
|
|
|
public Matrix MinorOf(Int2 index)
|
|
{
|
|
Matrix @this = this;
|
|
return new(@this.Size - Int2.One, delegate (int r, int c)
|
|
{
|
|
if (r >= index.x) r++;
|
|
if (c >= index.y) c++;
|
|
return @this[r, c];
|
|
});
|
|
}
|
|
|
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
{
|
|
if (obj == null) return base.Equals(obj);
|
|
Type t = obj.GetType();
|
|
if (t == typeof(Matrix)) return Equals((Matrix)obj);
|
|
else if (t == typeof(Matrix2x2)) return Equals((Matrix)obj);
|
|
else if (t == typeof(Matrix3x3)) return Equals((Matrix)obj);
|
|
else if (t == typeof(Matrix4x4)) return Equals((Matrix)obj);
|
|
|
|
return base.Equals(obj);
|
|
}
|
|
public bool Equals(Matrix? other)
|
|
{
|
|
if (other is null) return false;
|
|
return array.Equals(other.array);
|
|
}
|
|
public override int GetHashCode() => array.GetHashCode();
|
|
public override string ToString()
|
|
{
|
|
string res = "";
|
|
for (int r = 0; r < Size.x; r++)
|
|
{
|
|
for (int c = 0; c < Size.y; c++) res += array[r, c] + " ";
|
|
res += "\n";
|
|
}
|
|
return res;
|
|
}
|
|
|
|
public object Clone() => new Matrix(Size, array);
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
public IEnumerator<float> GetEnumerator()
|
|
{
|
|
for (int r = 0; r < Size.y; r++) for (int c = 0; c < Size.x; c++) yield return array[c, r];
|
|
}
|
|
|
|
public float[] ToArray() => array.Flatten(new(Size.y, Size.x));
|
|
public float[,] ToArray2D() => array;
|
|
public Fill<float> ToFill() => ToFillExtension.ToFill(this);
|
|
public Fill2D<float> ToFill2D()
|
|
{
|
|
Matrix @this = this;
|
|
return (x, y) => @this[x, y];
|
|
}
|
|
public List<float> ToList() => ToArray().ToList();
|
|
|
|
public static Matrix operator +(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] + b[r, c]);
|
|
public static Matrix? operator -(Matrix m) => m.Inverse();
|
|
public static Matrix operator -(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] - b[r, c]);
|
|
public static Matrix operator *(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] * b);
|
|
public static Matrix operator *(Matrix a, Matrix b)
|
|
{
|
|
if (a.Size.y != b.Size.x) throw new InvalidSizeException("Incompatible Dimensions.");
|
|
return new(new(a.Size.x, b.Size.y), (r, c) => Mathf.Dot(a.GetRow(r), b.GetColumn(c)));
|
|
}
|
|
public static Complex operator *(Matrix a, Complex b) => (Complex)(a * (Matrix)b);
|
|
public static Quaternion operator *(Matrix a, Quaternion b) => (Quaternion)(a * (Matrix)b);
|
|
public static Float2 operator *(Matrix a, Float2 b) => (Float2)(a * (Matrix)b);
|
|
public static Float3 operator *(Matrix a, Float3 b) => (Float3)(a * (Matrix)b);
|
|
public static Float4 operator *(Matrix a, Float4 b) => (Float4)(a * (Matrix)b);
|
|
public static Vector2d operator *(Matrix a, Vector2d b) => (Vector2d)(a * (Matrix)b);
|
|
public static Vector3d operator *(Matrix a, Vector3d b) => (Vector3d)(a * (Matrix)b);
|
|
public static Matrix operator /(Matrix a, float b) => new(a.Size, (r, c) => a[r, c] / b);
|
|
public static Matrix operator /(Matrix a, Matrix b)
|
|
{
|
|
Matrix? bInv = b.Inverse();
|
|
if (bInv is null) throw new NoInverseException(b);
|
|
return a * bInv;
|
|
}
|
|
public static Complex operator /(Matrix a, Complex b) => (Complex)(a / (Matrix)b);
|
|
public static Quaternion operator /(Matrix a, Quaternion b) => (Quaternion)(a / (Matrix)b);
|
|
public static Float2 operator /(Matrix a, Float2 b) => (Float2)(a / (Matrix)b);
|
|
public static Float3 operator /(Matrix a, Float3 b) => (Float3)(a / (Matrix)b);
|
|
public static Float4 operator /(Matrix a, Float4 b) => (Float4)(a / (Matrix)b);
|
|
public static Vector2d operator /(Matrix a, Vector2d b) => (Vector2d)(a / (Matrix)b);
|
|
public static Vector3d operator /(Matrix a, Vector3d b) => (Vector3d)(a / (Matrix)b);
|
|
// Single number multiplication
|
|
public static Matrix operator ^(Matrix a, Matrix b) => new(a.Size, (r, c) => a[r, c] * b[r, c]);
|
|
public static bool operator ==(Matrix a, Matrix b) => a.Equals(b);
|
|
public static bool operator !=(Matrix a, Matrix b) => !a.Equals(b);
|
|
|
|
public static explicit operator Matrix(Complex c) => (Matrix)(Float2)c;
|
|
public static explicit operator Matrix(Quaternion c) => (Matrix)(Float4)c;
|
|
public static explicit operator Matrix(Float2 f) => new(new(2, 1), i => f[i]);
|
|
public static explicit operator Matrix(Float3 f) => new(new(3, 1), i => f[i]);
|
|
public static explicit operator Matrix(Float4 f) => new(new(4, 1), i => f[i]);
|
|
public static implicit operator Matrix(Matrix2x2 m) => new(new(2, 2), m.ToFill2D());
|
|
public static implicit operator Matrix(Matrix3x3 m) => new(new(3, 3), m.ToFill2D());
|
|
public static implicit operator Matrix(Matrix4x4 m) => new(new(4, 4), m.ToFill2D());
|
|
public static explicit operator Matrix(Vector2d v) => (Matrix)v.ToXYZ();
|
|
public static explicit operator Matrix(Vector3d v) => (Matrix)v.ToXYZ();
|
|
|
|
private static bool CheckSize(params Matrix[] vals)
|
|
{
|
|
if (vals.Length <= 1) return true;
|
|
Int2 size = vals[0].Size;
|
|
for (int i = 1; i < vals.Length; i++) if (size != vals[i].Size) return false;
|
|
|
|
return true;
|
|
}
|
|
}
|