// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2015 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.Runtime.Serialization; namespace IStation.Numerics.LinearAlgebra.Storage { [Serializable] [DataContract(Namespace = "urn:IStation/Numerics/LinearAlgebra")] public abstract partial class MatrixStorage : IEquatable> where T : struct, IEquatable, IFormattable { // [ruegg] public fields are OK here protected static readonly T Zero = BuilderInstance.Matrix.Zero; [DataMember(Order = 1)] public readonly int RowCount; [DataMember(Order = 2)] public readonly int ColumnCount; protected MatrixStorage(int rowCount, int columnCount) { if (rowCount < 0) { throw new ArgumentOutOfRangeException(nameof(rowCount), "The number of rows of a matrix must be non-negative."); } if (columnCount < 0) { throw new ArgumentOutOfRangeException(nameof(columnCount), "The number of columns of a matrix must be non-negative."); } RowCount = rowCount; ColumnCount = columnCount; } /// /// True if the matrix storage format is dense. /// public abstract bool IsDense { get; } /// /// True if all fields of this matrix can be set to any value. /// False if some fields are fixed, like on a diagonal matrix. /// public abstract bool IsFullyMutable { get; } /// /// True if the specified field can be set to any value. /// False if the field is fixed, like an off-diagonal field on a diagonal matrix. /// public abstract bool IsMutableAt(int row, int column); /// /// Gets or sets the value at the given row and column, with range checking. /// /// /// The row of the element. /// /// /// The column of the element. /// /// The value to get or set. /// This method is ranged checked. and /// to get and set values without range checking. public T this[int row, int column] { get { ValidateRange(row, column); return At(row, column); } set { ValidateRange(row, column); At(row, column, value); } } /// /// Retrieves the requested element without range checking. /// /// /// The row of the element. /// /// /// The column of the element. /// /// /// The requested element. /// /// Not range-checked. public abstract T At(int row, int column); /// /// Sets the element without range checking. /// /// The row of the element. /// The column of the element. /// The value to set the element to. /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks. public abstract void At(int row, int column, T value); /// /// Indicates whether the current object is equal to another object of the same type. /// /// /// An object to compare with this object. /// /// /// true if the current object is equal to the parameter; otherwise, false. /// public bool Equals(MatrixStorage other) { // Reject equality when the argument is null or has a different shape. if (other == null) { return false; } if (ColumnCount != other.ColumnCount || RowCount != other.RowCount) { return false; } // Accept if the argument is the same object as this. if (ReferenceEquals(this, other)) { return true; } // Perform element wise comparison. return Find2Unchecked(other, (a, b) => !a.Equals(b), Zeros.AllowSkip) == null; } /// /// Determines whether the specified is equal to the current . /// /// /// true if the specified is equal to the current ; otherwise, false. /// /// The to compare with the current . public sealed override bool Equals(object obj) { return Equals(obj as MatrixStorage); } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// public override int GetHashCode() { var hashNum = Math.Min(RowCount*ColumnCount, 25); int hash = 17; unchecked { for (var i = 0; i < hashNum; i++) { #if NETSTANDARD1_3 int col = i%ColumnCount; int row = i/ColumnCount; #else int col; int row = Math.DivRem(i, ColumnCount, out col); #endif hash = hash*31 + At(row, col).GetHashCode(); } } return hash; } // CLEARING public virtual void Clear() { for (var i = 0; i < RowCount; i++) { for (var j = 0; j < ColumnCount; j++) { At(i, j, Zero); } } } public void Clear(int rowIndex, int rowCount, int columnIndex, int columnCount) { if (rowCount < 1 || columnCount < 1) { return; } if (rowIndex + rowCount > RowCount || rowIndex < 0) { throw new ArgumentOutOfRangeException(nameof(rowIndex)); } if (columnIndex + columnCount > ColumnCount || columnIndex < 0) { throw new ArgumentOutOfRangeException(nameof(columnIndex)); } ClearUnchecked(rowIndex, rowCount, columnIndex, columnCount); } internal virtual void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount) { for (var i = rowIndex; i < rowIndex + rowCount; i++) { for (var j = columnIndex; j < columnIndex + columnCount; j++) { At(i, j, Zero); } } } public void ClearRows(int[] rowIndices) { if (rowIndices.Length == 0) { return; } for (int k = 0; k < rowIndices.Length; k++) { if (rowIndices[k] < 0 || rowIndices[k] >= RowCount) { throw new ArgumentOutOfRangeException(nameof(rowIndices)); } } ClearRowsUnchecked(rowIndices); } public void ClearColumns(int[] columnIndices) { if (columnIndices.Length == 0) { return; } for (int k = 0; k < columnIndices.Length; k++) { if ((uint)columnIndices[k] >= (uint)ColumnCount) { throw new ArgumentOutOfRangeException(nameof(columnIndices)); } } ClearColumnsUnchecked(columnIndices); } internal virtual void ClearRowsUnchecked(int[] rowIndices) { for (var k = 0; k < rowIndices.Length; k++) { int row = rowIndices[k]; for (var j = 0; j < ColumnCount; j++) { At(row, j, Zero); } } } internal virtual void ClearColumnsUnchecked(int[] columnIndices) { for (var k = 0; k < columnIndices.Length; k++) { int column = columnIndices[k]; for (var i = 0; i < RowCount; i++) { At(i, column, Zero); } } } // MATRIX COPY public void CopyTo(MatrixStorage target, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (ReferenceEquals(this, target)) { return; } if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {target.RowCount}x{target.ColumnCount}."; throw new ArgumentException(message, nameof(target)); } CopyToUnchecked(target, existingData); } internal virtual void CopyToUnchecked(MatrixStorage target, ExistingData existingData) { for (int j = 0; j < ColumnCount; j++) { for (int i = 0; i < RowCount; i++) { target.At(i, j, At(i, j)); } } } public void CopySubMatrixTo(MatrixStorage target, int sourceRowIndex, int targetRowIndex, int rowCount, int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (rowCount == 0 || columnCount == 0) { return; } if (sourceRowIndex == 0 && targetRowIndex == 0 && rowCount == RowCount && rowCount == target.RowCount && sourceColumnIndex == 0 && targetColumnIndex == 0 && columnCount == ColumnCount && columnCount == target.ColumnCount) { CopyTo(target); return; } if (ReferenceEquals(this, target)) { throw new NotSupportedException(); } ValidateSubMatrixRange(target, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount); CopySubMatrixToUnchecked(target, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, existingData); } internal virtual void CopySubMatrixToUnchecked(MatrixStorage target, int sourceRowIndex, int targetRowIndex, int rowCount, int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) { for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) { for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) { target.At(ii, jj, At(i, j)); } } } // ROW COPY public void CopyRowTo(VectorStorage target, int rowIndex, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } ValidateRowRange(target, rowIndex); CopySubRowToUnchecked(target, rowIndex, 0, 0, ColumnCount, existingData); } public void CopySubRowTo(VectorStorage target, int rowIndex, int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (columnCount == 0) { return; } ValidateSubRowRange(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount); CopySubRowToUnchecked(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount, existingData); } internal virtual void CopySubRowToUnchecked(VectorStorage target, int rowIndex, int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) { for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) { target.At(jj, At(rowIndex, j)); } } // COLUMN COPY public void CopyColumnTo(VectorStorage target, int columnIndex, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } ValidateColumnRange(target, columnIndex); CopySubColumnToUnchecked(target, columnIndex, 0, 0, RowCount, existingData); } public void CopySubColumnTo(VectorStorage target, int columnIndex, int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (rowCount == 0) { return; } ValidateSubColumnRange(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount); CopySubColumnToUnchecked(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount, existingData); } internal virtual void CopySubColumnToUnchecked(VectorStorage target, int columnIndex, int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData) { for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) { target.At(ii, At(i, columnIndex)); } } // TRANSPOSE public void TransposeTo(MatrixStorage target, ExistingData existingData = ExistingData.Clear) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (RowCount != target.ColumnCount || ColumnCount != target.RowCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {target.RowCount}x{target.ColumnCount}."; throw new ArgumentException(message, nameof(target)); } if (ReferenceEquals(this, target)) { TransposeSquareInplaceUnchecked(); return; } TransposeToUnchecked(target, existingData); } internal virtual void TransposeToUnchecked(MatrixStorage target, ExistingData existingData) { for (int j = 0; j < ColumnCount; j++) { for (int i = 0; i < RowCount; i++) { target.At(j, i, At(i, j)); } } } internal virtual void TransposeSquareInplaceUnchecked() { for (int j = 0; j < ColumnCount; j++) { for (int i = 0; i < j; i++) { T swap = At(i, j); At(i, j, At(j, i)); At(j, i, swap); } } } // EXTRACT public virtual T[] ToRowMajorArray() { var ret = new T[RowCount*ColumnCount]; for (int i = 0; i < RowCount; i++) { var offset = i*ColumnCount; for (int j = 0; j < ColumnCount; j++) { ret[offset + j] = At(i, j); } } return ret; } public virtual T[] ToColumnMajorArray() { var ret = new T[RowCount*ColumnCount]; for (int j = 0; j < ColumnCount; j++) { var offset = j*RowCount; for (int i = 0; i < RowCount; i++) { ret[offset + i] = At(i, j); } } return ret; } public virtual T[][] ToRowArrays() { var ret = new T[RowCount][]; for (int i = 0; i < RowCount; i++) { var row = new T[ColumnCount]; for (int j = 0; j < ColumnCount; j++) { row[j] = At(i, j); } ret[i] = row; } return ret; } public virtual T[][] ToColumnArrays() { var ret = new T[ColumnCount][]; for (int j = 0; j < ColumnCount; j++) { var column = new T[RowCount]; for (int i = 0; i < RowCount; i++) { column[i] = At(i, j); } ret[j] = column; } return ret; } public virtual T[,] ToArray() { var ret = new T[RowCount, ColumnCount]; for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { ret[i, j] = At(i, j); } } return ret; } public virtual T[] AsRowMajorArray() { return null; } public virtual T[] AsColumnMajorArray() { return null; } public virtual T[][] AsRowArrays() { return null; } public virtual T[][] AsColumnArrays() { return null; } public virtual T[,] AsArray() { return null; } // ENUMERATION public virtual IEnumerable Enumerate() { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { yield return At(i, j); } } } public virtual IEnumerable> EnumerateIndexed() { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { yield return new Tuple(i, j, At(i, j)); } } } public virtual IEnumerable EnumerateNonZero() { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { var x = At(i, j); if (!Zero.Equals(x)) { yield return x; } } } } public virtual IEnumerable> EnumerateNonZeroIndexed() { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { var x = At(i, j); if (!Zero.Equals(x)) { yield return new Tuple(i, j, x); } } } } // FIND public virtual Tuple Find(Func predicate, Zeros zeros) { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { var item = At(i, j); if (predicate(item)) { return new Tuple(i, j, item); } } } return null; } public Tuple Find2(MatrixStorage other, Func predicate, Zeros zeros) where TOther : struct, IEquatable, IFormattable { if (other == null) { throw new ArgumentNullException(nameof(other)); } if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {other.RowCount}x{other.ColumnCount}."; throw new ArgumentException(message, nameof(other)); } return Find2Unchecked(other, predicate, zeros); } internal virtual Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros) where TOther : struct, IEquatable, IFormattable { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { var item = At(i, j); var otherItem = other.At(i, j); if (predicate(item, otherItem)) { return new Tuple(i, j, item, otherItem); } } } return null; } // FUNCTIONAL COMBINATORS: MAP public virtual void MapInplace(Func f, Zeros zeros) { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { At(i, j, f(At(i, j))); } } } public virtual void MapIndexedInplace(Func f, Zeros zeros) { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { At(i, j, f(i, j, At(i, j))); } } } public void MapTo(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {target.RowCount}x{target.ColumnCount}."; throw new ArgumentException(message, nameof(target)); } MapToUnchecked(target, f, zeros, existingData); } internal virtual void MapToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { target.At(i, j, f(At(i, j))); } } } public void MapIndexedTo(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {target.RowCount}x{target.ColumnCount}."; throw new ArgumentException(message, nameof(target)); } MapIndexedToUnchecked(target, f, zeros, existingData); } internal virtual void MapIndexedToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { for (int j = 0; j < ColumnCount; j++) { for (int i = 0; i < RowCount; i++) { target.At(i, j, f(i, j, At(i, j))); } } } public void MapSubMatrixIndexedTo(MatrixStorage target, Func f, int sourceRowIndex, int targetRowIndex, int rowCount, int sourceColumnIndex, int targetColumnIndex, int columnCount, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (rowCount == 0 || columnCount == 0) { return; } if (ReferenceEquals(this, target)) { throw new NotSupportedException(); } ValidateSubMatrixRange(target, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount); MapSubMatrixIndexedToUnchecked(target, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros, existingData); } internal virtual void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f, int sourceRowIndex, int targetRowIndex, int rowCount, int sourceColumnIndex, int targetColumnIndex, int columnCount, Zeros zeros, ExistingData existingData) where TU : struct, IEquatable, IFormattable { for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) { for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) { target.At(ii, jj, f(ii, jj, At(i, j))); } } } public void Map2To(MatrixStorage target, MatrixStorage other, Func f, Zeros zeros, ExistingData existingData) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (other == null) { throw new ArgumentNullException(nameof(other)); } if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {target.RowCount}x{target.ColumnCount}."; throw new ArgumentException(message, nameof(target)); } if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {other.RowCount}x{other.ColumnCount}."; throw new ArgumentException(message, nameof(other)); } Map2ToUnchecked(target, other, f, zeros, existingData); } internal virtual void Map2ToUnchecked(MatrixStorage target, MatrixStorage other, Func f, Zeros zeros, ExistingData existingData) { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { target.At(i, j, f(At(i, j), other.At(i, j))); } } } // FUNCTIONAL COMBINATORS: FOLD /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). public void FoldByRow(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (target.Length != RowCount) { throw new ArgumentException("All vectors must have the same dimensionality.", nameof(target)); } if (state == null) { throw new ArgumentNullException(nameof(state)); } if (state.Length != RowCount) { throw new ArgumentException("All vectors must have the same dimensionality.", nameof(state)); } FoldByRowUnchecked(target, f, finalize, state, zeros); } /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). internal virtual void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) { for (int i = 0; i < RowCount; i++) { TU s = state[i]; for (int j = 0; j < ColumnCount; j++) { s = f(s, At(i, j)); } target[i] = finalize(s, ColumnCount); } } /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). public void FoldByColumn(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (target.Length != ColumnCount) { throw new ArgumentException("All vectors must have the same dimensionality.", nameof(target)); } if (state == null) { throw new ArgumentNullException(nameof(state)); } if (state.Length != ColumnCount) { throw new ArgumentException("All vectors must have the same dimensionality.", nameof(state)); } FoldByColumnUnchecked(target, f, finalize, state, zeros); } /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). internal virtual void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) { for (int j = 0; j < ColumnCount; j++) { TU s = state[j]; for (int i = 0; i < RowCount; i++) { s = f(s, At(i, j)); } target[j] = finalize(s, RowCount); } } public TState Fold2(MatrixStorage other, Func f, TState state, Zeros zeros) where TOther : struct, IEquatable, IFormattable { if (other == null) { throw new ArgumentNullException(nameof(other)); } if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) { var message = $"Matrix dimensions must agree: op1 is {RowCount}x{ColumnCount}, op2 is {other.RowCount}x{other.ColumnCount}."; throw new ArgumentException(message, nameof(other)); } return Fold2Unchecked(other, f, state, zeros); } internal virtual TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros) where TOther : struct, IEquatable, IFormattable { for (int i = 0; i < RowCount; i++) { for (int j = 0; j < ColumnCount; j++) { state = f(state, At(i, j), other.At(i, j)); } } return state; } } }