1#include "mlir/Analysis/Presburger/Barvinok.h"
2#include "./Utils.h"
3#include "Parser.h"
4#include <gmock/gmock.h>
5#include <gtest/gtest.h>
6
7using namespace mlir;
8using namespace presburger;
9using namespace mlir::presburger::detail;
10
11/// The following are 3 randomly generated vectors with 4
12/// entries each and define a cone's H-representation
13/// using these numbers. We check that the dual contains
14/// the same numbers.
15/// We do the same in the reverse case.
16TEST(BarvinokTest, getDual) {
17 ConeH cone1 = defineHRep(numVars: 4);
18 cone1.addInequality(inEq: {1, 2, 3, 4, 0});
19 cone1.addInequality(inEq: {3, 4, 2, 5, 0});
20 cone1.addInequality(inEq: {6, 2, 6, 1, 0});
21
22 ConeV dual1 = getDual(cone: cone1);
23
24 EXPECT_EQ_INT_MATRIX(
25 a: dual1, b: makeIntMatrix(numRow: 3, numColumns: 4, matrix: {{1, 2, 3, 4}, {3, 4, 2, 5}, {6, 2, 6, 1}}));
26
27 ConeV cone2 = makeIntMatrix(numRow: 3, numColumns: 4, matrix: {{3, 6, 1, 5}, {3, 1, 7, 2}, {9, 3, 2, 7}});
28
29 ConeH dual2 = getDual(cone: cone2);
30
31 ConeH expected = defineHRep(numVars: 4);
32 expected.addInequality(inEq: {3, 6, 1, 5, 0});
33 expected.addInequality(inEq: {3, 1, 7, 2, 0});
34 expected.addInequality(inEq: {9, 3, 2, 7, 0});
35
36 EXPECT_TRUE(dual2.isEqual(expected));
37}
38
39/// We randomly generate a nxn matrix to use as a cone
40/// with n inequalities in n variables and check for
41/// the determinant being equal to the index.
42TEST(BarvinokTest, getIndex) {
43 ConeV cone = makeIntMatrix(numRow: 3, numColumns: 3, matrix: {{4, 2, 1}, {5, 2, 7}, {4, 1, 6}});
44 EXPECT_EQ(getIndex(cone), cone.determinant());
45
46 cone = makeIntMatrix(
47 numRow: 4, numColumns: 4, matrix: {{4, 2, 5, 1}, {4, 1, 3, 6}, {8, 2, 5, 6}, {5, 2, 5, 7}});
48 EXPECT_EQ(getIndex(cone), cone.determinant());
49}
50
51// The following cones and vertices are randomly generated
52// (s.t. the cones are unimodular) and the generating functions
53// are computed. We check that the results contain the correct
54// matrices.
55TEST(BarvinokTest, unimodularConeGeneratingFunction) {
56 ConeH cone = defineHRep(numVars: 2);
57 cone.addInequality(inEq: {0, -1, 0});
58 cone.addInequality(inEq: {-1, -2, 0});
59
60 ParamPoint vertex =
61 makeFracMatrix(numRow: 2, numColumns: 3, matrix: {{2, 2, 0}, {-1, -Fraction(1, 2), 1}});
62
63 GeneratingFunction gf =
64 computeUnimodularConeGeneratingFunction(vertex, sign: 1, cone);
65
66 EXPECT_EQ_REPR_GENERATINGFUNCTION(
67 a: gf, b: GeneratingFunction(
68 2, {1},
69 {makeFracMatrix(numRow: 3, numColumns: 2, matrix: {{-1, 0}, {-Fraction(1, 2), 1}, {1, 2}})},
70 {{{2, -1}, {-1, 0}}}));
71
72 cone = defineHRep(numVars: 3);
73 cone.addInequality(inEq: {7, 1, 6, 0});
74 cone.addInequality(inEq: {9, 1, 7, 0});
75 cone.addInequality(inEq: {8, -1, 1, 0});
76
77 vertex = makeFracMatrix(numRow: 3, numColumns: 2, matrix: {{5, 2}, {6, 2}, {7, 1}});
78
79 gf = computeUnimodularConeGeneratingFunction(vertex, sign: 1, cone);
80
81 EXPECT_EQ_REPR_GENERATINGFUNCTION(
82 a: gf,
83 b: GeneratingFunction(
84 1, {1}, {makeFracMatrix(numRow: 2, numColumns: 3, matrix: {{-83, -100, -41}, {-22, -27, -15}})},
85 {{{8, 47, -17}, {-7, -41, 15}, {1, 5, -2}}}));
86}
87
88// The following vectors are randomly generated.
89// We then check that the output of the function has non-zero
90// dot product with all non-null vectors.
91TEST(BarvinokTest, getNonOrthogonalVector) {
92 std::vector<Point> vectors = {Point({1, 2, 3, 4}), Point({-1, 0, 1, 1}),
93 Point({2, 7, 0, 0}), Point({0, 0, 0, 0})};
94 Point nonOrth = getNonOrthogonalVector(vectors);
95
96 for (unsigned i = 0; i < 3; i++)
97 EXPECT_NE(dotProduct(nonOrth, vectors[i]), 0);
98
99 vectors = {Point({0, 1, 3}), Point({-2, -1, 1}), Point({6, 3, 0}),
100 Point({0, 0, -3}), Point({5, 0, -1})};
101 nonOrth = getNonOrthogonalVector(vectors);
102
103 for (const Point &vector : vectors)
104 EXPECT_NE(dotProduct(nonOrth, vector), 0);
105}
106
107// The following polynomials are randomly generated and the
108// coefficients are computed by hand.
109// Although the function allows the coefficients of the numerator
110// to be arbitrary quasipolynomials, we stick to constants for simplicity,
111// as the relevant arithmetic operations on quasipolynomials
112// are tested separately.
113TEST(BarvinokTest, getCoefficientInRationalFunction) {
114 std::vector<QuasiPolynomial> numerator = {
115 QuasiPolynomial(0, 2), QuasiPolynomial(0, 3), QuasiPolynomial(0, 5)};
116 std::vector<Fraction> denominator = {Fraction(1), Fraction(0), Fraction(4),
117 Fraction(3)};
118 QuasiPolynomial coeff =
119 getCoefficientInRationalFunction(power: 1, num: numerator, den: denominator);
120 EXPECT_EQ(coeff.getConstantTerm(), 3);
121
122 numerator = {QuasiPolynomial(0, -1), QuasiPolynomial(0, 4),
123 QuasiPolynomial(0, -2), QuasiPolynomial(0, 5),
124 QuasiPolynomial(0, 6)};
125 denominator = {Fraction(8), Fraction(4), Fraction(0), Fraction(-2)};
126 coeff = getCoefficientInRationalFunction(power: 3, num: numerator, den: denominator);
127 EXPECT_EQ(coeff.getConstantTerm(), Fraction(55, 64));
128}
129
130TEST(BarvinokTest, computeNumTermsCone) {
131 // The following test is taken from
132 // Verdoolaege, Sven, et al. "Counting integer points in parametric
133 // polytopes using Barvinok's rational functions." Algorithmica 48 (2007):
134 // 37-66.
135 // It represents a right-angled triangle with right angle at the origin,
136 // with height and base lengths (p/2).
137 GeneratingFunction gf(
138 1, {1, 1, 1},
139 {makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, Fraction(1, 2)}, {0, 0}}),
140 makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, Fraction(1, 2)}, {0, 0}}),
141 makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, 0}, {0, 0}})},
142 {{{-1, 1}, {-1, 0}}, {{1, -1}, {0, -1}}, {{1, 0}, {0, 1}}});
143
144 QuasiPolynomial numPoints = computeNumTerms(gf).collectTerms();
145
146 // First, we make sure that all the affine functions are of the form ⌊p/2⌋.
147 for (const std::vector<SmallVector<Fraction>> &term : numPoints.getAffine()) {
148 for (const SmallVector<Fraction> &aff : term) {
149 EXPECT_EQ(aff.size(), 2u);
150 EXPECT_EQ(aff[0], Fraction(1, 2));
151 EXPECT_EQ(aff[1], Fraction(0, 1));
152 }
153 }
154
155 // Now, we can gather the like terms because we know there's only
156 // either ⌊p/2⌋^2, ⌊p/2⌋, or constants.
157 // The total coefficient of ⌊p/2⌋^2 is the sum of coefficients of all
158 // terms with 2 affine functions, and
159 // the coefficient of total ⌊p/2⌋ is the sum of coefficients of all
160 // terms with 1 affine function,
161 Fraction pSquaredCoeff = 0, pCoeff = 0, constantTerm = 0;
162 SmallVector<Fraction> coefficients = numPoints.getCoefficients();
163 for (unsigned i = 0; i < numPoints.getCoefficients().size(); i++)
164 if (numPoints.getAffine()[i].size() == 2)
165 pSquaredCoeff = pSquaredCoeff + coefficients[i];
166 else if (numPoints.getAffine()[i].size() == 1)
167 pCoeff = pCoeff + coefficients[i];
168
169 // We expect the answer to be (1/2)⌊p/2⌋^2 + (3/2)⌊p/2⌋ + 1.
170 EXPECT_EQ(pSquaredCoeff, Fraction(1, 2));
171 EXPECT_EQ(pCoeff, Fraction(3, 2));
172 EXPECT_EQ(numPoints.getConstantTerm(), Fraction(1, 1));
173
174 // The following generating function corresponds to a cuboid
175 // with length M (x-axis), width N (y-axis), and height P (z-axis).
176 // There are eight terms.
177 gf = GeneratingFunction(
178 3, {1, 1, 1, 1, 1, 1, 1, 1},
179 {makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}),
180 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{1, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}),
181 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{0, 0, 0}, {0, 1, 0}, {0, 0, 0}, {0, 0, 0}}),
182 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{0, 0, 0}, {0, 0, 0}, {0, 0, 1}, {0, 0, 0}}),
183 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{1, 0, 0}, {0, 1, 0}, {0, 0, 0}, {0, 0, 0}}),
184 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}, {0, 0, 0}}),
185 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{0, 0, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, 0}}),
186 makeFracMatrix(numRow: 4, numColumns: 3, matrix: {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, 0}})},
187 {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
188 {{-1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
189 {{1, 0, 0}, {0, -1, 0}, {0, 0, 1}},
190 {{1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
191 {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1}},
192 {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
193 {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
194 {{-1, 0, 0}, {0, -1, 0}, {0, 0, -1}}});
195
196 numPoints = computeNumTerms(gf);
197 numPoints = numPoints.collectTerms().simplify();
198
199 // First, we make sure all the affine functions are either
200 // M, N, P, or 1.
201 for (const std::vector<SmallVector<Fraction>> &term : numPoints.getAffine()) {
202 for (const SmallVector<Fraction> &aff : term) {
203 // First, ensure that the coefficients are all nonnegative integers.
204 for (const Fraction &c : aff) {
205 EXPECT_TRUE(c >= 0);
206 EXPECT_EQ(c, c.getAsInteger());
207 }
208 // Now, if the coefficients add up to 1, we can be sure the term is
209 // either M, N, P, or 1.
210 EXPECT_EQ(aff[0] + aff[1] + aff[2] + aff[3], 1);
211 }
212 }
213
214 // We store the coefficients of M, N and P in this array.
215 Fraction count[2][2][2];
216 coefficients = numPoints.getCoefficients();
217 for (unsigned i = 0, e = coefficients.size(); i < e; i++) {
218 unsigned mIndex = 0, nIndex = 0, pIndex = 0;
219 for (const SmallVector<Fraction> &aff : numPoints.getAffine()[i]) {
220 if (aff[0] == 1)
221 mIndex = 1;
222 if (aff[1] == 1)
223 nIndex = 1;
224 if (aff[2] == 1)
225 pIndex = 1;
226 EXPECT_EQ(aff[3], 0);
227 }
228 count[mIndex][nIndex][pIndex] += coefficients[i];
229 }
230
231 // We expect the answer to be
232 // (⌊M⌋ + 1)(⌊N⌋ + 1)(⌊P⌋ + 1) =
233 // ⌊M⌋⌊N⌋⌊P⌋ + ⌊M⌋⌊N⌋ + ⌊N⌋⌊P⌋ + ⌊M⌋⌊P⌋ + ⌊M⌋ + ⌊N⌋ + ⌊P⌋ + 1.
234 for (unsigned i = 0; i < 2; i++)
235 for (unsigned j = 0; j < 2; j++)
236 for (unsigned k = 0; k < 2; k++)
237 EXPECT_EQ(count[i][j][k], 1);
238}
239
240/// We define some simple polyhedra with unimodular tangent cones and verify
241/// that the returned generating functions correspond to those calculated by
242/// hand.
243TEST(BarvinokTest, computeNumTermsPolytope) {
244 // A cube of side 1.
245 PolyhedronH poly =
246 parseRelationFromSet(set: "(x, y, z) : (x >= 0, y >= 0, z >= 0, -x + 1 >= 0, "
247 "-y + 1 >= 0, -z + 1 >= 0)",
248 numDomain: 0);
249
250 std::vector<std::pair<PresburgerSet, GeneratingFunction>> count =
251 computePolytopeGeneratingFunction(poly);
252 // There is only one chamber, as it is non-parametric.
253 EXPECT_EQ(count.size(), 9u);
254
255 GeneratingFunction gf = count[0].second;
256 EXPECT_EQ_REPR_GENERATINGFUNCTION(
257 a: gf,
258 b: GeneratingFunction(
259 0, {1, 1, 1, 1, 1, 1, 1, 1},
260 {makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{1, 1, 1}}), makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 1, 1}}),
261 makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 1, 1}}), makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 0, 1}}),
262 makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 1, 1}}), makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 0, 1}}),
263 makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 0, 1}}),
264 makeFracMatrix(numRow: 1, numColumns: 3, matrix: {{0, 0, 0}})},
265 {{{-1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
266 {{0, 0, 1}, {-1, 0, 0}, {0, -1, 0}},
267 {{0, 1, 0}, {-1, 0, 0}, {0, 0, -1}},
268 {{0, 1, 0}, {0, 0, 1}, {-1, 0, 0}},
269 {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
270 {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}},
271 {{1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
272 {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}));
273
274 // A right-angled triangle with side p.
275 poly =
276 parseRelationFromSet(set: "(x, y)[N] : (x >= 0, y >= 0, -x - y + N >= 0)", numDomain: 0);
277
278 count = computePolytopeGeneratingFunction(poly);
279 // There is only one chamber: p ≥ 0
280 EXPECT_EQ(count.size(), 4u);
281
282 gf = count[0].second;
283 EXPECT_EQ_REPR_GENERATINGFUNCTION(
284 a: gf, b: GeneratingFunction(
285 1, {1, 1, 1},
286 {makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, 1}, {0, 0}}),
287 makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, 1}, {0, 0}}),
288 makeFracMatrix(numRow: 2, numColumns: 2, matrix: {{0, 0}, {0, 0}})},
289 {{{-1, 1}, {-1, 0}}, {{1, -1}, {0, -1}}, {{1, 0}, {0, 1}}}));
290
291 // Cartesian product of a cube with side M and a right triangle with side N.
292 poly = parseRelationFromSet(
293 set: "(x, y, z, w, a)[M, N] : (x >= 0, y >= 0, z >= 0, -x + M >= 0, -y + M >= "
294 "0, -z + M >= 0, w >= 0, a >= 0, -w - a + N >= 0)",
295 numDomain: 0);
296
297 count = computePolytopeGeneratingFunction(poly);
298
299 EXPECT_EQ(count.size(), 25u);
300
301 gf = count[0].second;
302 EXPECT_EQ(gf.getNumerators().size(), 24u);
303}
304

source code of mlir/unittests/Analysis/Presburger/BarvinokTest.cpp