1 //===- Matrix.h - MLIR Matrix Class -----------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This is a simple 2D matrix class that supports reading, writing, resizing, 10 // swapping rows, and swapping columns. It can hold integers (DynamicAPInt) or 11 // rational numbers (Fraction). 12 // 13 //===----------------------------------------------------------------------===// 14 15 #ifndef MLIR_ANALYSIS_PRESBURGER_MATRIX_H 16 #define MLIR_ANALYSIS_PRESBURGER_MATRIX_H 17 18 #include "mlir/Analysis/Presburger/Fraction.h" 19 #include "llvm/ADT/ArrayRef.h" 20 #include "llvm/Support/raw_ostream.h" 21 #include <cassert> 22 23 namespace mlir { 24 namespace presburger { 25 using llvm::ArrayRef; 26 using llvm::MutableArrayRef; 27 using llvm::raw_ostream; 28 using llvm::SmallVector; 29 30 /// This is a class to represent a resizable matrix. 31 /// 32 /// More columns and rows can be reserved than are currently used. The data is 33 /// stored as a single 1D array, viewed as a 2D matrix with nRows rows and 34 /// nReservedColumns columns, stored in row major form. Thus the element at 35 /// (i, j) is stored at data[i*nReservedColumns + j]. The reserved but unused 36 /// columns always have all zero values. The reserved rows are just reserved 37 /// space in the underlying SmallVector's capacity. 38 /// This class only works for the types DynamicAPInt and Fraction, since the 39 /// method implementations are in the Matrix.cpp file. Only these two types have 40 /// been explicitly instantiated there. 41 template <typename T> 42 class Matrix { 43 static_assert(std::is_same_v<T, DynamicAPInt> || std::is_same_v<T, Fraction>, 44 "T must be DynamicAPInt or Fraction."); 45 46 public: 47 Matrix() = delete; 48 49 /// Construct a matrix with the specified number of rows and columns. 50 /// The number of reserved rows and columns will be at least the number 51 /// specified, and will always be sufficient to accomodate the number of rows 52 /// and columns specified. 53 /// 54 /// Initially, the entries are initialized to ero. 55 Matrix(unsigned rows, unsigned columns, unsigned reservedRows = 0, 56 unsigned reservedColumns = 0); 57 58 /// Return the identity matrix of the specified dimension. 59 static Matrix identity(unsigned dimension); 60 61 /// Access the element at the specified row and column. at(unsigned row,unsigned column)62 T &at(unsigned row, unsigned column) { 63 assert(row < nRows && "Row outside of range"); 64 assert(column < nColumns && "Column outside of range"); 65 return data[row * nReservedColumns + column]; 66 } 67 at(unsigned row,unsigned column)68 T at(unsigned row, unsigned column) const { 69 assert(row < nRows && "Row outside of range"); 70 assert(column < nColumns && "Column outside of range"); 71 return data[row * nReservedColumns + column]; 72 } 73 operator()74 T &operator()(unsigned row, unsigned column) { return at(row, column); } 75 operator()76 T operator()(unsigned row, unsigned column) const { return at(row, column); } 77 78 bool operator==(const Matrix<T> &m) const; 79 80 /// Swap the given columns. 81 void swapColumns(unsigned column, unsigned otherColumn); 82 83 /// Swap the given rows. 84 void swapRows(unsigned row, unsigned otherRow); 85 getNumRows()86 unsigned getNumRows() const { return nRows; } 87 getNumColumns()88 unsigned getNumColumns() const { return nColumns; } 89 90 /// Return the maximum number of rows/columns that can be added without 91 /// incurring a reallocation. 92 unsigned getNumReservedRows() const; getNumReservedColumns()93 unsigned getNumReservedColumns() const { return nReservedColumns; } 94 95 /// Reserve enough space to resize to the specified number of rows without 96 /// reallocations. 97 void reserveRows(unsigned rows); 98 99 /// Get a [Mutable]ArrayRef corresponding to the specified row. 100 MutableArrayRef<T> getRow(unsigned row); 101 ArrayRef<T> getRow(unsigned row) const; 102 103 /// Set the specified row to `elems`. 104 void setRow(unsigned row, ArrayRef<T> elems); 105 106 /// Insert columns having positions pos, pos + 1, ... pos + count - 1. 107 /// Columns that were at positions 0 to pos - 1 will stay where they are; 108 /// columns that were at positions pos to nColumns - 1 will be pushed to the 109 /// right. pos should be at most nColumns. 110 void insertColumns(unsigned pos, unsigned count); 111 void insertColumn(unsigned pos); 112 113 /// Insert rows having positions pos, pos + 1, ... pos + count - 1. 114 /// Rows that were at positions 0 to pos - 1 will stay where they are; 115 /// rows that were at positions pos to nColumns - 1 will be pushed to the 116 /// right. pos should be at most nRows. 117 void insertRows(unsigned pos, unsigned count); 118 void insertRow(unsigned pos); 119 120 /// Remove the columns having positions pos, pos + 1, ... pos + count - 1. 121 /// Rows that were at positions 0 to pos - 1 will stay where they are; 122 /// columns that were at positions pos + count - 1 or later will be pushed to 123 /// the right. The columns to be deleted must be valid rows: pos + count - 1 124 /// must be at most nColumns - 1. 125 void removeColumns(unsigned pos, unsigned count); 126 void removeColumn(unsigned pos); 127 128 /// Remove the rows having positions pos, pos + 1, ... pos + count - 1. 129 /// Rows that were at positions 0 to pos - 1 will stay where they are; 130 /// rows that were at positions pos + count - 1 or later will be pushed to the 131 /// right. The rows to be deleted must be valid rows: pos + count - 1 must be 132 /// at most nRows - 1. 133 void removeRows(unsigned pos, unsigned count); 134 void removeRow(unsigned pos); 135 136 void copyRow(unsigned sourceRow, unsigned targetRow); 137 138 void fillRow(unsigned row, const T &value); fillRow(unsigned row,int64_t value)139 void fillRow(unsigned row, int64_t value) { fillRow(row, T(value)); } 140 141 /// Add `scale` multiples of the source row to the target row. 142 void addToRow(unsigned sourceRow, unsigned targetRow, const T &scale); addToRow(unsigned sourceRow,unsigned targetRow,int64_t scale)143 void addToRow(unsigned sourceRow, unsigned targetRow, int64_t scale) { 144 addToRow(sourceRow, targetRow, T(scale)); 145 } 146 /// Add `scale` multiples of the rowVec row to the specified row. 147 void addToRow(unsigned row, ArrayRef<T> rowVec, const T &scale); 148 149 /// Multiply the specified row by a factor of `scale`. 150 void scaleRow(unsigned row, const T &scale); 151 152 /// Add `scale` multiples of the source column to the target column. 153 void addToColumn(unsigned sourceColumn, unsigned targetColumn, 154 const T &scale); addToColumn(unsigned sourceColumn,unsigned targetColumn,int64_t scale)155 void addToColumn(unsigned sourceColumn, unsigned targetColumn, 156 int64_t scale) { 157 addToColumn(sourceColumn, targetColumn, T(scale)); 158 } 159 160 /// Negate the specified column. 161 void negateColumn(unsigned column); 162 163 /// Negate the specified row. 164 void negateRow(unsigned row); 165 166 /// Negate the entire matrix. 167 void negateMatrix(); 168 169 /// The given vector is interpreted as a row vector v. Post-multiply v with 170 /// this matrix, say M, and return vM. 171 SmallVector<T, 8> preMultiplyWithRow(ArrayRef<T> rowVec) const; 172 173 /// The given vector is interpreted as a column vector v. Pre-multiply v with 174 /// this matrix, say M, and return Mv. 175 SmallVector<T, 8> postMultiplyWithColumn(ArrayRef<T> colVec) const; 176 177 /// Resize the matrix to the specified dimensions. If a dimension is smaller, 178 /// the values are truncated; if it is bigger, the new values are initialized 179 /// to zero. 180 /// 181 /// Due to the representation of the matrix, resizing vertically (adding rows) 182 /// is less expensive than increasing the number of columns beyond 183 /// nReservedColumns. 184 void resize(unsigned newNRows, unsigned newNColumns); 185 void resizeHorizontally(unsigned newNColumns); 186 void resizeVertically(unsigned newNRows); 187 188 /// Add an extra row at the bottom of the matrix and return its position. 189 unsigned appendExtraRow(); 190 /// Same as above, but copy the given elements into the row. The length of 191 /// `elems` must be equal to the number of columns. 192 unsigned appendExtraRow(ArrayRef<T> elems); 193 194 // Transpose the matrix without modifying it. 195 Matrix<T> transpose() const; 196 197 // Copy the cells in the intersection of 198 // the rows between `fromRows` and `toRows` and 199 // the columns between `fromColumns` and `toColumns`, both inclusive. 200 Matrix<T> getSubMatrix(unsigned fromRow, unsigned toRow, unsigned fromColumn, 201 unsigned toColumn) const; 202 203 /// Split the rows of a matrix into two matrices according to which bits are 204 /// 1 and which are 0 in a given bitset. 205 /// 206 /// The first matrix returned has the rows corresponding to 1 and the second 207 /// corresponding to 2. 208 std::pair<Matrix<T>, Matrix<T>> splitByBitset(ArrayRef<int> indicator); 209 210 /// Print the matrix. 211 void print(raw_ostream &os) const; 212 void dump() const; 213 214 /// Return whether the Matrix is in a consistent state with all its 215 /// invariants satisfied. 216 bool hasConsistentState() const; 217 218 /// Move the columns in the source range [srcPos, srcPos + num) to the 219 /// specified destination [dstPos, dstPos + num), while moving the columns 220 /// adjacent to the source range to the left/right of the shifted columns. 221 /// 222 /// When moving the source columns right (i.e. dstPos > srcPos), columns that 223 /// were at positions [0, srcPos) and [dstPos + num, nCols) will stay where 224 /// they are; columns that were at positions [srcPos, srcPos + num) will be 225 /// moved to [dstPos, dstPos + num); and columns that were at positions 226 /// [srcPos + num, dstPos + num) will be moved to [srcPos, dstPos). 227 /// Equivalently, the columns [srcPos + num, dstPos + num) are interchanged 228 /// with [srcPos, srcPos + num). 229 /// For example, if m = |0 1 2 3 4 5| then: 230 /// m.moveColumns(1, 3, 2) will result in m = |0 4 1 2 3 5|; or 231 /// m.moveColumns(1, 2, 4) will result in m = |0 3 4 5 1 2|. 232 /// 233 /// The left shift operation (i.e. dstPos < srcPos) works in a similar way. 234 void moveColumns(unsigned srcPos, unsigned num, unsigned dstPos); 235 236 protected: 237 /// The current number of rows, columns, and reserved columns. The underlying 238 /// data vector is viewed as an nRows x nReservedColumns matrix, of which the 239 /// first nColumns columns are currently in use, and the remaining are 240 /// reserved columns filled with zeros. 241 unsigned nRows, nColumns, nReservedColumns; 242 243 /// Stores the data. data.size() is equal to nRows * nReservedColumns. 244 /// data.capacity() / nReservedColumns is the number of reserved rows. 245 SmallVector<T, 16> data; 246 }; 247 248 extern template class Matrix<DynamicAPInt>; 249 extern template class Matrix<Fraction>; 250 251 // An inherited class for integer matrices, with no new data attributes. 252 // This is only used for the matrix-related methods which apply only 253 // to integers (hermite normal form computation and row normalisation). 254 class IntMatrix : public Matrix<DynamicAPInt> { 255 public: 256 IntMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0, 257 unsigned reservedColumns = 0) 258 : Matrix<DynamicAPInt>(rows, columns, reservedRows, reservedColumns) {} 259 IntMatrix(Matrix<DynamicAPInt> m)260 IntMatrix(Matrix<DynamicAPInt> m) : Matrix<DynamicAPInt>(std::move(m)) {} 261 262 /// Return the identity matrix of the specified dimension. 263 static IntMatrix identity(unsigned dimension); 264 265 /// Given the current matrix M, returns the matrices H, U such that H is the 266 /// column hermite normal form of M, i.e. H = M * U, where U is unimodular and 267 /// the matrix H has the following restrictions: 268 /// - H is lower triangular. 269 /// - The leading coefficient (the first non-zero entry from the top, called 270 /// the pivot) of a non-zero column is always strictly below of the leading 271 /// coefficient of the column before it; moreover, it is positive. 272 /// - The elements to the right of the pivots are zero and the elements to 273 /// the left of the pivots are nonnegative and strictly smaller than the 274 /// pivot. 275 std::pair<IntMatrix, IntMatrix> computeHermiteNormalForm() const; 276 277 /// Divide the first `nCols` of the specified row by their GCD. 278 /// Returns the GCD of the first `nCols` of the specified row. 279 DynamicAPInt normalizeRow(unsigned row, unsigned nCols); 280 /// Divide the columns of the specified row by their GCD. 281 /// Returns the GCD of the columns of the specified row. 282 DynamicAPInt normalizeRow(unsigned row); 283 284 // Compute the determinant of the matrix (cubic time). 285 // Stores the integer inverse of the matrix in the pointer 286 // passed (if any). The pointer is unchanged if the inverse 287 // does not exist, which happens iff det = 0. 288 // For a matrix M, the integer inverse is the matrix M' such that 289 // M x M' = M' M = det(M) x I. 290 // Assert-fails if the matrix is not square. 291 DynamicAPInt determinant(IntMatrix *inverse = nullptr) const; 292 }; 293 294 // An inherited class for rational matrices, with no new data attributes. 295 // This class is for functionality that only applies to matrices of fractions. 296 class FracMatrix : public Matrix<Fraction> { 297 public: 298 FracMatrix(unsigned rows, unsigned columns, unsigned reservedRows = 0, 299 unsigned reservedColumns = 0) 300 : Matrix<Fraction>(rows, columns, reservedRows, reservedColumns){}; 301 FracMatrix(Matrix<Fraction> m)302 FracMatrix(Matrix<Fraction> m) : Matrix<Fraction>(std::move(m)){}; 303 304 explicit FracMatrix(IntMatrix m); 305 306 /// Return the identity matrix of the specified dimension. 307 static FracMatrix identity(unsigned dimension); 308 309 // Compute the determinant of the matrix (cubic time). 310 // Stores the inverse of the matrix in the pointer 311 // passed (if any). The pointer is unchanged if the inverse 312 // does not exist, which happens iff det = 0. 313 // Assert-fails if the matrix is not square. 314 Fraction determinant(FracMatrix *inverse = nullptr) const; 315 316 // Computes the Gram-Schmidt orthogonalisation 317 // of the rows of matrix (cubic time). 318 // The rows of the matrix must be linearly independent. 319 FracMatrix gramSchmidt() const; 320 321 // Run LLL basis reduction on the matrix, modifying it in-place. 322 // The parameter is what [the original 323 // paper](https://www.cs.cmu.edu/~avrim/451f11/lectures/lect1129_LLL.pdf) 324 // calls `y`, usually 3/4. 325 void LLL(Fraction delta); 326 327 // Multiply each row of the matrix by the LCM of the denominators, thereby 328 // converting it to an integer matrix. 329 IntMatrix normalizeRows() const; 330 }; 331 332 } // namespace presburger 333 } // namespace mlir 334 335 #endif // MLIR_ANALYSIS_PRESBURGER_MATRIX_H 336