Checkpoint the party/runtime stack before share-program and malicious-mode work.

Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

View file

@ -0,0 +1,695 @@
#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)));
}