//===- 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 (DynamicAPInt) or // rational numbers (Fraction). // //===----------------------------------------------------------------------===// #ifndef MLIR_ANALYSIS_PRESBURGER_MATRIX_H #define MLIR_ANALYSIS_PRESBURGER_MATRIX_H #include "mlir/Analysis/Presburger/Fraction.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/raw_ostream.h" #include namespace mlir { namespace presburger { using llvm::ArrayRef; using llvm::MutableArrayRef; using llvm::raw_ostream; using llvm::SmallVector; /// 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 DynamicAPInt and Fraction, since the /// method implementations are in the Matrix.cpp file. Only these two types have /// been explicitly instantiated there. template class Matrix { static_assert(std::is_same_v || std::is_same_v, "T must be DynamicAPInt 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 &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 getRow(unsigned row); ArrayRef getRow(unsigned row) const; /// Set the specified row to `elems`. void setRow(unsigned row, ArrayRef 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 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 preMultiplyWithRow(ArrayRef rowVec) const; /// The given vector is interpreted as a column vector v. Pre-multiply v with /// this matrix, say M, and return Mv. SmallVector postMultiplyWithColumn(ArrayRef 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 elems); // Transpose the matrix without modifying it. Matrix transpose() const; // Copy the cells in the intersection of // the rows between `fromRows` and `toRows` and // the columns between `fromColumns` and `toColumns`, inclusive on the left // but exclusive on the right (same as canonical C++ ranges). Matrix 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> splitByBitset(ArrayRef 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); /// Returns the matrix right-multiplied with `other`. Matrix postMultiply(const Matrix &other) const; 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 data; }; extern template class Matrix; extern template class Matrix; // 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 FracMatrix; class IntMatrix : public Matrix { public: IntMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0, unsigned reservedColumns = 0) : Matrix(rows, columns, reservedRows, reservedColumns) {} IntMatrix(Matrix m) : Matrix(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 computeHermiteNormalForm() const; /// Given the current matrix M, returns the matrices U, D, V such that /// UMV = D, where D is called the Smith Normal Form (SNF). /// The matrices have the following properties: /// - U, V are unimodular. In other words, det(U), det(V) are 1 or -1; /// their inverses also contain integer entries. /// - D is diagonal. /// - For all i, the diagonal element D_{i, i} divides D_{i + 1, i + 1}. std::tuple computeSmithNormalForm() const; /// Divide the first `nCols` of the specified row by their GCD. /// Returns the GCD of the first `nCols` of the specified row. DynamicAPInt 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. DynamicAPInt 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. DynamicAPInt determinant(IntMatrix *inverse = nullptr) const; // Converts the matrix into a FracMatrix as-is. FracMatrix asFracMatrix() 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 { public: FracMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0, unsigned reservedColumns = 0) : Matrix(rows, columns, reservedRows, reservedColumns){}; FracMatrix(Matrix m) : Matrix(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(const Fraction &delta); // Multiply each row of the matrix by the LCM of the denominators, thereby // converting it to an integer matrix. IntMatrix normalizeRows() const; // Converts the matrix to an IntMatrix as-is. If any value in the matrix // is not an integer, the function triggers an assertion failure. IntMatrix asIntMatrix() const; }; } // namespace presburger } // namespace mlir #endif // MLIR_ANALYSIS_PRESBURGER_MATRIX_H