1 | //===- IntegerPolyhedron.cpp - Tests for IntegerPolyhedron class ----------===// |
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 | #include "Parser.h" |
10 | #include "Utils.h" |
11 | #include "mlir/Analysis/Presburger/IntegerRelation.h" |
12 | #include "mlir/Analysis/Presburger/PWMAFunction.h" |
13 | #include "mlir/Analysis/Presburger/Simplex.h" |
14 | |
15 | #include <gmock/gmock.h> |
16 | #include <gtest/gtest.h> |
17 | |
18 | #include <numeric> |
19 | #include <optional> |
20 | |
21 | using namespace mlir; |
22 | using namespace presburger; |
23 | |
24 | using testing::ElementsAre; |
25 | |
26 | enum class TestFunction { Sample, Empty }; |
27 | |
28 | /// Construct a IntegerPolyhedron from a set of inequality and |
29 | /// equality constraints. |
30 | static IntegerPolyhedron |
31 | makeSetFromConstraints(unsigned ids, ArrayRef<SmallVector<int64_t, 4>> ineqs, |
32 | ArrayRef<SmallVector<int64_t, 4>> eqs, |
33 | unsigned syms = 0) { |
34 | IntegerPolyhedron set( |
35 | ineqs.size(), eqs.size(), ids + 1, |
36 | PresburgerSpace::getSetSpace(numDims: ids - syms, numSymbols: syms, /*numLocals=*/0)); |
37 | for (const auto &eq : eqs) |
38 | set.addEquality(eq); |
39 | for (const auto &ineq : ineqs) |
40 | set.addInequality(inEq: ineq); |
41 | return set; |
42 | } |
43 | |
44 | static void dump(ArrayRef<MPInt> vec) { |
45 | for (const MPInt &x : vec) |
46 | llvm::errs() << x << ' '; |
47 | llvm::errs() << '\n'; |
48 | } |
49 | |
50 | /// If fn is TestFunction::Sample (default): |
51 | /// |
52 | /// If hasSample is true, check that findIntegerSample returns a valid sample |
53 | /// for the IntegerPolyhedron poly. Also check that getIntegerLexmin finds a |
54 | /// non-empty lexmin. |
55 | /// |
56 | /// If hasSample is false, check that findIntegerSample returns std::nullopt |
57 | /// and findIntegerLexMin returns Empty. |
58 | /// |
59 | /// If fn is TestFunction::Empty, check that isIntegerEmpty returns the |
60 | /// opposite of hasSample. |
61 | static void checkSample(bool hasSample, const IntegerPolyhedron &poly, |
62 | TestFunction fn = TestFunction::Sample) { |
63 | std::optional<SmallVector<MPInt, 8>> maybeSample; |
64 | MaybeOptimum<SmallVector<MPInt, 8>> maybeLexMin; |
65 | switch (fn) { |
66 | case TestFunction::Sample: |
67 | maybeSample = poly.findIntegerSample(); |
68 | maybeLexMin = poly.findIntegerLexMin(); |
69 | |
70 | if (!hasSample) { |
71 | EXPECT_FALSE(maybeSample.has_value()); |
72 | if (maybeSample.has_value()) { |
73 | llvm::errs() << "findIntegerSample gave sample: " ; |
74 | dump(vec: *maybeSample); |
75 | } |
76 | |
77 | EXPECT_TRUE(maybeLexMin.isEmpty()); |
78 | if (maybeLexMin.isBounded()) { |
79 | llvm::errs() << "findIntegerLexMin gave sample: " ; |
80 | dump(vec: *maybeLexMin); |
81 | } |
82 | } else { |
83 | ASSERT_TRUE(maybeSample.has_value()); |
84 | EXPECT_TRUE(poly.containsPoint(*maybeSample)); |
85 | |
86 | ASSERT_FALSE(maybeLexMin.isEmpty()); |
87 | if (maybeLexMin.isUnbounded()) { |
88 | EXPECT_TRUE(Simplex(poly).isUnbounded()); |
89 | } |
90 | if (maybeLexMin.isBounded()) { |
91 | EXPECT_TRUE(poly.containsPointNoLocal(*maybeLexMin)); |
92 | } |
93 | } |
94 | break; |
95 | case TestFunction::Empty: |
96 | EXPECT_EQ(!hasSample, poly.isIntegerEmpty()); |
97 | break; |
98 | } |
99 | } |
100 | |
101 | /// Check sampling for all the permutations of the dimensions for the given |
102 | /// constraint set. Since the GBR algorithm progresses dimension-wise, different |
103 | /// orderings may cause the algorithm to proceed differently. At least some of |
104 | ///.these permutations should make it past the heuristics and test the |
105 | /// implementation of the GBR algorithm itself. |
106 | /// Use TestFunction fn to test. |
107 | static void checkPermutationsSample(bool hasSample, unsigned nDim, |
108 | ArrayRef<SmallVector<int64_t, 4>> ineqs, |
109 | ArrayRef<SmallVector<int64_t, 4>> eqs, |
110 | TestFunction fn = TestFunction::Sample) { |
111 | SmallVector<unsigned, 4> perm(nDim); |
112 | std::iota(first: perm.begin(), last: perm.end(), value: 0); |
113 | auto permute = [&perm](ArrayRef<int64_t> coeffs) { |
114 | SmallVector<int64_t, 4> permuted; |
115 | for (unsigned id : perm) |
116 | permuted.push_back(Elt: coeffs[id]); |
117 | permuted.push_back(Elt: coeffs.back()); |
118 | return permuted; |
119 | }; |
120 | do { |
121 | SmallVector<SmallVector<int64_t, 4>, 4> permutedIneqs, permutedEqs; |
122 | for (const auto &ineq : ineqs) |
123 | permutedIneqs.push_back(Elt: permute(ineq)); |
124 | for (const auto &eq : eqs) |
125 | permutedEqs.push_back(Elt: permute(eq)); |
126 | |
127 | checkSample(hasSample, |
128 | poly: makeSetFromConstraints(ids: nDim, ineqs: permutedIneqs, eqs: permutedEqs), fn); |
129 | } while (std::next_permutation(first: perm.begin(), last: perm.end())); |
130 | } |
131 | |
132 | TEST(IntegerPolyhedronTest, removeInequality) { |
133 | IntegerPolyhedron set = |
134 | makeSetFromConstraints(ids: 1, ineqs: {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}, eqs: {}); |
135 | |
136 | set.removeInequalityRange(start: 0, end: 0); |
137 | EXPECT_EQ(set.getNumInequalities(), 5u); |
138 | |
139 | set.removeInequalityRange(start: 1, end: 3); |
140 | EXPECT_EQ(set.getNumInequalities(), 3u); |
141 | EXPECT_THAT(set.getInequality(0), ElementsAre(0, 0)); |
142 | EXPECT_THAT(set.getInequality(1), ElementsAre(3, 3)); |
143 | EXPECT_THAT(set.getInequality(2), ElementsAre(4, 4)); |
144 | |
145 | set.removeInequality(pos: 1); |
146 | EXPECT_EQ(set.getNumInequalities(), 2u); |
147 | EXPECT_THAT(set.getInequality(0), ElementsAre(0, 0)); |
148 | EXPECT_THAT(set.getInequality(1), ElementsAre(4, 4)); |
149 | } |
150 | |
151 | TEST(IntegerPolyhedronTest, removeEquality) { |
152 | IntegerPolyhedron set = |
153 | makeSetFromConstraints(ids: 1, ineqs: {}, eqs: {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}); |
154 | |
155 | set.removeEqualityRange(start: 0, end: 0); |
156 | EXPECT_EQ(set.getNumEqualities(), 5u); |
157 | |
158 | set.removeEqualityRange(start: 1, end: 3); |
159 | EXPECT_EQ(set.getNumEqualities(), 3u); |
160 | EXPECT_THAT(set.getEquality(0), ElementsAre(0, 0)); |
161 | EXPECT_THAT(set.getEquality(1), ElementsAre(3, 3)); |
162 | EXPECT_THAT(set.getEquality(2), ElementsAre(4, 4)); |
163 | |
164 | set.removeEquality(pos: 1); |
165 | EXPECT_EQ(set.getNumEqualities(), 2u); |
166 | EXPECT_THAT(set.getEquality(0), ElementsAre(0, 0)); |
167 | EXPECT_THAT(set.getEquality(1), ElementsAre(4, 4)); |
168 | } |
169 | |
170 | TEST(IntegerPolyhedronTest, clearConstraints) { |
171 | IntegerPolyhedron set = makeSetFromConstraints(ids: 1, ineqs: {}, eqs: {}); |
172 | |
173 | set.addInequality(inEq: {1, 0}); |
174 | EXPECT_EQ(set.atIneq(0, 0), 1); |
175 | EXPECT_EQ(set.atIneq(0, 1), 0); |
176 | |
177 | set.clearConstraints(); |
178 | |
179 | set.addInequality(inEq: {1, 0}); |
180 | EXPECT_EQ(set.atIneq(0, 0), 1); |
181 | EXPECT_EQ(set.atIneq(0, 1), 0); |
182 | } |
183 | |
184 | TEST(IntegerPolyhedronTest, removeIdRange) { |
185 | IntegerPolyhedron set(PresburgerSpace::getSetSpace(numDims: 3, numSymbols: 2, numLocals: 1)); |
186 | |
187 | set.addInequality(inEq: {10, 11, 12, 20, 21, 30, 40}); |
188 | set.removeVar(kind: VarKind::Symbol, pos: 1); |
189 | EXPECT_THAT(set.getInequality(0), |
190 | testing::ElementsAre(10, 11, 12, 20, 30, 40)); |
191 | |
192 | set.removeVarRange(kind: VarKind::SetDim, varStart: 0, varLimit: 2); |
193 | EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 30, 40)); |
194 | |
195 | set.removeVarRange(kind: VarKind::Local, varStart: 1, varLimit: 1); |
196 | EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 30, 40)); |
197 | |
198 | set.removeVarRange(kind: VarKind::Local, varStart: 0, varLimit: 1); |
199 | EXPECT_THAT(set.getInequality(0), testing::ElementsAre(12, 20, 40)); |
200 | } |
201 | |
202 | TEST(IntegerPolyhedronTest, FindSampleTest) { |
203 | // Bounded sets with only inequalities. |
204 | // 0 <= 7x <= 5 |
205 | checkSample(hasSample: true, |
206 | poly: parseIntegerPolyhedron(str: "(x) : (7 * x >= 0, -7 * x + 5 >= 0)" )); |
207 | |
208 | // 1 <= 5x and 5x <= 4 (no solution). |
209 | checkSample( |
210 | hasSample: false, poly: parseIntegerPolyhedron(str: "(x) : (5 * x - 1 >= 0, -5 * x + 4 >= 0)" )); |
211 | |
212 | // 1 <= 5x and 5x <= 9 (solution: x = 1). |
213 | checkSample( |
214 | hasSample: true, poly: parseIntegerPolyhedron(str: "(x) : (5 * x - 1 >= 0, -5 * x + 9 >= 0)" )); |
215 | |
216 | // Bounded sets with equalities. |
217 | // x >= 8 and 40 >= y and x = y. |
218 | checkSample(hasSample: true, poly: parseIntegerPolyhedron( |
219 | str: "(x,y) : (x - 8 >= 0, -y + 40 >= 0, x - y == 0)" )); |
220 | |
221 | // x <= 10 and y <= 10 and 10 <= z and x + 2y = 3z. |
222 | // solution: x = y = z = 10. |
223 | checkSample(hasSample: true, |
224 | poly: parseIntegerPolyhedron(str: "(x,y,z) : (-x + 10 >= 0, -y + 10 >= 0, " |
225 | "z - 10 >= 0, x + 2 * y - 3 * z == 0)" )); |
226 | |
227 | // x <= 10 and y <= 10 and 11 <= z and x + 2y = 3z. |
228 | // This implies x + 2y >= 33 and x + 2y <= 30, which has no solution. |
229 | checkSample(hasSample: false, |
230 | poly: parseIntegerPolyhedron(str: "(x,y,z) : (-x + 10 >= 0, -y + 10 >= 0, " |
231 | "z - 11 >= 0, x + 2 * y - 3 * z == 0)" )); |
232 | |
233 | // 0 <= r and r <= 3 and 4q + r = 7. |
234 | // Solution: q = 1, r = 3. |
235 | checkSample(hasSample: true, poly: parseIntegerPolyhedron( |
236 | str: "(q,r) : (r >= 0, -r + 3 >= 0, 4 * q + r - 7 == 0)" )); |
237 | |
238 | // 4q + r = 7 and r = 0. |
239 | // Solution: q = 1, r = 3. |
240 | checkSample(hasSample: false, |
241 | poly: parseIntegerPolyhedron(str: "(q,r) : (4 * q + r - 7 == 0, r == 0)" )); |
242 | |
243 | // The next two sets are large sets that should take a long time to sample |
244 | // with a naive branch and bound algorithm but can be sampled efficiently with |
245 | // the GBR algorithm. |
246 | // |
247 | // This is a triangle with vertices at (1/3, 0), (2/3, 0) and (10000, 10000). |
248 | checkSample( |
249 | hasSample: true, poly: parseIntegerPolyhedron(str: "(x,y) : (y >= 0, " |
250 | "300000 * x - 299999 * y - 100000 >= 0, " |
251 | "-300000 * x + 299998 * y + 200000 >= 0)" )); |
252 | |
253 | // This is a tetrahedron with vertices at |
254 | // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 10000), and (10000, 10000, 10000). |
255 | // The first three points form a triangular base on the xz plane with the |
256 | // apex at the fourth point, which is the only integer point. |
257 | checkPermutationsSample( |
258 | hasSample: true, nDim: 3, |
259 | ineqs: { |
260 | {0, 1, 0, 0}, // y >= 0 |
261 | {0, -1, 1, 0}, // z >= y |
262 | {300000, -299998, -1, |
263 | -100000}, // -300000x + 299998y + 100000 + z <= 0. |
264 | {-150000, 149999, 0, 100000}, // -150000x + 149999y + 100000 >= 0. |
265 | }, |
266 | eqs: {}); |
267 | |
268 | // Same thing with some spurious extra dimensions equated to constants. |
269 | checkSample(hasSample: true, |
270 | poly: parseIntegerPolyhedron( |
271 | str: "(a,b,c,d,e) : (b + d - e >= 0, -b + c - d + e >= 0, " |
272 | "300000 * a - 299998 * b - c - 9 * d + 21 * e - 112000 >= 0, " |
273 | "-150000 * a + 149999 * b - 15 * d + 47 * e + 68000 >= 0, " |
274 | "d - e == 0, d + e - 2000 == 0)" )); |
275 | |
276 | // This is a tetrahedron with vertices at |
277 | // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 100), (100, 100 - 1/3, 100). |
278 | checkPermutationsSample(hasSample: false, nDim: 3, |
279 | ineqs: { |
280 | {0, 1, 0, 0}, |
281 | {0, -300, 299, 0}, |
282 | {300 * 299, -89400, -299, -100 * 299}, |
283 | {-897, 894, 0, 598}, |
284 | }, |
285 | eqs: {}); |
286 | |
287 | // Two tests involving equalities that are integer empty but not rational |
288 | // empty. |
289 | |
290 | // This is a line segment from (0, 1/3) to (100, 100 + 1/3). |
291 | checkSample(hasSample: false, |
292 | poly: parseIntegerPolyhedron( |
293 | str: "(x,y) : (x >= 0, -x + 100 >= 0, 3 * x - 3 * y + 1 == 0)" )); |
294 | |
295 | // A thin parallelogram. 0 <= x <= 100 and x + 1/3 <= y <= x + 2/3. |
296 | checkSample(hasSample: false, poly: parseIntegerPolyhedron( |
297 | str: "(x,y) : (x >= 0, -x + 100 >= 0, " |
298 | "3 * x - 3 * y + 2 >= 0, -3 * x + 3 * y - 1 >= 0)" )); |
299 | |
300 | checkSample(hasSample: true, |
301 | poly: parseIntegerPolyhedron(str: "(x,y) : (2 * x >= 0, -2 * x + 99 >= 0, " |
302 | "2 * y >= 0, -2 * y + 99 >= 0)" )); |
303 | |
304 | // 2D cone with apex at (10000, 10000) and |
305 | // edges passing through (1/3, 0) and (2/3, 0). |
306 | checkSample(hasSample: true, poly: parseIntegerPolyhedron( |
307 | str: "(x,y) : (300000 * x - 299999 * y - 100000 >= 0, " |
308 | "-300000 * x + 299998 * y + 200000 >= 0)" )); |
309 | |
310 | // Cartesian product of a tetrahedron and a 2D cone. |
311 | // The tetrahedron has vertices at |
312 | // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 10000), and (10000, 10000, 10000). |
313 | // The first three points form a triangular base on the xz plane with the |
314 | // apex at the fourth point, which is the only integer point. |
315 | // The cone has apex at (10000, 10000) and |
316 | // edges passing through (1/3, 0) and (2/3, 0). |
317 | checkPermutationsSample( |
318 | hasSample: true /* not empty */, nDim: 5, |
319 | ineqs: { |
320 | // Tetrahedron contraints: |
321 | {0, 1, 0, 0, 0, 0}, // y >= 0 |
322 | {0, -1, 1, 0, 0, 0}, // z >= y |
323 | // -300000x + 299998y + 100000 + z <= 0. |
324 | {300000, -299998, -1, 0, 0, -100000}, |
325 | // -150000x + 149999y + 100000 >= 0. |
326 | {-150000, 149999, 0, 0, 0, 100000}, |
327 | |
328 | // Triangle constraints: |
329 | // 300000p - 299999q >= 100000 |
330 | {0, 0, 0, 300000, -299999, -100000}, |
331 | // -300000p + 299998q + 200000 >= 0 |
332 | {0, 0, 0, -300000, 299998, 200000}, |
333 | }, |
334 | eqs: {}); |
335 | |
336 | // Cartesian product of same tetrahedron as above and {(p, q) : 1/3 <= p <= |
337 | // 2/3}. Since the second set is empty, the whole set is too. |
338 | checkPermutationsSample( |
339 | hasSample: false /* empty */, nDim: 5, |
340 | ineqs: { |
341 | // Tetrahedron contraints: |
342 | {0, 1, 0, 0, 0, 0}, // y >= 0 |
343 | {0, -1, 1, 0, 0, 0}, // z >= y |
344 | // -300000x + 299998y + 100000 + z <= 0. |
345 | {300000, -299998, -1, 0, 0, -100000}, |
346 | // -150000x + 149999y + 100000 >= 0. |
347 | {-150000, 149999, 0, 0, 0, 100000}, |
348 | |
349 | // Second set constraints: |
350 | // 3p >= 1 |
351 | {0, 0, 0, 3, 0, -1}, |
352 | // 3p <= 2 |
353 | {0, 0, 0, -3, 0, 2}, |
354 | }, |
355 | eqs: {}); |
356 | |
357 | // Cartesian product of same tetrahedron as above and |
358 | // {(p, q, r) : 1 <= p <= 2 and p = 3q + 3r}. |
359 | // Since the second set is empty, the whole set is too. |
360 | checkPermutationsSample( |
361 | hasSample: false /* empty */, nDim: 5, |
362 | ineqs: { |
363 | // Tetrahedron contraints: |
364 | {0, 1, 0, 0, 0, 0, 0}, // y >= 0 |
365 | {0, -1, 1, 0, 0, 0, 0}, // z >= y |
366 | // -300000x + 299998y + 100000 + z <= 0. |
367 | {300000, -299998, -1, 0, 0, 0, -100000}, |
368 | // -150000x + 149999y + 100000 >= 0. |
369 | {-150000, 149999, 0, 0, 0, 0, 100000}, |
370 | |
371 | // Second set constraints: |
372 | // p >= 1 |
373 | {0, 0, 0, 1, 0, 0, -1}, |
374 | // p <= 2 |
375 | {0, 0, 0, -1, 0, 0, 2}, |
376 | }, |
377 | eqs: { |
378 | {0, 0, 0, 1, -3, -3, 0}, // p = 3q + 3r |
379 | }); |
380 | |
381 | // Cartesian product of a tetrahedron and a 2D cone. |
382 | // The tetrahedron is empty and has vertices at |
383 | // (1/3, 0, 0), (2/3, 0, 0), (2/3, 0, 100), and (100, 100 - 1/3, 100). |
384 | // The cone has apex at (10000, 10000) and |
385 | // edges passing through (1/3, 0) and (2/3, 0). |
386 | // Since the tetrahedron is empty, the Cartesian product is too. |
387 | checkPermutationsSample(hasSample: false /* empty */, nDim: 5, |
388 | ineqs: { |
389 | // Tetrahedron contraints: |
390 | {0, 1, 0, 0, 0, 0}, |
391 | {0, -300, 299, 0, 0, 0}, |
392 | {300 * 299, -89400, -299, 0, 0, -100 * 299}, |
393 | {-897, 894, 0, 0, 0, 598}, |
394 | |
395 | // Triangle constraints: |
396 | // 300000p - 299999q >= 100000 |
397 | {0, 0, 0, 300000, -299999, -100000}, |
398 | // -300000p + 299998q + 200000 >= 0 |
399 | {0, 0, 0, -300000, 299998, 200000}, |
400 | }, |
401 | eqs: {}); |
402 | |
403 | // Cartesian product of same tetrahedron as above and |
404 | // {(p, q) : 1/3 <= p <= 2/3}. |
405 | checkPermutationsSample(hasSample: false /* empty */, nDim: 5, |
406 | ineqs: { |
407 | // Tetrahedron contraints: |
408 | {0, 1, 0, 0, 0, 0}, |
409 | {0, -300, 299, 0, 0, 0}, |
410 | {300 * 299, -89400, -299, 0, 0, -100 * 299}, |
411 | {-897, 894, 0, 0, 0, 598}, |
412 | |
413 | // Second set constraints: |
414 | // 3p >= 1 |
415 | {0, 0, 0, 3, 0, -1}, |
416 | // 3p <= 2 |
417 | {0, 0, 0, -3, 0, 2}, |
418 | }, |
419 | eqs: {}); |
420 | |
421 | checkSample(hasSample: true, poly: parseIntegerPolyhedron( |
422 | str: "(x, y, z) : (2 * x - 1 >= 0, x - y - 1 == 0, " |
423 | "y - z == 0)" )); |
424 | |
425 | // Test with a local id. |
426 | checkSample(hasSample: true, poly: parseIntegerPolyhedron(str: "(x) : (x == 5*(x floordiv 2))" )); |
427 | |
428 | // Regression tests for the computation of dual coefficients. |
429 | checkSample(hasSample: false, poly: parseIntegerPolyhedron(str: "(x, y, z) : (" |
430 | "6*x - 4*y + 9*z + 2 >= 0," |
431 | "x + 5*y + z + 5 >= 0," |
432 | "-4*x + y + 2*z - 1 >= 0," |
433 | "-3*x - 2*y - 7*z - 1 >= 0," |
434 | "-7*x - 5*y - 9*z - 1 >= 0)" )); |
435 | checkSample(hasSample: true, poly: parseIntegerPolyhedron(str: "(x, y, z) : (" |
436 | "3*x + 3*y + 3 >= 0," |
437 | "-4*x - 8*y - z + 4 >= 0," |
438 | "-7*x - 4*y + z + 1 >= 0," |
439 | "2*x - 7*y - 8*z - 7 >= 0," |
440 | "9*x + 8*y - 9*z - 7 >= 0)" )); |
441 | } |
442 | |
443 | TEST(IntegerPolyhedronTest, IsIntegerEmptyTest) { |
444 | // 1 <= 5x and 5x <= 4 (no solution). |
445 | EXPECT_TRUE(parseIntegerPolyhedron("(x) : (5 * x - 1 >= 0, -5 * x + 4 >= 0)" ) |
446 | .isIntegerEmpty()); |
447 | // 1 <= 5x and 5x <= 9 (solution: x = 1). |
448 | EXPECT_FALSE(parseIntegerPolyhedron("(x) : (5 * x - 1 >= 0, -5 * x + 9 >= 0)" ) |
449 | .isIntegerEmpty()); |
450 | |
451 | // Unbounded sets. |
452 | EXPECT_TRUE( |
453 | parseIntegerPolyhedron("(x,y,z) : (2 * y - 1 >= 0, -2 * y + 1 >= 0, " |
454 | "2 * z - 1 >= 0, 2 * x - 1 == 0)" ) |
455 | .isIntegerEmpty()); |
456 | |
457 | EXPECT_FALSE(parseIntegerPolyhedron( |
458 | "(x,y,z) : (2 * x - 1 >= 0, -3 * x + 3 >= 0, " |
459 | "5 * z - 6 >= 0, -7 * z + 17 >= 0, 3 * y - 2 >= 0)" ) |
460 | .isIntegerEmpty()); |
461 | |
462 | EXPECT_FALSE(parseIntegerPolyhedron( |
463 | "(x,y,z) : (2 * x - 1 >= 0, x - y - 1 == 0, y - z == 0)" ) |
464 | .isIntegerEmpty()); |
465 | |
466 | // IntegerPolyhedron::isEmpty() does not detect the following sets to be |
467 | // empty. |
468 | |
469 | // 3x + 7y = 1 and 0 <= x, y <= 10. |
470 | // Since x and y are non-negative, 3x + 7y can never be 1. |
471 | EXPECT_TRUE(parseIntegerPolyhedron( |
472 | "(x,y) : (x >= 0, -x + 10 >= 0, y >= 0, -y + 10 >= 0, " |
473 | "3 * x + 7 * y - 1 == 0)" ) |
474 | .isIntegerEmpty()); |
475 | |
476 | // 2x = 3y and y = x - 1 and x + y = 6z + 2 and 0 <= x, y <= 100. |
477 | // Substituting y = x - 1 in 3y = 2x, we obtain x = 3 and hence y = 2. |
478 | // Since x + y = 5 cannot be equal to 6z + 2 for any z, the set is empty. |
479 | EXPECT_TRUE(parseIntegerPolyhedron( |
480 | "(x,y,z) : (x >= 0, -x + 100 >= 0, y >= 0, -y + 100 >= 0, " |
481 | "2 * x - 3 * y == 0, x - y - 1 == 0, x + y - 6 * z - 2 == 0)" ) |
482 | .isIntegerEmpty()); |
483 | |
484 | // 2x = 3y and y = x - 1 + 6z and x + y = 6q + 2 and 0 <= x, y <= 100. |
485 | // 2x = 3y implies x is a multiple of 3 and y is even. |
486 | // Now y = x - 1 + 6z implies y = 2 mod 3. In fact, since y is even, we have |
487 | // y = 2 mod 6. Then since x = y + 1 + 6z, we have x = 3 mod 6, implying |
488 | // x + y = 5 mod 6, which contradicts x + y = 6q + 2, so the set is empty. |
489 | EXPECT_TRUE( |
490 | parseIntegerPolyhedron( |
491 | "(x,y,z,q) : (x >= 0, -x + 100 >= 0, y >= 0, -y + 100 >= 0, " |
492 | "2 * x - 3 * y == 0, x - y + 6 * z - 1 == 0, x + y - 6 * q - 2 == 0)" ) |
493 | .isIntegerEmpty()); |
494 | |
495 | // Set with symbols. |
496 | EXPECT_FALSE(parseIntegerPolyhedron("(x)[s] : (x + s >= 0, x - s == 0)" ) |
497 | .isIntegerEmpty()); |
498 | } |
499 | |
500 | TEST(IntegerPolyhedronTest, removeRedundantConstraintsTest) { |
501 | IntegerPolyhedron poly = |
502 | parseIntegerPolyhedron(str: "(x) : (x - 2 >= 0, -x + 2 >= 0, x - 2 == 0)" ); |
503 | poly.removeRedundantConstraints(); |
504 | |
505 | // Both inequalities are redundant given the equality. Both have been removed. |
506 | EXPECT_EQ(poly.getNumInequalities(), 0u); |
507 | EXPECT_EQ(poly.getNumEqualities(), 1u); |
508 | |
509 | IntegerPolyhedron poly2 = |
510 | parseIntegerPolyhedron(str: "(x,y) : (x - 3 >= 0, y - 2 >= 0, x - y == 0)" ); |
511 | poly2.removeRedundantConstraints(); |
512 | |
513 | // The second inequality is redundant and should have been removed. The |
514 | // remaining inequality should be the first one. |
515 | EXPECT_EQ(poly2.getNumInequalities(), 1u); |
516 | EXPECT_THAT(poly2.getInequality(0), ElementsAre(1, 0, -3)); |
517 | EXPECT_EQ(poly2.getNumEqualities(), 1u); |
518 | |
519 | IntegerPolyhedron poly3 = |
520 | parseIntegerPolyhedron(str: "(x,y,z) : (x - y == 0, x - z == 0, y - z == 0)" ); |
521 | poly3.removeRedundantConstraints(); |
522 | |
523 | // One of the three equalities can be removed. |
524 | EXPECT_EQ(poly3.getNumInequalities(), 0u); |
525 | EXPECT_EQ(poly3.getNumEqualities(), 2u); |
526 | |
527 | IntegerPolyhedron poly4 = parseIntegerPolyhedron( |
528 | str: "(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q) : (" |
529 | "b - 1 >= 0," |
530 | "-b + 500 >= 0," |
531 | "-16 * d + f >= 0," |
532 | "f - 1 >= 0," |
533 | "-f + 998 >= 0," |
534 | "16 * d - f + 15 >= 0," |
535 | "-16 * e + g >= 0," |
536 | "g - 1 >= 0," |
537 | "-g + 998 >= 0," |
538 | "16 * e - g + 15 >= 0," |
539 | "h >= 0," |
540 | "-h + 1 >= 0," |
541 | "j - 1 >= 0," |
542 | "-j + 500 >= 0," |
543 | "-f + 16 * l + 15 >= 0," |
544 | "f - 16 * l >= 0," |
545 | "-16 * m + o >= 0," |
546 | "o - 1 >= 0," |
547 | "-o + 998 >= 0," |
548 | "16 * m - o + 15 >= 0," |
549 | "p >= 0," |
550 | "-p + 1 >= 0," |
551 | "-g - h + 8 * q + 8 >= 0," |
552 | "-o - p + 8 * q + 8 >= 0," |
553 | "o + p - 8 * q - 1 >= 0," |
554 | "g + h - 8 * q - 1 >= 0," |
555 | "-f + n >= 0," |
556 | "f - n >= 0," |
557 | "k - 10 >= 0," |
558 | "-k + 10 >= 0," |
559 | "i - 13 >= 0," |
560 | "-i + 13 >= 0," |
561 | "c - 10 >= 0," |
562 | "-c + 10 >= 0," |
563 | "a - 13 >= 0," |
564 | "-a + 13 >= 0" |
565 | ")" ); |
566 | |
567 | // The above is a large set of constraints without any redundant constraints, |
568 | // as verified by the Fourier-Motzkin based removeRedundantInequalities. |
569 | unsigned nIneq = poly4.getNumInequalities(); |
570 | unsigned nEq = poly4.getNumEqualities(); |
571 | poly4.removeRedundantInequalities(); |
572 | ASSERT_EQ(poly4.getNumInequalities(), nIneq); |
573 | ASSERT_EQ(poly4.getNumEqualities(), nEq); |
574 | // Now we test that removeRedundantConstraints does not find any constraints |
575 | // to be redundant either. |
576 | poly4.removeRedundantConstraints(); |
577 | EXPECT_EQ(poly4.getNumInequalities(), nIneq); |
578 | EXPECT_EQ(poly4.getNumEqualities(), nEq); |
579 | |
580 | IntegerPolyhedron poly5 = parseIntegerPolyhedron( |
581 | str: "(x,y) : (128 * x + 127 >= 0, -x + 7 >= 0, -128 * x + y >= 0, y >= 0)" ); |
582 | // 128x + 127 >= 0 implies that 128x >= 0, since x has to be an integer. |
583 | // (This should be caught by GCDTightenInqualities().) |
584 | // So -128x + y >= 0 and 128x + 127 >= 0 imply y >= 0 since we have |
585 | // y >= 128x >= 0. |
586 | poly5.removeRedundantConstraints(); |
587 | EXPECT_EQ(poly5.getNumInequalities(), 3u); |
588 | SmallVector<MPInt, 8> redundantConstraint = getMPIntVec(range: {0, 1, 0}); |
589 | for (unsigned i = 0; i < 3; ++i) { |
590 | // Ensure that the removed constraint was the redundant constraint [3]. |
591 | EXPECT_NE(poly5.getInequality(i), ArrayRef<MPInt>(redundantConstraint)); |
592 | } |
593 | } |
594 | |
595 | TEST(IntegerPolyhedronTest, addConstantUpperBound) { |
596 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 2)); |
597 | poly.addBound(type: BoundType::UB, pos: 0, value: 1); |
598 | EXPECT_EQ(poly.atIneq(0, 0), -1); |
599 | EXPECT_EQ(poly.atIneq(0, 1), 0); |
600 | EXPECT_EQ(poly.atIneq(0, 2), 1); |
601 | |
602 | poly.addBound(type: BoundType::UB, expr: {1, 2, 3}, value: 1); |
603 | EXPECT_EQ(poly.atIneq(1, 0), -1); |
604 | EXPECT_EQ(poly.atIneq(1, 1), -2); |
605 | EXPECT_EQ(poly.atIneq(1, 2), -2); |
606 | } |
607 | |
608 | TEST(IntegerPolyhedronTest, addConstantLowerBound) { |
609 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 2)); |
610 | poly.addBound(type: BoundType::LB, pos: 0, value: 1); |
611 | EXPECT_EQ(poly.atIneq(0, 0), 1); |
612 | EXPECT_EQ(poly.atIneq(0, 1), 0); |
613 | EXPECT_EQ(poly.atIneq(0, 2), -1); |
614 | |
615 | poly.addBound(type: BoundType::LB, expr: {1, 2, 3}, value: 1); |
616 | EXPECT_EQ(poly.atIneq(1, 0), 1); |
617 | EXPECT_EQ(poly.atIneq(1, 1), 2); |
618 | EXPECT_EQ(poly.atIneq(1, 2), 2); |
619 | } |
620 | |
621 | /// Check if the expected division representation of local variables matches the |
622 | /// computed representation. The expected division representation is given as |
623 | /// a vector of expressions set in `expectedDividends` and the corressponding |
624 | /// denominator in `expectedDenominators`. The `denominators` and `dividends` |
625 | /// obtained through `getLocalRepr` function is verified against the |
626 | /// `expectedDenominators` and `expectedDividends` respectively. |
627 | static void checkDivisionRepresentation( |
628 | IntegerPolyhedron &poly, |
629 | const std::vector<SmallVector<int64_t, 8>> &expectedDividends, |
630 | ArrayRef<int64_t> expectedDenominators) { |
631 | DivisionRepr divs = poly.getLocalReprs(); |
632 | |
633 | // Check that the `denominators` and `expectedDenominators` match. |
634 | EXPECT_EQ(ArrayRef<MPInt>(getMPIntVec(expectedDenominators)), |
635 | divs.getDenoms()); |
636 | |
637 | // Check that the `dividends` and `expectedDividends` match. If the |
638 | // denominator for a division is zero, we ignore its dividend. |
639 | EXPECT_TRUE(divs.getNumDivs() == expectedDividends.size()); |
640 | for (unsigned i = 0, e = divs.getNumDivs(); i < e; ++i) { |
641 | if (divs.hasRepr(i)) { |
642 | for (unsigned j = 0, f = divs.getNumVars() + 1; j < f; ++j) { |
643 | EXPECT_TRUE(expectedDividends[i][j] == divs.getDividend(i)[j]); |
644 | } |
645 | } |
646 | } |
647 | } |
648 | |
649 | TEST(IntegerPolyhedronTest, computeLocalReprSimple) { |
650 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 1)); |
651 | |
652 | poly.addLocalFloorDiv(dividend: {1, 4}, divisor: 10); |
653 | poly.addLocalFloorDiv(dividend: {1, 0, 100}, divisor: 10); |
654 | |
655 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 0, 0, 4}, |
656 | {1, 0, 0, 100}}; |
657 | SmallVector<int64_t, 8> denoms = {10, 10}; |
658 | |
659 | // Check if floordivs can be computed when no other inequalities exist |
660 | // and floor divs do not depend on each other. |
661 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
662 | } |
663 | |
664 | TEST(IntegerPolyhedronTest, computeLocalReprConstantFloorDiv) { |
665 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 4)); |
666 | |
667 | poly.addInequality(inEq: {1, 0, 3, 1, 2}); |
668 | poly.addInequality(inEq: {1, 2, -8, 1, 10}); |
669 | poly.addEquality(eq: {1, 2, -4, 1, 10}); |
670 | |
671 | poly.addLocalFloorDiv(dividend: {0, 0, 0, 0, 100}, divisor: 30); |
672 | poly.addLocalFloorDiv(dividend: {0, 0, 0, 0, 0, 206}, divisor: 101); |
673 | |
674 | std::vector<SmallVector<int64_t, 8>> divisions = {{0, 0, 0, 0, 0, 0, 3}, |
675 | {0, 0, 0, 0, 0, 0, 2}}; |
676 | SmallVector<int64_t, 8> denoms = {1, 1}; |
677 | |
678 | // Check if floordivs with constant numerator can be computed. |
679 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
680 | } |
681 | |
682 | TEST(IntegerPolyhedronTest, computeLocalReprRecursive) { |
683 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 4)); |
684 | poly.addInequality(inEq: {1, 0, 3, 1, 2}); |
685 | poly.addInequality(inEq: {1, 2, -8, 1, 10}); |
686 | poly.addEquality(eq: {1, 2, -4, 1, 10}); |
687 | |
688 | poly.addLocalFloorDiv(dividend: {0, -2, 7, 2, 10}, divisor: 3); |
689 | poly.addLocalFloorDiv(dividend: {3, 0, 9, 2, 2, 10}, divisor: 5); |
690 | poly.addLocalFloorDiv(dividend: {0, 1, -123, 2, 0, -4, 10}, divisor: 3); |
691 | |
692 | poly.addInequality(inEq: {1, 2, -2, 1, -5, 0, 6, 100}); |
693 | poly.addInequality(inEq: {1, 2, -8, 1, 3, 7, 0, -9}); |
694 | |
695 | std::vector<SmallVector<int64_t, 8>> divisions = { |
696 | {0, -2, 7, 2, 0, 0, 0, 10}, |
697 | {3, 0, 9, 2, 2, 0, 0, 10}, |
698 | {0, 1, -123, 2, 0, -4, 0, 10}}; |
699 | |
700 | SmallVector<int64_t, 8> denoms = {3, 5, 3}; |
701 | |
702 | // Check if floordivs which may depend on other floordivs can be computed. |
703 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
704 | } |
705 | |
706 | TEST(IntegerPolyhedronTest, computeLocalReprTightUpperBound) { |
707 | { |
708 | IntegerPolyhedron poly = parseIntegerPolyhedron(str: "(i) : (i mod 3 - 1 >= 0)" ); |
709 | |
710 | // The set formed by the poly is: |
711 | // 3q - i + 2 >= 0 <-- Division lower bound |
712 | // -3q + i - 1 >= 0 |
713 | // -3q + i >= 0 <-- Division upper bound |
714 | // We remove redundant constraints to get the set: |
715 | // 3q - i + 2 >= 0 <-- Division lower bound |
716 | // -3q + i - 1 >= 0 <-- Tighter division upper bound |
717 | // thus, making the upper bound tighter. |
718 | poly.removeRedundantConstraints(); |
719 | |
720 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 0, 0}}; |
721 | SmallVector<int64_t, 8> denoms = {3}; |
722 | |
723 | // Check if the divisions can be computed even with a tighter upper bound. |
724 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
725 | } |
726 | |
727 | { |
728 | IntegerPolyhedron poly = parseIntegerPolyhedron( |
729 | str: "(i, j, q) : (4*q - i - j + 2 >= 0, -4*q + i + j >= 0)" ); |
730 | // Convert `q` to a local variable. |
731 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 2, varLimit: 3); |
732 | |
733 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 1, 0, 1}}; |
734 | SmallVector<int64_t, 8> denoms = {4}; |
735 | |
736 | // Check if the divisions can be computed even with a tighter upper bound. |
737 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
738 | } |
739 | } |
740 | |
741 | TEST(IntegerPolyhedronTest, computeLocalReprFromEquality) { |
742 | { |
743 | IntegerPolyhedron poly = |
744 | parseIntegerPolyhedron(str: "(i, j, q) : (-4*q + i + j == 0)" ); |
745 | // Convert `q` to a local variable. |
746 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 2, varLimit: 3); |
747 | |
748 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 1, 0, 0}}; |
749 | SmallVector<int64_t, 8> denoms = {4}; |
750 | |
751 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
752 | } |
753 | { |
754 | IntegerPolyhedron poly = |
755 | parseIntegerPolyhedron(str: "(i, j, q) : (4*q - i - j == 0)" ); |
756 | // Convert `q` to a local variable. |
757 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 2, varLimit: 3); |
758 | |
759 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 1, 0, 0}}; |
760 | SmallVector<int64_t, 8> denoms = {4}; |
761 | |
762 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
763 | } |
764 | { |
765 | IntegerPolyhedron poly = |
766 | parseIntegerPolyhedron(str: "(i, j, q) : (3*q + i + j - 2 == 0)" ); |
767 | // Convert `q` to a local variable. |
768 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 2, varLimit: 3); |
769 | |
770 | std::vector<SmallVector<int64_t, 8>> divisions = {{-1, -1, 0, 2}}; |
771 | SmallVector<int64_t, 8> denoms = {3}; |
772 | |
773 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
774 | } |
775 | } |
776 | |
777 | TEST(IntegerPolyhedronTest, computeLocalReprFromEqualityAndInequality) { |
778 | { |
779 | IntegerPolyhedron poly = |
780 | parseIntegerPolyhedron(str: "(i, j, q, k) : (-3*k + i + j == 0, 4*q - " |
781 | "i - j + 2 >= 0, -4*q + i + j >= 0)" ); |
782 | // Convert `q` and `k` to local variables. |
783 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 2, varLimit: 4); |
784 | |
785 | std::vector<SmallVector<int64_t, 8>> divisions = {{1, 1, 0, 0, 1}, |
786 | {1, 1, 0, 0, 0}}; |
787 | SmallVector<int64_t, 8> denoms = {4, 3}; |
788 | |
789 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
790 | } |
791 | } |
792 | |
793 | TEST(IntegerPolyhedronTest, computeLocalReprNoRepr) { |
794 | IntegerPolyhedron poly = |
795 | parseIntegerPolyhedron(str: "(x, q) : (x - 3 * q >= 0, -x + 3 * q + 3 >= 0)" ); |
796 | // Convert q to a local variable. |
797 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 1, varLimit: 2); |
798 | |
799 | std::vector<SmallVector<int64_t, 8>> divisions = {{0, 0, 0}}; |
800 | SmallVector<int64_t, 8> denoms = {0}; |
801 | |
802 | // Check that no division is computed. |
803 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
804 | } |
805 | |
806 | TEST(IntegerPolyhedronTest, computeLocalReprNegConstNormalize) { |
807 | IntegerPolyhedron poly = parseIntegerPolyhedron( |
808 | str: "(x, q) : (-1 - 3*x - 6 * q >= 0, 6 + 3*x + 6*q >= 0)" ); |
809 | // Convert q to a local variable. |
810 | poly.convertToLocal(kind: VarKind::SetDim, varStart: 1, varLimit: 2); |
811 | |
812 | // q = floor((-1/3 - x)/2) |
813 | // = floor((1/3) + (-1 - x)/2) |
814 | // = floor((-1 - x)/2). |
815 | std::vector<SmallVector<int64_t, 8>> divisions = {{-1, 0, -1}}; |
816 | SmallVector<int64_t, 8> denoms = {2}; |
817 | checkDivisionRepresentation(poly, expectedDividends: divisions, expectedDenominators: denoms); |
818 | } |
819 | |
820 | TEST(IntegerPolyhedronTest, simplifyLocalsTest) { |
821 | // (x) : (exists y: 2x + y = 1 and y = 2). |
822 | IntegerPolyhedron poly(PresburgerSpace::getSetSpace(numDims: 1, numSymbols: 0, numLocals: 1)); |
823 | poly.addEquality(eq: {2, 1, -1}); |
824 | poly.addEquality(eq: {0, 1, -2}); |
825 | |
826 | EXPECT_TRUE(poly.isEmpty()); |
827 | |
828 | // (x) : (exists y, z, w: 3x + y = 1 and 2y = z and 3y = w and z = w). |
829 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1, numSymbols: 0, numLocals: 3)); |
830 | poly2.addEquality(eq: {3, 1, 0, 0, -1}); |
831 | poly2.addEquality(eq: {0, 2, -1, 0, 0}); |
832 | poly2.addEquality(eq: {0, 3, 0, -1, 0}); |
833 | poly2.addEquality(eq: {0, 0, 1, -1, 0}); |
834 | |
835 | EXPECT_TRUE(poly2.isEmpty()); |
836 | |
837 | // (x) : (exists y: x >= y + 1 and 2x + y = 0 and y >= -1). |
838 | IntegerPolyhedron poly3(PresburgerSpace::getSetSpace(numDims: 1, numSymbols: 0, numLocals: 1)); |
839 | poly3.addInequality(inEq: {1, -1, -1}); |
840 | poly3.addInequality(inEq: {0, 1, 1}); |
841 | poly3.addEquality(eq: {2, 1, 0}); |
842 | |
843 | EXPECT_TRUE(poly3.isEmpty()); |
844 | } |
845 | |
846 | TEST(IntegerPolyhedronTest, mergeDivisionsSimple) { |
847 | { |
848 | // (x) : (exists z, y = [x / 2] : x = 3y and x + z + 1 >= 0). |
849 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1, numSymbols: 0, numLocals: 1)); |
850 | poly1.addLocalFloorDiv(dividend: {1, 0, 0}, divisor: 2); // y = [x / 2]. |
851 | poly1.addEquality(eq: {1, 0, -3, 0}); // x = 3y. |
852 | poly1.addInequality(inEq: {1, 1, 0, 1}); // x + z + 1 >= 0. |
853 | |
854 | // (x) : (exists y = [x / 2], z : x = 5y). |
855 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
856 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
857 | poly2.addEquality(eq: {1, -5, 0}); // x = 5y. |
858 | poly2.appendVar(kind: VarKind::Local); // Add local id z. |
859 | |
860 | poly1.mergeLocalVars(other&: poly2); |
861 | |
862 | // Local space should be same. |
863 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
864 | |
865 | // 1 division should be matched + 2 unmatched local ids. |
866 | EXPECT_EQ(poly1.getNumLocalVars(), 3u); |
867 | EXPECT_EQ(poly2.getNumLocalVars(), 3u); |
868 | } |
869 | |
870 | { |
871 | // (x) : (exists z = [x / 5], y = [x / 2] : x = 3y). |
872 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
873 | poly1.addLocalFloorDiv(dividend: {1, 0}, divisor: 5); // z = [x / 5]. |
874 | poly1.addLocalFloorDiv(dividend: {1, 0, 0}, divisor: 2); // y = [x / 2]. |
875 | poly1.addEquality(eq: {1, 0, -3, 0}); // x = 3y. |
876 | |
877 | // (x) : (exists y = [x / 2], z = [x / 5]: x = 5z). |
878 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
879 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
880 | poly2.addLocalFloorDiv(dividend: {1, 0, 0}, divisor: 5); // z = [x / 5]. |
881 | poly2.addEquality(eq: {1, 0, -5, 0}); // x = 5z. |
882 | |
883 | poly1.mergeLocalVars(other&: poly2); |
884 | |
885 | // Local space should be same. |
886 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
887 | |
888 | // 2 divisions should be matched. |
889 | EXPECT_EQ(poly1.getNumLocalVars(), 2u); |
890 | EXPECT_EQ(poly2.getNumLocalVars(), 2u); |
891 | } |
892 | |
893 | { |
894 | // Division Normalization test. |
895 | // (x) : (exists z, y = [x / 2] : x = 3y and x + z + 1 >= 0). |
896 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1, numSymbols: 0, numLocals: 1)); |
897 | // This division would be normalized. |
898 | poly1.addLocalFloorDiv(dividend: {3, 0, 0}, divisor: 6); // y = [3x / 6] -> [x/2]. |
899 | poly1.addEquality(eq: {1, 0, -3, 0}); // x = 3z. |
900 | poly1.addInequality(inEq: {1, 1, 0, 1}); // x + y + 1 >= 0. |
901 | |
902 | // (x) : (exists y = [x / 2], z : x = 5y). |
903 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
904 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
905 | poly2.addEquality(eq: {1, -5, 0}); // x = 5y. |
906 | poly2.appendVar(kind: VarKind::Local); // Add local id z. |
907 | |
908 | poly1.mergeLocalVars(other&: poly2); |
909 | |
910 | // Local space should be same. |
911 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
912 | |
913 | // One division should be matched + 2 unmatched local ids. |
914 | EXPECT_EQ(poly1.getNumLocalVars(), 3u); |
915 | EXPECT_EQ(poly2.getNumLocalVars(), 3u); |
916 | } |
917 | } |
918 | |
919 | TEST(IntegerPolyhedronTest, mergeDivisionsNestedDivsions) { |
920 | { |
921 | // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z >= x). |
922 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
923 | poly1.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
924 | poly1.addLocalFloorDiv(dividend: {1, 1, 0}, divisor: 3); // z = [x + y / 3]. |
925 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
926 | |
927 | // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z <= x). |
928 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
929 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
930 | poly2.addLocalFloorDiv(dividend: {1, 1, 0}, divisor: 3); // z = [x + y / 3]. |
931 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
932 | |
933 | poly1.mergeLocalVars(other&: poly2); |
934 | |
935 | // Local space should be same. |
936 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
937 | |
938 | // 2 divisions should be matched. |
939 | EXPECT_EQ(poly1.getNumLocalVars(), 2u); |
940 | EXPECT_EQ(poly2.getNumLocalVars(), 2u); |
941 | } |
942 | |
943 | { |
944 | // (x) : (exists y = [x / 2], z = [x + y / 3], w = [z + 1 / 5]: y + z >= x). |
945 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
946 | poly1.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
947 | poly1.addLocalFloorDiv(dividend: {1, 1, 0}, divisor: 3); // z = [x + y / 3]. |
948 | poly1.addLocalFloorDiv(dividend: {0, 0, 1, 1}, divisor: 5); // w = [z + 1 / 5]. |
949 | poly1.addInequality(inEq: {-1, 1, 1, 0, 0}); // y + z >= x. |
950 | |
951 | // (x) : (exists y = [x / 2], z = [x + y / 3], w = [z + 1 / 5]: y + z <= x). |
952 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
953 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
954 | poly2.addLocalFloorDiv(dividend: {1, 1, 0}, divisor: 3); // z = [x + y / 3]. |
955 | poly2.addLocalFloorDiv(dividend: {0, 0, 1, 1}, divisor: 5); // w = [z + 1 / 5]. |
956 | poly2.addInequality(inEq: {1, -1, -1, 0, 0}); // y + z <= x. |
957 | |
958 | poly1.mergeLocalVars(other&: poly2); |
959 | |
960 | // Local space should be same. |
961 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
962 | |
963 | // 3 divisions should be matched. |
964 | EXPECT_EQ(poly1.getNumLocalVars(), 3u); |
965 | EXPECT_EQ(poly2.getNumLocalVars(), 3u); |
966 | } |
967 | { |
968 | // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z >= x). |
969 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
970 | poly1.addLocalFloorDiv(dividend: {2, 0}, divisor: 4); // y = [2x / 4] -> [x / 2]. |
971 | poly1.addLocalFloorDiv(dividend: {1, 1, 0}, divisor: 3); // z = [x + y / 3]. |
972 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
973 | |
974 | // (x) : (exists y = [x / 2], z = [x + y / 3]: y + z <= x). |
975 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
976 | poly2.addLocalFloorDiv(dividend: {1, 0}, divisor: 2); // y = [x / 2]. |
977 | // This division would be normalized. |
978 | poly2.addLocalFloorDiv(dividend: {3, 3, 0}, divisor: 9); // z = [3x + 3y / 9] -> [x + y / 3]. |
979 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
980 | |
981 | poly1.mergeLocalVars(other&: poly2); |
982 | |
983 | // Local space should be same. |
984 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
985 | |
986 | // 2 divisions should be matched. |
987 | EXPECT_EQ(poly1.getNumLocalVars(), 2u); |
988 | EXPECT_EQ(poly2.getNumLocalVars(), 2u); |
989 | } |
990 | } |
991 | |
992 | TEST(IntegerPolyhedronTest, mergeDivisionsConstants) { |
993 | { |
994 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z >= x). |
995 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
996 | poly1.addLocalFloorDiv(dividend: {1, 1}, divisor: 2); // y = [x + 1 / 2]. |
997 | poly1.addLocalFloorDiv(dividend: {1, 0, 2}, divisor: 3); // z = [x + 2 / 3]. |
998 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
999 | |
1000 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). |
1001 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
1002 | poly2.addLocalFloorDiv(dividend: {1, 1}, divisor: 2); // y = [x + 1 / 2]. |
1003 | poly2.addLocalFloorDiv(dividend: {1, 0, 2}, divisor: 3); // z = [x + 2 / 3]. |
1004 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
1005 | |
1006 | poly1.mergeLocalVars(other&: poly2); |
1007 | |
1008 | // Local space should be same. |
1009 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
1010 | |
1011 | // 2 divisions should be matched. |
1012 | EXPECT_EQ(poly1.getNumLocalVars(), 2u); |
1013 | EXPECT_EQ(poly2.getNumLocalVars(), 2u); |
1014 | } |
1015 | { |
1016 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z >= x). |
1017 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
1018 | poly1.addLocalFloorDiv(dividend: {1, 1}, divisor: 2); // y = [x + 1 / 2]. |
1019 | // Normalization test. |
1020 | poly1.addLocalFloorDiv(dividend: {3, 0, 6}, divisor: 9); // z = [3x + 6 / 9] -> [x + 2 / 3]. |
1021 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
1022 | |
1023 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). |
1024 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
1025 | // Normalization test. |
1026 | poly2.addLocalFloorDiv(dividend: {2, 2}, divisor: 4); // y = [2x + 2 / 4] -> [x + 1 / 2]. |
1027 | poly2.addLocalFloorDiv(dividend: {1, 0, 2}, divisor: 3); // z = [x + 2 / 3]. |
1028 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
1029 | |
1030 | poly1.mergeLocalVars(other&: poly2); |
1031 | |
1032 | // Local space should be same. |
1033 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
1034 | |
1035 | // 2 divisions should be matched. |
1036 | EXPECT_EQ(poly1.getNumLocalVars(), 2u); |
1037 | EXPECT_EQ(poly2.getNumLocalVars(), 2u); |
1038 | } |
1039 | } |
1040 | |
1041 | TEST(IntegerPolyhedronTest, mergeDivisionsDuplicateInSameSet) { |
1042 | // (x) : (exists y = [x + 1 / 3], z = [x + 1 / 3]: y + z >= x). |
1043 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
1044 | poly1.addLocalFloorDiv(dividend: {1, 1}, divisor: 3); // y = [x + 1 / 2]. |
1045 | poly1.addLocalFloorDiv(dividend: {1, 0, 1}, divisor: 3); // z = [x + 1 / 3]. |
1046 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
1047 | |
1048 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). |
1049 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
1050 | poly2.addLocalFloorDiv(dividend: {1, 1}, divisor: 3); // y = [x + 1 / 3]. |
1051 | poly2.addLocalFloorDiv(dividend: {1, 0, 2}, divisor: 3); // z = [x + 2 / 3]. |
1052 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
1053 | |
1054 | poly1.mergeLocalVars(other&: poly2); |
1055 | |
1056 | // Local space should be same. |
1057 | EXPECT_EQ(poly1.getNumLocalVars(), poly2.getNumLocalVars()); |
1058 | |
1059 | // 1 divisions should be matched. |
1060 | EXPECT_EQ(poly1.getNumLocalVars(), 3u); |
1061 | EXPECT_EQ(poly2.getNumLocalVars(), 3u); |
1062 | } |
1063 | |
1064 | TEST(IntegerPolyhedronTest, negativeDividends) { |
1065 | // (x) : (exists y = [-x + 1 / 2], z = [-x - 2 / 3]: y + z >= x). |
1066 | IntegerPolyhedron poly1(PresburgerSpace::getSetSpace(numDims: 1)); |
1067 | poly1.addLocalFloorDiv(dividend: {-1, 1}, divisor: 2); // y = [x + 1 / 2]. |
1068 | // Normalization test with negative dividends |
1069 | poly1.addLocalFloorDiv(dividend: {-3, 0, -6}, divisor: 9); // z = [3x + 6 / 9] -> [x + 2 / 3]. |
1070 | poly1.addInequality(inEq: {-1, 1, 1, 0}); // y + z >= x. |
1071 | |
1072 | // (x) : (exists y = [x + 1 / 3], z = [x + 2 / 3]: y + z <= x). |
1073 | IntegerPolyhedron poly2(PresburgerSpace::getSetSpace(numDims: 1)); |
1074 | // Normalization test. |
1075 | poly2.addLocalFloorDiv(dividend: {-2, 2}, divisor: 4); // y = [-2x + 2 / 4] -> [-x + 1 / 2]. |
1076 | poly2.addLocalFloorDiv(dividend: {-1, 0, -2}, divisor: 3); // z = [-x - 2 / 3]. |
1077 | poly2.addInequality(inEq: {1, -1, -1, 0}); // y + z <= x. |
1078 | |
1079 | poly1.mergeLocalVars(other&: poly2); |
1080 | |
1081 | // Merging triggers normalization. |
1082 | std::vector<SmallVector<int64_t, 8>> divisions = {{-1, 0, 0, 1}, |
1083 | {-1, 0, 0, -2}}; |
1084 | SmallVector<int64_t, 8> denoms = {2, 3}; |
1085 | checkDivisionRepresentation(poly&: poly1, expectedDividends: divisions, expectedDenominators: denoms); |
1086 | } |
1087 | |
1088 | void expectRationalLexMin(const IntegerPolyhedron &poly, |
1089 | ArrayRef<Fraction> min) { |
1090 | auto lexMin = poly.findRationalLexMin(); |
1091 | ASSERT_TRUE(lexMin.isBounded()); |
1092 | EXPECT_EQ(ArrayRef<Fraction>(*lexMin), min); |
1093 | } |
1094 | |
1095 | void expectNoRationalLexMin(OptimumKind kind, const IntegerPolyhedron &poly) { |
1096 | ASSERT_NE(kind, OptimumKind::Bounded) |
1097 | << "Use expectRationalLexMin for bounded min" ; |
1098 | EXPECT_EQ(poly.findRationalLexMin().getKind(), kind); |
1099 | } |
1100 | |
1101 | TEST(IntegerPolyhedronTest, findRationalLexMin) { |
1102 | expectRationalLexMin( |
1103 | poly: parseIntegerPolyhedron( |
1104 | str: "(x, y, z) : (x + 10 >= 0, y + 40 >= 0, z + 30 >= 0)" ), |
1105 | min: {{-10, 1}, {-40, 1}, {-30, 1}}); |
1106 | expectRationalLexMin( |
1107 | poly: parseIntegerPolyhedron( |
1108 | str: "(x, y, z) : (2*x + 7 >= 0, 3*y - 5 >= 0, 8*z + 10 >= 0, 9*z >= 0)" ), |
1109 | min: {{-7, 2}, {5, 3}, {0, 1}}); |
1110 | expectRationalLexMin( |
1111 | poly: parseIntegerPolyhedron(str: "(x, y) : (3*x + 2*y + 10 >= 0, -3*y + 10 >= " |
1112 | "0, 4*x - 7*y - 10 >= 0)" ), |
1113 | min: {{-50, 29}, {-70, 29}}); |
1114 | |
1115 | // Test with some locals. This is basically x >= 11, 0 <= x - 2e <= 1. |
1116 | // It'll just choose x = 11, e = 5.5 since it's rational lexmin. |
1117 | expectRationalLexMin( |
1118 | poly: parseIntegerPolyhedron( |
1119 | str: "(x, y) : (x - 2*(x floordiv 2) == 0, y - 2*x >= 0, x - 11 >= 0)" ), |
1120 | min: {{11, 1}, {22, 1}}); |
1121 | |
1122 | expectRationalLexMin( |
1123 | poly: parseIntegerPolyhedron(str: "(x, y) : (3*x + 2*y + 10 >= 0," |
1124 | "-4*x + 7*y + 10 >= 0, -3*y + 10 >= 0)" ), |
1125 | min: {{-50, 9}, {10, 3}}); |
1126 | |
1127 | // Cartesian product of above with itself. |
1128 | expectRationalLexMin( |
1129 | poly: parseIntegerPolyhedron( |
1130 | str: "(x, y, z, w) : (3*x + 2*y + 10 >= 0, -4*x + 7*y + 10 >= 0," |
1131 | "-3*y + 10 >= 0, 3*z + 2*w + 10 >= 0, -4*z + 7*w + 10 >= 0," |
1132 | "-3*w + 10 >= 0)" ), |
1133 | min: {{-50, 9}, {10, 3}, {-50, 9}, {10, 3}}); |
1134 | |
1135 | // Same as above but for the constraints on z and w, we express "10" in terms |
1136 | // of x and y. We know that x and y still have to take the values |
1137 | // -50/9 and 10/3 since their constraints are the same and their values are |
1138 | // minimized first. Accordingly, the values -9x - 12y, -9x - 0y - 10, |
1139 | // and -9x - 15y + 10 are all equal to 10. |
1140 | expectRationalLexMin( |
1141 | poly: parseIntegerPolyhedron( |
1142 | str: "(x, y, z, w) : (3*x + 2*y + 10 >= 0, -4*x + 7*y + 10 >= 0, " |
1143 | "-3*y + 10 >= 0, 3*z + 2*w - 9*x - 12*y >= 0," |
1144 | "-4*z + 7*w + - 9*x - 9*y - 10 >= 0, -3*w - 9*x - 15*y + 10 >= 0)" ), |
1145 | min: {{-50, 9}, {10, 3}, {-50, 9}, {10, 3}}); |
1146 | |
1147 | // Same as above with one constraint removed, making the lexmin unbounded. |
1148 | expectNoRationalLexMin( |
1149 | kind: OptimumKind::Unbounded, |
1150 | poly: parseIntegerPolyhedron( |
1151 | str: "(x, y, z, w) : (3*x + 2*y + 10 >= 0, -4*x + 7*y + 10 >= 0," |
1152 | "-3*y + 10 >= 0, 3*z + 2*w - 9*x - 12*y >= 0," |
1153 | "-4*z + 7*w + - 9*x - 9*y - 10>= 0)" )); |
1154 | |
1155 | // Again, the lexmin is unbounded. |
1156 | expectNoRationalLexMin( |
1157 | kind: OptimumKind::Unbounded, |
1158 | poly: parseIntegerPolyhedron( |
1159 | str: "(x, y, z) : (2*x + 5*y + 8*z - 10 >= 0," |
1160 | "2*x + 10*y + 8*z - 10 >= 0, 2*x + 5*y + 10*z - 10 >= 0)" )); |
1161 | |
1162 | // The set is empty. |
1163 | expectNoRationalLexMin( |
1164 | kind: OptimumKind::Empty, |
1165 | poly: parseIntegerPolyhedron(str: "(x) : (2*x >= 0, -x - 1 >= 0)" )); |
1166 | } |
1167 | |
1168 | void expectIntegerLexMin(const IntegerPolyhedron &poly, ArrayRef<int64_t> min) { |
1169 | MaybeOptimum<SmallVector<MPInt, 8>> lexMin = poly.findIntegerLexMin(); |
1170 | ASSERT_TRUE(lexMin.isBounded()); |
1171 | EXPECT_EQ(*lexMin, getMPIntVec(min)); |
1172 | } |
1173 | |
1174 | void expectNoIntegerLexMin(OptimumKind kind, const IntegerPolyhedron &poly) { |
1175 | ASSERT_NE(kind, OptimumKind::Bounded) |
1176 | << "Use expectRationalLexMin for bounded min" ; |
1177 | EXPECT_EQ(poly.findRationalLexMin().getKind(), kind); |
1178 | } |
1179 | |
1180 | TEST(IntegerPolyhedronTest, findIntegerLexMin) { |
1181 | expectIntegerLexMin( |
1182 | poly: parseIntegerPolyhedron(str: "(x, y, z) : (2*x + 13 >= 0, 4*y - 3*x - 2 >= " |
1183 | "0, 11*z + 5*y - 3*x + 7 >= 0)" ), |
1184 | min: {-6, -4, 0}); |
1185 | // Similar to above but no lower bound on z. |
1186 | expectNoIntegerLexMin( |
1187 | kind: OptimumKind::Unbounded, |
1188 | poly: parseIntegerPolyhedron(str: "(x, y, z) : (2*x + 13 >= 0, 4*y - 3*x - 2 " |
1189 | ">= 0, -11*z + 5*y - 3*x + 7 >= 0)" )); |
1190 | } |
1191 | |
1192 | void expectSymbolicIntegerLexMin( |
1193 | StringRef polyStr, |
1194 | ArrayRef<std::pair<StringRef, StringRef>> expectedLexminRepr, |
1195 | ArrayRef<StringRef> expectedUnboundedDomainRepr) { |
1196 | IntegerPolyhedron poly = parseIntegerPolyhedron(str: polyStr); |
1197 | |
1198 | ASSERT_NE(poly.getNumDimVars(), 0u); |
1199 | ASSERT_NE(poly.getNumSymbolVars(), 0u); |
1200 | |
1201 | SymbolicLexOpt result = poly.findSymbolicIntegerLexMin(); |
1202 | |
1203 | if (expectedLexminRepr.empty()) { |
1204 | EXPECT_TRUE(result.lexopt.getDomain().isIntegerEmpty()); |
1205 | } else { |
1206 | PWMAFunction expectedLexmin = parsePWMAF(pieces: expectedLexminRepr); |
1207 | EXPECT_TRUE(result.lexopt.isEqual(expectedLexmin)); |
1208 | } |
1209 | |
1210 | if (expectedUnboundedDomainRepr.empty()) { |
1211 | EXPECT_TRUE(result.unboundedDomain.isIntegerEmpty()); |
1212 | } else { |
1213 | PresburgerSet expectedUnboundedDomain = |
1214 | parsePresburgerSet(strs: expectedUnboundedDomainRepr); |
1215 | EXPECT_TRUE(result.unboundedDomain.isEqual(expectedUnboundedDomain)); |
1216 | } |
1217 | } |
1218 | |
1219 | void expectSymbolicIntegerLexMin( |
1220 | StringRef polyStr, ArrayRef<std::pair<StringRef, StringRef>> result) { |
1221 | expectSymbolicIntegerLexMin(polyStr, expectedLexminRepr: result, expectedUnboundedDomainRepr: {}); |
1222 | } |
1223 | |
1224 | TEST(IntegerPolyhedronTest, findSymbolicIntegerLexMin) { |
1225 | expectSymbolicIntegerLexMin(polyStr: "(x)[a] : (x - a >= 0)" , |
1226 | result: { |
1227 | {"()[a] : ()" , "()[a] -> (a)" }, |
1228 | }); |
1229 | |
1230 | expectSymbolicIntegerLexMin( |
1231 | polyStr: "(x)[a, b] : (x - a >= 0, x - b >= 0)" , |
1232 | result: { |
1233 | {"()[a, b] : (a - b >= 0)" , "()[a, b] -> (a)" }, |
1234 | {"()[a, b] : (b - a - 1 >= 0)" , "()[a, b] -> (b)" }, |
1235 | }); |
1236 | |
1237 | expectSymbolicIntegerLexMin( |
1238 | polyStr: "(x)[a, b, c] : (x -a >= 0, x - b >= 0, x - c >= 0)" , |
1239 | result: { |
1240 | {"()[a, b, c] : (a - b >= 0, a - c >= 0)" , "()[a, b, c] -> (a)" }, |
1241 | {"()[a, b, c] : (b - a - 1 >= 0, b - c >= 0)" , "()[a, b, c] -> (b)" }, |
1242 | {"()[a, b, c] : (c - a - 1 >= 0, c - b - 1 >= 0)" , |
1243 | "()[a, b, c] -> (c)" }, |
1244 | }); |
1245 | |
1246 | expectSymbolicIntegerLexMin(polyStr: "(x, y)[a] : (x - a >= 0, x + y >= 0)" , |
1247 | result: { |
1248 | {"()[a] : ()" , "()[a] -> (a, -a)" }, |
1249 | }); |
1250 | |
1251 | expectSymbolicIntegerLexMin(polyStr: "(x, y)[a] : (x - a >= 0, x + y >= 0, y >= 0)" , |
1252 | result: { |
1253 | {"()[a] : (a >= 0)" , "()[a] -> (a, 0)" }, |
1254 | {"()[a] : (-a - 1 >= 0)" , "()[a] -> (a, -a)" }, |
1255 | }); |
1256 | |
1257 | expectSymbolicIntegerLexMin( |
1258 | polyStr: "(x, y)[a, b, c] : (x - a >= 0, y - b >= 0, c - x - y >= 0)" , |
1259 | result: { |
1260 | {"()[a, b, c] : (c - a - b >= 0)" , "()[a, b, c] -> (a, b)" }, |
1261 | }); |
1262 | |
1263 | expectSymbolicIntegerLexMin( |
1264 | polyStr: "(x, y, z)[a, b, c] : (c - z >= 0, b - y >= 0, x + y + z - a == 0)" , |
1265 | result: { |
1266 | {"()[a, b, c] : ()" , "()[a, b, c] -> (a - b - c, b, c)" }, |
1267 | }); |
1268 | |
1269 | expectSymbolicIntegerLexMin( |
1270 | polyStr: "(x)[a, b] : (a >= 0, b >= 0, x >= 0, a + b + x - 1 >= 0)" , |
1271 | result: { |
1272 | {"()[a, b] : (a >= 0, b >= 0, a + b - 1 >= 0)" , "()[a, b] -> (0)" }, |
1273 | {"()[a, b] : (a == 0, b == 0)" , "()[a, b] -> (1)" }, |
1274 | }); |
1275 | |
1276 | expectSymbolicIntegerLexMin( |
1277 | polyStr: "(x)[a, b] : (1 - a >= 0, a >= 0, 1 - b >= 0, b >= 0, 1 - x >= 0, x >= " |
1278 | "0, a + b + x - 1 >= 0)" , |
1279 | result: { |
1280 | {"()[a, b] : (1 - a >= 0, a >= 0, 1 - b >= 0, b >= 0, a + b - 1 >= " |
1281 | "0)" , |
1282 | "()[a, b] -> (0)" }, |
1283 | {"()[a, b] : (a == 0, b == 0)" , "()[a, b] -> (1)" }, |
1284 | }); |
1285 | |
1286 | expectSymbolicIntegerLexMin( |
1287 | polyStr: "(x, y, z)[a, b] : (x - a == 0, y - b == 0, x >= 0, y >= 0, z >= 0, x + " |
1288 | "y + z - 1 >= 0)" , |
1289 | result: { |
1290 | {"()[a, b] : (a >= 0, b >= 0, 1 - a - b >= 0)" , |
1291 | "()[a, b] -> (a, b, 1 - a - b)" }, |
1292 | {"()[a, b] : (a >= 0, b >= 0, a + b - 2 >= 0)" , |
1293 | "()[a, b] -> (a, b, 0)" }, |
1294 | }); |
1295 | |
1296 | expectSymbolicIntegerLexMin( |
1297 | polyStr: "(x)[a, b] : (x - a == 0, x - b >= 0)" , |
1298 | result: { |
1299 | {"()[a, b] : (a - b >= 0)" , "()[a, b] -> (a)" }, |
1300 | }); |
1301 | |
1302 | expectSymbolicIntegerLexMin( |
1303 | polyStr: "(q)[a] : (a - 1 - 3*q == 0, q >= 0)" , |
1304 | result: { |
1305 | {"()[a] : (a - 1 - 3*(a floordiv 3) == 0, a >= 0)" , |
1306 | "()[a] -> (a floordiv 3)" }, |
1307 | }); |
1308 | |
1309 | expectSymbolicIntegerLexMin( |
1310 | polyStr: "(r, q)[a] : (a - r - 3*q == 0, q >= 0, 1 - r >= 0, r >= 0)" , |
1311 | result: { |
1312 | {"()[a] : (a - 0 - 3*(a floordiv 3) == 0, a >= 0)" , |
1313 | "()[a] -> (0, a floordiv 3)" }, |
1314 | {"()[a] : (a - 1 - 3*(a floordiv 3) == 0, a >= 0)" , |
1315 | "()[a] -> (1, a floordiv 3)" }, // (1 a floordiv 3) |
1316 | }); |
1317 | |
1318 | expectSymbolicIntegerLexMin( |
1319 | polyStr: "(r, q)[a] : (a - r - 3*q == 0, q >= 0, 2 - r >= 0, r - 1 >= 0)" , |
1320 | result: { |
1321 | {"()[a] : (a - 1 - 3*(a floordiv 3) == 0, a >= 0)" , |
1322 | "()[a] -> (1, a floordiv 3)" }, |
1323 | {"()[a] : (a - 2 - 3*(a floordiv 3) == 0, a >= 0)" , |
1324 | "()[a] -> (2, a floordiv 3)" }, |
1325 | }); |
1326 | |
1327 | expectSymbolicIntegerLexMin( |
1328 | polyStr: "(r, q)[a] : (a - r - 3*q == 0, q >= 0, r >= 0)" , |
1329 | result: { |
1330 | {"()[a] : (a - 3*(a floordiv 3) == 0, a >= 0)" , |
1331 | "()[a] -> (0, a floordiv 3)" }, |
1332 | {"()[a] : (a - 1 - 3*(a floordiv 3) == 0, a >= 0)" , |
1333 | "()[a] -> (1, a floordiv 3)" }, |
1334 | {"()[a] : (a - 2 - 3*(a floordiv 3) == 0, a >= 0)" , |
1335 | "()[a] -> (2, a floordiv 3)" }, |
1336 | }); |
1337 | |
1338 | expectSymbolicIntegerLexMin( |
1339 | polyStr: "(x, y, z, w)[g] : (" |
1340 | // x, y, z, w are boolean variables. |
1341 | "1 - x >= 0, x >= 0, 1 - y >= 0, y >= 0," |
1342 | "1 - z >= 0, z >= 0, 1 - w >= 0, w >= 0," |
1343 | // We have some constraints on them: |
1344 | "x + y + z - 1 >= 0," // x or y or z |
1345 | "x + y + w - 1 >= 0," // x or y or w |
1346 | "1 - x + 1 - y + 1 - w - 1 >= 0," // ~x or ~y or ~w |
1347 | // What's the lexmin solution using exactly g true vars? |
1348 | "g - x - y - z - w == 0)" , |
1349 | result: { |
1350 | {"()[g] : (g - 1 == 0)" , "()[g] -> (0, 1, 0, 0)" }, |
1351 | {"()[g] : (g - 2 == 0)" , "()[g] -> (0, 0, 1, 1)" }, |
1352 | {"()[g] : (g - 3 == 0)" , "()[g] -> (0, 1, 1, 1)" }, |
1353 | }); |
1354 | |
1355 | // Bezout's lemma: if a, b are constants, |
1356 | // the set of values that ax + by can take is all multiples of gcd(a, b). |
1357 | expectSymbolicIntegerLexMin( |
1358 | // If (x, y) is a solution for a given [a, r], then so is (x - 5, y + 2). |
1359 | // So the lexmin is unbounded if it exists. |
1360 | polyStr: "(x, y)[a, r] : (a >= 0, r - a + 14*x + 35*y == 0)" , expectedLexminRepr: {}, |
1361 | // According to Bezout's lemma, 14x + 35y can take on all multiples |
1362 | // of 7 and no other values. So the solution exists iff r - a is a |
1363 | // multiple of 7. |
1364 | expectedUnboundedDomainRepr: {"()[a, r] : (a >= 0, r - a - 7*((r - a) floordiv 7) == 0)" }); |
1365 | |
1366 | // The lexmins are unbounded. |
1367 | expectSymbolicIntegerLexMin(polyStr: "(x, y)[a] : (9*x - 4*y - 2*a >= 0)" , expectedLexminRepr: {}, |
1368 | expectedUnboundedDomainRepr: {"()[a] : ()" }); |
1369 | |
1370 | // Test cases adapted from isl. |
1371 | expectSymbolicIntegerLexMin( |
1372 | // a = 2b - 2(c - b), c - b >= 0. |
1373 | // So b is minimized when c = b. |
1374 | polyStr: "(b, c)[a] : (a - 4*b + 2*c == 0, c - b >= 0)" , |
1375 | result: { |
1376 | {"()[a] : (a - 2*(a floordiv 2) == 0)" , |
1377 | "()[a] -> (a floordiv 2, a floordiv 2)" }, |
1378 | }); |
1379 | |
1380 | expectSymbolicIntegerLexMin( |
1381 | // 0 <= b <= 255, 1 <= a - 512b <= 509, |
1382 | // b + 8 >= 1 + 16*(b + 8 floordiv 16) // i.e. b % 16 != 8 |
1383 | polyStr: "(b)[a] : (255 - b >= 0, b >= 0, a - 512*b - 1 >= 0, 512*b -a + 509 >= " |
1384 | "0, b + 7 - 16*((8 + b) floordiv 16) >= 0)" , |
1385 | result: { |
1386 | {"()[a] : (255 - (a floordiv 512) >= 0, a >= 0, a - 512*(a floordiv " |
1387 | "512) - 1 >= 0, 512*(a floordiv 512) - a + 509 >= 0, (a floordiv " |
1388 | "512) + 7 - 16*((8 + (a floordiv 512)) floordiv 16) >= 0)" , |
1389 | "()[a] -> (a floordiv 512)" }, |
1390 | }); |
1391 | |
1392 | expectSymbolicIntegerLexMin( |
1393 | polyStr: "(a, b)[K, N, x, y] : (N - K - 2 >= 0, K + 4 - N >= 0, x - 4 >= 0, x + 6 " |
1394 | "- 2*N >= 0, K+N - x - 1 >= 0, a - N + 1 >= 0, K+N-1-a >= 0,a + 6 - b - " |
1395 | "N >= 0, 2*N - 4 - a >= 0," |
1396 | "2*N - 3*K + a - b >= 0, 4*N - K + 1 - 3*b >= 0, b - N >= 0, a - x - 1 " |
1397 | ">= 0)" , |
1398 | result: { |
1399 | {"()[K, N, x, y] : (x + 6 - 2*N >= 0, 2*N - 5 - x >= 0, x + 1 -3*K + " |
1400 | "N >= 0, N + K - 2 - x >= 0, x - 4 >= 0)" , |
1401 | "()[K, N, x, y] -> (1 + x, N)" }, |
1402 | }); |
1403 | } |
1404 | |
1405 | static void |
1406 | expectComputedVolumeIsValidOverapprox(const IntegerPolyhedron &poly, |
1407 | std::optional<int64_t> trueVolume, |
1408 | std::optional<int64_t> resultBound) { |
1409 | expectComputedVolumeIsValidOverapprox(computedVolume: poly.computeVolume(), trueVolume, |
1410 | resultBound); |
1411 | } |
1412 | |
1413 | TEST(IntegerPolyhedronTest, computeVolume) { |
1414 | // 0 <= x <= 3 + 1/3, -5.5 <= y <= 2 + 3/5, 3 <= z <= 1.75. |
1415 | // i.e. 0 <= x <= 3, -5 <= y <= 2, 3 <= z <= 3 + 1/4. |
1416 | // So volume is 4 * 8 * 1 = 32. |
1417 | expectComputedVolumeIsValidOverapprox( |
1418 | poly: parseIntegerPolyhedron( |
1419 | str: "(x, y, z) : (x >= 0, -3*x + 10 >= 0, 2*y + 11 >= 0," |
1420 | "-5*y + 13 >= 0, z - 3 >= 0, -4*z + 13 >= 0)" ), |
1421 | /*trueVolume=*/32ull, /*resultBound=*/32ull); |
1422 | |
1423 | // Same as above but y has bounds 2 + 1/5 <= y <= 2 + 3/5. So the volume is |
1424 | // zero. |
1425 | expectComputedVolumeIsValidOverapprox( |
1426 | poly: parseIntegerPolyhedron( |
1427 | str: "(x, y, z) : (x >= 0, -3*x + 10 >= 0, 5*y - 11 >= 0," |
1428 | "-5*y + 13 >= 0, z - 3 >= 0, -4*z + 13 >= 0)" ), |
1429 | /*trueVolume=*/0ull, /*resultBound=*/0ull); |
1430 | |
1431 | // Now x is unbounded below but y still has no integer values. |
1432 | expectComputedVolumeIsValidOverapprox( |
1433 | poly: parseIntegerPolyhedron(str: "(x, y, z) : (-3*x + 10 >= 0, 5*y - 11 >= 0," |
1434 | "-5*y + 13 >= 0, z - 3 >= 0, -4*z + 13 >= 0)" ), |
1435 | /*trueVolume=*/0ull, /*resultBound=*/0ull); |
1436 | |
1437 | // A diamond shape, 0 <= x + y <= 10, 0 <= x - y <= 10, |
1438 | // with vertices at (0, 0), (5, 5), (5, 5), (10, 0). |
1439 | // x and y can take 11 possible values so result computed is 11*11 = 121. |
1440 | expectComputedVolumeIsValidOverapprox( |
1441 | poly: parseIntegerPolyhedron( |
1442 | str: "(x, y) : (x + y >= 0, -x - y + 10 >= 0, x - y >= 0," |
1443 | "-x + y + 10 >= 0)" ), |
1444 | /*trueVolume=*/61ull, /*resultBound=*/121ull); |
1445 | |
1446 | // Effectively the same diamond as above; constrain the variables to be even |
1447 | // and double the constant terms of the constraints. The algorithm can't |
1448 | // eliminate locals exactly, so the result is an overapproximation by |
1449 | // computing that x and y can take 21 possible values so result is 21*21 = |
1450 | // 441. |
1451 | expectComputedVolumeIsValidOverapprox( |
1452 | poly: parseIntegerPolyhedron( |
1453 | str: "(x, y) : (x + y >= 0, -x - y + 20 >= 0, x - y >= 0," |
1454 | " -x + y + 20 >= 0, x - 2*(x floordiv 2) == 0," |
1455 | "y - 2*(y floordiv 2) == 0)" ), |
1456 | /*trueVolume=*/61ull, /*resultBound=*/441ull); |
1457 | |
1458 | // Unbounded polytope. |
1459 | expectComputedVolumeIsValidOverapprox( |
1460 | poly: parseIntegerPolyhedron(str: "(x, y) : (2*x - y >= 0, y - 3*x >= 0)" ), |
1461 | /*trueVolume=*/{}, /*resultBound=*/{}); |
1462 | } |
1463 | |
1464 | bool containsPointNoLocal(const IntegerPolyhedron &poly, |
1465 | ArrayRef<int64_t> point) { |
1466 | return poly.containsPointNoLocal(point: getMPIntVec(range: point)).has_value(); |
1467 | } |
1468 | |
1469 | TEST(IntegerPolyhedronTest, containsPointNoLocal) { |
1470 | IntegerPolyhedron poly1 = |
1471 | parseIntegerPolyhedron(str: "(x) : ((x floordiv 2) - x == 0)" ); |
1472 | EXPECT_TRUE(poly1.containsPointNoLocal({0})); |
1473 | EXPECT_FALSE(poly1.containsPointNoLocal({1})); |
1474 | |
1475 | IntegerPolyhedron poly2 = parseIntegerPolyhedron( |
1476 | str: "(x) : (x - 2*(x floordiv 2) == 0, x - 4*(x floordiv 4) - 2 == 0)" ); |
1477 | EXPECT_TRUE(containsPointNoLocal(poly2, {6})); |
1478 | EXPECT_FALSE(containsPointNoLocal(poly2, {4})); |
1479 | |
1480 | IntegerPolyhedron poly3 = |
1481 | parseIntegerPolyhedron(str: "(x, y) : (2*x - y >= 0, y - 3*x >= 0)" ); |
1482 | |
1483 | EXPECT_TRUE(poly3.containsPointNoLocal(ArrayRef<int64_t>({0, 0}))); |
1484 | EXPECT_FALSE(poly3.containsPointNoLocal({1, 0})); |
1485 | } |
1486 | |
1487 | TEST(IntegerPolyhedronTest, truncateEqualityRegressionTest) { |
1488 | // IntegerRelation::truncate was truncating inequalities to the number of |
1489 | // equalities. |
1490 | IntegerRelation set(PresburgerSpace::getSetSpace(numDims: 1)); |
1491 | IntegerRelation::CountsSnapshot snapshot = set.getCounts(); |
1492 | set.addEquality(eq: {1, 0}); |
1493 | set.truncate(counts: snapshot); |
1494 | EXPECT_EQ(set.getNumEqualities(), 0u); |
1495 | } |
1496 | |