696 lines
27 KiB
C++
696 lines
27 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include <array>
|
||
|
|
#include <cstdint>
|
||
|
|
#include <stdexcept>
|
||
|
|
#include <tuple>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include "dpf.hpp"
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
|
||
|
|
using In = std::uint8_t;
|
||
|
|
|
||
|
|
template <typename A, typename B>
|
||
|
|
auto open(const A & a, const B & b)
|
||
|
|
{
|
||
|
|
if constexpr (dpf::is_secret_share_v<A>)
|
||
|
|
return dpf::reconstruct(a, b);
|
||
|
|
else if constexpr (dpf::utils::is_xor_wrapper_v<A>)
|
||
|
|
return static_cast<A>(a ^ b);
|
||
|
|
else
|
||
|
|
return static_cast<A>(a - b);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(InnerProduct, ScalarIntervalMatchesPoints)
|
||
|
|
{
|
||
|
|
const In alpha = 42;
|
||
|
|
const std::uint64_t beta = 7;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
const In from = 40, to = 50;
|
||
|
|
std::vector<std::uint64_t> w(to - from + 1);
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
{
|
||
|
|
w[i] = i + 1;
|
||
|
|
const In x = static_cast<In>(from + i);
|
||
|
|
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||
|
|
expect += static_cast<std::uint64_t>(y) * w[i];
|
||
|
|
}
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||
|
|
expect);
|
||
|
|
EXPECT_EQ(expect, beta * w[alpha - from]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, FullDomainOnlyAlpha)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{7}, std::uint64_t{9});
|
||
|
|
std::vector<std::uint64_t> w(256);
|
||
|
|
for (unsigned i = 0; i < w.size(); ++i)
|
||
|
|
w[i] = (i * 3u) & 15u;
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_full_inner_product(dpf::paired, k0, w),
|
||
|
|
dpf::eval_full_inner_product(dpf::paired, k1, w)),
|
||
|
|
std::uint64_t{9} * w[7]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, TwoOutputsSameLeaf)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{9}, std::uint32_t{3}, std::uint32_t{5});
|
||
|
|
std::vector<std::array<std::uint32_t, 2>> rows;
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
for (In x = 8;; ++x)
|
||
|
|
{
|
||
|
|
const std::uint32_t w0 = 1, w1 = x;
|
||
|
|
rows.push_back({w0, w1});
|
||
|
|
const auto y0 = open(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x));
|
||
|
|
const auto y1 = open(*dpf::eval_point<1>(k0, x), *dpf::eval_point<1>(k1, x));
|
||
|
|
expect += static_cast<std::uint64_t>(y0) * w0
|
||
|
|
+ static_cast<std::uint64_t>(y1) * w1;
|
||
|
|
if (x == 10)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product<0, 1>(dpf::paired, k0, In{8}, In{10}, rows),
|
||
|
|
dpf::eval_inner_product<0, 1>(dpf::paired, k1, In{8}, In{10}, rows)),
|
||
|
|
expect);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, TupleRowMatchesArray)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{2}, std::uint16_t{6});
|
||
|
|
const std::vector<In> pts{1, 4, 8};
|
||
|
|
std::vector<std::tuple<std::uint32_t, std::uint32_t>> tuples{
|
||
|
|
{1u, 1u}, {3u, 5u}, {7u, 9u}};
|
||
|
|
std::vector<std::array<std::uint32_t, 2>> arrays{{1u, 1u}, {3u, 5u}, {7u, 9u}};
|
||
|
|
const auto t0 = dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), tuples);
|
||
|
|
const auto t1 = dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), tuples);
|
||
|
|
const auto a0 = dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), arrays);
|
||
|
|
const auto a1 = dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), arrays);
|
||
|
|
EXPECT_EQ(open(t0, t1), open(a0, a1));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, AncestorAndLeaf)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{0x2a}, dpf::at<4>(std::uint8_t{5}), std::uint8_t{9});
|
||
|
|
const std::vector<In> pts{0x10, 0x2a, 0x2b, 0x30};
|
||
|
|
const std::vector<std::array<std::uint32_t, 2>> rows{{1, 0}, {1, 1}, {2, 4}, {8, 1}};
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
for (std::size_t i = 0; i < pts.size(); ++i)
|
||
|
|
{
|
||
|
|
const auto y0 = open(*dpf::eval_point(dpf::out<0>, k0, pts[i]),
|
||
|
|
*dpf::eval_point(dpf::out<0>, k1, pts[i]));
|
||
|
|
const auto y1 = open(*dpf::eval_point(dpf::out<1>, k0, pts[i]),
|
||
|
|
*dpf::eval_point(dpf::out<1>, k1, pts[i]));
|
||
|
|
expect += static_cast<std::uint64_t>(y0) * rows[i][0]
|
||
|
|
+ static_cast<std::uint64_t>(y1) * rows[i][1];
|
||
|
|
if (pts[i] == In{0x2a} || pts[i] == In{0x2b})
|
||
|
|
EXPECT_EQ(y0, std::uint8_t{5});
|
||
|
|
else
|
||
|
|
EXPECT_EQ(y0, std::uint8_t{0});
|
||
|
|
}
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), rows),
|
||
|
|
dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), rows)),
|
||
|
|
expect);
|
||
|
|
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_sequence_inner_product<0, 1>(k0, recipe, pts.begin(), pts.end(), rows),
|
||
|
|
dpf::eval_sequence_inner_product<0, 1>(k1, recipe, pts.begin(), pts.end(), rows)),
|
||
|
|
expect);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, WrapInterval)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{255}, std::uint32_t{4});
|
||
|
|
const In from = 250, to = 2;
|
||
|
|
std::vector<std::uint32_t> w;
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
auto push = [&](In x) {
|
||
|
|
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||
|
|
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||
|
|
expect += static_cast<std::uint64_t>(y) * w.back();
|
||
|
|
};
|
||
|
|
for (unsigned x = from; x < 256; ++x)
|
||
|
|
push(static_cast<In>(x));
|
||
|
|
for (unsigned x = 0; x <= to; ++x)
|
||
|
|
push(static_cast<In>(x));
|
||
|
|
EXPECT_EQ(w.size(), 9u);
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||
|
|
expect);
|
||
|
|
EXPECT_EQ(expect, std::uint64_t{4} * w[5]); // 255 is the 6th point, index 5
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, XorWeights)
|
||
|
|
{
|
||
|
|
using X = dpf::xor_wrapper<std::uint32_t>;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{3}, X{0x0fu});
|
||
|
|
const std::vector<In> pts{1, 3, 4};
|
||
|
|
const std::vector<X> w{X{0xffu}, X{0xf0u}, X{0x0fu}};
|
||
|
|
// Only x=3 is hot: 0x0f AND 0xf0.
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||
|
|
dpf::eval_sequence_inner_product<0>(k1, pts.begin(), pts.end(), w)),
|
||
|
|
X{0x0fu & 0xf0u});
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, EmptySequenceIsZero)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||
|
|
const std::vector<In> pts;
|
||
|
|
const std::vector<std::uint64_t> w;
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||
|
|
dpf::eval_sequence_inner_product<0>(k1, pts.begin(), pts.end(), w)),
|
||
|
|
std::uint64_t{0});
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, UnsortedSequenceThrows)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||
|
|
(void)k1;
|
||
|
|
const std::vector<In> pts{3, 1};
|
||
|
|
const std::vector<std::uint64_t> w{1, 1};
|
||
|
|
EXPECT_THROW(
|
||
|
|
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||
|
|
std::runtime_error);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsThreeMomentsOneWalk)
|
||
|
|
{
|
||
|
|
const In alpha = 42;
|
||
|
|
const std::uint64_t beta = 7;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
const std::vector<In> pts{40, 41, 42, 50};
|
||
|
|
std::vector<std::uint64_t> r(pts.size());
|
||
|
|
std::vector<std::uint64_t> r2(pts.size());
|
||
|
|
std::vector<std::uint64_t> ones(pts.size(), 1);
|
||
|
|
std::uint64_t expect_sum = 0, expect_dot = 0, expect_sq = 0;
|
||
|
|
for (std::size_t i = 0; i < pts.size(); ++i)
|
||
|
|
{
|
||
|
|
r[i] = 3 + i * 5;
|
||
|
|
r2[i] = r[i] * r[i];
|
||
|
|
const auto y = open(*dpf::eval_point(k0, pts[i]), *dpf::eval_point(k1, pts[i]));
|
||
|
|
expect_sum += static_cast<std::uint64_t>(y);
|
||
|
|
expect_dot += static_cast<std::uint64_t>(y) * r[i];
|
||
|
|
expect_sq += static_cast<std::uint64_t>(y) * r2[i];
|
||
|
|
}
|
||
|
|
const auto streams = std::tie(ones, r, r2);
|
||
|
|
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), streams);
|
||
|
|
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k1, pts.begin(), pts.end(), streams);
|
||
|
|
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), expect_sum);
|
||
|
|
EXPECT_EQ(open(std::get<1>(s0), std::get<1>(s1)), expect_dot);
|
||
|
|
EXPECT_EQ(open(std::get<2>(s0), std::get<2>(s1)), expect_sq);
|
||
|
|
EXPECT_EQ(expect_sum, beta);
|
||
|
|
EXPECT_EQ(expect_dot, beta * r[2]);
|
||
|
|
EXPECT_EQ(expect_sq, beta * r2[2]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsCallableStreamsAndSideVisit)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{9}, std::uint32_t{4});
|
||
|
|
const std::vector<In> pts{1, 9, 12};
|
||
|
|
auto ones = [](std::size_t) { return std::uint32_t{1}; };
|
||
|
|
auto scale = [](std::size_t i) { return std::uint32_t(i + 2); };
|
||
|
|
std::size_t visits = 0;
|
||
|
|
std::uint32_t seen = 0;
|
||
|
|
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), std::tie(ones, scale),
|
||
|
|
dpf::project([](auto share) { return share + share; }),
|
||
|
|
dpf::also([&](std::size_t, In x, auto share) {
|
||
|
|
++visits;
|
||
|
|
if (x == In{9})
|
||
|
|
seen = share.raw();
|
||
|
|
}));
|
||
|
|
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k1, pts.begin(), pts.end(), std::tie(ones, scale),
|
||
|
|
dpf::project([](auto share) { return share + share; }));
|
||
|
|
EXPECT_EQ(visits, pts.size());
|
||
|
|
const auto hot = *dpf::eval_point(k0, In{9});
|
||
|
|
EXPECT_EQ(seen, hot.raw());
|
||
|
|
// project doubles the share, so the opened moments are 2 * beta * weight.
|
||
|
|
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), std::uint64_t{8});
|
||
|
|
EXPECT_EQ(open(std::get<1>(s0), std::get<1>(s1)), std::uint64_t{8} * 3u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsIntervalMatchesSequence)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{6});
|
||
|
|
const In from = 2, to = 6;
|
||
|
|
std::vector<std::uint16_t> w;
|
||
|
|
for (In x = from;; ++x)
|
||
|
|
{
|
||
|
|
w.push_back(static_cast<std::uint16_t>(x));
|
||
|
|
if (x == to)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const auto a0 = dpf::eval_inner_product<0>(dpf::columns, k0, from, to, std::tie(w));
|
||
|
|
const auto a1 = dpf::eval_inner_product<0>(dpf::columns, k1, from, to, std::tie(w));
|
||
|
|
std::vector<In> pts;
|
||
|
|
for (In x = from;; ++x)
|
||
|
|
{
|
||
|
|
pts.push_back(x);
|
||
|
|
if (x == to)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const auto b0 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), std::tie(w));
|
||
|
|
const auto b1 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k1, pts.begin(), pts.end(), std::tie(w));
|
||
|
|
EXPECT_EQ(open(std::get<0>(a0), std::get<0>(a1)),
|
||
|
|
open(std::get<0>(b0), std::get<0>(b1)));
|
||
|
|
EXPECT_EQ(open(std::get<0>(a0), std::get<0>(a1)), std::uint16_t{6} * 4);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, RecipeLengthMismatchThrows)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||
|
|
(void)k1;
|
||
|
|
const std::vector<In> pts{1, 2};
|
||
|
|
const std::vector<In> shorter{1};
|
||
|
|
const std::vector<std::uint64_t> w{1, 1};
|
||
|
|
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||
|
|
EXPECT_THROW(
|
||
|
|
dpf::eval_sequence_inner_product<0>(k0, recipe, shorter.begin(), shorter.end(), w),
|
||
|
|
std::invalid_argument);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsUnsortedAndRecipeMismatchThrow)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||
|
|
(void)k1;
|
||
|
|
const std::vector<In> unsorted{3, 1};
|
||
|
|
const std::vector<std::uint64_t> w{1, 1};
|
||
|
|
EXPECT_THROW(
|
||
|
|
dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, unsorted.begin(), unsorted.end(), std::tie(w)),
|
||
|
|
std::runtime_error);
|
||
|
|
|
||
|
|
const std::vector<In> pts{1, 2};
|
||
|
|
const std::vector<In> shorter{1};
|
||
|
|
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||
|
|
EXPECT_THROW(
|
||
|
|
dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, recipe, shorter.begin(), shorter.end(), std::tie(w)),
|
||
|
|
std::invalid_argument);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, BatchedLeafWalkMatchesPaired)
|
||
|
|
{
|
||
|
|
// Plain `eval_inner_product` (memoized leaf walk) and `dpf::paired` both
|
||
|
|
// compute sum_x DPF(x)*w[x] when there is one output and `[from, to]` is
|
||
|
|
// leaf-aligned, so the covering-leaf weight layout matches the clipped
|
||
|
|
// domain points.
|
||
|
|
const In alpha = 36;
|
||
|
|
const std::uint64_t beta = 11;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
const In from = 30, to = 45; // even..odd => full covering leaves
|
||
|
|
ASSERT_EQ(decltype(k0)::outputs_per_leaf, 2u);
|
||
|
|
std::vector<std::uint64_t> w(to - from + 1);
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
w[i] = (i * 7u) + 3u;
|
||
|
|
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||
|
|
const auto leaf0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||
|
|
const auto leaf1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||
|
|
const auto pair0 = dpf::eval_inner_product(dpf::paired, k0, from, to, w);
|
||
|
|
const auto pair1 = dpf::eval_inner_product(dpf::paired, k1, from, to, w);
|
||
|
|
EXPECT_EQ(open(leaf0, leaf1), open(pair0, pair1));
|
||
|
|
EXPECT_EQ(open(leaf0, leaf1), beta * w[alpha - from]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsOneStreamMatchesPaired)
|
||
|
|
{
|
||
|
|
// Transposed (`columns`) with a single weight stream is the same scalar
|
||
|
|
// product as `paired` on that stream.
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{12}, std::uint32_t{5});
|
||
|
|
const In from = 8, to = 20;
|
||
|
|
std::vector<std::uint32_t> w;
|
||
|
|
for (In x = from;; ++x)
|
||
|
|
{
|
||
|
|
w.push_back(static_cast<std::uint32_t>(x + 1));
|
||
|
|
if (x == to)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const auto p0 = dpf::eval_inner_product(dpf::paired, k0, from, to, w);
|
||
|
|
const auto p1 = dpf::eval_inner_product(dpf::paired, k1, from, to, w);
|
||
|
|
const auto c0 = dpf::eval_inner_product<0>(dpf::columns, k0, from, to, std::tie(w));
|
||
|
|
const auto c1 = dpf::eval_inner_product<0>(dpf::columns, k1, from, to, std::tie(w));
|
||
|
|
EXPECT_EQ(open(p0, p1), open(std::get<0>(c0), std::get<0>(c1)));
|
||
|
|
EXPECT_EQ(open(p0, p1), std::uint32_t{5} * w[12 - 8]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, LengthOneOddAndUnaligned)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{41}, std::uint64_t{9});
|
||
|
|
|
||
|
|
// Single domain point.
|
||
|
|
{
|
||
|
|
const In x = 41;
|
||
|
|
std::vector<std::uint64_t> w{4};
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, x, x, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, x, x, w)),
|
||
|
|
std::uint64_t{9} * 4u);
|
||
|
|
const auto c0 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k0, x, x, std::tie(w));
|
||
|
|
const auto c1 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k1, x, x, std::tie(w));
|
||
|
|
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), std::uint64_t{9} * 4u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Odd length, not leaf-aligned (uint64 packs two lanes per leaf).
|
||
|
|
// `paired` / `columns` weight by clipped domain points; the batched leaf
|
||
|
|
// walk weights the covering leaves (see file brief / cohort docs).
|
||
|
|
{
|
||
|
|
const In from = 39, to = 45; // 7 points; covering leaf also holds 38
|
||
|
|
std::vector<std::uint64_t> w(to - from + 1);
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
{
|
||
|
|
w[i] = i + 2;
|
||
|
|
const In x = static_cast<In>(from + i);
|
||
|
|
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||
|
|
expect += static_cast<std::uint64_t>(y) * w[i];
|
||
|
|
}
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||
|
|
expect);
|
||
|
|
const auto c0 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k0, from, to, std::tie(w));
|
||
|
|
const auto c1 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k1, from, to, std::tie(w));
|
||
|
|
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), expect);
|
||
|
|
EXPECT_EQ(expect, std::uint64_t{9} * w[41 - 39]);
|
||
|
|
|
||
|
|
// Covering-leaf weights: pad the missing start lane (x=38) with 0 so
|
||
|
|
// the batched walk agrees with the clipped paired result.
|
||
|
|
constexpr std::size_t opl = decltype(k0)::outputs_per_leaf;
|
||
|
|
ASSERT_EQ(opl, 2u);
|
||
|
|
std::vector<std::uint64_t> cover(w.size() + 1, 0);
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
cover[i + 1] = w[i];
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(k0, from, to, cover, m0),
|
||
|
|
dpf::eval_inner_product(k1, from, to, cover, m1)),
|
||
|
|
expect);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, EmptyIntervalWeightsStillZero)
|
||
|
|
{
|
||
|
|
// Empty point list already covered; empty closed interval is impossible,
|
||
|
|
// but a sequence of length 0 for columns must stay zero.
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||
|
|
const std::vector<In> pts;
|
||
|
|
const std::vector<std::uint64_t> w;
|
||
|
|
const auto c0 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), std::tie(w));
|
||
|
|
const auto c1 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k1, pts.begin(), pts.end(), std::tie(w));
|
||
|
|
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), std::uint64_t{0});
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ColumnsFullAndAlsoThenProject)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{7}, std::uint16_t{4});
|
||
|
|
std::vector<std::uint16_t> ones(256, 1);
|
||
|
|
std::vector<std::uint16_t> r(256);
|
||
|
|
for (unsigned i = 0; i < 256; ++i)
|
||
|
|
r[i] = static_cast<std::uint16_t>((i * 3u) & 15u);
|
||
|
|
|
||
|
|
const auto f0 = dpf::eval_full_inner_product<0>(
|
||
|
|
dpf::columns, k0, std::tie(ones, r));
|
||
|
|
const auto f1 = dpf::eval_full_inner_product<0>(
|
||
|
|
dpf::columns, k1, std::tie(ones, r));
|
||
|
|
EXPECT_EQ(open(std::get<0>(f0), std::get<0>(f1)), std::uint16_t{4});
|
||
|
|
EXPECT_EQ(open(std::get<1>(f0), std::get<1>(f1)), std::uint16_t{4} * r[7]);
|
||
|
|
|
||
|
|
const std::vector<In> pts{3, 7, 9};
|
||
|
|
std::size_t visits = 0;
|
||
|
|
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), std::tie(ones),
|
||
|
|
dpf::also([&](std::size_t, In, auto) { ++visits; }),
|
||
|
|
dpf::project([](auto share) { return share; }));
|
||
|
|
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k1, pts.begin(), pts.end(), std::tie(ones));
|
||
|
|
EXPECT_EQ(visits, pts.size());
|
||
|
|
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), std::uint16_t{4});
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, PrepareMemoizerThenInnerProduct)
|
||
|
|
{
|
||
|
|
// uint64 packs two lanes per leaf. [10, 31] fills those leaves, so a
|
||
|
|
// weight per domain point is also a weight per covering lane.
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||
|
|
const In from = 10, to = 31;
|
||
|
|
ASSERT_EQ(decltype(k0)::outputs_per_leaf, 2u);
|
||
|
|
ASSERT_EQ(static_cast<unsigned>(to - from + 1) % 2u, 0u);
|
||
|
|
std::vector<std::uint64_t> w(to - from + 1, 2);
|
||
|
|
auto cold0 = dpf::eval_inner_product(k0, from, to, w,
|
||
|
|
dpf::make_basic_interval_memoizer(k0, from, to));
|
||
|
|
auto cold1 = dpf::eval_inner_product(k1, from, to, w,
|
||
|
|
dpf::make_basic_interval_memoizer(k1, from, to));
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||
|
|
dpf::eval_prepare_interval(k0, from, to, m0);
|
||
|
|
dpf::eval_prepare_interval(k1, from, to, m1);
|
||
|
|
const auto warm0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||
|
|
const auto warm1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||
|
|
EXPECT_EQ(warm0, cold0);
|
||
|
|
EXPECT_EQ(warm1, cold1);
|
||
|
|
EXPECT_EQ(open(warm0, warm1), std::uint64_t{6} * 2u);
|
||
|
|
// A second pass on the warm memoizer must not rebuild a different share.
|
||
|
|
EXPECT_EQ(dpf::eval_inner_product(k0, from, to, w, m0), warm0);
|
||
|
|
EXPECT_EQ(dpf::eval_inner_product(k1, from, to, w, m1), warm1);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, IntervalMatchesPointReconstruction)
|
||
|
|
{
|
||
|
|
// Long interval: reopen against point-by-point reconstruction for both
|
||
|
|
// paired and columns (two streams).
|
||
|
|
const In alpha = 100;
|
||
|
|
const std::uint64_t beta = 13;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
const In from = 80, to = 140;
|
||
|
|
const std::size_t n = static_cast<std::size_t>(to - from + 1);
|
||
|
|
std::vector<std::uint64_t> ones(n, 1);
|
||
|
|
std::vector<std::uint64_t> scale(n);
|
||
|
|
std::uint64_t expect_sum = 0, expect_dot = 0;
|
||
|
|
for (std::size_t i = 0; i < n; ++i)
|
||
|
|
{
|
||
|
|
scale[i] = i + 1;
|
||
|
|
const In x = static_cast<In>(from + i);
|
||
|
|
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||
|
|
expect_sum += static_cast<std::uint64_t>(y);
|
||
|
|
expect_dot += static_cast<std::uint64_t>(y) * scale[i];
|
||
|
|
}
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, scale),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, scale)),
|
||
|
|
expect_dot);
|
||
|
|
const auto c0 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k0, from, to, std::tie(ones, scale));
|
||
|
|
const auto c1 = dpf::eval_inner_product<0>(
|
||
|
|
dpf::columns, k1, from, to, std::tie(ones, scale));
|
||
|
|
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), expect_sum);
|
||
|
|
EXPECT_EQ(open(std::get<1>(c0), std::get<1>(c1)), expect_dot);
|
||
|
|
EXPECT_EQ(expect_sum, beta);
|
||
|
|
EXPECT_EQ(expect_dot, beta * scale[alpha - from]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, ShortCoveringWeightsThrow)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||
|
|
(void)k1;
|
||
|
|
// [10, 30] is 21 points and 22 covering lanes (the leaf of 30 also holds 31).
|
||
|
|
const In from = 10, to = 30;
|
||
|
|
std::vector<std::uint64_t> clipped(to - from + 1, 2);
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, clipped, m0),
|
||
|
|
std::invalid_argument);
|
||
|
|
dpf::eval_prepare_interval(k0, from, to, m0);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, clipped, m0),
|
||
|
|
std::invalid_argument);
|
||
|
|
|
||
|
|
std::vector<std::uint64_t> cover(clipped.size() + 1, 2);
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(k0, from, to, cover, m0),
|
||
|
|
dpf::eval_inner_product(k1, from, to, cover,
|
||
|
|
dpf::make_basic_interval_memoizer(k1, from, to))),
|
||
|
|
std::uint64_t{6} * 2u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, OneLaneShortStillThrows)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint64_t{1});
|
||
|
|
(void)k1;
|
||
|
|
const In from = 0, to = 2; // 3 points, 4 covering lanes
|
||
|
|
std::vector<std::uint64_t> almost(3, 1);
|
||
|
|
auto memo = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, almost, memo),
|
||
|
|
std::invalid_argument);
|
||
|
|
almost.push_back(0);
|
||
|
|
EXPECT_NO_THROW(dpf::eval_inner_product(k0, from, to, almost, memo));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, MemoizerSmallerThanIntervalThrows)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{8}, std::uint64_t{3});
|
||
|
|
(void)k1;
|
||
|
|
auto small = dpf::make_basic_interval_memoizer(k0, In{0}, In{1});
|
||
|
|
std::vector<std::uint64_t> w(64, 1);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product(k0, In{0}, In{30}, w, small),
|
||
|
|
std::length_error);
|
||
|
|
EXPECT_THROW(dpf::eval_prepare_interval(k0, In{0}, In{30}, small),
|
||
|
|
std::length_error);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, PairedAndColumnsRejectShortWeights)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{5}, std::uint64_t{2});
|
||
|
|
(void)k1;
|
||
|
|
const In from = 1, to = 8;
|
||
|
|
std::vector<std::uint64_t> short_w(3, 1);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product(dpf::paired, k0, from, to, short_w),
|
||
|
|
std::invalid_argument);
|
||
|
|
EXPECT_THROW(dpf::eval_inner_product<0>(dpf::columns, k0, from, to,
|
||
|
|
std::tie(short_w)), std::invalid_argument);
|
||
|
|
|
||
|
|
const std::vector<In> pts{1, 5, 8};
|
||
|
|
const std::vector<std::uint64_t> one_row{1};
|
||
|
|
EXPECT_THROW(dpf::eval_sequence_inner_product<0>(
|
||
|
|
k0, pts.begin(), pts.end(), one_row), std::invalid_argument);
|
||
|
|
const std::vector<std::uint64_t> one{1};
|
||
|
|
EXPECT_THROW(dpf::eval_sequence_inner_product<0>(
|
||
|
|
dpf::columns, k0, pts.begin(), pts.end(), std::tie(one)),
|
||
|
|
std::invalid_argument);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, PrepareOnAWrapStillMatchesPoints)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint32_t{9});
|
||
|
|
const In from = 250, to = 4;
|
||
|
|
std::vector<std::uint32_t> w;
|
||
|
|
std::uint64_t expect = 0;
|
||
|
|
for (unsigned x = from; x < 256; ++x)
|
||
|
|
{
|
||
|
|
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||
|
|
const auto y = open(*dpf::eval_point(k0, static_cast<In>(x)),
|
||
|
|
*dpf::eval_point(k1, static_cast<In>(x)));
|
||
|
|
expect += static_cast<std::uint64_t>(y) * w.back();
|
||
|
|
}
|
||
|
|
for (unsigned x = 0; x <= to; ++x)
|
||
|
|
{
|
||
|
|
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||
|
|
const auto y = open(*dpf::eval_point(k0, static_cast<In>(x)),
|
||
|
|
*dpf::eval_point(k1, static_cast<In>(x)));
|
||
|
|
expect += static_cast<std::uint64_t>(y) * w.back();
|
||
|
|
}
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||
|
|
dpf::eval_prepare_interval(k0, from, to, m0);
|
||
|
|
dpf::eval_prepare_interval(k1, from, to, m1);
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||
|
|
expect);
|
||
|
|
// A wrap is two leaf segments. The memoizer keeps one, so prepare must
|
||
|
|
// leave the paired product unchanged and must not throw.
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||
|
|
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||
|
|
expect);
|
||
|
|
std::vector<std::uint32_t> too_short(w.size() - 1, 1);
|
||
|
|
EXPECT_THROW(
|
||
|
|
dpf::eval_inner_product(dpf::paired, k0, from, to, too_short),
|
||
|
|
std::invalid_argument);
|
||
|
|
(void)m0;
|
||
|
|
(void)m1;
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, BatchedWalkMatchesIntervalBuffer)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||
|
|
const In from = 10, to = 30;
|
||
|
|
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
|
||
|
|
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
|
||
|
|
(void)it0;
|
||
|
|
(void)it1;
|
||
|
|
ASSERT_GT(buf0.size(), static_cast<std::size_t>(to - from + 1));
|
||
|
|
std::vector<std::uint64_t> w(buf0.size());
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
w[i] = (i * 3u) + 1u;
|
||
|
|
std::uint64_t e0 = 0, e1 = 0, opened = 0;
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
{
|
||
|
|
e0 += static_cast<std::uint64_t>(buf0[i].value) * w[i];
|
||
|
|
e1 += static_cast<std::uint64_t>(buf1[i].value) * w[i];
|
||
|
|
opened += static_cast<std::uint64_t>(open(buf0[i], buf1[i])) * w[i];
|
||
|
|
}
|
||
|
|
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||
|
|
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||
|
|
dpf::eval_prepare_interval(k0, from, to, m0);
|
||
|
|
dpf::eval_prepare_interval(k1, from, to, m1);
|
||
|
|
const auto ip0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||
|
|
const auto ip1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||
|
|
EXPECT_EQ(ip0, e0);
|
||
|
|
EXPECT_EQ(ip1, e1);
|
||
|
|
EXPECT_EQ(open(ip0, ip1), opened);
|
||
|
|
std::size_t hot = w.size();
|
||
|
|
for (std::size_t i = 0; i < w.size(); ++i)
|
||
|
|
{
|
||
|
|
if (open(buf0[i], buf1[i]) != 0)
|
||
|
|
{
|
||
|
|
EXPECT_EQ(hot, w.size());
|
||
|
|
hot = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ASSERT_LT(hot, w.size());
|
||
|
|
EXPECT_EQ(opened, std::uint64_t{6} * w[hot]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, FullDomainBatchedRejectsAShortVector)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{3}, std::uint64_t{1});
|
||
|
|
(void)k1;
|
||
|
|
std::vector<std::uint64_t> w(255, 1);
|
||
|
|
auto memo = dpf::make_basic_full_memoizer(k0);
|
||
|
|
EXPECT_THROW(dpf::eval_full_inner_product(k0, w, memo),
|
||
|
|
std::invalid_argument);
|
||
|
|
w.push_back(1);
|
||
|
|
EXPECT_EQ(open(
|
||
|
|
dpf::eval_full_inner_product(k0, w, memo),
|
||
|
|
dpf::eval_full_inner_product(k1, w,
|
||
|
|
dpf::make_basic_full_memoizer(k1))),
|
||
|
|
std::uint64_t{1} * w[3]);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(InnerProduct, TwoOutputRowsRejectAShortList)
|
||
|
|
{
|
||
|
|
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{2}, std::uint16_t{6});
|
||
|
|
(void)k1;
|
||
|
|
const std::vector<In> pts{1, 4, 8};
|
||
|
|
std::vector<std::array<std::uint32_t, 2>> rows{{1u, 1u}};
|
||
|
|
EXPECT_THROW((dpf::eval_sequence_inner_product<0, 1>(
|
||
|
|
k0, pts.begin(), pts.end(), rows)), std::invalid_argument);
|
||
|
|
rows.push_back({3u, 5u});
|
||
|
|
rows.push_back({7u, 9u});
|
||
|
|
EXPECT_NO_THROW((dpf::eval_sequence_inner_product<0, 1>(
|
||
|
|
k0, pts.begin(), pts.end(), rows)));
|
||
|
|
}
|