Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 23:18:10 -06:00
parent 0d8a5a8131
commit 0dff6df8ed
250 changed files with 12199 additions and 1981 deletions

View file

@ -1,6 +1,6 @@
/// @file dpf/utils.hpp
/// @brief miscellaneous helper functions, structs, preprocessor directives
/// @details
/// @details Type traits, bit lengths, and small tuple helpers shared by the headers.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
@ -95,13 +95,13 @@ class numeric_limits<uint128_t const>
: public numeric_limits<uint128_t> {};
/// @details specializes `std::numeric_limits` for
/// `uint128_t volatile`
/// @brief `uint128_t volatile`
template<>
class numeric_limits<uint128_t volatile>
: public numeric_limits<uint128_t> {};
/// @details specializes `std::numeric_limits` for
/// `uint128_t const volatile`
/// @brief `uint128_t const volatile`
template<>
class numeric_limits<uint128_t const volatile>
: public numeric_limits<uint128_t> {};
@ -162,13 +162,13 @@ class numeric_limits<uint256_t const>
: public numeric_limits<uint256_t> {};
/// @details specializes `std::numeric_limits` for
/// `uint256_t volatile`
/// @brief `uint256_t volatile`
template<>
class numeric_limits<uint256_t volatile>
: public numeric_limits<uint256_t> {};
/// @details specializes `std::numeric_limits` for
/// `uint256_t const volatile`
/// @brief `uint256_t const volatile`
template<>
class numeric_limits<uint256_t const volatile>
: public numeric_limits<uint256_t> {};
@ -184,6 +184,11 @@ namespace utils
{
/// @brief Ugly hack to implement `constexpr`-frien`dly conditional `throw`
/// @tparam Exception exception
/// @param b the `b`
/// @param what the diagnostic message
/// @return Ugly hack to implement `constexpr`-frien`dly conditional `throw`
/// @throws Exception
template <typename Exception>
HEDLEY_ALWAYS_INLINE
static constexpr auto constexpr_maybe_throw(bool b, std::string_view what) -> void
@ -213,6 +218,11 @@ template <typename T>
static constexpr bool is_quotient_integer_v = is_quotient_integer<T>::value;
/// @brief Integer overflow-proof ceiling of division
/// @tparam T value type
/// @tparam T value type
/// @param numerator the `numerator`
/// @param denominator the `denominator`
/// @return Integer overflow-proof ceiling of division
template <typename T,
std::enable_if_t<is_quotient_integer_v<T>, bool> = false>
HEDLEY_CONST
@ -225,6 +235,11 @@ static constexpr T quotient_ceiling(T numerator, T denominator) noexcept
}
/// @brief Integer overflow-proof floor of division
/// @tparam T value type
/// @tparam T value type
/// @param numerator the `numerator`
/// @param denominator the `denominator`
/// @return Integer overflow-proof floor of division
template <typename T,
std::enable_if_t<is_quotient_integer_v<T>, bool> = false>
HEDLEY_CONST
@ -242,11 +257,12 @@ struct is_signed_integral
template <typename T>
static constexpr bool is_signed_integral_v = is_signed_integral<T>::value;
/// Whether DPF keygen/eval flip the input MSB (two's-complement domains).
/// Distinct from `is_signed_integral`: wrappers such as signed `fixedpoint`
/// @brief Whether DPF keygen/eval flip the input MSB (two's-complement domains).
/// @details Distinct from `is_signed_integral`: wrappers such as signed `fixedpoint`
/// are not `std::is_integral`, and treating them as such would break
/// `make_unsigned`. Sequence recipe construction and breadth-first eval
/// must use this trait, not `is_signed_integral_v`.
/// @tparam T value type
template <typename T>
struct uses_signed_msb
: std::bool_constant<
@ -274,6 +290,9 @@ template <typename T>
using make_unsigned_t = typename make_unsigned<T>::type;
/// @brief Make an `std::bitset` from a variadic list of `bool`s
/// @tparam Bools bools
/// @param bs the `bs`
/// @return Make an `std::bitset` from a variadic list of `bool`s
template <typename ...Bools>
auto make_bitset(Bools ...bs)
{
@ -356,6 +375,7 @@ struct bitlength_of<simde__m128i>
template <>
struct bitlength_of<simde__m256i>
: public std::integral_constant<std::size_t, 256> { };
HEDLEY_PRAGMA(GCC diagnostic pop)
// template <>
// struct bitlength_of<simde__m512i>
@ -364,7 +384,6 @@ struct bitlength_of<simde__m256i>
template <typename T, std::size_t N>
struct bitlength_of<std::array<T, N>>
: public std::integral_constant<std::size_t, bitlength_of_v<T> * N> { };
HEDLEY_PRAGMA(GCC diagnostic pop)
template <typename OutputT,
typename NodeT>
@ -385,6 +404,9 @@ template <typename OutputT,
static constexpr std::size_t bitlength_of_output_v = bitlength_of_output<OutputT, NodeT>::value;
/// @brief the primitive integral type used to represent non integral types
/// @tparam Nbits width in bits
/// @tparam MinBits min bits
/// @tparam MaxBits max bits
template <std::size_t Nbits,
std::size_t MinBits = Nbits,
std::size_t MaxBits = std::max(Nbits, MinBits)>
@ -413,6 +435,9 @@ template <std::size_t Nbits,
using integral_type_from_bitlength_t = typename integral_type_from_bitlength<Nbits, MinBits, MaxBits>::type;
/// @brief the primitive integral type used to represent non integral types
/// @tparam Nbits width in bits
/// @tparam MinBits min bits
/// @tparam MaxBits max bits
template <std::size_t Nbits,
std::size_t MinBits = Nbits,
std::size_t MaxBits = std::max(std::size_t(256), MinBits)>
@ -475,9 +500,13 @@ struct make_from_integral_value
}
};
/// Reconstruct `x0 XOR x1` via the integral bridge. Prefer this over
/// @brief Reconstruct `x0 XOR x1` via the integral bridge. Prefer this over
/// `static_cast<T>(x0 ^ x1)`: for `keyword`, `operator^` yields the parent
/// `modint`, which cannot convert back through the private keyword ctor.
/// @tparam T value type
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @return Reconstruct `x0 XOR x1` via the integral bridge
template <typename T>
HEDLEY_NO_THROW
constexpr T xor_input_shares(T x0, T x1) noexcept
@ -507,8 +536,12 @@ static constexpr IntegralT get_node_mask(InputT mask, std::size_t level_index)
return static_cast<IntegralT>(to_int(mask) >> (level_index-1 + dpf_type::lg_outputs_per_leaf));
}
/// Logical right shift. Offsets at or past the width yield 0 (a `>>` of that
/// @brief Logical right shift. Offsets at or past the width yield 0 (a `>>` of that
/// width is undefined for the native unsigned types).
/// @tparam IntegralT integral type
/// @param value the value to convert or store
/// @param offset the public offset
/// @return Logical right shift
template <typename IntegralT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
@ -519,7 +552,11 @@ constexpr IntegralT shift_right(IntegralT value, std::size_t offset) noexcept
return static_cast<IntegralT>(value >> offset);
}
/// Floor of `from_inclusive / 2^lg_opl`. `lg_opl` is `log2(outputs_per_leaf)`.
/// @brief Floor of `from_inclusive / 2^lg_opl`. `lg_opl` is `log2(outputs_per_leaf)`.
/// @tparam IntegralT integral type
/// @param from_inclusive the `from_inclusive`
/// @param lg_opl the `lg_opl`
/// @return Floor of `from_inclusive / 2^lg_opl`
template <typename IntegralT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
@ -530,11 +567,15 @@ constexpr IntegralT leaf_node_floor(IntegralT from_inclusive, std::size_t lg_opl
return shift_right(from_inclusive, lg_opl);
}
/// Exclusive leaf index of an inclusive input `to_inclusive`.
/// `2^lg_opl` outputs share a leaf. When `to_inclusive + 1` does not fit in
/// @brief Exclusive leaf index of an inclusive input `to_inclusive`.
/// @details `2^lg_opl` outputs share a leaf. When `to_inclusive + 1` does not fit in
/// `IntegralT`, the exclusive node index is `2^(width - lg_opl)`. That value
/// itself does not fit when `lg_opl == 0`; the returned 0 is that saturated
/// end (`[from, 2^width)`), which `split_leaf_nodes` interprets.
/// @tparam IntegralT integral type
/// @param to_inclusive the `to_inclusive`
/// @param lg_opl the `lg_opl`
/// @return Exclusive leaf index of an inclusive input `to_inclusive`
template <typename IntegralT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
@ -553,9 +594,15 @@ constexpr IntegralT leaf_node_ceil_exclusive(IntegralT to_inclusive, std::size_t
return quotient_ceiling(next, opl);
}
/// Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and,
/// @brief Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and,
/// for interval splitting, its `tree_level`) explicitly. The classic wrappers
/// below forward the deepest-slot packing (`DpfKey::lg_outputs_per_leaf`).
/// @tparam InputT input domain type
/// @tparam size_t size type
/// @param from the inclusive start of the range
/// @param lg_opl the `lg_opl`
/// @return Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and, for interval
/// splitting, its `tree_level`) explicitly
template <typename InputT,
typename IntegralT = integral_type_from_bitlength_t<
bitlength_of_v<InputT>, bitlength_of_v<std::size_t>>>
@ -591,8 +638,9 @@ static constexpr IntegralT get_to_node(InputT to)
return get_to_node_at<InputT, IntegralT>(to, DpfKey::lg_outputs_per_leaf);
}
/// One half-open leaf-node range. `to_node == 0` with a nonzero `count` is the
/// @brief One half-open leaf-node range. `to_node == 0` with a nonzero `count` is the
/// saturated end `[from_node, 2^width)`.
/// @tparam IntegralT integral type
template <typename IntegralT>
struct node_segment
{
@ -609,9 +657,14 @@ struct node_segments
std::size_t total = 0;
};
/// True when the inclusive walk `[from, to]` wraps the low `bits` of the
/// @brief True when the inclusive walk `[from, to]` wraps the low `bits` of the
/// domain. Comparison is on the post-MSB-flip bit pattern. Leaf ids alone
/// cannot carry this: packing can put a wrapping pair into `from_node <= to_node`.
/// @tparam IntegralT integral type
/// @param from the inclusive start of the range
/// @param to the `to`
/// @param bits the packed bits
/// @return True when the inclusive walk `[from, to]` wraps the low `bits` of the domain
template <typename IntegralT>
inline bool interval_wraps(IntegralT from, IntegralT to, std::size_t bits)
{
@ -626,18 +679,25 @@ inline bool interval_wraps(IntegralT from, IntegralT to, std::size_t bits)
return from > to;
}
/// Split an inclusive output interval, already reduced to leaf ids, into one
/// @brief Split an inclusive output interval, already reduced to leaf ids, into one
/// or two half-open walks. A linearized `from_node > to_node` wraps the node
/// id space `[0, 2^depth)`. A saturated `to_node == 0` means the exclusive end
/// is `2^{bitwidth(IntegralT)}`, which is the whole id space when `depth` is
/// that width.
///
/// `input_wraps` is the order of the original inputs, before leaf coarsening.
/// The buffer is still two runs, `[from_node, 2^depth)` then `[0, to_node)`,
/// @details The buffer is still two runs, `[from_node, 2^depth)` then `[0, to_node)`,
/// even when packing makes `from_node <= to_node`. In that case the runs
/// overlap on the shared leaf: the iterable's preclip consumes the start of
/// the first copy and its length stops inside the second. Collapsing the
/// overlap into one forward segment writes the wrong leaves.
/// @tparam IntegralT integral type
/// @param from_node the `from_node`
/// @param to_node the `to_node`
/// @param depth the tree depth
/// @param input_wraps the `input_wraps`
/// @return the returned `node_segments<IntegralT>`
/// @throws std::length_error if `DPF leaf domain does not fit in size_t`
template <typename IntegralT>
inline node_segments<IntegralT> split_leaf_nodes(IntegralT from_node,
IntegralT to_node, std::size_t depth, bool input_wraps = false)
@ -745,7 +805,13 @@ static std::size_t get_leafnodes_in_output_interval(InputT from, InputT to)
static_cast<std::size_t>(DpfKey::depth), wraps).total;
}
/// Historical name used by the test suite.
/// @brief Historical name used by the test suite.
/// @tparam DpfKey DPF key type
/// @tparam InputT input domain type
/// @tparam IntegralT integral type
/// @param from the inclusive start of the range
/// @param to the `to`
/// @return Historical name used by the test suite
template <typename DpfKey,
typename InputT = typename DpfKey::input_type,
typename IntegralT = typename DpfKey::integral_type>
@ -1092,6 +1158,7 @@ struct countr_zero<simde__m256i>
return suffix_len;
}
};
HEDLEY_PRAGMA(GCC diagnostic pop)
// template <>
// struct countl_zero<simde__m512i>
@ -1113,7 +1180,6 @@ struct countr_zero<simde__m256i>
// return prefix_len;
// }
// };
HEDLEY_PRAGMA(GCC diagnostic pop)
template <typename T>
struct is_xor_wrapper : std::false_type {};
@ -1121,9 +1187,10 @@ struct is_xor_wrapper : std::false_type {};
template <typename T>
static constexpr bool is_xor_wrapper_v = is_xor_wrapper<T>::value;
/// Sub-byte DPF outputs whose lanes are packed inside a leaf node
/// @brief Sub-byte DPF outputs whose lanes are packed inside a leaf node
/// (`dpf::bit` is 1, `dpf::twobit` is 2, `dpf::nyble` is 4). The leaf
/// image is the buffer image: interval eval memcpy's the node.
/// @tparam T value type
template <typename T>
struct is_packed_subbyte : std::false_type {};
@ -1147,7 +1214,10 @@ constexpr auto data(T & bar) noexcept // NOLINT(runtime/references)
return std::data(bar);
}
/// Pointer overload. Constness of `bar` is the constness of `T`.
/// @brief Pointer overload. Constness of `bar` is the constness of `T`.
/// @tparam T value type
/// @param bar the `bar`
/// @return Pointer overload
template <typename T>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
@ -1269,6 +1339,39 @@ auto get_common_part_hash(const std::array<InteriorNodeT, Depth> & correction_wo
return digest;
}
template <typename InteriorNodeT,
std::size_t Depth,
typename LeafTupleT,
typename WildcardMaskT,
typename ExtraT>
auto get_common_part_hash(const std::array<InteriorNodeT, Depth> & correction_words,
const std::array<psnip_uint8_t, Depth> & correction_advice,
const LeafTupleT & leaf_tuple,
const WildcardMaskT & wildcard_mask,
const ExtraT & extra)
{
using zero_type = unsigned char;
static constexpr zero_type zero{};
SHA256 h;
digest_type digest;
h.add(&correction_words, sizeof(correction_words));
h.add(&correction_advice, sizeof(correction_advice));
if constexpr (std::tuple_size_v<ExtraT> > 0)
h.add(&extra, sizeof(extra));
std::apply([&h, &wildcard_mask](auto const & ...leaf)
{
std::apply([&h, &leaf...](auto ...is_wildcard)
{
(h.add(!is_wildcard ? reinterpret_cast<const zero_type*>(&leaf.get()) : &zero, !is_wildcard ? sizeof(leaf.get()) : sizeof(zero)), ...);
}, wildcard_mask);
}, leaf_tuple);
h.getHash(digest.data());
return digest;
}
template <typename DpfKey>
auto get_common_part_hash(const DpfKey & dpf)
{
@ -1284,6 +1387,7 @@ struct has_operators_plus_minus : public std::false_type { };
/// @brief True when `a + b` and `a - b` are valid expressions.
/// Overload sets are accepted; taking the address of `operator+`
/// is not, because that fails when `+` or `-` is overloaded.
/// @tparam OutputT output type
template <typename OutputT>
struct has_operators_plus_minus<OutputT,
std::void_t<