Skip to main content

yui_matrix/
perm.rs

1//! Permutation of `0..n`, used to track row/column reorderings during
2//! matrix decompositions (PLUQ, pivot search, Schur complement).
3//!
4//! Internally, the identity permutation is represented by just its size,
5//! making `Perm::id(n)` zero-cost.
6
7use std::ops::{Mul, MulAssign};
8use auto_impl_ops::auto_ops;
9use either::Either;
10
11/// A permutation of `0..n`.
12///
13/// Stored as `Either<usize, Vec<usize>>`: `Left(n)` is the identity
14/// permutation on `0..n`, and `Right(v)` is an explicit image vector
15/// where `v[i]` is the image of `i`.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct Perm {
18    data: Either<usize, Vec<usize>>,
19}
20
21impl Perm {
22    /// Create from the image vector. `data[i]` is the image of `i`.
23    /// Panics if `data` is not a valid permutation of `0..data.len()`.
24    // checked in release too: `apply_to`'s raw-pointer writes rely on this.
25    pub(crate) fn new(data: Vec<usize>) -> Self {
26        assert!(is_valid_perm(&data), "not a valid permutation: {:?}", data);
27        Self::new_unchecked(data)
28    }
29
30    // for callers that already established the invariant; skips the O(n) re-scan.
31    fn new_unchecked(data: Vec<usize>) -> Self {
32        Self { data: Either::Right(data) }
33    }
34
35    /// Create from an iterator of images. The `i`-th item is the image of `i`.
36    /// Panics if the resulting sequence is not a valid permutation.
37    pub fn from_indices<I>(images: I) -> Self
38    where I: IntoIterator<Item = usize> {
39        Self::new(images.into_iter().collect())
40    }
41
42    /// The identity permutation on `0..n`. Zero-cost.
43    pub fn id(n: usize) -> Self {
44        Self { data: Either::Left(n) }
45    }
46
47    pub fn len(&self) -> usize {
48        match &self.data {
49            Either::Left(n) => *n,
50            Either::Right(v) => v.len(),
51        }
52    }
53
54    /// The image of `i`.
55    pub fn at(&self, i: usize) -> usize {
56        match &self.data {
57            Either::Left(_) => i,
58            Either::Right(v) => v[i],
59        }
60    }
61
62    /// `true` iff this is the identity permutation.
63    pub fn is_id(&self) -> bool {
64        match &self.data {
65            Either::Left(_) => true,
66            Either::Right(v) => v.iter().enumerate().all(|(i, &x)| i == x),
67        }
68    }
69
70    /// The inverse permutation.
71    pub fn inv(&self) -> Self {
72        match &self.data {
73            Either::Left(n) => Self::id(*n),
74            Either::Right(v) => {
75                let mut inv = vec![0; v.len()];
76                for (i, &j) in v.iter().enumerate() {
77                    inv[j] = i;
78                }
79                Self { data: Either::Right(inv) }
80            }
81        }
82    }
83
84    /// Left group action consuming `y`: returns a vector `result` such that
85    /// `result[self.at(i)] = y[i]` for every `i`.
86    pub fn apply_to<R>(&self, y: Vec<R>) -> Vec<R> {
87        assert_eq!(y.len(), self.len());
88        match &self.data {
89            Either::Left(_) => y,
90            Either::Right(v) => {
91                let n = v.len();
92                let mut result: Vec<R> = Vec::with_capacity(n);
93                let dst = result.as_mut_ptr();
94                for (i, x) in y.into_iter().enumerate() {
95                    // SAFETY: v is a permutation of 0..n (Perm invariant),
96                    // so dst.add(v[i]) is in bounds and each slot is written exactly once.
97                    unsafe { dst.add(v[i]).write(x); }
98                }
99                // SAFETY: all n slots have been written.
100                unsafe { result.set_len(n); }
101                result
102            }
103        }
104    }
105
106    /// Inverse-left action consuming `y`: returns a vector `result` such that
107    /// `result[k] = y[self.at(k)]` for every `k`. Equivalent to
108    /// `self.inv().apply_to(y)` but without allocating an inverse permutation.
109    pub fn apply_inv_to<R>(&self, y: Vec<R>) -> Vec<R> {
110        assert_eq!(y.len(), self.len());
111        match &self.data {
112            Either::Left(_) => y,
113            Either::Right(v) => {
114                let mut y = std::mem::ManuallyDrop::new(y);
115                let src = y.as_mut_ptr();
116                let cap = y.capacity();
117                // SAFETY: v is a permutation of 0..n, so each y[k] is read exactly once.
118                let result: Vec<R> = v.iter().map(|&k| unsafe { src.add(k).read() }).collect();
119                // SAFETY: every element of y has been moved out; deallocate the buffer
120                // by reconstructing a zero-length Vec with the original capacity.
121                unsafe { drop(Vec::from_raw_parts(src, 0, cap)); }
122                result
123            }
124        }
125    }
126
127    /// Returns a permutation of dimension `self.len() + r` that is the
128    /// identity on `[0..r)` and acts as `self` (shifted by `r`) on
129    /// `[r..r + self.len())`. Identity is preserved (zero-cost).
130    pub fn shift(self, r: usize) -> Self {
131        match self.data {
132            Either::Left(n) => Self::id(n + r),
133            Either::Right(v) => {
134                let mut data: Vec<usize> = (0..r).collect();
135                data.extend(v.into_iter().map(|x| x + r));
136                Self::new(data)
137            }
138        }
139    }
140
141    /// Returns a permutation of dimension `self.len() + r` that acts as
142    /// `self` on `[0..self.len())` and the identity on the appended
143    /// `[self.len()..self.len() + r)`. Identity is preserved (zero-cost).
144    pub fn extend(self, r: usize) -> Self {
145        match self.data {
146            Either::Left(n) => Self::id(n + r),
147            Either::Right(mut v) => {
148                let m = v.len();
149                v.extend(m..m + r);
150                Self::new(v)
151            }
152        }
153    }
154
155    /// Permutation `p` of `0..n` that sends each index in `prefix` to a
156    /// position `0, 1, 2, ...` (in the order given), with the remaining
157    /// indices filling positions in sorted order.
158    /// If `prefix` is empty, returns the identity (zero-cost).
159    pub fn forward_indices<I>(n: usize, prefix: I) -> Self
160    where I: IntoIterator<Item = usize> {
161        let mut data = vec![0usize; n];
162        let mut taken = vec![false; n];
163        let mut k = 0;
164        for i in prefix {
165            assert!(i < n, "index {i} out of range 0..{n}");
166            assert!(!taken[i], "duplicate index {i} in prefix");
167            data[i] = k;
168            taken[i] = true;
169            k += 1;
170        }
171        if k == 0 {
172            return Self::id(n);
173        }
174        let mut pos = k;
175        for j in 0..n {
176            if !taken[j] {
177                data[j] = pos;
178                pos += 1;
179            }
180        }
181        Self::new_unchecked(data)
182    }
183}
184
185/// Composition `(p * q)(i) = p(q(i))` (right-to-left, math convention).
186#[auto_ops]
187impl Mul<&Perm> for &Perm {
188    type Output = Perm;
189    fn mul(self, rhs: &Perm) -> Perm {
190        assert_eq!(self.len(), rhs.len(), "permutations must have the same length");
191        match (&self.data, &rhs.data) {
192            (Either::Left(_), _) => rhs.clone(),
193            (_, Either::Left(_)) => self.clone(),
194            (Either::Right(_), Either::Right(qv)) => {
195                Perm::new(qv.iter().map(|&i| self.at(i)).collect())
196            }
197        }
198    }
199}
200
201fn is_valid_perm(v: &[usize]) -> bool {
202    let n = v.len();
203    let mut seen = vec![false; n];
204    for &x in v {
205        if x >= n || seen[x] {
206            return false;
207        }
208        seen[x] = true;
209    }
210    true
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    // --- constructors ---
218
219    #[test]
220    fn id() {
221        let p = Perm::id(4);
222        assert!(p.is_id());
223        assert_eq!(p.len(), 4);
224        for i in 0..4 {
225            assert_eq!(p.at(i), i);
226        }
227    }
228
229    #[test]
230    fn new_accepts_valid() {
231        let p = Perm::new(vec![2, 0, 1]);
232        assert_eq!(p.at(0), 2);
233        assert_eq!(p.at(1), 0);
234        assert_eq!(p.at(2), 1);
235    }
236
237    #[test]
238    #[should_panic]
239    fn new_rejects_duplicate() {
240        let _ = Perm::new(vec![0, 0, 1]);
241    }
242
243    #[test]
244    #[should_panic]
245    fn new_rejects_out_of_range() {
246        let _ = Perm::new(vec![0, 1, 5]);
247    }
248
249    // --- accessors ---
250
251    #[test]
252    fn is_id_true_false() {
253        assert!(Perm::id(3).is_id());
254        // identity image vector via `new` is still detected as identity
255        assert!(Perm::new(vec![0, 1, 2]).is_id());
256        assert!(!Perm::new(vec![1, 0]).is_id());
257    }
258
259    #[test]
260    fn accessors() {
261        let v = vec![2, 0, 1, 3];
262        let p = Perm::new(v.clone());
263        assert_eq!(p.len(), 4);
264        for (i, &expected) in v.iter().enumerate() {
265            assert_eq!(p.at(i), expected);
266        }
267    }
268
269    // --- inverse ---
270
271    #[test]
272    fn inv_of_id() {
273        let id = Perm::id(5);
274        assert_eq!(id.inv(), id);
275    }
276
277    #[test]
278    fn inv_of_inv() {
279        let p = Perm::new(vec![2, 0, 3, 1]);
280        assert_eq!(p.inv().inv(), p);
281    }
282
283    #[test]
284    fn inv_roundtrip() {
285        let p = Perm::new(vec![2, 0, 3, 1]);
286        let pi = p.inv();
287        for i in 0..p.len() {
288            assert_eq!(pi.at(p.at(i)), i);
289            assert_eq!(p.at(pi.at(i)), i);
290        }
291    }
292
293    // --- composition ---
294
295    #[test]
296    fn mul_formula() {
297        // (p * q)(i) = p(q(i))
298        let p = Perm::new(vec![2, 0, 3, 1]);
299        let q = Perm::new(vec![1, 2, 0, 3]);
300        let pq = &p * &q;
301        for i in 0..4 {
302            assert_eq!(pq.at(i), p.at(q.at(i)));
303        }
304    }
305
306    #[test]
307    fn mul_with_id() {
308        let p = Perm::new(vec![2, 0, 3, 1]);
309        let id = Perm::id(4);
310        assert_eq!(&p * &id, p);
311        assert_eq!(&id * &p, p);
312    }
313
314    #[test]
315    fn mul_id_with_id() {
316        let id = Perm::id(4);
317        assert_eq!(&id * &id, id);
318    }
319
320    #[test]
321    fn mul_by_inverse_is_id() {
322        let p = Perm::new(vec![2, 0, 3, 1]);
323        assert!((&p * &p.inv()).is_id());
324        assert!((&p.inv() * &p).is_id());
325    }
326
327    #[test]
328    fn mul_associative() {
329        let p = Perm::new(vec![2, 0, 3, 1]);
330        let q = Perm::new(vec![1, 3, 0, 2]);
331        let r = Perm::new(vec![3, 1, 2, 0]);
332        assert_eq!(&(&p * &q) * &r, &p * &(&q * &r));
333    }
334
335    #[test]
336    #[should_panic]
337    fn mul_panics_on_dim_mismatch() {
338        let p = Perm::id(3);
339        let q = Perm::id(4);
340        let _ = &p * &q;
341    }
342
343    // --- apply_to / apply_inv_to (group actions) ---
344
345    #[test]
346    fn apply_to_consumes() {
347        // p = [1, 2, 0]: 0→1, 1→2, 2→0.
348        // result[p(i)] = y[i] ⇒ result = [y[2], y[0], y[1]] = [30, 10, 20].
349        let p = Perm::from_indices([1, 2, 0]);
350        assert_eq!(p.apply_to(vec![10, 20, 30]), vec![30, 10, 20]);
351    }
352
353    #[test]
354    fn apply_to_by_id() {
355        let id = Perm::id(4);
356        assert_eq!(id.apply_to(vec![1, 2, 3, 4]), vec![1, 2, 3, 4]);
357    }
358
359    #[test]
360    fn apply_inv_to_consumes() {
361        // p = [1, 2, 0]; result[k] = y[p(k)] ⇒ result = [y[1], y[2], y[0]] = [20, 30, 10].
362        let p = Perm::from_indices([1, 2, 0]);
363        assert_eq!(p.apply_inv_to(vec![10, 20, 30]), vec![20, 30, 10]);
364    }
365
366    #[test]
367    fn apply_inv_to_inverts_apply_to() {
368        // For any p and y: p.apply_inv_to(p.apply_to(y)) == y.
369        let p = Perm::from_indices([2, 0, 3, 1]);
370        let y = vec![10, 20, 30, 40];
371        let permuted = p.apply_to(y.clone());
372        assert_eq!(p.apply_inv_to(permuted), y);
373    }
374
375    #[test]
376    #[should_panic]
377    fn apply_to_panics_on_len_mismatch() {
378        let p = Perm::id(3);
379        let _ = p.apply_to(vec![1, 2]);
380    }
381
382    // --- shift ---
383
384    #[test]
385    fn shift_basic() {
386        // perm = [1, 2, 0], shift by 2 → [0, 1, 3, 4, 2]
387        let p = Perm::from_indices([1, 2, 0]);
388        let s = p.shift(2);
389        assert_eq!(s.len(), 5);
390        for (i, &x) in [0, 1, 3, 4, 2].iter().enumerate() {
391            assert_eq!(s.at(i), x);
392        }
393    }
394
395    #[test]
396    fn shift_zero() {
397        let p = Perm::from_indices([2, 0, 1]);
398        let s = p.clone().shift(0);
399        assert_eq!(s, p);
400    }
401
402    #[test]
403    fn shift_of_id() {
404        // Shifting an identity stays identity, and its dim grows.
405        let s = Perm::id(3).shift(2);
406        assert!(s.is_id());
407        assert_eq!(s.len(), 5);
408    }
409
410    // --- extend ---
411
412    #[test]
413    fn extend_basic() {
414        // perm = [1, 2, 0], extend by 2 → [1, 2, 0, 3, 4]
415        let p = Perm::from_indices([1, 2, 0]);
416        let s = p.extend(2);
417        assert_eq!(s.len(), 5);
418        for (i, &x) in [1, 2, 0, 3, 4].iter().enumerate() {
419            assert_eq!(s.at(i), x);
420        }
421    }
422
423    #[test]
424    fn extend_zero() {
425        let p = Perm::from_indices([2, 0, 1]);
426        let s = p.clone().extend(0);
427        assert_eq!(s, p);
428    }
429
430    #[test]
431    fn extend_of_id() {
432        // Extending an identity stays identity, and its dim grows.
433        let s = Perm::id(3).extend(2);
434        assert!(s.is_id());
435        assert_eq!(s.len(), 5);
436    }
437
438    // --- forward_indices ---
439
440    #[test]
441    fn forward_indices_basic() {
442        // n=5, prefix=[3,1] → sends 3→0, 1→1, others fill sorted: 0→2, 2→3, 4→4.
443        let p = Perm::forward_indices(5, [3, 1]);
444        let expected = [2, 1, 3, 0, 4];
445        for (i, &x) in expected.iter().enumerate() {
446            assert_eq!(p.at(i), x);
447        }
448    }
449
450    #[test]
451    fn forward_indices_empty_prefix() {
452        let p = Perm::forward_indices(4, std::iter::empty());
453        assert!(p.is_id());
454    }
455
456    #[test]
457    fn forward_indices_full_prefix() {
458        // Specifying every index reduces to: p(prefix[k]) = k.
459        let p = Perm::forward_indices(4, [2, 0, 3, 1]);
460        assert_eq!(p.at(2), 0);
461        assert_eq!(p.at(0), 1);
462        assert_eq!(p.at(3), 2);
463        assert_eq!(p.at(1), 3);
464    }
465
466    // --- auto_ops-derived variants ---
467
468    #[test]
469    fn mul_all_ref_variants() {
470        let p = Perm::new(vec![2, 0, 3, 1]);
471        let q = Perm::new(vec![1, 2, 0, 3]);
472        let expected = &p * &q;
473
474        assert_eq!(p.clone() * q.clone(), expected);
475        assert_eq!(p.clone() * &q, expected);
476        assert_eq!(&p * q.clone(), expected);
477        assert_eq!(&p * &q, expected);
478    }
479}