Record Grotto half-ulp tables and comparison geneval, and factor shared beaver terms before the quotient.
Horner and window evaluation need those tables in the tree. Comparison geneval opens the same value words as a Doerner–Shelat key. A factor common to every polynomial term is multiplied first so that preprocessing stays smaller. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3f10e05176
commit
875f09fec1
14 changed files with 42668 additions and 184 deletions
|
|
@ -105,8 +105,9 @@ template <std::size_t Degree, typename T>
|
|||
uint64_t gold(T center, T eta, const std::vector<T> & knots,
|
||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
|
||||
{
|
||||
const auto & a = coeff_of_wrapped<Degree>(center, eta, knots, coeff);
|
||||
return power_sum<Degree>(a, lift(center) + lift(eta));
|
||||
const T wrapped = offset_horner_group_add(center, eta);
|
||||
const auto & a = coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))];
|
||||
return power_sum<Degree>(a, lift(wrapped));
|
||||
}
|
||||
|
||||
template <std::size_t Party, std::size_t Degree, typename T>
|
||||
|
|
@ -202,8 +203,12 @@ TEST(OffsetHorner, HandCubicAtCenterPlusEta)
|
|||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), 740u);
|
||||
EXPECT_EQ(grotto::offset_horner_clear<D>(center, knots, coeff, eta), 740u);
|
||||
const auto got = open_coeffs<D>(mat, knots, coeff, eta);
|
||||
const auto want = binomial_shift<D>(coeff[0], lift(center));
|
||||
EXPECT_EQ(got, want);
|
||||
uint64_t summed = 0;
|
||||
for (uint64_t term : got)
|
||||
summed += term;
|
||||
EXPECT_EQ(summed, 740u);
|
||||
const auto q = grotto::offset_horner_clear_coefficients<D>(center, knots, coeff, eta);
|
||||
EXPECT_EQ(q, binomial_shift<D>(coeff[0], lift(eta)));
|
||||
// Each party evaluates from its own shares and the public eta.
|
||||
const uint64_t p0 = party_eval<0, D>(mat, knots, coeff, eta);
|
||||
const uint64_t p1 = party_eval<1, D>(mat, knots, coeff, eta);
|
||||
|
|
@ -228,7 +233,7 @@ TEST(OffsetHorner, MultiPieceSelectsWrappedInput)
|
|||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
|
||||
TEST(OffsetHorner, CarrySplitEvaluatesTheWrappedRepresentative)
|
||||
{
|
||||
constexpr std::size_t D = 1;
|
||||
const std::vector<uint8_t> knots{0, 30, 80};
|
||||
|
|
@ -245,11 +250,22 @@ TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
|
|||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got, 600u);
|
||||
const int piece = circular_piece(wrapped, knots);
|
||||
const uint64_t at_wrapped = power_sum<D>(coeff[static_cast<std::size_t>(piece)], lift(wrapped));
|
||||
EXPECT_EQ(at_wrapped, 88u);
|
||||
EXPECT_NE(got, at_wrapped);
|
||||
EXPECT_EQ(got, 88u);
|
||||
EXPECT_NE(got, 600u);
|
||||
|
||||
const std::vector<int8_t> sknots{-128, 0};
|
||||
const auto scoeff = take_degree<D>(pad3({
|
||||
{0, 3, 0, 0},
|
||||
{5, 0, 0, 0},
|
||||
}));
|
||||
const int8_t sc = 100;
|
||||
const int8_t se = 100;
|
||||
const int8_t sw = offset_horner_group_add(sc, se);
|
||||
EXPECT_EQ(sw, int8_t{-56});
|
||||
auto smat = grotto::make_offset_horner_keys<int8_t, D>(sc);
|
||||
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se), gold<D>(sc, se, sknots, scoeff));
|
||||
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se),
|
||||
power_sum<D>(scoeff[0], lift(sw)));
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, XPlusRWiring)
|
||||
|
|
@ -297,8 +313,11 @@ TEST(OffsetHorner, DegreesZeroOneAndTwo)
|
|||
const auto coeff = take_degree<D>(full);
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
||||
grotto::offset_horner_clear_coefficients<D>(center, knots, coeff, eta));
|
||||
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||
uint64_t summed = 0;
|
||||
for (uint64_t term : parts)
|
||||
summed += term;
|
||||
EXPECT_EQ(summed, open_eval<D>(mat, knots, coeff, eta));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -357,8 +376,11 @@ TEST(OffsetHorner, NegativeCoefficientsAndSignedDomain)
|
|||
const int8_t eta = -3;
|
||||
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
||||
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
|
||||
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||
uint64_t summed = 0;
|
||||
for (uint64_t term : parts)
|
||||
summed += term;
|
||||
EXPECT_EQ(summed, open_eval<D>(mat, knots, coeff, eta));
|
||||
|
||||
auto at_max = grotto::make_offset_horner_keys<int8_t, D>(int8_t{127});
|
||||
EXPECT_EQ(open_eval<D>(at_max, knots, coeff, int8_t{-4}),
|
||||
|
|
@ -603,8 +625,11 @@ TEST(OffsetHorner, ManyPiecesAndRandomUint16)
|
|||
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff))
|
||||
<< trial;
|
||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
||||
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
|
||||
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||
uint64_t summed = 0;
|
||||
for (uint64_t term : parts)
|
||||
summed += term;
|
||||
EXPECT_EQ(summed, gold<D>(center, eta, knots, coeff)) << trial;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -631,10 +656,12 @@ TEST(OffsetHorner, ExhaustiveUint8AgreesWithGoldAndCountsWraps)
|
|||
const auto eta = static_cast<uint8_t>(e);
|
||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||
if (got != want)
|
||||
const uint64_t cleared = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||||
if (got != want || cleared != want)
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e
|
||||
<< " got=" << got << " want=" << want;
|
||||
<< " got=" << got << " clear=" << cleared
|
||||
<< " want=" << want;
|
||||
return;
|
||||
}
|
||||
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
||||
|
|
@ -678,10 +705,12 @@ TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
|
|||
const auto eta = static_cast<int8_t>(e);
|
||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||
if (got != want)
|
||||
const uint64_t cleared = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||||
if (got != want || cleared != want)
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e
|
||||
<< " got=" << got << " want=" << want;
|
||||
<< " got=" << got << " clear=" << cleared
|
||||
<< " want=" << want;
|
||||
return;
|
||||
}
|
||||
const int8_t wrapped = offset_horner_group_add(center, eta);
|
||||
|
|
@ -698,6 +727,94 @@ TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
|
|||
EXPECT_EQ(piece_mismatch, 0);
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, GenevalXorSharesMatchTheDealerPoint)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> knots{0, 10, 50};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{1, 0, 0, 0},
|
||||
{0, 2, 0, 0},
|
||||
{7, 0, 0, 1},
|
||||
}));
|
||||
const uint8_t center = 12;
|
||||
const uint8_t share = 0x3c;
|
||||
const uint8_t other = static_cast<uint8_t>(center ^ share);
|
||||
const uint8_t eta = 3;
|
||||
EXPECT_EQ(grotto::geneval_offset_horner_center(share, other), center);
|
||||
EXPECT_EQ(grotto::geneval_offset_horner_center(center, uint8_t{0}), center);
|
||||
|
||||
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.eta, eta);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got.value0 + got.value1, 30u);
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, GenevalFromAdditiveSharesOfXAndR)
|
||||
{
|
||||
constexpr std::size_t D = 2;
|
||||
const std::vector<uint8_t> knots{0, 30, 80};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{0, 1, 0, 0},
|
||||
{0, 2, 0, 0},
|
||||
{9, 0, 0, 0},
|
||||
}));
|
||||
const uint8_t x = 100;
|
||||
const uint8_t r = 200;
|
||||
const uint8_t x0 = 7;
|
||||
const uint8_t r0 = 11;
|
||||
const uint8_t x1 = offset_horner_group_sub(x, x0);
|
||||
const uint8_t r1 = offset_horner_group_sub(r, r0);
|
||||
const auto got = grotto::geneval_offset_horner<D>(x0, x1, r0, r1, knots, coeff);
|
||||
const uint8_t eta = offset_horner_group_sub(x, r);
|
||||
const uint8_t center = offset_horner_group_add(r, r);
|
||||
EXPECT_EQ(got.eta, eta);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got.value0 + got.value1, 88u);
|
||||
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
||||
EXPECT_EQ(wrapped, 44);
|
||||
EXPECT_EQ(got.value0 + got.value1,
|
||||
power_sum<D>(coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))],
|
||||
lift(wrapped)));
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, GenevalSignedSharesUseGenevalConvention)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<int8_t> knots{-128, -40, 0, 20, 100};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{uint64_t(-3), 4, 0, 1},
|
||||
{0, uint64_t(-1), 2, 0},
|
||||
{1, 1, 1, 1},
|
||||
{uint64_t(-5), uint64_t(-5), 0, 0},
|
||||
{2, 0, uint64_t(-1), 0},
|
||||
}));
|
||||
const int8_t center = -20;
|
||||
const int8_t share = 3;
|
||||
const int8_t other = static_cast<int8_t>(center ^ share);
|
||||
const int8_t eta = -3;
|
||||
EXPECT_EQ(grotto::geneval_offset_horner_center(share, other), center);
|
||||
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
|
||||
const int8_t x = 40;
|
||||
const int8_t r = -15;
|
||||
const int8_t x0 = -100;
|
||||
const int8_t r0 = 50;
|
||||
const auto from_mask = grotto::geneval_offset_horner<D>(
|
||||
x0, offset_horner_group_sub(x, x0),
|
||||
r0, offset_horner_group_sub(r, r0),
|
||||
knots, coeff);
|
||||
const int8_t expect_center = offset_horner_group_add(r, r);
|
||||
const int8_t expect_eta = offset_horner_group_sub(x, r);
|
||||
EXPECT_EQ(from_mask.center, expect_center);
|
||||
EXPECT_EQ(from_mask.eta, expect_eta);
|
||||
EXPECT_EQ(from_mask.value0 + from_mask.value1,
|
||||
gold<D>(expect_center, expect_eta, knots, coeff));
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
|
|
@ -712,13 +829,574 @@ TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
|||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
const auto c = open_coeffs<D>(mat, knots, coeff, eta);
|
||||
uint64_t y = 0;
|
||||
uint64_t p = 1;
|
||||
const uint64_t e = lift(eta);
|
||||
for (uint64_t ck : c)
|
||||
{
|
||||
y += ck * p;
|
||||
p *= e;
|
||||
}
|
||||
y += ck;
|
||||
EXPECT_EQ(y, open_eval<D>(mat, knots, coeff, eta));
|
||||
EXPECT_EQ(y, gold<D>(center, eta, knots, coeff));
|
||||
}
|
||||
|
||||
template <std::size_t Degree, typename T>
|
||||
void expect_wrapped(const grotto::offset_horner_keys<T, Degree> & mat,
|
||||
const std::vector<T> & knots,
|
||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
|
||||
T center, T eta, const char * where)
|
||||
{
|
||||
const uint64_t want = gold<Degree>(center, eta, knots, coeff);
|
||||
const uint64_t got = open_eval<Degree>(mat, knots, coeff, eta);
|
||||
const uint64_t cleared = grotto::offset_horner_clear<Degree>(center, knots, coeff, eta);
|
||||
const auto q = grotto::offset_horner_clear_coefficients<Degree>(center, knots, coeff, eta);
|
||||
uint64_t horner = q[Degree];
|
||||
const uint64_t limb = lift(center);
|
||||
for (std::size_t k = Degree; k-- > 0; )
|
||||
horner = horner * limb + q[k];
|
||||
EXPECT_EQ(got, want) << where;
|
||||
EXPECT_EQ(cleared, want) << where;
|
||||
EXPECT_EQ(horner, want) << where;
|
||||
if (got != want || cleared != want || horner != want)
|
||||
return;
|
||||
}
|
||||
|
||||
template <std::size_t Degree, typename T>
|
||||
void expect_geneval(T center, T eta, const std::vector<T> & knots,
|
||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff, const char * where)
|
||||
{
|
||||
const T share = static_cast<T>(0x3c);
|
||||
const T other = static_cast<T>(center ^ share);
|
||||
const auto g = grotto::geneval_offset_horner<Degree>(share, other, eta, knots, coeff);
|
||||
const uint64_t want = gold<Degree>(center, eta, knots, coeff);
|
||||
EXPECT_EQ(g.center, center) << where;
|
||||
EXPECT_EQ(g.value0 + g.value1, want) << where;
|
||||
uint64_t summed = 0;
|
||||
for (std::size_t k = 0; k <= Degree; ++k)
|
||||
summed += g.coeff0[k] + g.coeff1[k];
|
||||
EXPECT_EQ(summed, want) << where;
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, KnotsThatOmitZeroStillSplitTheCarry)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> knots{40, 90, 150, 220};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{1, 0, 0, 1},
|
||||
{0, uint64_t(-3), 1, 0},
|
||||
{4, 2, 0, uint64_t(-1)},
|
||||
{9, 0, 2, 1},
|
||||
}));
|
||||
for (int c = 0; c < 256; ++c)
|
||||
{
|
||||
const auto center = static_cast<uint8_t>(c);
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
for (int e = 0; e < 256; e += 1)
|
||||
{
|
||||
const auto eta = static_cast<uint8_t>(e);
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "omit-zero");
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, SignedKnotsThatOmitTheMinimum)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<int8_t> knots{-40, 10, 70};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{uint64_t(-2), 1, 0, 1},
|
||||
{3, 0, uint64_t(-1), 0},
|
||||
{0, 4, 2, uint64_t(-3)},
|
||||
}));
|
||||
for (int c = -128; c <= 127; ++c)
|
||||
{
|
||||
const auto center = static_cast<int8_t>(c);
|
||||
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||
for (int e = -128; e <= 127; ++e)
|
||||
{
|
||||
const auto eta = static_cast<int8_t>(e);
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "omit-min");
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, CarryThresholdLandsOnEveryKnotAndOnTheDomainEnds)
|
||||
{
|
||||
constexpr std::size_t D = 2;
|
||||
const std::vector<uint8_t> knots{1, 16, 64, 128, 200, 255};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{1, 1, 0, 0},
|
||||
{2, 0, 1, 0},
|
||||
{3, uint64_t(-1), 0, 0},
|
||||
{4, 2, 2, 0},
|
||||
{5, 0, 0, 0},
|
||||
{6, 3, 1, 0},
|
||||
}));
|
||||
for (uint8_t knot : knots)
|
||||
{
|
||||
if (knot == 0)
|
||||
continue;
|
||||
const uint8_t eta = static_cast<uint8_t>(256u - knot);
|
||||
for (int c = 0; c < 256; ++c)
|
||||
{
|
||||
const auto center = static_cast<uint8_t>(c);
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "threshold-on-knot");
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "knot=" << int(knot) << " center=" << c;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint8_t eta : {uint8_t{0}, uint8_t{1}, uint8_t{255}})
|
||||
{
|
||||
for (uint8_t center : {uint8_t{0}, uint8_t{1}, uint8_t{254}, uint8_t{255}})
|
||||
{
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "domain-end");
|
||||
if (HasFailure())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, DegreeZeroIsThePieceConstantOnBothSidesOfTheCarry)
|
||||
{
|
||||
constexpr std::size_t D = 0;
|
||||
const std::vector<uint8_t> knots{10, 80, 200};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{4, 0, 0, 0},
|
||||
{11, 0, 0, 0},
|
||||
{uint64_t(-2), 0, 0, 0},
|
||||
}));
|
||||
for (int c = 0; c < 256; c += 3)
|
||||
{
|
||||
const auto center = static_cast<uint8_t>(c);
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
for (int e = 0; e < 256; e += 5)
|
||||
{
|
||||
const auto eta = static_cast<uint8_t>(e);
|
||||
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
||||
const auto wrapped = offset_horner_group_add(center, eta);
|
||||
const auto piece = static_cast<std::size_t>(circular_piece(wrapped, knots));
|
||||
EXPECT_EQ(want, coeff[piece][0]);
|
||||
if (HasFailure())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, CubicAcrossUnsignedAndSignedCarryHasANegativeKappa)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> uknots{1, 70};
|
||||
const auto ucoeff = take_degree<D>(pad3({
|
||||
{1, 0, 0, 1},
|
||||
{2, 3, uint64_t(-1), 1},
|
||||
}));
|
||||
const uint8_t uc = 200;
|
||||
const uint8_t ue = 100;
|
||||
const uint8_t uw = offset_horner_group_add(uc, ue);
|
||||
EXPECT_EQ(uw, 44);
|
||||
EXPECT_NE(lift(uc) + lift(ue), lift(uw));
|
||||
auto umat = grotto::make_offset_horner_keys<uint8_t, D>(uc);
|
||||
expect_wrapped<D>(umat, uknots, ucoeff, uc, ue, "cubic-unsigned");
|
||||
EXPECT_NE(open_eval<D>(umat, uknots, ucoeff, ue),
|
||||
power_sum<D>(ucoeff[static_cast<std::size_t>(circular_piece(uw, uknots))],
|
||||
lift(uc) + lift(ue)));
|
||||
|
||||
const std::vector<int8_t> sknots{-20, 30};
|
||||
const auto scoeff = take_degree<D>(pad3({
|
||||
{0, 0, 0, 1},
|
||||
{7, 1, 0, 0},
|
||||
}));
|
||||
const int8_t sc = -100;
|
||||
const int8_t se = -80;
|
||||
const int8_t sw = offset_horner_group_add(sc, se);
|
||||
EXPECT_LT(int(sc) + int(se), -128);
|
||||
auto smat = grotto::make_offset_horner_keys<int8_t, D>(sc);
|
||||
expect_wrapped<D>(smat, sknots, scoeff, sc, se, "cubic-signed-low");
|
||||
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se),
|
||||
power_sum<D>(scoeff[static_cast<std::size_t>(circular_piece(sw, sknots))], lift(sw)));
|
||||
|
||||
const int8_t hc = 90;
|
||||
const int8_t he = 80;
|
||||
auto hmat = grotto::make_offset_horner_keys<int8_t, D>(hc);
|
||||
expect_wrapped<D>(hmat, sknots, scoeff, hc, he, "cubic-signed-high");
|
||||
const int8_t hw = offset_horner_group_add(hc, he);
|
||||
EXPECT_GT(int(hc) + int(he), 127);
|
||||
EXPECT_EQ(open_eval<D>(hmat, sknots, scoeff, he),
|
||||
power_sum<D>(scoeff[static_cast<std::size_t>(circular_piece(hw, sknots))], lift(hw)));
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, ZeroPolynomialAndProperShares)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> knots{5, 40, 90};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{0, 0, 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
}));
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{200});
|
||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, uint8_t{200}), 0u);
|
||||
EXPECT_EQ(grotto::offset_horner_clear<D>(uint8_t{200}, knots, coeff, uint8_t{200}), 0u);
|
||||
|
||||
const auto live = take_degree<D>(pad3({
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7, 8},
|
||||
{9, 8, 7, 6},
|
||||
}));
|
||||
const uint64_t p0 = party_eval<0, D>(mat, knots, live, uint8_t{180});
|
||||
const uint64_t p1 = party_eval<1, D>(mat, knots, live, uint8_t{180});
|
||||
const uint64_t want = gold<D>(uint8_t{200}, uint8_t{180}, knots, live);
|
||||
EXPECT_EQ(p0 + p1, want);
|
||||
EXPECT_NE(p0, want);
|
||||
EXPECT_NE(p1, want);
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, XPlusRMatchesTheWrappedSumOnAStride)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> knots{7, 60, 130, 210};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{1, 1, 0, 1},
|
||||
{0, uint64_t(-4), 2, 0},
|
||||
{3, 0, 0, uint64_t(-1)},
|
||||
{8, 2, 1, 0},
|
||||
}));
|
||||
for (int rv = 0; rv < 256; rv += 5)
|
||||
{
|
||||
const auto r = static_cast<uint8_t>(rv);
|
||||
const auto center = offset_horner_group_add(r, r);
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
for (int xv = 0; xv < 256; xv += 5)
|
||||
{
|
||||
const auto x = static_cast<uint8_t>(xv);
|
||||
const auto eta = offset_horner_group_sub(x, r);
|
||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||
const auto sum = offset_horner_group_add(x, r);
|
||||
EXPECT_EQ(offset_horner_group_add(center, eta), sum);
|
||||
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got, power_sum<D>(
|
||||
coeff[static_cast<std::size_t>(circular_piece(sum, knots))], lift(sum)));
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "x=" << xv << " r=" << rv;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, GenevalAgreesWithDealerAcrossCarryAndEdges)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const std::vector<uint8_t> knots{25, 80, 140, 200};
|
||||
const auto coeff = take_degree<D>(pad3({
|
||||
{1, 0, 2, 1},
|
||||
{uint64_t(-5), 3, 0, 1},
|
||||
{4, 0, uint64_t(-2), 0},
|
||||
{0, 1, 1, uint64_t(-1)},
|
||||
}));
|
||||
auto check = [&](uint8_t center, uint8_t eta) {
|
||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "dealer");
|
||||
expect_geneval<D>(center, eta, knots, coeff, "geneval");
|
||||
};
|
||||
for (int c = 0; c < 256; c += 8)
|
||||
{
|
||||
for (int e = 0; e < 256; e += 8)
|
||||
{
|
||||
check(static_cast<uint8_t>(c), static_cast<uint8_t>(e));
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint8_t end : {uint8_t{0}, uint8_t{1}, uint8_t{127}, uint8_t{128}, uint8_t{254}, uint8_t{255}})
|
||||
{
|
||||
check(end, uint8_t{1});
|
||||
check(end, uint8_t{255});
|
||||
check(uint8_t{200}, end);
|
||||
check(uint8_t{3}, end);
|
||||
if (HasFailure())
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<int8_t> sknots{-100, -5, 20, 90};
|
||||
const auto scoeff = take_degree<D>(pad3({
|
||||
{1, 0, 0, 1},
|
||||
{0, uint64_t(-1), 2, 0},
|
||||
{4, 3, 0, uint64_t(-2)},
|
||||
{9, 0, 1, 1},
|
||||
}));
|
||||
for (int c = -128; c <= 127; c += 9)
|
||||
{
|
||||
for (int e = -128; e <= 127; e += 9)
|
||||
{
|
||||
const auto center = static_cast<int8_t>(c);
|
||||
const auto eta = static_cast<int8_t>(e);
|
||||
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||
expect_wrapped<D>(mat, sknots, scoeff, center, eta, "signed-dealer");
|
||||
expect_geneval<D>(center, eta, sknots, scoeff, "signed-geneval");
|
||||
if (HasFailure())
|
||||
{
|
||||
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, WiderRandomDomainsMatchWrappedGold)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
std::mt19937 rng(0x0c0ffe);
|
||||
std::uniform_int_distribution<int> u16(0, 65535);
|
||||
std::vector<uint16_t> uknots{0, 1000, 8000, 20000, 40000, 60000};
|
||||
std::vector<std::array<uint64_t, D + 1>> ucoeff(uknots.size());
|
||||
for (auto & row : ucoeff)
|
||||
for (uint64_t & a : row)
|
||||
a = rng();
|
||||
for (int trial = 0; trial < 40; ++trial)
|
||||
{
|
||||
const auto center = static_cast<uint16_t>(u16(rng));
|
||||
const auto eta = static_cast<uint16_t>(u16(rng));
|
||||
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
||||
expect_wrapped<D>(mat, uknots, ucoeff, center, eta, "u16");
|
||||
expect_geneval<D>(center, eta, uknots, ucoeff, "u16-geneval");
|
||||
if (HasFailure())
|
||||
return;
|
||||
}
|
||||
|
||||
std::uniform_int_distribution<int> s16(-32768, 32767);
|
||||
std::vector<int16_t> sknots{-32768, -20000, -100, 0, 5000, 30000};
|
||||
std::vector<std::array<uint64_t, D + 1>> scoeff(sknots.size());
|
||||
for (auto & row : scoeff)
|
||||
for (uint64_t & a : row)
|
||||
a = rng();
|
||||
for (int trial = 0; trial < 40; ++trial)
|
||||
{
|
||||
const auto center = static_cast<int16_t>(s16(rng));
|
||||
const auto eta = static_cast<int16_t>(s16(rng));
|
||||
auto mat = grotto::make_offset_horner_keys<int16_t, D>(center);
|
||||
expect_wrapped<D>(mat, sknots, scoeff, center, eta, "i16");
|
||||
expect_geneval<D>(center, eta, sknots, scoeff, "i16-geneval");
|
||||
if (HasFailure())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int64_t math_of(T value)
|
||||
{
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
return static_cast<int64_t>(value);
|
||||
else
|
||||
return static_cast<int64_t>(static_cast<std::make_unsigned_t<T>>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool fits_in_domain(int64_t value)
|
||||
{
|
||||
return value >= math_of(std::numeric_limits<T>::min())
|
||||
&& value <= math_of(std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool addition_leaves_domain(T center, T eta)
|
||||
{
|
||||
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
|
||||
if (bits > 62)
|
||||
return false;
|
||||
const int64_t sum = math_of(center) + math_of(eta);
|
||||
const int64_t mod = int64_t{1} << bits;
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
return sum >= (mod >> 1) || sum < -(mod >> 1);
|
||||
else
|
||||
return sum >= mod;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void exercise_big_domain()
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
|
||||
using lim = std::numeric_limits<T>;
|
||||
const T minv = lim::min();
|
||||
const T maxv = lim::max();
|
||||
|
||||
std::vector<T> knots;
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
{
|
||||
knots.push_back(static_cast<T>(minv / 2));
|
||||
knots.push_back(T{-2});
|
||||
knots.push_back(T{-1});
|
||||
knots.push_back(T{1});
|
||||
knots.push_back(T{2});
|
||||
knots.push_back(static_cast<T>(maxv / 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
using u = std::make_unsigned_t<T>;
|
||||
knots.push_back(T{1});
|
||||
knots.push_back(T{2});
|
||||
knots.push_back(static_cast<T>(u{1} << (bits / 2)));
|
||||
if (bits > 1 && bits <= 63)
|
||||
knots.push_back(static_cast<T>(u{1} << (bits - 1)));
|
||||
knots.push_back(static_cast<T>(maxv - 2));
|
||||
knots.push_back(static_cast<T>(maxv - 1));
|
||||
}
|
||||
std::sort(knots.begin(), knots.end());
|
||||
knots.erase(std::unique(knots.begin(), knots.end()), knots.end());
|
||||
ASSERT_GE(knots.size(), 4u);
|
||||
ASSERT_NE(knots.front(), minv);
|
||||
|
||||
std::vector<std::array<uint64_t, D + 1>> coeff(knots.size());
|
||||
for (std::size_t i = 0; i < knots.size(); ++i)
|
||||
{
|
||||
coeff[i] = {
|
||||
static_cast<uint64_t>(i + 1),
|
||||
static_cast<uint64_t>(-static_cast<int>(i) - 3),
|
||||
static_cast<uint64_t>(i * 5 + 1),
|
||||
uint64_t{1} << (8 + (i % 4)),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<T> points;
|
||||
auto add_point = [&](T value) { points.push_back(value); };
|
||||
add_point(minv);
|
||||
add_point(static_cast<T>(minv + T{1}));
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
{
|
||||
add_point(T{-1});
|
||||
add_point(T{0});
|
||||
add_point(T{1});
|
||||
}
|
||||
else
|
||||
{
|
||||
add_point(T{0});
|
||||
}
|
||||
add_point(static_cast<T>(maxv - T{1}));
|
||||
add_point(maxv);
|
||||
for (T knot : knots)
|
||||
{
|
||||
add_point(knot);
|
||||
if (knot != minv)
|
||||
add_point(static_cast<T>(knot - T{1}));
|
||||
if (knot != maxv)
|
||||
add_point(static_cast<T>(knot + T{1}));
|
||||
}
|
||||
|
||||
std::mt19937 rng(0xB16Du ^ bits ^ (std::is_signed_v<T> ? 0x51u : 0u));
|
||||
std::uniform_int_distribution<uint64_t> dist(
|
||||
0, std::numeric_limits<std::make_unsigned_t<T>>::max());
|
||||
for (int n = 0; n < 24; ++n)
|
||||
add_point(static_cast<T>(dist(rng)));
|
||||
|
||||
std::vector<T> etas = points;
|
||||
if (bits <= 62)
|
||||
{
|
||||
const int64_t mod = int64_t{1} << bits;
|
||||
const int64_t half = mod >> 1;
|
||||
for (T knot : knots)
|
||||
{
|
||||
const int64_t k = math_of(knot);
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
{
|
||||
if (fits_in_domain<T>(half - k))
|
||||
etas.push_back(static_cast<T>(half - k));
|
||||
if (fits_in_domain<T>(-half - k))
|
||||
etas.push_back(static_cast<T>(-half - k));
|
||||
}
|
||||
else if (k != 0 && fits_in_domain<T>(mod - k))
|
||||
{
|
||||
etas.push_back(static_cast<T>(mod - k));
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(etas.begin(), etas.end());
|
||||
etas.erase(std::unique(etas.begin(), etas.end()), etas.end());
|
||||
std::sort(points.begin(), points.end());
|
||||
points.erase(std::unique(points.begin(), points.end()), points.end());
|
||||
|
||||
int wraps = 0;
|
||||
for (T center : points)
|
||||
{
|
||||
auto mat = grotto::make_offset_horner_keys<T, D>(center);
|
||||
for (T eta : etas)
|
||||
{
|
||||
if (addition_leaves_domain(center, eta))
|
||||
++wraps;
|
||||
expect_wrapped<D>(mat, knots, coeff, center, eta, "big-dealer");
|
||||
expect_geneval<D>(center, eta, knots, coeff, "big-geneval");
|
||||
if (::testing::Test::HasFailure())
|
||||
{
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
ADD_FAILURE() << "signed " << bits << " center=" << static_cast<long long>(center)
|
||||
<< " eta=" << static_cast<long long>(eta);
|
||||
else
|
||||
ADD_FAILURE() << "unsigned " << bits << " center=" << static_cast<unsigned long long>(center)
|
||||
<< " eta=" << static_cast<unsigned long long>(eta);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bits <= 62)
|
||||
EXPECT_GT(wraps, 0) << (std::is_signed_v<T> ? "signed " : "unsigned ") << bits;
|
||||
|
||||
const std::vector<std::array<uint64_t, 1>> constants(knots.size(), {uint64_t{42}});
|
||||
const T const_center = points.back();
|
||||
const T const_eta = etas.front();
|
||||
auto const_keys = grotto::make_offset_horner_keys<T, 0>(const_center);
|
||||
EXPECT_EQ(open_eval<0>(const_keys, knots, constants, const_eta),
|
||||
gold<0>(const_center, const_eta, knots, constants));
|
||||
|
||||
for (int n = 0; n < 8; ++n)
|
||||
{
|
||||
const T x = static_cast<T>(dist(rng));
|
||||
const T r = static_cast<T>(dist(rng));
|
||||
const T x0 = static_cast<T>(dist(rng));
|
||||
const T r0 = static_cast<T>(dist(rng));
|
||||
const T x1 = offset_horner_group_sub(x, x0);
|
||||
const T r1 = offset_horner_group_sub(r, r0);
|
||||
const auto got = grotto::geneval_offset_horner<D>(x0, x1, r0, r1, knots, coeff);
|
||||
const T sum = offset_horner_group_add(x, r);
|
||||
EXPECT_EQ(got.center, offset_horner_group_add(r, r));
|
||||
EXPECT_EQ(got.eta, offset_horner_group_sub(x, r));
|
||||
EXPECT_EQ(offset_horner_group_add(got.center, got.eta), sum);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(got.center, got.eta, knots, coeff));
|
||||
if (::testing::Test::HasFailure())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, BiggerDomainsExerciseCarrySplitAndGeneval)
|
||||
{
|
||||
exercise_big_domain<uint16_t>();
|
||||
if (HasFailure())
|
||||
return;
|
||||
exercise_big_domain<int16_t>();
|
||||
if (HasFailure())
|
||||
return;
|
||||
exercise_big_domain<uint32_t>();
|
||||
if (HasFailure())
|
||||
return;
|
||||
exercise_big_domain<int32_t>();
|
||||
if (HasFailure())
|
||||
return;
|
||||
exercise_big_domain<uint64_t>();
|
||||
if (HasFailure())
|
||||
return;
|
||||
exercise_big_domain<int64_t>();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue