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

vigna / sux-rs / 15155442919

21 May 2025 06:48AM UTC coverage: 69.033% (-6.4%) from 75.47%
15155442919

push

github

web-flow
Merge pull request #62 from progval/patch-3

3627 of 5254 relevant lines covered (69.03%)

124437649.62 hits per line

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

77.51
/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
#![allow(unexpected_cfgs)]
10
#![allow(clippy::comparison_chain)]
11
use std::ptr;
12

13
use crate::{bit_vec, traits::Word};
14
use anyhow::{bail, ensure, Result};
15
use arbitrary_chunks::ArbitraryChunks;
16

17
/// An equation on `W::BITS`-dimensional vectors with coefficients in **F**â‚‚.
18
#[derive(Clone, Debug)]
19
pub struct Modulo2Equation<W: Word = usize> {
20
    /// The variables in increasing order.
21
    vars: Vec<u32>,
22
    /// The constant term
23
    c: W,
24
}
25

26
/// A system of [equations](struct.Modulo2Equation.html).
27
#[derive(Clone, Debug)]
28
pub struct Modulo2System<W: Word = usize> {
29
    /// The number of variables.
30
    num_vars: usize,
31
    /// The equations in the system.
32
    equations: Vec<Modulo2Equation<W>>,
33
}
34

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

46
    #[inline(always)]
47
    unsafe fn add_ptr(
1,756,242✔
48
        mut left: *const u32,
49
        left_end: *const u32,
50
        mut right: *const u32,
51
        right_end: *const u32,
52
        mut dst: *mut u32,
53
    ) -> *mut u32 {
54
        while left != left_end && right != right_end {
2,147,483,647✔
55
            let less = *left <= *right;
1,099,765,859✔
56
            let more = *left >= *right;
×
57

58
            let src = if less { left } else { right };
1,099,765,859✔
59
            *dst = *src;
×
60

61
            left = left.add(less as usize);
×
62
            right = right.add(more as usize);
×
63
            dst = dst.add((less ^ more) as usize);
×
64
        }
65

66
        let rem_left = left_end.offset_from(left) as usize;
5,268,726✔
67
        ptr::copy_nonoverlapping(left, dst, rem_left);
7,024,968✔
68
        dst = dst.add(rem_left);
3,512,484✔
69
        let rem_right = right_end.offset_from(right) as usize;
5,268,726✔
70
        ptr::copy_nonoverlapping(right, dst, rem_right);
7,024,968✔
71
        dst = dst.add(rem_right);
3,512,484✔
72
        dst
1,756,242✔
73
    }
74

75
    pub fn add(&mut self, other: &Modulo2Equation<W>) {
1,756,241✔
76
        let left_range = self.vars.as_ptr_range();
3,512,482✔
77
        let left = left_range.start;
3,512,482✔
78
        let left_end = left_range.end;
3,512,482✔
79
        let right_range = other.vars.as_ptr_range();
3,512,482✔
80
        let right = right_range.start;
3,512,482✔
81
        let right_end = right_range.end;
3,512,482✔
82
        let mut vars = Vec::with_capacity(self.vars.len() + other.vars.len());
8,781,205✔
83
        let dst = vars.as_mut_ptr();
5,268,723✔
84

85
        unsafe {
86
            let copied =
3,512,482✔
87
                Self::add_ptr(left, left_end, right, right_end, dst).offset_from(dst) as usize;
14,049,928✔
88
            vars.set_len(copied);
3,512,482✔
89
        }
90

91
        self.vars = vars;
3,512,482✔
92
        self.c ^= other.c;
1,756,241✔
93
    }
94

95
    /// Checks whether the equation is unsolvable.
96
    fn is_unsolvable(&self) -> bool {
736,008✔
97
        self.vars.is_empty() && self.c != W::ZERO
1,472,148✔
98
    }
99

100
    /// Checks whether the equation is an identity.
101
    fn is_identity(&self) -> bool {
754,757✔
102
        self.vars.is_empty() && self.c == W::ZERO
1,509,515✔
103
    }
104

105
    /// Evaluates the XOR of the values associated to the given variables.
106
    fn eval_vars(vars: impl AsRef<[u32]>, values: impl AsRef<[W]>) -> W {
589,403✔
107
        let mut sum = W::ZERO;
1,178,806✔
108
        for &var in vars.as_ref() {
302,109,293✔
109
            sum ^= values.as_ref()[var as usize];
×
110
        }
111
        sum
589,403✔
112
    }
113
}
114

115
impl<W: Word> Modulo2System<W> {
116
    pub fn new(num_vars: usize) -> Self {
4✔
117
        Modulo2System {
118
            num_vars,
119
            equations: vec![],
4✔
120
        }
121
    }
122

123
    pub fn num_vars(&self) -> usize {
×
124
        self.num_vars
×
125
    }
126

127
    pub fn num_equations(&self) -> usize {
25✔
128
        self.equations.len()
50✔
129
    }
130

131
    /// Creates a new `Modulo2System` from existing equations.
132
    ///
133
    /// # Safety
134
    ///
135
    /// The caller must ensure that variables in each equation match the number
136
    /// of variables in the system.
137
    pub unsafe fn from_parts(num_vars: usize, equations: Vec<Modulo2Equation<W>>) -> Self {
215✔
138
        Modulo2System {
139
            num_vars,
140
            equations,
141
        }
142
    }
143

144
    /// Adds an equation to the system.
145
    pub fn push(&mut self, equation: Modulo2Equation<W>) {
11✔
146
        self.equations.push(equation);
33✔
147
    }
148

149
    /// Checks if a given solution satisfies the system of equations.
150
    pub fn check(&self, solution: &[W]) -> bool {
3✔
151
        assert_eq!(solution.len(), self.num_vars, "The number of variables in the solution ({}) does not match the number of variables in the system ({})", solution.len(), self.num_vars);
9✔
152
        self.equations
3✔
153
            .iter()
154
            .all(|eq| eq.c == Modulo2Equation::<W>::eval_vars(&eq.vars, solution))
30✔
155
    }
156

157
    /// Puts the system into echelon form.
158
    fn echelon_form(&mut self) -> Result<()> {
59✔
159
        let equations = &mut self.equations;
118✔
160
        if equations.is_empty() {
118✔
161
            return Ok(());
2✔
162
        }
163
        'main: for i in 0..equations.len() - 1 {
19,211✔
164
            ensure!(!equations[i].vars.is_empty());
38,422✔
165
            for j in i + 1..equations.len() {
15,020,695✔
166
                // SAFETY: to add the two equations, multiple references to the vector
167
                // of equations are needed, one of which is mutable
168
                let eq_j = unsafe { &*(&equations[j] as *const Modulo2Equation<W>) };
30,002,968✔
169
                let eq_i = &mut equations[i];
30,002,968✔
170

171
                let first_var_j = eq_j.vars[0];
30,002,968✔
172

173
                if eq_i.vars[0] == first_var_j {
15,001,484✔
174
                    eq_i.add(eq_j);
2,144,895✔
175
                    if eq_i.is_unsolvable() {
1,429,930✔
176
                        bail!("System is unsolvable");
31✔
177
                    }
178
                    if eq_i.is_identity() {
×
179
                        continue 'main;
×
180
                    }
181
                }
182

183
                if eq_i.vars[0] > first_var_j {
15,001,453✔
184
                    equations.swap(i, j)
30,317,403✔
185
                }
186
            }
187
        }
188
        Ok(())
26✔
189
    }
190

191
    /// Solves the system using Gaussian elimination.
192
    pub fn gaussian_elimination(&mut self) -> Result<Vec<W>> {
59✔
193
        self.echelon_form()?;
149✔
194
        let mut solution = vec![W::ZERO; self.num_vars];
28✔
195
        self.equations
×
196
            .iter()
197
            .rev()
198
            .filter(|eq| !eq.is_identity())
37,760✔
199
            .for_each(|eq| {
18,880✔
200
                solution[eq.vars[0] as usize] =
56,640✔
201
                    eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
56,640✔
202
            });
203
        Ok(solution)
×
204
    }
205

206
    /// Builds the data structures needed for [lazy Gaussian
207
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517).
208
    ///
209
    /// This method returns the variable-to-equation mapping, the weight of each
210
    /// variable (the number of equations in which it appears), and the priority
211
    /// of each equation (the number of variables in it). The
212
    /// variable-to-equation mapping is materialized as a vector of mutable
213
    /// slices, each of which points inside a provided backing vector. This
214
    /// approach greatly reduces the number of allocations.
215
    fn setup<'a>(
160✔
216
        &self,
217
        backing: &'a mut Vec<usize>,
218
    ) -> (Vec<&'a mut [usize]>, Vec<usize>, Vec<usize>) {
219
        let mut weight = vec![0; self.num_vars];
480✔
220
        let mut priority = vec![0; self.equations.len()];
640✔
221

222
        let mut total_vars = 0;
320✔
223
        for (i, equation) in self.equations.iter().enumerate() {
630,353✔
224
            priority[i] = equation.vars.len();
×
225
            total_vars += equation.vars.len();
×
226
            for &var in &equation.vars {
4,410,211✔
227
                weight[var as usize] += 1;
×
228
            }
229
        }
230

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

234
        backing.arbitrary_chunks_mut(&weight).for_each(|chunk| {
1,223,490✔
235
            var_to_eq.push(chunk);
3,669,030✔
236
        });
237

238
        let mut pos = vec![0usize; self.num_vars];
480✔
239
        for (i, equation) in self.equations.iter().enumerate() {
630,353✔
240
            for &var in &equation.vars {
4,410,211✔
241
                let var = var as usize;
×
242
                var_to_eq[var][pos[var]] = i;
×
243
                pos[var] += 1;
×
244
            }
245
        }
246

247
        (var_to_eq, weight, priority)
320✔
248
    }
249

250
    /// Solves the system using [lazy Gaussian
251
    /// elimination](https://doi.org/10.1016/j.ic.2020.104517).
252
    pub fn lazy_gaussian_elimination(&mut self) -> Result<Vec<W>> {
159✔
253
        let num_vars = self.num_vars;
318✔
254
        let num_equations = self.equations.len();
477✔
255

256
        if num_equations == 0 {
159✔
257
            return Ok(vec![W::ZERO; num_vars]);
×
258
        }
259

260
        let mut backing = vec![];
×
261
        let (var_to_eqs, mut weight, mut priority);
×
262
        (var_to_eqs, weight, priority) = self.setup(&mut backing);
×
263

264
        let mut variables = vec![0; num_vars];
×
265
        {
266
            let mut count = vec![0; num_equations + 1];
×
267

268
            for x in 0..num_vars {
1,222,999✔
269
                count[weight[x]] += 1
×
270
            }
271
            for i in 1..count.len() {
630,027✔
272
                count[i] += count[i - 1]
×
273
            }
274
            for i in (0..num_vars).rev() {
1,222,999✔
275
                count[weight[i]] -= 1;
×
276
                variables[count[weight[i]]] = i;
×
277
            }
278
        }
279

280
        let mut equation_list: Vec<usize> = (0..priority.len())
×
281
            .rev()
282
            .filter(|&x| priority[x] <= 1)
630,027✔
283
            .collect();
284

285
        let mut dense = vec![];
×
286
        let mut solved = vec![];
×
287
        let mut pivots = vec![];
×
288

289
        let equations = &mut self.equations;
×
290
        let mut idle = bit_vec![true; num_vars];
×
291

292
        let mut remaining = equations.len();
×
293

294
        while remaining != 0 {
661,774✔
295
            if equation_list.is_empty() {
1,323,430✔
296
                let mut var = variables.pop().unwrap();
162,520✔
297
                while weight[var] == 0 {
591,296✔
298
                    var = variables.pop().unwrap()
550,666✔
299
                }
300
                idle.set(var, false);
121,890✔
301
                var_to_eqs[var].as_ref().iter().for_each(|&eq| {
347,681✔
302
                    priority[eq] -= 1;
225,791✔
303
                    if priority[eq] == 1 {
225,791✔
304
                        equation_list.push(eq)
127,209✔
305
                    }
306
                });
307
            } else {
308
                remaining -= 1;
621,085✔
309
                let first = equation_list.pop().unwrap();
×
310

311
                if priority[first] == 0 {
×
312
                    let equation = &mut equations[first];
42,086✔
313
                    if equation.is_unsolvable() {
42,086✔
314
                        bail!("System is unsolvable")
100✔
315
                    }
316
                    if equation.is_identity() {
×
317
                        continue;
1✔
318
                    }
319
                    dense.push(equation.to_owned());
83,768✔
320
                } else if priority[first] == 1 {
600,042✔
321
                    // SAFETY: to add the equations, multiple references to the vector
322
                    // of equations are needed, one of which is mutable
323
                    let equation = unsafe { &*(&equations[first] as *const Modulo2Equation<W>) };
1,200,084✔
324

325
                    let pivot = equation
1,200,084✔
326
                        .vars
600,042✔
327
                        .iter()
328
                        .copied()
329
                        .find(|x| idle.get(*x as usize))
467,670,243✔
330
                        .expect("Missing expected idle variable in equation");
331
                    pivots.push(pivot as usize);
1,800,126✔
332
                    solved.push(first);
1,800,126✔
333
                    weight[pivot as usize] = 0;
600,042✔
334
                    var_to_eqs[pivot as usize]
600,042✔
335
                        .as_ref()
336
                        .iter()
337
                        .filter(|&&eq_idx| eq_idx != first)
3,882,676✔
338
                        .for_each(|&eq| {
1,641,317✔
339
                            equations[eq].add(equation);
3,123,825✔
340

341
                            priority[eq] -= 1;
1,041,275✔
342
                            if priority[eq] == 1 {
1,041,275✔
343
                                equation_list.push(eq)
1,737,117✔
344
                            }
345
                        });
346
                }
347
            }
348
        }
349

350
        // SAFETY: the usage of the method is safe, as the equations have the
351
        // right number of variables
352
        let mut dense_system = unsafe { Modulo2System::from_parts(num_vars, dense) };
59✔
353
        let mut solution = dense_system.gaussian_elimination()?;
59✔
354

355
        for i in 0..solved.len() {
570,514✔
356
            let eq = &equations[solved[i]];
1,711,542✔
357
            let pivot = pivots[i];
1,141,028✔
358
            assert!(solution[pivot] == W::ZERO);
1,141,028✔
359
            solution[pivot] = eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
570,514✔
360
        }
361

362
        Ok(solution)
28✔
363
    }
364
}
365

366
#[cfg(test)]
367
mod tests {
368
    use super::*;
369

370
    #[test]
371
    fn test_add_ptr() {
372
        let a = [0, 1, 3, 4];
373
        let b = [0, 2, 4, 5];
374
        let mut c = Vec::with_capacity(a.len() + b.len());
375

376
        let ra = a.as_ptr_range();
377
        let rb = b.as_ptr_range();
378
        let mut dst = c.as_mut_ptr();
379
        unsafe {
380
            dst = Modulo2Equation::<u32>::add_ptr(ra.start, ra.end, rb.start, rb.end, dst);
381
            assert_eq!(dst.offset_from(c.as_ptr()), 4);
382
            c.set_len(4);
383
            assert_eq!(c, vec![1, 2, 3, 5]);
384
        }
385
    }
386

387
    #[test]
388
    fn test_equation_builder() {
389
        let eq = unsafe { Modulo2Equation::from_parts(vec![0, 1, 2], 3_usize) };
390
        assert_eq!(eq.vars.len(), 3);
391
        assert_eq!(eq.vars.to_vec(), vec![0, 1, 2]);
392
    }
393

394
    #[test]
395
    fn test_equation_addition() {
396
        let mut eq0 = unsafe { Modulo2Equation::from_parts(vec![1, 4, 9], 3_usize) };
397
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![1, 4, 10], 3_usize) };
398
        eq0.add(&eq1);
399
        assert_eq!(eq0.vars, vec![9, 10]);
400
        assert_eq!(eq0.c, 0);
401
    }
402

403
    #[test]
404
    fn test_system_one_equation() {
405
        let mut system = Modulo2System::<usize>::new(2);
406
        let eq = unsafe { Modulo2Equation::from_parts(vec![0], 3_usize) };
407
        system.push(eq);
408
        let solution = system.lazy_gaussian_elimination();
409
        assert!(solution.is_ok());
410
        assert!(system.check(&solution.unwrap()));
411
    }
412

413
    #[test]
414
    fn test_impossible_system() {
415
        let mut system = Modulo2System::<usize>::new(1);
416
        let eq0 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
417
        system.push(eq0);
418
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![0], 1_usize) };
419
        system.push(eq1);
420
        let solution = system.lazy_gaussian_elimination();
421
        assert!(solution.is_err());
422
    }
423

424
    #[test]
425
    fn test_redundant_system() {
426
        let mut system = Modulo2System::<usize>::new(1);
427
        let eq0 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
428
        system.push(eq0);
429
        let eq1 = unsafe { Modulo2Equation::from_parts(vec![0], 0_usize) };
430
        system.push(eq1);
431
        let solution = system.lazy_gaussian_elimination();
432
        assert!(solution.is_ok());
433
        assert!(system.check(&solution.unwrap()));
434
    }
435

436
    #[test]
437
    fn test_small_system() {
438
        let mut system = Modulo2System::<usize>::new(11);
439
        let mut eq = unsafe { Modulo2Equation::from_parts(vec![1, 4, 10], 0) };
440
        system.push(eq);
441
        eq = unsafe { Modulo2Equation::from_parts(vec![1, 4, 9], 2) };
442
        system.push(eq);
443
        eq = unsafe { Modulo2Equation::from_parts(vec![0, 6, 8], 0) };
444
        system.push(eq);
445
        eq = unsafe { Modulo2Equation::from_parts(vec![0, 6, 9], 1) };
446
        system.push(eq);
447
        eq = unsafe { Modulo2Equation::from_parts(vec![2, 4, 8], 2) };
448
        system.push(eq);
449
        eq = unsafe { Modulo2Equation::from_parts(vec![2, 6, 10], 0) };
450
        system.push(eq);
451

452
        let solution = system.lazy_gaussian_elimination();
453
        assert!(solution.is_ok());
454
        assert!(system.check(&solution.unwrap()));
455
    }
456

457
    #[test]
458
    fn test_var_to_vec_builder() {
459
        let system = unsafe {
460
            Modulo2System::from_parts(
461
                11,
462
                vec![
463
                    Modulo2Equation::from_parts(vec![1, 4, 10], 0_usize),
464
                    Modulo2Equation::from_parts(vec![1, 4, 9], 1),
465
                    Modulo2Equation::from_parts(vec![0, 6, 8], 2),
466
                    Modulo2Equation::from_parts(vec![0, 6, 9], 3),
467
                    Modulo2Equation::from_parts(vec![2, 4, 8], 4),
468
                    Modulo2Equation::from_parts(vec![2, 6, 10], 5),
469
                ],
470
            )
471
        };
472
        let mut backing: Vec<usize> = vec![];
473
        let (var_to_eqs, _weight, _priority) = system.setup(&mut backing);
474
        let expected_res = vec![
475
            vec![2, 3],
476
            vec![0, 1],
477
            vec![4, 5],
478
            vec![],
479
            vec![0, 1, 4],
480
            vec![],
481
            vec![2, 3, 5],
482
            vec![],
483
            vec![2, 4],
484
            vec![1, 3],
485
            vec![0, 5],
486
        ];
487
        var_to_eqs
488
            .iter()
489
            .zip(expected_res.iter())
490
            .for_each(|(v, e)| v.iter().zip(e.iter()).for_each(|(x, y)| assert_eq!(x, y)));
491
    }
492
}
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