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

vigna / webgraph-rs / 20017908129

08 Dec 2025 05:36AM UTC coverage: 62.065% (+0.4%) from 61.641%
20017908129

push

github

zommiommy
Fix doctests

5435 of 8757 relevant lines covered (62.06%)

46674689.93 hits per line

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

97.17
/webgraph/src/graphs/bvgraph/comp/bvcomp.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 super::OffsetsWriter;
9
use crate::prelude::*;
10
use core::cmp::Ordering;
11
use dsi_bitstream::codes::ToNat;
12
use lender::prelude::*;
13
use std::{io::Write, path::Path};
14

15
/// Compression statistics for a BvGraph compression.
16
#[derive(Debug, Clone, Copy, Default)]
17
pub struct CompStats {
18
    /// Number of source nodes compressed.
19
    pub num_nodes: usize,
20
    /// Number of arcs compressed.
21
    pub num_arcs: u64,
22
    /// Length of the compressed graph's bitstream.
23
    pub written_bits: u64,
24
    /// Length of the offsets bitstream.
25
    pub offsets_written_bits: u64,
26
}
27

28
/// A BvGraph compressor, this is used to compress a graph into a BvGraph
29
#[derive(Debug)]
30
pub struct BvComp<E, W: Write> {
31
    /// The ring-buffer that stores the neighbors of the last
32
    /// `compression_window` neighbors
33
    backrefs: CircularBuffer<Vec<usize>>,
34
    /// The ring-buffer that stores how many recursion steps are needed to
35
    /// decode the last `compression_window` nodes, this is used for
36
    /// `max_ref_count` which is used to modulate the compression / decoding
37
    /// speed tradeoff
38
    ref_counts: CircularBuffer<usize>,
39
    /// The bitstream writer, this implements the mock function so we can
40
    /// do multiple tentative compressions and use the real one once we figured
41
    /// out how to compress the graph best
42
    encoder: E,
43
    /// The offset writer, we should push the number of bits used by each node.
44
    pub offsets_writer: OffsetsWriter<W>,
45
    /// When compressing we need to store metadata. So we store the compressors
46
    /// to reuse the allocations for perf reasons.
47
    compressors: Vec<Compressor>,
48
    /// The number of previous nodes that will be considered during the compression
49
    compression_window: usize,
50
    /// The maximum recursion depth that will be used to decompress a node
51
    max_ref_count: usize,
52
    /// The minimum length of sequences that will be compressed as a (start, len)
53
    min_interval_length: usize,
54
    /// The current node we are compressing
55
    curr_node: usize,
56
    /// The first node we are compressing, this is needed because during
57
    /// parallel compression we need to work on different chunks
58
    start_node: usize,
59
    /// The statistics of the compression process.
60
    stats: CompStats,
61
}
62

63
impl BvComp<(), std::io::Sink> {
64
    /// Convenience method returning a [`BvCompConfig`] with
65
    /// settings suitable for the standard Boldi–Vigna compressor.
66
    pub fn with_basename(basename: impl AsRef<Path>) -> BvCompConfig {
31,115✔
67
        BvCompConfig::new(basename)
62,230✔
68
    }
69
}
70

71
#[derive(Debug, Clone, PartialEq, Eq)]
72
/// Compute how to encode the successors of a node, given a reference node.
73
/// This could be a function, but we made it a struct so we can reuse the
74
/// allocations for performance reasons
75
pub(crate) struct Compressor {
76
    /// The outdegree of the node we are compressing
77
    outdegree: usize,
78
    /// The blocks of nodes we are copying from the reference node
79
    blocks: Vec<usize>,
80
    /// The non-copied nodes
81
    extra_nodes: Vec<usize>,
82
    /// The starts of the intervals
83
    left_interval: Vec<usize>,
84
    /// The lengths of the intervals
85
    len_interval: Vec<usize>,
86
    /// The nodes left to encode as gaps
87
    residuals: Vec<usize>,
88
}
89

90
impl Compressor {
91
    /// Constant used only to make the code more readable.
92
    /// When min_interval_length is 0, we don't use intervals, which might be
93
    /// counter-intuitive
94
    pub(crate) const NO_INTERVALS: usize = 0;
95

96
    /// Creates a new empty compressor
97
    pub(crate) fn new() -> Self {
37,669,560✔
98
        Compressor {
99
            outdegree: 0,
100
            blocks: Vec::with_capacity(1024),
75,339,120✔
101
            extra_nodes: Vec::with_capacity(1024),
75,339,120✔
102
            left_interval: Vec::with_capacity(1024),
75,339,120✔
103
            len_interval: Vec::with_capacity(1024),
37,669,560✔
104
            residuals: Vec::with_capacity(1024),
37,669,560✔
105
        }
106
    }
107

108
    /// Writes the current node to the bitstream, this dumps the internal
109
    /// buffers which are initialized by calling `compress` so this has to be
110
    /// called only after `compress`.
111
    ///
112
    /// This returns the number of bits written.
113
    pub(crate) fn write<E: Encode>(
465,506,439✔
114
        &self,
115
        writer: &mut E,
116
        curr_node: usize,
117
        reference_offset: Option<usize>,
118
        min_interval_length: usize,
119
    ) -> Result<u64, E::Error> {
120
        let mut written_bits: u64 = 0;
1,396,519,317✔
121
        written_bits += writer.start_node(curr_node)? as u64;
1,396,519,317✔
122
        // write the outdegree
123
        written_bits += writer.write_outdegree(self.outdegree as u64)? as u64;
1,396,519,317✔
124
        // write the references
125
        if self.outdegree != 0 {
465,506,439✔
126
            if let Some(reference_offset) = reference_offset {
878,271,240✔
127
                written_bits += writer.write_reference_offset(reference_offset as u64)? as u64;
1,291,850,907✔
128
                if reference_offset != 0 {
430,616,969✔
129
                    written_bits += writer.write_block_count(self.blocks.len() as _)? as u64;
1,167,800,220✔
130
                    if !self.blocks.is_empty() {
291,950,055✔
131
                        for i in 0..self.blocks.len() {
977,444,816✔
132
                            written_bits += writer.write_block((self.blocks[i] - 1) as u64)? as u64;
2,095,373,334✔
133
                        }
134
                    }
135
                }
136
            }
137
        }
138
        // write the intervals
139
        if !self.extra_nodes.is_empty() && min_interval_length != Self::NO_INTERVALS {
900,369,184✔
140
            written_bits += writer.write_interval_count(self.left_interval.len() as _)? as u64;
1,246,138,100✔
141

142
            if !self.left_interval.is_empty() {
311,534,525✔
143
                written_bits += writer.write_interval_start(
197,485,684✔
144
                    (self.left_interval[0] as i64 - curr_node as i64).to_nat(),
197,485,684✔
145
                )? as u64;
×
146
                written_bits += writer
98,742,842✔
147
                    .write_interval_len((self.len_interval[0] - min_interval_length) as u64)?
197,485,684✔
148
                    as u64;
×
149
                let mut prev = self.left_interval[0] + self.len_interval[0];
296,228,526✔
150

151
                for i in 1..self.left_interval.len() {
156,792,255✔
152
                    written_bits += writer
58,049,413✔
153
                        .write_interval_start((self.left_interval[i] - prev - 1) as u64)?
116,098,826✔
154
                        as u64;
×
155
                    written_bits += writer
58,049,413✔
156
                        .write_interval_len((self.len_interval[i] - min_interval_length) as u64)?
116,098,826✔
157
                        as u64;
×
158
                    prev = self.left_interval[i] + self.len_interval[i];
116,098,826✔
159
                }
160
            }
161
        }
162
        // write the residuals
163
        // first signal the number of residuals to the encoder
164
        writer.num_of_residuals(self.residuals.len());
1,862,025,756✔
165
        if !self.residuals.is_empty() {
465,506,439✔
166
            written_bits += writer
432,945,883✔
167
                .write_first_residual((self.residuals[0] as i64 - curr_node as i64).to_nat())?
1,298,837,649✔
168
                as u64;
×
169

170
            for i in 1..self.residuals.len() {
2,147,483,647✔
171
                written_bits += writer
2,147,483,647✔
172
                    .write_residual((self.residuals[i] - self.residuals[i - 1] - 1) as u64)?
2,147,483,647✔
173
                    as u64;
×
174
            }
175
        }
176

177
        written_bits += writer.end_node(curr_node)? as u64;
1,396,519,317✔
178
        Ok(written_bits)
465,506,439✔
179
    }
180

181
    #[inline(always)]
182
    /// Reset the compressor for a new compression
183
    pub(crate) fn clear(&mut self) {
466,688,783✔
184
        self.outdegree = 0;
466,688,783✔
185
        self.blocks.clear();
933,377,566✔
186
        self.extra_nodes.clear();
933,377,566✔
187
        self.left_interval.clear();
933,377,566✔
188
        self.len_interval.clear();
933,377,566✔
189
        self.residuals.clear();
933,377,566✔
190
    }
191

192
    /// setup the internal buffers for the compression of the given values
193
    pub(crate) fn compress(
466,688,783✔
194
        &mut self,
195
        curr_list: &[usize],
196
        ref_list: Option<&[usize]>,
197
        min_interval_length: usize,
198
    ) -> anyhow::Result<()> {
199
        self.clear();
933,377,566✔
200
        self.outdegree = curr_list.len();
466,688,783✔
201

202
        if self.outdegree != 0 {
466,688,783✔
203
            if let Some(ref_list) = ref_list {
1,018,124,891✔
204
                self.diff_comp(curr_list, ref_list);
847,493,298✔
205
            } else {
206
                self.extra_nodes.extend(curr_list)
511,894,779✔
207
            }
208

209
            if !self.extra_nodes.is_empty() {
453,129,359✔
210
                if min_interval_length != Self::NO_INTERVALS {
757,629,845✔
211
                    self.intervalize(min_interval_length);
628,483,574✔
212
                } else {
213
                    self.residuals.extend(&self.extra_nodes);
258,292,542✔
214
                }
215
            }
216
        }
217
        debug_assert_eq!(self.left_interval.len(), self.len_interval.len());
2,147,483,647✔
218
        Ok(())
466,688,783✔
219
    }
220

221
    /// Get the extra nodes, compute all the intervals of consecutive nodes
222
    /// longer than min_interval_length and put the rest in the residuals
223
    fn intervalize(&mut self, min_interval_length: usize) {
314,241,787✔
224
        let vl = self.extra_nodes.len();
942,725,361✔
225
        let mut i = 0;
628,483,574✔
226

227
        while i < vl {
2,147,483,647✔
228
            let mut j = 0;
2,147,483,647✔
229
            if i < vl - 1 && self.extra_nodes[i] + 1 == self.extra_nodes[i + 1] {
2,147,483,647✔
230
                j += 1;
319,314,764✔
231
                while i + j < vl - 1 && self.extra_nodes[i + j] + 1 == self.extra_nodes[i + j + 1] {
2,147,483,647✔
232
                    j += 1;
236,290,086✔
233
                }
234
                j += 1;
319,314,764✔
235

236
                // Now j is the number of integers in the interval.
237
                if j >= min_interval_length {
481,387,011✔
238
                    self.left_interval.push(self.extra_nodes[i]);
648,288,988✔
239
                    self.len_interval.push(j);
486,216,741✔
240
                    i += j - 1;
162,072,247✔
241
                }
242
            }
243
            if j < min_interval_length {
2,147,483,647✔
244
                self.residuals.push(self.extra_nodes[i]);
2,147,483,647✔
245
            }
246

247
            i += 1;
2,147,483,647✔
248
        }
249
    }
250

251
    /// Compute the copy blocks and the ignore blocks.
252
    /// The copy blocks are blocks of nodes we will copy from the reference node.
253
    fn diff_comp(&mut self, curr_list: &[usize], ref_list: &[usize]) {
282,497,766✔
254
        // j is the index of the next successor of the current node we must examine
255
        let mut j = 0;
564,995,532✔
256
        // k is the index of the next successor of the reference node we must examine
257
        let mut k = 0;
564,995,532✔
258
        // currBlockLen is the number of entries (in the reference list) we have already copied/ignored (in the current block)
259
        let mut curr_block_len = 0;
564,995,532✔
260
        // copying is true iff we are producing a copy block (instead of an ignore block)
261
        let mut copying = true;
564,995,532✔
262

263
        while j < curr_list.len() && k < ref_list.len() {
2,147,483,647✔
264
            // First case: we are currently copying entries from the reference list
265
            if copying {
2,147,483,647✔
266
                match curr_list[j].cmp(&ref_list[k]) {
2,147,483,647✔
267
                    Ordering::Greater => {
425,147,885✔
268
                        /* If while copying we trespass the current element of the reference list,
425,147,885✔
269
                        we must stop copying. */
425,147,885✔
270
                        self.blocks.push(curr_block_len);
1,700,591,540✔
271
                        copying = false;
425,147,885✔
272
                        curr_block_len = 0;
425,147,885✔
273
                    }
274
                    Ordering::Less => {
452,108,815✔
275
                        /* If while copying we find a non-matching element of the reference list which
452,108,815✔
276
                        is larger than us, we can just add the current element to the extra list
452,108,815✔
277
                        and move on. j gets increased. */
452,108,815✔
278
                        self.extra_nodes.push(curr_list[j]);
1,356,326,445✔
279
                        j += 1;
452,108,815✔
280
                    }
281
                    Ordering::Equal => {
679,543,618✔
282
                        // currList[j] == refList[k]
283
                        /* If the current elements of the two lists are equal, we just increase the block length.
679,543,618✔
284
                        both j and k get increased. */
679,543,618✔
285
                        j += 1;
1,359,087,236✔
286
                        k += 1;
679,543,618✔
287
                        curr_block_len += 1;
679,543,618✔
288
                        // if (forReal) copiedArcs++;
289
                    }
290
                }
291
            } else {
292
                match curr_list[j].cmp(&ref_list[k]) {
2,147,483,647✔
293
                    Ordering::Greater => {
2,046,281,561✔
294
                        /* If we trespassed the current element of the reference list, we
2,046,281,561✔
295
                        increase the block length. k gets increased. */
2,046,281,561✔
296
                        k += 1;
2,046,281,561✔
297
                        curr_block_len += 1;
2,046,281,561✔
298
                    }
299
                    Ordering::Less => {
1,546,495,312✔
300
                        /* If we did not trespass the current element of the reference list, we just
1,546,495,312✔
301
                        add the current element to the extra list and move on. j gets increased. */
1,546,495,312✔
302
                        self.extra_nodes.push(curr_list[j]);
2,147,483,647✔
303
                        j += 1;
1,546,495,312✔
304
                    }
305
                    Ordering::Equal => {
225,422,751✔
306
                        // currList[j] == refList[k]
307
                        /* If we found a match we flush the current block and start a new copying phase. */
225,422,751✔
308
                        self.blocks.push(curr_block_len);
901,691,004✔
309
                        copying = true;
225,422,751✔
310
                        curr_block_len = 0;
225,422,751✔
311
                    }
312
                }
313
            }
314
        }
315
        /* We do not record the last block. The only case when we have to enqueue the last block's length
316
         * is when we were copying and we did not copy up to the end of the reference list.
317
         */
318
        if copying && k < ref_list.len() {
480,449,975✔
319
            self.blocks.push(curr_block_len);
64,813,890✔
320
        }
321

322
        // If there are still missing elements, we add them to the extra list.
323
        while j < curr_list.len() {
1,581,625,536✔
324
            self.extra_nodes.push(curr_list[j]);
1,016,630,004✔
325
            j += 1;
338,876,668✔
326
        }
327
        // add a 1 to the first block so we can uniformly write them later
328
        if !self.blocks.is_empty() {
555,325,862✔
329
            self.blocks[0] += 1;
272,828,096✔
330
        }
331
    }
332
}
333

334
impl<E: EncodeAndEstimate, W: Write> BvComp<E, W> {
335
    /// This value for `min_interval_length` implies that no intervalization will be performed.
336
    pub const NO_INTERVALS: usize = Compressor::NO_INTERVALS;
337

338
    /// Creates a new BvGraph compressor.
339
    pub fn new(
395,942✔
340
        encoder: E,
341
        offsets_writer: OffsetsWriter<W>,
342
        compression_window: usize,
343
        max_ref_count: usize,
344
        min_interval_length: usize,
345
        start_node: usize,
346
    ) -> Self {
347
        BvComp {
348
            backrefs: CircularBuffer::new(compression_window + 1),
791,884✔
349
            ref_counts: CircularBuffer::new(compression_window + 1),
791,884✔
350
            encoder,
351
            offsets_writer,
352
            min_interval_length,
353
            compression_window,
354
            max_ref_count,
355
            start_node,
356
            curr_node: start_node,
357
            compressors: (0..compression_window + 1)
395,942✔
358
                .map(|_| Compressor::new())
359
                .collect(),
360
            stats: CompStats::default(),
395,942✔
361
        }
362
    }
363

364
    /// Push a new node to the compressor.
365
    /// The iterator must yield the successors of the node and the nodes HAVE
366
    /// TO BE CONTIGUOUS (i.e. if a node has no neighbors you have to pass an
367
    /// empty iterator)
368
    pub fn push<I: IntoIterator<Item = usize>>(&mut self, succ_iter: I) -> anyhow::Result<()> {
25,252,060✔
369
        // collect the iterator inside the backrefs, to reuse the capacity already
370
        // allocated
371
        {
372
            let mut succ_vec = self.backrefs.take(self.curr_node);
126,260,300✔
373
            succ_vec.clear();
75,756,180✔
374
            succ_vec.extend(succ_iter);
101,008,240✔
375
            self.backrefs.replace(self.curr_node, succ_vec);
75,756,180✔
376
        }
377
        // get the ref
378
        let curr_list = &self.backrefs[self.curr_node];
50,504,120✔
379
        self.stats.num_nodes += 1;
25,252,060✔
380
        self.stats.num_arcs += curr_list.len() as u64;
25,252,060✔
381
        // first try to compress the current node without references
382
        let compressor = &mut self.compressors[0];
50,504,120✔
383
        // Compute how we would compress this
384
        compressor.compress(curr_list, None, self.min_interval_length)?;
126,260,300✔
385
        // avoid the mock writing
386
        if self.compression_window == 0 {
25,252,060✔
387
            let written_bits = compressor.write(
14,694,117✔
388
                &mut self.encoder,
4,898,039✔
389
                self.curr_node,
4,898,039✔
390
                None,
4,898,039✔
391
                self.min_interval_length,
4,898,039✔
392
            )?;
393
            // update the current node
394
            self.curr_node += 1;
4,898,039✔
395

396
            // write the offset
397
            self.stats.offsets_written_bits += self.offsets_writer.push(written_bits)? as u64;
14,694,117✔
398
            self.stats.written_bits += written_bits;
4,898,039✔
399
            return Ok(());
4,898,039✔
400
        }
401
        // The delta of the best reference, by default 0 which is no compression
402
        let mut ref_delta = 0;
40,708,042✔
403
        let mut min_bits = {
20,354,021✔
404
            let mut estimator = self.encoder.estimator();
61,062,063✔
405
            // Write the compressed data
406
            compressor.write(
40,708,042✔
407
                &mut estimator,
20,354,021✔
408
                self.curr_node,
20,354,021✔
409
                Some(0),
20,354,021✔
410
                self.min_interval_length,
20,354,021✔
411
            )?
412
        };
413

414
        let mut ref_count = 0;
40,708,042✔
415

416
        let deltas = 1 + self
61,062,063✔
417
            .compression_window
40,708,042✔
418
            .min(self.curr_node - self.start_node);
40,708,042✔
419
        // compression windows is not zero, so compress the current node
420
        for delta in 1..deltas {
187,191,619✔
421
            let ref_node = self.curr_node - delta;
333,675,196✔
422
            // If the reference node is too far, we don't consider it
423
            let count = self.ref_counts[ref_node];
333,675,196✔
424
            if count >= self.max_ref_count {
166,837,598✔
425
                continue;
36,225,390✔
426
            }
427
            // Get the neighbors of this previous ref_node
428
            let ref_list = &self.backrefs[ref_node];
261,224,416✔
429
            // No neighbors, no compression
430
            if ref_list.is_empty() {
261,224,416✔
431
                continue;
64,514,636✔
432
            }
433
            // Get its compressor
434
            let compressor = &mut self.compressors[delta];
132,195,144✔
435
            // Compute how we would compress this
436
            compressor.compress(curr_list, Some(ref_list), self.min_interval_length)?;
330,487,860✔
437
            // Compute how many bits it would use, using the mock writer
438
            let bits = {
66,097,572✔
439
                let mut estimator = self.encoder.estimator();
198,292,716✔
440
                compressor.write(
132,195,144✔
441
                    &mut estimator,
66,097,572✔
442
                    self.curr_node,
66,097,572✔
443
                    Some(delta),
66,097,572✔
444
                    self.min_interval_length,
66,097,572✔
445
                )?
446
            };
447
            // keep track of the best, it's strictly less so we keep the
448
            // nearest one in the case of multiple equal ones
449
            if bits < min_bits {
72,303,250✔
450
                min_bits = bits;
12,411,356✔
451
                ref_delta = delta;
6,205,678✔
452
                ref_count = count + 1;
6,205,678✔
453
            }
454
        }
455
        // write the best result reusing the precomputed compression
456
        let compressor = &mut self.compressors[ref_delta];
40,708,042✔
457
        let written_bits = compressor.write(
61,062,063✔
458
            &mut self.encoder,
20,354,021✔
459
            self.curr_node,
20,354,021✔
460
            Some(ref_delta),
20,354,021✔
461
            self.min_interval_length,
20,354,021✔
462
        )?;
463
        self.ref_counts[self.curr_node] = ref_count;
20,354,021✔
464
        // consistency check
465
        // debug_assert_eq!(written_bits, min_bits);
466
        // update the current node
467
        self.curr_node += 1;
20,354,021✔
468
        // write the offset
469
        self.stats.offsets_written_bits += self.offsets_writer.push(written_bits)? as u64;
61,062,063✔
470
        self.stats.written_bits += written_bits;
20,354,021✔
471
        Ok(())
20,354,021✔
472
    }
473

474
    /// Consume the compressor and return the statistics about compression.
475
    pub fn flush(mut self) -> anyhow::Result<CompStats> {
395,942✔
476
        self.encoder.flush()?;
791,884✔
477
        self.offsets_writer.flush()?;
791,884✔
478
        Ok(self.stats)
395,942✔
479
    }
480

481
    /// Given an iterator over the nodes successors iterators, push them all.
482
    /// The iterator must yield the successors of the node and the nodes HAVE
483
    /// TO BE CONTIGUOUS (i.e. if a node has no neighbors you have to pass an
484
    /// empty iterator).
485
    ///
486
    /// This most commonly is called with a reference to a graph.
487
    pub fn extend<L>(&mut self, iter_nodes: L) -> anyhow::Result<()>
6,952✔
488
    where
489
        L: IntoLender,
490
        L::Lender: for<'next> NodeLabelsLender<'next, Label = usize>,
491
    {
492
        for_! ( (_, succ) in iter_nodes {
591,416✔
493
            self.push(succ.into_iter())?;
2,337,856✔
494
        });
495
        Ok(())
6,952✔
496
    }
497
}
498

499
#[cfg(test)]
500
mod test {
501
    use super::*;
502
    use dsi_bitstream::prelude::*;
503
    use tempfile::Builder;
504

505
    #[test]
506
    fn test_compressor_no_ref() -> anyhow::Result<()> {
507
        let mut compressor = Compressor::new();
508
        compressor.compress(&[0, 1, 2, 5, 7, 8, 9], None, 2)?;
509
        assert_eq!(
510
            compressor,
511
            Compressor {
512
                outdegree: 7,
513
                blocks: vec![],
514
                extra_nodes: vec![0, 1, 2, 5, 7, 8, 9],
515
                left_interval: vec![0, 7],
516
                len_interval: vec![3, 3],
517
                residuals: vec![5],
518
            }
519
        );
520
        Ok(())
521
    }
522

523
    #[test]
524
    fn test_compressor1() -> anyhow::Result<()> {
525
        let mut compressor = Compressor::new();
526
        compressor.compress(&[0, 1, 2, 5, 7, 8, 9], Some(&[0, 1, 2]), 2)?;
527
        assert_eq!(
528
            compressor,
529
            Compressor {
530
                outdegree: 7,
531
                blocks: vec![],
532
                extra_nodes: vec![5, 7, 8, 9],
533
                left_interval: vec![7],
534
                len_interval: vec![3],
535
                residuals: vec![5],
536
            }
537
        );
538
        Ok(())
539
    }
540

541
    #[test]
542
    fn test_compressor2() -> anyhow::Result<()> {
543
        let mut compressor = Compressor::new();
544
        compressor.compress(&[0, 1, 2, 5, 7, 8, 9], Some(&[0, 1, 2, 100]), 2)?;
545
        assert_eq!(
546
            compressor,
547
            Compressor {
548
                outdegree: 7,
549
                blocks: vec![4],
550
                extra_nodes: vec![5, 7, 8, 9],
551
                left_interval: vec![7],
552
                len_interval: vec![3],
553
                residuals: vec![5],
554
            }
555
        );
556
        Ok(())
557
    }
558

559
    #[test]
560
    fn test_compressor3() -> anyhow::Result<()> {
561
        let mut compressor = Compressor::new();
562
        compressor.compress(
563
            &[0, 1, 2, 5, 7, 8, 9, 100],
564
            Some(&[0, 1, 2, 4, 7, 8, 9, 101]),
565
            2,
566
        )?;
567
        assert_eq!(
568
            compressor,
569
            Compressor {
570
                outdegree: 8,
571
                blocks: vec![4, 1, 3],
572
                extra_nodes: vec![5, 100],
573
                left_interval: vec![],
574
                len_interval: vec![],
575
                residuals: vec![5, 100],
576
            }
577
        );
578
        Ok(())
579
    }
580

581
    #[test]
582
    fn test_writer_window_zero() -> anyhow::Result<()> {
583
        test_compression(0, 0)?;
584
        test_compression(0, 1)?;
585
        test_compression(0, 2)?;
586
        Ok(())
587
    }
588

589
    #[test]
590
    fn test_writer_window_one() -> anyhow::Result<()> {
591
        test_compression(1, 0)?;
592
        test_compression(1, 1)?;
593
        test_compression(1, 2)?;
594
        Ok(())
595
    }
596

597
    #[test]
598
    fn test_writer_window_two() -> anyhow::Result<()> {
599
        test_compression(2, 0)?;
600
        test_compression(2, 1)?;
601
        test_compression(2, 2)?;
602
        Ok(())
603
    }
604

605
    #[test]
606
    fn test_writer_cnr() -> anyhow::Result<()> {
607
        let cnr_2000 = BvGraphSeq::with_basename("../data/cnr-2000")
608
            .endianness::<BE>()
609
            .load()?;
610

611
        let tmp_dir = Builder::new().prefix("bvcomp_test").tempdir()?;
612
        let basename = tmp_dir.path().join("cnr-2000");
613
        BvComp::with_basename(&basename).comp_graph::<BE>(&cnr_2000)?;
614
        let seq_graph = BvGraphSeq::with_basename(&basename).load()?;
615
        labels::eq_sorted(&cnr_2000, &seq_graph)?;
616

617
        BvComp::with_basename(&basename).par_comp_graph::<BE>(&cnr_2000)?;
618

619
        let seq_graph = BvGraphSeq::with_basename(&basename).load()?;
620
        labels::eq_sorted(&cnr_2000, &seq_graph)?;
621
        Ok(())
622
    }
623

624
    fn test_compression(
625
        compression_window: usize,
626
        min_interval_length: usize,
627
    ) -> anyhow::Result<()> {
628
        let cnr_2000 = BvGraphSeq::with_basename("../data/cnr-2000").load()?;
629

630
        let tmp_dir = Builder::new().prefix("bvcomp_test").tempdir()?;
631
        let basename = tmp_dir.path().join("cnr-2000");
632

633
        BvComp::with_basename(&basename)
634
            .with_comp_flags(CompFlags {
635
                compression_window,
636
                min_interval_length,
637
                ..Default::default()
638
            })
639
            .comp_graph::<BE>(&cnr_2000)?;
640

641
        let seq_graph = BvGraphSeq::with_basename(&basename).load()?;
642

643
        labels::eq_sorted(&cnr_2000, &seq_graph)?;
644
        Ok(())
645
    }
646
}
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