//
// 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.Linq;
using System.Runtime.Serialization;
namespace IStation.Numerics.LinearAlgebra.Storage
{
[Serializable]
[DataContract(Namespace = "urn:IStation/Numerics/LinearAlgebra")]
public class SparseCompressedRowMatrixStorage : MatrixStorage
where T : struct, IEquatable, IFormattable
{
// [ruegg] public fields are OK here
///
/// The array containing the row indices of the existing rows. Element "i" of the array gives the index of the
/// element in the array that is first non-zero element in a row "i".
/// The last value is equal to ValueCount, so that the number of non-zero entries in row "i" is always
/// given by RowPointers[i+i] - RowPointers[i]. This array thus has length RowCount+1.
///
[DataMember(Order = 1)]
public readonly int[] RowPointers;
///
/// An array containing the column indices of the non-zero values. Element "j" of the array
/// is the number of the column in matrix that contains the j-th value in the array.
///
[DataMember(Order = 2)]
public int[] ColumnIndices;
///
/// Array that contains the non-zero elements of matrix. Values of the non-zero elements of matrix are mapped into the values
/// array using the row-major storage mapping described in a compressed sparse row (CSR) format.
///
[DataMember(Order = 3)]
public T[] Values;
///
/// Gets the number of non zero elements in the matrix.
///
/// The number of non zero elements.
public int ValueCount => RowPointers[RowCount];
internal SparseCompressedRowMatrixStorage(int rows, int columns)
: base(rows, columns)
{
RowPointers = new int[rows + 1];
ColumnIndices = new int[0];
Values = new T[0];
}
internal SparseCompressedRowMatrixStorage(int rows, int columns, int[] rowPointers, int[] columnIndices, T[] values)
: base(rows, columns)
{
RowPointers = rowPointers;
ColumnIndices = columnIndices;
Values = values;
// Explicit zeros are not intentionally removed.
// Sort ColumnIndices.
NormalizeOrdering();
NormalizeDuplicates();
}
///
/// True if the matrix storage format is dense.
///
public override bool IsDense => false;
///
/// 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 override bool IsFullyMutable => true;
///
/// 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 override bool IsMutableAt(int row, int column)
{
return true;
}
///
/// Retrieves the requested element without range checking.
///
///
/// The row of the element.
///
///
/// The column of the element.
///
///
/// The requested element.
///
/// Not range-checked.
public override T At(int row, int column)
{
var index = FindItem(row, column);
return index >= 0 ? Values[index] : Zero;
}
///
/// 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 override void At(int row, int column, T value)
{
var index = FindItem(row, column);
if (index >= 0)
{
// Non-zero item found in matrix
if (Zero.Equals(value))
{
// Delete existing item
RemoveAtIndexUnchecked(index, row);
}
else
{
// Update item
Values[index] = value;
}
}
else
{
// Item not found. Add new value
if (Zero.Equals(value))
{
return;
}
index = ~index;
var valueCount = RowPointers[RowPointers.Length - 1];
// Check if the storage needs to be increased
if ((valueCount == Values.Length) && (valueCount < ((long)RowCount*ColumnCount)))
{
// Value array is completely full so we increase the size
// Determine the increase in size. We will not grow beyond the size of the matrix
var size = Math.Min(Values.Length + GrowthSize(), (long)RowCount*ColumnCount);
if (size > int.MaxValue)
{
throw new NotSupportedException("We only support sparse matrix with less than int.MaxValue elements.");
}
Array.Resize(ref Values, (int)size);
Array.Resize(ref ColumnIndices, (int)size);
}
// Move all values (with a position larger than index) in the value array to the next position
// move all values (with a position larger than index) in the columIndices array to the next position
Array.Copy(Values, index, Values, index + 1, valueCount - index);
Array.Copy(ColumnIndices, index, ColumnIndices, index + 1, valueCount - index);
// Add the value and the column index
Values[index] = value;
ColumnIndices[index] = column;
// add 1 to all the row indices for rows bigger than rowIndex
// so that they point to the correct part of the value array again.
for (var i = row + 1; i < RowPointers.Length; i++)
{
RowPointers[i] += 1;
}
}
}
///
/// Delete value from internal storage
///
/// Index of value in nonZeroValues array
/// Row number of matrix
/// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks
void RemoveAtIndexUnchecked(int itemIndex, int row)
{
var valueCount = RowPointers[RowPointers.Length - 1];
// Move all values (with a position larger than index) in the value array to the previous position
// move all values (with a position larger than index) in the columIndices array to the previous position
Array.Copy(Values, itemIndex + 1, Values, itemIndex, valueCount - itemIndex - 1);
Array.Copy(ColumnIndices, itemIndex + 1, ColumnIndices, itemIndex, valueCount - itemIndex - 1);
// Decrease value in Row
for (var i = row + 1; i < RowPointers.Length; i++)
{
RowPointers[i] -= 1;
}
valueCount -= 1;
// Check whether we need to shrink the arrays. This is reasonable to do if
// there are a lot of non-zero elements and storage is two times bigger
if ((valueCount > 1024) && (valueCount < Values.Length/2))
{
Array.Resize(ref Values, valueCount);
Array.Resize(ref ColumnIndices, valueCount);
}
}
///
/// Find item Index in nonZeroValues array
///
/// Matrix row index
/// Matrix column index
/// Item index
/// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks
public int FindItem(int row, int column)
{
// Determine bounds in columnIndices array where this item should be searched (using rowIndex)
return Array.BinarySearch(ColumnIndices, RowPointers[row], RowPointers[row + 1] - RowPointers[row], column);
}
///
/// Calculates the amount with which to grow the storage array's if they need to be
/// increased in size.
///
/// The amount grown.
int GrowthSize()
{
int delta;
if (Values.Length > 1024)
{
delta = Values.Length/4;
}
else
{
if (Values.Length > 256)
{
delta = 512;
}
else
{
delta = Values.Length > 64 ? 128 : 32;
}
}
return delta;
}
public void Normalize()
{
NormalizeOrdering();
NormalizeZeros();
}
public void NormalizeOrdering()
{
for (int i = 0; i < RowCount; i++)
{
int index = RowPointers[i];
int count = RowPointers[i + 1] - index;
if (count > 1)
{
Sorting.Sort(ColumnIndices, Values, index, count);
}
}
}
public void NormalizeZeros()
{
MapInplace(x => x, Zeros.AllowSkip);
}
///
/// Eliminate duplicate entries by adding them together.
///
public void NormalizeDuplicates()
{
var builder = BuilderInstance.Matrix;
int valueCount = 0;
for (int i = 0; i < RowCount; i++)
{
int index = RowPointers[i];
int last = RowPointers[i + 1];
while (index < last)
{
var col = ColumnIndices[index];
var val = Values[index];
index++;
while (index < last)
{
if (ColumnIndices[index] == col)
{
val = builder.Add(val, Values[index]);
index++;
}
else
{
break;
}
}
ColumnIndices[valueCount] = col;
Values[valueCount] = val;
valueCount++;
}
RowPointers[i + 1] = valueCount;
}
// Remove extra space from arrays.
Array.Resize(ref Values, valueCount);
Array.Resize(ref ColumnIndices, valueCount);
}
///
/// Fill zeros explicitly on the diagonal entries as required by the Intel MKL direct sparse solver.
///
public void PopulateExplicitZerosOnDiagonal()
{
var delta = 0; // number of missing diagonal entries
for (int row = 0; row < RowCount; row++)
{
var found = false;
for (int j = RowPointers[row]; j < RowPointers[row + 1]; j++)
{
if (ColumnIndices[j] == row)
{
found = true;
break;
}
}
if (!found) delta++;
}
if (delta > 0)
{
var size = Values.Length + delta;
if (size > int.MaxValue)
{
throw new NotSupportedException("We only support sparse matrix with less than int.MaxValue elements.");
}
var newRowPointers = new int[RowCount + 1];
var newColumnIndices = new int[size];
var newValues = new T[size];
delta = 0;
for (int row = 0; row < RowCount; row++)
{
var found = false;
for (int j = RowPointers[row]; j < RowPointers[row + 1]; j++)
{
newColumnIndices[j + delta] = ColumnIndices[j];
newValues[j + delta] = Values[j];
if (ColumnIndices[j] == row)
{
found = true;
}
}
if (!found)
{
var start = RowPointers[row] + delta;
var end = RowPointers[row + 1] + delta;
var count = end - start + 1;
newColumnIndices[end] = row;
newValues[end] = Zero;
// Ordering may be not necessary
Sorting.Sort(newColumnIndices, newValues, start, count);
delta++;
}
newRowPointers[row + 1] = RowPointers[row + 1] + delta;
}
Array.Copy(newRowPointers, RowPointers, RowCount + 1);
ColumnIndices = newColumnIndices;
Values = newValues;
}
}
///
/// Returns a hash code for this instance.
///
///
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
///
public override int GetHashCode()
{
var values = Values;
var hashNum = Math.Min(ValueCount, 25);
int hash = 17;
unchecked
{
for (var i = 0; i < hashNum; i++)
{
hash = hash*31 + values[i].GetHashCode();
}
}
return hash;
}
// CLEARING
public override void Clear()
{
Array.Clear(RowPointers, 0, RowPointers.Length);
}
internal override void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount)
{
if (rowIndex == 0 && columnIndex == 0 && rowCount == RowCount && columnCount == ColumnCount)
{
Clear();
return;
}
var valueCount = RowPointers[RowPointers.Length - 1];
for (int row = rowIndex + rowCount - 1; row >= rowIndex; row--)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
// empty row
if (startIndex == endIndex)
{
continue;
}
// multiple entries in row
var first = Array.BinarySearch(ColumnIndices, startIndex, endIndex - startIndex, columnIndex);
var last = Array.BinarySearch(ColumnIndices, startIndex, endIndex - startIndex, columnIndex + columnCount - 1);
if (first < 0) first = ~first;
if (last < 0) last = ~last - 1;
int count = last - first + 1;
if (count > 0)
{
// Move all values (with a position larger than index) in the value array to the previous position
// move all values (with a position larger than index) in the columIndices array to the previous position
Array.Copy(Values, first + count, Values, first, valueCount - first - count);
Array.Copy(ColumnIndices, first + count, ColumnIndices, first, valueCount - first - count);
// Decrease value in Row
for (var k = row + 1; k < RowPointers.Length; k++)
{
RowPointers[k] -= count;
}
valueCount -= count;
}
}
// Check whether we need to shrink the arrays. This is reasonable to do if
// there are a lot of non-zero elements and storage is two times bigger
if ((valueCount > 1024) && (valueCount < Values.Length/2))
{
Array.Resize(ref Values, valueCount);
Array.Resize(ref ColumnIndices, valueCount);
}
}
internal override void ClearRowsUnchecked(int[] rowIndices)
{
var rows = new bool[RowCount];
for (int i = 0; i < rowIndices.Length; i++)
{
rows[rowIndices[i]] = true;
}
MapIndexedInplace((i, j, x) => rows[i] ? Zero : x, Zeros.AllowSkip);
}
internal override void ClearColumnsUnchecked(int[] columnIndices)
{
var columns = new bool[ColumnCount];
for (int i = 0; i < columnIndices.Length; i++)
{
columns[columnIndices[i]] = true;
}
MapIndexedInplace((i, j, x) => columns[j] ? Zero : x, Zeros.AllowSkip);
}
// INITIALIZATION
public static SparseCompressedRowMatrixStorage OfMatrix(MatrixStorage matrix)
{
var storage = new SparseCompressedRowMatrixStorage(matrix.RowCount, matrix.ColumnCount);
matrix.CopyToUnchecked(storage, ExistingData.AssumeZeros);
return storage;
}
public static SparseCompressedRowMatrixStorage OfValue(int rows, int columns, T value)
{
if (Zero.Equals(value))
{
return new SparseCompressedRowMatrixStorage(rows, columns);
}
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var values = new T[rows * columns];
for (int i = 0; i < values.Length; i++)
{
values[i] = value;
}
var rowPointers = storage.RowPointers;
for (int i = 0; i <= rows; i++)
{
rowPointers[i] = i*columns;
}
var columnIndices = new int[values.Length];
for (int row = 0; row < rows; row++)
{
int offset = row*columns;
for (int col = 0; col < columns; col++)
{
columnIndices[offset + col] = col;
}
}
rowPointers[rows] = values.Length;
storage.ColumnIndices = columnIndices;
storage.Values = values;
return storage;
}
public static SparseCompressedRowMatrixStorage OfInit(int rows, int columns, Func init)
{
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
for (int row = 0; row < rows; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < columns; col++)
{
var x = init(row, col);
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(col);
}
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfDiagonalInit(int rows, int columns, Func init)
{
int diagonalLength = Math.Min(rows, columns);
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List(diagonalLength);
var values = new List(diagonalLength);
for (int i = 0; i < diagonalLength; i++)
{
rowPointers[i] = values.Count;
var x = init(i);
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(i);
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfArray(T[,] array)
{
var storage = new SparseCompressedRowMatrixStorage(array.GetLength(0), array.GetLength(1));
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
for (int row = 0; row < storage.RowCount; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < storage.ColumnCount; col++)
{
if (!Zero.Equals(array[row, col]))
{
values.Add(array[row, col]);
columnIndices.Add(col);
}
}
}
rowPointers[storage.RowCount] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfRowArrays(T[][] data)
{
if (data.Length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(data), "Matrices can not be empty and must have at least one row and column.");
}
var storage = new SparseCompressedRowMatrixStorage(data.Length, data[0].Length);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
for (int row = 0; row < storage.RowCount; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < storage.ColumnCount; col++)
{
T x = data[row][col];
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(col);
}
}
}
rowPointers[storage.RowCount] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfColumnArrays(T[][] data)
{
if (data.Length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(data), "Matrices can not be empty and must have at least one row and column.");
}
var storage = new SparseCompressedRowMatrixStorage(data[0].Length, data.Length);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
for (int row = 0; row < storage.RowCount; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < storage.ColumnCount; col++)
{
T x = data[col][row];
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(col);
}
}
}
rowPointers[storage.RowCount] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfRowVectors(VectorStorage[] data)
{
if (data.Length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(data), "Matrices can not be empty and must have at least one row and column.");
}
var storage = new SparseCompressedRowMatrixStorage(data.Length, data[0].Length);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
// TODO PERF: Optimize for sparse and dense cases
for (int row = 0; row < storage.RowCount; row++)
{
var vector = data[row];
rowPointers[row] = values.Count;
for (int col = 0; col < storage.ColumnCount; col++)
{
var x = vector.At(col);
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(col);
}
}
}
rowPointers[storage.RowCount] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfColumnVectors(VectorStorage[] data)
{
if (data.Length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(data), "Matrices can not be empty and must have at least one row and column.");
}
var storage = new SparseCompressedRowMatrixStorage(data[0].Length, data.Length);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
// TODO PERF: Optimize for sparse and dense cases
for (int row = 0; row < storage.RowCount; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < storage.ColumnCount; col++)
{
var x = data[col].At(row);
if (!Zero.Equals(x))
{
values.Add(x);
columnIndices.Add(col);
}
}
}
rowPointers[storage.RowCount] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfIndexedEnumerable(int rows, int columns, IEnumerable> data)
{
var trows = new List>[rows];
foreach (var item in data)
{
if (!Zero.Equals(item.Item3))
{
var row = trows[item.Item1] ?? (trows[item.Item1] = new List>());
row.Add(new Tuple(item.Item2, item.Item3));
}
}
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
int index = 0;
for (int row = 0; row < rows; row++)
{
rowPointers[row] = index;
var trow = trows[row];
if (trow != null)
{
trow.Sort();
foreach (var item in trow)
{
values.Add(item.Item2);
columnIndices.Add(item.Item1);
index++;
}
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfRowEnumerables(int rows, int columns, IEnumerable> data)
{
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
using (var rowIterator = data.GetEnumerator())
{
for (int row = 0; row < rows; row++)
{
if (!rowIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {rows}.");
rowPointers[row] = values.Count;
using (var columnIterator = rowIterator.Current.GetEnumerator())
{
for (int col = 0; col < columns; col++)
{
if (!columnIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {columns}.");
if (!Zero.Equals(columnIterator.Current))
{
values.Add(columnIterator.Current);
columnIndices.Add(col);
}
}
if (columnIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {columns}.");
}
}
if (rowIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {rows}.");
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfColumnEnumerables(int rows, int columns, IEnumerable> data)
{
var trows = new List>[rows];
using (var columnIterator = data.GetEnumerator())
{
for (int column = 0; column < columns; column++)
{
if (!columnIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {columns}.");
using (var rowIterator = columnIterator.Current.GetEnumerator())
{
for (int row = 0; row < rows; row++)
{
if (!rowIterator.MoveNext()) throw new ArgumentOutOfRangeException(nameof(data), $"The given array has the wrong length. Should be {rows}.");
if (!Zero.Equals(rowIterator.Current))
{
var trow = trows[row] ?? (trows[row] = new List>());
trow.Add(new Tuple(column, rowIterator.Current));
}
}
}
}
}
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
int index = 0;
for (int row = 0; row < rows; row++)
{
rowPointers[row] = index;
var trow = trows[row];
if (trow != null)
{
trow.Sort();
foreach (var item in trow)
{
values.Add(item.Item2);
columnIndices.Add(item.Item1);
index++;
}
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfRowMajorEnumerable(int rows, int columns, IEnumerable data)
{
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
using (var iterator = data.GetEnumerator())
{
for (int row = 0; row < rows; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < columns; col++)
{
iterator.MoveNext();
if (!Zero.Equals(iterator.Current))
{
values.Add(iterator.Current);
columnIndices.Add(col);
}
}
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
public static SparseCompressedRowMatrixStorage OfColumnMajorList(int rows, int columns, IList data)
{
if (rows*columns != data.Count)
{
throw new ArgumentException("Matrix dimensions must agree.");
}
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var rowPointers = storage.RowPointers;
var columnIndices = new List();
var values = new List();
for (int row = 0; row < rows; row++)
{
rowPointers[row] = values.Count;
for (int col = 0; col < columns; col++)
{
var item = data[row + (col*rows)];
if (!Zero.Equals(item))
{
values.Add(item);
columnIndices.Add(col);
}
}
}
rowPointers[rows] = values.Count;
storage.ColumnIndices = columnIndices.ToArray();
storage.Values = values.ToArray();
return storage;
}
///
/// Create a new sparse storage from a compressed sparse row (CSR) format.
/// This new storage will be independent from the given arrays.
/// A new memory block will be allocated for storing the matrix.
///
/// The number of rows.
/// The number of columns.
/// The number of stored values including explicit zeros.
/// The row pointer array of the compressed sparse row format.
/// The column index array of the compressed sparse row format.
/// The data array of the compressed sparse row format.
/// The sparse storage from the compressed sparse row format.
/// Duplicate entries will be summed together and explicit zeros will be not intentionally removed.
public static SparseCompressedRowMatrixStorage OfCompressedSparseRowFormat(int rows, int columns, int valueCount, int[] rowPointers, int[] columnIndices, T[] values)
{
if (values == null) throw new NullReferenceException(nameof(values));
if (columnIndices == null) throw new NullReferenceException(nameof(columnIndices));
if (rowPointers == null) throw new NullReferenceException(nameof(rowPointers));
if (rowPointers.Length < rows) throw new Exception($"The given array has the wrong length. Should be {rows + 1}.");
if (valueCount != rowPointers[rows]) throw new Exception($"{nameof(valueCount)} should be same to {rowPointers[rows]}");
// copy arrays to new memory block.
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var csrValues = new T[valueCount];
Array.Copy(values, csrValues, valueCount);
var csrColumnIndices = new int[valueCount];
Array.Copy(columnIndices, csrColumnIndices, valueCount);
Array.Copy(rowPointers, storage.RowPointers, rows + 1);
storage.ColumnIndices = csrColumnIndices;
storage.Values = csrValues;
storage.NormalizeOrdering();
storage.NormalizeDuplicates();
return storage;
}
///
/// Create a new sparse matrix storage from a compressed sparse column (CSC) format.
/// This new storage will be independent from the given arrays.
/// A new memory block will be allocated.
///
/// The number of rows.
/// The number of columns.
/// The number of stored values including explicit zeros.
/// The row index array of the compressed sparse column format.
/// The column pointer array of the compressed sparse column format.
/// The data array of the compressed sparse column format.
/// The sparse storage from the compressed sparse column format.
/// Duplicate entries will be summed together and explicit zeros will be not intentionally removed.
public static SparseCompressedRowMatrixStorage OfCompressedSparseColumnFormat(int rows, int columns, int valueCount, int[] rowIndices, int[] columnPointers, T[] values)
{
if (values == null) throw new NullReferenceException(nameof(values));
if (rowIndices == null) throw new NullReferenceException(nameof(rowIndices));
if (columnPointers == null) throw new NullReferenceException(nameof(columnPointers));
if (columnPointers.Length < columns) throw new Exception($"The given array has the wrong length. Should be {columns + 1}.");
if (valueCount != columnPointers[columns]) throw new Exception($"{nameof(valueCount)} should be same to {columnPointers[columns]}");
// convert from CSC to CSR
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var csrValues = new T[valueCount];
var csrRowPointers = storage.RowPointers;
var csrColumnIndices = new int[valueCount];
for (int i = 0; i < columns; i++)
{
for (int j = columnPointers[i]; j < columnPointers[i + 1]; j++)
{
csrRowPointers[rowIndices[j] + 1]++;
}
}
for (int i = 1; i < rows + 1; i++)
{
csrRowPointers[i] += csrRowPointers[i - 1];
}
var curr = new int[rows];
for (int i = 0; i < columns; i++)
{
for (int j = columnPointers[i]; j < columnPointers[i + 1]; j++)
{
var loc = csrRowPointers[rowIndices[j]] + curr[rowIndices[j]];
curr[rowIndices[j]]++;
csrColumnIndices[loc] = i;
csrValues[loc] = values[j];
}
}
storage.ColumnIndices = csrColumnIndices;
storage.Values = csrValues;
storage.NormalizeOrdering();
storage.NormalizeDuplicates();
return storage;
}
///
/// Create a new sparse storage from a coordinate (COO) format.
/// This new storage will be independent from the given arrays.
/// A new memory block will be allocated for storing the matrix.
///
/// The number of rows.
/// The number of columns.
/// The number of stored values including explicit zeros.
/// The row index array of the coordinate format.
/// The column index array of the coordinate format.
/// The data array of the coordinate format.
/// The sparse storage from the coordinate format.
/// Duplicate entries will be summed together and
/// explicit zeros will be not intentionally removed.
public static SparseCompressedRowMatrixStorage OfCoordinateFormat(int rows, int columns, int valueCount, int[] rowIndices, int[] columnIndices, T[] values)
{
if (values == null) throw new NullReferenceException(nameof(values));
if (rowIndices == null) throw new NullReferenceException(nameof(rowIndices));
if (columnIndices == null) throw new NullReferenceException(nameof(columnIndices));
if (rowIndices.Length < valueCount || columnIndices.Length < valueCount || values.Length < valueCount)
{
throw new Exception($"The given array has the wrong length. Should be {valueCount}.");
}
// convert from COO to CSR
var storage = new SparseCompressedRowMatrixStorage(rows, columns);
var csrRowPointers = storage.RowPointers;
var csrColumnIndices = new int[valueCount];
var csrValues = new T[valueCount];
for (int i = 0; i < valueCount; i++)
{
csrRowPointers[rowIndices[i]]++;
}
for (int i = 0, cumsum = 0; i < rows; i++)
{
var temp = csrRowPointers[i];
csrRowPointers[i] = cumsum;
cumsum += temp;
}
csrRowPointers[rows] = valueCount;
for (int i = 0; i < valueCount; i++)
{
var row = rowIndices[i];
var loc = csrRowPointers[row];
csrColumnIndices[loc] = columnIndices[i];
csrValues[loc] = values[i];
csrRowPointers[row]++;
}
for (int i = 0, last = 0; i <= rows; i++)
{
var temp = csrRowPointers[i];
csrRowPointers[i] = last;
last = temp;
}
storage.ColumnIndices = csrColumnIndices;
storage.Values = csrValues;
storage.NormalizeOrdering();
storage.NormalizeDuplicates();
return storage;
}
// MATRIX COPY
internal override void CopyToUnchecked(MatrixStorage target, ExistingData existingData)
{
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
CopyToUnchecked(sparseTarget);
return;
}
if (target is DenseColumnMajorMatrixStorage denseTarget)
{
CopyToUnchecked(denseTarget, existingData);
return;
}
// FALL BACK
if (existingData == ExistingData.Clear)
{
target.Clear();
}
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.At(row, ColumnIndices[j], Values[j]);
}
}
}
}
void CopyToUnchecked(SparseCompressedRowMatrixStorage target)
{
target.Values = new T[ValueCount];
target.ColumnIndices = new int[ValueCount];
if (ValueCount != 0)
{
Array.Copy(Values, 0, target.Values, 0, ValueCount);
Buffer.BlockCopy(ColumnIndices, 0, target.ColumnIndices, 0, ValueCount*Constants.SizeOfInt);
Buffer.BlockCopy(RowPointers, 0, target.RowPointers, 0, (RowCount + 1)*Constants.SizeOfInt);
}
}
void CopyToUnchecked(DenseColumnMajorMatrixStorage target, ExistingData existingData)
{
if (existingData == ExistingData.Clear)
{
target.Clear();
}
// TODO: proper implementation
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.At(row, ColumnIndices[j], Values[j]);
}
}
}
}
internal override void CopySubMatrixToUnchecked(MatrixStorage target,
int sourceRowIndex, int targetRowIndex, int rowCount,
int sourceColumnIndex, int targetColumnIndex, int columnCount,
ExistingData existingData)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
CopySubMatrixToUnchecked(sparseTarget,
sourceRowIndex, targetRowIndex, rowCount,
sourceColumnIndex, targetColumnIndex, columnCount,
existingData);
return;
}
// FALL BACK
if (existingData == ExistingData.Clear)
{
target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount);
}
for (int i = sourceRowIndex, row = 0; i < sourceRowIndex + rowCount; i++, row++)
{
var startIndex = RowPointers[i];
var endIndex = RowPointers[i + 1];
for (int j = startIndex; j < endIndex; j++)
{
// check if the column index is in the range
if ((ColumnIndices[j] >= sourceColumnIndex) && (ColumnIndices[j] < sourceColumnIndex + columnCount))
{
var column = ColumnIndices[j] - sourceColumnIndex;
target.At(targetRowIndex + row, targetColumnIndex + column, Values[j]);
}
}
}
}
void CopySubMatrixToUnchecked(SparseCompressedRowMatrixStorage target,
int sourceRowIndex, int targetRowIndex, int rowCount,
int sourceColumnIndex, int targetColumnIndex, int columnCount,
ExistingData existingData)
{
var rowOffset = targetRowIndex - sourceRowIndex;
var columnOffset = targetColumnIndex - sourceColumnIndex;
// special case for empty target - much faster
if (target.ValueCount == 0)
{
// note: ValueCount is maximum resulting ValueCount (just using max to avoid internal copying)
// resulting arrays will likely be smaller - unless all values fit in the chosen range.
var values = new List(ValueCount);
var columnIndices = new List(ValueCount);
var rowPointers = target.RowPointers;
for (int i = sourceRowIndex; i < sourceRowIndex + rowCount; i++)
{
rowPointers[i + rowOffset] = values.Count;
var startIndex = RowPointers[i];
var endIndex = RowPointers[i + 1];
// note: we might be able to replace this loop with Array.Copy (perf)
for (int k = startIndex; k < endIndex; k++)
{
// check if the column index is in the range
if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount))
{
values.Add(Values[k]);
columnIndices.Add(ColumnIndices[k] + columnOffset);
}
}
}
for (int i = targetRowIndex + rowCount; i < rowPointers.Length; i++)
{
rowPointers[i] = values.Count;
}
target.RowPointers[target.RowCount] = values.Count;
target.Values = values.ToArray();
target.ColumnIndices = columnIndices.ToArray();
return;
}
if (existingData == ExistingData.Clear)
{
target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount);
}
// NOTE: potential for more efficient implementation
for (int i = sourceRowIndex, row = 0; row < rowCount; i++, row++)
{
var startIndex = RowPointers[i];
var endIndex = RowPointers[i + 1];
for (int j = startIndex; j < endIndex; j++)
{
// check if the column index is in the range
if ((ColumnIndices[j] >= sourceColumnIndex) && (ColumnIndices[j] < sourceColumnIndex + columnCount))
{
var column = ColumnIndices[j] - sourceColumnIndex;
target.At(targetRowIndex + row, targetColumnIndex + column, Values[j]);
}
}
}
}
// ROW COPY
internal override void CopySubRowToUnchecked(VectorStorage target, int rowIndex,
int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData)
{
// Determine bounds in columnIndices array where this item should be searched (using rowIndex)
var startIndexOfRow = RowPointers[rowIndex];
var endIndexOfRow = RowPointers[rowIndex + 1];
if (startIndexOfRow == endIndexOfRow)
{
if (existingData == ExistingData.Clear)
{
target.Clear(targetColumnIndex, columnCount);
}
return;
}
if (target is SparseVectorStorage targetSparse)
{
if ((sourceColumnIndex == 0) && (targetColumnIndex == 0) && (columnCount == ColumnCount) && (ColumnCount == targetSparse.Length))
{
// rebuild of the values, indices, no clean necessary
targetSparse.ValueCount = endIndexOfRow - startIndexOfRow;
targetSparse.Values = new T[targetSparse.ValueCount];
targetSparse.Indices = new int[targetSparse.ValueCount];
Array.Copy(ColumnIndices, startIndexOfRow, targetSparse.Indices, 0, targetSparse.ValueCount);
Array.Copy(Values, startIndexOfRow, targetSparse.Values, 0, targetSparse.ValueCount);
}
else
{
int sourceStartPos = Array.BinarySearch(ColumnIndices, startIndexOfRow, endIndexOfRow - startIndexOfRow, sourceColumnIndex);
if (sourceStartPos < 0)
{
sourceStartPos = ~sourceStartPos;
}
int sourceEndPos = Array.BinarySearch(ColumnIndices, startIndexOfRow, endIndexOfRow - startIndexOfRow, sourceColumnIndex + columnCount);
if (sourceEndPos < 0)
{
sourceEndPos = ~sourceEndPos;
}
int positionsToCopy = sourceEndPos - sourceStartPos;
if (positionsToCopy > 0)
{
// rebuild the target (no clean necessary)
int targetStartPos = Array.BinarySearch(targetSparse.Indices,0, targetSparse.ValueCount, targetColumnIndex);
if (targetStartPos < 0)
{
targetStartPos = ~targetStartPos;
}
int targetEndPos = Array.BinarySearch(targetSparse.Indices,0,targetSparse.ValueCount, targetColumnIndex + columnCount);
if (targetEndPos < 0)
{
targetEndPos = Math.Max(~targetEndPos, targetStartPos);
}
int newValueCount = targetSparse.ValueCount - (targetEndPos - targetStartPos) + positionsToCopy;
T[] newValues = new T[newValueCount];
int[] newIndices = new int[newValueCount];
// copy before
Array.Copy(targetSparse.Indices, 0, newIndices, 0, targetStartPos);
Array.Copy(targetSparse.Values, 0, newValues, 0, targetStartPos);
// copy values themselves, with new positions
int shiftRight = targetColumnIndex - sourceColumnIndex;
for (int i = 0; i < positionsToCopy;++i)
{
newIndices[targetStartPos + i] = ColumnIndices[sourceStartPos + i] + shiftRight;
}
Array.Copy(Values, sourceStartPos, newValues, targetStartPos, positionsToCopy);
// copy after
Array.Copy(targetSparse.Indices, targetEndPos, newIndices, positionsToCopy + targetStartPos, targetSparse.ValueCount - targetEndPos);
Array.Copy(targetSparse.Values, targetEndPos, newValues, positionsToCopy + targetStartPos, targetSparse.ValueCount - targetEndPos);
targetSparse.Values = newValues;
targetSparse.Indices = newIndices;
targetSparse.ValueCount = newValueCount;
}
else
{
// although there are no values to copy, we still need to clean the existing values (if necessary)
if (existingData == ExistingData.Clear)
{
target.Clear(targetColumnIndex, columnCount);
}
}
}
return;
}
// FALLBACK
if (existingData == ExistingData.Clear)
{
target.Clear(targetColumnIndex, columnCount);
}
// If there are non-zero elements use base class implementation
for (int i = sourceColumnIndex, j = 0; i < sourceColumnIndex + columnCount; i++, j++)
{
var index = FindItem(rowIndex, i);
target.At(j, index >= 0 ? Values[index] : Zero);
}
}
// TRANSPOSE
internal override void TransposeToUnchecked(MatrixStorage target, ExistingData existingData)
{
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
TransposeToUnchecked(sparseTarget);
return;
}
if (target is DenseColumnMajorMatrixStorage denseTarget)
{
TransposeToUnchecked(denseTarget, existingData);
return;
}
// FALL BACK
if (existingData == ExistingData.Clear)
{
target.Clear();
}
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.At(ColumnIndices[j], row, Values[j]);
}
}
}
}
void TransposeToUnchecked(SparseCompressedRowMatrixStorage target)
{
target.Values = new T[ValueCount];
target.ColumnIndices = new int[ValueCount];
var cx = target.Values;
var cp = target.RowPointers;
var ci = target.ColumnIndices;
// Column counts
int[] w = new int[ColumnCount];
for (int p = 0; p < RowPointers[RowCount]; p++)
{
w[ColumnIndices[p]]++;
}
// Column pointers
int nz = 0;
for (int i = 0; i < ColumnCount; i++)
{
cp[i] = nz;
nz += w[i];
w[i] = cp[i];
}
cp[ColumnCount] = nz;
for (int i = 0; i < RowCount; i++)
{
for (int p = RowPointers[i]; p < RowPointers[i + 1]; p++)
{
int j = w[ColumnIndices[p]]++;
// Place A(i,j) as entry C(j,i)
ci[j] = i;
cx[j] = Values[p];
}
}
}
void TransposeToUnchecked(DenseColumnMajorMatrixStorage target, ExistingData existingData)
{
if (existingData == ExistingData.Clear)
{
target.Clear();
}
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var targetIndex = row * ColumnCount;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.Data[targetIndex + ColumnIndices[j]] = Values[j];
}
}
}
}
internal override void TransposeSquareInplaceUnchecked()
{
var cx = new T[ValueCount]; //target.Values;
var cp = new int[RowCount + 1];
var ci = new int[ValueCount]; //target.ColumnIndices;
// Column counts
int[] w = new int[ColumnCount];
for (int p = 0; p < RowPointers[RowCount]; p++)
{
w[ColumnIndices[p]]++;
}
// Column pointers
int nz = 0;
for (int i = 0; i < ColumnCount; i++)
{
cp[i] = nz;
nz += w[i];
w[i] = cp[i];
}
cp[ColumnCount] = nz;
for (int i = 0; i < RowCount; i++)
{
for (int p = RowPointers[i]; p < RowPointers[i + 1]; p++)
{
int j = w[ColumnIndices[p]]++;
// Place A(i,j) as entry C(j,i)
ci[j] = i;
cx[j] = Values[p];
}
}
Array.Copy(cx, 0, Values, 0, ValueCount);
Buffer.BlockCopy(ci, 0, ColumnIndices, 0, ValueCount * Constants.SizeOfInt);
Buffer.BlockCopy(cp, 0, RowPointers, 0, (RowCount + 1) * Constants.SizeOfInt);
}
// EXTRACT
public override T[] ToRowMajorArray()
{
var ret = new T[RowCount*ColumnCount];
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var offset = row*ColumnCount;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
ret[offset + ColumnIndices[j]] = Values[j];
}
}
}
return ret;
}
public override T[] ToColumnMajorArray()
{
var ret = new T[RowCount*ColumnCount];
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
ret[(ColumnIndices[j])*RowCount + row] = Values[j];
}
}
}
return ret;
}
public override T[][] ToRowArrays()
{
var ret = new T[RowCount][];
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var array = new T[ColumnCount];
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
array[ColumnIndices[j]] = Values[j];
}
ret[row] = array;
}
}
return ret;
}
public override T[][] ToColumnArrays()
{
var ret = new T[ColumnCount][];
for (int j = 0; j < ColumnCount; j++)
{
ret[j] = new T[RowCount];
}
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
ret[ColumnIndices[j]][row] = Values[j];
}
}
}
return ret;
}
public override T[,] ToArray()
{
var ret = new T[RowCount, ColumnCount];
if (ValueCount != 0)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
ret[row, ColumnIndices[j]] = Values[j];
}
}
}
return ret;
}
// ENUMERATION
public override IEnumerable Enumerate()
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
yield return k < RowPointers[row + 1] && ColumnIndices[k] == col
? Values[k++]
: Zero;
}
}
}
public override IEnumerable> EnumerateIndexed()
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
yield return k < RowPointers[row + 1] && ColumnIndices[k] == col
? new Tuple(row, col, Values[k++])
: new Tuple(row, col, Zero);
}
}
}
public override IEnumerable EnumerateNonZero()
{
return Values.Take(ValueCount).Where(x => !Zero.Equals(x));
}
public override IEnumerable> EnumerateNonZeroIndexed()
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
if (!Zero.Equals(Values[j]))
{
yield return new Tuple(row, ColumnIndices[j], Values[j]);
}
}
}
}
// FIND
public override Tuple Find(Func predicate, Zeros zeros)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
if (predicate(Values[j]))
{
return new Tuple(row, ColumnIndices[j], Values[j]);
}
}
}
if (zeros == Zeros.Include && ValueCount < (RowCount * ColumnCount))
{
if (predicate(Zero))
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
if (k < RowPointers[row + 1] && ColumnIndices[k] == col)
{
k++;
}
else
{
return new Tuple(row, col, Zero);
}
}
}
}
}
return null;
}
internal override Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros)
{
if (other is DenseColumnMajorMatrixStorage denseOther)
{
TOther[] otherData = denseOther.Data;
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
if (predicate(available ? Values[k++] : Zero, otherData[col*RowCount + row]))
{
return new Tuple(row, col, available ? Values[k - 1] : Zero, otherData[col*RowCount + row]);
}
}
}
return null;
}
if (other is DiagonalMatrixStorage diagonalOther)
{
TOther[] otherData = diagonalOther.Data;
TOther otherZero = BuilderInstance.Matrix.Zero;
// Full Scan
if (zeros == Zeros.Include && predicate(Zero, otherZero))
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
if (predicate(available ? Values[k++] : Zero, row == col ? otherData[row] : otherZero))
{
return new Tuple(row, col, available ? Values[k - 1] : Zero, row == col ? otherData[row] : otherZero);
}
}
}
return null;
}
// Sparse Scan
for (int row = 0; row < RowCount; row++)
{
bool diagonal = false;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
if (ColumnIndices[j] == row)
{
diagonal = true;
if (predicate(Values[j], otherData[row]))
{
return new Tuple(row, row, Values[j], otherData[row]);
}
}
else
{
if (predicate(Values[j], otherZero))
{
return new Tuple(row, ColumnIndices[j], Values[j], otherZero);
}
}
}
if (!diagonal && row < ColumnCount)
{
if (predicate(Zero, otherData[row]))
{
return new Tuple(row, row, Zero, otherData[row]);
}
}
}
return null;
}
if (other is SparseCompressedRowMatrixStorage sparseOther)
{
int[] otherRowPointers = sparseOther.RowPointers;
int[] otherColumnIndices = sparseOther.ColumnIndices;
TOther[] otherValues = sparseOther.Values;
TOther otherZero = BuilderInstance.Matrix.Zero;
if (zeros == Zeros.Include)
{
int k = 0, otherk = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
bool otherAvailable = otherk < otherRowPointers[row + 1] && otherColumnIndices[otherk] == col;
if (predicate(available ? Values[k++] : Zero, otherAvailable ? otherValues[otherk++] : otherZero))
{
return new Tuple(row, col, available ? Values[k - 1] : Zero, otherAvailable ? otherValues[otherk - 1] : otherZero);
}
}
}
return null;
}
for (int row = 0; row < RowCount; row++)
{
var endIndex = RowPointers[row + 1];
var otherEndIndex = otherRowPointers[row + 1];
var k = RowPointers[row];
var otherk = otherRowPointers[row];
while (k < endIndex || otherk < otherEndIndex)
{
if (k == endIndex || otherk < otherEndIndex && ColumnIndices[k] > otherColumnIndices[otherk])
{
if (predicate(Zero, otherValues[otherk++]))
{
return new Tuple(row, otherColumnIndices[otherk - 1], Zero, otherValues[otherk - 1]);
}
}
else if (otherk == otherEndIndex || ColumnIndices[k] < otherColumnIndices[otherk])
{
if (predicate(Values[k++], otherZero))
{
return new Tuple(row, ColumnIndices[k - 1], Values[k - 1], otherZero);
}
}
else
{
if (predicate(Values[k++], otherValues[otherk++]))
{
return new Tuple(row, ColumnIndices[k - 1], Values[k - 1], otherValues[otherk - 1]);
}
}
}
}
return null;
}
// FALL BACK
return base.Find2Unchecked(other, predicate, zeros);
}
// FUNCTIONAL COMBINATORS: MAP
public override void MapInplace(Func f, Zeros zeros)
{
if (zeros == Zeros.Include || !Zero.Equals(f(Zero)))
{
var newRowPointers = RowPointers;
var newColumnIndices = new List(ColumnIndices.Length);
var newValues = new List(Values.Length);
int k = 0;
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
for (int col = 0; col < ColumnCount; col++)
{
var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(Values[k++]) : f(Zero);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(col);
}
}
}
ColumnIndices = newColumnIndices.ToArray();
Values = newValues.ToArray();
newRowPointers[RowCount] = newValues.Count;
}
else
{
// we can safely do this in-place:
int nonZero = 0;
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
RowPointers[row] = nonZero;
for (var j = startIndex; j < endIndex; j++)
{
var item = f(Values[j]);
if (!Zero.Equals(item))
{
Values[nonZero] = item;
ColumnIndices[nonZero] = ColumnIndices[j];
nonZero++;
}
}
}
Array.Resize(ref ColumnIndices, nonZero);
Array.Resize(ref Values, nonZero);
RowPointers[RowCount] = nonZero;
}
}
public override void MapIndexedInplace(Func f, Zeros zeros)
{
if (zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)))
{
var newRowPointers = RowPointers;
var newColumnIndices = new List(ColumnIndices.Length);
var newValues = new List(Values.Length);
int k = 0;
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
for (int col = 0; col < ColumnCount; col++)
{
var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(row, col, Values[k++]) : f(row, col, Zero);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(col);
}
}
}
ColumnIndices = newColumnIndices.ToArray();
Values = newValues.ToArray();
newRowPointers[RowCount] = newValues.Count;
}
else
{
// we can safely do this in-place:
int nonZero = 0;
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
RowPointers[row] = nonZero;
for (var j = startIndex; j < endIndex; j++)
{
var item = f(row, ColumnIndices[j], Values[j]);
if (!Zero.Equals(item))
{
Values[nonZero] = item;
ColumnIndices[nonZero] = ColumnIndices[j];
nonZero++;
}
}
}
Array.Resize(ref ColumnIndices, nonZero);
Array.Resize(ref Values, nonZero);
RowPointers[RowCount] = nonZero;
}
}
internal override void MapToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData)
{
var processZeros = zeros == Zeros.Include || !Zero.Equals(f(Zero));
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
var newRowPointers = sparseTarget.RowPointers;
var newColumnIndices = new List(ColumnIndices.Length);
var newValues = new List(Values.Length);
if (processZeros)
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
for (int col = 0; col < ColumnCount; col++)
{
var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(Values[k++]) : f(Zero);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(col);
}
}
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
var item = f(Values[j]);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(ColumnIndices[j]);
}
}
}
}
sparseTarget.ColumnIndices = newColumnIndices.ToArray();
sparseTarget.Values = newValues.ToArray();
newRowPointers[RowCount] = newValues.Count;
return;
}
// FALL BACK
if (existingData == ExistingData.Clear && !processZeros)
{
target.Clear();
}
if (processZeros)
{
for (int row = 0; row < RowCount; row++)
{
var index = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (int j = 0; j < ColumnCount; j++)
{
if (index < endIndex && j == ColumnIndices[index])
{
target.At(row, j, f(Values[index]));
index = Math.Min(index + 1, endIndex);
}
else
{
target.At(row, j, f(Zero));
}
}
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.At(row, ColumnIndices[j], f(Values[j]));
}
}
}
}
internal override void MapIndexedToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData)
{
var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero));
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
var newRowPointers = sparseTarget.RowPointers;
var newColumnIndices = new List(ColumnIndices.Length);
var newValues = new List(Values.Length);
if (processZeros)
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
for (int col = 0; col < ColumnCount; col++)
{
var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(row, col, Values[k++]) : f(row, col, Zero);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(col);
}
}
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
newRowPointers[row] = newValues.Count;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
var item = f(row, ColumnIndices[j], Values[j]);
if (!Zero.Equals(item))
{
newValues.Add(item);
newColumnIndices.Add(ColumnIndices[j]);
}
}
}
}
sparseTarget.ColumnIndices = newColumnIndices.ToArray();
sparseTarget.Values = newValues.ToArray();
newRowPointers[RowCount] = newValues.Count;
return;
}
// FALL BACK
if (existingData == ExistingData.Clear && !processZeros)
{
target.Clear();
}
if (processZeros)
{
for (int row = 0; row < RowCount; row++)
{
var index = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (int j = 0; j < ColumnCount; j++)
{
if (index < endIndex && j == ColumnIndices[index])
{
target.At(row, j, f(row, j, Values[index]));
index = Math.Min(index + 1, endIndex);
}
else
{
target.At(row, j, f(row, j, Zero));
}
}
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
target.At(row, ColumnIndices[j], f(row, ColumnIndices[j], Values[j]));
}
}
}
}
internal override void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f,
int sourceRowIndex, int targetRowIndex, int rowCount,
int sourceColumnIndex, int targetColumnIndex, int columnCount,
Zeros zeros, ExistingData existingData)
{
if (target is SparseCompressedRowMatrixStorage sparseTarget)
{
MapSubMatrixIndexedToUnchecked(sparseTarget, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros, existingData);
return;
}
// FALL BACK
var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero));
if (existingData == ExistingData.Clear && !processZeros)
{
target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount);
}
if (processZeros)
{
for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++)
{
var index = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
// move forward to our sub-range
for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++)
{
}
for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++)
{
if (index < endIndex && sc == ColumnIndices[index])
{
target.At(tr, tc, f(tr, tc, Values[index]));
index = Math.Min(index + 1, endIndex);
}
else
{
target.At(tr, tc, f(tr, tc, Zero));
}
}
}
}
else
{
int columnOffset = targetColumnIndex - sourceColumnIndex;
for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++)
{
var startIndex = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
for (int k = startIndex; k < endIndex; k++)
{
// check if the column index is in the range
if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount))
{
int tc = ColumnIndices[k] + columnOffset;
target.At(tr, tc, f(tr, tc, Values[k]));
}
}
}
}
}
void MapSubMatrixIndexedToUnchecked(SparseCompressedRowMatrixStorage target, Func f,
int sourceRowIndex, int targetRowIndex, int rowCount,
int sourceColumnIndex, int targetColumnIndex, int columnCount,
Zeros zeros, ExistingData existingData)
where TU : struct, IEquatable, IFormattable
{
var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero));
if (existingData == ExistingData.Clear && !processZeros)
{
target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount);
}
var rowOffset = targetRowIndex - sourceRowIndex;
var columnOffset = targetColumnIndex - sourceColumnIndex;
var zero = Matrix.Zero;
// special case for empty target - much faster
if (target.ValueCount == 0)
{
var values = new List(ValueCount);
var columnIndices = new List(ValueCount);
var rowPointers = target.RowPointers;
if (processZeros)
{
for (int sr = sourceRowIndex; sr < sourceRowIndex + rowCount; sr++)
{
int tr = sr + rowOffset;
rowPointers[tr] = values.Count;
var index = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
// move forward to our sub-range
for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++)
{
}
for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++)
{
if (index < endIndex && sc == ColumnIndices[index])
{
TU item = f(tr, tc, Values[index]);
if (!zero.Equals(item))
{
values.Add(item);
columnIndices.Add(tc);
}
index = Math.Min(index + 1, endIndex);
}
else
{
TU item = f(tr, tc, Zero);
if (!zero.Equals(item))
{
values.Add(item);
columnIndices.Add(tc);
}
}
}
}
}
else
{
for (int sr = sourceRowIndex; sr < sourceRowIndex + rowCount; sr++)
{
int tr = sr + rowOffset;
rowPointers[tr] = values.Count;
var startIndex = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
for (int k = startIndex; k < endIndex; k++)
{
// check if the column index is in the range
if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount))
{
int tc = ColumnIndices[k] + columnOffset;
TU item = f(tr, tc, Values[k]);
if (!zero.Equals(item))
{
values.Add(item);
columnIndices.Add(tc);
}
}
}
}
}
for (int i = targetRowIndex + rowCount; i < rowPointers.Length; i++)
{
rowPointers[i] = values.Count;
}
target.RowPointers[target.RowCount] = values.Count;
target.Values = values.ToArray();
target.ColumnIndices = columnIndices.ToArray();
return;
}
// TODO: proper general sparse case - the following is essentially a fall back, not leveraging the target data structure
if (processZeros)
{
for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++)
{
var index = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
// move forward to our sub-range
for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++)
{
}
for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++)
{
if (index < endIndex && sc == ColumnIndices[index])
{
target.At(tr, tc, f(tr, tc, Values[index]));
index = Math.Min(index + 1, endIndex);
}
else
{
target.At(tr, tc, f(tr, tc, Zero));
}
}
}
}
else
{
for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++)
{
var startIndex = RowPointers[sr];
var endIndex = RowPointers[sr + 1];
for (int k = startIndex; k < endIndex; k++)
{
// check if the column index is in the range
if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount))
{
int tc = ColumnIndices[k] + columnOffset;
target.At(tr, tc, f(tr, tc, Values[k]));
}
}
}
}
}
// FUNCTIONAL COMBINATORS: FOLD
internal override void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros)
{
if (zeros == Zeros.AllowSkip)
{
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
TU s = state[row];
for (var j = startIndex; j < endIndex; j++)
{
s = f(s, Values[j]);
}
target[row] = finalize(s, endIndex - startIndex);
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
var index = RowPointers[row];
var endIndex = RowPointers[row + 1];
TU s = state[row];
for (int j = 0; j < ColumnCount; j++)
{
if (index < endIndex && j == ColumnIndices[index])
{
s = f(s, Values[index]);
index = Math.Min(index + 1, endIndex);
}
else
{
s = f(s, Zero);
}
}
target[row] = finalize(s, ColumnCount);
}
}
}
internal override void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros)
{
if (!ReferenceEquals(state, target))
{
Array.Copy(state, 0, target, 0, state.Length);
}
if (zeros == Zeros.AllowSkip)
{
int[] count = new int[ColumnCount];
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
var column = ColumnIndices[j];
target[column] = f(target[column], Values[j]);
count[column]++;
}
}
for (int j = 0; j < ColumnCount; j++)
{
target[j] = finalize(target[j], count[j]);
}
}
else
{
for (int row = 0; row < RowCount; row++)
{
var index = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (int j = 0; j < ColumnCount; j++)
{
if (index < endIndex && j == ColumnIndices[index])
{
target[j] = f(target[j], Values[index]);
index = Math.Min(index + 1, endIndex);
}
else
{
target[j] = f(target[j], Zero);
}
}
}
for (int j = 0; j < ColumnCount; j++)
{
target[j] = finalize(target[j], RowCount);
}
}
}
internal override TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros)
{
if (other is DenseColumnMajorMatrixStorage denseOther)
{
TOther[] otherData = denseOther.Data;
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
state = f(state, available ? Values[k++] : Zero, otherData[col*RowCount + row]);
}
}
return state;
}
if (other is DiagonalMatrixStorage diagonalOther)
{
TOther[] otherData = diagonalOther.Data;
TOther otherZero = BuilderInstance.Matrix.Zero;
if (zeros == Zeros.Include)
{
int k = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
state = f(state, available ? Values[k++] : Zero, row == col ? otherData[row] : otherZero);
}
}
return state;
}
for (int row = 0; row < RowCount; row++)
{
bool diagonal = false;
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
for (var j = startIndex; j < endIndex; j++)
{
if (ColumnIndices[j] == row)
{
diagonal = true;
state = f(state, Values[j], otherData[row]);
}
else
{
state = f(state, Values[j], otherZero);
}
}
if (!diagonal && row < ColumnCount)
{
state = f(state, Zero, otherData[row]);
}
}
return state;
}
if (other is SparseCompressedRowMatrixStorage sparseOther)
{
int[] otherRowPointers = sparseOther.RowPointers;
int[] otherColumnIndices = sparseOther.ColumnIndices;
TOther[] otherValues = sparseOther.Values;
TOther otherZero = BuilderInstance.Matrix.Zero;
if (zeros == Zeros.Include)
{
int k = 0, otherk = 0;
for (int row = 0; row < RowCount; row++)
{
for (int col = 0; col < ColumnCount; col++)
{
bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col;
bool otherAvailable = otherk < otherRowPointers[row + 1] && otherColumnIndices[otherk] == col;
state = f(state, available ? Values[k++] : Zero, otherAvailable ? otherValues[otherk++] : otherZero);
}
}
return state;
}
for (int row = 0; row < RowCount; row++)
{
var startIndex = RowPointers[row];
var endIndex = RowPointers[row + 1];
var otherStartIndex = otherRowPointers[row];
var otherEndIndex = otherRowPointers[row + 1];
var j1 = startIndex;
var j2 = otherStartIndex;
while (j1 < endIndex || j2 < otherEndIndex)
{
if (j1 == endIndex || j2 < otherEndIndex && ColumnIndices[j1] > otherColumnIndices[j2])
{
state = f(state, Zero, otherValues[j2++]);
}
else if (j2 == otherEndIndex || ColumnIndices[j1] < otherColumnIndices[j2])
{
state = f(state, Values[j1++], otherZero);
}
else
{
state = f(state, Values[j1++], otherValues[j2++]);
}
}
}
return state;
}
// FALL BACK
return base.Fold2Unchecked(other, f, state, zeros);
}
}
}