-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[MLIR][Presburger] Implement function to evaluate the number of terms in a generating function. #78078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MLIR][Presburger] Implement function to evaluate the number of terms in a generating function. #78078
Changes from all commits
1ec4bd3
f286b7b
19ade35
39dd6a8
64a55bc
b3331b2
f78ef76
06ca87a
92a73fc
aad6ca2
5205765
fc0dba7
d3e758e
ac6b038
94fe370
fd7879a
819ec57
eeb020c
0bda772
e811dfd
3b40fb5
0ef2159
f20bb63
98ae71e
3ab2f48
8b2c962
aa10c1f
5ebc95d
e6fae1d
b387fbf
afefb1b
ca40b83
64bf738
0c261a1
b609445
02c2ce3
65baf07
5289ffd
ff5f967
b919d7c
66bc0d2
44d9fc5
8c7e15b
f809eae
92d5937
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
#include "mlir/Analysis/Presburger/Barvinok.h" | ||
#include "mlir/Analysis/Presburger/Utils.h" | ||
#include "llvm/ADT/Sequence.h" | ||
#include <algorithm> | ||
|
||
|
@@ -245,3 +246,241 @@ QuasiPolynomial mlir::presburger::detail::getCoefficientInRationalFunction( | |
} | ||
return coefficients[power].simplify(); | ||
} | ||
|
||
/// Substitute x_i = t^μ_i in one term of a generating function, returning | ||
/// a quasipolynomial which represents the exponent of the numerator | ||
/// of the result, and a vector which represents the exponents of the | ||
/// denominator of the result. | ||
/// If the returned value is {num, dens}, it represents the function | ||
/// t^num / \prod_j (1 - t^dens[j]). | ||
/// v represents the affine functions whose floors are multiplied by the | ||
/// generators, and ds represents the list of generators. | ||
std::pair<QuasiPolynomial, std::vector<Fraction>> | ||
substituteMuInTerm(unsigned numParams, ParamPoint v, std::vector<Point> ds, | ||
Point mu) { | ||
unsigned numDims = mu.size(); | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (const Point &d : ds) | ||
assert(d.size() == numDims && | ||
"μ has to have the same number of dimensions as the generators!"); | ||
|
||
// First, the exponent in the numerator becomes | ||
// - (μ • u_1) * (floor(first col of v)) | ||
// - (μ • u_2) * (floor(second col of v)) - ... | ||
// - (μ • u_d) * (floor(d'th col of v)) | ||
// So we store the negation of the dot products. | ||
|
||
// We have d terms, each of whose coefficient is the negative dot product. | ||
SmallVector<Fraction> coefficients; | ||
coefficients.reserve(numDims); | ||
for (const Point &d : ds) | ||
coefficients.push_back(-dotProduct(mu, d)); | ||
|
||
// Then, the affine function is a single floor expression, given by the | ||
// corresponding column of v. | ||
ParamPoint vTranspose = v.transpose(); | ||
std::vector<std::vector<SmallVector<Fraction>>> affine; | ||
affine.reserve(numDims); | ||
for (unsigned j = 0; j < numDims; ++j) | ||
affine.push_back({SmallVector<Fraction>(vTranspose.getRow(j))}); | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
QuasiPolynomial num(numParams, coefficients, affine); | ||
num = num.simplify(); | ||
|
||
std::vector<Fraction> dens; | ||
dens.reserve(ds.size()); | ||
// Similarly, each term in the denominator has exponent | ||
// given by the dot product of μ with u_i. | ||
for (const Point &d : ds) { | ||
// This term in the denominator is | ||
// (1 - t^dens.back()) | ||
dens.push_back(dotProduct(d, mu)); | ||
} | ||
|
||
return {num, dens}; | ||
} | ||
|
||
/// Normalize all denominator exponents `dens` to their absolute values | ||
/// by multiplying and dividing by the inverses, in a function of the form | ||
/// sign * t^num / prod_j (1 - t^dens[j]). | ||
/// Here, sign = ± 1, | ||
/// num is a QuasiPolynomial, and | ||
/// each dens[j] is a Fraction. | ||
void normalizeDenominatorExponents(int &sign, QuasiPolynomial &num, | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::vector<Fraction> &dens) { | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// We track the number of exponents that are negative in the | ||
// denominator, and convert them to their absolute values. | ||
unsigned numNegExps = 0; | ||
Fraction sumNegExps(0, 1); | ||
for (unsigned j = 0, e = dens.size(); j < e; ++j) { | ||
if (dens[j] < 0) { | ||
numNegExps += 1; | ||
sumNegExps += dens[j]; | ||
} | ||
} | ||
|
||
// If we have (1 - t^-c) in the denominator, for positive c, | ||
// multiply and divide by t^c. | ||
// We convert all negative-exponent terms at once; therefore | ||
// we multiply and divide by t^sumNegExps. | ||
// Then we get | ||
// -(1 - t^c) in the denominator, | ||
// increase the numerator by c, and | ||
// flip the sign of the function. | ||
if (numNegExps % 2 == 1) | ||
sign = -sign; | ||
num = num - QuasiPolynomial(num.getNumInputs(), sumNegExps); | ||
} | ||
|
||
/// Compute the binomial coefficients nCi for 0 ≤ i ≤ r, | ||
/// where n is a QuasiPolynomial. | ||
std::vector<QuasiPolynomial> getBinomialCoefficients(QuasiPolynomial n, | ||
unsigned r) { | ||
unsigned numParams = n.getNumInputs(); | ||
std::vector<QuasiPolynomial> coefficients; | ||
coefficients.reserve(r + 1); | ||
coefficients.push_back(QuasiPolynomial(numParams, 1)); | ||
for (unsigned j = 1; j <= r; ++j) | ||
// We use the recursive formula for binomial coefficients here and below. | ||
coefficients.push_back( | ||
(coefficients[j - 1] * (n - QuasiPolynomial(numParams, j - 1)) / | ||
Fraction(j, 1)) | ||
.simplify()); | ||
return coefficients; | ||
} | ||
|
||
/// Compute the binomial coefficients nCi for 0 ≤ i ≤ r, | ||
/// where n is a QuasiPolynomial. | ||
std::vector<Fraction> getBinomialCoefficients(Fraction n, Fraction r) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes more sense for r to be an integer here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it already is; not sure why it showed you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it still says that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry my mistake There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is Fraction because we need to call it as getBinomialCoefficients(den + 1, den + 1) |
||
std::vector<Fraction> coefficients; | ||
coefficients.reserve((int64_t)floor(r)); | ||
coefficients.push_back(1); | ||
for (unsigned j = 1; j <= r; ++j) | ||
coefficients.push_back(coefficients[j - 1] * (n - (j - 1)) / (j)); | ||
return coefficients; | ||
} | ||
|
||
/// We have a generating function of the form | ||
/// f_p(x) = \sum_i sign_i * (x^n_i(p)) / (\prod_j (1 - x^d_{ij}) | ||
/// | ||
/// where sign_i is ±1, | ||
/// n_i \in Q^p -> Q^d is the sum of the vectors d_{ij}, weighted by the | ||
/// floors of d affine functions on p parameters. | ||
/// d_{ij} \in Q^d are vectors. | ||
/// | ||
/// We need to find the number of terms of the form x^t in the expansion of | ||
/// this function. | ||
/// However, direct substitution (x = (1, ..., 1)) causes the denominator | ||
/// to become zero. | ||
/// | ||
/// We therefore use the following procedure instead: | ||
/// 1. Substitute x_i = (s+1)^μ_i for some vector μ. This makes the generating | ||
/// function a function of a scalar s. | ||
/// 2. Write each term in this function as P(s)/Q(s), where P and Q are | ||
/// polynomials. P has coefficients as quasipolynomials in d parameters, while | ||
/// Q has coefficients as scalars. | ||
/// 3. Find the constant term in the expansion of each term P(s)/Q(s). This is | ||
/// equivalent to substituting s = 0. | ||
/// | ||
/// Verdoolaege, Sven, et al. "Counting integer points in parametric | ||
/// polytopes using Barvinok's rational functions." Algorithmica 48 (2007): | ||
/// 37-66. | ||
QuasiPolynomial | ||
mlir::presburger::detail::computeNumTerms(const GeneratingFunction &gf) { | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Step (1) We need to find a μ such that we can substitute x_i = | ||
// (s+1)^μ_i. After this substitution, the exponent of (s+1) in the | ||
// denominator is (μ_i • d_{ij}) in each term. Clearly, this cannot become | ||
// zero. Hence we find a vector μ that is not orthogonal to any of the | ||
// d_{ij} and substitute x accordingly. | ||
std::vector<Point> allDenominators; | ||
for (ArrayRef<Point> den : gf.getDenominators()) | ||
allDenominators.insert(allDenominators.end(), den.begin(), den.end()); | ||
Point mu = getNonOrthogonalVector(allDenominators); | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
unsigned numParams = gf.getNumParams(); | ||
const std::vector<std::vector<Point>> &ds = gf.getDenominators(); | ||
QuasiPolynomial totalTerm(numParams, 0); | ||
for (unsigned i = 0, e = ds.size(); i < e; ++i) { | ||
int sign = gf.getSigns()[i]; | ||
|
||
// Compute the new exponents of (s+1) for the numerator and the | ||
// denominator after substituting μ. | ||
auto [numExp, dens] = | ||
substituteMuInTerm(numParams, gf.getNumerators()[i], ds[i], mu); | ||
// Now the numerator is (s+1)^numExp | ||
// and the denominator is \prod_j (1 - (s+1)^dens[j]). | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Step (2) We need to express the terms in the function as quotients of | ||
// polynomials. Each term is now of the form | ||
// sign_i * (s+1)^numExp / (\prod_j (1 - (s+1)^dens[j])) | ||
// For the i'th term, we first normalize the denominator to have only | ||
// positive exponents. We convert all the dens[j] to their | ||
// absolute values and change the sign and exponent in the numerator. | ||
normalizeDenominatorExponents(sign, numExp, dens); | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Then, using the formula for geometric series, we replace each (1 - | ||
// (s+1)^(dens[j])) with | ||
// (-s)(\sum_{0 ≤ k < dens[j]} (s+1)^k). | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (unsigned j = 0, e = dens.size(); j < e; ++j) | ||
dens[j] = abs(dens[j]) - 1; | ||
// Note that at this point, the semantics of `dens[j]` changes to mean | ||
// a term (\sum_{0 ≤ k ≤ dens[j]} (s+1)^k). The denominator is, as before, | ||
// a product of these terms. | ||
|
||
// Since the -s are taken out, the sign changes if there is an odd number | ||
// of such terms. | ||
unsigned r = dens.size(); | ||
if (dens.size() % 2 == 1) | ||
sign = -sign; | ||
|
||
// Thus the term overall now has the form | ||
// sign'_i * (s+1)^numExp / | ||
// (s^r * \prod_j (\sum_{0 ≤ k < dens[j]} (s+1)^k)). | ||
// This means that | ||
// the numerator is a polynomial in s, with coefficients as | ||
// quasipolynomials (given by binomial coefficients), and the denominator | ||
// is a polynomial in s, with integral coefficients (given by taking the | ||
// convolution over all j). | ||
|
||
// Step (3) We need to find the constant term in the expansion of each | ||
// term. Since each term has s^r as a factor in the denominator, we avoid | ||
// substituting s = 0 directly; instead, we find the coefficient of s^r in | ||
// sign'_i * (s+1)^numExp / (\prod_j (\sum_k (s+1)^k)), | ||
// Letting P(s) = (s+1)^numExp and Q(s) = \prod_j (...), | ||
// we need to find the coefficient of s^r in P(s)/Q(s), | ||
// for which we use the `getCoefficientInRationalFunction()` function. | ||
|
||
// First, we compute the coefficients of P(s), which are binomial | ||
// coefficients. | ||
// We only need the first r+1 of these, as higher-order terms do not | ||
// contribute to the coefficient of s^r. | ||
std::vector<QuasiPolynomial> numeratorCoefficients = | ||
getBinomialCoefficients(numExp, r); | ||
|
||
// Then we compute the coefficients of each individual term in Q(s), | ||
// which are (dens[i]+1) C (k+1) for 0 ≤ k ≤ dens[i]. | ||
std::vector<std::vector<Fraction>> eachTermDenCoefficients; | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::vector<Fraction> singleTermDenCoefficients; | ||
eachTermDenCoefficients.reserve(r); | ||
for (const Fraction &den : dens) { | ||
singleTermDenCoefficients = getBinomialCoefficients(den + 1, den + 1); | ||
eachTermDenCoefficients.push_back( | ||
ArrayRef<Fraction>(singleTermDenCoefficients).slice(1)); | ||
} | ||
|
||
// Now we find the coefficients in Q(s) itself | ||
// by taking the convolution of the coefficients | ||
// of all the terms. | ||
std::vector<Fraction> denominatorCoefficients; | ||
denominatorCoefficients = eachTermDenCoefficients[0]; | ||
for (unsigned j = 1, e = eachTermDenCoefficients.size(); j < e; ++j) | ||
denominatorCoefficients = multiplyPolynomials(denominatorCoefficients, | ||
eachTermDenCoefficients[j]); | ||
|
||
totalTerm = | ||
Abhinav271828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
totalTerm + getCoefficientInRationalFunction(r, numeratorCoefficients, | ||
denominatorCoefficients) * | ||
QuasiPolynomial(numParams, sign); | ||
} | ||
|
||
return totalTerm.simplify(); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.