• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zeFresk / ProPauli / 18401797324

10 Oct 2025 09:02AM UTC coverage: 75.354% (-0.05%) from 75.403%
18401797324

Pull #19

github

web-flow
Merge 830573bbd into 29fb7d4f3
Pull Request #19: Parallel version

1009 of 1884 branches covered (53.56%)

Branch coverage included in aggregate %.

222 of 227 new or added lines in 8 files covered. (97.8%)

22 existing lines in 2 files now uncovered.

2241 of 2429 relevant lines covered (92.26%)

15962.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.44
/include/container/bit_operations.hpp
1
#ifndef PP_BIT_OPERATIONS_HPP
2
#define PP_BIT_OPERATIONS_HPP
3

4
/**
5
 * @file bit_operations.hpp
6
 * @brief Provides a collection of constexpr helper functions for low-level bit manipulation.
7
 *
8
 * This header contains various standalone, compile-time-evaluated functions for
9
 * creating bitmasks and performing other bitwise operations. These utilities are the
10
 * fundamental building blocks for the memory-efficient bit-packing scheme used by
11
 * the `PauliTermContainer` class to store Pauli strings.
12
 */
13

14
#include <cstddef>
15
#include <array>
16
#include <concepts>
17
#include <bit>
18

19
template <typename T>
20
bool is_power_of_two(T k) {
21
        return (k > 0) && ((k & (k - 1)) == 0);
22
}
23

24
template <typename size_type>
25
constexpr size_type next_power_of_two(size_type n) {
38✔
26
        if (n == 0)
38!
NEW
27
                return 1;
×
28
        n--;
38✔
29
        n |= n >> 1;
38✔
30
        n |= n >> 2;
38✔
31
        n |= n >> 4;
38✔
32
        n |= n >> 8;
38✔
33
        n |= n >> 16;
38✔
34
        if constexpr (sizeof(size_type) > 4)
35
                n |= n >> 32;
38✔
36
        return ++n;
38✔
37
}
38✔
38

39
/**
40
 * @brief Creates a bitmask with a specified number of lower bits set to 1.
41
 * @tparam T The integer type for the mask.
42
 * @param nb_bits The number of low-order bits to set to 1.
43
 * @return An integer of type T with the `nb_bits` least significant bits set.
44
 * @note For example, `compute_mask<uint8_t>(3)` returns `7` (0b00000111).
45
 */
46
template <typename T>
47
constexpr T compute_mask(T nb_bits) {
×
48
        T ret = 0;
×
49
        for (T i = 0; i < nb_bits; ++i) {
×
NEW
50
                ret |= (T{ 1 } << i);
×
51
        }
×
52
        return ret;
×
53
}
×
54

55
/**
56
 * @brief Calculates the minimum number of underlying integers required to store a number of objects.
57
 * @tparam T A numeric type for the calculation.
58
 * @param nb_objs The total number of objects to store.
59
 * @param objs_per_underlying The number of objects that fit into a single underlying integer.
60
 * @return The smallest number of underlying integers needed.
61
 * @note This is effectively a ceiling division: `ceil(nb_objs / objs_per_underlying)`.
62
 */
63
template <typename T>
64
constexpr std::size_t minimum_size(std::size_t nb_objs, T objs_per_underlying) {
65
        auto rem = nb_objs % objs_per_underlying;
66
        auto quo = nb_objs / objs_per_underlying;
67
        return quo + (1 * (rem > 0));
68
}
69

70
/**
71
 * @brief Computes a shifted mask for a specific object packed within an integer.
72
 * @tparam T The integer type of the mask.
73
 * @param idx The index of the object within a packed sequence.
74
 * @param mask The base mask for a single object (e.g., 0b11).
75
 * @param objs_per_underlying The number of objects that fit into a single underlying integer.
76
 * @param bits_per_obj The number of bits per object.
77
 * @return The `mask` shifted to the correct position for the object at `idx`.
78
 */
79
template <typename T>
80
constexpr T compute_mask_idx(std::size_t idx, T mask, T objs_per_underlying, T bits_per_obj) {
81
        auto rem = idx % objs_per_underlying;
82
        return mask << (rem * bits_per_obj);
83
}
84

85
/**
86
 * @brief Computes the index of the underlying integer that contains the object at a given index.
87
 * @tparam T An integer type for the calculation.
88
 * @param idx The absolute index of the object.
89
 * @param objs_per_underlying The number of objects per underlying integer.
90
 * @return The index into an array of underlying integers.
91
 */
92
template <typename T>
93
constexpr std::size_t compute_idx(std::size_t idx, T objs_per_underlying) {
94
        return idx / objs_per_underlying;
95
}
96

97
/**
98
 * @brief Sets a value within a field of bits defined by a mask.
99
 * @tparam T The integer type.
100
 * @param out The integer to modify.
101
 * @param mask The mask defining the bit field to change (e.g., 0b001100).
102
 * @param masked_value The new value, already shifted to align with the mask.
103
 */
104
template <typename T>
105
constexpr void set_on_mask(T& out, T mask, T masked_value) {
106
        out &= ~mask; // Set bits in the field to 0
107
        out |= masked_value; // Set the new value
108
}
109

110
/**
111
 * @brief Computes a compile-time lookup table (LUT) of bitmasks for packed objects.
112
 * @tparam Underlying The integer type in which objects are packed.
113
 * @tparam OBJS_PER_UNDERLYING The number of objects packed into one `Underlying` integer.
114
 * @return A `std::array` where each element is a mask for one object slot within the `Underlying` type.
115
 * @note This is used by `PauliTermContainer` to avoid repeated shift calculations at runtime when
116
 * accessing packed Pauli data.
117
 */
118
template <typename Underlying, std::size_t OBJS_PER_UNDERLYING>
UNCOV
119
constexpr std::array<Underlying, OBJS_PER_UNDERLYING> compute_mask_lut() {
×
UNCOV
120
        const Underlying bits_per_obj = (sizeof(Underlying) * 8) / OBJS_PER_UNDERLYING;
×
UNCOV
121
        const Underlying mask = compute_mask<Underlying>(bits_per_obj);
×
UNCOV
122
        std::array<Underlying, OBJS_PER_UNDERLYING> ret{};
×
UNCOV
123
        for (Underlying i = 0; i < OBJS_PER_UNDERLYING; ++i) {
×
UNCOV
124
                ret[i] = mask << (i * bits_per_obj);
×
UNCOV
125
        }
×
UNCOV
126
        return ret;
×
UNCOV
127
}
×
128

129
/**
130
 * @brief Creates a mask to select the low bit of every 2-bit pair in an integer.
131
 * @tparam T An unsigned integral type.
132
 * @return A mask of the form `...01010101`.
133
 */
134
template <std::unsigned_integral T>
UNCOV
135
constexpr T create_low_bit_mask() {
×
UNCOV
136
        T mask = 0;
×
UNCOV
137
        for (size_t i = 0; i < sizeof(T) * 8; i += 2) {
×
UNCOV
138
                mask |= (T(1) << i);
×
UNCOV
139
        }
×
UNCOV
140
        return mask;
×
UNCOV
141
}
×
142

143
/**
144
 * @brief Efficiently counts the number of non-zero 2-bit pairs in an unsigned integer.
145
 * @tparam T An unsigned integral type.
146
 * @param input The integer whose bit pairs are to be counted.
147
 * @return The number of 2-bit chunks in `input` that are not `00`.
148
 *
149
 * @note This function is a key optimization for calculating the Pauli weight of a term
150
 * directly from its packed representation. Since each Pauli operator is stored as a 2-bit
151
 * value and the Identity operator is `00`, counting the non-zero pairs is equivalent
152
 * to counting the non-Identity operators. The algorithm works by ORing the low and high
153
 * bits of each pair together, then using `std::popcount` on the result.
154
 */
155
template <std::unsigned_integral T>
156
constexpr int count_nonzero_pairs(T input) {
42✔
157
        static constexpr T low_bits_mask = create_low_bit_mask<T>();
42✔
158
        static constexpr T high_bits_mask = low_bits_mask << 1;
42✔
159

160
        const T low_bits = input & low_bits_mask;
42✔
161
        const T high_bits_shifted = (input & high_bits_mask) >> 1;
42✔
162
        const T result_bits = low_bits | high_bits_shifted;
42✔
163

164
        return std::popcount(result_bits);
42✔
165
}
42✔
166

167
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc