blob: c20a7bcecd52dc2882c6e01fcd2b2038dc2c3276 [file] [log] [blame]
//===- Matrix.h - MLIR Matrix Class -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is a simple 2D matrix class that supports reading, writing, resizing,
// swapping rows, and swapping columns. It can hold integers (MPInt) or rational
// numbers (Fraction).
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_ANALYSIS_PRESBURGER_MATRIX_H
#define MLIR_ANALYSIS_PRESBURGER_MATRIX_H
#include "mlir/Analysis/Presburger/Fraction.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/raw_ostream.h"
#include <bitset>
#include <cassert>
namespace mlir {
namespace presburger {
/// This is a class to represent a resizable matrix.
///
/// More columns and rows can be reserved than are currently used. The data is
/// stored as a single 1D array, viewed as a 2D matrix with nRows rows and
/// nReservedColumns columns, stored in row major form. Thus the element at
/// (i, j) is stored at data[i*nReservedColumns + j]. The reserved but unused
/// columns always have all zero values. The reserved rows are just reserved
/// space in the underlying SmallVector's capacity.
/// This class only works for the types MPInt and Fraction, since the method
/// implementations are in the Matrix.cpp file. Only these two types have
/// been explicitly instantiated there.
template <typename T>
class Matrix {
static_assert(std::is_same_v<T, MPInt> || std::is_same_v<T, Fraction>,
"T must be MPInt or Fraction.");
public:
Matrix() = delete;
/// Construct a matrix with the specified number of rows and columns.
/// The number of reserved rows and columns will be at least the number
/// specified, and will always be sufficient to accomodate the number of rows
/// and columns specified.
///
/// Initially, the entries are initialized to ero.
Matrix(unsigned rows, unsigned columns, unsigned reservedRows = 0,
unsigned reservedColumns = 0);
/// Return the identity matrix of the specified dimension.
static Matrix identity(unsigned dimension);
/// Access the element at the specified row and column.
T &at(unsigned row, unsigned column) {
assert(row < nRows && "Row outside of range");
assert(column < nColumns && "Column outside of range");
return data[row * nReservedColumns + column];
}
T at(unsigned row, unsigned column) const {
assert(row < nRows && "Row outside of range");
assert(column < nColumns && "Column outside of range");
return data[row * nReservedColumns + column];
}
T &operator()(unsigned row, unsigned column) { return at(row, column); }
T operator()(unsigned row, unsigned column) const { return at(row, column); }
bool operator==(const Matrix<T> &m) const;
/// Swap the given columns.
void swapColumns(unsigned column, unsigned otherColumn);
/// Swap the given rows.
void swapRows(unsigned row, unsigned otherRow);
unsigned getNumRows() const { return nRows; }
unsigned getNumColumns() const { return nColumns; }
/// Return the maximum number of rows/columns that can be added without
/// incurring a reallocation.
unsigned getNumReservedRows() const;
unsigned getNumReservedColumns() const { return nReservedColumns; }
/// Reserve enough space to resize to the specified number of rows without
/// reallocations.
void reserveRows(unsigned rows);
/// Get a [Mutable]ArrayRef corresponding to the specified row.
MutableArrayRef<T> getRow(unsigned row);
ArrayRef<T> getRow(unsigned row) const;
/// Set the specified row to `elems`.
void setRow(unsigned row, ArrayRef<T> elems);
/// Insert columns having positions pos, pos + 1, ... pos + count - 1.
/// Columns that were at positions 0 to pos - 1 will stay where they are;
/// columns that were at positions pos to nColumns - 1 will be pushed to the
/// right. pos should be at most nColumns.
void insertColumns(unsigned pos, unsigned count);
void insertColumn(unsigned pos);
/// Insert rows having positions pos, pos + 1, ... pos + count - 1.
/// Rows that were at positions 0 to pos - 1 will stay where they are;
/// rows that were at positions pos to nColumns - 1 will be pushed to the
/// right. pos should be at most nRows.
void insertRows(unsigned pos, unsigned count);
void insertRow(unsigned pos);
/// Remove the columns having positions pos, pos + 1, ... pos + count - 1.
/// Rows that were at positions 0 to pos - 1 will stay where they are;
/// columns that were at positions pos + count - 1 or later will be pushed to
/// the right. The columns to be deleted must be valid rows: pos + count - 1
/// must be at most nColumns - 1.
void removeColumns(unsigned pos, unsigned count);
void removeColumn(unsigned pos);
/// Remove the rows having positions pos, pos + 1, ... pos + count - 1.
/// Rows that were at positions 0 to pos - 1 will stay where they are;
/// rows that were at positions pos + count - 1 or later will be pushed to the
/// right. The rows to be deleted must be valid rows: pos + count - 1 must be
/// at most nRows - 1.
void removeRows(unsigned pos, unsigned count);
void removeRow(unsigned pos);
void copyRow(unsigned sourceRow, unsigned targetRow);
void fillRow(unsigned row, const T &value);
void fillRow(unsigned row, int64_t value) { fillRow(row, T(value)); }
/// Add `scale` multiples of the source row to the target row.
void addToRow(unsigned sourceRow, unsigned targetRow, const T &scale);
void addToRow(unsigned sourceRow, unsigned targetRow, int64_t scale) {
addToRow(sourceRow, targetRow, T(scale));
}
/// Add `scale` multiples of the rowVec row to the specified row.
void addToRow(unsigned row, ArrayRef<T> rowVec, const T &scale);
/// Multiply the specified row by a factor of `scale`.
void scaleRow(unsigned row, const T &scale);
/// Add `scale` multiples of the source column to the target column.
void addToColumn(unsigned sourceColumn, unsigned targetColumn,
const T &scale);
void addToColumn(unsigned sourceColumn, unsigned targetColumn,
int64_t scale) {
addToColumn(sourceColumn, targetColumn, T(scale));
}
/// Negate the specified column.
void negateColumn(unsigned column);
/// Negate the specified row.
void negateRow(unsigned row);
/// Negate the entire matrix.
void negateMatrix();
/// The given vector is interpreted as a row vector v. Post-multiply v with
/// this matrix, say M, and return vM.
SmallVector<T, 8> preMultiplyWithRow(ArrayRef<T> rowVec) const;
/// The given vector is interpreted as a column vector v. Pre-multiply v with
/// this matrix, say M, and return Mv.
SmallVector<T, 8> postMultiplyWithColumn(ArrayRef<T> colVec) const;
/// Resize the matrix to the specified dimensions. If a dimension is smaller,
/// the values are truncated; if it is bigger, the new values are initialized
/// to zero.
///
/// Due to the representation of the matrix, resizing vertically (adding rows)
/// is less expensive than increasing the number of columns beyond
/// nReservedColumns.
void resize(unsigned newNRows, unsigned newNColumns);
void resizeHorizontally(unsigned newNColumns);
void resizeVertically(unsigned newNRows);
/// Add an extra row at the bottom of the matrix and return its position.
unsigned appendExtraRow();
/// Same as above, but copy the given elements into the row. The length of
/// `elems` must be equal to the number of columns.
unsigned appendExtraRow(ArrayRef<T> elems);
// Transpose the matrix without modifying it.
Matrix<T> transpose() const;
// Copy the cells in the intersection of
// the rows between `fromRows` and `toRows` and
// the columns between `fromColumns` and `toColumns`, both inclusive.
Matrix<T> getSubMatrix(unsigned fromRow, unsigned toRow, unsigned fromColumn,
unsigned toColumn) const;
/// Split the rows of a matrix into two matrices according to which bits are
/// 1 and which are 0 in a given bitset.
///
/// The first matrix returned has the rows corresponding to 1 and the second
/// corresponding to 2.
std::pair<Matrix<T>, Matrix<T>> splitByBitset(ArrayRef<int> indicator);
/// Print the matrix.
void print(raw_ostream &os) const;
void dump() const;
/// Return whether the Matrix is in a consistent state with all its
/// invariants satisfied.
bool hasConsistentState() const;
/// Move the columns in the source range [srcPos, srcPos + num) to the
/// specified destination [dstPos, dstPos + num), while moving the columns
/// adjacent to the source range to the left/right of the shifted columns.
///
/// When moving the source columns right (i.e. dstPos > srcPos), columns that
/// were at positions [0, srcPos) and [dstPos + num, nCols) will stay where
/// they are; columns that were at positions [srcPos, srcPos + num) will be
/// moved to [dstPos, dstPos + num); and columns that were at positions
/// [srcPos + num, dstPos + num) will be moved to [srcPos, dstPos).
/// Equivalently, the columns [srcPos + num, dstPos + num) are interchanged
/// with [srcPos, srcPos + num).
/// For example, if m = |0 1 2 3 4 5| then:
/// m.moveColumns(1, 3, 2) will result in m = |0 4 1 2 3 5|; or
/// m.moveColumns(1, 2, 4) will result in m = |0 3 4 5 1 2|.
///
/// The left shift operation (i.e. dstPos < srcPos) works in a similar way.
void moveColumns(unsigned srcPos, unsigned num, unsigned dstPos);
protected:
/// The current number of rows, columns, and reserved columns. The underlying
/// data vector is viewed as an nRows x nReservedColumns matrix, of which the
/// first nColumns columns are currently in use, and the remaining are
/// reserved columns filled with zeros.
unsigned nRows, nColumns, nReservedColumns;
/// Stores the data. data.size() is equal to nRows * nReservedColumns.
/// data.capacity() / nReservedColumns is the number of reserved rows.
SmallVector<T, 16> data;
};
extern template class Matrix<MPInt>;
extern template class Matrix<Fraction>;
// An inherited class for integer matrices, with no new data attributes.
// This is only used for the matrix-related methods which apply only
// to integers (hermite normal form computation and row normalisation).
class IntMatrix : public Matrix<MPInt> {
public:
IntMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0,
unsigned reservedColumns = 0)
: Matrix<MPInt>(rows, columns, reservedRows, reservedColumns){};
IntMatrix(Matrix<MPInt> m) : Matrix<MPInt>(std::move(m)){};
/// Return the identity matrix of the specified dimension.
static IntMatrix identity(unsigned dimension);
/// Given the current matrix M, returns the matrices H, U such that H is the
/// column hermite normal form of M, i.e. H = M * U, where U is unimodular and
/// the matrix H has the following restrictions:
/// - H is lower triangular.
/// - The leading coefficient (the first non-zero entry from the top, called
/// the pivot) of a non-zero column is always strictly below of the leading
/// coefficient of the column before it; moreover, it is positive.
/// - The elements to the right of the pivots are zero and the elements to
/// the left of the pivots are nonnegative and strictly smaller than the
/// pivot.
std::pair<IntMatrix, IntMatrix> computeHermiteNormalForm() const;
/// Divide the first `nCols` of the specified row by their GCD.
/// Returns the GCD of the first `nCols` of the specified row.
MPInt normalizeRow(unsigned row, unsigned nCols);
/// Divide the columns of the specified row by their GCD.
/// Returns the GCD of the columns of the specified row.
MPInt normalizeRow(unsigned row);
// Compute the determinant of the matrix (cubic time).
// Stores the integer inverse of the matrix in the pointer
// passed (if any). The pointer is unchanged if the inverse
// does not exist, which happens iff det = 0.
// For a matrix M, the integer inverse is the matrix M' such that
// M x M' = M'  M = det(M) x I.
// Assert-fails if the matrix is not square.
MPInt determinant(IntMatrix *inverse = nullptr) const;
};
// An inherited class for rational matrices, with no new data attributes.
// This class is for functionality that only applies to matrices of fractions.
class FracMatrix : public Matrix<Fraction> {
public:
FracMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0,
unsigned reservedColumns = 0)
: Matrix<Fraction>(rows, columns, reservedRows, reservedColumns){};
FracMatrix(Matrix<Fraction> m) : Matrix<Fraction>(std::move(m)){};
explicit FracMatrix(IntMatrix m);
/// Return the identity matrix of the specified dimension.
static FracMatrix identity(unsigned dimension);
// Compute the determinant of the matrix (cubic time).
// Stores the inverse of the matrix in the pointer
// passed (if any). The pointer is unchanged if the inverse
// does not exist, which happens iff det = 0.
// Assert-fails if the matrix is not square.
Fraction determinant(FracMatrix *inverse = nullptr) const;
// Computes the Gram-Schmidt orthogonalisation
// of the rows of matrix (cubic time).
// The rows of the matrix must be linearly independent.
FracMatrix gramSchmidt() const;
// Run LLL basis reduction on the matrix, modifying it in-place.
// The parameter is what [the original
// paper](https://www.cs.cmu.edu/~avrim/451f11/lectures/lect1129_LLL.pdf)
// calls `y`, usually 3/4.
void LLL(Fraction delta);
// Multiply each row of the matrix by the LCM of the denominators, thereby
// converting it to an integer matrix.
IntMatrix normalizeRows() const;
};
} // namespace presburger
} // namespace mlir
#endif // MLIR_ANALYSIS_PRESBURGER_MATRIX_H