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

vigna / sux-rs / 18495149581

14 Oct 2025 11:34AM UTC coverage: 65.126% (-2.0%) from 67.167%
18495149581

push

github

vigna
Reviewed change log

6 of 8 new or added lines in 4 files covered. (75.0%)

266 existing lines in 9 files now uncovered.

3735 of 5735 relevant lines covered (65.13%)

117158861.86 hits per line

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

75.86
/src/utils/mod2_sys.rs
1
/*
2
 *
3
 * SPDX-FileCopyrightText: 2025 Dario Moschetti
4
 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
5
 *
6
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
7
 */
8

9
//! Fast solution of random systems of linear equations with coefficients in
10
//! **F**â‚‚.
11

12
#![allow(unexpected_cfgs)]
13
#![allow(clippy::comparison_chain)]
14
use std::ptr;
15

16
use crate::{
17
    bit_vec,
18
    traits::Word,
19
    traits::{BitVecOps, BitVecOpsMut},
20
};
21
use anyhow::{Result, bail, ensure};
22
use arbitrary_chunks::ArbitraryChunks;
23

24
/// An equation on `W::BITS`-dimensional vectors with coefficients in **F**â‚‚.
25
#[derive(Clone, Debug)]
26
pub struct Modulo2Equation<W: Word = usize> {
27
    /// The variables in increasing order.
28
    vars: Vec<u32>,
29
    /// The constant term
30
    c: W,
31
}
32

33
/// A system of [equations](struct.Modulo2Equation.html).
34
#[derive(Clone, Debug)]
35
pub struct Modulo2System<W: Word = usize> {
36
    /// The number of variables.
37
    num_vars: usize,
38
    /// The equations in the system.
39
    equations: Vec<Modulo2Equation<W>>,
40
}
41

42
impl<W: Word> Modulo2Equation<W> {
43
    /// Creates a new equation with given variables constant term.
44
    ///
45
    /// # Safety
46
    ///
47
    /// The caller must ensure that the variables are sorted.
48
    pub unsafe fn from_parts(vars: Vec<u32>, c: W) -> Self {
630,036✔
49
        debug_assert!(vars.iter().is_sorted());
1,890,108✔
50
        Modulo2Equation { vars, c }
51
    }
52

53
    #[inline(always)]
54
    unsafe fn add_ptr(
1,756,242✔
55
        mut left: *const u32,
56
        left_end: *const u32,
57
        mut right: *const u32,
58
        right_end: *const u32,
59
        mut dst: *mut u32,
60
    ) -> *mut u32 {
61
        unsafe {
62
            while left != left_end && right != right_end {
2,147,483,647✔
63
                let less = *left <= *right;
1,099,765,859✔
64
                let more = *left >= *right;
×
65

66
                let src = if less { left } else { right };
1,099,765,859✔
67
                *dst = *src;
×
68

UNCOV
69
                left = left.add(less as usize);
×
UNCOV
70
                right = right.add(more as usize);
×
UNCOV
71
                dst = dst.add((less ^ more) as usize);
×
72
            }
73

74
            let rem_left = left_end.offset_from(left) as usize;
5,268,726✔
75
            ptr::copy_nonoverlapping(left, dst, rem_left);
7,024,968✔
76
            dst = dst.add(rem_left);
3,512,484✔
77
            let rem_right = right_end.offset_from(right) as usize;
5,268,726✔
78
            ptr::copy_nonoverlapping(right, dst, rem_right);
7,024,968✔
79
            dst = dst.add(rem_right);
3,512,484✔
80
            dst
1,756,242✔
81
        }
82
    }
83

84
    pub fn add(&mut self, other: &Modulo2Equation<W>) {
1,756,241✔
85
        let left_range = self.vars.as_ptr_range();
3,512,482✔
86
        let left = left_range.start;
3,512,482✔
87
        let left_end = left_range.end;
3,512,482✔
88
        let right_range = other.vars.as_ptr_range();
3,512,482✔
89
        let right = right_range.start;
3,512,482✔
90
        let right_end = right_range.end;
3,512,482✔
91
        let mut vars = Vec::with_capacity(self.vars.len() + other.vars.len());
8,781,205✔
92
        let dst = vars.as_mut_ptr();
5,268,723✔
93

94
        unsafe {
95
            let copied =
3,512,482✔
96
                Self::add_ptr(left, left_end, right, right_end, dst).offset_from(dst) as usize;
14,049,928✔
97
            vars.set_len(copied);
3,512,482✔
98
        }
99

100
        self.vars = vars;
3,512,482✔
101
        self.c ^= other.c;
1,756,241✔
102
    }
103

104
    /// Checks whether the equation is unsolvable.
105
    fn is_unsolvable(&self) -> bool {
736,008✔
106
        self.vars.is_empty() && self.c != W::ZERO
1,472,148✔
107
    }
108

109
    /// Checks whether the equation is an identity.
110
    fn is_identity(&self) -> bool {
754,757✔
111
        self.vars.is_empty() && self.c == W::ZERO
1,509,515✔
112
    }
113

114
    /// Evaluates the XOR of the values associated to the given variables.
115
    fn eval_vars(vars: impl AsRef<[u32]>, values: impl AsRef<[W]>) -> W {
589,403✔
116
        let mut sum = W::ZERO;
1,178,806✔
117
        for &var in vars.as_ref() {
302,109,293✔
UNCOV
118
            sum ^= values.as_ref()[var as usize];
×
119
        }
120
        sum
589,403✔
121
    }
122
}
123

124
impl<W: Word> Modulo2System<W> {
125
    pub fn new(num_vars: usize) -> Self {
4✔
126
        Modulo2System {
127
            num_vars,
128
            equations: vec![],
4✔
129
        }
130
    }
131

UNCOV
132
    pub fn num_vars(&self) -> usize {
×
UNCOV
133
        self.num_vars
×
134
    }
135

136
    pub fn num_equations(&self) -> usize {
25✔
137
        self.equations.len()
50✔
138
    }
139

140
    /// Creates a new `Modulo2System` from existing equations.
141
    ///
142
    /// # Safety
143
    ///
144
    /// The caller must ensure that variables in each equation match the number
145
    /// of variables in the system.
146
    pub unsafe fn from_parts(num_vars: usize, equations: Vec<Modulo2Equation<W>>) -> Self {
215✔
147
        Modulo2System {
148
            num_vars,
149
            equations,
150
        }
151
    }
152

153
    /// Adds an equation to the system.
154
    pub fn push(&mut self, equation: Modulo2Equation<W>) {
11✔
155
        self.equations.push(equation);
33✔
156
    }
157

158
    /// Checks if a given solution satisfies the system of equations.
159
    pub fn check(&self, solution: &[W]) -> bool {
3✔
160
        assert_eq!(
3✔
161
            solution.len(),
6✔
162
            self.num_vars,
×
UNCOV
163
            "The number of variables in the solution ({}) does not match the number of variables in the system ({})",
×
UNCOV
164
            solution.len(),
×
UNCOV
165
            self.num_vars
×
166
        );
167
        self.equations
3✔
168
            .iter()
169
            .all(|eq| eq.c == Modulo2Equation::<W>::eval_vars(&eq.vars, solution))
30✔
170
    }
171

172
    /// Puts the system into echelon form.
173
    fn echelon_form(&mut self) -> Result<()> {
59✔
174
        let equations = &mut self.equations;
118✔
175
        if equations.is_empty() {
118✔
176
            return Ok(());
2✔
177
        }
178
        'main: for i in 0..equations.len() - 1 {
19,211✔
179
            ensure!(!equations[i].vars.is_empty());
38,422✔
180
            for j in i + 1..equations.len() {
15,020,695✔
181
                // SAFETY: to add the two equations, multiple references to the vector
182
                // of equations are needed, one of which is mutable
183
                let eq_j = unsafe { &*(&equations[j] as *const Modulo2Equation<W>) };
30,002,968✔
184
                let eq_i = &mut equations[i];
30,002,968✔
185

186
                let first_var_j = eq_j.vars[0];
30,002,968✔
187

188
                if eq_i.vars[0] == first_var_j {
15,001,484✔
189
                    eq_i.add(eq_j);
2,144,895✔
190
                    if eq_i.is_unsolvable() {
1,429,930✔
191
                        bail!("System is unsolvable");
31✔
192
                    }
UNCOV
193
                    if eq_i.is_identity() {
×
UNCOV
194
                        continue 'main;
×
195
                    }
196
                }
197

198
                if eq_i.vars[0] > first_var_j {
15,001,453✔
199
                    equations.swap(i, j)
30,317,403✔
200
                }
201
            }
202
        }
203
        Ok(())
26✔
204
    }
205

206
    /// Solves the system using Gaussian elimination.
207
    pub fn gaussian_elimination(&mut self) -> Result<Vec<W>> {
59✔
208
        self.echelon_form()?;
149✔
209
        let mut solution = vec![W::ZERO; self.num_vars];
28✔
UNCOV
210
        self.equations
×
211
            .iter()
212
            .rev()
213
            .filter(|eq| !eq.is_identity())
37,760✔
214
            .for_each(|eq| {
18,880✔
215
                solution[eq.vars[0] as usize] =
56,640✔
216
                    eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
56,640✔
217
            });
UNCOV
218
        Ok(solution)
×
219
    }
220

221
    /// Builds the data structures needed for [lazy Gaussian
222
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517).
223
    ///
224
    /// This method returns the variable-to-equation mapping, the weight of each
225
    /// variable (the number of equations in which it appears), and the priority
226
    /// of each equation (the number of variables in it). The
227
    /// variable-to-equation mapping is materialized as a vector of mutable
228
    /// slices, each of which points inside a provided backing vector. This
229
    /// approach greatly reduces the number of allocations.
230
    fn setup<'a>(
160✔
231
        &self,
232
        backing: &'a mut Vec<usize>,
233
    ) -> (Vec<&'a mut [usize]>, Vec<usize>, Vec<usize>) {
234
        let mut weight = vec![0; self.num_vars];
480✔
235
        let mut priority = vec![0; self.equations.len()];
640✔
236

237
        let mut total_vars = 0;
320✔
238
        for (i, equation) in self.equations.iter().enumerate() {
630,353✔
239
            priority[i] = equation.vars.len();
×
UNCOV
240
            total_vars += equation.vars.len();
×
241
            for &var in &equation.vars {
4,410,211✔
UNCOV
242
                weight[var as usize] += 1;
×
243
            }
244
        }
245

246
        backing.resize(total_vars, 0);
480✔
247
        let mut var_to_eq: Vec<&mut [usize]> = Vec::with_capacity(self.num_vars);
640✔
248

249
        backing.arbitrary_chunks_mut(&weight).for_each(|chunk| {
1,223,490✔
250
            var_to_eq.push(chunk);
3,669,030✔
251
        });
252

253
        let mut pos = vec![0usize; self.num_vars];
480✔
254
        for (i, equation) in self.equations.iter().enumerate() {
630,353✔
255
            for &var in &equation.vars {
4,410,211✔
UNCOV
256
                let var = var as usize;
×
UNCOV
257
                var_to_eq[var][pos[var]] = i;
×
UNCOV
258
                pos[var] += 1;
×
259
            }
260
        }
261

262
        (var_to_eq, weight, priority)
320✔
263
    }
264

265
    /// Solves the system using [lazy Gaussian
266
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517).
267
    pub fn lazy_gaussian_elimination(&mut self) -> Result<Vec<W>> {
159✔
268
        let num_vars = self.num_vars;
318✔
269
        let num_equations = self.equations.len();
477✔
270

271
        if num_equations == 0 {
159✔
272
            return Ok(vec![W::ZERO; num_vars]);
×
273
        }
274

UNCOV
275
        let mut backing = vec![];
×
276
        let (var_to_eqs, mut weight, mut priority);
×
UNCOV
277
        (var_to_eqs, weight, priority) = self.setup(&mut backing);
×
278

UNCOV
279
        let mut variables = vec![0; num_vars];
×
280
        {
281
            let mut count = vec![0; num_equations + 1];
×
282

283
            for x in 0..num_vars {
1,222,999✔
284
                count[weight[x]] += 1
×
285
            }
286
            for i in 1..count.len() {
630,027✔
287
                count[i] += count[i - 1]
×
288
            }
289
            for i in (0..num_vars).rev() {
1,222,999✔
UNCOV
290
                count[weight[i]] -= 1;
×
UNCOV
291
                variables[count[weight[i]]] = i;
×
292
            }
293
        }
294

UNCOV
295
        let mut equation_list: Vec<usize> = (0..priority.len())
×
296
            .rev()
297
            .filter(|&x| priority[x] <= 1)
630,027✔
298
            .collect();
299

UNCOV
300
        let mut dense = vec![];
×
301
        let mut solved = vec![];
×
302
        let mut pivots = vec![];
×
303

304
        let equations = &mut self.equations;
×
UNCOV
305
        let mut idle = bit_vec![true; num_vars];
×
306

UNCOV
307
        let mut remaining = equations.len();
×
308

309
        while remaining != 0 {
661,774✔
310
            if equation_list.is_empty() {
1,323,430✔
311
                let mut var = variables.pop().unwrap();
162,520✔
312
                while weight[var] == 0 {
591,296✔
313
                    var = variables.pop().unwrap()
550,666✔
314
                }
315
                idle.set(var, false);
121,890✔
316
                var_to_eqs[var].as_ref().iter().for_each(|&eq| {
347,681✔
317
                    priority[eq] -= 1;
225,791✔
318
                    if priority[eq] == 1 {
225,791✔
319
                        equation_list.push(eq)
127,209✔
320
                    }
321
                });
322
            } else {
323
                remaining -= 1;
621,085✔
UNCOV
324
                let first = equation_list.pop().unwrap();
×
325

UNCOV
326
                if priority[first] == 0 {
×
327
                    let equation = &mut equations[first];
42,086✔
328
                    if equation.is_unsolvable() {
42,086✔
329
                        bail!("System is unsolvable")
100✔
330
                    }
UNCOV
331
                    if equation.is_identity() {
×
332
                        continue;
1✔
333
                    }
334
                    dense.push(equation.to_owned());
83,768✔
335
                } else if priority[first] == 1 {
600,042✔
336
                    // SAFETY: to add the equations, multiple references to the vector
337
                    // of equations are needed, one of which is mutable
338
                    let equation = unsafe { &*(&equations[first] as *const Modulo2Equation<W>) };
1,200,084✔
339

340
                    let pivot = equation
1,200,084✔
341
                        .vars
600,042✔
342
                        .iter()
343
                        .copied()
344
                        .find(|x| idle.get(*x as usize))
467,670,243✔
345
                        .expect("Missing expected idle variable in equation");
346
                    pivots.push(pivot as usize);
1,800,126✔
347
                    solved.push(first);
1,800,126✔
348
                    weight[pivot as usize] = 0;
600,042✔
349
                    var_to_eqs[pivot as usize]
600,042✔
350
                        .as_ref()
351
                        .iter()
352
                        .filter(|&&eq_idx| eq_idx != first)
3,882,676✔
353
                        .for_each(|&eq| {
1,641,317✔
354
                            equations[eq].add(equation);
3,123,825✔
355

356
                            priority[eq] -= 1;
1,041,275✔
357
                            if priority[eq] == 1 {
1,041,275✔
358
                                equation_list.push(eq)
1,737,117✔
359
                            }
360
                        });
361
                }
362
            }
363
        }
364

365
        // SAFETY: the usage of the method is safe, as the equations have the
366
        // right number of variables
367
        let mut dense_system = unsafe { Modulo2System::from_parts(num_vars, dense) };
59✔
368
        let mut solution = dense_system.gaussian_elimination()?;
59✔
369

370
        for i in 0..solved.len() {
570,514✔
371
            let eq = &equations[solved[i]];
1,711,542✔
372
            let pivot = pivots[i];
1,141,028✔
373
            assert!(solution[pivot] == W::ZERO);
1,141,028✔
374
            solution[pivot] = eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
570,514✔
375
        }
376

377
        Ok(solution)
28✔
378
    }
379
}
380

381
#[cfg(test)]
382
mod tests {
383
    use super::*;
384

385
    #[test]
386
    fn test_add_ptr() {
387
        let a = [0, 1, 3, 4];
388
        let b = [0, 2, 4, 5];
389
        let mut c = Vec::with_capacity(a.len() + b.len());
390

391
        let ra = a.as_ptr_range();
392
        let rb = b.as_ptr_range();
393
        let mut dst = c.as_mut_ptr();
394
        unsafe {
395
            dst = Modulo2Equation::<u32>::add_ptr(ra.start, ra.end, rb.start, rb.end, dst);
396
            assert_eq!(dst.offset_from(c.as_ptr()), 4);
397
            c.set_len(4);
398
            assert_eq!(c, vec![1, 2, 3, 5]);
399
        }
400
    }
401

402
    #[test]
403
    fn test_equation_builder() {
404
        let eq = unsafe { Modulo2Equation::from_parts(vec![0, 1, 2], 3_usize) };
405
        assert_eq!(eq.vars.len(), 3);
406
        assert_eq!(eq.vars.to_vec(), vec![0, 1, 2]);
407
    }
408

409
    #[test]
410
    fn test_equation_addition() {
411
        let mut eq0 = unsafe { Modulo2Equation::from_parts(vec![1, 4, 9], 3_usize) };
412
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![1, 4, 10], 3_usize) };
413
        eq0.add(&eq1);
414
        assert_eq!(eq0.vars, vec![9, 10]);
415
        assert_eq!(eq0.c, 0);
416
    }
417

418
    #[test]
419
    fn test_system_one_equation() {
420
        let mut system = Modulo2System::<usize>::new(2);
421
        let eq = unsafe { Modulo2Equation::from_parts(vec![0], 3_usize) };
422
        system.push(eq);
423
        let solution = system.lazy_gaussian_elimination();
424
        assert!(solution.is_ok());
425
        assert!(system.check(&solution.unwrap()));
426
    }
427

428
    #[test]
429
    fn test_impossible_system() {
430
        let mut system = Modulo2System::<usize>::new(1);
431
        let eq0 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
432
        system.push(eq0);
433
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![0], 1_usize) };
434
        system.push(eq1);
435
        let solution = system.lazy_gaussian_elimination();
436
        assert!(solution.is_err());
437
    }
438

439
    #[test]
440
    fn test_redundant_system() {
441
        let mut system = Modulo2System::<usize>::new(1);
442
        let eq0 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
443
        system.push(eq0);
444
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
445
        system.push(eq1);
446
        let solution = system.lazy_gaussian_elimination();
447
        assert!(solution.is_ok());
448
        assert!(system.check(&solution.unwrap()));
449
    }
450

451
    #[test]
452
    fn test_small_system() {
453
        let mut system = Modulo2System::<usize>::new(11);
454
        let mut eq = unsafe { Modulo2Equation::from_parts(vec![1, 4, 10], 0) };
455
        system.push(eq);
456
        eq = unsafe { Modulo2Equation::from_parts(vec![1, 4, 9], 2) };
457
        system.push(eq);
458
        eq = unsafe { Modulo2Equation::from_parts(vec![0, 6, 8], 0) };
459
        system.push(eq);
460
        eq = unsafe { Modulo2Equation::from_parts(vec![0, 6, 9], 1) };
461
        system.push(eq);
462
        eq = unsafe { Modulo2Equation::from_parts(vec![2, 4, 8], 2) };
463
        system.push(eq);
464
        eq = unsafe { Modulo2Equation::from_parts(vec![2, 6, 10], 0) };
465
        system.push(eq);
466

467
        let solution = system.lazy_gaussian_elimination();
468
        assert!(solution.is_ok());
469
        assert!(system.check(&solution.unwrap()));
470
    }
471

472
    #[test]
473
    fn test_var_to_vec_builder() {
474
        let system = unsafe {
475
            Modulo2System::from_parts(
476
                11,
477
                vec![
478
                    Modulo2Equation::from_parts(vec![1, 4, 10], 0_usize),
479
                    Modulo2Equation::from_parts(vec![1, 4, 9], 1),
480
                    Modulo2Equation::from_parts(vec![0, 6, 8], 2),
481
                    Modulo2Equation::from_parts(vec![0, 6, 9], 3),
482
                    Modulo2Equation::from_parts(vec![2, 4, 8], 4),
483
                    Modulo2Equation::from_parts(vec![2, 6, 10], 5),
484
                ],
485
            )
486
        };
487
        let mut backing: Vec<usize> = vec![];
488
        let (var_to_eqs, _weight, _priority) = system.setup(&mut backing);
489
        let expected_res = vec![
490
            vec![2, 3],
491
            vec![0, 1],
492
            vec![4, 5],
493
            vec![],
494
            vec![0, 1, 4],
495
            vec![],
496
            vec![2, 3, 5],
497
            vec![],
498
            vec![2, 4],
499
            vec![1, 3],
500
            vec![0, 5],
501
        ];
502
        var_to_eqs
503
            .iter()
504
            .zip(expected_res.iter())
505
            .for_each(|(v, e)| v.iter().zip(e.iter()).for_each(|(x, y)| assert_eq!(x, y)));
506
    }
507
}
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