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

vigna / sux-rs / 20054142115

09 Dec 2025 06:25AM UTC coverage: 74.599% (-0.05%) from 74.65%
20054142115

push

github

vigna
Date on change log

4470 of 5992 relevant lines covered (74.6%)

121956486.15 hits per line

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

78.76
/src/func/vbuilder.rs
1
/*
2
 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
3
 *
4
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
5
 */
6

7
#![allow(clippy::type_complexity)]
8
#![allow(clippy::too_many_arguments)]
9

10
use crate::bits::*;
11
use crate::dict::{BitSignedVFunc, SignedVFunc, VFilter};
12
use crate::func::{shard_edge::ShardEdge, *};
13
use crate::traits::BitVecOpsMut;
14
use crate::traits::bit_field_slice::{BitFieldSlice, BitFieldSliceMut, Word};
15
use crate::utils::*;
16
use common_traits::{
17
    CastableInto, DowncastableFrom, DowncastableInto, UnsignedInt, UpcastableFrom, UpcastableInto,
18
};
19
use derivative::Derivative;
20
use derive_setters::*;
21
use dsi_progress_logger::*;
22
use lender::FallibleLending;
23
use log::info;
24
use rand::SeedableRng;
25
use rand::rngs::SmallRng;
26
use rand::{Rng, RngCore};
27
use rayon::iter::ParallelIterator;
28
use rayon::slice::ParallelSlice;
29
use rdst::*;
30
use std::any::TypeId;
31
use std::borrow::{Borrow, Cow};
32
use std::hint::unreachable_unchecked;
33
use std::marker::PhantomData;
34
use std::mem::transmute;
35
use std::ops::{BitXor, BitXorAssign};
36
use std::slice::Iter;
37
use std::sync::Arc;
38
use std::sync::atomic::{AtomicBool, Ordering};
39
use std::time::Instant;
40
use thread_priority::ThreadPriority;
41
use value_traits::slices::{SliceByValue, SliceByValueMut};
42

43
use super::shard_edge::FuseLge3Shards;
44

45
const LOG2_MAX_SHARDS: u32 = 16;
46

47
/// A builder for [`VFunc`] and [`VFilter`].
48
///
49
/// Keys must implement the [`ToSig`] trait, which provides a method to compute
50
/// a signature of the key.
51
///
52
/// There are two construction modes: in core memory (default) and
53
/// [offline](VBuilder::offline); both use a [`SigStore`]. In the first case,
54
/// space will be allocated in core memory for signatures and associated values
55
/// for all keys; in the second case, such information will be stored in a
56
/// number of on-disk buckets.
57
///
58
/// There are several setters: for example, you can set [set the maximum number
59
/// of threads](VBuilder::max_num_threads).
60
///
61
/// Once signatures have been computed, each parallel thread will process a
62
/// shard of the signature/value pairs. For very large key sets shards will be
63
/// significantly smaller than the number of keys, so the memory usage, in
64
/// particular in offline mode, can be significantly reduced. Note that using
65
/// too many threads might actually be harmful due to memory contention.
66
///
67
/// The generic parameters are explained in the [`VFunc`] documentation. You
68
/// have to choose the type of the output values and the backend. The remaining
69
/// parameters have default values that are the same as those of
70
/// [`VFunc`]/[`VFilter`], and some elaboration about them can be found in their
71
/// documentation.
72
///
73
/// All construction methods require to pass one or two [`FallibleRewindableLender`]s
74
/// (keys and possibly values), and the construction might fail and keys might
75
/// be scanned again. The structures in the [`lenders`] module provide easy ways
76
/// to build such lenders, even starting from compressed files of UTF-8 strings.
77
/// The type of the keys of the resulting filter or function will be the type of
78
/// the elements of the [`FallibleRewindableLender`].
79
///
80
/// # Signed Index Functions
81
///
82
/// The methods [`try_build_sig_index`](VBuilder::try_build_sig_index) and
83
/// [`try_build_bit_sig_index`](VBuilder::try_build_bit_sig_index) builds [index
84
/// functions] (i.e., functions mapping elements of a list to their rank) and
85
/// they associate hashes to keys, so the result of the index function can be
86
/// checked. See the documentation of the
87
/// [`signed_vfunc`](crate::dict::signed_vfunc) module for more details.
88
///
89
/// # Examples
90
///
91
/// In this example, we build a function that maps each key to itself using a
92
/// boxed slice of `usize` as a backend (note that this is really wasteful). The
93
/// setter for the expected number of keys is used to optimize the construction.
94
/// We use the [`FromCloneableIntoIterator`] adapter to turn a clonable [`IntoIterator`]
95
/// into a [`FallibleRewindableLender`]. Note that you need the
96
/// [`dsi-progress-logger`](https://crates.io/crates/dsi-progress-logger) crate.
97
///
98
/// Type inference derives the input type (`usize`) from the type of the items
99
/// returned by the first [`FallibleRewindableLender`], and the output type (again,
100
/// `usize`, the first type parameter), from the backend type (`Box<[usize]>`,
101
/// the second type parameter):
102
///
103
/// ```rust
104
/// # use sux::func::VBuilder;
105
/// # use dsi_progress_logger::no_logging;
106
/// # use sux::utils::FromCloneableIntoIterator;
107
/// let builder = VBuilder::<_, Box<[usize]>>::default()
108
///     .expected_num_keys(100);
109
/// let func = builder.try_build_func(
110
///    FromCloneableIntoIterator::new(0..100),
111
///    FromCloneableIntoIterator::new(0..100),
112
///    no_logging![]
113
/// )?;
114
///
115
/// for i in 0..100 {
116
///    assert_eq!(i, func.get(&i));
117
/// }
118
/// # Ok::<(), Box<dyn std::error::Error>>(())
119
/// ```
120
///
121
/// Alternatively we can use the bit-field vector backend, that will use
122
/// ⌈log₂(99)⌉ bits per backend element:
123
///
124
/// ```rust
125
/// # use sux::func::VBuilder;
126
/// # use dsi_progress_logger::no_logging;
127
/// # use sux::utils::FromCloneableIntoIterator;
128
/// # use sux::bits::BitFieldVec;
129
/// let builder = VBuilder::<_, BitFieldVec<usize>>::default()
130
///     .expected_num_keys(100);
131
/// let func = builder.try_build_func(
132
///    FromCloneableIntoIterator::new(0..100),
133
///    FromCloneableIntoIterator::new(0..100),
134
///    no_logging![]
135
/// )?;
136
///
137
/// for i in 0..100 {
138
///    assert_eq!(i, func.get(&i));
139
/// }
140
/// # Ok::<(), Box<dyn std::error::Error>>(())
141
/// ```
142
///
143
/// Since the numbers are small, we can also try to use a fixed-size output:
144
/// type inference takes care of making the second range `0..100` a range of
145
/// `u8`. Note that the type of keys is always `usize`, as it is still inferred
146
/// from the type of the items returned by the first [`FallibleRewindableLender`]:
147
///
148
/// ```rust
149
/// # use sux::func::VBuilder;
150
/// # use dsi_progress_logger::no_logging;
151
/// # use sux::utils::FromCloneableIntoIterator;
152
/// # use sux::bits::BitFieldVec;
153
/// let builder = VBuilder::<_, Box<[u8]>>::default()
154
///     .expected_num_keys(100);
155
/// let func = builder.try_build_func(
156
///    FromCloneableIntoIterator::new(0..100),
157
///    FromCloneableIntoIterator::new(0..100),
158
///    no_logging![]
159
/// )?;
160
///
161
/// for i in 0..100 {
162
///    assert_eq!(i, func.get(&i));
163
/// }
164
/// # Ok::<(), Box<dyn std::error::Error>>(())
165
/// ```
166
///
167
///
168
/// We now try to build a fast 8-bit filter for the same key set, using a boxed
169
/// slice of `u8` as a backend (this is not wasteful, as the filter uses 8-bit
170
/// hashes):
171
///
172
/// ```rust
173
/// # use sux::func::VBuilder;
174
/// # use dsi_progress_logger::no_logging;
175
/// # use sux::utils::FromCloneableIntoIterator;
176
/// let builder = VBuilder::<_, Box<[u8]>>::default()
177
///     .expected_num_keys(100);
178
/// let func = builder.try_build_filter(
179
///    FromCloneableIntoIterator::new(0..100),
180
///    no_logging![]
181
/// )?;
182
///
183
/// for i in 0..100 {
184
///    assert!(func[i]);
185
/// }
186
/// # Ok::<(), Box<dyn std::error::Error>>(())
187
/// ```
188
///
189
/// Since the keys are very few, we can switch to 64-bit signatures, and no
190
/// shards, which will yield faster queries:
191
///
192
/// ```rust
193
/// # use sux::func::VBuilder;
194
/// # use sux::func::shard_edge::FuseLge3NoShards;
195
/// # use dsi_progress_logger::no_logging;
196
/// # use sux::utils::FromCloneableIntoIterator;
197
/// let builder = VBuilder::<_, Box<[u8]>, [u64; 1], FuseLge3NoShards>::default()
198
///     .expected_num_keys(100);
199
/// let func = builder.try_build_filter(
200
///    FromCloneableIntoIterator::new(0..100),
201
///    no_logging![]
202
/// )?;
203
///
204
/// for i in 0..100 {
205
///    assert!(func[i]);
206
/// }
207
/// # Ok::<(), Box<dyn std::error::Error>>(())
208
/// ```
209

210
#[derive(Setters, Debug, Derivative)]
211
#[derivative(Default)]
212
#[setters(generate = false)]
213
pub struct VBuilder<
214
    W: Word + BinSafe,
215
    D: BitFieldSlice<W> + Send + Sync = Box<[W]>,
216
    S = [u64; 2],
217
    E: ShardEdge<S, 3> = FuseLge3Shards,
218
> {
219
    /// The expected number of keys.
220
    ///
221
    /// While this setter is optional, setting this value to a reasonable bound
222
    /// on the actual number of keys will significantly speed up the
223
    /// construction.
224
    #[setters(generate = true, strip_option)]
225
    #[derivative(Default(value = "None"))]
226
    expected_num_keys: Option<usize>,
227

228
    /// The maximum number of parallel threads to use. The default is 8.
229
    #[setters(generate = true)]
230
    #[derivative(Default(value = "8"))]
231
    max_num_threads: usize,
232

233
    /// Use disk-based buckets to reduce core memory usage at construction time.
234
    #[setters(generate = true)]
235
    offline: bool,
236

237
    /// Check for duplicated signatures. This is not necessary in general,
238
    /// but if you suspect you might be feeding duplicate keys, you can
239
    /// enable this check.
240
    #[setters(generate = true)]
241
    check_dups: bool,
242

243
    /// Use always the low-memory peel-by-signature algorithm (true) or the
244
    /// high-memory peel-by-index algorithm (false). The former is slightly
245
    /// slower, but it uses much less memory. Normally [`VBuilder`] uses
246
    /// high-mem and switches to low-mem if there are more
247
    /// than two threads and more than four shards.
248
    #[setters(generate = true, strip_option)]
249
    #[derivative(Default(value = "None"))]
250
    low_mem: Option<bool>,
251

252
    /// The seed for the random number generator.
253
    #[setters(generate = true)]
254
    seed: u64,
255

256
    /// The base-2 logarithm of buckets of the [`SigStore`]. The default is 8.
257
    /// This value is automatically overridden, even if set, if you provide an
258
    /// [expected number of keys](VBuilder::expected_num_keys).
259
    #[setters(generate = true, strip_option)]
260
    #[derivative(Default(value = "8"))]
261
    log2_buckets: u32,
262

263
    /// The target relative space loss due to [ε-cost
264
    /// sharding](https://doi.org/10.4230/LIPIcs.ESA.2019.38).s
265
    ///
266
    /// The default is 0.001. Setting a larger target, for example, 0.01, will
267
    /// increase the space overhead due to sharding, but will provide in general
268
    /// finer shards. This might not always happen, however, because the ε-cost
269
    /// bound is just one of the bounds concurring in limiting the amount of
270
    /// sharding for a specific [`ShardEdge`]. For example, increasing the
271
    /// target to 0.01 will provide very fine sharding using `Mwhc3Shards`
272
    /// shard/edge logic, activated by the `mwhc` feature, but will affect only
273
    /// slightly [`FuseLge3Shards`] or
274
    /// [`FuseLge3FullSigs`](crate::func::shard_edge::FuseLge3FullSigs).
275
    #[setters(generate = true, strip_option)]
276
    #[derivative(Default(value = "0.001"))]
277
    eps: f64,
278

279
    /// The bit width of the maximum value.
280
    bit_width: usize,
281
    /// The edge generator.
282
    shard_edge: E,
283
    /// The number of keys.
284
    num_keys: usize,
285
    /// The ratio between the number of vertices and the number of edges
286
    /// (i.e., between the number of variables and the number of equations).
287
    c: f64,
288
    /// Whether we should use [lazy Gaussian
289
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517).
290
    lge: bool,
291
    /// The number of threads to use.
292
    num_threads: usize,
293
    /// Fast-stop for failed attempts.
294
    failed: AtomicBool,
295
    #[doc(hidden)]
296
    _marker_v: PhantomData<(W, D, S)>,
297
}
298

299
/// Fatal build errors.
300
#[derive(thiserror::Error, Debug)]
301
pub enum BuildError {
302
    #[error("Duplicate key")]
303
    /// A duplicate key was detected.
304
    DuplicateKey,
305
    #[error("Duplicate local signatures: use full signatures")]
306
    /// A duplicate key was detected.
307
    DuplicateLocalSignatures,
308
    #[error("Value too large for specified bit size")]
309
    /// A value is too large for the specified bit size.
310
    ValueTooLarge,
311
}
312

313
#[derive(thiserror::Error, Debug)]
314
/// Transient error during the build, leading to
315
/// trying with a different seed.
316
pub enum SolveError {
317
    #[error("Duplicate signature")]
318
    /// A duplicate signature was detected.
319
    DuplicateSignature,
320
    #[error("Duplicate local signature")]
321
    /// A duplicate local signature was detected.
322
    DuplicateLocalSignature,
323
    #[error("Max shard too big")]
324
    /// The maximum shard is too big relatively to the average shard.
325
    MaxShardTooBig,
326
    #[error("Unsolvable shard")]
327
    /// A shard cannot be solved.
328
    UnsolvableShard,
329
}
330

331
/// The result of a peeling procedure.
332
enum PeelResult<
333
    'a,
334
    W: Word + BinSafe + Send + Sync,
335
    D: BitFieldSlice<W> + BitFieldSliceMut<W> + Send + Sync + 'a,
336
    S: Sig + BinSafe,
337
    E: ShardEdge<S, 3>,
338
    V: BinSafe,
339
> {
340
    Complete(),
341
    Partial {
342
        /// The shard index.
343
        shard_index: usize,
344
        /// The shard.
345
        shard: Arc<Vec<SigVal<S, V>>>,
346
        /// The data.
347
        data: ShardData<'a, D>,
348
        /// The double stack whose upper stack contains the peeled edges
349
        /// (possibly represented by the vertex from which they have been
350
        /// peeled).
351
        double_stack: DoubleStack<E::Vertex>,
352
        /// The sides stack.
353
        sides_stack: Vec<u8>,
354
        _marker: PhantomData<W>,
355
    },
356
}
357

358
/// A graph represented compactly.
359
///
360
/// Each (*k*-hyper)edge is a set of *k* vertices (by construction fuse graphs
361
/// do not have degenerate edges), but we represent it internally as a vector.
362
/// We call *side* the position of a vertex in the edge.
363
///
364
/// For each vertex we store information about the edges incident to the vertex
365
/// and the sides of the vertex in such edges. While technically not necessary
366
/// to perform peeling, the knowledge of the sides speeds up the peeling visit
367
/// by reducing the number of tests that are necessary to update the degrees
368
/// once the edge is peeled (see the `peel_by_*` methods). For the same reason
369
/// it also speeds up assignment.
370
///
371
/// Depending on the peeling method (by signature or by index), the graph will
372
/// store edge indices or signature/value pairs (the generic parameter `X`).
373
///
374
/// Edge information is packed together using Djamal's XOR trick (see
375
/// [“Cache-Oblivious Peeling of Random
376
/// Hypergraphs”](https://doi.org/10.1109/DCC.2014.48)): since during the
377
/// peeling b visit we need to know the content of the list only when a single
378
/// edge index is present, we can XOR together all the edge information.
379
///
380
/// We use a single byte to store the degree (six upper bits) and the XOR of the
381
/// sides (lower two bits). The degree can be stored with a small number of bits
382
/// because the graph is random, so the maximum degree is *O*(log log *n*).
383
/// In any case, the Boolean field `overflow` will become `true` in case of
384
/// overflow.
385
///
386
/// When we peel an edge, we just [zero the degree](Self::zero), leaving the
387
/// edge information and the side in place for further processing later.
388
struct XorGraph<X: Copy + Default + BitXor + BitXorAssign> {
389
    edges: Box<[X]>,
390
    degrees_sides: Box<[u8]>,
391
    overflow: bool,
392
}
393

394
impl<X: BitXor + BitXorAssign + Default + Copy> XorGraph<X> {
395
    pub fn new(n: usize) -> XorGraph<X> {
201✔
396
        XorGraph {
397
            edges: vec![X::default(); n].into(),
1,005✔
398
            degrees_sides: vec![0; n].into(),
402✔
399
            overflow: false,
400
        }
401
    }
402

403
    #[inline(always)]
404
    pub fn add(&mut self, v: usize, x: X, side: usize) {
69,284,100✔
405
        debug_assert!(side < 3);
138,568,200✔
406
        let (degree_size, overflow) = self.degrees_sides[v].overflowing_add(4);
207,852,300✔
407
        self.degrees_sides[v] = degree_size;
69,284,100✔
408
        self.overflow |= overflow;
69,284,100✔
409
        self.degrees_sides[v] ^= side as u8;
69,284,100✔
410
        self.edges[v] ^= x;
69,284,100✔
411
    }
412

413
    #[inline(always)]
414
    pub fn remove(&mut self, v: usize, x: X, side: usize) {
44,895,524✔
415
        debug_assert!(side < 3);
89,791,048✔
416
        self.degrees_sides[v] -= 4;
44,895,524✔
417
        self.degrees_sides[v] ^= side as u8;
44,895,524✔
418
        self.edges[v] ^= x;
44,895,524✔
419
    }
420

421
    #[inline(always)]
422
    pub fn zero(&mut self, v: usize) {
22,447,762✔
423
        self.degrees_sides[v] &= 0b11;
22,447,762✔
424
    }
425

426
    #[inline(always)]
427
    pub fn edge_and_side(&self, v: usize) -> (X, usize) {
32,447,762✔
428
        debug_assert!(self.degree(v) < 2);
97,343,286✔
429
        (self.edges[v] as _, (self.degrees_sides[v] & 0b11) as _)
64,895,524✔
430
    }
431

432
    #[inline(always)]
433
    pub fn degree(&self, v: usize) -> u8 {
101,300,676✔
434
        self.degrees_sides[v] >> 2
101,300,676✔
435
    }
436

437
    pub fn degrees(
201✔
438
        &self,
439
    ) -> std::iter::Map<std::iter::Copied<std::slice::Iter<'_, u8>>, fn(u8) -> u8> {
440
        self.degrees_sides.iter().copied().map(|d| d >> 2)
26,109,944✔
441
    }
442
}
443

444
/// A preallocated stack implementation that avoids the expensive (even if
445
/// rarely taken) branch of the `Vec` implementation in which memory is
446
/// reallocated. Note that using [`Vec::with_capacity`] is not enough, because
447
/// for the CPU the branch is still there.
448
struct FastStack<X: Copy + Default> {
449
    stack: Vec<X>,
450
    top: usize,
451
}
452

453
impl<X: Copy + Default> FastStack<X> {
454
    pub fn new(n: usize) -> FastStack<X> {
24✔
455
        FastStack {
456
            stack: vec![X::default(); n],
48✔
457
            top: 0,
458
        }
459
    }
460

461
    pub fn push(&mut self, x: X) {
24,000,000✔
462
        debug_assert!(self.top < self.stack.len());
72,000,000✔
463
        self.stack[self.top] = x;
24,000,000✔
464
        self.top += 1;
24,000,000✔
465
    }
466

467
    pub fn len(&self) -> usize {
12✔
468
        self.top
12✔
469
    }
470

471
    pub fn iter(&self) -> std::slice::Iter<'_, X> {
24✔
472
        self.stack[..self.top].iter()
48✔
473
    }
474
}
475

476
/// Two stacks in the same vector.
477
///
478
/// This struct implements a pair of stacks sharing the same memory. The lower
479
/// stack grows from the beginning of the vector, the upper stack grows from the
480
/// end of the vector. Since we use the lower stack for the visit and the upper
481
/// stack for peeled edges (possibly represented by the vertex from which they
482
/// have been peeled), the sum of the lengths of the two stacks cannot exceed
483
/// the length of the vector.
484
#[derive(Debug)]
485
struct DoubleStack<V> {
486
    stack: Vec<V>,
487
    lower: usize,
488
    upper: usize,
489
}
490

491
impl<V: Default + Copy> DoubleStack<V> {
492
    fn new(n: usize) -> DoubleStack<V> {
189✔
493
        DoubleStack {
494
            stack: vec![V::default(); n],
567✔
495
            lower: 0,
496
            upper: n,
497
        }
498
    }
499
}
500

501
impl<V: Copy> DoubleStack<V> {
502
    #[inline(always)]
503
    fn push_lower(&mut self, v: V) {
10,948,160✔
504
        debug_assert!(self.lower < self.upper);
21,896,320✔
505
        self.stack[self.lower] = v;
10,948,160✔
506
        self.lower += 1;
10,948,160✔
507
    }
508

509
    #[inline(always)]
510
    fn push_upper(&mut self, v: V) {
10,447,762✔
511
        debug_assert!(self.lower < self.upper);
20,895,524✔
512
        self.upper -= 1;
10,447,762✔
513
        self.stack[self.upper] = v;
10,447,762✔
514
    }
515

516
    #[inline(always)]
517
    fn pop_lower(&mut self) -> Option<V> {
10,948,349✔
518
        if self.lower == 0 {
10,948,349✔
519
            None
189✔
520
        } else {
521
            self.lower -= 1;
10,948,160✔
522
            Some(self.stack[self.lower])
10,948,160✔
523
        }
524
    }
525

526
    fn upper_len(&self) -> usize {
358✔
527
        self.stack.len() - self.upper
716✔
528
    }
529

530
    fn iter_upper(&self) -> Iter<'_, V> {
216✔
531
        self.stack[self.upper..].iter()
432✔
532
    }
533
}
534

535
/// An iterator over segments of data associated with each shard.
536
type ShardDataIter<'a, D> = <D as SliceByValueMut>::ChunksMut<'a>;
537
/// A segment of data associated with a specific shard.
538
type ShardData<'a, D> = <ShardDataIter<'a, D> as Iterator>::Item;
539

540
impl<W: Word + BinSafe, S: Sig + Send + Sync, E: ShardEdge<S, 3>> VBuilder<W, Box<[W]>, S, E>
541
where
542
    u128: UpcastableFrom<W>,
543
    SigVal<S, W>: RadixKey,
544
    SigVal<E::LocalSig, W>: BitXor + BitXorAssign,
545
    Box<[W]>: BitFieldSliceMut<W> + BitFieldSlice<W>,
546
{
547
    /// Builds a new function using a `Box<[W]>` to store values.
548
    ///
549
    /// Since values are stored in a boxed slice access is particularly fast, but
550
    /// the bit width of the output of the function will be exactly the bit width of
551
    /// the unsigned type `W`.
552
    pub fn try_build_func<T: ?Sized + ToSig<S> + std::fmt::Debug, B: ?Sized + Borrow<T>>(
×
553
        mut self,
554
        keys: impl FallibleRewindableLender<
555
            RewindError: std::error::Error + Send + Sync + 'static,
556
            Error: std::error::Error + Send + Sync + 'static,
557
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
558
        values: impl FallibleRewindableLender<
559
            RewindError: std::error::Error + Send + Sync + 'static,
560
            Error: std::error::Error + Send + Sync + 'static,
561
        > + for<'lend> FallibleLending<'lend, Lend = &'lend W>,
562
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
563
    ) -> anyhow::Result<VFunc<T, W, Box<[W]>, S, E>>
564
    where
565
        for<'a> <<Box<[W]> as SliceByValueMut>::ChunksMut<'a> as Iterator>::Item:
566
            BitFieldSliceMut<W>,
567
        for<'a> ShardDataIter<'a, Box<[W]>>: Send,
568
        for<'a> ShardData<'a, Box<[W]>>: Send,
569
    {
570
        let get_val = |_shard_edge: &E, sig_val: SigVal<E::LocalSig, W>| sig_val.val;
×
571
        let new_data = |_bit_width: usize, len: usize| vec![W::ZERO; len].into();
×
572
        Ok(self
×
573
            .build_loop(keys, values, None, &get_val, new_data, false, pl)?
×
574
            .0)
×
575
    }
576
}
577

578
impl<W: Word + BinSafe + DowncastableFrom<u64>, S: Sig + Send + Sync, E: ShardEdge<S, 3>>
579
    VBuilder<W, Box<[W]>, S, E>
580
where
581
    u128: UpcastableFrom<W>,
582
    SigVal<S, EmptyVal>: RadixKey,
583
    SigVal<E::LocalSig, EmptyVal>: BitXor + BitXorAssign,
584
    Box<[W]>: BitFieldSliceMut<W> + BitFieldSlice<W>,
585
{
586
    /// Builds a new filter using a `Box<[W]>` to store values.
587
    ///
588
    /// Since values are stored in a boxed slice access is particularly fast, but
589
    /// the number of bits of the hashes will be  exactly the bit width of the
590
    /// unsigned type `W`.
591
    pub fn try_build_filter<T: ?Sized + ToSig<S> + std::fmt::Debug, B: ?Sized + Borrow<T>>(
36✔
592
        mut self,
593
        keys: impl FallibleRewindableLender<
594
            RewindError: std::error::Error + Send + Sync + 'static,
595
            Error: std::error::Error + Send + Sync + 'static,
596
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
597
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
598
    ) -> anyhow::Result<VFilter<W, VFunc<T, W, Box<[W]>, S, E>>>
599
    where
600
        for<'a> <<Box<[W]> as SliceByValueMut>::ChunksMut<'a> as Iterator>::Item:
601
            BitFieldSliceMut<W>,
602
        for<'a> ShardDataIter<'a, Box<[W]>>: Send,
603
        for<'a> ShardData<'a, Box<[W]>>: Send,
604
    {
605
        let filter_mask = W::MAX;
72✔
606
        let get_val = |shard_edge: &E, sig_val: SigVal<E::LocalSig, EmptyVal>| {
12,630,474✔
607
            mix64(shard_edge.edge_hash(sig_val.sig)).downcast()
63,152,190✔
608
        };
609
        let new_data = |_bit_width: usize, len: usize| vec![W::ZERO; len].into();
378✔
610

611
        Ok(VFilter {
×
612
            func: self
36✔
613
                .build_loop(
36✔
614
                    keys,
36✔
615
                    FromCloneableIntoIterator::from(itertools::repeat_n(
72✔
616
                        EmptyVal::default(),
36✔
617
                        usize::MAX,
×
618
                    )),
619
                    Some(W::BITS),
36✔
620
                    &get_val,
36✔
621
                    new_data,
36✔
622
                    false,
×
623
                    pl,
36✔
624
                )?
625
                .0,
×
626
            filter_mask,
36✔
627
            hash_bits: W::BITS as u32,
36✔
628
        })
629
    }
630
}
631

632
impl<W: Word + BinSafe, S: Sig + Send + Sync, E: ShardEdge<S, 3>> VBuilder<W, BitFieldVec<W>, S, E>
633
where
634
    u128: UpcastableFrom<W>,
635
    SigVal<S, W>: RadixKey,
636
    SigVal<E::LocalSig, W>: BitXor + BitXorAssign,
637
{
638
    fn _try_build_func<T: ?Sized + ToSig<S> + std::fmt::Debug, B: ?Sized + Borrow<T>>(
29✔
639
        mut self,
640
        keys: impl FallibleRewindableLender<
641
            RewindError: std::error::Error + Send + Sync + 'static,
642
            Error: std::error::Error + Send + Sync + 'static,
643
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
644
        values: impl FallibleRewindableLender<
645
            RewindError: std::error::Error + Send + Sync + 'static,
646
            Error: std::error::Error + Send + Sync + 'static,
647
        > + for<'lend> FallibleLending<'lend, Lend = &'lend W>,
648
        keep_store: bool,
649
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
650
    ) -> anyhow::Result<(
651
        VFunc<T, W, BitFieldVec<W>, S, E>,
652
        Option<AnyShardStore<S, W>>,
653
    )> {
654
        let get_val = |_shard_edge: &E, sig_val: SigVal<E::LocalSig, W>| sig_val.val;
29✔
655
        let new_data = |bit_width, len| BitFieldVec::<W>::new_unaligned(bit_width, len);
317✔
656

657
        self.build_loop(keys, values, None, &get_val, new_data, keep_store, pl)
261✔
658
    }
659

660
    /// Builds a new function using a [bit-field vector](BitFieldVec) on words of
661
    /// the unsigned type `W` to store values.
662
    ///
663
    /// Since values are stored in a bit-field vector access will be slower than
664
    /// when using a boxed slice, but the bit width of stored values will be the
665
    /// minimum necessary. It must be in any case at most the bit width of `W`.
666
    ///
667
    /// Typically `W` will be `usize` or `u64`.
668
    pub fn try_build_func<T: ?Sized + ToSig<S> + std::fmt::Debug, B: ?Sized + Borrow<T>>(
27✔
669
        self,
670
        keys: impl FallibleRewindableLender<
671
            RewindError: std::error::Error + Send + Sync + 'static,
672
            Error: std::error::Error + Send + Sync + 'static,
673
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
674
        values: impl FallibleRewindableLender<
675
            RewindError: std::error::Error + Send + Sync + 'static,
676
            Error: std::error::Error + Send + Sync + 'static,
677
        > + for<'lend> FallibleLending<'lend, Lend = &'lend W>,
678
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
679
    ) -> anyhow::Result<VFunc<T, W, BitFieldVec<W>, S, E>> {
680
        self._try_build_func(keys, values, false, pl)
135✔
681
            .map(|res| res.0)
27✔
682
    }
683
}
684

685
impl<S: Sig + Send + Sync, E: ShardEdge<S, 3>> VBuilder<usize, BitFieldVec<usize>, S, E>
686
where
687
    SigVal<S, usize>: RadixKey,
688
    SigVal<E::LocalSig, usize>: BitXor + BitXorAssign,
689
{
690
    /// Builds a _bit-signed index function_, that is, a function that maps each
691
    /// key to its position in the input sequence and verifies its out against a
692
    /// [`BitFieldVec`] containing hashes of the keys.
693
    ///
694
    /// The hashes contain `hash_width` bits, providing a false-positive error
695
    /// rate (i.e., keys that are not part of the function definition, but for
696
    /// which this property is not detected) of 1/2<sup>-`hash_width`</sup>.
697
    ///
698
    /// This type of signed function offers more resolution than [`SignedVFunc`]
699
    /// in choosing the hash size, but testing signatures will be slower.
700
    pub fn try_build_bit_sig_index<
1✔
701
        T: ?Sized + ToSig<S> + std::fmt::Debug,
702
        B: ?Sized + Borrow<T>,
703
    >(
704
        self,
705
        keys: impl FallibleRewindableLender<
706
            RewindError: std::error::Error + Send + Sync + 'static,
707
            Error: std::error::Error + Send + Sync + 'static,
708
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
709
        hash_width: usize,
710
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
711
    ) -> anyhow::Result<BitSignedVFunc<VFunc<T, usize, BitFieldVec<usize>, S, E>, BitFieldVec>>
712
    {
713
        assert!(hash_width > 0);
2✔
714
        assert!(hash_width <= 64);
2✔
715

716
        let (func, store) =
2✔
717
            self._try_build_func(keys, FromCloneableIntoIterator::from(0..), true, pl)?;
6✔
718

719
        let num_keys = func.num_keys;
2✔
720
        let shard_edge = &func.shard_edge;
2✔
721
        let hash_mask = if hash_width == 64 {
2✔
722
            u64::MAX
×
723
        } else {
724
            (1u64 << hash_width) - 1
1✔
725
        };
726

727
        // Create the signature vector
728
        let mut hashes = BitFieldVec::<usize>::new_unaligned(hash_width, num_keys);
4✔
729

730
        // Enumerate the store and extract signatures using the same method as filters
731
        pl.item_name("hash");
2✔
732
        pl.expected_updates(Some(num_keys));
3✔
733
        pl.start("Storing hashes...");
2✔
734

735
        // We passed keep_store = true
736
        match store.unwrap() {
1✔
737
            AnyShardStore::Online(mut shard_store) => {
1✔
738
                for shard in shard_store.iter() {
3✔
739
                    for sig_val in shard.iter() {
2,001✔
740
                        let pos = sig_val.val;
3,000✔
741
                        let local_sig = shard_edge.local_sig(sig_val.sig);
5,000✔
742
                        let hash = (mix64(shard_edge.edge_hash(local_sig)) & hash_mask) as usize;
6,000✔
743
                        hashes.set_value(pos, hash);
5,000✔
744
                        pl.light_update();
1,000✔
745
                    }
746
                }
747
            }
748
            AnyShardStore::Offline(mut shard_store) => {
×
749
                for shard in shard_store.iter() {
×
750
                    for sig_val in shard.iter() {
×
751
                        let pos = sig_val.val;
×
752
                        let local_sig = shard_edge.local_sig(sig_val.sig);
×
753
                        let hash = (mix64(shard_edge.edge_hash(local_sig)) & hash_mask) as usize;
×
754
                        hashes.set_value(pos, hash);
×
755
                        pl.light_update();
×
756
                    }
757
                }
758
            }
759
        }
760

761
        pl.done();
2✔
762

763
        Ok(BitSignedVFunc {
1✔
764
            func,
2✔
765
            hashes,
1✔
766
            hash_mask,
1✔
767
        })
768
    }
769

770
    /// Builds a _signed index function_, that is, a function that maps each key
771
    /// to its position in the input sequence and verifies its out against a
772
    /// boxed slice containing hashes of the keys.
773
    ///
774
    /// The hashes are of type `H`, and the false-positive error rate (i.e.,
775
    /// keys that are not part of the function definition, but for which this
776
    /// property is not detected) of 1/2<sup>-`H::BITS`</sup>.
777
    ///
778
    /// This type of signed function offers more speed than [`BitSignedVFunc`],
779
    /// but you have less resolution in the choice of the hash size.
780
    pub fn try_build_sig_index<
1✔
781
        T: ?Sized + ToSig<S> + std::fmt::Debug,
782
        B: ?Sized + Borrow<T>,
783
        H: Word,
784
    >(
785
        self,
786
        keys: impl FallibleRewindableLender<
787
            RewindError: std::error::Error + Send + Sync + 'static,
788
            Error: std::error::Error + Send + Sync + 'static,
789
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
790
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
791
    ) -> anyhow::Result<SignedVFunc<VFunc<T, usize, BitFieldVec<usize>, S, E>, Box<[H]>>>
792
    where
793
        u64: CastableInto<H>,
794
    {
795
        let (func, store) =
2✔
796
            self._try_build_func(keys, FromCloneableIntoIterator::from(0..), true, pl)?;
6✔
797

798
        let num_keys = func.num_keys;
2✔
799
        let shard_edge = &func.shard_edge;
2✔
800

801
        // Create the hash vector
802
        let mut hashes = vec![H::ZERO; num_keys].into_boxed_slice();
4✔
803

804
        // Enumerate the store and extract hashes using the same method as filters
805
        pl.item_name("hash");
2✔
806
        pl.expected_updates(Some(num_keys));
3✔
807
        pl.start("Storing hashes...");
2✔
808

809
        match store.expect("Store should be present when keep_store is true") {
2✔
810
            AnyShardStore::Online(mut shard_store) => {
1✔
811
                for shard in shard_store.iter() {
3✔
812
                    for sig_val in shard.iter() {
2,001✔
813
                        let pos = sig_val.val;
3,000✔
814
                        let local_sig = shard_edge.local_sig(sig_val.sig);
5,000✔
815
                        let hash = mix64(shard_edge.edge_hash(local_sig)).cast();
7,000✔
816
                        hashes.set_value(pos, hash);
5,000✔
817
                        pl.light_update();
1,000✔
818
                    }
819
                }
820
            }
821
            AnyShardStore::Offline(mut shard_store) => {
×
822
                for shard in shard_store.iter() {
×
823
                    for sig_val in shard.iter() {
×
824
                        let pos = sig_val.val;
×
825
                        let local_sig = shard_edge.local_sig(sig_val.sig);
×
826
                        let hash = mix64(shard_edge.edge_hash(local_sig)).cast();
×
827
                        hashes.set_value(pos, hash);
×
828
                        pl.light_update();
×
829
                    }
830
                }
831
            }
832
        }
833

834
        pl.done();
2✔
835

836
        Ok(SignedVFunc { func, hashes })
1✔
837
    }
838
}
839

840
impl<W: Word + BinSafe + DowncastableFrom<u64>, S: Sig + Send + Sync, E: ShardEdge<S, 3>>
841
    VBuilder<W, BitFieldVec<W>, S, E>
842
where
843
    u128: UpcastableFrom<W>,
844
    SigVal<S, EmptyVal>: RadixKey,
845
    SigVal<E::LocalSig, EmptyVal>: BitXor + BitXorAssign,
846
{
847
    /// Builds a new filter using a [bit-field vector](BitFieldVec) on words of the
848
    /// unsigned type `W` to store values.
849
    ///
850
    /// Since values are stored in a bit-field vector access will be slower than
851
    /// when using a boxed slice, but the number of bits of the hashes can be set at
852
    /// will. It must be in any case at most the bit width of `W`.
853
    ///
854
    /// Typically `W` will be `usize` or `u64`.
855
    pub fn try_build_filter<T: ?Sized + ToSig<S> + std::fmt::Debug, B: ?Sized + Borrow<T>>(
×
856
        mut self,
857
        keys: impl FallibleRewindableLender<
858
            RewindError: std::error::Error + Send + Sync + 'static,
859
            Error: std::error::Error + Send + Sync + 'static,
860
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
861
        filter_bits: usize,
862
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
863
    ) -> anyhow::Result<VFilter<W, VFunc<T, W, BitFieldVec<W>, S, E>>> {
864
        assert!(filter_bits > 0);
×
865
        assert!(filter_bits <= W::BITS);
×
866
        let filter_mask = W::MAX >> (W::BITS - filter_bits);
×
867
        let get_val = |shard_edge: &E, sig_val: SigVal<E::LocalSig, EmptyVal>| {
×
868
            <W as DowncastableFrom<u64>>::downcast_from(mix64(shard_edge.edge_hash(sig_val.sig)))
×
869
                & filter_mask
×
870
        };
871
        let new_data = |bit_width, len| BitFieldVec::<W>::new_unaligned(bit_width, len);
×
872

873
        Ok(VFilter {
×
874
            func: self
×
875
                .build_loop(
×
876
                    keys,
×
877
                    FromCloneableIntoIterator::from(itertools::repeat_n(
×
878
                        EmptyVal::default(),
×
879
                        usize::MAX,
×
880
                    )),
881
                    Some(filter_bits),
×
882
                    &get_val,
×
883
                    new_data,
×
884
                    false,
×
885
                    pl,
×
886
                )?
887
                .0,
×
888
            filter_mask,
×
889
            hash_bits: filter_bits as _,
×
890
        })
891
    }
892
}
893

894
impl<
895
    W: Word + BinSafe,
896
    D: BitFieldSlice<W>
897
        + for<'a> BitFieldSliceMut<W, ChunksMut<'a>: Iterator<Item: BitFieldSliceMut<W>>>
898
        + Send
899
        + Sync,
900
    S: Sig + Send + Sync,
901
    E: ShardEdge<S, 3>,
902
> VBuilder<W, D, S, E>
903
{
904
    /// Builds and returns a new function with given keys and values.
905
    ///
906
    /// This method can build functions based both on vectors and on bit-field
907
    /// vectors. The necessary abstraction is provided by the
908
    /// `new_data(bit_width, len)` function, which is called to create the data
909
    /// structure to store the values.
910
    ///
911
    /// When `V` is [`EmptyVal`], the this method builds a function supporting a
912
    /// filter by mapping each key to a mix of its local signature. The
913
    /// necessary abstraction is provided by the `get_val` function, which is
914
    /// called to extract the value from the signature/value pair; in the case
915
    /// of functions it returns the value stored in the signature/value pair,
916
    /// and in the case of filters it returns the hash associated with the
917
    /// signature.
918
    ///
919
    /// If `keep_store` is true, the shard store is returned wrapped in
920
    /// [`AnyShardStore`]; otherwise, `None` is returned and `into_iter()` is
921
    /// used for efficiency.
922
    fn build_loop<
65✔
923
        T: ?Sized + ToSig<S> + std::fmt::Debug,
924
        B: ?Sized + Borrow<T>,
925
        V: BinSafe + Default + Send + Sync + Ord + UpcastableInto<u128>,
926
    >(
927
        &mut self,
928
        mut keys: impl FallibleRewindableLender<
929
            RewindError: std::error::Error + Send + Sync + 'static,
930
            Error: std::error::Error + Send + Sync + 'static,
931
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>,
932
        mut values: impl FallibleRewindableLender<
933
            RewindError: std::error::Error + Send + Sync + 'static,
934
            Error: std::error::Error + Send + Sync + 'static,
935
        > + for<'lend> FallibleLending<'lend, Lend = &'lend V>,
936
        bit_width: Option<usize>,
937
        get_val: &(impl Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync),
938
        new_data: fn(usize, usize) -> D,
939
        keep_store: bool,
940
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
941
    ) -> anyhow::Result<(VFunc<T, W, D, S, E>, Option<AnyShardStore<S, V>>)>
942
    where
943
        u128: UpcastableFrom<W>,
944
        SigVal<S, V>: RadixKey,
945
        SigVal<E::LocalSig, V>: BitXor + BitXorAssign,
946
        for<'a> ShardDataIter<'a, D>: Send,
947
        for<'a> <ShardDataIter<'a, D> as Iterator>::Item: Send,
948
    {
949
        let mut dup_count = 0;
130✔
950
        let mut local_dup_count = 0;
130✔
951
        let mut prng = SmallRng::seed_from_u64(self.seed);
195✔
952

953
        if let Some(expected_num_keys) = self.expected_num_keys {
173✔
954
            self.shard_edge.set_up_shards(expected_num_keys, self.eps);
270✔
955
            self.log2_buckets = self.shard_edge.shard_high_bits();
54✔
956
        }
957

958
        pl.info(format_args!("Using 2^{} buckets", self.log2_buckets));
195✔
959

960
        // Loop until success or duplicate detection
961
        loop {
×
962
            let seed = prng.random();
630✔
963

964
            // We need separate branches for offline/online because they return
965
            // different ShardStore types that must be wrapped in AnyShardStore
966
            let result = if self.offline {
420✔
967
                self.try_seed(
144✔
968
                    seed,
72✔
969
                    sig_store::new_offline::<S, V>(
72✔
970
                        self.log2_buckets,
72✔
971
                        LOG2_MAX_SHARDS,
×
972
                        self.expected_num_keys,
72✔
973
                    )?,
974
                    &mut keys,
72✔
975
                    &mut values,
72✔
976
                    bit_width,
72✔
977
                    get_val,
72✔
978
                    new_data,
72✔
979
                    keep_store,
72✔
980
                    pl,
72✔
981
                )
982
                .map(|(func, store)| (func, store.map(AnyShardStore::Offline)))
132✔
983
            } else {
984
                self.try_seed(
276✔
985
                    seed,
138✔
986
                    sig_store::new_online::<S, V>(
138✔
987
                        self.log2_buckets,
138✔
988
                        LOG2_MAX_SHARDS,
×
989
                        self.expected_num_keys,
138✔
990
                    )?,
991
                    &mut keys,
138✔
992
                    &mut values,
138✔
993
                    bit_width,
138✔
994
                    get_val,
138✔
995
                    new_data,
138✔
996
                    keep_store,
138✔
997
                    pl,
138✔
998
                )
999
                .map(|(func, store)| (func, store.map(AnyShardStore::Online)))
270✔
1000
            };
1001

1002
            match result {
210✔
1003
                Ok((func, store)) => {
128✔
1004
                    return Ok((func, store));
64✔
1005
                }
1006
                Err(error) => {
146✔
1007
                    match error.downcast::<SolveError>() {
146✔
1008
                        Ok(vfunc_error) => match vfunc_error {
292✔
1009
                            // Let's try another seed, but just a few times--most likely,
1010
                            // duplicate keys
1011
                            SolveError::DuplicateSignature => {
×
1012
                                if dup_count >= 3 {
4✔
1013
                                    pl.error(format_args!("Duplicate keys (duplicate 128-bit signatures with four different seeds)"));
3✔
1014
                                    return Err(BuildError::DuplicateKey.into());
1✔
1015
                                }
1016
                                pl.warn(format_args!(
9✔
1017
                                "Duplicate 128-bit signature, trying again with a different seed..."
×
1018
                                ));
1019
                                dup_count += 1;
3✔
1020
                            }
1021
                            // Let's try another seed, but just a few times
1022
                            SolveError::DuplicateLocalSignature => {
×
1023
                                if local_dup_count >= 2 {
×
1024
                                    pl.error(format_args!("Duplicate local signatures: use full signatures (duplicate local signatures with three different seeds)"));
×
1025
                                    return Err(BuildError::DuplicateLocalSignatures.into());
×
1026
                                }
1027
                                pl.warn(format_args!(
×
1028
                                "Duplicate local signature, trying again with a different seed..."
×
1029
                                ));
1030
                                local_dup_count += 1;
×
1031
                            }
1032
                            SolveError::MaxShardTooBig => {
×
1033
                                pl.warn(format_args!(
×
1034
                                "The maximum shard is too big, trying again with a different seed..."
×
1035
                               ));
1036
                            }
1037
                            // Let's just try another seed
1038
                            SolveError::UnsolvableShard => {
142✔
1039
                                pl.warn(format_args!(
284✔
1040
                                    "Unsolvable shard, trying again with a different seed..."
142✔
1041
                                ));
1042
                            }
1043
                        },
1044
                        Err(error) => return Err(error),
×
1045
                    }
1046
                }
1047
            }
1048

1049
            values = values.rewind()?;
435✔
1050
            keys = keys.rewind()?;
435✔
1051
        }
1052
    }
1053
}
1054

1055
impl<
1056
    W: Word + BinSafe,
1057
    D: BitFieldSlice<W>
1058
        + for<'a> BitFieldSliceMut<W, ChunksMut<'a>: Iterator<Item: BitFieldSliceMut<W>>>
1059
        + Send
1060
        + Sync,
1061
    S: Sig + Send + Sync,
1062
    E: ShardEdge<S, 3>,
1063
> VBuilder<W, D, S, E>
1064
{
1065
    /// Tries to build a function using specific seed. See the comments in the
1066
    /// [`VBuilder::build_loop`] method for more details.
1067
    ///
1068
    /// This methods reads the input, sets up the shards, allocates the backend
1069
    /// using `new_data`, and passes the backend and an iterator on shards to
1070
    /// the [`VBuilder::try_build_from_shard_iter`] method.
1071
    ///
1072
    /// If `keep_store` is true, uses `iter()` on the shard store so it can be
1073
    /// returned; otherwise uses `into_iter()` for efficiency.
1074
    fn try_seed<
210✔
1075
        T: ?Sized + ToSig<S> + std::fmt::Debug,
1076
        B: ?Sized + Borrow<T>,
1077
        V: BinSafe + Default + Send + Sync + Ord + UpcastableInto<u128>,
1078
        G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync,
1079
        SS: SigStore<S, V>,
1080
    >(
1081
        &mut self,
1082
        seed: u64,
1083
        mut sig_store: SS,
1084
        keys: &mut (
1085
                 impl FallibleRewindableLender<
1086
            RewindError: std::error::Error + Send + Sync + 'static,
1087
            Error: std::error::Error + Send + Sync + 'static,
1088
        > + for<'lend> FallibleLending<'lend, Lend = &'lend B>
1089
             ),
1090
        values: &mut (
1091
                 impl FallibleRewindableLender<
1092
            RewindError: std::error::Error + Send + Sync + 'static,
1093
            Error: std::error::Error + Send + Sync + 'static,
1094
        > + for<'lend> FallibleLending<'lend, Lend = &'lend V>
1095
             ),
1096
        bit_width: Option<usize>,
1097
        get_val: &G,
1098
        new_data: fn(usize, usize) -> D,
1099
        keep_store: bool,
1100
        pl: &mut (impl ProgressLog + Clone + Send + Sync),
1101
    ) -> anyhow::Result<(VFunc<T, W, D, S, E>, Option<SS::ShardStore>)>
1102
    where
1103
        SigVal<S, V>: RadixKey,
1104
        SigVal<E::LocalSig, V>: BitXor + BitXorAssign,
1105
        for<'a> ShardDataIter<'a, D>: Send,
1106
        for<'a> <ShardDataIter<'a, D> as Iterator>::Item: Send,
1107
    {
1108
        let shard_edge = &mut self.shard_edge;
420✔
1109

1110
        pl.expected_updates(self.expected_num_keys);
630✔
1111
        pl.item_name("key");
420✔
1112
        pl.start(format!(
630✔
1113
            "Computing and storing {}-bit signatures in {} using seed 0x{:016x}...",
×
1114
            std::mem::size_of::<S>() * 8,
210✔
1115
            sig_store
210✔
1116
                .temp_dir()
210✔
1117
                .map(|d| d.path().to_string_lossy())
354✔
1118
                .unwrap_or(Cow::Borrowed("memory")),
420✔
1119
            seed
×
1120
        ));
1121

1122
        // This will be the maximum value for functions and EmptyVal for filters
1123
        let mut maybe_max_value = V::default();
420✔
1124
        let start = Instant::now();
420✔
1125

1126
        while let Some(key) = keys.next()? {
69,284,640✔
1127
            pl.light_update();
46,189,480✔
1128
            // This might be an actual value, if we are building a
1129
            // function, or EmptyVal, if we are building a filter.
1130
            let &maybe_val = values.next()?.expect("Not enough values");
115,473,700✔
1131
            let sig_val = SigVal {
1132
                sig: T::to_sig(key.borrow(), seed),
92,378,960✔
1133
                val: maybe_val,
1134
            };
1135
            maybe_max_value = Ord::max(maybe_max_value, maybe_val);
46,189,480✔
1136
            sig_store.try_push(sig_val)?;
69,284,220✔
1137
        }
1138
        pl.done();
420✔
1139

1140
        self.num_keys = sig_store.len();
210✔
1141
        self.bit_width = if TypeId::of::<V>() == TypeId::of::<EmptyVal>() {
210✔
1142
            bit_width.expect("Bit width must be set for filters")
342✔
1143
        } else {
1144
            let len_width = maybe_max_value.upcast().len() as usize;
288✔
1145
            if let Some(bit_width) = bit_width {
96✔
1146
                if len_width > bit_width {
×
1147
                    return Err(BuildError::ValueTooLarge.into());
×
1148
                }
1149
                bit_width
×
1150
            } else {
1151
                len_width
96✔
1152
            }
1153
        };
1154

1155
        info!(
210✔
1156
            "Computation of signatures from inputs completed in {:.3} seconds ({} keys, {:.3} ns/key)",
×
1157
            start.elapsed().as_secs_f64(),
348✔
1158
            self.num_keys,
×
1159
            start.elapsed().as_nanos() as f64 / self.num_keys as f64
174✔
1160
        );
1161

1162
        shard_edge.set_up_shards(self.num_keys, self.eps);
840✔
1163

1164
        let start = Instant::now();
420✔
1165

1166
        let mut shard_store = sig_store.into_shard_store(shard_edge.shard_high_bits())?;
1,050✔
1167
        let max_shard = shard_store.shard_sizes().iter().copied().max().unwrap_or(0);
1,260✔
1168
        let filter = TypeId::of::<V>() == TypeId::of::<EmptyVal>();
420✔
1169

1170
        (self.c, self.lge) = shard_edge.set_up_graphs(self.num_keys, max_shard);
1,050✔
1171

1172
        if filter {
324✔
1173
            pl.info(format_args!(
228✔
1174
                "Number of keys: {} Bit width: {}",
114✔
1175
                self.num_keys, self.bit_width,
114✔
1176
            ));
1177
        } else {
1178
            pl.info(format_args!(
384✔
1179
                "Number of keys: {} Max value: {} Bit width: {}",
96✔
1180
                self.num_keys,
96✔
1181
                maybe_max_value.upcast(),
96✔
1182
                self.bit_width,
96✔
1183
            ));
1184
        }
1185

1186
        if shard_edge.shard_high_bits() != 0 {
215✔
1187
            pl.info(format_args!(
20✔
1188
                "Max shard / average shard: {:.2}%",
5✔
1189
                (100.0 * max_shard as f64)
10✔
1190
                    / (self.num_keys as f64 / shard_edge.num_shards() as f64)
10✔
1191
            ));
1192
        }
1193

1194
        if max_shard as f64 > 1.01 * self.num_keys as f64 / shard_edge.num_shards() as f64 {
630✔
1195
            // This might sometimes happen with small sharded graphs
1196
            Err(SolveError::MaxShardTooBig.into())
×
1197
        } else {
1198
            let data = new_data(
1199
                self.bit_width,
420✔
1200
                shard_edge.num_vertices() * shard_edge.num_shards(),
630✔
1201
            );
1202
            // Use iter() when keep_store is true so we can return the shard_store,
1203
            // otherwise use into_iter() for efficiency
1204
            if keep_store {
210✔
1205
                let func = self
16✔
1206
                    .try_build_from_shard_iter(seed, data, shard_store.iter(), get_val, pl)
98✔
1207
                    .inspect(|_| {
16✔
1208
                        info!(
2✔
1209
                            "Construction from signatures completed in {:.3} seconds ({} keys, {:.3} ns/key)",
×
1210
                            start.elapsed().as_secs_f64(),
4✔
1211
                            self.num_keys,
×
1212
                            start.elapsed().as_nanos() as f64 / self.num_keys as f64
2✔
1213
                        );
1214
                    })?;
1215
                Ok((func, Some(shard_store)))
2✔
1216
            } else {
1217
                let func = self
258✔
1218
                    .try_build_from_shard_iter(seed, data, shard_store.into_iter(), get_val, pl)
1,372✔
1219
                    .inspect(|_| {
258✔
1220
                        info!(
62✔
1221
                            "Construction from signatures completed in {:.3} seconds ({} keys, {:.3} ns/key)",
×
1222
                            start.elapsed().as_secs_f64(),
104✔
1223
                            self.num_keys,
×
1224
                            start.elapsed().as_nanos() as f64 / self.num_keys as f64
52✔
1225
                        );
1226
                    })?;
1227
                Ok((func, None))
62✔
1228
            }
1229
        }
1230
    }
1231

1232
    /// Builds and return a new function starting from an iterator on shards.
1233
    ///
1234
    /// See [`VBuilder::build_loop`] for more details on the parameters.
1235
    fn try_build_from_shard_iter<
210✔
1236
        T: ?Sized + ToSig<S>,
1237
        I,
1238
        P,
1239
        V: BinSafe + Default + Send + Sync,
1240
        G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync,
1241
    >(
1242
        &mut self,
1243
        seed: u64,
1244
        mut data: D,
1245
        shard_iter: I,
1246
        get_val: &G,
1247
        pl: &mut P,
1248
    ) -> Result<VFunc<T, W, D, S, E>, SolveError>
1249
    where
1250
        SigVal<S, V>: RadixKey,
1251
        SigVal<E::LocalSig, V>: BitXor + BitXorAssign,
1252
        P: ProgressLog + Clone + Send + Sync,
1253
        I: Iterator<Item = Arc<Vec<SigVal<S, V>>>> + Send,
1254
        for<'a> ShardDataIter<'a, D>: Send,
1255
        for<'a> <ShardDataIter<'a, D> as Iterator>::Item: Send,
1256
    {
1257
        let shard_edge = &self.shard_edge;
420✔
1258
        self.num_threads = shard_edge.num_shards().min(self.max_num_threads);
630✔
1259

1260
        pl.info(format_args!("{}", self.shard_edge));
630✔
1261
        pl.info(format_args!(
630✔
1262
            "c: {}, Overhead: {:+.4}% Number of threads: {}",
×
1263
            self.c,
×
1264
            100. * ((shard_edge.num_vertices() * shard_edge.num_shards()) as f64
630✔
1265
                / (self.num_keys as f64)
210✔
1266
                - 1.),
210✔
1267
            self.num_threads
×
1268
        ));
1269

1270
        if self.lge {
210✔
1271
            pl.info(format_args!("Peeling towards lazy Gaussian elimination"));
564✔
1272
            self.par_solve(
376✔
1273
                shard_iter,
188✔
1274
                &mut data,
188✔
1275
                |this, shard_index, shard, data, pl| {
179✔
1276
                    this.lge_shard(shard_index, shard, data, get_val, pl)
1,253✔
1277
                },
1278
                &mut pl.concurrent(),
188✔
1279
                pl,
188✔
1280
            )?;
1281
        } else if self.low_mem == Some(true)
22✔
1282
            || self.low_mem.is_none() && self.num_threads > 3 && shard_edge.num_shards() > 2
26✔
1283
        {
1284
            // Less memory, but slower
1285
            self.par_solve(
20✔
1286
                shard_iter,
10✔
1287
                &mut data,
10✔
1288
                |this, shard_index, shard, data, pl| {
10✔
1289
                    this.peel_by_sig_vals_low_mem(shard_index, shard, data, get_val, pl)
70✔
1290
                },
1291
                &mut pl.concurrent(),
10✔
1292
                pl,
10✔
1293
            )?;
1294
        } else {
1295
            // More memory, but faster
1296
            self.par_solve(
24✔
1297
                shard_iter,
12✔
1298
                &mut data,
12✔
1299
                |this, shard_index, shard, data, pl| {
12✔
1300
                    this.peel_by_sig_vals_high_mem(shard_index, shard, data, get_val, pl)
84✔
1301
                },
1302
                &mut pl.concurrent(),
12✔
1303
                pl,
12✔
1304
            )?;
1305
        }
1306

1307
        pl.info(format_args!(
192✔
1308
            "Bits/keys: {} ({:+.4}%)",
×
1309
            data.len() as f64 * self.bit_width as f64 / self.num_keys as f64,
64✔
1310
            100.0 * (data.len() as f64 / self.num_keys as f64 - 1.),
64✔
1311
        ));
1312

1313
        Ok(VFunc {
64✔
1314
            seed,
128✔
1315
            shard_edge: self.shard_edge,
128✔
1316
            num_keys: self.num_keys,
128✔
1317
            data,
128✔
1318
            _marker_t: std::marker::PhantomData,
128✔
1319
            _marker_w: std::marker::PhantomData,
64✔
1320
            _marker_s: std::marker::PhantomData,
64✔
1321
        })
1322
    }
1323
}
1324

1325
macro_rules! remove_edge {
1326
    ($xor_graph: ident, $e: ident, $side: ident, $edge: ident, $stack: ident, $push:ident) => {
1327
        match $side {
1328
            0 => {
1329
                if $xor_graph.degree($e[1]) == 2 {
1330
                    $stack.$push($e[1].cast());
1331
                }
1332
                $xor_graph.remove($e[1], $edge, 1);
1333
                if $xor_graph.degree($e[2]) == 2 {
1334
                    $stack.$push($e[2].cast());
1335
                }
1336
                $xor_graph.remove($e[2], $edge, 2);
1337
            }
1338
            1 => {
1339
                if $xor_graph.degree($e[0]) == 2 {
1340
                    $stack.$push($e[0].cast());
1341
                }
1342
                $xor_graph.remove($e[0], $edge, 0);
1343
                if $xor_graph.degree($e[2]) == 2 {
1344
                    $stack.$push($e[2].cast());
1345
                }
1346
                $xor_graph.remove($e[2], $edge, 2);
1347
            }
1348
            2 => {
1349
                if $xor_graph.degree($e[0]) == 2 {
1350
                    $stack.$push($e[0].cast());
1351
                }
1352
                $xor_graph.remove($e[0], $edge, 0);
1353
                if $xor_graph.degree($e[1]) == 2 {
1354
                    $stack.$push($e[1].cast());
1355
                }
1356
                $xor_graph.remove($e[1], $edge, 1);
1357
            }
1358
            _ => unsafe { unreachable_unchecked() },
1359
        }
1360
    };
1361
}
1362

1363
impl<
1364
    W: Word + BinSafe + Send + Sync,
1365
    D: BitFieldSlice<W>
1366
        + for<'a> BitFieldSliceMut<W, ChunksMut<'a>: Iterator<Item: BitFieldSliceMut<W>>>
1367
        + Send
1368
        + Sync,
1369
    S: Sig + BinSafe,
1370
    E: ShardEdge<S, 3>,
1371
> VBuilder<W, D, S, E>
1372
{
1373
    fn count_sort<V: BinSafe>(&self, data: &mut [SigVal<S, V>]) {
201✔
1374
        let num_sort_keys = self.shard_edge.num_sort_keys();
603✔
1375
        let mut count = vec![0; num_sort_keys];
603✔
1376

1377
        let mut copied = Box::new_uninit_slice(data.len());
804✔
1378
        for (&sig_val, copy) in data.iter().zip(copied.iter_mut()) {
69,285,105✔
1379
            count[self.shard_edge.sort_key(sig_val.sig)] += 1;
92,378,800✔
1380
            copy.write(sig_val);
46,189,400✔
1381
        }
1382
        let copied = unsafe { copied.assume_init() };
603✔
1383

1384
        count.iter_mut().fold(0, |acc, c| {
9,115✔
1385
            let old = *c;
17,426✔
1386
            *c = acc;
8,713✔
1387
            acc + old
8,713✔
1388
        });
1389

1390
        for &sig_val in copied.iter() {
46,189,802✔
1391
            let key = self.shard_edge.sort_key(sig_val.sig);
115,473,500✔
1392
            data[count[key]] = sig_val;
46,189,400✔
1393
            count[key] += 1;
23,094,700✔
1394
        }
1395
    }
1396

1397
    /// After this number of keys, in the case of filters we remove duplicate
1398
    /// edges.
1399
    const MAX_NO_LOCAL_SIG_CHECK: usize = 1 << 33;
1400

1401
    /// Solves in parallel shards returned by an iterator, storing
1402
    /// the result in `data`.
1403
    ///
1404
    /// # Arguments
1405
    ///
1406
    /// * `shard_iter`: an iterator returning the shards to solve.
1407
    ///
1408
    /// * `data`: the storage for the solution values.
1409
    ///
1410
    /// * `solve_shard`: a method to solve a shard; it takes the shard index,
1411
    ///   the shard, shard-local data, and a progress logger. It may
1412
    ///   fail by returning an error.
1413
    ///
1414
    /// * `main_pl`: the progress logger for the overall computation.
1415
    ///
1416
    /// * `pl`: a progress logger that will be cloned to display the progress of
1417
    ///   a current shard.
1418
    ///
1419
    /// # Errors
1420
    ///
1421
    /// This method returns an error if one of the shards cannot be solved, or
1422
    /// if duplicates are detected.
1423
    fn par_solve<
210✔
1424
        'b,
1425
        V: BinSafe,
1426
        I: IntoIterator<Item = Arc<Vec<SigVal<S, V>>>> + Send,
1427
        SS: Fn(&Self, usize, Arc<Vec<SigVal<S, V>>>, ShardData<'b, D>, &mut P) -> Result<(), ()>
1428
            + Send
1429
            + Sync
1430
            + Copy,
1431
        C: ConcurrentProgressLog + Send + Sync,
1432
        P: ProgressLog + Clone + Send + Sync,
1433
    >(
1434
        &self,
1435
        shard_iter: I,
1436
        data: &'b mut D,
1437
        solve_shard: SS,
1438
        main_pl: &mut C,
1439
        pl: &mut P,
1440
    ) -> Result<(), SolveError>
1441
    where
1442
        I::IntoIter: Send,
1443
        SigVal<S, V>: RadixKey,
1444
        for<'a> ShardDataIter<'a, D>: Send,
1445
        for<'a> <ShardDataIter<'a, D> as Iterator>::Item: Send,
1446
    {
1447
        main_pl
420✔
1448
            .item_name("shard")
1449
            .expected_updates(Some(self.shard_edge.num_shards()))
420✔
1450
            .display_memory(true)
1451
            .start("Solving shards...");
1452

1453
        self.failed.store(false, Ordering::Relaxed);
630✔
1454
        let num_shards = self.shard_edge.num_shards();
630✔
1455
        let buffer_size = self.num_threads.ilog2() as usize;
420✔
1456

1457
        let (err_send, err_recv) = crossbeam_channel::bounded::<_>(self.num_threads);
630✔
1458
        let (data_send, data_recv) = crossbeam_channel::bounded::<(
1459
            usize,
1460
            (Arc<Vec<SigVal<S, V>>>, ShardData<'_, D>),
1461
        )>(buffer_size);
210✔
1462

1463
        let result = std::thread::scope(|scope| {
630✔
1464
            scope.spawn(move || {
630✔
1465
                let _ = thread_priority::set_current_thread_priority(ThreadPriority::Max);
210✔
1466
                for val in shard_iter
425✔
1467
                    .into_iter()
210✔
1468
                    .zip(data.try_chunks_mut(self.shard_edge.num_vertices()).unwrap())
1,260✔
1469
                    .enumerate()
210✔
1470
                {
1471
                    if data_send.send(val).is_err() {
645✔
1472
                        break;
×
1473
                    }
1474
                }
1475

1476
                drop(data_send);
420✔
1477
            });
1478

1479
            for _thread_id in 0..self.num_threads {
425✔
1480
                let mut main_pl = main_pl.clone();
645✔
1481
                let mut pl = pl.clone();
645✔
1482
                let err_send = err_send.clone();
645✔
1483
                let data_recv = data_recv.clone();
645✔
1484
                scope.spawn(move || {
645✔
1485
                    loop {
×
1486
                        match data_recv.recv() {
274✔
1487
                            Err(_) => return,
59✔
1488
                            Ok((shard_index, (shard, mut data))) => {
645✔
1489
                                if shard.is_empty() {
430✔
1490
                                    return;
10✔
1491
                                }
1492

1493
                                main_pl.info(format_args!(
615✔
1494
                                    "Analyzing shard {}/{}...",
×
1495
                                    shard_index + 1,
205✔
1496
                                    num_shards
×
1497
                                ));
1498

1499
                                pl.start(format!(
615✔
1500
                                    "Sorting shard {}/{}...",
×
1501
                                    shard_index + 1,
205✔
1502
                                    num_shards
×
1503
                                ));
1504

1505
                                {
1506
                                    // SAFETY: only one thread may be accessing the
1507
                                    // shard, and we will be consuming it
1508
                                    let shard = unsafe {
205✔
1509
                                        &mut *(Arc::as_ptr(&shard) as *mut Vec<SigVal<S, V>>)
205✔
1510
                                    };
1511

1512
                                    if self.check_dups {
205✔
1513
                                        shard.radix_sort_builder().sort();
12✔
1514
                                        if shard.par_windows(2).any(|w| w[0].sig == w[1].sig) {
16✔
1515
                                            let _ = err_send.send(SolveError::DuplicateSignature);
8✔
1516
                                            return;
4✔
1517
                                        }
1518
                                    }
1519

1520
                                    if TypeId::of::<E::LocalSig>() != TypeId::of::<S>()
201✔
1521
                                        && self.num_keys > Self::MAX_NO_LOCAL_SIG_CHECK
123✔
1522
                                    {
1523
                                        // Check for duplicate local signatures
1524

1525
                                        // E::SortSig provides a transmutable
1526
                                        // view of SigVal with an implementation
1527
                                        // of RadixKey that is compatible with
1528
                                        // the sort induced by the key returned
1529
                                        // by ShardEdge::sort_key, and equality
1530
                                        // that implies equality of local
1531
                                        // signatures.
1532

1533
                                        // SAFETY: we drop this immediately after sorting.
1534
                                        let shard = unsafe {
×
1535
                                            transmute::<
×
1536
                                                &mut Vec<SigVal<S, V>>,
×
1537
                                                &mut Vec<E::SortSigVal<V>>,
×
1538
                                            >(shard)
×
1539
                                        };
1540

1541
                                        let builder = shard.radix_sort_builder();
×
1542
                                        if self.max_num_threads == 1 {
×
1543
                                            builder
×
1544
                                                .with_single_threaded_tuner()
×
1545
                                                .with_parallel(false)
×
1546
                                        } else {
1547
                                            builder
×
1548
                                        }
1549
                                        .sort();
×
1550

1551
                                        let shard_len = shard.len();
×
1552
                                        shard.dedup();
×
1553

1554
                                        if TypeId::of::<V>() == TypeId::of::<EmptyVal>() {
×
1555
                                            // Duplicate local signatures on
1556
                                            // large filters can be simply
1557
                                            // removed: they do not change the
1558
                                            // semantics of the filter because
1559
                                            // hashes are computed on
1560
                                            // local signatures.
1561
                                            pl.info(format_args!(
×
1562
                                                "Removed signatures: {}",
×
1563
                                                shard_len - shard.len()
×
1564
                                            ));
1565
                                        } else {
1566
                                            // For function, we have to try again
1567
                                            if shard_len != shard.len() {
×
1568
                                                let _ = err_send
×
1569
                                                    .send(SolveError::DuplicateLocalSignature);
×
1570
                                                return;
×
1571
                                            }
1572
                                        }
1573
                                    } else if self.shard_edge.num_sort_keys() != 1 {
402✔
1574
                                        // Sorting the signatures increases locality
1575
                                        self.count_sort::<V>(shard);
402✔
1576
                                    }
1577
                                }
1578

1579
                                pl.done_with_count(shard.len());
804✔
1580

1581
                                main_pl.info(format_args!(
603✔
1582
                                    "Solving shard {}/{}...",
×
1583
                                    shard_index + 1,
201✔
1584
                                    num_shards
×
1585
                                ));
1586

1587
                                if self.failed.load(Ordering::Relaxed) {
603✔
1588
                                    return;
×
1589
                                }
1590

1591
                                if TypeId::of::<V>() == TypeId::of::<EmptyVal>() {
312✔
1592
                                    // For filters, we fill the array with random data, otherwise
1593
                                    // elements with signature 0 would have a significantly higher
1594
                                    // probability of being false positives.
1595
                                    //
1596
                                    // SAFETY: We work around the fact that [usize] does not implement Fill
1597
                                    Mwc192::seed_from_u64(self.seed).fill_bytes(unsafe {
333✔
1598
                                        data.as_mut_slice().align_to_mut::<u8>().1
111✔
1599
                                    });
1600
                                }
1601

1602
                                if solve_shard(self, shard_index, shard, data, &mut pl).is_err() {
1,206✔
1603
                                    let _ = err_send.send(SolveError::UnsolvableShard);
284✔
1604
                                    return;
142✔
1605
                                }
1606

1607
                                if self.failed.load(Ordering::Relaxed) {
177✔
1608
                                    return;
×
1609
                                }
1610

1611
                                main_pl.info(format_args!(
177✔
1612
                                    "Completed shard {}/{}",
×
1613
                                    shard_index + 1,
59✔
1614
                                    num_shards
×
1615
                                ));
1616
                                main_pl.update_and_display();
118✔
1617
                            }
1618
                        }
1619
                    }
1620
                });
1621
            }
1622

1623
            drop(err_send);
420✔
1624
            drop(data_recv);
420✔
1625

1626
            if let Some(error) = err_recv.into_iter().next() {
356✔
1627
                self.failed.store(true, Ordering::Relaxed);
438✔
1628
                return Err(error);
146✔
1629
            }
1630

1631
            Ok(())
64✔
1632
        });
1633

1634
        main_pl.done();
420✔
1635
        result
210✔
1636
    }
1637

1638
    /// Peels a shard via edge indices.
1639
    ///
1640
    /// This peeler uses a [`SigVal`] per key (the shard), a
1641
    /// [`ShardEdge::Vertex`] and a byte per vertex (for the [`XorGraph`]), a
1642
    /// [`ShardEdge::Vertex`] per vertex (for the [`DoubleStack`]), and a final
1643
    /// byte per vertex (for the stack of sides).
1644
    ///
1645
    /// This peeler uses more memory than
1646
    /// [`peek_by_sig_vals_low_mem`](VBuilder::peel_by_sig_vals_low_mem) but
1647
    /// less memory than
1648
    /// [`peel_by_sig_vals_high_mem`](VBuilder::peel_by_sig_vals_high_mem). It
1649
    /// is fairly slow as it has to go through a cache-unfriendly memory
1650
    /// indirection every time it has to retrieve a [`SigVal`] from the shard,
1651
    /// but it is the peeler of choice when [lazy Gaussian
1652
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517) is required, as
1653
    /// after a failed peel-by-sig-vals it is not possible to retrieve
1654
    /// information about the signature/value pairs in the shard.
1655
    ///
1656
    /// In theory one could avoid the stack of sides by putting vertices,
1657
    /// instead of edge indices, on the upper stack, and retrieving edge indices
1658
    /// and sides from the [`XorGraph`], as
1659
    /// [`peel_by_sig_vals_low_mem`](VBuilder::peel_by_sig_vals_low_mem) does,
1660
    ///  but this would be less cache friendly. This peeler is only used for
1661
    /// very small instances, and since we are going to pass through lazy
1662
    /// Gaussian elimination some additional speed is a good idea.
1663
    fn peel_by_index<'a, V: BinSafe, G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync>(
179✔
1664
        &self,
1665
        shard_index: usize,
1666
        shard: Arc<Vec<SigVal<S, V>>>,
1667
        data: ShardData<'a, D>,
1668
        get_val: &G,
1669
        pl: &mut impl ProgressLog,
1670
    ) -> Result<PeelResult<'a, W, D, S, E, V>, ()> {
1671
        let shard_edge = &self.shard_edge;
358✔
1672
        let num_vertices = shard_edge.num_vertices();
537✔
1673
        let num_shards = shard_edge.num_shards();
537✔
1674

1675
        pl.start(format!(
537✔
1676
            "Generating graph for shard {}/{}...",
×
1677
            shard_index + 1,
179✔
1678
            num_shards
×
1679
        ));
1680

1681
        let mut xor_graph = XorGraph::<E::Vertex>::new(num_vertices);
537✔
1682
        for (edge_index, sig_val) in shard.iter().enumerate() {
2,189,758✔
1683
            for (side, &v) in shard_edge
8,757,600✔
1684
                .local_edge(shard_edge.local_sig(sig_val.sig))
3,284,100✔
1685
                .iter()
1,094,700✔
1686
                .enumerate()
1,094,700✔
1687
            {
1688
                xor_graph.add(v, edge_index.cast(), side);
16,420,500✔
1689
            }
1690
        }
1691
        pl.done_with_count(shard.len());
716✔
1692

1693
        assert!(
179✔
1694
            !xor_graph.overflow,
179✔
1695
            "Degree overflow for shard {}/{}",
×
1696
            shard_index + 1,
×
1697
            num_shards
×
1698
        );
1699

1700
        if self.failed.load(Ordering::Relaxed) {
537✔
1701
            return Err(());
×
1702
        }
1703

1704
        // The lower stack contains vertices to be visited. The upper stack
1705
        // contains peeled edges.
1706
        let mut double_stack = DoubleStack::<E::Vertex>::new(num_vertices);
537✔
1707
        let mut sides_stack = Vec::<u8>::new();
358✔
1708

1709
        pl.start(format!(
537✔
1710
            "Peeling graph for shard {}/{} by edge indices...",
×
1711
            shard_index + 1,
179✔
1712
            num_shards
×
1713
        ));
1714

1715
        // Preload all vertices of degree one in the visit stack
1716
        for (v, degree) in xor_graph.degrees().enumerate() {
2,476,993✔
1717
            if degree == 1 {
1,469,978✔
1718
                double_stack.push_lower(v.cast());
695,250✔
1719
            }
1720
        }
1721

1722
        while let Some(v) = double_stack.pop_lower() {
961,467✔
1723
            let v: usize = v.upcast();
1,922,576✔
1724
            if xor_graph.degree(v) == 0 {
961,288✔
1725
                continue;
32,882✔
1726
            }
1727
            debug_assert!(xor_graph.degree(v) == 1);
1,343,286✔
1728
            let (edge_index, side) = xor_graph.edge_and_side(v);
1,791,048✔
1729
            xor_graph.zero(v);
1,343,286✔
1730
            double_stack.push_upper(edge_index);
1,343,286✔
1731
            sides_stack.push(side as u8);
1,343,286✔
1732

1733
            let e = shard_edge.local_edge(shard_edge.local_sig(shard[edge_index.upcast()].sig));
3,134,334✔
1734
            remove_edge!(xor_graph, e, side, edge_index, double_stack, push_lower);
447,762✔
1735
        }
1736

1737
        pl.done();
358✔
1738

1739
        if shard.len() != double_stack.upper_len() {
537✔
1740
            pl.info(format_args!(
507✔
1741
                "Peeling failed for shard {}/{} (peeled {} out of {} edges)",
×
1742
                shard_index + 1,
169✔
1743
                num_shards,
×
1744
                double_stack.upper_len(),
338✔
1745
                shard.len(),
338✔
1746
            ));
1747
            return Ok(PeelResult::Partial {
169✔
1748
                shard_index,
338✔
1749
                shard,
338✔
1750
                data,
338✔
1751
                double_stack,
338✔
1752
                sides_stack,
169✔
1753
                _marker: std::marker::PhantomData,
169✔
1754
            });
1755
        }
1756

1757
        self.assign(
20✔
1758
            shard_index,
10✔
1759
            data,
10✔
1760
            double_stack
10✔
1761
                .iter_upper()
10✔
1762
                .map(|&edge_index| {
110✔
1763
                    // Turn edge indices into local edge signatures
1764
                    // and associated values
1765
                    let sig_val = &shard[edge_index.upcast()];
300✔
1766
                    let local_sig = shard_edge.local_sig(sig_val.sig);
400✔
1767
                    (
1768
                        local_sig,
200✔
1769
                        get_val(
200✔
1770
                            shard_edge,
200✔
1771
                            SigVal {
100✔
1772
                                sig: local_sig,
100✔
1773
                                val: sig_val.val,
100✔
1774
                            },
1775
                        ),
1776
                    )
1777
                })
1778
                .zip(sides_stack.into_iter().rev()),
40✔
1779
            pl,
10✔
1780
        );
1781

1782
        Ok(PeelResult::Complete())
10✔
1783
    }
1784

1785
    /// Peels a shard via signature/value pairs using a stack of peeled
1786
    /// signatures/value pairs.
1787
    ///
1788
    /// This peeler does not need the shard once the [`XorGraph`] is built, so
1789
    /// it drops it immediately after building the graph.
1790
    ///
1791
    /// It uses a [`SigVal`] and a byte per vertex (for the [`XorGraph`]), a
1792
    /// [`ShardEdge::Vertex`] per vertex (for visit stack, albeit usually the stack
1793
    /// never contains more than a third of the vertices), and a [`SigVal`] and
1794
    /// a byte per key (for the stack of peeled edges).
1795
    ///
1796
    /// This is the fastest and more memory-consuming peeler. It has however
1797
    /// just a small advantage during assignment with respect to
1798
    /// [`peek_by_sig_vals_low_mem`](VBuilder::peel_by_sig_vals_low_mem), which
1799
    /// uses almost half the memory. It is the peeler of choice for low levels
1800
    /// of parallelism.
1801
    ///
1802
    /// This peeler cannot be used in conjunction with [lazy Gaussian
1803
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517) as after a failed
1804
    /// peeling it is not possible to retrieve information about the
1805
    /// signature/value pairs in the shard.
1806
    fn peel_by_sig_vals_high_mem<V: BinSafe, G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync>(
12✔
1807
        &self,
1808
        shard_index: usize,
1809
        shard: Arc<Vec<SigVal<S, V>>>,
1810
        data: ShardData<'_, D>,
1811
        get_val: &G,
1812
        pl: &mut impl ProgressLog,
1813
    ) -> Result<(), ()>
1814
    where
1815
        SigVal<E::LocalSig, V>: BitXor + BitXorAssign + Default,
1816
    {
1817
        let shard_edge = &self.shard_edge;
24✔
1818
        let num_vertices = shard_edge.num_vertices();
36✔
1819
        let num_shards = shard_edge.num_shards();
36✔
1820
        let shard_len = shard.len();
36✔
1821

1822
        pl.start(format!(
36✔
1823
            "Generating graph for shard {}/{}...",
×
1824
            shard_index + 1,
12✔
1825
            num_shards
×
1826
        ));
1827

1828
        let mut xor_graph = XorGraph::<SigVal<E::LocalSig, V>>::new(num_vertices);
36✔
1829
        for &sig_val in shard.iter() {
12,000,012✔
1830
            let local_sig = shard_edge.local_sig(sig_val.sig);
48,000,000✔
1831
            for (side, &v) in shard_edge.local_edge(local_sig).iter().enumerate() {
156,000,000✔
1832
                xor_graph.add(
108,000,000✔
1833
                    v,
72,000,000✔
1834
                    SigVal {
36,000,000✔
1835
                        sig: local_sig,
36,000,000✔
1836
                        val: sig_val.val,
36,000,000✔
1837
                    },
1838
                    side,
36,000,000✔
1839
                );
1840
            }
1841
        }
1842
        pl.done_with_count(shard.len());
48✔
1843

1844
        // We are using a consuming iterator over the shard store, so this
1845
        // drop will free the memory used by the signatures
1846
        drop(shard);
24✔
1847

1848
        assert!(
12✔
1849
            !xor_graph.overflow,
12✔
1850
            "Degree overflow for shard {}/{}",
×
1851
            shard_index + 1,
×
1852
            num_shards
×
1853
        );
1854

1855
        if self.failed.load(Ordering::Relaxed) {
36✔
1856
            return Err(());
×
1857
        }
1858

1859
        let mut sig_vals_stack = FastStack::<SigVal<E::LocalSig, V>>::new(shard_len);
36✔
1860
        let mut sides_stack = FastStack::<u8>::new(shard_len);
36✔
1861
        // Experimentally this stack never grows beyond a little more than
1862
        // num_vertices / 4
1863
        let mut visit_stack = Vec::<E::Vertex>::with_capacity(num_vertices / 3);
36✔
1864

1865
        pl.start(format!(
36✔
1866
            "Peeling graph for shard {}/{} by signatures (high-mem)...",
×
1867
            shard_index + 1,
12✔
1868
            num_shards
×
1869
        ));
1870

1871
        // Preload all vertices of degree one in the visit stack
1872
        for (v, degree) in xor_graph.degrees().enumerate() {
27,131,940✔
1873
            if degree == 1 {
16,096,586✔
1874
                visit_stack.push(v.cast());
7,591,902✔
1875
            }
1876
        }
1877

1878
        while let Some(v) = visit_stack.pop() {
25,122,948✔
1879
            let v: usize = v.upcast();
50,245,872✔
1880
            if xor_graph.degree(v) == 0 {
25,122,936✔
1881
                continue;
561,468✔
1882
            }
1883
            let (sig_val, side) = xor_graph.edge_and_side(v);
48,000,000✔
1884
            xor_graph.zero(v);
36,000,000✔
1885
            sig_vals_stack.push(sig_val);
36,000,000✔
1886
            sides_stack.push(side as u8);
36,000,000✔
1887

1888
            let e = self.shard_edge.local_edge(sig_val.sig);
48,000,000✔
1889
            remove_edge!(xor_graph, e, side, sig_val, visit_stack, push);
12,000,000✔
1890
        }
1891

1892
        pl.done();
24✔
1893

1894
        if shard_len != sig_vals_stack.len() {
24✔
1895
            pl.info(format_args!(
×
1896
                "Peeling failed for shard {}/{} (peeled {} out of {} edges)",
×
1897
                shard_index + 1,
×
1898
                num_shards,
×
1899
                sig_vals_stack.len(),
×
1900
                shard_len
×
1901
            ));
1902
            return Err(());
×
1903
        }
1904

1905
        self.assign(
24✔
1906
            shard_index,
12✔
1907
            data,
12✔
1908
            sig_vals_stack
12✔
1909
                .iter()
12✔
1910
                .rev()
12✔
1911
                .map(|&sig_val| (sig_val.sig, get_val(shard_edge, sig_val)))
36,000,012✔
1912
                .zip(sides_stack.iter().copied().rev()),
60✔
1913
            pl,
12✔
1914
        );
1915

1916
        Ok(())
12✔
1917
    }
1918

1919
    /// Peels a shard via signature/value pairs using a stack of vertices to
1920
    /// represent peeled edges.
1921
    ///
1922
    /// This peeler does not need the shard once the [`XorGraph`] is built, so
1923
    /// it drops it immediately after building the graph.
1924
    ///
1925
    /// It uses a [`SigVal`] and a byte per vertex (for the [`XorGraph`]) and a
1926
    /// [`ShardEdge::Vertex`] per vertex (for a [`DoubleStack`]).
1927
    ///
1928
    /// This is by far the less memory-hungry peeler, and it is just slightly
1929
    /// slower than
1930
    /// [`peek_by_sig_vals_high_mem`](VBuilder::peel_by_sig_vals_high_mem),
1931
    /// which uses almost twice the memory. It is the peeler of choice for
1932
    /// significant levels of parallelism.
1933
    ///
1934
    /// This peeler cannot be used in conjunction with [lazy Gaussian
1935
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517) as after a
1936
    /// failed peeling it is not possible to retrieve information about the
1937
    /// signature/value pairs in the shard.
1938
    fn peel_by_sig_vals_low_mem<V: BinSafe, G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync>(
10✔
1939
        &self,
1940
        shard_index: usize,
1941
        shard: Arc<Vec<SigVal<S, V>>>,
1942
        data: ShardData<'_, D>,
1943
        get_val: &G,
1944
        pl: &mut impl ProgressLog,
1945
    ) -> Result<(), ()>
1946
    where
1947
        SigVal<E::LocalSig, V>: BitXor + BitXorAssign + Default,
1948
    {
1949
        let shard_edge = &self.shard_edge;
20✔
1950
        let num_vertices = shard_edge.num_vertices();
30✔
1951
        let num_shards = shard_edge.num_shards();
30✔
1952
        let shard_len = shard.len();
30✔
1953

1954
        pl.start(format!(
30✔
1955
            "Generating graph for shard {}/{}...",
×
1956
            shard_index + 1,
10✔
1957
            num_shards,
×
1958
        ));
1959

1960
        let mut xor_graph = XorGraph::<SigVal<E::LocalSig, V>>::new(num_vertices);
30✔
1961
        for &sig_val in shard.iter() {
10,000,010✔
1962
            let local_sig = shard_edge.local_sig(sig_val.sig);
40,000,000✔
1963
            for (side, &v) in shard_edge.local_edge(local_sig).iter().enumerate() {
130,000,000✔
1964
                xor_graph.add(
90,000,000✔
1965
                    v,
60,000,000✔
1966
                    SigVal {
30,000,000✔
1967
                        sig: local_sig,
30,000,000✔
1968
                        val: sig_val.val,
30,000,000✔
1969
                    },
1970
                    side,
30,000,000✔
1971
                );
1972
            }
1973
        }
1974
        pl.done_with_count(shard.len());
40✔
1975

1976
        // We are using a consuming iterator over the shard store, so this
1977
        // drop will free the memory used by the signatures
1978
        drop(shard);
20✔
1979

1980
        assert!(
10✔
1981
            !xor_graph.overflow,
10✔
1982
            "Degree overflow for shard {}/{}",
×
1983
            shard_index + 1,
×
1984
            num_shards
×
1985
        );
1986

1987
        if self.failed.load(Ordering::Relaxed) {
30✔
1988
            return Err(());
×
1989
        }
1990

1991
        let mut visit_stack = DoubleStack::<E::Vertex>::new(num_vertices);
30✔
1992

1993
        pl.start(format!(
30✔
1994
            "Peeling graph for shard {}/{} by signatures (low-mem)...",
×
1995
            shard_index + 1,
10✔
1996
            num_shards
×
1997
        ));
1998

1999
        // Preload all vertices of degree one in the visit stack
2000
        for (v, degree) in xor_graph.degrees().enumerate() {
22,609,950✔
2001
            if degree == 1 {
13,413,566✔
2002
                visit_stack.push_lower(v.cast());
6,325,818✔
2003
            }
2004
        }
2005

2006
        while let Some(v) = visit_stack.pop_lower() {
20,935,042✔
2007
            let v: usize = v.upcast();
41,870,064✔
2008
            if xor_graph.degree(v) == 0 {
20,935,032✔
2009
                continue;
467,516✔
2010
            }
2011
            let (sig_val, side) = xor_graph.edge_and_side(v);
40,000,000✔
2012
            xor_graph.zero(v);
30,000,000✔
2013
            visit_stack.push_upper(v.cast());
40,000,000✔
2014

2015
            let e = self.shard_edge.local_edge(sig_val.sig);
40,000,000✔
2016
            remove_edge!(xor_graph, e, side, sig_val, visit_stack, push_lower);
10,000,000✔
2017
        }
2018

2019
        pl.done();
20✔
2020

2021
        if shard_len != visit_stack.upper_len() {
20✔
2022
            pl.info(format_args!(
×
2023
                "Peeling failed for shard {}/{} (peeled {} out of {} edges)",
×
2024
                shard_index + 1,
×
2025
                num_shards,
×
2026
                visit_stack.upper_len(),
×
2027
                shard_len
×
2028
            ));
2029
            return Err(());
×
2030
        }
2031

2032
        self.assign(
20✔
2033
            shard_index,
10✔
2034
            data,
10✔
2035
            visit_stack.iter_upper().map(|&v| {
10,000,030✔
2036
                let (sig_val, side) = xor_graph.edge_and_side(v.upcast());
50,000,000✔
2037
                ((sig_val.sig, get_val(shard_edge, sig_val)), side as u8)
30,000,000✔
2038
            }),
2039
            pl,
10✔
2040
        );
2041

2042
        Ok(())
10✔
2043
    }
2044

2045
    /// Solves a shard of given index possibly using [lazy Gaussian
2046
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517), and stores the
2047
    /// solution in the given data.
2048
    ///
2049
    /// As a first try, the shard is [peeled by index](VBuilder::peel_by_index).
2050
    /// If the peeling is [partial](PeelResult::Partial), lazy Gaussian
2051
    /// elimination is used to solve the remaining edges.
2052
    ///
2053
    /// This method will scan the double stack, without emptying it, to check
2054
    /// which edges have been peeled. The information will be then passed to
2055
    /// [`VBuilder::assign`] to complete the assignment of values.
2056
    fn lge_shard<V: BinSafe, G: Fn(&E, SigVal<E::LocalSig, V>) -> W + Send + Sync>(
179✔
2057
        &self,
2058
        shard_index: usize,
2059
        shard: Arc<Vec<SigVal<S, V>>>,
2060
        data: ShardData<'_, D>,
2061
        get_val: &G,
2062
        pl: &mut impl ProgressLog,
2063
    ) -> Result<(), ()> {
2064
        let shard_edge = &self.shard_edge;
358✔
2065
        // Let's try to peel first
2066
        match self.peel_by_index(shard_index, shard, data, get_val, pl) {
1,253✔
2067
            Err(()) => Err(()),
×
2068
            Ok(PeelResult::Complete()) => Ok(()),
10✔
2069
            Ok(PeelResult::Partial {
×
2070
                shard_index,
169✔
2071
                shard,
169✔
2072
                mut data,
169✔
2073
                double_stack,
169✔
2074
                sides_stack,
169✔
2075
                _marker: PhantomData,
×
2076
            }) => {
×
2077
                pl.info(format_args!("Switching to lazy Gaussian elimination..."));
507✔
2078
                // Likely result--we have solve the rest
2079
                pl.start(format!(
507✔
2080
                    "Generating system for shard {}/{}...",
×
2081
                    shard_index + 1,
169✔
2082
                    shard_edge.num_shards()
338✔
2083
                ));
2084

2085
                let num_vertices = shard_edge.num_vertices();
507✔
2086
                let mut peeled_edges = BitVec::new(shard.len());
676✔
2087
                let mut used_vars = BitVec::new(num_vertices);
507✔
2088
                for &edge in double_stack.iter_upper() {
895,662✔
2089
                    peeled_edges.set(edge.upcast(), true);
1,342,986✔
2090
                }
2091

2092
                // Create data for a system using non-peeled edges
2093
                //
2094
                // SAFETY: there is no undefined behavior here, but the
2095
                // raw construction methods we use assume that the
2096
                // equations are sorted, that the variables are not repeated,
2097
                // and the variables are in the range [0..num_vertices).
2098
                let mut system = unsafe {
2099
                    crate::utils::mod2_sys::Modulo2System::from_parts(
2100
                        num_vertices,
169✔
2101
                        shard
169✔
2102
                            .iter()
169✔
2103
                            .enumerate()
169✔
2104
                            .filter(|(edge_index, _)| !peeled_edges[*edge_index])
1,094,769✔
2105
                            .map(|(_edge_index, sig_val)| {
647,107✔
2106
                                let local_sig = shard_edge.local_sig(sig_val.sig);
2,587,752✔
2107
                                let mut eq: Vec<_> = shard_edge
2,587,752✔
2108
                                    .local_edge(local_sig)
646,938✔
2109
                                    .iter()
646,938✔
2110
                                    .map(|&x| {
2,587,752✔
2111
                                        used_vars.set(x, true);
5,822,442✔
2112
                                        x as u32
1,940,814✔
2113
                                    })
2114
                                    .collect();
646,938✔
2115
                                eq.sort_unstable();
646,938✔
2116
                                crate::utils::mod2_sys::Modulo2Equation::from_parts(
646,938✔
2117
                                    eq,
646,938✔
2118
                                    get_val(
1,293,876✔
2119
                                        shard_edge,
1,293,876✔
2120
                                        SigVal {
646,938✔
2121
                                            sig: local_sig,
646,938✔
2122
                                            val: sig_val.val,
646,938✔
2123
                                        },
2124
                                    ),
2125
                                )
2126
                            })
2127
                            .collect(),
169✔
2128
                    )
2129
                };
2130

2131
                if self.failed.load(Ordering::Relaxed) {
507✔
2132
                    return Err(());
×
2133
                }
2134

2135
                pl.start("Solving system...");
338✔
2136
                let result = system.lazy_gaussian_elimination().map_err(|_| ())?;
676✔
2137
                pl.done_with_count(system.num_equations());
108✔
2138

2139
                for (v, &value) in result.iter().enumerate().filter(|(v, _)| used_vars[*v]) {
2,996,448✔
2140
                    data.set_value(v, value);
1,853,583✔
2141
                }
2142

2143
                self.assign(
54✔
2144
                    shard_index,
27✔
2145
                    data,
27✔
2146
                    double_stack
27✔
2147
                        .iter_upper()
27✔
2148
                        .map(|&edge_index| {
412,567✔
2149
                            let sig_val = &shard[edge_index.upcast()];
1,237,620✔
2150
                            let local_sig = shard_edge.local_sig(sig_val.sig);
1,650,160✔
2151
                            (
2152
                                local_sig,
825,080✔
2153
                                get_val(
825,080✔
2154
                                    shard_edge,
825,080✔
2155
                                    SigVal {
412,540✔
2156
                                        sig: local_sig,
412,540✔
2157
                                        val: sig_val.val,
412,540✔
2158
                                    },
2159
                                ),
2160
                            )
2161
                        })
2162
                        .zip(sides_stack.into_iter().rev()),
108✔
2163
                    pl,
27✔
2164
                );
2165
                Ok(())
27✔
2166
            }
2167
        }
2168
    }
2169

2170
    /// Perform assignment of values based on peeling data.
2171
    ///
2172
    /// This method might be called after a successful peeling procedure, or
2173
    /// after a linear solver has been used to solve the remaining edges.
2174
    ///
2175
    /// `sig_vals_sides` is an iterator returning pairs of signature/value pairs
2176
    /// and sides in reverse peeling order.
2177
    fn assign(
59✔
2178
        &self,
2179
        shard_index: usize,
2180
        mut data: ShardData<'_, D>,
2181
        sigs_vals_sides: impl Iterator<Item = ((E::LocalSig, W), u8)>,
2182
        pl: &mut impl ProgressLog,
2183
    ) where
2184
        for<'a> ShardData<'a, D>: SliceByValueMut<Value = W>,
2185
    {
2186
        if self.failed.load(Ordering::Relaxed) {
177✔
2187
            return;
×
2188
        }
2189

2190
        pl.start(format!(
177✔
2191
            "Assigning values for shard {}/{}...",
×
2192
            shard_index + 1,
59✔
2193
            self.shard_edge.num_shards()
118✔
2194
        ));
2195

2196
        for ((sig, val), side) in sigs_vals_sides {
67,237,979✔
2197
            let edge = self.shard_edge.local_edge(sig);
89,650,560✔
2198
            let side = side as usize;
44,825,280✔
2199
            unsafe {
2200
                let xor = match side {
44,825,280✔
2201
                    0 => data.get_value_unchecked(edge[1]) ^ data.get_value_unchecked(edge[2]),
19,670,045✔
2202
                    1 => data.get_value_unchecked(edge[0]) ^ data.get_value_unchecked(edge[2]),
34,053,675✔
2203
                    2 => data.get_value_unchecked(edge[0]) ^ data.get_value_unchecked(edge[1]),
58,339,480✔
2204
                    _ => core::hint::unreachable_unchecked(),
2205
                };
2206

2207
                data.set_value_unchecked(edge[side], val ^ xor);
89,650,560✔
2208
            }
2209
        }
2210
        pl.done();
118✔
2211
    }
2212
}
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