#include #include #include #include #include #include #include "dpf.hpp" namespace { using In = std::uint8_t; template auto open(const A & a, const B & b) { if constexpr (dpf::is_secret_share_v) return dpf::reconstruct(a, b); else if constexpr (dpf::utils::is_xor_wrapper_v) return static_cast(a ^ b); else return static_cast(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 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(from + i); const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)); expect += static_cast(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 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> 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(y0) * w0 + static_cast(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 pts{1, 4, 8}; std::vector> tuples{ {1u, 1u}, {3u, 5u}, {7u, 9u}}; std::vector> 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 pts{0x10, 0x2a, 0x2b, 0x30}; const std::vector> 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(y0) * rows[i][0] + static_cast(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(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 w; std::uint64_t expect = 0; auto push = [&](In x) { w.push_back(static_cast(w.size() + 1)); const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)); expect += static_cast(y) * w.back(); }; for (unsigned x = from; x < 256; ++x) push(static_cast(x)); for (unsigned x = 0; x <= to; ++x) push(static_cast(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; auto [k0, k1] = dpf::make_dpf(In{3}, X{0x0fu}); const std::vector pts{1, 3, 4}; const std::vector 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 pts; const std::vector 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 pts{3, 1}; const std::vector 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 pts{40, 41, 42, 50}; std::vector r(pts.size()); std::vector r2(pts.size()); std::vector 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(y); expect_dot += static_cast(y) * r[i]; expect_sq += static_cast(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 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 w; for (In x = from;; ++x) { w.push_back(static_cast(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 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 pts{1, 2}; const std::vector shorter{1}; const std::vector w{1, 1}; const auto recipe = dpf::make_sequence_recipe(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 unsorted{3, 1}; const std::vector 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 pts{1, 2}; const std::vector shorter{1}; const auto recipe = dpf::make_sequence_recipe(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 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 w; for (In x = from;; ++x) { w.push_back(static_cast(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 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 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(from + i); const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)); expect += static_cast(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 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 pts; const std::vector 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 ones(256, 1); std::vector r(256); for (unsigned i = 0; i < 256; ++i) r[i] = static_cast((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 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(to - from + 1) % 2u, 0u); std::vector 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(to - from + 1); std::vector ones(n, 1); std::vector 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(from + i); const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)); expect_sum += static_cast(y); expect_dot += static_cast(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 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 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 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 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 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 pts{1, 5, 8}; const std::vector one_row{1}; EXPECT_THROW(dpf::eval_sequence_inner_product<0>( k0, pts.begin(), pts.end(), one_row), std::invalid_argument); const std::vector 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 w; std::uint64_t expect = 0; for (unsigned x = from; x < 256; ++x) { w.push_back(static_cast(w.size() + 1)); const auto y = open(*dpf::eval_point(k0, static_cast(x)), *dpf::eval_point(k1, static_cast(x))); expect += static_cast(y) * w.back(); } for (unsigned x = 0; x <= to; ++x) { w.push_back(static_cast(w.size() + 1)); const auto y = open(*dpf::eval_point(k0, static_cast(x)), *dpf::eval_point(k1, static_cast(x))); expect += static_cast(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 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(to - from + 1)); std::vector 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(buf0[i].value) * w[i]; e1 += static_cast(buf1[i].value) * w[i]; opened += static_cast(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 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 pts{1, 4, 8}; std::vector> 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))); }