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

vigna / sux-rs / 14013051218

22 Mar 2025 10:46PM UTC coverage: 76.118% (-2.4%) from 78.482%
14013051218

push

github

vigna
Ported changes from zcs

18 of 51 new or added lines in 3 files covered. (35.29%)

142 existing lines in 6 files now uncovered.

3984 of 5234 relevant lines covered (76.12%)

125756095.75 hits per line

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

84.02
/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 {
729,845✔
42
        debug_assert!(vars.iter().is_sorted());
1,459,690✔
43
        Modulo2Equation { vars, c }
44
    }
45

46
    #[inline(always)]
47
    unsafe fn add_ptr(
1,844,470✔
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,120,280,318✔
55
            let less = *left <= *right;
1,059,009,695✔
56
            let more = *left >= *right;
1,059,009,695✔
57

58
            let src = if less { left } else { right };
1,059,009,695✔
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;
1,844,470✔
67
        ptr::copy_nonoverlapping(left, dst, rem_left);
1,844,470✔
68
        dst = dst.add(rem_left);
1,844,470✔
69
        let rem_right = right_end.offset_from(right) as usize;
1,844,470✔
70
        ptr::copy_nonoverlapping(right, dst, rem_right);
1,844,470✔
71
        dst = dst.add(rem_right);
1,844,470✔
72
        dst
1,844,470✔
73
    }
74

75
    pub fn add(&mut self, other: &Modulo2Equation<W>) {
1,844,469✔
76
        let left_range = self.vars.as_ptr_range();
1,844,469✔
77
        let left = left_range.start;
1,844,469✔
78
        let left_end = left_range.end;
1,844,469✔
79
        let right_range = other.vars.as_ptr_range();
1,844,469✔
80
        let right = right_range.start;
1,844,469✔
81
        let right_end = right_range.end;
1,844,469✔
82
        let mut vars = Vec::with_capacity(self.vars.len() + other.vars.len());
1,844,469✔
83
        let dst = vars.as_mut_ptr();
1,844,469✔
84

85
        unsafe {
86
            let copied =
1,844,469✔
87
                Self::add_ptr(left, left_end, right, right_end, dst).offset_from(dst) as usize;
1,844,469✔
88
            vars.set_len(copied);
1,844,469✔
89
        }
90

91
        self.vars = vars;
1,844,469✔
92
        self.c ^= other.c;
1,844,469✔
93
    }
94

95
    /// Checks whether the equation is unsolvable.
96
    fn is_unsolvable(&self) -> bool {
705,901✔
97
        self.vars.is_empty() && self.c != W::ZERO
706,343✔
98
    }
99

100
    /// Checks whether the equation is an identity.
101
    fn is_identity(&self) -> bool {
723,846✔
102
        self.vars.is_empty() && self.c == W::ZERO
723,847✔
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 {
595,087✔
107
        let mut sum = W::ZERO;
595,087✔
108
        for &var in vars.as_ref() {
304,723,364✔
109
            sum ^= values.as_ref()[var as usize];
×
110
        }
111
        sum
595,087✔
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()
25✔
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 {
598✔
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);
11✔
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);
3✔
152
        self.equations
3✔
153
            .iter()
154
            .all(|eq| eq.c == Modulo2Equation::<W>::eval_vars(&eq.vars, solution))
15✔
155
    }
156

157
    /// Puts the system into echelon form.
158
    fn echelon_form(&mut self) -> Result<()> {
132✔
159
        let equations = &mut self.equations;
132✔
160
        if equations.is_empty() {
132✔
161
            return Ok(());
2✔
162
        }
163
        'main: for i in 0..equations.len() - 1 {
19,807✔
164
            ensure!(!equations[i].vars.is_empty());
19,807✔
165
            for j in i + 1..equations.len() {
14,572,192✔
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>) };
14,552,385✔
169
                let eq_i = &mut equations[i];
14,552,385✔
170

171
                let first_var_j = eq_j.vars[0];
14,552,385✔
172

173
                if eq_i.vars[0] == first_var_j {
14,552,385✔
174
                    eq_i.add(eq_j);
679,900✔
175
                    if eq_i.is_unsolvable() {
679,900✔
176
                        bail!("System is unsolvable");
104✔
177
                    }
178
                    if eq_i.is_identity() {
679,796✔
UNCOV
179
                        continue 'main;
×
180
                    }
181
                }
182

183
                if eq_i.vars[0] > first_var_j {
14,552,281✔
184
                    equations.swap(i, j)
9,731,869✔
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>> {
132✔
193
        self.echelon_form()?;
236✔
194
        let mut solution = vec![W::ZERO; self.num_vars];
28✔
195
        self.equations
28✔
196
            .iter()
197
            .rev()
198
            .filter(|eq| !eq.is_identity())
18,414✔
199
            .for_each(|eq| {
18,386✔
200
                solution[eq.vars[0] as usize] =
18,386✔
201
                    eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
18,386✔
202
            });
203
        Ok(solution)
×
204
    }
205

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

221
        let mut total_vars = 0;
470✔
222
        for (i, equation) in self.equations.iter().enumerate() {
730,312✔
223
            priority[i] = equation.vars.len();
×
224
            total_vars += equation.vars.len();
×
225
            for &var in &equation.vars {
5,108,874✔
226
                weight[var as usize] += 1;
×
227
            }
228
        }
229

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

233
        backing.arbitrary_chunks_mut(&weight).for_each(|chunk| {
1,385,596✔
234
            var_to_eq.push(chunk);
1,385,126✔
235
        });
236

237
        let mut pos = vec![0usize; self.num_vars];
470✔
238
        for (i, equation) in self.equations.iter().enumerate() {
730,312✔
239
            for &var in &equation.vars {
5,108,874✔
240
                let var = var as usize;
×
241
                var_to_eq[var][pos[var]] = i;
×
242
                pos[var] += 1;
×
243
            }
244
        }
245

246
        (var_to_eq, weight, priority)
470✔
247
    }
248

249
    /// Solves the system using lazy Gaussian elimination.
250
    pub fn lazy_gaussian_elimination(&mut self) -> Result<Vec<W>> {
469✔
251
        let num_vars = self.num_vars;
469✔
252
        let num_equations = self.equations.len();
469✔
253

254
        if num_equations == 0 {
469✔
255
            return Ok(vec![W::ZERO; num_vars]);
×
256
        }
257

258
        let mut backing = vec![];
469✔
259
        let (var_to_eqs, mut weight, mut priority);
469✔
260
        (var_to_eqs, weight, priority) = self.setup(&mut backing);
469✔
261

262
        let mut variables = vec![0; num_vars];
469✔
263
        {
264
            let mut count = vec![0; num_equations + 1];
469✔
265

266
            for x in 0..num_vars {
1,385,115✔
267
                count[weight[x]] += 1
×
268
            }
269
            for i in 1..count.len() {
729,836✔
270
                count[i] += count[i - 1]
×
271
            }
272
            for i in (0..num_vars).rev() {
1,385,115✔
273
                count[weight[i]] -= 1;
×
274
                variables[count[weight[i]]] = i;
×
275
            }
276
        }
277

278
        let mut equation_list: Vec<usize> = (0..priority.len())
×
279
            .rev()
280
            .filter(|&x| priority[x] <= 1)
729,836✔
281
            .collect();
282

283
        let mut dense = vec![];
×
284
        let mut solved = vec![];
×
285
        let mut pivots = vec![];
×
286

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

290
        let mut remaining = equations.len();
×
291

292
        while remaining != 0 {
735,847✔
293
            if equation_list.is_empty() {
735,715✔
294
                let mut var = variables.pop().unwrap();
45,565✔
295
                while weight[var] == 0 {
648,797✔
296
                    var = variables.pop().unwrap()
603,232✔
297
                }
298
                idle.set(var, false);
45,565✔
299
                var_to_eqs[var].as_ref().iter().for_each(|&eq| {
300,471✔
300
                    priority[eq] -= 1;
254,906✔
301
                    if priority[eq] == 1 {
254,906✔
302
                        equation_list.push(eq)
47,743✔
303
                    }
304
                });
305
            } else {
306
                remaining -= 1;
690,150✔
307
                let first = equation_list.pop().unwrap();
690,150✔
308

309
                if priority[first] == 0 {
690,150✔
310
                    let equation = &mut equations[first];
26,001✔
311
                    if equation.is_unsolvable() {
26,001✔
312
                        bail!("System is unsolvable")
337✔
313
                    }
314
                    if equation.is_identity() {
25,664✔
315
                        continue;
1✔
316
                    }
317
                    dense.push(equation.to_owned());
25,663✔
318
                } else if priority[first] == 1 {
1,328,298✔
319
                    // SAFETY: to add the equations, multiple references to the vector
320
                    // of equations are needed, one of which is mutable
321
                    let equation = unsafe { &*(&equations[first] as *const Modulo2Equation<W>) };
664,149✔
322

323
                    let pivot = equation
664,149✔
324
                        .vars
664,149✔
325
                        .iter()
326
                        .copied()
327
                        .find(|x| idle.get(*x as usize))
148,920,030✔
328
                        .expect("Missing expected idle variable in equation");
329
                    pivots.push(pivot as usize);
664,149✔
330
                    solved.push(first);
664,149✔
331
                    weight[pivot as usize] = 0;
664,149✔
332
                    var_to_eqs[pivot as usize]
664,149✔
333
                        .as_ref()
334
                        .iter()
335
                        .filter(|&&eq_idx| eq_idx != first)
3,157,015✔
336
                        .for_each(|&eq| {
1,828,717✔
337
                            equations[eq].add(equation);
1,164,568✔
338

339
                            priority[eq] -= 1;
1,164,568✔
340
                            if priority[eq] == 1 {
1,164,568✔
341
                                equation_list.push(eq)
643,216✔
342
                            }
343
                        });
344
                }
345
            }
346
        }
347

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

353
        for i in 0..solved.len() {
576,692✔
354
            let eq = &equations[solved[i]];
576,692✔
355
            let pivot = pivots[i];
576,692✔
356
            assert!(solution[pivot] == W::ZERO);
576,692✔
357
            solution[pivot] = eq.c ^ Modulo2Equation::<W>::eval_vars(&eq.vars, &solution);
576,692✔
358
        }
359

360
        Ok(solution)
28✔
361
    }
362
}
363

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

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

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

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

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

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

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

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

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

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

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