724 lines
25 KiB
C++
724 lines
25 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include "dpf.hpp"
|
|
#include "grotto/offset_horner.hpp"
|
|
#include "grotto/prefix_parity.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <random>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
using grotto::offset_horner_group_add;
|
|
using grotto::offset_horner_group_sub;
|
|
|
|
template <typename T>
|
|
uint64_t lift(T v)
|
|
{
|
|
if constexpr (std::is_signed_v<T>)
|
|
return static_cast<uint64_t>(static_cast<std::int64_t>(v));
|
|
else
|
|
return static_cast<uint64_t>(v);
|
|
}
|
|
|
|
template <typename T>
|
|
int circular_piece(T point, const std::vector<T> & knots)
|
|
{
|
|
if (knots.size() <= 1)
|
|
return 0;
|
|
for (std::size_t i = 0; i + 1 < knots.size(); ++i)
|
|
{
|
|
if (point >= knots[i] && point < knots[i + 1])
|
|
return static_cast<int>(i);
|
|
}
|
|
return static_cast<int>(knots.size() - 1);
|
|
}
|
|
|
|
template <std::size_t Degree>
|
|
uint64_t power_sum(const std::array<uint64_t, Degree + 1> & a, uint64_t point)
|
|
{
|
|
uint64_t acc = 0;
|
|
uint64_t p = 1;
|
|
for (std::size_t m = 0; m <= Degree; ++m)
|
|
{
|
|
acc += a[m] * p;
|
|
p *= point;
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
template <std::size_t Degree>
|
|
std::array<uint64_t, Degree + 1> binomial_shift(
|
|
const std::array<uint64_t, Degree + 1> & a, uint64_t center)
|
|
{
|
|
static constexpr uint64_t binom[4][4] = {
|
|
{1, 0, 0, 0},
|
|
{1, 1, 0, 0},
|
|
{1, 2, 1, 0},
|
|
{1, 3, 3, 1},
|
|
};
|
|
std::array<uint64_t, Degree + 1> c{};
|
|
for (std::size_t m = 0; m <= Degree; ++m)
|
|
{
|
|
for (std::size_t k = 0; k <= m; ++k)
|
|
{
|
|
uint64_t cmk = 1;
|
|
for (std::size_t t = 0; t < m - k; ++t)
|
|
cmk *= center;
|
|
c[k] += a[m] * binom[m][k] * cmk;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
template <std::size_t Degree, typename T>
|
|
const std::array<uint64_t, Degree + 1> & coeff_of_wrapped(
|
|
T center, T eta, const std::vector<T> & knots,
|
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
|
|
{
|
|
struct row
|
|
{
|
|
T knot;
|
|
std::size_t id;
|
|
};
|
|
std::vector<row> rows(knots.size());
|
|
for (std::size_t i = 0; i < knots.size(); ++i)
|
|
rows[i] = row{offset_horner_group_sub(knots[i], eta), i};
|
|
std::sort(rows.begin(), rows.end(),
|
|
[](const row & a, const row & b) { return a.knot < b.knot; });
|
|
std::vector<T> shifted(rows.size());
|
|
for (std::size_t i = 0; i < rows.size(); ++i)
|
|
shifted[i] = rows[i].knot;
|
|
const int hot = circular_piece(center, shifted);
|
|
return coeff[rows[static_cast<std::size_t>(hot)].id];
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
template <std::size_t Party, std::size_t Degree, typename T>
|
|
uint64_t party_eval(const grotto::offset_horner_keys<T, Degree> & mat,
|
|
const std::vector<T> & knots,
|
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff, T eta)
|
|
{
|
|
return grotto::offset_horner_eval<Party, Degree>(mat, knots, coeff, eta);
|
|
}
|
|
|
|
template <std::size_t Degree, typename T>
|
|
uint64_t open_eval(const grotto::offset_horner_keys<T, Degree> & mat,
|
|
const std::vector<T> & knots,
|
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff, T eta)
|
|
{
|
|
return party_eval<0, Degree>(mat, knots, coeff, eta)
|
|
+ party_eval<1, Degree>(mat, knots, coeff, eta);
|
|
}
|
|
|
|
template <std::size_t Degree, typename T>
|
|
std::array<uint64_t, Degree + 1> open_coeffs(
|
|
const grotto::offset_horner_keys<T, Degree> & mat,
|
|
const std::vector<T> & knots,
|
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff, T eta)
|
|
{
|
|
auto a = grotto::offset_horner_coefficient_share<0, Degree>(mat, knots, coeff, eta);
|
|
auto b = grotto::offset_horner_coefficient_share<1, Degree>(mat, knots, coeff, eta);
|
|
for (std::size_t k = 0; k <= Degree; ++k)
|
|
a[k] += b[k];
|
|
return a;
|
|
}
|
|
|
|
inline std::vector<std::array<uint64_t, 4>> pad3(
|
|
std::initializer_list<std::initializer_list<uint64_t>> rows)
|
|
{
|
|
std::vector<std::array<uint64_t, 4>> out;
|
|
out.reserve(rows.size());
|
|
for (const auto & row : rows)
|
|
{
|
|
std::array<uint64_t, 4> a{};
|
|
std::size_t k = 0;
|
|
for (uint64_t v : row)
|
|
{
|
|
if (k >= 4)
|
|
break;
|
|
a[k++] = v;
|
|
}
|
|
out.push_back(a);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
template <std::size_t Degree>
|
|
std::vector<std::array<uint64_t, Degree + 1>> take_degree(
|
|
const std::vector<std::array<uint64_t, 4>> & rows)
|
|
{
|
|
std::vector<std::array<uint64_t, Degree + 1>> out(rows.size());
|
|
for (std::size_t i = 0; i < rows.size(); ++i)
|
|
for (std::size_t k = 0; k <= Degree; ++k)
|
|
out[i][k] = rows[i][k];
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(OffsetHorner, BinomialAgreesWithPowerSum)
|
|
{
|
|
const std::array<uint64_t, 4> a{5, 0, 1, 2};
|
|
const auto c = binomial_shift<3>(a, 3);
|
|
EXPECT_EQ(c[0], 68u);
|
|
EXPECT_EQ(c[1], 60u);
|
|
EXPECT_EQ(c[2], 19u);
|
|
EXPECT_EQ(c[3], 2u);
|
|
uint64_t y = 0;
|
|
uint64_t p = 1;
|
|
for (uint64_t ck : c)
|
|
{
|
|
y += ck * p;
|
|
p *= 4;
|
|
}
|
|
EXPECT_EQ(y, 740u);
|
|
EXPECT_EQ(power_sum<3>(a, 7), 740u);
|
|
}
|
|
|
|
TEST(OffsetHorner, HandCubicAtCenterPlusEta)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const std::vector<uint8_t> knots{0};
|
|
const auto coeff = take_degree<D>(pad3({{5, 0, 1, 2}}));
|
|
const uint8_t center = 3;
|
|
const uint8_t eta = 4;
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
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);
|
|
// 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);
|
|
EXPECT_EQ(p0 + p1, 740u);
|
|
EXPECT_NE(p0, 740u);
|
|
}
|
|
|
|
TEST(OffsetHorner, MultiPieceSelectsWrappedInput)
|
|
{
|
|
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 eta = 3;
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
|
EXPECT_EQ(want, 30u);
|
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
|
}
|
|
|
|
TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
|
|
{
|
|
constexpr std::size_t D = 1;
|
|
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 center = 144;
|
|
const uint8_t eta = 156;
|
|
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
|
EXPECT_EQ(wrapped, 44);
|
|
EXPECT_EQ(lift(center) + lift(eta), 300u);
|
|
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);
|
|
}
|
|
|
|
TEST(OffsetHorner, XPlusRWiring)
|
|
{
|
|
constexpr std::size_t D = 2;
|
|
const std::vector<uint8_t> knots{0, 40, 100};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{3, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{4, 0, 0, 0},
|
|
}));
|
|
const uint8_t x = 20;
|
|
const uint8_t r = 6;
|
|
const auto q = grotto::offset_horner_at_x_plus_r(x, r);
|
|
EXPECT_EQ(q.eta, offset_horner_group_sub(x, r));
|
|
EXPECT_EQ(q.center, offset_horner_group_add(r, r));
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(q.center);
|
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, q.eta), gold<D>(q.center, q.eta, knots, coeff));
|
|
}
|
|
|
|
TEST(OffsetHorner, DegreesZeroOneAndTwo)
|
|
{
|
|
const std::vector<uint8_t> knots{0, 20, 40};
|
|
const auto full = pad3({
|
|
{4, 0, 0, 0},
|
|
{1, 3, 0, 0},
|
|
{2, 0, 5, 0},
|
|
});
|
|
const uint8_t center = 25;
|
|
const uint8_t eta = 7;
|
|
{
|
|
constexpr std::size_t D = 0;
|
|
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));
|
|
}
|
|
{
|
|
constexpr std::size_t D = 1;
|
|
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));
|
|
}
|
|
{
|
|
constexpr std::size_t D = 2;
|
|
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));
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, WholeDomainAndWrapPiece)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const auto only = take_degree<D>(pad3({{8, 1, 0, 1}}));
|
|
auto one = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{200});
|
|
EXPECT_EQ(open_eval<D>(one, std::vector<uint8_t>{0}, only, uint8_t{9}),
|
|
gold<D>(uint8_t{200}, uint8_t{9}, std::vector<uint8_t>{0}, only));
|
|
|
|
const std::vector<uint8_t> knots{10, 20};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 0, 0, 0},
|
|
{6, 2, 0, 0},
|
|
}));
|
|
for (uint8_t center : {uint8_t{0}, uint8_t{5}, uint8_t{10}, uint8_t{19}, uint8_t{20}, uint8_t{255}})
|
|
{
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
for (uint8_t eta : {uint8_t{0}, uint8_t{1}, uint8_t{15}, uint8_t{200}})
|
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff))
|
|
<< "center=" << int(center) << " eta=" << int(eta);
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, CenterOrEtaZero)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const std::vector<uint8_t> knots{0, 8, 16, 64, 200};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 2, 3, 4},
|
|
{5, 0, 1, 0},
|
|
{0, 0, 0, 1},
|
|
{9, 9, 0, 0},
|
|
{2, 0, 0, 0},
|
|
}));
|
|
auto at0 = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{0});
|
|
EXPECT_EQ(open_eval<D>(at0, knots, coeff, uint8_t{3}), gold<D>(uint8_t{0}, uint8_t{3}, knots, coeff));
|
|
auto at = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{70});
|
|
EXPECT_EQ(open_eval<D>(at, knots, coeff, uint8_t{0}), gold<D>(uint8_t{70}, uint8_t{0}, knots, coeff));
|
|
}
|
|
|
|
TEST(OffsetHorner, NegativeCoefficientsAndSignedDomain)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const std::vector<int8_t> knots{-128, -40, -1, 0, 20, 100};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{uint64_t(-3), 4, 0, 1},
|
|
{0, uint64_t(-1), 2, 0},
|
|
{8, 0, 0, 0},
|
|
{1, 1, 1, 1},
|
|
{uint64_t(-5), uint64_t(-5), 0, 0},
|
|
{2, 0, uint64_t(-1), 0},
|
|
}));
|
|
const int8_t center = -2;
|
|
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)));
|
|
|
|
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}),
|
|
gold<D>(int8_t{127}, int8_t{-4}, knots, coeff));
|
|
auto at_min = grotto::make_offset_horner_keys<int8_t, D>(std::numeric_limits<int8_t>::min());
|
|
EXPECT_EQ(open_eval<D>(at_min, knots, coeff, int8_t{1}),
|
|
gold<D>(std::numeric_limits<int8_t>::min(), int8_t{1}, knots, coeff));
|
|
}
|
|
|
|
TEST(OffsetHorner, ShiftedKnotsThatAreNotSortedStillSelect)
|
|
{
|
|
constexpr std::size_t D = 2;
|
|
const std::vector<uint8_t> knots{0, 10, 20};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 0, 0, 0},
|
|
{0, 3, 0, 0},
|
|
{0, 0, 2, 0},
|
|
}));
|
|
// eta = 5 rotates 0,10,20 to 251,5,15. The walk sees them sorted.
|
|
const uint8_t eta = 5;
|
|
for (uint8_t center : {uint8_t{3}, uint8_t{6}, uint8_t{16}, uint8_t{252}})
|
|
{
|
|
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))
|
|
<< int(center);
|
|
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
|
const auto & selected = coeff_of_wrapped<D>(center, eta, knots, coeff);
|
|
EXPECT_EQ(selected, coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))]);
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, RingOverflowDiffersFromWideInteger)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const std::vector<uint32_t> knots{0};
|
|
const std::array<uint64_t, 4> a{0, 0, 0, 1};
|
|
const auto coeff = std::vector<std::array<uint64_t, 4>>{a};
|
|
const uint32_t center = 3u << 20;
|
|
const uint32_t eta = 1u << 20;
|
|
using u128 = unsigned __int128;
|
|
const u128 wide = u128(center) + eta;
|
|
const u128 wide_p = wide * wide * wide;
|
|
auto mat = grotto::make_offset_horner_keys<uint32_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, static_cast<uint64_t>(wide_p));
|
|
EXPECT_NE(wide_p, u128(got));
|
|
}
|
|
|
|
TEST(OffsetHorner, WrapSharesHideThePayload)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const uint8_t center = 9;
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
uint64_t pow = 1;
|
|
for (std::size_t m = 0; m <= D; ++m)
|
|
{
|
|
EXPECT_EQ(mat.wrap_share[m][0] + mat.wrap_share[m][1], pow);
|
|
if (pow != 0)
|
|
EXPECT_NE(mat.wrap_share[m][0], pow);
|
|
pow *= lift(center);
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, PowerKeysAreIndependentComparisons)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const uint16_t center = 1000;
|
|
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
|
const auto & k0 = std::get<0>(mat.keys[0]);
|
|
const auto & k1 = std::get<0>(mat.keys[1]);
|
|
EXPECT_NE(std::memcmp(&k0.root(), &k1.root(), sizeof(k0.root())), 0);
|
|
bool cw_differs = false;
|
|
const std::size_t depth = std::remove_reference_t<decltype(k0)>::depth;
|
|
for (std::size_t level = 0; level < depth; ++level)
|
|
{
|
|
if (k0.value_cw(level) != k1.value_cw(level))
|
|
cw_differs = true;
|
|
}
|
|
EXPECT_TRUE(cw_differs);
|
|
EXPECT_EQ(mat.keys.size(), D + 1);
|
|
}
|
|
|
|
TEST(OffsetHorner, DegreeZeroMatchesSignRespectingDot)
|
|
{
|
|
constexpr std::size_t D = 0;
|
|
const std::vector<uint8_t> knots{0, 15, 80, 200};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{4, 0, 0, 0},
|
|
{11, 0, 0, 0},
|
|
{uint64_t(-2), 0, 0, 0},
|
|
{9, 0, 0, 0},
|
|
}));
|
|
const uint8_t center = 90;
|
|
const uint8_t eta = 30;
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
|
|
struct row { uint8_t knot; uint64_t a; };
|
|
std::vector<row> rows;
|
|
for (std::size_t i = 0; i < knots.size(); ++i)
|
|
rows.push_back(row{offset_horner_group_sub(knots[i], eta), coeff[i][0]});
|
|
std::sort(rows.begin(), rows.end(),
|
|
[](const row & a, const row & b) { return a.knot < b.knot; });
|
|
std::array<uint8_t, 4> shifted{};
|
|
for (std::size_t i = 0; i < rows.size(); ++i)
|
|
shifted[i] = rows[i].knot;
|
|
auto unit = dpf::make_dpf(center, dpf::gt(uint64_t{1}));
|
|
const auto s0 = grotto::signed_segment_parities(std::get<0>(unit), shifted);
|
|
const auto s1 = grotto::signed_segment_parities(std::get<1>(unit), shifted);
|
|
uint64_t dot = 0;
|
|
for (std::size_t i = 0; i < rows.size(); ++i)
|
|
dot += (s0[i] + s1[i]) * rows[i].a;
|
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), dot);
|
|
EXPECT_EQ(dot, gold<D>(center, eta, knots, coeff));
|
|
}
|
|
|
|
TEST(OffsetHorner, AdviceBitSignIsNotACoefficientShare)
|
|
{
|
|
const std::vector<uint8_t> knots{0, 10, 40, 90, 140, 200};
|
|
std::array<uint8_t, 6> ends{};
|
|
for (std::size_t i = 0; i < knots.size(); ++i)
|
|
ends[i] = knots[i];
|
|
const std::array<uint64_t, 6> constants{3, 5, 7, 11, 13, 17};
|
|
bool saw_negative = false;
|
|
bool saw_positive = false;
|
|
for (int alpha = 0; alpha < 256; ++alpha)
|
|
{
|
|
const auto a = static_cast<uint8_t>(alpha);
|
|
auto [k0, k1] = dpf::make_dpf(a, dpf::bit::one);
|
|
const auto s0 = grotto::segment_parities(k0, ends);
|
|
const auto s1 = grotto::segment_parities(k1, ends);
|
|
int sign = 0;
|
|
int64_t acc = 0;
|
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
|
{
|
|
const int bit = int(s0[i]) - int(s1[i]);
|
|
sign += bit;
|
|
acc += bit * static_cast<int64_t>(constants[i]);
|
|
}
|
|
ASSERT_EQ(sign * sign, 1) << alpha;
|
|
const int64_t corrected = sign * acc;
|
|
const int hot = circular_piece(a, knots);
|
|
ASSERT_EQ(corrected, static_cast<int64_t>(constants[static_cast<std::size_t>(hot)])) << alpha;
|
|
if (sign < 0)
|
|
{
|
|
saw_negative = true;
|
|
EXPECT_EQ(acc, -corrected);
|
|
}
|
|
else
|
|
{
|
|
saw_positive = true;
|
|
}
|
|
}
|
|
EXPECT_TRUE(saw_negative);
|
|
EXPECT_TRUE(saw_positive);
|
|
|
|
// The sign-respecting unit payload does not flip.
|
|
const uint8_t probe = 40;
|
|
auto cmp = dpf::make_dpf(probe, dpf::gt(uint64_t{1}));
|
|
const auto p0 = grotto::signed_segment_parities(std::get<0>(cmp), ends);
|
|
const auto p1 = grotto::signed_segment_parities(std::get<1>(cmp), ends);
|
|
uint64_t opened = 0;
|
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
|
opened += (p0[i] + p1[i]) * constants[i];
|
|
EXPECT_EQ(opened, constants[static_cast<std::size_t>(circular_piece(probe, knots))]);
|
|
}
|
|
|
|
TEST(OffsetHorner, SignRespectingOneHotIsNotTheShiftedCubic)
|
|
{
|
|
constexpr std::size_t D = 2;
|
|
const std::vector<uint8_t> knots{0, 50, 150};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 0, 0, 0},
|
|
{0, 4, 1, 0},
|
|
{8, 0, 0, 0},
|
|
}));
|
|
const uint8_t center = 10;
|
|
const uint8_t eta = 60;
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
|
const uint64_t ours = open_eval<D>(mat, knots, coeff, eta);
|
|
|
|
auto unit = dpf::make_dpf(center, dpf::gt(uint64_t{1}));
|
|
std::array<uint8_t, 3> ends{0, 50, 150};
|
|
const auto s0 = grotto::signed_segment_parities(std::get<0>(unit), ends);
|
|
const auto s1 = grotto::signed_segment_parities(std::get<1>(unit), ends);
|
|
uint64_t const_term = 0;
|
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
|
const_term += (s0[i] + s1[i]) * coeff[i][0];
|
|
// Unit payload, unshifted knots: the piece of `center`, and only its constant.
|
|
EXPECT_EQ(const_term, 1u);
|
|
EXPECT_NE(ours, const_term);
|
|
EXPECT_EQ(ours, gold<D>(center, eta, knots, coeff));
|
|
}
|
|
|
|
TEST(OffsetHorner, PrefixIntoMatchesFixedArray)
|
|
{
|
|
const uint8_t alpha = 40;
|
|
auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{7}));
|
|
const std::array<uint8_t, 5> ends{0, 1, 10, 40, 200};
|
|
const auto fixed = grotto::signed_prefix_parities(k0, ends);
|
|
uint64_t into[5] = {};
|
|
grotto::signed_prefix_parities_into(k0, ends.data(), ends.size(), into);
|
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
|
EXPECT_EQ(into[i], fixed[i]);
|
|
const uint64_t mask = k0.cmp().mask;
|
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
|
{
|
|
const auto one = std::array<uint8_t, 1>{ends[i]};
|
|
const auto alone0 = grotto::signed_prefix_parities(k0, one);
|
|
const auto alone1 = grotto::signed_prefix_parities(k1, one);
|
|
EXPECT_EQ((alone0[0] + alone1[0]) & mask, ends[i] > alpha ? 7u : 0u);
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, RejectsBadKnots)
|
|
{
|
|
constexpr std::size_t D = 1;
|
|
const auto coeff = take_degree<D>(pad3({{1, 1, 0, 0}, {2, 0, 0, 0}}));
|
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{1});
|
|
EXPECT_THROW(open_eval<D>(mat, std::vector<uint8_t>{}, coeff, uint8_t{0}), std::invalid_argument);
|
|
EXPECT_THROW(open_eval<D>(mat, std::vector<uint8_t>{1, 1}, coeff, uint8_t{0}), std::invalid_argument);
|
|
EXPECT_THROW(open_eval<D>(mat, std::vector<uint8_t>{3, 2}, coeff, uint8_t{0}), std::invalid_argument);
|
|
EXPECT_THROW(open_eval<D>(mat, std::vector<uint8_t>{0}, coeff, uint8_t{0}), std::invalid_argument);
|
|
}
|
|
|
|
TEST(OffsetHorner, ManyPiecesAndRandomUint16)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
std::mt19937 rng(0x0ff5e7u);
|
|
std::vector<uint16_t> knots;
|
|
for (uint16_t k = 0; knots.size() < 20; k = static_cast<uint16_t>(k + 1000))
|
|
knots.push_back(k);
|
|
std::vector<std::array<uint64_t, D + 1>> coeff(knots.size());
|
|
for (auto & row : coeff)
|
|
for (uint64_t & a : row)
|
|
a = rng();
|
|
std::uniform_int_distribution<int> dist(0, 65535);
|
|
for (int trial = 0; trial < 30; ++trial)
|
|
{
|
|
const auto center = static_cast<uint16_t>(dist(rng));
|
|
const auto eta = static_cast<uint16_t>(dist(rng));
|
|
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)));
|
|
}
|
|
}
|
|
|
|
TEST(OffsetHorner, ExhaustiveUint8AgreesWithGoldAndCountsWraps)
|
|
{
|
|
constexpr std::size_t D = 2;
|
|
const std::vector<uint8_t> knots{0, 30, 80, 140, 200};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 2, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{4, 1, 0, 0},
|
|
{9, 0, 2, 0},
|
|
{3, 5, 0, 0},
|
|
}));
|
|
int point_mismatch = 0;
|
|
int value_mismatch = 0;
|
|
int piece_mismatch = 0;
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
ADD_FAILURE() << "center=" << c << " eta=" << e
|
|
<< " got=" << got << " want=" << want;
|
|
return;
|
|
}
|
|
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
|
const uint64_t unreduced = lift(center) + lift(eta);
|
|
if (unreduced != lift(wrapped))
|
|
++point_mismatch;
|
|
const auto & selected = coeff_of_wrapped<D>(center, eta, knots, coeff);
|
|
const auto & wrapped_row = coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))];
|
|
if (selected != wrapped_row)
|
|
++piece_mismatch;
|
|
if (power_sum<D>(selected, unreduced) != power_sum<D>(selected, lift(wrapped)))
|
|
++value_mismatch;
|
|
}
|
|
}
|
|
EXPECT_EQ(point_mismatch, 32640);
|
|
EXPECT_EQ(piece_mismatch, 0);
|
|
EXPECT_GT(value_mismatch, 0);
|
|
EXPECT_LT(value_mismatch, 65536);
|
|
}
|
|
|
|
TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
const std::vector<int8_t> knots{-128, -50, -3, 0, 40, 90};
|
|
const auto coeff = take_degree<D>(pad3({
|
|
{1, 0, 0, 1},
|
|
{0, uint64_t(-2), 0, 0},
|
|
{5, 1, 1, 0},
|
|
{0, 0, 3, 0},
|
|
{2, 0, 0, 0},
|
|
{uint64_t(-4), 1, 0, 1},
|
|
}));
|
|
int point_mismatch = 0;
|
|
int piece_mismatch = 0;
|
|
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);
|
|
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
|
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
|
if (got != want)
|
|
{
|
|
ADD_FAILURE() << "center=" << c << " eta=" << e
|
|
<< " got=" << got << " want=" << want;
|
|
return;
|
|
}
|
|
const int8_t wrapped = offset_horner_group_add(center, eta);
|
|
const uint64_t unreduced = lift(center) + lift(eta);
|
|
if (unreduced != lift(wrapped))
|
|
++point_mismatch;
|
|
const auto & selected = coeff_of_wrapped<D>(center, eta, knots, coeff);
|
|
const auto & wrapped_row = coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))];
|
|
if (selected != wrapped_row)
|
|
++piece_mismatch;
|
|
}
|
|
}
|
|
EXPECT_EQ(point_mismatch, 16384);
|
|
EXPECT_EQ(piece_mismatch, 0);
|
|
}
|
|
|
|
TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
|
{
|
|
constexpr std::size_t D = 3;
|
|
std::mt19937 rng(1);
|
|
const std::vector<uint8_t> knots{0, 25, 100, 180};
|
|
std::vector<std::array<uint64_t, D + 1>> coeff(knots.size());
|
|
for (auto & row : coeff)
|
|
for (uint64_t & a : row)
|
|
a = rng();
|
|
const uint8_t center = 77;
|
|
const uint8_t eta = 19;
|
|
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;
|
|
}
|
|
EXPECT_EQ(y, open_eval<D>(mat, knots, coeff, eta));
|
|
EXPECT_EQ(y, gold<D>(center, eta, knots, coeff));
|
|
}
|