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

vigna / webgraph-rs / 22111438379

17 Feb 2026 06:52PM UTC coverage: 72.254% (+0.01%) from 72.24%
22111438379

push

github

vigna
fmt

2 of 2 new or added lines in 1 file covered. (100.0%)

245 existing lines in 9 files now uncovered.

6099 of 8441 relevant lines covered (72.25%)

50036569.1 hits per line

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

86.82
/webgraph/src/graphs/bvgraph/sequential.rs
1
/*
2
 * SPDX-FileCopyrightText: 2023 Inria
3
 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4
 *
5
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6
 */
7

8
use std::path::PathBuf;
9

10
use super::*;
11
use crate::utils::CircularBuffer;
12
use anyhow::Result;
13
use bitflags::Flags;
14
use dsi_bitstream::codes::ToInt;
15
use dsi_bitstream::traits::BE;
16
use dsi_bitstream::traits::BitSeek;
17
use lender::*;
18

19
/// A sequential graph in the BV format.
20
///
21
/// The graph depends on a [`SequentialDecoderFactory`] that can be used to
22
/// create decoders for the graph. This allows to decouple the graph from the
23
/// underlying storage format, and to use different storage formats for the same
24
/// graph. For example, one can use a memory-mapped file for the graph, or load
25
/// it in memory, or even generate it on the fly.
26
///
27
/// Note that the knowledge of the codes used by the graph is in the factory,
28
/// which provides methods to read each component of the BV format (outdegree,
29
/// reference offset, block count, etc.), whereas the knowledge of the
30
/// compression parameters (compression window and minimum interval length) is
31
/// in this structure.
32
#[derive(Debug, Clone)]
33
pub struct BvGraphSeq<F> {
34
    factory: F,
35
    number_of_nodes: usize,
36
    number_of_arcs: Option<u64>,
37
    compression_window: usize,
38
    min_interval_length: usize,
39
}
40

41
impl BvGraphSeq<()> {
42
    pub fn with_basename(
249,070✔
43
        basename: impl AsRef<std::path::Path>,
44
    ) -> LoadConfig<BE, Sequential, Dynamic, Mmap, Mmap> {
45
        LoadConfig {
46
            basename: PathBuf::from(basename.as_ref()),
996,280✔
47
            graph_load_flags: Flags::empty(),
498,140✔
48
            offsets_load_flags: Flags::empty(),
249,070✔
49
            _marker: std::marker::PhantomData,
50
        }
51
    }
52
}
53

54
impl<F: SequentialDecoderFactory> SplitLabeling for BvGraphSeq<F>
55
where
56
    for<'a> <F as SequentialDecoderFactory>::Decoder<'a>: Clone + Send + Sync,
57
{
58
    type SplitLender<'a>
59
        = split::seq::Lender<'a, BvGraphSeq<F>>
60
    where
61
        Self: 'a;
62
    type IntoIterator<'a>
63
        = split::seq::IntoIterator<'a, BvGraphSeq<F>>
64
    where
65
        Self: 'a;
66

67
    fn split_iter(&self, how_many: usize) -> Self::IntoIterator<'_> {
15✔
68
        split::seq::Iter::new(self.iter(), self.num_nodes(), how_many)
90✔
69
    }
70
}
71

72
impl<F: SequentialDecoderFactory> SequentialLabeling for BvGraphSeq<F> {
73
    type Label = usize;
74
    type Lender<'a>
75
        = NodeLabels<F::Decoder<'a>>
76
    where
77
        Self: 'a;
78

79
    /// Returns the number of nodes in the graph.
80
    #[inline(always)]
81
    fn num_nodes(&self) -> usize {
622,131✔
82
        self.number_of_nodes
622,131✔
83
    }
84

85
    #[inline(always)]
86
    fn num_arcs_hint(&self) -> Option<u64> {
19✔
87
        self.number_of_arcs
19✔
88
    }
89

90
    #[inline(always)]
91
    fn iter_from(&self, from: usize) -> Self::Lender<'_> {
629,369✔
92
        let mut iter = NodeLabels::new(
93
            self.factory.new_decoder().unwrap(),
1,888,107✔
94
            self.number_of_nodes,
629,369✔
95
            self.compression_window,
629,369✔
96
            self.min_interval_length,
629,369✔
97
        );
98

99
        let _ = iter.advance_by(from);
1,258,738✔
100

101
        iter
629,369✔
102
    }
103

104
    fn build_dcf(&self) -> DCF {
3✔
105
        let n = self.num_nodes();
9✔
106
        let num_arcs = self
9✔
107
            .num_arcs_hint()
6✔
108
            .expect("build_dcf requires num_arcs_hint()") as usize;
3✔
109
        let mut efb = sux::dict::EliasFanoBuilder::new(n + 1, num_arcs);
12✔
110
        efb.push(0);
6✔
111
        let mut cumul_deg = 0usize;
6✔
112
        let mut iter = OffsetDegIter::new(
113
            self.factory.new_decoder().unwrap(),
9✔
114
            n,
3✔
115
            self.compression_window,
3✔
116
            self.min_interval_length,
3✔
117
        );
118
        for _ in 0..n {
325,576✔
119
            cumul_deg += iter.next_degree().unwrap();
976,719✔
120
            efb.push(cumul_deg);
651,146✔
121
        }
122
        unsafe {
123
            efb.build().map_high_bits(|high_bits| {
12✔
124
                sux::rank_sel::SelectZeroAdaptConst::<_, _, 12, 4>::new(
3✔
125
                    sux::rank_sel::SelectAdaptConst::<_, _, 12, 4>::new(high_bits),
6✔
126
                )
127
            })
128
        }
129
    }
130
}
131

132
impl<F: SequentialDecoderFactory> SequentialGraph for BvGraphSeq<F> {}
133

134
/// Convenience implementation that makes it possible to iterate
135
/// over the graph using the [`for_`] macro
136
/// (see the [crate documentation](crate)).
137
impl<'a, F: SequentialDecoderFactory> IntoLender for &'a BvGraphSeq<F> {
138
    type Lender = <BvGraphSeq<F> as SequentialLabeling>::Lender<'a>;
139

140
    #[inline(always)]
UNCOV
141
    fn into_lender(self) -> Self::Lender {
×
UNCOV
142
        self.iter()
×
143
    }
144
}
145

146
impl<F: SequentialDecoderFactory> BvGraphSeq<F> {
147
    /// Creates a new sequential graph from a codes reader builder
148
    /// and the number of nodes.
149
    pub fn new(
629,312✔
150
        codes_reader_builder: F,
151
        number_of_nodes: usize,
152
        number_of_arcs: Option<u64>,
153
        compression_window: usize,
154
        min_interval_length: usize,
155
    ) -> Self {
156
        Self {
157
            factory: codes_reader_builder,
158
            number_of_nodes,
159
            number_of_arcs,
160
            compression_window,
161
            min_interval_length,
162
        }
163
    }
164

165
    #[inline(always)]
UNCOV
166
    pub fn map_factory<F1, F0>(self, map_func: F0) -> BvGraphSeq<F1>
×
167
    where
168
        F0: FnOnce(F) -> F1,
169
        F1: SequentialDecoderFactory,
170
    {
171
        BvGraphSeq {
UNCOV
172
            factory: map_func(self.factory),
×
UNCOV
173
            number_of_nodes: self.number_of_nodes,
×
UNCOV
174
            number_of_arcs: self.number_of_arcs,
×
UNCOV
175
            compression_window: self.compression_window,
×
UNCOV
176
            min_interval_length: self.min_interval_length,
×
177
        }
178
    }
179

180
    /// Consumes self and returns the factory.
181
    #[inline(always)]
UNCOV
182
    pub fn into_inner(self) -> F {
×
UNCOV
183
        self.factory
×
184
    }
185
}
186

187
impl<F: SequentialDecoderFactory> BvGraphSeq<F> {
188
    /// Creates an iterator specialized in the degrees of the nodes.
189
    /// This is slightly faster because it can avoid decoding some of
190
    /// the nodes and completely skip the merging step.
191
    #[inline(always)]
192
    pub fn offset_deg_iter(&self) -> OffsetDegIter<F::Decoder<'_>> {
255,798✔
193
        OffsetDegIter::new(
194
            self.factory.new_decoder().unwrap(),
767,394✔
195
            self.number_of_nodes,
255,798✔
196
            self.compression_window,
255,798✔
197
            self.min_interval_length,
255,798✔
198
        )
199
    }
200
}
201

202
/// A fast sequential iterator over the nodes of the graph and their successors.
203
/// This iterator does not require to know the offsets of each node in the graph.
204
#[derive(Debug, Clone)]
205
pub struct NodeLabels<D: Decode> {
206
    pub(crate) number_of_nodes: usize,
207
    pub(crate) compression_window: usize,
208
    pub(crate) min_interval_length: usize,
209
    pub(crate) decoder: D,
210
    pub(crate) backrefs: CircularBuffer<Vec<usize>>,
211
    pub(crate) current_node: usize,
212
}
213

214
impl<D: Decode + BitSeek> NodeLabels<D> {
215
    /// Forwards the call of `get_pos` to the inner `codes_reader`.
216
    /// This returns the current bits offset in the bitstream.
217
    #[inline(always)]
218
    pub fn bit_pos(&mut self) -> Result<u64, <D as BitSeek>::Error> {
1,175,880✔
219
        self.decoder.bit_pos()
2,351,760✔
220
    }
221
}
222

223
impl<D: Decode> NodeLabels<D> {
224
    /// Creates a new iterator from a codes reader
225
    pub fn new(
1,002,649✔
226
        decoder: D,
227
        number_of_nodes: usize,
228
        compression_window: usize,
229
        min_interval_length: usize,
230
    ) -> Self {
231
        Self {
232
            number_of_nodes,
233
            compression_window,
234
            min_interval_length,
235
            decoder,
236
            backrefs: CircularBuffer::new(compression_window + 1),
1,002,649✔
237
            current_node: 0,
238
        }
239
    }
240

241
    /// Get the successors of the next node in the stream
UNCOV
242
    pub fn next_successors(&mut self) -> Result<&[usize]> {
×
UNCOV
243
        let mut res = self.backrefs.take(self.current_node);
×
UNCOV
244
        res.clear();
×
UNCOV
245
        self.get_successors_iter_priv(self.current_node, &mut res)?;
×
UNCOV
246
        let res = self.backrefs.replace(self.current_node, res);
×
UNCOV
247
        self.current_node += 1;
×
UNCOV
248
        Ok(res)
×
249
    }
250

251
    /// Inner method called by `next_successors` and the iterator `next` method
252
    fn get_successors_iter_priv(&mut self, node_id: usize, results: &mut Vec<usize>) -> Result<()> {
230,118,442✔
253
        let degree = self.decoder.read_outdegree() as usize;
460,236,884✔
254
        // no edges, we are done!
255
        if degree == 0 {
230,118,442✔
256
            return Ok(());
28,365,322✔
257
        }
258

259
        // ensure that we have enough capacity in the vector for not reallocating
260
        results.reserve(degree.saturating_sub(results.capacity()));
1,210,518,720✔
261
        // read the reference offset
262
        let ref_delta = if self.compression_window != 0 {
403,506,240✔
263
            self.decoder.read_reference_offset() as usize
175,384,566✔
264
        } else {
265
            0
26,368,554✔
266
        };
267
        // if we copy nodes from a previous one
268
        if ref_delta != 0 {
201,753,120✔
269
            // compute the node id of the reference
270
            let reference_node_id = node_id - ref_delta;
154,202,948✔
271
            // retrieve the data
272
            let successors = &self.backrefs[reference_node_id];
154,202,948✔
273
            //debug_assert!(!neighbours.is_empty());
274
            // get the info on which destinations to copy
275
            let number_of_blocks = self.decoder.read_block_count() as usize;
154,202,948✔
276
            // no blocks, we copy everything
277
            if number_of_blocks == 0 {
103,490,692✔
278
                results.extend_from_slice(successors);
52,778,436✔
279
            } else {
280
                // otherwise we copy only the blocks of even index
281
                // the first block could be zero
282
                let mut idx = self.decoder.read_block() as usize;
101,424,512✔
283
                results.extend_from_slice(&successors[..idx]);
152,136,768✔
284

285
                // while the other can't
286
                for block_id in 1..number_of_blocks {
132,477,379✔
287
                    let block = self.decoder.read_block() as usize;
163,530,246✔
288
                    let end = idx + block + 1;
163,530,246✔
289
                    if block_id % 2 == 0 {
107,732,366✔
290
                        results.extend_from_slice(&successors[idx..end]);
103,868,972✔
291
                    }
292
                    idx = end;
81,765,123✔
293
                }
294
                if number_of_blocks & 1 == 0 {
80,542,893✔
295
                    results.extend_from_slice(&successors[idx..]);
89,491,911✔
296
                }
297
            }
298
        };
299

300
        // if we still have to read nodes
301
        let nodes_left_to_decode = degree - results.len();
605,259,360✔
302
        if nodes_left_to_decode != 0 && self.min_interval_length != 0 {
379,028,829✔
303
            // read the number of intervals
304
            let number_of_intervals = self.decoder.read_interval_count() as usize;
285,593,278✔
305
            if number_of_intervals != 0 {
142,796,639✔
306
                // pre-allocate with capacity for efficiency
307
                let node_id_offset = self.decoder.read_interval_start().to_int();
141,999,448✔
308
                let mut start = (node_id as i64 + node_id_offset) as usize;
70,999,724✔
309
                let mut delta = self.decoder.read_interval_len() as usize;
70,999,724✔
310
                delta += self.min_interval_length;
35,499,862✔
311
                // save the first interval
312
                results.extend(start..(start + delta));
141,999,448✔
313
                start += delta;
35,499,862✔
314
                // decode the intervals
315
                for _ in 1..number_of_intervals {
56,794,706✔
316
                    start += 1 + self.decoder.read_interval_start() as usize;
42,589,688✔
317
                    delta = self.decoder.read_interval_len() as usize;
42,589,688✔
318
                    delta += self.min_interval_length;
42,589,688✔
319

320
                    results.extend(start..(start + delta));
85,179,376✔
321

322
                    start += delta;
21,294,844✔
323
                }
324
            }
325
        }
326

327
        // decode the extra nodes if needed
328
        let nodes_left_to_decode = degree - results.len();
605,259,360✔
329
        self.decoder.num_of_residuals(nodes_left_to_decode);
605,259,360✔
330
        if nodes_left_to_decode != 0 {
201,753,120✔
331
            // pre-allocate with capacity for efficiency
332
            let node_id_offset = self.decoder.read_first_residual().to_int();
701,693,132✔
333
            let mut extra = (node_id as i64 + node_id_offset) as usize;
350,846,566✔
334
            results.push(extra);
526,269,849✔
335
            // decode the successive extra nodes
336
            for _ in 1..nodes_left_to_decode {
1,165,270,640✔
337
                extra += 1 + self.decoder.read_residual() as usize;
1,979,694,714✔
338
                results.push(extra);
1,979,694,714✔
339
            }
340
        }
341

342
        results.sort();
201,753,120✔
343
        Ok(())
201,753,120✔
344
    }
345
}
346

347
impl<'succ, D: Decode> NodeLabelsLender<'succ> for NodeLabels<D> {
348
    type Label = usize;
349
    type IntoIterator = crate::traits::labels::AssumeSortedIterator<
350
        std::iter::Copied<std::slice::Iter<'succ, Self::Label>>,
351
    >;
352
}
353

354
impl<'succ, D: Decode> Lending<'succ> for NodeLabels<D> {
355
    type Lend = (usize, <Self as NodeLabelsLender<'succ>>::IntoIterator);
356
}
357

358
impl<D: Decode> Lender for NodeLabels<D> {
359
    check_covariance!();
360

361
    fn next(&mut self) -> Option<Lend<'_, Self>> {
230,118,852✔
362
        if self.current_node >= self.number_of_nodes {
230,118,852✔
363
            return None;
410✔
364
        }
365
        let mut res = self.backrefs.take(self.current_node);
920,473,768✔
366
        res.clear();
460,236,884✔
367
        self.get_successors_iter_priv(self.current_node, &mut res)
920,473,768✔
368
            .unwrap();
369

370
        let res = self.backrefs.replace(self.current_node, res);
1,150,592,210✔
371
        let node_id = self.current_node;
460,236,884✔
372
        self.current_node += 1;
230,118,442✔
373
        Some((node_id, unsafe {
460,236,884✔
374
            crate::traits::labels::AssumeSortedIterator::new(res.iter().copied())
460,236,884✔
375
        }))
376
    }
377

378
    fn size_hint(&self) -> (usize, Option<usize>) {
3,581,266✔
379
        let len = self.len();
10,743,798✔
380
        (len, Some(len))
3,581,266✔
381
    }
382
}
383

384
unsafe impl<D: Decode> SortedLender for NodeLabels<D> {}
385

386
impl<D: Decode> ExactSizeLender for NodeLabels<D> {
387
    #[inline(always)]
388
    fn len(&self) -> usize {
3,581,266✔
389
        self.number_of_nodes - self.current_node
3,581,266✔
390
    }
391
}
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