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:
parent
0d8a5a8131
commit
0dff6df8ed
250 changed files with 12199 additions and 1981 deletions
|
|
@ -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<
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue