#include #include "dpf.hpp" #include "grotto/fixedpoint.hpp" #include #include #include #include #include #include #include namespace { simde__m128i g_roots[16]; int g_ri = 0; simde__m128i take_root() { return g_roots[g_ri++]; } std::vector g_tape(1 << 18); std::size_t g_ti = 0; void tape_fill(void * p, std::size_t n) { if (g_ti + n > g_tape.size()) std::abort(); std::memcpy(p, g_tape.data() + g_ti, n); g_ti += n; } struct PadA { uint64_t n = 1; simde__m128i block() { auto v = simde_mm_set_epi64x(static_cast(n), static_cast(n * 9 + 3)); n += 2; return v; } uint8_t bit() { return static_cast(n++ & 1u); } }; struct PadB { uint64_t n = 99; simde__m128i block() { auto v = simde_mm_set_epi64x(static_cast(n * 7), static_cast(n ^ 0x5a5a)); n += 3; return v; } uint8_t bit() { return static_cast((n++ >> 2) & 1u); } }; void reset_tape_roots() { g_ri = 0; g_ti = 0; } void seed_fixed_rng() { for (int i = 0; i < 16; ++i) g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xABCD0000u + i * 17); for (std::size_t i = 0; i < g_tape.size(); ++i) g_tape[i] = static_cast(i * 17 + 3); } bool same_bytes(const void * a, const void * b, std::size_t n) { return std::memcmp(a, b, n) == 0; } template constexpr bool is_xor_out_v = std::is_same_v || dpf::utils::is_xor_wrapper_v; template auto recon(const A & a, const B & b) { if constexpr (dpf::is_secret_share_v && dpf::is_secret_share_v) return dpf::reconstruct(a, b); else if constexpr (is_xor_out_v) return static_cast(a ^ b); else return static_cast(a - b); // subtractive leaf: party0 - party1 } template bool same_leaf_beaver(const Key & a, const Key & b) { if constexpr (I < Key::num_outputs) { const auto & la = a.template leaf(); const auto & lb = b.template leaf(); if (!same_bytes(&la, &lb, sizeof(la))) return false; const auto & ba = a.template beaver(); const auto & bb = b.template beaver(); using out_t = typename Key::template output_type_t; if constexpr (dpf::is_wildcard_v) { return same_bytes(&ba.output_blind, &bb.output_blind, sizeof(ba.output_blind)) && same_bytes(&ba.vector_blind, &bb.vector_blind, sizeof(ba.vector_blind)) && same_bytes(&ba.blinded_vector, &bb.blinded_vector, sizeof(ba.blinded_vector)) && same_leaf_beaver(a, b); } return same_bytes(&ba, &bb, sizeof(ba)) && same_leaf_beaver(a, b); } return true; } template bool same_incr_key(const Key & a, const Key & b) { return same_bytes(&a.root(), &b.root(), sizeof(a.root())) && same_bytes(a.correction_words().data(), b.correction_words().data(), sizeof(a.correction_words())) && same_bytes(a.correction_advice().data(), b.correction_advice().data(), sizeof(a.correction_advice())) && same_leaf_beaver(a, b); } template bool same_leaf_beaver_classic(const Key & a, const Key & b) { if constexpr (I < std::tuple_size_v) { const auto & la = a.template leaf(); const auto & lb = b.template leaf(); if (!same_bytes(&la, &lb, sizeof(la))) return false; const auto & ba = a.template beaver(); const auto & bb = b.template beaver(); using out_t = typename Key::template output_type_t; if constexpr (dpf::is_wildcard_v) { return same_bytes(&ba.output_blind, &bb.output_blind, sizeof(ba.output_blind)) && same_bytes(&ba.vector_blind, &bb.vector_blind, sizeof(ba.vector_blind)) && same_bytes(&ba.blinded_vector, &bb.blinded_vector, sizeof(ba.blinded_vector)) && same_leaf_beaver_classic(a, b); } return same_bytes(&ba, &bb, sizeof(ba)) && same_leaf_beaver_classic(a, b); } return true; } template bool same_classic_key(const Key & a, const Key & b) { if (!same_bytes(&a.root(), &b.root(), sizeof(a.root()))) return false; if (!same_bytes(a.correction_words().data(), b.correction_words().data(), sizeof(a.correction_words()))) return false; if (!same_bytes(a.correction_advice().data(), b.correction_advice().data(), sizeof(a.correction_advice()))) return false; return same_leaf_beaver_classic(a, b); } /// Flip the low bit of the N-bit MSB prefix (neighbor lane for packed leaves). template InputT flip_lane_lsb(InputT x, std::size_t prefix, std::size_t bitlen) { return static_cast(x ^ (InputT{1} << (bitlen - prefix))); } } // namespace class IncrementalDpfTest : public ::testing::Test { protected: void SetUp() override { seed_fixed_rng(); dpf::detail::uniform_bytes_hook = tape_fill; reset_tape_roots(); } void TearDown() override { dpf::detail::uniform_bytes_hook = nullptr; } }; TEST_F(IncrementalDpfTest, ClassicPathByteIdentical) { uint32_t x = 0x00abcdefu; uint32_t y = 0x55555555u; reset_tape_roots(); auto via_args = dpf::make_dpf(dpf::make_dpfargs(x, y), take_root); reset_tape_roots(); auto via_conv = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, y); using KT = std::decay_t; static_assert(std::is_same_v>); EXPECT_TRUE(same_classic_key(via_args.first, via_conv.first)); EXPECT_TRUE(same_classic_key(via_args.second, via_conv.second)); auto a0 = dpf::eval_point(via_conv.first, x); auto a1 = dpf::eval_point(via_conv.second, x); EXPECT_EQ(static_cast(recon(*a0, *a1)), y); } TEST_F(IncrementalDpfTest, At10BitDepthAndLanes) { uint32_t x = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one)); using KT = std::decay_t; EXPECT_EQ(KT::depth, 3u); // 10 - lg(128)=7 EXPECT_EQ(KT::meta[0].prefix, 10u); EXPECT_EQ(KT::meta[0].tree_level, 3u); EXPECT_EQ(KT::meta[0].pos_base, 0u); // only / deepest group auto y0 = dpf::eval_point(dpf::out<0, 10>, k0, x); auto y1 = dpf::eval_point(dpf::out<0, 10>, k1, x); EXPECT_TRUE(static_cast(recon(*y0, *y1))); uint32_t nb = flip_lane_lsb(x, 10, 32); auto z0 = dpf::eval_point(dpf::out<0, 10>, k0, nb); auto z1 = dpf::eval_point(dpf::out<0, 10>, k1, nb); EXPECT_FALSE(static_cast(recon(*z0, *z1))); } TEST_F(IncrementalDpfTest, SameWidthPackedInOneGroup) { uint32_t x = 0x12345678u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{3}, uint8_t{5}, uint8_t{7})); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 3u); EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id); EXPECT_EQ(KT::meta[1].group_id, KT::meta[2].group_id); EXPECT_EQ(KT::meta[0].index_in_group, 0u); EXPECT_EQ(KT::meta[1].index_in_group, 1u); EXPECT_EQ(KT::meta[2].index_in_group, 2u); // 12 - lg(16)=4 => level 8 EXPECT_EQ(KT::depth, 8u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)), uint8_t{3}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)), uint8_t{5}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)), uint8_t{7}); uint32_t nb = flip_lane_lsb(x, 12, 32); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)), uint8_t{0}); } TEST_F(IncrementalDpfTest, MixedWidthsSamePrefixSeparateGroups) { uint32_t x = 0x0f0f0f0fu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(dpf::bit::one, uint8_t{9}, uint16_t{0xabcd})); using KT = std::decay_t; // bit level 12-7=5, u8 level 12-4=8, u16 level 12-3=9 — wait, same prefix // different lg => different tree levels! EXPECT_EQ(KT::meta[0].tree_level, 5u); EXPECT_EQ(KT::meta[1].tree_level, 8u); EXPECT_EQ(KT::meta[2].tree_level, 9u); EXPECT_NE(KT::meta[0].group_id, KT::meta[1].group_id); EXPECT_NE(KT::meta[1].group_id, KT::meta[2].group_id); EXPECT_EQ(KT::depth, 9u); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)), uint8_t{9}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)), uint16_t{0xabcd}); } TEST_F(IncrementalDpfTest, ManyLevelsManyTypes) { uint32_t x = 0xa5a5a5a5u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{2}, uint8_t{4}), dpf::at<18>(uint16_t{1000}), dpf::at<22>(uint32_t{0x11111111u}), uint64_t{0x2222222233333333ull}, dpf::xor_wrapper{0xdeadbeefu}, dpf::bit{true}); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 8u); // deepest is full-domain u64/xor/bit at level 32-lg EXPECT_EQ(KT::deepest_prefix, 32u); EXPECT_EQ(KT::meta[0].tree_level, 3u); // at<10>(bit): 10-7 EXPECT_EQ(KT::meta[1].tree_level, 10u); // at<14>(u8): 14-4 EXPECT_EQ(KT::meta[3].tree_level, 15u); // at<18>(u16): 18-3 EXPECT_EQ(KT::meta[4].tree_level, 20u); // at<22>(u32): 22-2 EXPECT_EQ(KT::depth, 31u); // u64: 32-1 EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)), uint8_t{2}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)), uint8_t{4}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 18>, k0, x), *dpf::eval_point(dpf::out<3, 18>, k1, x)), uint16_t{1000}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 22>, k0, x), *dpf::eval_point(dpf::out<4, 22>, k1, x)), uint32_t{0x11111111u}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)), uint64_t{0x2222222233333333ull}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 32>, k0, x), *dpf::eval_point(dpf::out<6, 32>, k1, x)), dpf::xor_wrapper{0xdeadbeefu}); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x)))); // Leave the 10-bit MSB prefix entirely. uint32_t off_pref = x ^ (1u << 31); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_pref), *dpf::eval_point(dpf::out<0, 10>, k1, off_pref)))); // Neighbor lane within the same prefix node. uint32_t off_lane = flip_lane_lsb(x, 10, 32); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_lane), *dpf::eval_point(dpf::out<0, 10>, k1, off_lane)))); // Full-domain off-point. uint32_t off = x ^ 1u; EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, off), *dpf::eval_point(dpf::out<5, 32>, k1, off)), uint64_t{0}); // eval_point() defaults to deepest output (first at prefix 32 = slot 5) EXPECT_EQ(KT::deepest_output, 5u); EXPECT_EQ(recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)), uint64_t{0x2222222233333333ull}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6>, k0, x), *dpf::eval_point(dpf::out<6>, k1, x)), dpf::xor_wrapper{0xdeadbeefu}); } TEST_F(IncrementalDpfTest, ManySameLevelGroupsHighPosBase) { // Same tree level for several widths by choosing N = level + lg(opl). // level 10: bit needs N=17, u8 needs N=14, u16 needs N=13, u32 needs N=12 // Different levels. To share a level use matching N-lg. // Force many groups at the *deepest* level with distinct widths — pos starts 0. // For non-final: put many groups on a shallow shared level. // bit@10 (lvl3), and also use at<10> with only bits in multiple at<>? same group. // Use distinct prefixes that collide on level via different types: // at<10>(bit) lvl 3, at<7>(u8) lvl 3, at<6>(u16) lvl 3, at<5>(u32) lvl 3 uint32_t x = 0x7f3a9c1bu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), dpf::at<7>(uint8_t{11}), dpf::at<6>(uint16_t{22}), dpf::at<5>(uint32_t{33}), // another batch at level 8: u8@12, u16@11, u32@10 — wait u32@10 is lvl 8 dpf::at<12>(uint8_t{44}), dpf::at<11>(uint16_t{55}), dpf::at<10>(uint32_t{66}), // deepest full domain uint64_t{77}); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].tree_level, 3u); EXPECT_EQ(KT::meta[1].tree_level, 3u); EXPECT_EQ(KT::meta[2].tree_level, 3u); EXPECT_EQ(KT::meta[3].tree_level, 3u); // four groups at level 3: pos bases 2,3,4,5 (each 1 block) EXPECT_EQ(KT::meta[0].pos_base, 2u); EXPECT_EQ(KT::meta[1].pos_base, 3u); EXPECT_EQ(KT::meta[2].pos_base, 4u); EXPECT_EQ(KT::meta[3].pos_base, 5u); EXPECT_EQ(KT::meta[4].tree_level, 8u); EXPECT_EQ(KT::meta[5].tree_level, 8u); EXPECT_EQ(KT::meta[6].tree_level, 8u); EXPECT_EQ(KT::meta[4].pos_base, 2u); EXPECT_EQ(KT::meta[5].pos_base, 3u); EXPECT_EQ(KT::meta[6].pos_base, 4u); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 7>, k0, x), *dpf::eval_point(dpf::out<1, 7>, k1, x)), uint8_t{11}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 6>, k0, x), *dpf::eval_point(dpf::out<2, 6>, k1, x)), uint16_t{22}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 5>, k0, x), *dpf::eval_point(dpf::out<3, 5>, k1, x)), uint32_t{33}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 12>, k0, x), *dpf::eval_point(dpf::out<4, 12>, k1, x)), uint8_t{44}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 11>, k0, x), *dpf::eval_point(dpf::out<5, 11>, k1, x)), uint16_t{55}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 10>, k0, x), *dpf::eval_point(dpf::out<6, 10>, k1, x)), uint32_t{66}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x)), uint64_t{77}); } TEST_F(IncrementalDpfTest, NinePlusGroupsScales) { // 9 distinct (prefix,width) groups — previously the hard 8-group cap. uint32_t x = 0x10203040u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), dpf::at<11>(dpf::bit::one), dpf::at<12>(dpf::bit::one), dpf::at<13>(dpf::bit::one), dpf::at<14>(dpf::bit::one), dpf::at<15>(dpf::bit::one), dpf::at<16>(dpf::bit::one), dpf::at<17>(dpf::bit::one), dpf::at<18>(dpf::bit::one)); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 9u); std::size_t ng = 0; for (std::size_t i = 0; i < 9; ++i) ng = std::max(ng, KT::meta[i].group_id + 1); EXPECT_EQ(ng, 9u); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x)))); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<8, 18>, k0, x), *dpf::eval_point(dpf::out<8, 18>, k1, x)))); uint32_t off = x ^ (1u << 20); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<8, 18>, k0, off), *dpf::eval_point(dpf::out<8, 18>, k1, off)))); } TEST_F(IncrementalDpfTest, DealerMatchesDoernerShelat) { uint32_t x = 0x00abcdefu; uint32_t x0 = 0x12345678u; uint32_t x1 = x ^ x0; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{3}, uint8_t{5}), uint32_t{9}, dpf::xor_wrapper{0xcafe}); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{3}, uint8_t{5}), uint32_t{9}, dpf::xor_wrapper{0xcafe}); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rngb{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds2 = dpf::make_dpf_doerner_shelat(x0, x1, rngb, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{3}, uint8_t{5}), uint32_t{9}, dpf::xor_wrapper{0xcafe}); EXPECT_TRUE(same_incr_key(ds.first, ds2.first)); EXPECT_TRUE(same_incr_key(ds.second, ds2.second)); } TEST_F(IncrementalDpfTest, IntermediateWildcardSameKey) { uint32_t x = 0x55aa55aau; uint32_t x0 = 0x0f0f0f0fu; uint32_t x1 = x ^ x0; dpf::wildcard_value wc; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<10>(dpf::bit::one), dpf::at<16>(wc), uint64_t{42}); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::at<16>(wc), uint64_t{42}); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); // concrete outputs still reconstruct EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, x), *dpf::eval_point(dpf::out<0, 10>, dealer.second, x)))); EXPECT_EQ( recon(*dpf::eval_point(dpf::out<2, 32>, dealer.first, x), *dpf::eval_point(dpf::out<2, 32>, dealer.second, x)), uint64_t{42}); } TEST_F(IncrementalDpfTest, EvalSweepAroundPoint) { uint16_t x = 0x1234; auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(dpf::bit::one), uint32_t{0xabcdef01u}); using path_t = dpf::nonmemoizing_path_memoizer>; path_t p0{}, p1{}; auto prefix8 = [](uint16_t q) { return static_cast(q >> (16 - 8)); }; const auto xp = prefix8(x); for (int d = -64; d <= 64; ++d) { uint16_t q = static_cast(x + d); auto b0 = dpf::eval_point(dpf::out<0, 8>, k0, q, p0); auto b1 = dpf::eval_point(dpf::out<0, 8>, k1, q, p1); auto u0 = dpf::eval_point(dpf::out<1, 16>, k0, q, p0); auto u1 = dpf::eval_point(dpf::out<1, 16>, k1, q, p1); EXPECT_EQ(static_cast(recon(*b0, *b1)), prefix8(q) == xp) << "q=" << q; uint32_t expect = (q == x) ? 0xabcdef01u : 0u; EXPECT_EQ(recon(*u0, *u1), expect) << "q=" << q; } // Explicitly leave the 8-bit MSB bucket. uint16_t other = static_cast(x ^ 0x8000); EXPECT_NE(prefix8(other), xp); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<0, 8>, k0, other), *dpf::eval_point(dpf::out<0, 8>, k1, other)))); } TEST_F(IncrementalDpfTest, SignedInput) { int32_t x = -1000; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint16_t{7}), int32_t{42}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)), uint16_t{7}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x), *dpf::eval_point(dpf::out<1, 32>, k1, x)), int32_t{42}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x + 1), *dpf::eval_point(dpf::out<1, 32>, k1, x + 1)), int32_t{0}); } TEST_F(IncrementalDpfTest, FullDomainAtEqualsBare) { uint32_t x = 0x9999u; uint32_t y = 12345u; reset_tape_roots(); auto bare = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, y); reset_tape_roots(); // at<32> same width alone should still be classic path auto placed = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<32>(y)); using BareT = std::decay_t; using PlacedT = std::decay_t; static_assert(std::is_same_v); EXPECT_TRUE(same_classic_key(bare.first, placed.first)); EXPECT_TRUE(same_classic_key(bare.second, placed.second)); } TEST_F(IncrementalDpfTest, DepthZeroLeaf) { // at<7>(bit): tree level 0, no correction words. uint32_t x = 0x00ffffffu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<7>(dpf::bit::one)); using KT = std::decay_t; EXPECT_EQ(KT::depth, 0u); EXPECT_EQ(KT::meta[0].pos_base, 0u); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 7>, k0, x), *dpf::eval_point(dpf::out<0, 7>, k1, x)))); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<0, 7>, k0, flip_lane_lsb(x, 7, 32)), *dpf::eval_point(dpf::out<0, 7>, k1, flip_lane_lsb(x, 7, 32))))); } TEST_F(IncrementalDpfTest, MemoizedPathEval) { uint32_t x = 0xabcdef01u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), uint32_t{1234}); using KT = std::decay_t; dpf::basic_path_memoizer p0{}, p1{}; for (uint32_t q : {x, x ^ 1u, x ^ 0x80000000u, 0u}) { auto e0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q, p0), *dpf::eval_point(dpf::out<0, 10>, k1, q, p1)); auto e1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q, p0), *dpf::eval_point(dpf::out<1, 32>, k1, q, p1)); auto r0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q), *dpf::eval_point(dpf::out<0, 10>, k1, q)); auto r1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q), *dpf::eval_point(dpf::out<1, 32>, k1, q)); EXPECT_EQ(static_cast(e0), static_cast(r0)); EXPECT_EQ(e1, r1); } } TEST_F(IncrementalDpfTest, DsEvalAgreesWithDealer) { uint32_t x = 0x31415926u; uint32_t x0 = 0x27182818u; uint32_t x1 = x ^ x0; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<10>(dpf::bit::one), uint32_t{99}); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), uint32_t{99}); uint32_t pts[] = {x, x ^ 1u, 0u, 0xffffffffu, 0x80000000u}; for (uint32_t q : pts) { EXPECT_EQ(static_cast(recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, q), *dpf::eval_point(dpf::out<0, 10>, dealer.second, q))), static_cast(recon(*dpf::eval_point(dpf::out<0, 10>, ds.first, q), *dpf::eval_point(dpf::out<0, 10>, ds.second, q)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, dealer.first, q), *dpf::eval_point(dpf::out<1, 32>, dealer.second, q)), recon(*dpf::eval_point(dpf::out<1, 32>, ds.first, q), *dpf::eval_point(dpf::out<1, 32>, ds.second, q))); } } TEST_F(IncrementalDpfTest, SixteenUint8PackedOneLevel) { // u8 has opl=16: fill an entire packed node at at<12> (level 8). uint32_t x = 0x4c1d2e3fu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>( uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}, uint8_t{5}, uint8_t{6}, uint8_t{7}, uint8_t{8}, uint8_t{9}, uint8_t{10}, uint8_t{11}, uint8_t{12}, uint8_t{13}, uint8_t{14}, uint8_t{15}, uint8_t{16})); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 16u); EXPECT_EQ(KT::meta[0].group_id, KT::meta[15].group_id); EXPECT_EQ(KT::meta[15].index_in_group, 15u); EXPECT_EQ(KT::depth, 8u); EXPECT_EQ(KT::meta[0].pos_base, 0u); auto check = [&](auto idx, uint8_t expect) { constexpr std::size_t I = decltype(idx)::value; EXPECT_EQ(recon(*dpf::eval_point(dpf::out, k0, x), *dpf::eval_point(dpf::out, k1, x)), expect); }; check(std::integral_constant{}, uint8_t{1}); check(std::integral_constant{}, uint8_t{2}); check(std::integral_constant{}, uint8_t{8}); check(std::integral_constant{}, uint8_t{16}); uint32_t nb = flip_lane_lsb(x, 12, 32); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)), uint8_t{0}); uint32_t off_pref = x ^ (1u << 31); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 12>, k0, off_pref), *dpf::eval_point(dpf::out<15, 12>, k1, off_pref)), uint8_t{0}); } TEST_F(IncrementalDpfTest, EightUint16AndFourUint32Packed) { uint32_t x = 0x11121314u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<15>( uint16_t{100}, uint16_t{200}, uint16_t{300}, uint16_t{400}, uint16_t{500}, uint16_t{600}, uint16_t{700}, uint16_t{800}), dpf::at<20>( uint32_t{1000}, uint32_t{2000}, uint32_t{3000}, uint32_t{4000})); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 12u); // u16@15 => level 12; u32@20 => level 18 EXPECT_EQ(KT::meta[0].tree_level, 12u); EXPECT_EQ(KT::meta[8].tree_level, 18u); EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id); EXPECT_EQ(KT::meta[8].group_id, KT::meta[11].group_id); EXPECT_NE(KT::meta[0].group_id, KT::meta[8].group_id); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 15>, k0, x), *dpf::eval_point(dpf::out<0, 15>, k1, x)), uint16_t{100}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 15>, k0, x), *dpf::eval_point(dpf::out<7, 15>, k1, x)), uint16_t{800}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 20>, k0, x), *dpf::eval_point(dpf::out<8, 20>, k1, x)), uint32_t{1000}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<11, 20>, k0, x), *dpf::eval_point(dpf::out<11, 20>, k1, x)), uint32_t{4000}); } TEST_F(IncrementalDpfTest, ManyBitsOnePrefix) { uint32_t x = 0x00c0ffeeu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>( dpf::bit::one, dpf::bit::one, dpf::bit{false}, dpf::bit::one, dpf::bit{false}, dpf::bit::one, dpf::bit::one, dpf::bit{false})); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id); EXPECT_EQ(KT::depth, 3u); bool expect[] = {true, true, false, true, false, true, true, false}; auto check_bit = [&](auto idx, bool e) { constexpr std::size_t I = decltype(idx)::value; EXPECT_EQ(static_cast( recon(*dpf::eval_point(dpf::out, k0, x), *dpf::eval_point(dpf::out, k1, x))), e); }; check_bit(std::integral_constant{}, expect[0]); check_bit(std::integral_constant{}, expect[2]); check_bit(std::integral_constant{}, expect[7]); } TEST_F(IncrementalDpfTest, FixedpointIntermediateAndFullDomain) { using fp16 = grotto::fixedpoint<16>; using fp8 = grotto::fixedpoint<8, int32_t>; uint32_t x = 0x2a2b2c2du; fp16 a = fp16::from_raw(0x00010000); // 1.0 fp16 b = fp16::from_raw(0x00008000); // 0.5 fp8 c = fp8::from_raw(0x00000100); // 1.0 in Q8.8? frac=8 on int32 auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(a, b), dpf::at<18>(c), fp16::from_raw(0x00020000)); // 2.0 full-domain using KT = std::decay_t; // fp16 is 64-bit packable: lg=1 => at<12> level 11; fp8 32-bit lg=2 => at<18> level 16 EXPECT_EQ(KT::meta[0].tree_level, 11u); EXPECT_EQ(KT::meta[1].tree_level, 11u); EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id); EXPECT_EQ(KT::meta[2].tree_level, 16u); EXPECT_EQ(KT::depth, 31u); // full-domain fp16: 32-1 EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)), a); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)), b); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 18>, k0, x), *dpf::eval_point(dpf::out<2, 18>, k1, x)), c); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x), *dpf::eval_point(dpf::out<3, 32>, k1, x)), fp16::from_raw(0x00020000)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, flip_lane_lsb(x, 12, 32)), *dpf::eval_point(dpf::out<0, 12>, k1, flip_lane_lsb(x, 12, 32))), fp16{}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x ^ 1u), *dpf::eval_point(dpf::out<3, 32>, k1, x ^ 1u)), fp16{}); } TEST_F(IncrementalDpfTest, FixedpointMixedWithIntegralAndBit) { using fp16 = grotto::fixedpoint<16>; uint32_t x = 0x55aa00ffu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), dpf::at<14>(fp16::from_raw(0x00004000), uint8_t{9}), dpf::at<20>(uint32_t{42}, fp16::from_raw(0xffff0000)), int64_t{-7}); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)), fp16::from_raw(0x00004000)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)), uint8_t{9}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 20>, k0, x), *dpf::eval_point(dpf::out<3, 20>, k1, x)), uint32_t{42}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 20>, k0, x), *dpf::eval_point(dpf::out<4, 20>, k1, x)), fp16::from_raw(0xffff0000)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)), int64_t{-7}); } TEST_F(IncrementalDpfTest, FixedpointDealerMatchesDoernerShelat) { using fp16 = grotto::fixedpoint<16>; using fp8 = grotto::fixedpoint<8, int32_t>; uint32_t x = 0x0abcdef0u; uint32_t x0 = 0x11111111u; uint32_t x1 = x ^ x0; fp16 y = fp16::from_raw(0x00018000); fp8 z = fp8::from_raw(0x00000200); dpf::wildcard_value wc; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<12>(y, z), dpf::at<16>(wc), fp16::from_raw(0x00030000)); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<12>(y, z), dpf::at<16>(wc), fp16::from_raw(0x00030000)); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, dealer.first, x), *dpf::eval_point(dpf::out<0, 12>, dealer.second, x)), y); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, dealer.first, x), *dpf::eval_point(dpf::out<1, 12>, dealer.second, x)), z); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, dealer.first, x), *dpf::eval_point(dpf::out<3, 32>, dealer.second, x)), fp16::from_raw(0x00030000)); } TEST_F(IncrementalDpfTest, DenseSameLevelManyGroups) { // 12 groups on tree level 5 via matching N - lg(opl): // bit@12, u8@9, u16@8, u32@7, u64@6, xu32@7 — only a few unique. // Use distinct bit prefixes all at level 5: N = 5+7 = 12 for bits only, // and pad with other widths at level 5. // bit N=12, u8 N=9, u16 N=8, u32 N=7, fp8(i32) N=7, xor u32 N=7 — still few. // Stack many *bit* prefixes that share level by using N=12 only once... // Instead: many groups of different widths all non-final at level 10, // each with several packed leaves to push pos_base up. uint32_t x = 0x6f5e4d3cu; using fp8 = grotto::fixedpoint<8, int32_t>; auto [k0, k1] = dpf::make_dpf(x, // level 3: four bit prefixes (10,11,12,13) — wait different levels // Force level 10 for: u8@14, u16@13, u32@12, u64@11, fp8@12, xu32@12 dpf::at<14>(uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}), dpf::at<13>(uint16_t{10}, uint16_t{20}, uint16_t{30}, uint16_t{40}), dpf::at<12>(uint32_t{100}, uint32_t{200}), dpf::at<11>(uint64_t{1000}, uint64_t{2000}), dpf::at<12>(fp8::from_raw(0x10), fp8::from_raw(0x20)), dpf::at<12>(dpf::xor_wrapper{0xaa}, dpf::xor_wrapper{0xbb}), // deepest uint32_t{999}); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].tree_level, 10u); // 14-4 EXPECT_EQ(KT::meta[4].tree_level, 10u); // 13-3 EXPECT_EQ(KT::meta[8].tree_level, 10u); // 12-2 EXPECT_EQ(KT::meta[10].tree_level, 10u); // 11-1 // groups at level 10 take consecutive pos bases starting at 2 EXPECT_EQ(KT::meta[0].pos_base, 2u); EXPECT_GE(KT::meta[10].pos_base, 2u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)), uint8_t{1}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 14>, k0, x), *dpf::eval_point(dpf::out<3, 14>, k1, x)), uint8_t{4}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 13>, k0, x), *dpf::eval_point(dpf::out<4, 13>, k1, x)), uint16_t{10}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 13>, k0, x), *dpf::eval_point(dpf::out<7, 13>, k1, x)), uint16_t{40}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 12>, k0, x), *dpf::eval_point(dpf::out<8, 12>, k1, x)), uint32_t{100}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<10, 11>, k0, x), *dpf::eval_point(dpf::out<10, 11>, k1, x)), uint64_t{1000}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 12>, k0, x), *dpf::eval_point(dpf::out<12, 12>, k1, x)), fp8::from_raw(0x10)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<14, 12>, k0, x), *dpf::eval_point(dpf::out<14, 12>, k1, x)), dpf::xor_wrapper{0xaa}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 32>, k0, x), *dpf::eval_point(dpf::out<16, 32>, k1, x)), uint32_t{999}); } TEST_F(IncrementalDpfTest, TwelveBitPrefixesPlusFullDomain) { // 12 distinct bit prefixes (12 groups) plus a full-domain payload. uint32_t x = 0x13579bdFu; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), dpf::at<11>(dpf::bit::one), dpf::at<12>(dpf::bit::one), dpf::at<13>(dpf::bit::one), dpf::at<14>(dpf::bit::one), dpf::at<15>(dpf::bit::one), dpf::at<16>(dpf::bit::one), dpf::at<17>(dpf::bit::one), dpf::at<18>(dpf::bit::one), dpf::at<19>(dpf::bit::one), dpf::at<20>(dpf::bit::one), dpf::at<21>(dpf::bit::one), uint64_t{0x1122334455667788ull}); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 13u); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x)))); EXPECT_TRUE(static_cast( recon(*dpf::eval_point(dpf::out<11, 21>, k0, x), *dpf::eval_point(dpf::out<11, 21>, k1, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 32>, k0, x), *dpf::eval_point(dpf::out<12, 32>, k1, x)), uint64_t{0x1122334455667788ull}); // Off-prefix for the deepest bit leaf. uint32_t off = x ^ (1u << (32 - 21)); EXPECT_FALSE(static_cast( recon(*dpf::eval_point(dpf::out<11, 21>, k0, off), *dpf::eval_point(dpf::out<11, 21>, k1, off)))); } TEST_F(IncrementalDpfTest, TwoFullPackedNodesSameLevel) { // Two separate at<14>(16 x u8) would be same prefix+width => one group. // Use at<14> and at<15> both u8: levels 10 and 11. // Same level two full nodes: at<14>(16 u8) is one group of 16 (= 16 blocks // if each u8 is 1 block — yes blen=1, so 16 blocks in one make_leaves). // Add at<13>(8 u16) at level 10 as second group on same level. uint32_t x = 0x01020304u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<14>( uint8_t{0}, uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}, uint8_t{5}, uint8_t{6}, uint8_t{7}, uint8_t{8}, uint8_t{9}, uint8_t{10}, uint8_t{11}, uint8_t{12}, uint8_t{13}, uint8_t{14}, uint8_t{15}), dpf::at<13>( uint16_t{0x10}, uint16_t{0x20}, uint16_t{0x30}, uint16_t{0x40}, uint16_t{0x50}, uint16_t{0x60}, uint16_t{0x70}, uint16_t{0x80})); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].tree_level, 10u); EXPECT_EQ(KT::meta[16].tree_level, 10u); EXPECT_EQ(KT::meta[0].pos_base, 0u); // deepest (only) level EXPECT_EQ(KT::meta[16].pos_base, 16u); // after 16 u8 blocks EXPECT_EQ(KT::num_outputs, 24u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)), uint8_t{0}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 14>, k0, x), *dpf::eval_point(dpf::out<15, 14>, k1, x)), uint8_t{15}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 13>, k0, x), *dpf::eval_point(dpf::out<16, 13>, k1, x)), uint16_t{0x10}); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<23, 13>, k0, x), *dpf::eval_point(dpf::out<23, 13>, k1, x)), uint16_t{0x80}); } TEST_F(IncrementalDpfTest, IntervalAtMatchesPointEval) { // at<12>(uint8) => lg_opl=4, tree_level=8, lane domain 2^12. uint32_t x = 0x00a5b6c7u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42})); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].tree_level, 8u); const uint32_t lane = x >> (32 - 12); const uint32_t from = (lane & ~0xffu); const uint32_t to = from + 255u; auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to); auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to); (void)it0; (void)it1; auto y0 = dpf::eval_point(dpf::out<0, 12>, k0, x); auto y1 = dpf::eval_point(dpf::out<0, 12>, k1, x); const auto expect = recon(*y0, *y1); const std::size_t opl = KT::template outputs_per_leaf_of<0>; const std::size_t idx = static_cast(lane - from); const std::size_t leaf = idx / opl; const std::size_t off = idx % opl; EXPECT_EQ(recon(buf0[leaf * opl + off], buf1[leaf * opl + off]), expect); const uint32_t other_lane = (lane ^ 1u); if (other_lane >= from && other_lane <= to) { const std::size_t oidx = static_cast(other_lane - from); EXPECT_EQ(recon(buf0[(oidx / opl) * opl + (oidx % opl)], buf1[(oidx / opl) * opl + (oidx % opl)]), uint8_t{0}); } } TEST_F(IncrementalDpfTest, FullAtDeepestAndSequenceAt) { uint32_t x = 0x11121314u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{7}), uint32_t{99}); using KT = std::decay_t; constexpr auto I = KT::deepest_output; constexpr auto N = KT::meta[I].prefix; EXPECT_EQ(N, 32u); std::array pts{{x, x ^ 1u, x ^ 0x100u}}; auto buf0 = dpf::make_output_buffer_for(k0, pts.size()); auto buf1 = dpf::make_output_buffer_for(k1, pts.size()); dpf::eval_sequence(dpf::out, k0, pts.begin(), pts.end(), buf0); dpf::eval_sequence(dpf::out, k1, pts.begin(), pts.end(), buf1); constexpr auto opl = KT::template outputs_per_leaf_of; auto e0 = dpf::eval_point(dpf::out, k0, pts[0]); auto e1 = dpf::eval_point(dpf::out, k1, pts[0]); EXPECT_EQ(recon(buf0[e0.offset], buf1[e1.offset]), recon(*e0, *e1)); auto f0 = dpf::eval_point(dpf::out, k0, pts[1]); auto f1 = dpf::eval_point(dpf::out, k1, pts[1]); EXPECT_EQ(recon(buf0[opl + f0.offset], buf1[opl + f1.offset]), recon(*f0, *f1)); auto buf0b = dpf::make_output_buffer_for(k0, pts.size()); dpf::eval_sequence(k0, pts.begin(), pts.end(), buf0b); EXPECT_EQ(buf0b[e0.offset], buf0[e0.offset]); } TEST_F(IncrementalDpfTest, LocalCwProtocolMatchesDsRandomness) { uint32_t x = 0xabcdef01u; uint32_t x0 = 0x11111111u; uint32_t x1 = x ^ x0; reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto via_rng = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), uint32_t{5}); reset_tape_roots(); PadA pads{}; dpf::local_cw_protocol proto{pads}; auto via_proto = dpf::make_dpf_doerner_shelat(x0, x1, take_root, proto, dpf::at<10>(dpf::bit::one), uint32_t{5}); EXPECT_EQ(static_cast(recon(*dpf::eval_point(dpf::out<0, 10>, via_rng.first, x), *dpf::eval_point(dpf::out<0, 10>, via_rng.second, x))), static_cast(recon(*dpf::eval_point(dpf::out<0, 10>, via_proto.first, x), *dpf::eval_point(dpf::out<0, 10>, via_proto.second, x)))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, via_rng.first, x), *dpf::eval_point(dpf::out<1, 32>, via_rng.second, x)), recon(*dpf::eval_point(dpf::out<1, 32>, via_proto.first, x), *dpf::eval_point(dpf::out<1, 32>, via_proto.second, x))); } TEST_F(IncrementalDpfTest, DpfAndCmpSameKey) { const uint32_t alpha = 0x00abcdefu; const uint64_t yt = 42u; auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(yt)); EXPECT_TRUE(k0.has_cmp()); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)), 7u); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u), *dpf::eval_point(k1, alpha ^ 1u)), 0u); const uint64_t mask = k0.cmp().mask; auto recon_cmp = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask; }; EXPECT_EQ(recon_cmp(alpha - 1u), yt); EXPECT_EQ(recon_cmp(0u), yt); EXPECT_EQ(recon_cmp(alpha), 0u); EXPECT_EQ(recon_cmp(alpha + 1u), 0u); auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt)); auto recon_ge = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask; }; EXPECT_EQ(recon_ge(alpha - 1u), 0u); EXPECT_EQ(recon_ge(alpha), yt); EXPECT_EQ(recon_ge(alpha + 1u), yt); } TEST_F(IncrementalDpfTest, CmpPairAndRelations) { const uint32_t alpha = 100u; const uint64_t yt = 5u, yf = 9u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf)); const uint64_t mask = k0.cmp().mask; auto r = [&](uint32_t q) { return dpf::reconstruct( dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask; }; EXPECT_EQ(r(50u), yt); EXPECT_EQ(r(100u), yf); EXPECT_EQ(r(200u), yf); auto [a0, a1] = dpf::make_dpf(alpha, dpf::leq(yt, yf)); auto rq = [&](auto &x0, auto &x1, uint32_t q) { return dpf::reconstruct( dpf::eval_point(dpf::cmp, x0, q), dpf::eval_point(dpf::cmp, x1, q)) & mask; }; EXPECT_EQ(rq(a0, a1, 100u), yt); EXPECT_EQ(rq(a0, a1, 101u), yf); auto [b0, b1] = dpf::make_dpf(alpha, dpf::gt(yt, yf)); EXPECT_EQ(rq(b0, b1, 100u), yf); EXPECT_EQ(rq(b0, b1, 101u), yt); auto [c0, c1] = dpf::make_dpf(alpha, dpf::geq(yt, yf)); EXPECT_EQ(rq(c0, c1, 99u), yf); EXPECT_EQ(rq(c0, c1, 100u), yt); } TEST_F(IncrementalDpfTest, EqSynonymAndEqAt) { const uint32_t alpha = 0x12345678u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7})); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)), 7u); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u), *dpf::eval_point(k1, alpha ^ 1u)), 0u); auto [p0, p1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7}, uint32_t{3})); EXPECT_EQ(recon(*dpf::eval_point(p0, alpha), *dpf::eval_point(p1, alpha)), 7u); EXPECT_EQ(recon(*dpf::eval_point(p0, alpha ^ 1u), *dpf::eval_point(p1, alpha ^ 1u)), 3u); auto [q0, q1] = dpf::make_dpf(alpha, dpf::eq_at<16>(uint16_t{9}), dpf::lt_at<8>(uint64_t{5})); EXPECT_TRUE(q0.has_cmp()); EXPECT_EQ(q0.cmp().nbits, 8); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, q0, alpha), *dpf::eval_point(dpf::out<0, 16>, q1, alpha)), 9u); const uint64_t m8 = q0.cmp().mask; auto top8 = [](uint32_t v) { return v >> 24; }; auto recon8 = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, q0, q), dpf::eval_point(dpf::cmp, q1, q)) & m8; }; EXPECT_EQ(recon8(alpha), 0u); EXPECT_EQ(recon8((top8(alpha) - 1u) << 24), 5u); EXPECT_EQ(recon8((top8(alpha) + 1u) << 24), 0u); } TEST_F(IncrementalDpfTest, CmpWithDoernerShelat) { const uint32_t alpha = 0x01020304u; const uint32_t x0 = 0x11111111u; const uint32_t x1 = alpha ^ x0; auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, uint32_t{3}, dpf::lt(uint64_t{11})); EXPECT_TRUE(k0.has_cmp()); const uint64_t mask = k0.cmp().mask; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1), dpf::eval_point(dpf::cmp, k1, alpha - 1)) & mask, 11u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)), 3u); } TEST_F(IncrementalDpfTest, CmpDealerMatchesDoernerShelat) { uint32_t x = 0x00abcdefu; uint32_t x0 = 0x12345678u; uint32_t x1 = x ^ x0; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::at<10>(dpf::bit::one), dpf::lt(uint64_t{42})); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::lt(uint64_t{42})); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); // Comparison channel must match (same tree value CWs; δ not clear on keys). EXPECT_EQ(dealer.first.cmp().nbits, ds.first.cmp().nbits); EXPECT_EQ(dealer.first.cmp().kind, ds.first.cmp().kind); EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last()); EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(), ds.first.value_cw().data(), dealer.first.value_cw().size() * sizeof(uint64_t))); EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend()); EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend()); // Shares reconstruct if_false (=0 here). EXPECT_EQ(dpf::reconstruct(dealer.first.cmp_addend(), dealer.second.cmp_addend()) & dealer.first.cmp().mask, 0u); const uint64_t mask = dealer.first.cmp().mask; for (uint32_t q : {x - 1u, x, x + 1u, 0u}) { EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q)) & mask, dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask); } } TEST_F(IncrementalDpfTest, CmpIntervalAndSequenceBuffers) { const uint16_t alpha = 0x00aau; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt_at<8>(uint64_t{5}, uint64_t{1})); EXPECT_TRUE(k0.has_cmp()); const uint64_t mask = k0.cmp().mask; auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, uint8_t{0x00}, uint8_t{0xff}); auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, uint8_t{0x00}, uint8_t{0xff}); dpf::eval_interval(dpf::cmp, k0, uint8_t{0x00}, uint8_t{0xff}, buf0); dpf::eval_interval(dpf::cmp, k1, uint8_t{0x00}, uint8_t{0xff}, buf1); // alpha top-8 is 0x00; wait alpha=0x00aa so top 8 of 16-bit is 0x00. // lt_at<8>: compare on top 8 bits of 16-bit domain = 0x00. // Lane 0x00 == alpha prefix => not < => if_false=1 // Lane < 0x00: none for uint8 EXPECT_EQ(recon(buf0[0], buf1[0]) & mask, 1u); EXPECT_EQ(recon(buf0[1], buf1[1]) & mask, 1u); // 0x01 > 0x00 // Use a mid alpha so both sides of the cut appear. const uint16_t a2 = 0x8000u; auto [p0, p1] = dpf::make_dpf(a2, dpf::lt_at<8>(uint64_t{5}, uint64_t{1})); auto b0 = dpf::eval_interval(dpf::cmp, p0, uint8_t{0x7f}, uint8_t{0x81}); auto b1 = dpf::eval_interval(dpf::cmp, p1, uint8_t{0x7f}, uint8_t{0x81}); // top8(a2)=0x80; lanes 0x7f,0x80,0x81 -> 5, 1, 1 EXPECT_EQ(recon(b0[0], b1[0]) & mask, 5u); EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u); EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u); std::array pts{{static_cast(a2 - 1), a2, static_cast(a2 + 1)}}; auto s0 = dpf::make_output_buffer(dpf::cmp, p0, pts.size()); auto s1 = dpf::make_output_buffer(dpf::cmp, p1, pts.size()); dpf::eval_sequence(dpf::cmp, p0, pts.begin(), pts.end(), s0); dpf::eval_sequence(dpf::cmp, p1, pts.begin(), pts.end(), s1); EXPECT_EQ(recon(s0[0], s1[0]) & mask, 5u); EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u); EXPECT_EQ(recon(s0[2], s1[2]) & mask, 1u); } TEST_F(IncrementalDpfTest, CmpSharesPathMemoizerWithEvalAt) { const uint32_t alpha = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(uint64_t{3})); using key_t = std::decay_t; auto path0 = dpf::make_basic_path_memoizer(k0); auto path1 = dpf::make_basic_path_memoizer(k1); EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0), *dpf::eval_point(k1, alpha, path1)), 7u); const uint64_t mask = k0.cmp().mask; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u, path0), dpf::eval_point(dpf::cmp, k1, alpha - 1u, path1)) & mask, 3u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0), dpf::eval_point(dpf::cmp, k1, alpha, path1)) & mask, 0u); // Re-eval point with the same memoizers (resume / reuse). EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0), *dpf::eval_point(k1, alpha, path1)), 7u); } TEST_F(IncrementalDpfTest, CmpOnlyZeroOutputs) { const uint32_t alpha = 0x42u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{9})); using KT = std::decay_t; EXPECT_EQ(KT::num_outputs, 0u); EXPECT_TRUE(k0.has_cmp()); EXPECT_EQ(k0.cmp().nbits, 32); const uint64_t mask = k0.cmp().mask; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u)) & mask, 9u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u); } TEST_F(IncrementalDpfTest, CmpDomainEdgeTrivial) { // leq at α = 2^n-1 is always-true; gt there is always-false. const uint8_t alpha = 0xffu; auto [l0, l1] = dpf::make_dpf(alpha, dpf::leq(uint64_t{7}, uint64_t{1})); EXPECT_EQ(l0.cmp().trivial, dpf::cmp_trivial::always_true); const uint64_t mask = l0.cmp().mask; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, uint8_t{0}), dpf::eval_point(dpf::cmp, l1, uint8_t{0})) & mask, 7u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, alpha), dpf::eval_point(dpf::cmp, l1, alpha)) & mask, 7u); // if_true = 7 is split across addends, not stored clear. EXPECT_EQ(dpf::reconstruct(l0.cmp_addend(), l1.cmp_addend()) & mask, 7u); auto [g0, g1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{7}, uint64_t{1})); EXPECT_EQ(g0.cmp().trivial, dpf::cmp_trivial::always_false); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, uint8_t{0}), dpf::eval_point(dpf::cmp, g1, uint8_t{0})) & mask, 1u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, 1u); EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, 1u); } TEST_F(IncrementalDpfTest, CmpValueCwGroupWidth) { // uint64 payload → 8-byte value_cw words (the classic full width). const uint32_t alpha = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{42})); using K64 = std::decay_t; EXPECT_EQ(sizeof(typename K64::value_cw_word), 8u); EXPECT_EQ(K64::cmp_out_bits, 64u); // uint16 payload → 2-byte value_cw words (no padded uint64 on the wire). auto [u0, u1] = dpf::make_dpf(alpha, dpf::lt(uint16_t{42})); using K16 = std::decay_t; EXPECT_EQ(sizeof(typename K16::value_cw_word), 2u); EXPECT_EQ(K16::cmp_out_bits, 16u); // bit payload → 1-byte value_cw words (no 8-byte-per-level waste). auto [b0, b1] = dpf::make_dpf(alpha, dpf::lt(dpf::bit::one)); using KB = std::decay_t; EXPECT_EQ(sizeof(typename KB::value_cw_word), 1u); EXPECT_EQ(KB::cmp_out_bits, 1u); // The narrow-width value CWs still reconstruct to the same (public) bytes. EXPECT_TRUE(same_bytes(b0.value_cw().data(), b1.value_cw().data(), b0.value_cw().size() * sizeof(typename KB::value_cw_word))); // uint16 comparison reconstructs correctly through the narrow words. const uint64_t m16 = u0.cmp().mask; auto r16 = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, u0, q), dpf::eval_point(dpf::cmp, u1, q)) & m16; }; EXPECT_EQ(r16(alpha - 1u), 42u); EXPECT_EQ(r16(alpha), 0u); EXPECT_EQ(r16(alpha + 1u), 0u); } TEST_F(IncrementalDpfTest, CmpValueCwGroupWidthDsParity) { // Narrow-width value CWs must stay byte-identical dealer↔Doerner–Shelat. uint32_t x = 0x00abcdefu; uint32_t x0 = 0x12345678u; uint32_t x1 = x ^ x0; reset_tape_roots(); auto dealer = dpf::make_dpf(x, dpf::root_sampler_t{take_root}, dpf::lt(uint16_t{42})); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::lt(uint16_t{42})); using KT = std::decay_t; EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u); EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(), ds.first.value_cw().data(), dealer.first.value_cw().size() * sizeof(typename KT::value_cw_word))); EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last()); EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend()); EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend()); const uint64_t mask = dealer.first.cmp().mask; for (uint32_t q : {x - 1u, x, x + 1u, 0u}) { EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q)) & mask, dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask); } } TEST_F(IncrementalDpfTest, CmpPayloadHiddenOnKeys) { const uint32_t alpha = 0x55u; const uint64_t yt = 42u, yf = 7u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf)); const uint64_t mask = k0.cmp().mask; const uint64_t delta = (yt - yf) & mask; // δ is not a clear field; if_false is only as additive shares. EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf); // value CWs are identical (public) and encode δ, not a readable beta field. EXPECT_TRUE(same_bytes(k0.value_cw().data(), k1.value_cw().data(), k0.value_cw().size() * sizeof(uint64_t))); EXPECT_EQ(k0.cw_last(), k1.cw_last()); (void)delta; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u)) & mask, yt); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, yf); auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt, yf)); // geq absorb target is δ + if_false = if_true. EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, yt); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha - 1u), dpf::eval_point(dpf::cmp, g1, alpha - 1u)) & mask, yf); } TEST_F(IncrementalDpfTest, UnifiedEvalTargetSurface) { const uint32_t alpha = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<16>(uint16_t{9}), uint32_t{7}, dpf::lt(uint64_t{3}, uint64_t{1})); using KT = std::decay_t; EXPECT_TRUE(dpf::is_incremental_dpf_key_v); EXPECT_TRUE(dpf::is_out_v)>); EXPECT_TRUE(dpf::is_cmp_target_v); // Point via out / out matches out<> point eval. EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha), *dpf::eval_point(dpf::out<0, 16>, k1, alpha)), 9u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha), *dpf::eval_point(dpf::out<1>, k1, alpha)), 7u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha), *dpf::eval_point(dpf::out<0, 16>, k1, alpha)), 9u); // Cmp via eval_point(cmp). const uint64_t mask = k0.cmp().mask; EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u)) & mask, 3u); EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 1u); // Interval / sequence target-first for cmp. auto b0 = dpf::eval_interval(dpf::cmp, k0, static_cast(alpha - 1u), static_cast(alpha + 1u)); auto b1 = dpf::eval_interval(dpf::cmp, k1, static_cast(alpha - 1u), static_cast(alpha + 1u)); EXPECT_EQ(recon(b0[0], b1[0]) & mask, 3u); EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u); EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u); std::array pts{{alpha - 1u, alpha}}; auto s0 = dpf::make_output_buffer(dpf::cmp, k0, pts.size()); auto s1 = dpf::make_output_buffer(dpf::cmp, k1, pts.size()); dpf::eval_sequence(dpf::cmp, k0, pts.begin(), pts.end(), s0); dpf::eval_sequence(dpf::cmp, k1, pts.begin(), pts.end(), s1); EXPECT_EQ(recon(s0[0], s1[0]) & mask, 3u); EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u); // Point interval via out<> matches out<> interval eval. constexpr std::size_t N = 16; constexpr std::size_t I = 0; auto lane = static_cast(alpha >> (32 - N)); auto [buf0, it0] = dpf::eval_interval(dpf::out, k0, lane, lane); auto [buf1, it1] = dpf::eval_interval(dpf::out, k1, lane, lane); (void)it0; (void)it1; EXPECT_EQ(recon(*dpf::eval_point(dpf::out, k0, alpha), *dpf::eval_point(dpf::out, k1, alpha)), 9u); } TEST_F(IncrementalDpfTest, ClassicKeyHasSlotMetaAndOutEval) { // Every key now carries a constexpr `slot_meta` table. A classic // (single-level, equal-width, no-cmp) key keeps `is_multilevel == false` // and still routes through the classic eval fast path, but the unified // `out` surface works on it too. uint32_t x = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{9}); using KT = std::decay_t; // Foundation traits: has slot meta (so "incremental" trait is true) but is // not multi-level. EXPECT_TRUE(dpf::is_incremental_dpf_key_v); EXPECT_FALSE(dpf::is_multilevel_key_v); EXPECT_EQ(KT::num_outputs, 2u); EXPECT_EQ(KT::cmp_depth, 0u); EXPECT_EQ(KT::deepest_output, 0u); EXPECT_EQ(KT::meta[0].prefix, 32u); EXPECT_EQ(KT::meta[1].prefix, 32u); EXPECT_EQ(KT::meta[0].tree_level, KT::depth); EXPECT_EQ(KT::meta[1].tree_level, KT::depth); // Equal-width classic packing: one group, consecutive block positions. // Effective leaf position = pos_base + index_in_group * block_len, which // matches the classic `block_offset_of_leaf` layout. EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id); EXPECT_EQ(KT::meta[0].pos_base, 0u); EXPECT_EQ(KT::meta[0].index_in_group, 0u); EXPECT_EQ(KT::meta[1].index_in_group, 1u); const std::size_t eff0 = KT::meta[0].pos_base + KT::meta[0].index_in_group * KT::meta[0].block_len; const std::size_t eff1 = KT::meta[1].pos_base + KT::meta[1].index_in_group * KT::meta[1].block_len; EXPECT_EQ(eff0, 0u); EXPECT_EQ(eff1, KT::meta[1].block_len); // Unified out eval matches the classic per-slot eval_point. EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x), *dpf::eval_point(dpf::out<0>, k1, x)), recon(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x))); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x), *dpf::eval_point(dpf::out<0>, k1, x)), 7u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x), *dpf::eval_point(dpf::out<1>, k1, x)), 9u); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x ^ 1u), *dpf::eval_point(dpf::out<1>, k1, x ^ 1u)), 0u); } TEST_F(IncrementalDpfTest, CmpWildcardAssignMatchesConcrete) { // Gen the comparison with a wildcard payload (δ opened at 0), then // `assign_cmp` the concrete `lt(42)` and check both the (public) value CWs // match a byte-for-byte concrete gen on the same tape and that the channel // reconstructs the comparison. const uint32_t alpha = 0x00abcdefu; const uint64_t yt = 42u; reset_tape_roots(); auto wc = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, dpf::lt(dpf::wildcard_value{})); using WT = std::decay_t; static_assert(WT::cmp_is_wildcard, "expected a wildcard cmp key"); EXPECT_TRUE(wc.first.has_cmp()); EXPECT_FALSE(wc.first.cmp_assigned()); // Evaluating before assignment must throw. EXPECT_THROW(dpf::eval_point(dpf::cmp, wc.first, alpha), std::exception); reset_tape_roots(); auto cc = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, dpf::lt(yt)); using CT = std::decay_t; static_assert(!CT::cmp_is_wildcard, "concrete key must not be wildcard"); dpf::assign_cmp(wc.first, wc.second, yt); EXPECT_TRUE(wc.first.cmp_assigned()); EXPECT_TRUE(wc.second.cmp_assigned()); // Value CWs / cw_last are public and now identical to the concrete gen. EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), cc.first.value_cw().data(), wc.first.value_cw().size() * sizeof(typename WT::value_cw_word))); EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), wc.second.value_cw().data(), wc.first.value_cw().size() * sizeof(typename WT::value_cw_word))); EXPECT_EQ(wc.first.cw_last(), cc.first.cw_last()); const uint64_t mask = wc.first.cmp().mask; EXPECT_EQ(dpf::reconstruct(wc.first.cmp_addend(), wc.second.cmp_addend()) & mask, 0u); auto r = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, wc.first, q), dpf::eval_point(dpf::cmp, wc.second, q)) & mask; }; EXPECT_EQ(r(alpha - 1u), yt); EXPECT_EQ(r(0u), yt); EXPECT_EQ(r(alpha), 0u); EXPECT_EQ(r(alpha + 1u), 0u); } TEST_F(IncrementalDpfTest, CmpWildcardPairAndGeqAndInterval) { // Wildcard with both if_true and if_false, and a geq relation. const uint32_t alpha = 100u; const uint64_t yt = 5u, yf = 9u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(dpf::wildcard_value{})); dpf::assign_cmp(k0, k1, yt, yf); const uint64_t mask = k0.cmp().mask; auto r = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask; }; EXPECT_EQ(r(50u), yt); EXPECT_EQ(r(100u), yf); EXPECT_EQ(r(200u), yf); // if_false reconstructs from the split addend shares. EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf); auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(dpf::wildcard_value{})); dpf::assign_cmp(g0, g1, yt, yf); auto rg = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask; }; EXPECT_EQ(rg(99u), yf); EXPECT_EQ(rg(100u), yt); EXPECT_EQ(rg(101u), yt); // geq absorb target is δ + if_false = if_true. EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt); // Interval eval on an assigned wildcard cmp still works. auto b0 = dpf::eval_interval(dpf::cmp, k0, static_cast(99u), static_cast(101u)); auto b1 = dpf::eval_interval(dpf::cmp, k1, static_cast(99u), static_cast(101u)); EXPECT_EQ(recon(b0[0], b1[0]) & mask, yt); // 99 < 100 EXPECT_EQ(recon(b0[1], b1[1]) & mask, yf); // 100 !< 100 EXPECT_EQ(recon(b0[2], b1[2]) & mask, yf); // 101 !< 100 } TEST_F(IncrementalDpfTest, CmpWildcardNarrowPayloadAndAt) { // Narrow (uint16) wildcard payload keeps the group-width value CW words, // and `lt_at` prefixes work through the wildcard path. const uint32_t alpha = 0x00abcdefu; auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<16>(uint16_t{9}), dpf::lt(dpf::wildcard_value{})); using KT = std::decay_t; EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u); EXPECT_TRUE(KT::cmp_is_wildcard); // The concrete output slot is unaffected by the wildcard cmp. EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha), *dpf::eval_point(dpf::out<0, 16>, k1, alpha)), 9u); dpf::assign_cmp(k0, k1, uint16_t{42}); const uint64_t mask = k0.cmp().mask; auto r = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask; }; EXPECT_EQ(r(alpha - 1u), 42u); EXPECT_EQ(r(alpha), 0u); EXPECT_EQ(r(alpha + 1u), 0u); } TEST_F(IncrementalDpfTest, DsWildcardCmpMatchesDealerThenAssign) { // Local Doerner–Shelat gen of a wildcard comparison payload must be // byte-identical to the dealer (δ = 0 CWs + addend blinds), and after // `assign_cmp` the patched public CWs / reconstruction must match a // concrete `lt(β)` keygen on the same tape. const uint32_t alpha = 0x00abcdefu; const uint32_t a0 = 0x12345678u; const uint32_t a1 = alpha ^ a0; const uint64_t yt = 42u; reset_tape_roots(); auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, dpf::lt(dpf::wildcard_value{})); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng, dpf::lt(dpf::wildcard_value{})); using WT = std::decay_t; static_assert(WT::cmp_is_wildcard); EXPECT_FALSE(dealer.first.cmp_assigned()); EXPECT_FALSE(ds.first.cmp_assigned()); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(), ds.first.value_cw().data(), dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word))); EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last()); EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend()); EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend()); reset_tape_roots(); auto concrete = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, dpf::lt(yt)); dpf::assign_cmp(dealer.first, dealer.second, yt); dpf::assign_cmp(ds.first, ds.second, yt); EXPECT_TRUE(dealer.first.cmp_assigned()); EXPECT_TRUE(ds.first.cmp_assigned()); EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(), concrete.first.value_cw().data(), dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word))); EXPECT_TRUE(same_bytes(ds.first.value_cw().data(), concrete.first.value_cw().data(), ds.first.value_cw().size() * sizeof(typename WT::value_cw_word))); EXPECT_EQ(dealer.first.cw_last(), concrete.first.cw_last()); EXPECT_EQ(ds.first.cw_last(), concrete.first.cw_last()); const uint64_t mask = dealer.first.cmp().mask; auto r_ds = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask; }; auto r_cc = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, concrete.first, q), dpf::eval_point(dpf::cmp, concrete.second, q)) & mask; }; for (uint32_t q : {alpha - 1u, alpha, alpha + 1u, 0u}) EXPECT_EQ(r_ds(q), r_cc(q)) << "q=" << q; } TEST_F(IncrementalDpfTest, DsWildcardCmpWithAtAndNarrowPayload) { const uint32_t alpha = 0x00abcdefu; const uint32_t a0 = 0x0f0f0f0fu; const uint32_t a1 = alpha ^ a0; reset_tape_roots(); auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, dpf::at<16>(uint16_t{9}), dpf::lt(dpf::wildcard_value{})); reset_tape_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng, dpf::at<16>(uint16_t{9}), dpf::lt(dpf::wildcard_value{})); EXPECT_TRUE(same_incr_key(dealer.first, ds.first)); EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, ds.first, alpha), *dpf::eval_point(dpf::out<0, 16>, ds.second, alpha)), uint16_t{9}); dpf::assign_cmp(ds.first, ds.second, uint16_t{7}, uint16_t{1}); const uint64_t mask = ds.first.cmp().mask; auto r = [&](uint32_t q) { return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask; }; EXPECT_EQ(r(alpha - 1u), 7u); EXPECT_EQ(r(alpha), 1u); EXPECT_EQ(dpf::reconstruct(ds.first.cmp_addend(), ds.second.cmp_addend()) & mask, 1u); } TEST_F(IncrementalDpfTest, MultilevelInnerProductMatchesInterval) { // Inner product of a prefix-slot (`at<12>(u8)`) against a public weight // vector must reconstruct to the dot of the interval outputs with the // weights. Additive output => recon(ip) == Σ recon(interval)[j] * w[j]. uint32_t x = 0x00a5b6c7u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{99}); const uint32_t lane = x >> (32 - 12); const uint32_t from = (lane & ~0xffu); const uint32_t to = from + 255u; auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to); auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to); (void)it0; (void)it1; std::vector w(buf0.size()); uint64_t expect = 0; for (std::size_t i = 0; i < w.size(); ++i) { w[i] = (i * 7u + 1u) & 0x3fu; expect += static_cast(recon(buf0[i], buf1[i])) * w[i]; } expect &= 0xffu; // u8 output wraps at 8 bits auto a = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w); auto b = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w); EXPECT_EQ(static_cast(recon(a, b)), static_cast(expect)); // Same result when driving a caller-supplied stop-level memoizer. using KT = std::decay_t; auto m0 = dpf::make_basic_interval_memoizer(from, to); auto m1 = dpf::make_basic_interval_memoizer(from, to); auto am = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w, m0); auto bm = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w, m1); EXPECT_EQ(am, a); EXPECT_EQ(bm, b); } TEST_F(IncrementalDpfTest, CmpInnerProductMatchesInterval) { // Cmp inner product: Σ (path-sum share) * w over the interval; the two // parties' results sum to Σ value[i] * w[i]. const uint16_t alpha = 0x00aau; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt_at<8>(uint64_t{5}, uint64_t{1})); const uint64_t mask = k0.cmp().mask; const uint8_t from = 0x00, to = 0x0f; auto b0 = dpf::eval_interval(dpf::cmp, k0, from, to); auto b1 = dpf::eval_interval(dpf::cmp, k1, from, to); std::vector w(b0.size()); uint64_t expect = 0; for (std::size_t i = 0; i < w.size(); ++i) { w[i] = (i * 3u + 2u) & 0xffu; const uint64_t value = recon(b0[i], b1[i]) & mask; expect = (expect + value * (w[i] & mask)) & mask; } auto a = dpf::eval_inner_product(dpf::cmp, k0, from, to, w); auto b = dpf::eval_inner_product(dpf::cmp, k1, from, to, w); EXPECT_EQ((a + b) & mask, expect); } TEST_F(IncrementalDpfTest, BreadthFirstAtNonFinalSlot) { // Breadth-first sequence eval stopping at a prefix slot's tree level. uint32_t x = 0x00a5b6c7u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{77}), uint32_t{99}); constexpr std::size_t N = 12, I = 0; const uint32_t xlane = x >> (32 - N); std::vector lanes = { (xlane & ~0xfu), xlane, static_cast(xlane ^ 1u), static_cast((xlane + 5u) & 0xfffu), static_cast((xlane + 300u) & 0xfffu)}; std::sort(lanes.begin(), lanes.end()); lanes.erase(std::unique(lanes.begin(), lanes.end()), lanes.end()); auto bf0 = dpf::eval_sequence_breadth_first(dpf::out, k0, lanes.begin(), lanes.end()); auto bf1 = dpf::eval_sequence_breadth_first(dpf::out, k1, lanes.begin(), lanes.end()); for (std::size_t i = 0; i < lanes.size(); ++i) { const uint32_t q = lanes[i] << (32 - N); const auto expect = recon(*dpf::eval_point(dpf::out, k0, q), *dpf::eval_point(dpf::out, k1, q)); EXPECT_EQ(recon(bf0[i], bf1[i]), expect) << "i=" << i; } } TEST_F(IncrementalDpfTest, CrossLevelPathMemoizerShallowThenDeep) { // A single path memoizer reused shallow (prefix slot) then deep (full // domain) must agree with independent non-memoized evals at both levels. uint32_t x = 0xabcdef01u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), uint32_t{1234}); using KT = std::decay_t; dpf::basic_path_memoizer p0{}, p1{}; for (uint32_t q : {x, static_cast(x ^ 1u), static_cast(x ^ 0x80000000u), 0u}) { // Shallow first, then deep, sharing the same memoizer. auto s0 = dpf::eval_point(dpf::out<0, 10>, k0, q, p0); auto s1 = dpf::eval_point(dpf::out<0, 10>, k1, q, p1); auto d0 = dpf::eval_point(dpf::out<1, 32>, k0, q, p0); auto d1 = dpf::eval_point(dpf::out<1, 32>, k1, q, p1); auto sref = static_cast( recon(*dpf::eval_point(dpf::out<0, 10>, k0, q), *dpf::eval_point(dpf::out<0, 10>, k1, q))); auto dref = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q), *dpf::eval_point(dpf::out<1, 32>, k1, q)); EXPECT_EQ(static_cast(recon(*s0, *s1)), sref) << "q=" << q; EXPECT_EQ(recon(*d0, *d1), dref) << "q=" << q; } } TEST_F(IncrementalDpfTest, SequenceRecipeAtPrefixSlot) { // Recipe depth follows the slot's tree_level, not the full key depth. uint32_t x = 0x00a5b6c7u; auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{7}); using KT = std::decay_t; EXPECT_EQ(KT::meta[0].tree_level, 8u); const uint32_t lane = x >> (32 - 12); std::array pts{{ static_cast(lane & ~1u), lane, static_cast((lane & ~1u) + 1u)}}; std::sort(pts.begin(), pts.end()); auto recipe = dpf::make_sequence_recipe(dpf::out<0, 12>, k0, pts.begin(), pts.end()); EXPECT_EQ(recipe.depth(), KT::meta[0].tree_level); EXPECT_EQ(recipe.output_indices().size(), pts.size()); // Breadth-first at the same slot reconstructs the point payloads. auto b0 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k0, pts.begin(), pts.end()); auto b1 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k1, pts.begin(), pts.end()); for (std::size_t i = 0; i < pts.size(); ++i) { const uint32_t q = static_cast(pts[i] << (32 - 12)); EXPECT_EQ(recon(b0[i], b1[i]), recon(*dpf::eval_point(dpf::out<0, 12>, k0, q), *dpf::eval_point(dpf::out<0, 12>, k1, q))); } }