xref: /llvm-project/mlir/include/mlir/Analysis/Presburger/Matrix.h (revision d0fee98e0cd6afa116bed90c47cc33bbf8d7b867)
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