• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===- Simplex.h - MLIR Simplex 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 // Functionality to perform analysis on FlatAffineConstraints. In particular,
10 // support for performing emptiness checks and redundancy checks.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef MLIR_ANALYSIS_PRESBURGER_SIMPLEX_H
15 #define MLIR_ANALYSIS_PRESBURGER_SIMPLEX_H
16 
17 #include "mlir/Analysis/AffineStructures.h"
18 #include "mlir/Analysis/Presburger/Fraction.h"
19 #include "mlir/Analysis/Presburger/Matrix.h"
20 #include "mlir/Support/LogicalResult.h"
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/Optional.h"
23 #include "llvm/ADT/SmallVector.h"
24 #include "llvm/Support/raw_ostream.h"
25 
26 namespace mlir {
27 
28 class GBRSimplex;
29 
30 /// This class implements a version of the Simplex and Generalized Basis
31 /// Reduction algorithms, which can perform analysis of integer sets with affine
32 /// inequalities and equalities. A Simplex can be constructed
33 /// by specifying the dimensionality of the set. It supports adding affine
34 /// inequalities and equalities, and can perform emptiness checks, i.e., it can
35 /// find a solution to the set of constraints if one exists, or say that the
36 /// set is empty if no solution exists. Currently, this only works for bounded
37 /// sets. Furthermore, it can find a subset of these constraints that are
38 /// redundant, i.e. a subset of constraints that doesn't constrain the affine
39 /// set further after adding the non-redundant constraints. Simplex can also be
40 /// constructed from a FlatAffineConstraints object.
41 ///
42 /// The implementation of this Simplex class, other than the functionality
43 /// for sampling, is based on the paper
44 /// "Simplify: A Theorem Prover for Program Checking"
45 /// by D. Detlefs, G. Nelson, J. B. Saxe.
46 ///
47 /// We define variables, constraints, and unknowns. Consider the example of a
48 /// two-dimensional set defined by 1 + 2x + 3y >= 0 and 2x - 3y >= 0. Here,
49 /// x, y, are variables while 1 + 2x + 3y >= 0, 2x - 3y >= 0 are
50 /// constraints. Unknowns are either variables or constraints, i.e., x, y,
51 /// 1 + 2x + 3y >= 0, 2x - 3y >= 0 are all unknowns.
52 ///
53 /// The implementation involves a matrix called a tableau, which can be thought
54 /// of as a 2D matrix of rational numbers having number of rows equal to the
55 /// number of constraints and number of columns equal to one plus the number of
56 /// variables. In our implementation, instead of storing rational numbers, we
57 /// store a common denominator for each row, so it is in fact a matrix of
58 /// integers with number of rows equal to number of constraints and number of
59 /// columns equal to _two_ plus the number of variables. For example, instead of
60 /// storing a row of three rationals [1/2, 2/3, 3], we would store [6, 3, 4, 18]
61 /// since 3/6 = 1/2, 4/6 = 2/3, and 18/6 = 3.
62 ///
63 /// Every row and column except the first and second columns is associated with
64 /// an unknown and every unknown is associated with a row or column. The second
65 /// column represents the constant, explained in more detail below. An unknown
66 /// associated with a row or column is said to be in row or column position
67 /// respectively.
68 ///
69 /// The vectors var and con store information about the variables and
70 /// constraints respectively, namely, whether they are in row or column
71 /// position, which row or column they are associated with, and whether they
72 /// correspond to a variable or a constraint.
73 ///
74 /// An unknown is addressed by its index. If the index i is non-negative, then
75 /// the variable var[i] is being addressed. If the index i is negative, then
76 /// the constraint con[~i] is being addressed. Effectively this maps
77 /// 0 -> var[0], 1 -> var[1], -1 -> con[0], -2 -> con[1], etc. rowUnknown[r] and
78 /// colUnknown[c] are the indexes of the unknowns associated with row r and
79 /// column c, respectively.
80 ///
81 /// The unknowns in column position are together called the basis. Initially the
82 /// basis is the set of variables -- in our example above, the initial basis is
83 /// x, y.
84 ///
85 /// The unknowns in row position are represented in terms of the basis unknowns.
86 /// If the basis unknowns are u_1, u_2, ... u_m, and a row in the tableau is
87 /// d, c, a_1, a_2, ... a_m, this representats the unknown for that row as
88 /// (c + a_1*u_1 + a_2*u_2 + ... + a_m*u_m)/d. In our running example, if the
89 /// basis is the initial basis of x, y, then the constraint 1 + 2x + 3y >= 0
90 /// would be represented by the row [1, 1, 2, 3].
91 ///
92 /// The association of unknowns to rows and columns can be changed by a process
93 /// called pivoting, where a row unknown and a column unknown exchange places
94 /// and the remaining row variables' representation is changed accordingly
95 /// by eliminating the old column unknown in favour of the new column unknown.
96 /// If we had pivoted the column for x with the row for 2x - 3y >= 0,
97 /// the new row for x would be [2, 1, 3] since x = (1*(2x - 3y) + 3*y)/2.
98 /// See the documentation for the pivot member function for details.
99 ///
100 /// The association of unknowns to rows and columns is called the _tableau
101 /// configuration_. The _sample value_ of an unknown in a particular tableau
102 /// configuration is its value if all the column unknowns were set to zero.
103 /// Concretely, for unknowns in column position the sample value is zero and
104 /// for unknowns in row position the sample value is the constant term divided
105 /// by the common denominator.
106 ///
107 /// The tableau configuration is called _consistent_ if the sample value of all
108 /// restricted unknowns is non-negative. Initially there are no constraints, and
109 /// the tableau is consistent. When a new constraint is added, its sample value
110 /// in the current tableau configuration may be negative. In that case, we try
111 /// to find a series of pivots to bring us to a consistent tableau
112 /// configuration, i.e. we try to make the new constraint's sample value
113 /// non-negative without making that of any other constraints negative. (See
114 /// findPivot and findPivotRow for details.) If this is not possible, then the
115 /// set of constraints is mutually contradictory and the tableau is marked
116 /// _empty_, which means the set of constraints has no solution.
117 ///
118 /// The Simplex class supports redundancy checking via detectRedundant and
119 /// isMarkedRedundant. A redundant constraint is one which is never violated as
120 /// long as the other constraints are not violated, i.e., removing a redundant
121 /// constraint does not change the set of solutions to the constraints. As a
122 /// heuristic, constraints that have been marked redundant can be ignored for
123 /// most operations. Therefore, these constraints are kept in rows 0 to
124 /// nRedundant - 1, where nRedundant is a member variable that tracks the number
125 /// of constraints that have been marked redundant.
126 ///
127 /// This Simplex class also supports taking snapshots of the current state
128 /// and rolling back to prior snapshots. This works by maintaining an undo log
129 /// of operations. Snapshots are just pointers to a particular location in the
130 /// log, and rolling back to a snapshot is done by reverting each log entry's
131 /// operation from the end until we reach the snapshot's location.
132 ///
133 /// Finding an integer sample is done with the Generalized Basis Reduction
134 /// algorithm. See the documentation for findIntegerSample and reduceBasis.
135 class Simplex {
136 public:
137   enum class Direction { Up, Down };
138 
139   Simplex() = delete;
140   explicit Simplex(unsigned nVar);
141   explicit Simplex(const FlatAffineConstraints &constraints);
142 
143   /// Returns true if the tableau is empty (has conflicting constraints),
144   /// false otherwise.
145   bool isEmpty() const;
146 
147   /// Add an inequality to the tableau. If coeffs is c_0, c_1, ... c_n, where n
148   /// is the current number of variables, then the corresponding inequality is
149   /// c_n + c_0*x_0 + c_1*x_1 + ... + c_{n-1}*x_{n-1} >= 0.
150   void addInequality(ArrayRef<int64_t> coeffs);
151 
152   /// Returns the number of variables in the tableau.
153   unsigned numVariables() const;
154 
155   /// Returns the number of constraints in the tableau.
156   unsigned numConstraints() const;
157 
158   /// Add an equality to the tableau. If coeffs is c_0, c_1, ... c_n, where n
159   /// is the current number of variables, then the corresponding equality is
160   /// c_n + c_0*x_0 + c_1*x_1 + ... + c_{n-1}*x_{n-1} == 0.
161   void addEquality(ArrayRef<int64_t> coeffs);
162 
163   /// Mark the tableau as being empty.
164   void markEmpty();
165 
166   /// Get a snapshot of the current state. This is used for rolling back.
167   unsigned getSnapshot() const;
168 
169   /// Rollback to a snapshot. This invalidates all later snapshots.
170   void rollback(unsigned snapshot);
171 
172   /// Add all the constraints from the given FlatAffineConstraints.
173   void intersectFlatAffineConstraints(const FlatAffineConstraints &fac);
174 
175   /// Compute the maximum or minimum value of the given row, depending on
176   /// direction. The specified row is never pivoted.
177   ///
178   /// Returns a (num, den) pair denoting the optimum, or None if no
179   /// optimum exists, i.e., if the expression is unbounded in this direction.
180   Optional<Fraction> computeRowOptimum(Direction direction, unsigned row);
181 
182   /// Compute the maximum or minimum value of the given expression, depending on
183   /// direction.
184   ///
185   /// Returns a (num, den) pair denoting the optimum, or a null value if no
186   /// optimum exists, i.e., if the expression is unbounded in this direction.
187   Optional<Fraction> computeOptimum(Direction direction,
188                                     ArrayRef<int64_t> coeffs);
189 
190   /// Returns whether the specified constraint has been marked as redundant.
191   /// Constraints are numbered from 0 starting at the first added inequality.
192   /// Equalities are added as a pair of inequalities and so correspond to two
193   /// inequalities with successive indices.
194   bool isMarkedRedundant(unsigned constraintIndex) const;
195 
196   /// Finds a subset of constraints that is redundant, i.e., such that
197   /// the set of solutions does not change if these constraints are removed.
198   /// Marks these constraints as redundant. Whether a specific constraint has
199   /// been marked redundant can be queried using isMarkedRedundant.
200   void detectRedundant();
201 
202   /// Returns a (min, max) pair denoting the minimum and maximum integer values
203   /// of the given expression.
204   std::pair<int64_t, int64_t> computeIntegerBounds(ArrayRef<int64_t> coeffs);
205 
206   /// Returns true if the polytope is unbounded, i.e., extends to infinity in
207   /// some direction. Otherwise, returns false.
208   bool isUnbounded();
209 
210   /// Make a tableau to represent a pair of points in the given tableaus, one in
211   /// tableau A and one in B.
212   static Simplex makeProduct(const Simplex &a, const Simplex &b);
213 
214   /// Returns the current sample point if it is integral. Otherwise, returns an
215   /// None.
216   Optional<SmallVector<int64_t, 8>> getSamplePointIfIntegral() const;
217 
218   /// Returns an integer sample point if one exists, or None
219   /// otherwise. This should only be called for bounded sets.
220   Optional<SmallVector<int64_t, 8>> findIntegerSample();
221 
222   /// Print the tableau's internal state.
223   void print(raw_ostream &os) const;
224   void dump() const;
225 
226 private:
227   friend class GBRSimplex;
228 
229   enum class Orientation { Row, Column };
230 
231   /// An Unknown is either a variable or a constraint. It is always associated
232   /// with either a row or column. Whether it's a row or a column is specified
233   /// by the orientation and pos identifies the specific row or column it is
234   /// associated with. If the unknown is restricted, then it has a
235   /// non-negativity constraint associated with it, i.e., its sample value must
236   /// always be non-negative and if it cannot be made non-negative without
237   /// violating other constraints, the tableau is empty.
238   struct Unknown {
UnknownUnknown239     Unknown(Orientation oOrientation, bool oRestricted, unsigned oPos)
240         : pos(oPos), orientation(oOrientation), restricted(oRestricted) {}
241     unsigned pos;
242     Orientation orientation;
243     bool restricted : 1;
244 
printUnknown245     void print(raw_ostream &os) const {
246       os << (orientation == Orientation::Row ? "r" : "c");
247       os << pos;
248       if (restricted)
249         os << " [>=0]";
250     }
251   };
252 
253   struct Pivot {
254     unsigned row, column;
255   };
256 
257   /// Find a pivot to change the sample value of row in the specified
258   /// direction. The returned pivot row will be row if and only
259   /// if the unknown is unbounded in the specified direction.
260   ///
261   /// Returns a (row, col) pair denoting a pivot, or an empty Optional if
262   /// no valid pivot exists.
263   Optional<Pivot> findPivot(int row, Direction direction) const;
264 
265   /// Swap the row with the column in the tableau's data structures but not the
266   /// tableau itself. This is used by pivot.
267   void swapRowWithCol(unsigned row, unsigned col);
268 
269   /// Pivot the row with the column.
270   void pivot(unsigned row, unsigned col);
271   void pivot(Pivot pair);
272 
273   /// Returns the unknown associated with index.
274   const Unknown &unknownFromIndex(int index) const;
275   /// Returns the unknown associated with col.
276   const Unknown &unknownFromColumn(unsigned col) const;
277   /// Returns the unknown associated with row.
278   const Unknown &unknownFromRow(unsigned row) const;
279   /// Returns the unknown associated with index.
280   Unknown &unknownFromIndex(int index);
281   /// Returns the unknown associated with col.
282   Unknown &unknownFromColumn(unsigned col);
283   /// Returns the unknown associated with row.
284   Unknown &unknownFromRow(unsigned row);
285 
286   /// Add a new row to the tableau and the associated data structures.
287   unsigned addRow(ArrayRef<int64_t> coeffs);
288 
289   /// Normalize the given row by removing common factors between the numerator
290   /// and the denominator.
291   void normalizeRow(unsigned row);
292 
293   /// Swap the two rows in the tableau and associated data structures.
294   void swapRows(unsigned i, unsigned j);
295 
296   /// Restore the unknown to a non-negative sample value.
297   ///
298   /// Returns true if the unknown was successfully restored to a non-negative
299   /// sample value, false otherwise.
300   LogicalResult restoreRow(Unknown &u);
301 
302   /// Mark the specified unknown redundant. This operation is added to the undo
303   /// log and will be undone by rollbacks. The specified unknown must be in row
304   /// orientation.
305   void markRowRedundant(Unknown &u);
306 
307   /// Enum to denote operations that need to be undone during rollback.
308   enum class UndoLogEntry {
309     RemoveLastConstraint,
310     UnmarkEmpty,
311     UnmarkLastRedundant
312   };
313 
314   /// Undo the operation represented by the log entry.
315   void undo(UndoLogEntry entry);
316 
317   /// Find a row that can be used to pivot the column in the specified
318   /// direction. If skipRow is not null, then this row is excluded
319   /// from consideration. The returned pivot will maintain all constraints
320   /// except the column itself and skipRow, if it is set. (if these unknowns
321   /// are restricted).
322   ///
323   /// Returns the row to pivot to, or an empty Optional if the column
324   /// is unbounded in the specified direction.
325   Optional<unsigned> findPivotRow(Optional<unsigned> skipRow,
326                                   Direction direction, unsigned col) const;
327 
328   /// Reduce the given basis, starting at the specified level, using general
329   /// basis reduction.
330   void reduceBasis(Matrix &basis, unsigned level);
331 
332   /// The number of rows in the tableau.
333   unsigned nRow;
334 
335   /// The number of columns in the tableau, including the common denominator
336   /// and the constant column.
337   unsigned nCol;
338 
339   /// The number of redundant rows in the tableau. These are the first
340   /// nRedundant rows.
341   unsigned nRedundant;
342 
343   /// The matrix representing the tableau.
344   Matrix tableau;
345 
346   /// This is true if the tableau has been detected to be empty, false
347   /// otherwise.
348   bool empty;
349 
350   /// Holds a log of operations, used for rolling back to a previous state.
351   SmallVector<UndoLogEntry, 8> undoLog;
352 
353   /// These hold the indexes of the unknown at a given row or column position.
354   /// We keep these as signed integers since that makes it convenient to check
355   /// if an index corresponds to a variable or a constraint by checking the
356   /// sign.
357   ///
358   /// colUnknown is padded with two null indexes at the front since the first
359   /// two columns don't correspond to any unknowns.
360   SmallVector<int, 8> rowUnknown, colUnknown;
361 
362   /// These hold information about each unknown.
363   SmallVector<Unknown, 8> con, var;
364 };
365 
366 } // namespace mlir
367 
368 #endif // MLIR_ANALYSIS_PRESBURGER_SIMPLEX_H
369