libdpf/test/tests/incremental_test.cpp

1897 lines
77 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include "dpf.hpp"
#include "grotto/fixedpoint.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <type_traits>
#include <vector>
namespace
{
simde__m128i g_roots[16];
int g_ri = 0;
simde__m128i take_root() { return g_roots[g_ri++]; }
std::vector<unsigned char> 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<long long>(n),
static_cast<long long>(n * 9 + 3));
n += 2;
return v;
}
uint8_t bit() { return static_cast<uint8_t>(n++ & 1u); }
};
struct PadB
{
uint64_t n = 99;
simde__m128i block()
{
auto v = simde_mm_set_epi64x(static_cast<long long>(n * 7),
static_cast<long long>(n ^ 0x5a5a));
n += 3;
return v;
}
uint8_t bit() { return static_cast<uint8_t>((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<unsigned char>(i * 17 + 3);
}
bool same_bytes(const void * a, const void * b, std::size_t n)
{
return std::memcmp(a, b, n) == 0;
}
template <typename T>
constexpr bool is_xor_out_v =
std::is_same_v<T, dpf::bit> || dpf::utils::is_xor_wrapper_v<T>;
template <typename A, typename B>
auto recon(const A & a, const B & b)
{
if constexpr (dpf::is_secret_share_v<A> && dpf::is_secret_share_v<B>)
return dpf::reconstruct(a, b);
else if constexpr (is_xor_out_v<A>)
return static_cast<A>(a ^ b);
else
return static_cast<A>(a - b); // subtractive leaf: party0 - party1
}
template <typename Key, std::size_t I = 0>
bool same_leaf_beaver(const Key & a, const Key & b)
{
if constexpr (I < Key::num_outputs)
{
const auto & la = a.template leaf<I>();
const auto & lb = b.template leaf<I>();
if (!same_bytes(&la, &lb, sizeof(la)))
return false;
const auto & ba = a.template beaver<I>();
const auto & bb = b.template beaver<I>();
using out_t = typename Key::template output_type_t<I>;
if constexpr (dpf::is_wildcard_v<out_t>)
{
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<Key, I + 1>(a, b);
}
return same_bytes(&ba, &bb, sizeof(ba))
&& same_leaf_beaver<Key, I + 1>(a, b);
}
return true;
}
template <typename Key>
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 <typename Key, std::size_t I = 0>
bool same_leaf_beaver_classic(const Key & a, const Key & b)
{
if constexpr (I < std::tuple_size_v<typename Key::outputs_tuple>)
{
const auto & la = a.template leaf<I>();
const auto & lb = b.template leaf<I>();
if (!same_bytes(&la, &lb, sizeof(la)))
return false;
const auto & ba = a.template beaver<I>();
const auto & bb = b.template beaver<I>();
using out_t = typename Key::template output_type_t<I>;
if constexpr (dpf::is_wildcard_v<out_t>)
{
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<Key, I + 1>(a, b);
}
return same_bytes(&ba, &bb, sizeof(ba))
&& same_leaf_beaver_classic<Key, I + 1>(a, b);
}
return true;
}
template <typename Key>
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 <typename InputT>
InputT flip_lane_lsb(InputT x, std::size_t prefix, std::size_t bitlen)
{
return static_cast<InputT>(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<dpf::prg::aes128>{take_root}, y);
using KT = std::decay_t<decltype(via_args.first)>;
static_assert(std::is_same_v<KT, std::decay_t<decltype(via_conv.first)>>);
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<uint32_t>(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<decltype(k0)>;
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<bool>(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<bool>(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<decltype(k0)>;
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<decltype(k0)>;
// 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<bool>(
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<uint32_t>{0xdeadbeefu},
dpf::bit{true});
using KT = std::decay_t<decltype(k0)>;
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<bool>(
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<uint32_t>{0xdeadbeefu});
EXPECT_TRUE(static_cast<bool>(
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<bool>(
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<bool>(
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<uint32_t>{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<decltype(k0)>;
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<bool>(
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<decltype(k0)>;
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<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_TRUE(static_cast<bool>(
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<bool>(
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<dpf::prg::aes128>{take_root},
dpf::at<10>(dpf::bit::one),
dpf::at<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::xor_wrapper<uint16_t>{0xcafe});
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> 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<uint16_t>{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<simde__m128i (*)(), PadB> 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<uint16_t>{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<uint32_t> wc;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{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<simde__m128i (*)(), PadA> 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<bool>(
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<std::decay_t<decltype(k0)>>;
path_t p0{}, p1{};
auto prefix8 = [](uint16_t q) {
return static_cast<uint16_t>(q >> (16 - 8));
};
const auto xp = prefix8(x);
for (int d = -64; d <= 64; ++d)
{
uint16_t q = static_cast<uint16_t>(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<bool>(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<uint16_t>(x ^ 0x8000);
EXPECT_NE(prefix8(other), xp);
EXPECT_FALSE(static_cast<bool>(
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<dpf::prg::aes128>{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<dpf::prg::aes128>{take_root}, dpf::at<32>(y));
using BareT = std::decay_t<decltype(bare.first)>;
using PlacedT = std::decay_t<decltype(placed.first)>;
static_assert(std::is_same_v<BareT, PlacedT>);
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<decltype(k0)>;
EXPECT_EQ(KT::depth, 0u);
EXPECT_EQ(KT::meta[0].pos_base, 0u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 7>, k0, x), *dpf::eval_point(dpf::out<0, 7>, k1, x))));
EXPECT_FALSE(static_cast<bool>(
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<decltype(k0)>;
dpf::basic_path_memoizer<KT> 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<bool>(e0), static_cast<bool>(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<dpf::prg::aes128>{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<simde__m128i (*)(), PadA> 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<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, q), *dpf::eval_point(dpf::out<0, 10>, dealer.second, q))),
static_cast<bool>(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<decltype(k0)>;
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<I, 12>, k0, x), *dpf::eval_point(dpf::out<I, 12>, k1, x)),
expect);
};
check(std::integral_constant<std::size_t, 0>{}, uint8_t{1});
check(std::integral_constant<std::size_t, 1>{}, uint8_t{2});
check(std::integral_constant<std::size_t, 7>{}, uint8_t{8});
check(std::integral_constant<std::size_t, 15>{}, 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<decltype(k0)>;
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<decltype(k0)>;
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<bool>(
recon(*dpf::eval_point(dpf::out<I, 10>, k0, x), *dpf::eval_point(dpf::out<I, 10>, k1, x))),
e);
};
check_bit(std::integral_constant<std::size_t, 0>{}, expect[0]);
check_bit(std::integral_constant<std::size_t, 2>{}, expect[2]);
check_bit(std::integral_constant<std::size_t, 7>{}, 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<decltype(k0)>;
// 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<bool>(
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<fp16> wc;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{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<simde__m128i (*)(), PadA> 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<uint32_t>{0xaa},
dpf::xor_wrapper<uint32_t>{0xbb}),
// deepest
uint32_t{999});
using KT = std::decay_t<decltype(k0)>;
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<uint32_t>{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<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 13u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_TRUE(static_cast<bool>(
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<bool>(
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<decltype(k0)>;
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<decltype(k0)>;
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<std::size_t>(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<std::size_t>(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<decltype(k0)>;
constexpr auto I = KT::deepest_output;
constexpr auto N = KT::meta[I].prefix;
EXPECT_EQ(N, 32u);
std::array<uint32_t, 3> pts{{x, x ^ 1u, x ^ 0x100u}};
auto buf0 = dpf::make_output_buffer_for<I>(k0, pts.size());
auto buf1 = dpf::make_output_buffer_for<I>(k1, pts.size());
dpf::eval_sequence(dpf::out<I, N>, k0, pts.begin(), pts.end(), buf0);
dpf::eval_sequence(dpf::out<I, N>, k1, pts.begin(), pts.end(), buf1);
constexpr auto opl = KT::template outputs_per_leaf_of<I>;
auto e0 = dpf::eval_point(dpf::out<I, N>, k0, pts[0]);
auto e1 = dpf::eval_point(dpf::out<I, N>, k1, pts[0]);
EXPECT_EQ(recon(buf0[e0.offset], buf1[e1.offset]), recon(*e0, *e1));
auto f0 = dpf::eval_point(dpf::out<I, N>, k0, pts[1]);
auto f1 = dpf::eval_point(dpf::out<I, N>, k1, pts[1]);
EXPECT_EQ(recon(buf0[opl + f0.offset], buf1[opl + f1.offset]),
recon(*f0, *f1));
auto buf0b = dpf::make_output_buffer_for<I>(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<simde__m128i (*)(), PadA> 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<PadA> 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<bool>(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<bool>(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<dpf::prg::aes128>{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<simde__m128i (*)(), PadA> 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<uint16_t, 3> pts{{static_cast<uint16_t>(a2 - 1), a2,
static_cast<uint16_t>(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<decltype(k0)>;
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<decltype(k0)>;
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<decltype(k0)>;
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<decltype(u0)>;
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<decltype(b0)>;
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<dpf::prg::aes128>{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<simde__m128i (*)(), PadA> 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<decltype(dealer.first)>;
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<decltype(k0)>;
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
EXPECT_TRUE(dpf::is_out_v<decltype(dpf::out<0>)>);
EXPECT_TRUE(dpf::is_cmp_target_v<decltype(dpf::cmp)>);
// Point via out<I> / out<I,N> 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<uint32_t>(alpha - 1u), static_cast<uint32_t>(alpha + 1u));
auto b1 = dpf::eval_interval(dpf::cmp, k1,
static_cast<uint32_t>(alpha - 1u), static_cast<uint32_t>(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<uint32_t, 2> 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<uint16_t>(alpha >> (32 - N));
auto [buf0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, lane, lane);
auto [buf1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, lane, lane);
(void)it0;
(void)it1;
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<I, N>, k0, alpha),
*dpf::eval_point(dpf::out<I, N>, 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<I>` 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<decltype(k0)>;
// Foundation traits: has slot meta (so "incremental" trait is true) but is
// not multi-level.
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
EXPECT_FALSE(dpf::is_multilevel_key_v<KT>);
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<I> 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<dpf::prg::aes128>{take_root},
dpf::lt(dpf::wildcard_value<uint64_t>{}));
using WT = std::decay_t<decltype(wc.first)>;
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<dpf::prg::aes128>{take_root}, dpf::lt(yt));
using CT = std::decay_t<decltype(cc.first)>;
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<uint64_t>{}));
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<uint64_t>{}));
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<uint32_t>(99u), static_cast<uint32_t>(101u));
auto b1 = dpf::eval_interval(dpf::cmp, k1,
static_cast<uint32_t>(99u), static_cast<uint32_t>(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<N>` 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<uint16_t>{}));
using KT = std::decay_t<decltype(k0)>;
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<dpf::prg::aes128>{take_root},
dpf::lt(dpf::wildcard_value<uint64_t>{}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng,
dpf::lt(dpf::wildcard_value<uint64_t>{}));
using WT = std::decay_t<decltype(dealer.first)>;
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<dpf::prg::aes128>{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<dpf::prg::aes128>{take_root},
dpf::at<16>(uint16_t{9}),
dpf::lt(dpf::wildcard_value<uint16_t>{}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> 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<uint16_t>{}));
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<uint64_t> 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<uint64_t>(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<uint8_t>(recon(a, b)), static_cast<uint8_t>(expect));
// Same result when driving a caller-supplied stop-level memoizer.
using KT = std::decay_t<decltype(k0)>;
auto m0 = dpf::make_basic_interval_memoizer<KT, 0>(from, to);
auto m1 = dpf::make_basic_interval_memoizer<KT, 0>(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<uint64_t> 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<uint32_t> lanes = {
(xlane & ~0xfu), xlane,
static_cast<uint32_t>(xlane ^ 1u),
static_cast<uint32_t>((xlane + 5u) & 0xfffu),
static_cast<uint32_t>((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<I, N>, k0,
lanes.begin(), lanes.end());
auto bf1 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, 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<I, N>, k0, q),
*dpf::eval_point(dpf::out<I, N>, 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<decltype(k0)>;
dpf::basic_path_memoizer<KT> p0{}, p1{};
for (uint32_t q : {x, static_cast<uint32_t>(x ^ 1u),
static_cast<uint32_t>(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<bool>(
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<bool>(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<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 8u);
const uint32_t lane = x >> (32 - 12);
std::array<uint32_t, 3> pts{{
static_cast<uint32_t>(lane & ~1u), lane,
static_cast<uint32_t>((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<uint32_t>(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)));
}
}