Skip to content

Commit 850f713

Browse files
[MLIR][Presburger] Helper functions to compute the constant term of a generating function (#77819)
We implement two functions that are needed to compute the constant term of a GF. One finds a vector not orthogonal to all the non-null vectors in a given set. One computes the coefficient of any term in an arbitrary rational function (quotient of two polynomials).
1 parent 01ddc0e commit 850f713

File tree

5 files changed

+174
-0
lines changed

5 files changed

+174
-0
lines changed

mlir/include/mlir/Analysis/Presburger/Barvinok.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "mlir/Analysis/Presburger/GeneratingFunction.h"
2828
#include "mlir/Analysis/Presburger/IntegerRelation.h"
2929
#include "mlir/Analysis/Presburger/Matrix.h"
30+
#include "mlir/Analysis/Presburger/QuasiPolynomial.h"
3031
#include <optional>
3132

3233
namespace mlir {
@@ -83,6 +84,21 @@ ConeH getDual(ConeV cone);
8384
GeneratingFunction unimodularConeGeneratingFunction(ParamPoint vertex, int sign,
8485
ConeH cone);
8586

87+
/// Find a vector that is not orthogonal to any of the given vectors,
88+
/// i.e., has nonzero dot product with those of the given vectors
89+
/// that are not null.
90+
/// If any of the vectors is null, it is ignored.
91+
Point getNonOrthogonalVector(ArrayRef<Point> vectors);
92+
93+
/// Find the coefficient of a given power of s in a rational function
94+
/// given by P(s)/Q(s), where
95+
/// P is a polynomial, in which the coefficients are QuasiPolynomials
96+
/// over d parameters (distinct from s), and
97+
/// and Q is a polynomial with Fraction coefficients.
98+
QuasiPolynomial getCoefficientInRationalFunction(unsigned power,
99+
ArrayRef<QuasiPolynomial> num,
100+
ArrayRef<Fraction> den);
101+
86102
} // namespace detail
87103
} // namespace presburger
88104
} // namespace mlir

mlir/include/mlir/Analysis/Presburger/QuasiPolynomial.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class QuasiPolynomial : public PresburgerSpace {
3939
QuasiPolynomial(unsigned numVars, SmallVector<Fraction> coeffs = {},
4040
std::vector<std::vector<SmallVector<Fraction>>> aff = {});
4141

42+
QuasiPolynomial(unsigned numVars, Fraction constant);
43+
4244
// Find the number of inputs (numDomain) to the polynomial.
4345
// numSymbols is set to zero.
4446
unsigned getNumInputs() const {
@@ -60,6 +62,8 @@ class QuasiPolynomial : public PresburgerSpace {
6062
// Removes terms which evaluate to zero from the expression.
6163
QuasiPolynomial simplify();
6264

65+
Fraction getConstantTerm();
66+
6367
private:
6468
SmallVector<Fraction> coefficients;
6569
std::vector<std::vector<SmallVector<Fraction>>> affine;

mlir/lib/Analysis/Presburger/Barvinok.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "mlir/Analysis/Presburger/Barvinok.h"
1010
#include "llvm/ADT/Sequence.h"
11+
#include <algorithm>
1112

1213
using namespace mlir;
1314
using namespace presburger;
@@ -144,3 +145,100 @@ GeneratingFunction mlir::presburger::detail::unimodularConeGeneratingFunction(
144145
std::vector({numerator}),
145146
std::vector({denominator}));
146147
}
148+
149+
/// We use an iterative procedure to find a vector not orthogonal
150+
/// to a given set, ignoring the null vectors.
151+
/// Let the inputs be {x_1, ..., x_k}, all vectors of length n.
152+
///
153+
/// In the following,
154+
/// vs[:i] means the elements of vs up to and including the i'th one,
155+
/// <vs, us> means the dot product of vs and us,
156+
/// vs ++ [v] means the vector vs with the new element v appended to it.
157+
///
158+
/// We proceed iteratively; for steps d = 0, ... n-1, we construct a vector
159+
/// which is not orthogonal to any of {x_1[:d], ..., x_n[:d]}, ignoring
160+
/// the null vectors.
161+
/// At step d = 0, we let vs = [1]. Clearly this is not orthogonal to
162+
/// any vector in the set {x_1[0], ..., x_n[0]}, except the null ones,
163+
/// which we ignore.
164+
/// At step d > 0 , we need a number v
165+
/// s.t. <x_i[:d], vs++[v]> != 0 for all i.
166+
/// => <x_i[:d-1], vs> + x_i[d]*v != 0
167+
/// => v != - <x_i[:d-1], vs> / x_i[d]
168+
/// We compute this value for all x_i, and then
169+
/// set v to be the maximum element of this set plus one. Thus
170+
/// v is outside the set as desired, and we append it to vs
171+
/// to obtain the result of the d'th step.
172+
Point mlir::presburger::detail::getNonOrthogonalVector(
173+
ArrayRef<Point> vectors) {
174+
unsigned dim = vectors[0].size();
175+
for (const Point &vector : vectors)
176+
assert(vector.size() == dim && "all vectors need to be the same size!");
177+
178+
SmallVector<Fraction> newPoint = {Fraction(1, 1)};
179+
Fraction maxDisallowedValue = -Fraction(1, 0),
180+
disallowedValue = Fraction(0, 1);
181+
182+
for (unsigned d = 1; d < dim; ++d) {
183+
// Compute the disallowed values - <x_i[:d-1], vs> / x_i[d] for each i.
184+
maxDisallowedValue = -Fraction(1, 0);
185+
for (const Point &vector : vectors) {
186+
if (vector[d] == 0)
187+
continue;
188+
disallowedValue =
189+
-dotProduct(ArrayRef(vector).slice(0, d), newPoint) / vector[d];
190+
191+
// Find the biggest such value
192+
maxDisallowedValue = std::max(maxDisallowedValue, disallowedValue);
193+
}
194+
newPoint.push_back(maxDisallowedValue + 1);
195+
}
196+
return newPoint;
197+
}
198+
199+
/// We use the following recursive formula to find the coefficient of
200+
/// s^power in the rational function given by P(s)/Q(s).
201+
///
202+
/// Let P[i] denote the coefficient of s^i in the polynomial P(s).
203+
/// (P/Q)[r] =
204+
/// if (r == 0) then
205+
/// P[0]/Q[0]
206+
/// else
207+
/// (P[r] - {Σ_{i=1}^r (P/Q)[r-i] * Q[i])}/(Q[0])
208+
/// We therefore recursively call `getCoefficientInRationalFunction` on
209+
/// all i \in [0, power).
210+
///
211+
/// https://math.ucdavis.edu/~deloera/researchsummary/
212+
/// barvinokalgorithm-latte1.pdf, p. 1285
213+
QuasiPolynomial mlir::presburger::detail::getCoefficientInRationalFunction(
214+
unsigned power, ArrayRef<QuasiPolynomial> num, ArrayRef<Fraction> den) {
215+
assert(den.size() != 0 &&
216+
"division by empty denominator in rational function!");
217+
218+
unsigned numParam = num[0].getNumInputs();
219+
for (const QuasiPolynomial &qp : num)
220+
// We use the `isEqual` method of PresburgerSpace, which QuasiPolynomial
221+
// inherits from.
222+
assert(num[0].isEqual(qp) &&
223+
"the quasipolynomials should all belong to the same space!");
224+
225+
std::vector<QuasiPolynomial> coefficients;
226+
coefficients.reserve(power + 1);
227+
228+
coefficients.push_back(num[0] / den[0]);
229+
for (unsigned i = 1; i <= power; ++i) {
230+
// If the power is not there in the numerator, the coefficient is zero.
231+
coefficients.push_back(i < num.size() ? num[i]
232+
: QuasiPolynomial(numParam, 0));
233+
234+
// After den.size(), the coefficients are zero, so we stop
235+
// subtracting at that point (if it is less than i).
236+
unsigned limit = std::min<unsigned long>(i, den.size() - 1);
237+
for (unsigned j = 1; j <= limit; ++j)
238+
coefficients[i] = coefficients[i] -
239+
coefficients[i - j] * QuasiPolynomial(numParam, den[j]);
240+
241+
coefficients[i] = coefficients[i] / den[0];
242+
}
243+
return coefficients[power].simplify();
244+
}

mlir/lib/Analysis/Presburger/QuasiPolynomial.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ QuasiPolynomial::QuasiPolynomial(
3636
#endif // NDEBUG
3737
}
3838

39+
/// Define a quasipolynomial which is a single constant.
40+
QuasiPolynomial::QuasiPolynomial(unsigned numVars, Fraction constant)
41+
: PresburgerSpace(/*numDomain=*/numVars, /*numRange=*/1, /*numSymbols=*/0,
42+
/*numLocals=*/0),
43+
coefficients({constant}), affine({{}}) {}
44+
3945
QuasiPolynomial QuasiPolynomial::operator+(const QuasiPolynomial &x) const {
4046
assert(getNumInputs() == x.getNumInputs() &&
4147
"two quasi-polynomials with different numbers of symbols cannot "
@@ -113,3 +119,11 @@ QuasiPolynomial QuasiPolynomial::simplify() {
113119
}
114120
return QuasiPolynomial(getNumInputs(), newCoeffs, newAffine);
115121
}
122+
123+
Fraction QuasiPolynomial::getConstantTerm() {
124+
Fraction constTerm = 0;
125+
for (unsigned i = 0, e = coefficients.size(); i < e; ++i)
126+
if (affine[i].size() == 0)
127+
constTerm += coefficients[i];
128+
return constTerm;
129+
}

mlir/unittests/Analysis/Presburger/BarvinokTest.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,45 @@ TEST(BarvinokTest, unimodularConeGeneratingFunction) {
8282
1, {1}, {makeFracMatrix(2, 3, {{-83, -100, -41}, {-22, -27, -15}})},
8383
{{{8, 47, -17}, {-7, -41, 15}, {1, 5, -2}}}));
8484
}
85+
86+
// The following vectors are randomly generated.
87+
// We then check that the output of the function has non-zero
88+
// dot product with all non-null vectors.
89+
TEST(BarvinokTest, getNonOrthogonalVector) {
90+
std::vector<Point> vectors = {Point({1, 2, 3, 4}), Point({-1, 0, 1, 1}),
91+
Point({2, 7, 0, 0}), Point({0, 0, 0, 0})};
92+
Point nonOrth = getNonOrthogonalVector(vectors);
93+
94+
for (unsigned i = 0; i < 3; i++)
95+
EXPECT_NE(dotProduct(nonOrth, vectors[i]), 0);
96+
97+
vectors = {Point({0, 1, 3}), Point({-2, -1, 1}), Point({6, 3, 0}),
98+
Point({0, 0, -3}), Point({5, 0, -1})};
99+
nonOrth = getNonOrthogonalVector(vectors);
100+
101+
for (const Point &vector : vectors)
102+
EXPECT_NE(dotProduct(nonOrth, vector), 0);
103+
}
104+
105+
// The following polynomials are randomly generated and the
106+
// coefficients are computed by hand.
107+
// Although the function allows the coefficients of the numerator
108+
// to be arbitrary quasipolynomials, we stick to constants for simplicity,
109+
// as the relevant arithmetic operations on quasipolynomials
110+
// are tested separately.
111+
TEST(BarvinokTest, getCoefficientInRationalFunction) {
112+
std::vector<QuasiPolynomial> numerator = {
113+
QuasiPolynomial(0, 2), QuasiPolynomial(0, 3), QuasiPolynomial(0, 5)};
114+
std::vector<Fraction> denominator = {Fraction(1), Fraction(0), Fraction(4),
115+
Fraction(3)};
116+
QuasiPolynomial coeff =
117+
getCoefficientInRationalFunction(1, numerator, denominator);
118+
EXPECT_EQ(coeff.getConstantTerm(), 3);
119+
120+
numerator = {QuasiPolynomial(0, -1), QuasiPolynomial(0, 4),
121+
QuasiPolynomial(0, -2), QuasiPolynomial(0, 5),
122+
QuasiPolynomial(0, 6)};
123+
denominator = {Fraction(8), Fraction(4), Fraction(0), Fraction(-2)};
124+
coeff = getCoefficientInRationalFunction(3, numerator, denominator);
125+
EXPECT_EQ(coeff.getConstantTerm(), Fraction(55, 64));
126+
}

0 commit comments

Comments
 (0)