[MLIR][Presburger] Generating functions and quasi-polynomials for Barvinok's algorithm (#75702)

Define basic types and classes for Barvinok's algorithm, including
polyhedra, generating functions and quasi-polynomials.
The class definitions include methods for arithmetic manipulation,
printing, logical relations, etc.
This commit is contained in:
Abhinav271828 2023-12-27 00:59:26 +05:30 committed by GitHub
parent 01bf29b9d0
commit 1022febd9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 481 additions and 0 deletions

View File

@ -0,0 +1,71 @@
//===- QuasiPolynomial.h - QuasiPolynomial Class ----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Definition of the QuasiPolynomial class for Barvinok's algorithm,
// which represents a single-valued function on a set of parameters.
// It is an expression of the form
// f(x) = \sum_i c_i * \prod_j ⌊g_{ij}(x)⌋
// where c_i \in Q and
// g_{ij} : Q^d -> Q are affine functionals over d parameters.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_ANALYSIS_PRESBURGER_QUASIPOLYNOMIAL_H
#define MLIR_ANALYSIS_PRESBURGER_QUASIPOLYNOMIAL_H
#include "mlir/Analysis/Presburger/Fraction.h"
#include "mlir/Analysis/Presburger/PresburgerSpace.h"
namespace mlir {
namespace presburger {
// A class to describe quasi-polynomials.
// A quasipolynomial consists of a set of terms.
// The ith term is a constant `coefficients[i]`, multiplied
// by the product of a set of affine functions on n parameters.
// Represents functions f : Q^n -> Q of the form
//
// f(x) = \sum_i c_i * \prod_j ⌊g_{ij}(x)⌋
//
// where c_i \in Q and
// g_{ij} : Q^n -> Q are affine functionals.
class QuasiPolynomial : public PresburgerSpace {
public:
QuasiPolynomial(unsigned numVars, SmallVector<Fraction> coeffs = {},
std::vector<std::vector<SmallVector<Fraction>>> aff = {});
// Find the number of inputs (numDomain) to the polynomial.
// numSymbols is set to zero.
unsigned getNumInputs() const {
return getNumDomainVars() + getNumSymbolVars();
}
const SmallVector<Fraction> &getCoefficients() const { return coefficients; }
const std::vector<std::vector<SmallVector<Fraction>>> &getAffine() const {
return affine;
}
// Arithmetic operations.
QuasiPolynomial operator+(const QuasiPolynomial &x) const;
QuasiPolynomial operator-(const QuasiPolynomial &x) const;
QuasiPolynomial operator*(const QuasiPolynomial &x) const;
QuasiPolynomial operator/(const Fraction x) const;
// Removes terms which evaluate to zero from the expression.
QuasiPolynomial simplify();
private:
SmallVector<Fraction> coefficients;
std::vector<std::vector<SmallVector<Fraction>>> affine;
};
} // namespace presburger
} // namespace mlir
#endif // MLIR_ANALYSIS_PRESBURGER_QUASIPOLYNOMIAL_H

View File

@ -6,6 +6,7 @@ add_mlir_library(MLIRPresburger
PresburgerRelation.cpp
PresburgerSpace.cpp
PWMAFunction.cpp
QuasiPolynomial.cpp
Simplex.cpp
SlowMPInt.cpp
Utils.cpp

View File

@ -0,0 +1,132 @@
//===- GeneratingFunction.h - Generating Functions over Q^d -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Definition of the GeneratingFunction class for Barvinok's algorithm,
// which represents a function over Q^n, parameterized by d parameters.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_ANALYSIS_PRESBURGER_GENERATINGFUNCTION_H
#define MLIR_ANALYSIS_PRESBURGER_GENERATINGFUNCTION_H
#include "mlir/Analysis/Presburger/Fraction.h"
#include "mlir/Analysis/Presburger/Matrix.h"
namespace mlir {
namespace presburger {
// A parametric point is a vector, each of whose elements
// is an affine function of n parameters. Each row
// in the matrix represents the affine function and
// has n+1 elements.
using ParamPoint = FracMatrix;
// A point is simply a vector.
using Point = SmallVector<Fraction>;
// A class to describe the type of generating function
// used to enumerate the integer points in a polytope.
// Consists of a set of terms, where the ith term has
// * a sign, ±1, stored in `signs[i]`
// * a numerator, of the form x^{n},
// where n, stored in `numerators[i]`,
// is a parametric point.
// * a denominator, of the form (1 - x^{d1})...(1 - x^{dn}),
// where each dj, stored in `denominators[i][j]`,
// is a vector.
//
// Represents functions f_p : Q^n -> Q of the form
//
// f_p(x) = \sum_i s_i * (x^n_i(p)) / (\prod_j (1 - x^d_{ij})
//
// where s_i is ±1,
// n_i \in Q^d -> Q^n is an n-vector of affine functions on d parameters, and
// g_{ij} \in Q^n are vectors.
class GeneratingFunction {
public:
GeneratingFunction(unsigned numParam, SmallVector<int, 8> signs,
std::vector<ParamPoint> nums,
std::vector<std::vector<Point>> dens)
: numParam(numParam), signs(signs), numerators(nums), denominators(dens) {
for (const ParamPoint &term : numerators)
assert(term.getNumColumns() == numParam + 1 &&
"dimensionality of numerator exponents does not match number of "
"parameters!");
}
unsigned getNumParams() { return numParam; }
SmallVector<int> getSigns() { return signs; }
std::vector<ParamPoint> getNumerators() { return numerators; }
std::vector<std::vector<Point>> getDenominators() { return denominators; }
GeneratingFunction operator+(const GeneratingFunction &gf) const {
assert(numParam == gf.getNumParams() &&
"two generating functions with different numbers of parameters "
"cannot be added!");
SmallVector<int> sumSigns = signs;
sumSigns.append(gf.signs);
std::vector<ParamPoint> sumNumerators = numerators;
sumNumerators.insert(sumNumerators.end(), gf.numerators.begin(),
gf.numerators.end());
std::vector<std::vector<Point>> sumDenominators = denominators;
sumDenominators.insert(sumDenominators.end(), gf.denominators.begin(),
gf.denominators.end());
return GeneratingFunction(sumSigns, sumNumerators, sumDenominators);
}
llvm::raw_ostream &print(llvm::raw_ostream &os) const {
for (unsigned i = 0, e = signs.size(); i < e; i++) {
if (i == 0) {
if (signs[i] == -1)
os << "- ";
} else {
if (signs[i] == 1)
os << " + ";
else
os << " - ";
}
os << "x^[";
unsigned r = numerators[i].getNumRows();
for (unsigned j = 0; j < r - 1; j++) {
os << "[";
for (unsigned k = 0, c = numerators[i].getNumColumns(); k < c - 1; k++)
os << numerators[i].at(j, k) << ",";
os << numerators[i].getRow(j).back() << "],";
}
os << "[";
for (unsigned k = 0, c = numerators[i].getNumColumns(); k < c - 1; k++)
os << numerators[i].at(r - 1, k) << ",";
os << numerators[i].getRow(r - 1).back() << "]]/";
for (const Point &den : denominators[i]) {
os << "(x^[";
for (unsigned j = 0, e = den.size(); j < e - 1; j++)
os << den[j] << ",";
os << den.back() << "])";
}
}
return os;
}
private:
unsigned numParam;
SmallVector<int, 8> signs;
std::vector<ParamPoint> numerators;
std::vector<std::vector<Point>> denominators;
};
} // namespace presburger
} // namespace mlir
#endif // MLIR_ANALYSIS_PRESBURGER_GENERATINGFUNCTION_H

View File

@ -0,0 +1,113 @@
//===- QuasiPolynomial.cpp - Quasipolynomial Class --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/Presburger/QuasiPolynomial.h"
#include "mlir/Analysis/Presburger/Fraction.h"
#include "mlir/Analysis/Presburger/PresburgerSpace.h"
#include "mlir/Analysis/Presburger/Utils.h"
using namespace mlir;
using namespace presburger;
QuasiPolynomial::QuasiPolynomial(
unsigned numVars, SmallVector<Fraction> coeffs,
std::vector<std::vector<SmallVector<Fraction>>> aff)
: PresburgerSpace(/*numDomain=*/numVars, /*numRange=*/1, /*numSymbols=*/0,
/*numLocals=*/0),
coefficients(coeffs), affine(aff) {
// For each term which involves at least one affine function,
for (const std::vector<SmallVector<Fraction>> &term : affine) {
if (term.size() == 0)
continue;
// the number of elements in each affine function is
// one more than the number of symbols.
for (const SmallVector<Fraction> &aff : term) {
assert(aff.size() == getNumInputs() + 1 &&
"dimensionality of affine functions does not match number of "
"symbols!");
}
}
}
QuasiPolynomial QuasiPolynomial::operator+(const QuasiPolynomial &x) const {
assert(getNumInputs() == x.getNumInputs() &&
"two quasi-polynomials with different numbers of symbols cannot "
"be added!");
SmallVector<Fraction> sumCoeffs = coefficients;
sumCoeffs.append(x.coefficients);
std::vector<std::vector<SmallVector<Fraction>>> sumAff = affine;
sumAff.insert(sumAff.end(), x.affine.begin(), x.affine.end());
return QuasiPolynomial(getNumInputs(), sumCoeffs, sumAff);
}
QuasiPolynomial QuasiPolynomial::operator-(const QuasiPolynomial &x) const {
assert(getNumInputs() == x.getNumInputs() &&
"two quasi-polynomials with different numbers of symbols cannot "
"be subtracted!");
QuasiPolynomial qp(getNumInputs(), x.coefficients, x.affine);
for (Fraction &coeff : qp.coefficients)
coeff = -coeff;
return *this + qp;
}
QuasiPolynomial QuasiPolynomial::operator*(const QuasiPolynomial &x) const {
assert(getNumInputs() == x.getNumInputs() &&
"two quasi-polynomials with different numbers of "
"symbols cannot be multiplied!");
SmallVector<Fraction> coeffs;
coeffs.reserve(coefficients.size() * x.coefficients.size());
for (const Fraction &coeff : coefficients)
for (const Fraction &xcoeff : x.coefficients)
coeffs.push_back(coeff * xcoeff);
std::vector<SmallVector<Fraction>> product;
std::vector<std::vector<SmallVector<Fraction>>> aff;
aff.reserve(affine.size() * x.affine.size());
for (const std::vector<SmallVector<Fraction>> &term : affine) {
for (const std::vector<SmallVector<Fraction>> &xterm : x.affine) {
product.clear();
product.insert(product.end(), term.begin(), term.end());
product.insert(product.end(), xterm.begin(), xterm.end());
aff.push_back(product);
}
}
return QuasiPolynomial(getNumInputs(), coeffs, aff);
}
QuasiPolynomial QuasiPolynomial::operator/(const Fraction x) const {
assert(x != 0 && "division by zero!");
QuasiPolynomial qp(*this);
for (Fraction &coeff : qp.coefficients)
coeff /= x;
return qp;
}
// Removes terms which evaluate to zero from the expression.
QuasiPolynomial QuasiPolynomial::simplify() {
SmallVector<Fraction> newCoeffs({});
std::vector<std::vector<SmallVector<Fraction>>> newAffine({});
for (unsigned i = 0, e = coefficients.size(); i < e; i++) {
// A term is zero if its coefficient is zero, or
if (coefficients[i] == Fraction(0, 1))
continue;
bool product_is_zero =
// if any of the affine functions in the product
llvm::any_of(affine[i], [](const SmallVector<Fraction> &affine_ij) {
// has all its coefficients as zero.
return llvm::all_of(affine_ij,
[](const Fraction &f) { return f == 0; });
});
if (product_is_zero)
continue;
newCoeffs.push_back(coefficients[i]);
newAffine.push_back(affine[i]);
}
return QuasiPolynomial(getNumInputs(), newCoeffs, newAffine);
}

View File

@ -11,6 +11,7 @@ add_mlir_unittest(MLIRPresburgerTests
PresburgerRelationTest.cpp
PresburgerSpaceTest.cpp
PWMAFunctionTest.cpp
QuasiPolynomialTest.cpp
SimplexTest.cpp
UtilsTest.cpp
)

View File

@ -0,0 +1,140 @@
//===- MatrixTest.cpp - Tests for QuasiPolynomial -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/Presburger/QuasiPolynomial.h"
#include "./Utils.h"
#include "mlir/Analysis/Presburger/Fraction.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace mlir;
using namespace presburger;
// Test the arithmetic operations on QuasiPolynomials;
// addition, subtraction, multiplication, and division
// by a constant.
// Two QPs of 3 parameters each were generated randomly
// and their sum, difference, and product computed by hand.
TEST(QuasiPolynomialTest, arithmetic) {
QuasiPolynomial qp1(
3, {Fraction(1, 3), Fraction(1, 1), Fraction(1, 2)},
{{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4), Fraction(0, 1)}}});
QuasiPolynomial qp2(
3, {Fraction(1, 1), Fraction(2, 1)},
{{{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1), Fraction(0, 1)}}});
QuasiPolynomial sum = qp1 + qp2;
EXPECT_EQ_REPR_QUASIPOLYNOMIAL(
sum,
QuasiPolynomial(
3,
{Fraction(1, 3), Fraction(1, 1), Fraction(1, 2), Fraction(1, 1),
Fraction(2, 1)},
{{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4), Fraction(0, 1)}},
{{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1),
Fraction(0, 1)}}}));
QuasiPolynomial diff = qp1 - qp2;
EXPECT_EQ_REPR_QUASIPOLYNOMIAL(
diff,
QuasiPolynomial(
3,
{Fraction(1, 3), Fraction(1, 1), Fraction(1, 2), Fraction(-1, 1),
Fraction(-2, 1)},
{{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4), Fraction(0, 1)}},
{{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1),
Fraction(0, 1)}}}));
QuasiPolynomial prod = qp1 * qp2;
EXPECT_EQ_REPR_QUASIPOLYNOMIAL(
prod,
QuasiPolynomial(
3,
{Fraction(1, 3), Fraction(2, 3), Fraction(1, 1), Fraction(2, 1),
Fraction(1, 2), Fraction(1, 1)},
{{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)},
{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)},
{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1), Fraction(0, 1)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)},
{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)},
{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1), Fraction(0, 1)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4), Fraction(0, 1)},
{Fraction(1, 2), Fraction(0, 1), Fraction(-1, 3), Fraction(5, 3)},
{Fraction(2, 1), Fraction(5, 4), Fraction(9, 7), Fraction(-1, 5)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4), Fraction(0, 1)},
{Fraction(1, 3), Fraction(-2, 3), Fraction(1, 1),
Fraction(0, 1)}}}));
QuasiPolynomial quot = qp1 / 2;
EXPECT_EQ_REPR_QUASIPOLYNOMIAL(
quot,
QuasiPolynomial(
3, {Fraction(1, 6), Fraction(1, 2), Fraction(1, 4)},
{{{Fraction(1, 1), Fraction(-1, 2), Fraction(4, 5), Fraction(0, 1)},
{Fraction(2, 3), Fraction(3, 4), Fraction(-1, 1), Fraction(5, 7)}},
{{Fraction(1, 2), Fraction(1, 1), Fraction(4, 5), Fraction(1, 1)}},
{{Fraction(-3, 2), Fraction(1, 1), Fraction(5, 6), Fraction(7, 5)},
{Fraction(1, 4), Fraction(2, 1), Fraction(6, 5), Fraction(-9, 8)},
{Fraction(3, 2), Fraction(2, 5), Fraction(-7, 4),
Fraction(0, 1)}}}));
}
// Test the simplify() operation on QPs, which removes terms that
// are identically zero. A random QP was generated and terms were
// changed to account for each condition in simplify()  
// the term coefficient being zero, or all the coefficients in some
// affine term in the product being zero.
TEST(QuasiPolynomialTest, simplify) {
QuasiPolynomial qp(2,
{Fraction(2, 3), Fraction(0, 1), Fraction(1, 1),
Fraction(1, 2), Fraction(0, 1)},
{{{Fraction(1, 1), Fraction(3, 4), Fraction(5, 3)},
{Fraction(2, 1), Fraction(0, 1), Fraction(0, 1)}},
{{Fraction(1, 3), Fraction(8, 5), Fraction(2, 5)}},
{{Fraction(2, 7), Fraction(9, 5), Fraction(0, 1)},
{Fraction(0, 1), Fraction(0, 1), Fraction(0, 1)}},
{{Fraction(1, 1), Fraction(4, 5), Fraction(6, 5)}},
{{Fraction(1, 3), Fraction(4, 3), Fraction(7, 8)}}});
EXPECT_EQ_REPR_QUASIPOLYNOMIAL(
qp.simplify(),
QuasiPolynomial(2, {Fraction(2, 3), Fraction(1, 2)},
{{{Fraction(1, 1), Fraction(3, 4), Fraction(5, 3)},
{Fraction(2, 1), Fraction(0, 1), Fraction(0, 1)}},
{{Fraction(1, 1), Fraction(4, 5), Fraction(6, 5)}}}));
}

View File

@ -17,6 +17,7 @@
#include "mlir/Analysis/Presburger/Matrix.h"
#include "mlir/Analysis/Presburger/PWMAFunction.h"
#include "mlir/Analysis/Presburger/PresburgerRelation.h"
#include "mlir/Analysis/Presburger/QuasiPolynomial.h"
#include "mlir/Analysis/Presburger/Simplex.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/Support/LLVM.h"
@ -71,6 +72,28 @@ inline void EXPECT_EQ_FRAC_MATRIX(FracMatrix a, FracMatrix b) {
EXPECT_EQ(a(row, col), b(row, col));
}
// Check the coefficients (in order) of two quasipolynomials.
// Note that this is not a true equality check.
inline void EXPECT_EQ_REPR_QUASIPOLYNOMIAL(QuasiPolynomial a, QuasiPolynomial b) {
EXPECT_EQ(a.getNumInputs(), b.getNumInputs());
SmallVector<Fraction> aCoeffs = a.getCoefficients(),
bCoeffs = b.getCoefficients();
EXPECT_EQ(aCoeffs.size(), bCoeffs.size());
for (unsigned i = 0, e = aCoeffs.size(); i < e; i++)
EXPECT_EQ(aCoeffs[i], bCoeffs[i]);
std::vector<std::vector<SmallVector<Fraction>>> aAff = a.getAffine(),
bAff = b.getAffine();
EXPECT_EQ(aAff.size(), bAff.size());
for (unsigned i = 0, e = aAff.size(); i < e; i++) {
EXPECT_EQ(aAff[i].size(), bAff[i].size());
for (unsigned j = 0, f = aAff[i].size(); j < f; j++)
for (unsigned k = 0, g = a.getNumInputs(); k <= g; k++)
EXPECT_EQ(aAff[i][j][k], bAff[i][j][k]);
}
}
/// lhs and rhs represent non-negative integers or positive infinity. The
/// infinity case corresponds to when the Optional is empty.
inline bool infinityOrUInt64LE(std::optional<MPInt> lhs,