qudit_core/perm.rs
1use std::collections::HashSet;
2
3use faer::Mat;
4use faer::mat::AsMatMut;
5use faer::mat::AsMatRef;
6use faer::perm::Perm;
7use faer::perm::permute_cols;
8use faer::perm::permute_rows;
9use faer::reborrow::Reborrow;
10use faer::reborrow::ReborrowMut;
11use faer_traits::ComplexField;
12
13use crate::QuditSystem;
14use crate::Radices;
15use crate::Radix;
16
17// TODO: TEST with FRPR
18// let vec = Mat::from_fn(radices.get_dimension(), 1, |i, _| {
19// c64::new(i as f64, 0.0)
20// });
21// let mut vec_buf = Mat::zeros(radices.get_dimension(), 1);
22// let mut extended_radices = radices.clone().to_vec();
23// let mut extended_perm = perm.clone().to_vec();
24// extended_radices.push(1);
25// extended_perm.push(perm.len());
26// fused_reshape_permute_reshape_into(
27// vec.as_ref(),
28// &extended_radices,
29// &extended_perm,
30// vec_buf.as_mut(),
31// );
32
33// let mut index_perm = vec![];
34// for i in 0..radices.get_dimension() {
35// index_perm.push(vec_buf.read(i, 0).re as usize);
36// }
37
38/// Calculate the index permutation from a qudit permutation for a given radices.
39///
40/// # Arguments
41///
42/// * `radices` - The radices of the system being permuted.
43///
44/// * `perm` - A vector describing the permutation.
45///
46/// # Returns
47///
48/// A vector of indices that describes the permutation in the index space.
49///
50/// # Examples
51///
52/// ```
53/// use qudit_core::{Radices, calc_index_permutation};
54/// let radices = Radices::new([2, 2]);
55/// let perm = vec![1, 0];
56/// let index_perm = calc_index_permutation(&radices, &perm);
57/// assert_eq!(index_perm, vec![0, 2, 1, 3]);
58/// ```
59pub fn calc_index_permutation(radices: &Radices, perm: &[usize]) -> Vec<usize> {
60 let mut index_perm = Vec::with_capacity(radices.dimension());
61 let place_values = radices.place_values();
62 let perm_place_values = perm
63 .iter()
64 .map(|&x| place_values[x])
65 .collect::<Vec<usize>>();
66 let perm_radices = perm.iter().map(|&x| radices[x]).collect::<Radices>();
67
68 for i in 0..radices.dimension() {
69 let expansion = perm_radices.expand(i);
70
71 let mut acm_val = 0usize;
72 for i in 0..radices.len() {
73 acm_val += expansion[i] * perm_place_values[i];
74 }
75 index_perm.push(acm_val);
76 }
77
78 index_perm
79}
80
81/// A permutation of qudits.
82///
83/// Qudit systems are often shuffled around in compiler pipelines. This
84/// object captures a specific shuffling operation which can be represented
85/// in many ways.
86#[derive(Hash, PartialEq, Eq, Clone, Debug)]
87pub struct QuditPermutation {
88 /// The number of qudits in the permutation.
89 num_qudits: usize,
90
91 /// The radices of the qudit system being permuted.
92 radices: Radices,
93
94 /// The permutation vector in the qudit space.
95 perm: Vec<usize>,
96
97 /// The permutation vector in the index space.
98 index_perm: Vec<usize>,
99
100 /// The inverse permutation vector in the index space.
101 inverse_index_perm: Vec<usize>,
102}
103
104impl QuditPermutation {
105 /// Returns a permutation mapping qudit `i` to `perm[i]`.
106 ///
107 /// # Arguments
108 ///
109 /// * `radices` - The radices of the system being permuted
110 ///
111 /// * `perm` - A vector describing the permutation. The resulting operation
112 /// will shift the i-th qudit to the `perm[i]`-th qudit.
113 ///
114 /// # Panics
115 ///
116 /// * If the supplied permutation is not a proper permutation. This can
117 /// happen when there is a duplicate or invalid qudit index.
118 ///
119 /// * If the permutation and radices describe systems with different qudit
120 /// counts, i.e. they have different lengths.
121 ///
122 /// # Examples
123 ///
124 /// ```
125 /// use qudit_core::{Radices, QuditPermutation};
126 ///
127 /// let three_qubits = Radices::new([2; 3]);
128 /// let qudit_shift = QuditPermutation::new(three_qubits, &vec![2, 0, 1]);
129 /// ```
130 ///
131 /// # See Also
132 ///
133 /// * [Self::from_qubit_location] - A convenience constructor for qubit permutations.
134 /// * [Self::from_qudit_location] - A convenience constructor for qudit permutations.
135 pub fn new<R: Into<Radices>>(radices: R, perm: &[usize]) -> QuditPermutation {
136 fn __new_impl(radices: Radices, perm: &[usize]) -> QuditPermutation {
137 if perm.len() != radices.len() {
138 panic!("Invalid qudit permutation: perm's qudit count doesn't match radices'.");
139 }
140
141 if !perm.iter().all(|x| x < &radices.len()) {
142 panic!("Invalid qudit permutation: permutation has invalid qudit index.");
143 }
144
145 let mut uniq = HashSet::new();
146 if !perm.iter().all(|x| uniq.insert(x)) {
147 panic!("Invalid qudit permutation: permutation has duplicate entries.");
148 }
149
150 let index_perm = calc_index_permutation(&radices, perm);
151
152 let mut inverse_index_perm = vec![0; radices.dimension()];
153 index_perm
154 .iter()
155 .enumerate()
156 .for_each(|(s, d)| inverse_index_perm[*d] = s);
157
158 QuditPermutation {
159 num_qudits: radices.len(),
160 radices,
161 perm: perm.to_vec(),
162 index_perm,
163 inverse_index_perm,
164 }
165 }
166 __new_impl(radices.into(), perm)
167 }
168
169 /// Returns a qubit permutation specifed by `perm`.
170 ///
171 /// # Arguments
172 ///
173 /// * `perm` - The qubit permutation.
174 ///
175 /// # Panics
176 ///
177 /// * If the supplied permutation is not a proper permutation. This can
178 /// happen when there is a duplicate or invalid qudit index.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use qudit_core::{Radices, QuditPermutation};
184 ///
185 /// let qubit_shift = QuditPermutation::from_qubit_location(&vec![2, 0, 1]);
186 /// ```
187 ///
188 /// # See Also
189 ///
190 /// * [Self::new] - A general constructor for qudit permutations.
191 /// * [Self::from_qudit_location] - A convenience constructor for qudit permutations.
192 #[inline]
193 pub fn from_qubit_location(perm: &[usize]) -> QuditPermutation {
194 Self::from_qudit_location(2u8, perm)
195 }
196
197 /// Returns a qudit permutation specifed by `perm` with a uniform `radix`.
198 ///
199 /// # Arguments
200 ///
201 /// * `perm` - The qubit permutation.
202 ///
203 /// * `radix` - The radix of the qudits being permuted.
204 ///
205 /// # Panics
206 ///
207 /// * If the supplied permutation is not a proper permutation. This can
208 /// happen when there is a duplicate or invalid qudit index.
209 ///
210 /// * If the radix is not a valid radix.
211 ///
212 /// # Examples
213 ///
214 /// ```
215 /// use qudit_core::{Radices, QuditPermutation};
216 ///
217 /// let qutrit_swap = QuditPermutation::from_qudit_location(3, &vec![1, 0]);
218 /// ```
219 ///
220 /// # See Also
221 ///
222 /// * [Self::new] - A general constructor for qudit permutations.
223 /// * [Self::from_qubit_location] - A convenience constructor for qubit permutations.
224 #[inline]
225 pub fn from_qudit_location<T: Into<Radix>>(radix: T, perm: &[usize]) -> QuditPermutation {
226 let qudit_iter = core::iter::repeat_n(radix.into(), perm.len());
227 let rdx = Radices::from_iter(qudit_iter);
228 QuditPermutation::new(rdx, perm)
229 }
230
231 /// Returns true if the permutation has physical meaning.
232 ///
233 /// A permutation cannot be physically implemented if it permutes
234 /// qudits of different radices.
235 ///
236 /// # Examples
237 /// ```
238 /// use qudit_core::{Radices, QuditPermutation};
239 ///
240 /// let three_qubits = Radices::new([2; 3]);
241 /// let qudit_shift = QuditPermutation::new(three_qubits, &vec![2, 0, 1]);
242 /// assert!(qudit_shift.is_physically_implementable());
243 ///
244 /// let three_qutrits = Radices::new([3; 3]);
245 /// let qudit_shift = QuditPermutation::new(three_qutrits, &vec![2, 0, 1]);
246 /// assert!(qudit_shift.is_physically_implementable());
247 ///
248 /// let three_qudits = Radices::new([2, 3, 4]);
249 /// let qudit_shift = QuditPermutation::new(three_qudits, &vec![2, 0, 1]);
250 /// assert!(!qudit_shift.is_physically_implementable());
251 /// ```
252 pub fn is_physically_implementable(&self) -> bool {
253 self.perm
254 .iter()
255 .enumerate()
256 .all(|x| self.radices[x.0] == self.radices[*x.1])
257 }
258
259 /// Returns a permutation that sorts the circuit location
260 ///
261 /// # Arguments
262 ///
263 /// * `loc` - The index list to be sorted.
264 ///
265 /// * `radices` - The radices of the system being permuted.
266 ///
267 /// # Panics
268 ///
269 /// * If the supplied radices are invalid.
270 ///
271 /// # Examples
272 ///
273 /// ```
274 /// use qudit_core::{Radices, QuditPermutation};
275 /// let radices = Radices::new([2, 3]);
276 /// let loc = vec![37, 24];
277 /// let hybrid_swap = QuditPermutation::locally_invert_location(radices.clone(), &loc);
278 /// assert_eq!(hybrid_swap, QuditPermutation::new(radices, &vec![1, 0]));
279 /// ```
280 pub fn locally_invert_location(radices: Radices, loc: &[usize]) -> QuditPermutation {
281 let mut perm: Vec<usize> = (0..loc.len()).collect();
282 perm.sort_by_key(|&i| &loc[i]);
283 QuditPermutation::new(radices, &perm)
284 }
285
286 /// Returns the permutation in the index space.
287 ///
288 /// The qudit permutation describes how the qudits of a system are permuted.
289 /// This ultimately results in the permutation of the index space of the system.
290 /// For example, two qubits have four basis states: |00>, |01>, |10>, and |11>.
291 /// If the qubits are permuted, the basis states are permuted as well.
292 /// The index permutation describes this permutation of the basis states.
293 ///
294 /// # Examples
295 /// ```
296 /// use qudit_core::{Radices, QuditPermutation};
297 ///
298 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
299 /// assert_eq!(qubit_swap.index_perm(), &vec![0, 2, 1, 3]);
300 /// ```
301 pub fn index_perm(&self) -> &[usize] {
302 &self.index_perm
303 }
304
305 /// Returns the radices of the system after being permuted.
306 ///
307 /// # Examples
308 /// ```
309 /// use qudit_core::{Radices, QuditPermutation};
310 /// let qudit_swap = QuditPermutation::new(Radices::new([2, 3]), &vec![1, 0]);
311 /// assert_eq!(qudit_swap.permuted_radices(), Radices::new([3, 2]));
312 ///
313 /// let qudit_swap = QuditPermutation::new(Radices::new([2, 3, 4]), &vec![1, 0, 2]);
314 /// assert_eq!(qudit_swap.permuted_radices(), Radices::new([3, 2, 4]));
315 /// ```
316 pub fn permuted_radices(&self) -> Radices {
317 self.perm.iter().map(|&i| self.radices[i]).collect()
318 }
319
320 /// Returns true is this permutation does not permute any qudit.
321 ///
322 /// # Examples
323 /// ```
324 /// use qudit_core::QuditPermutation;
325 /// let identity = QuditPermutation::from_qubit_location(&vec![0, 1]);
326 /// assert!(identity.is_identity());
327 /// ```
328 ///
329 /// ```
330 /// use qudit_core::QuditPermutation;
331 /// let identity = QuditPermutation::from_qubit_location(&vec![1, 0]);
332 /// assert!(!identity.is_identity());
333 /// ```
334 pub fn is_identity(&self) -> bool {
335 self.perm.iter().enumerate().all(|(s, d)| s == *d)
336 }
337
338 /// Returns a new permutation that composes `self` with `arg_perm`.
339 ///
340 /// # Panics
341 ///
342 /// If the permutations have incompatible radices.
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// use qudit_core::QuditPermutation;
348 /// let p1 = QuditPermutation::from_qubit_location(&vec![1, 0]);
349 /// let p2 = QuditPermutation::from_qubit_location(&vec![1, 0]);
350 /// assert!(p1.compose(&p2).is_identity());
351 ///
352 /// let p1 = QuditPermutation::from_qubit_location(&vec![0, 2, 1, 3]);
353 /// let p2 = QuditPermutation::from_qubit_location(&vec![1, 0, 3, 2]);
354 /// assert_eq!(p1.compose(&p2), QuditPermutation::from_qubit_location(&vec![2, 0, 3, 1]));
355 /// ```
356 pub fn compose(&self, arg_perm: &QuditPermutation) -> QuditPermutation {
357 if self.radices != arg_perm.radices {
358 panic!("Permutations being composed have incompatible radices.");
359 }
360
361 let mut composed_perm = vec![0; self.num_qudits()];
362 arg_perm
363 .iter()
364 .enumerate()
365 .for_each(|(s, d)| composed_perm[s] = self.perm[*d]);
366 QuditPermutation::new(self.radices.clone(), &composed_perm)
367 }
368
369 /// Returns a new permutation that inverts `self`.
370 ///
371 /// # Examples
372 ///
373 /// ```
374 /// use qudit_core::QuditPermutation;
375 /// let p1 = QuditPermutation::from_qubit_location(&vec![1, 0]);
376 /// assert_eq!(p1.invert(), p1);
377 /// ```
378 ///
379 /// ```
380 /// use qudit_core::QuditPermutation;
381 /// let p1 = QuditPermutation::from_qubit_location(&vec![2, 0, 3, 1]);
382 /// assert_eq!(p1.invert(), QuditPermutation::from_qubit_location(&vec![1, 3, 0, 2]));
383 /// ```
384 pub fn invert(&self) -> QuditPermutation {
385 let mut inverted_perm = vec![0; self.num_qudits()];
386 self.perm
387 .iter()
388 .enumerate()
389 .for_each(|(s, d)| inverted_perm[*d] = s);
390 QuditPermutation::new(self.radices.clone(), &inverted_perm)
391 }
392
393 /// Returns the permutation as a sequence of cycles.
394 ///
395 /// Any permutation can be represented as a product of disjoint cycles.
396 /// This function performs that decompositions.
397 ///
398 /// # Returns
399 ///
400 /// A vector of cycles, where each cycle is a vector of qudit indices.
401 ///
402 /// # Examples
403 /// ```
404 /// use qudit_core::{Radices, QuditPermutation};
405 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
406 /// assert_eq!(qubit_swap.cycles(), vec![vec![0, 1]]);
407 ///
408 /// let complex_perm = QuditPermutation::from_qudit_location(3, &vec![1, 2, 0]);
409 /// assert_eq!(complex_perm.cycles(), vec![vec![0, 1, 2]]);
410 ///
411 /// let complex_perm = QuditPermutation::from_qubit_location(&vec![1, 0, 3, 2]);
412 /// assert_eq!(complex_perm.cycles(), vec![vec![0, 1], vec![2, 3]]);
413 /// ```
414 ///
415 /// # Notes
416 ///
417 /// The cycles are not unique. For example, the permutation (0, 1, 2) can
418 /// be represented as either (0, 1, 2) or (2, 0, 1). This function returns
419 /// the former. Each new cycle starts with the smallest index.
420 ///
421 /// # See Also
422 ///
423 /// * [Self::transpositions] - Returns the permutation as a sequence of qudit swaps.
424 /// * [Self::index_cycles] - Returns the permutation as a sequence of index swaps.
425 /// * [Self::index_transpositions] - Returns the permutation as a sequence of index swaps.
426 pub fn cycles(&self) -> Vec<Vec<usize>> {
427 let mut cycles_vec = Vec::new();
428 let mut visited = vec![false; self.num_qudits];
429
430 for new_cycle_start_index in 0..self.num_qudits {
431 if visited[new_cycle_start_index] {
432 continue;
433 }
434
435 let mut cycle = Vec::new();
436 let mut cycle_iter_index = new_cycle_start_index;
437
438 while !visited[cycle_iter_index] {
439 visited[cycle_iter_index] = true;
440 cycle.push(cycle_iter_index);
441 cycle_iter_index = self.perm[cycle_iter_index];
442 }
443
444 if cycle.len() > 1 {
445 cycles_vec.push(cycle);
446 }
447 }
448
449 cycles_vec
450 }
451
452 /// Returns the index-space permutation as a sequence of cycles.
453 ///
454 /// # Returns
455 ///
456 /// A vector of cycles, where each cycle is a vector of qudit indices.
457 ///
458 /// # Examples
459 ///
460 /// ```
461 /// use qudit_core::{Radices, QuditPermutation};
462 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
463 /// assert_eq!(qubit_swap.index_cycles(), vec![vec![1, 2]]);
464 /// ```
465 ///
466 /// # See Also
467 ///
468 /// * [Self::cycles] - Returns the permutation as a sequence of cycles.
469 /// * [Self::transpositions] - Returns the permutation as a sequence of qudit swaps.
470 /// * [Self::index_transpositions] - Returns the permutation as a sequence of index swaps.
471 pub fn index_cycles(&self) -> Vec<Vec<usize>> {
472 let mut cycles_vec = Vec::new();
473 let mut visited = vec![false; self.dimension()];
474
475 for new_cycle_start_index in 0..self.dimension() {
476 if visited[new_cycle_start_index] {
477 continue;
478 }
479
480 let mut cycle = Vec::new();
481 let mut cycle_iter_index = new_cycle_start_index;
482
483 while !visited[cycle_iter_index] {
484 visited[cycle_iter_index] = true;
485 cycle.push(cycle_iter_index);
486 cycle_iter_index = self.index_perm[cycle_iter_index];
487 }
488
489 if cycle.len() > 1 {
490 cycles_vec.push(cycle);
491 }
492 }
493
494 cycles_vec
495 }
496
497 /// Return the permutation as a sequence of qudit swaps
498 ///
499 /// Any permutation can be represented as a product of transpositions
500 /// (2-cycles or swaps). This function decomposes the permutation as a
501 /// sequence of tranpositions.
502 ///
503 /// # Returns
504 ///
505 /// A vector of transpositions, where each swap is a qudit index tuple.
506 ///
507 /// # Examples
508 ///
509 /// ```
510 /// use qudit_core::{Radices, QuditPermutation};
511 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
512 /// assert_eq!(qubit_swap.transpositions(), vec![(0, 1)]);
513 ///
514 /// let complex_perm = QuditPermutation::from_qudit_location(3, &vec![1, 2, 0]);
515 /// assert_eq!(complex_perm.transpositions(), vec![(0, 1), (1, 2)]);
516 /// ```
517 ///
518 /// # See Also
519 ///
520 /// * [Self::cycles] - Returns the permutation as a sequence of cycles.
521 /// * [Self::index_cycles] - Returns the permutation as a sequence of index swaps.
522 /// * [Self::index_transpositions] - Returns the index permutation as a swap sequence.
523 pub fn transpositions(&self) -> Vec<(usize, usize)> {
524 let mut swaps_vec = Vec::new();
525 for cycle in self.cycles() {
526 for i in 0..cycle.len() - 1 {
527 swaps_vec.push((cycle[i], cycle[i + 1]));
528 }
529 }
530 swaps_vec
531 }
532
533 /// Return the index-space permutation as a sequence of swaps
534 ///
535 /// # Returns
536 ///
537 /// A vector of swaps, where each swap is a tuple of qudit indices.
538 ///
539 /// # Examples
540 ///
541 /// ```
542 /// use qudit_core::{Radices, QuditPermutation};
543 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
544 /// assert_eq!(qubit_swap.index_transpositions(), vec![(1, 2)]);
545 /// ```
546 ///
547 /// # See Also
548 ///
549 /// * [Self::cycles] - Returns the permutation as a sequence of cycles.
550 /// * [Self::transpositions] - Returns the permutation as a sequence of qudit swaps.
551 /// * [Self::index_cycles] - Returns the permutation as a sequence of index swaps.
552 pub fn index_transpositions(&self) -> Vec<(usize, usize)> {
553 let mut swaps_vec = Vec::new();
554 for cycle in self.index_cycles() {
555 for i in 0..cycle.len() - 1 {
556 swaps_vec.push((cycle[i], cycle[i + 1]));
557 }
558 }
559 swaps_vec
560 }
561
562 /// Swap the rows of a matrix according to the permutation.
563 ///
564 /// This function allocates and returns a new matrix. The permutation of
565 /// the rows is done in index space.
566 ///
567 /// # Arguments
568 ///
569 /// * `a` - The matrix to be permuted.
570 ///
571 /// # Returns
572 ///
573 /// A new matrix with the rows permuted.
574 ///
575 /// # Examples
576 ///
577 /// ```
578 /// use qudit_core::{Radices, QuditPermutation};
579 /// use faer::{mat, Mat};
580 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
581 /// let mat = mat![
582 /// [1., 2., 3., 4.],
583 /// [5., 6., 7., 8.],
584 /// [9., 10., 11., 12.],
585 /// [13., 14., 15., 16.]
586 /// ];
587 /// let permuted_mat = qubit_swap.swap_rows(&mat);
588 /// assert_eq!(permuted_mat, mat![
589 /// [1., 2., 3., 4.],
590 /// [9., 10., 11., 12.],
591 /// [5., 6., 7., 8.],
592 /// [13., 14., 15., 16.]
593 /// ]);
594 /// ```
595 ///
596 /// # See Also
597 ///
598 /// * [Self::swap_rows_to_buf] - Swaps the rows of a matrix witout allocations.
599 /// * [Self::swap_cols] - Swaps the columns of a matrix.
600 pub fn swap_rows<E: ComplexField>(
601 &self,
602 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
603 ) -> Mat<E> {
604 let in_mat = a.as_mat_ref();
605 let p_mat = self.as_faer();
606 let mut out = Mat::zeros(in_mat.nrows(), in_mat.ncols());
607 permute_rows(out.as_mut(), in_mat, p_mat.as_ref());
608 out
609 }
610
611 /// Swap the rows of a matrix in place according to the permutation.
612 ///
613 /// # Arguments
614 ///
615 /// * `a` - The matrix to be permuted.
616 ///
617 /// # Panics
618 ///
619 /// * If the matrix dimensions do not match the dimension of the permutation.
620 ///
621 /// # Examples
622 ///
623 /// ```
624 /// use qudit_core::{Radices, QuditPermutation};
625 /// use faer::{mat, Mat};
626 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
627 /// let mut mat = mat![
628 /// [1., 2., 3., 4.],
629 /// [5., 6., 7., 8.],
630 /// [9., 10., 11., 12.],
631 /// [13., 14., 15., 16.]
632 /// ];
633 /// qubit_swap.swap_rows_in_place(&mut mat);
634 /// assert_eq!(mat, mat![
635 /// [1., 2., 3., 4.],
636 /// [9., 10., 11., 12.],
637 /// [5., 6., 7., 8.],
638 /// [13., 14., 15., 16.]
639 /// ]);
640 /// ```
641 ///
642 /// # See Also
643 ///
644 /// * [Self::swap_rows] - Swaps the rows of a matrix and returns a new matrix.
645 /// * [Self::swap_rows_to_buf] - Swaps the rows of a matrix without allocations.
646 /// * [Self::swap_cols_in_place] - Swaps the columns of a matrix in place.
647 /// * [Self::swap_cols_to_buf] - Swaps the columns of a matrix without allocations.
648 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
649 ///
650 /// # Notes
651 ///
652 /// This is not entirely allocation free. The list of transpositions is calculated
653 /// resulting in a small allocation. To be entirely allocation free, one should
654 /// use the `swap_rows_to_buf` method instead.
655 pub fn swap_rows_in_place<E: ComplexField>(
656 &self,
657 mut a: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
658 ) {
659 let mut in_mat = a.as_mat_mut();
660 let ncols = in_mat.ncols();
661 assert_eq!(in_mat.nrows(), self.dimension());
662 assert_eq!(in_mat.ncols(), self.dimension());
663 for cycle in self.index_transpositions() {
664 for col in 0..ncols {
665 // SAFETY: The matrix dimensions have been checked.
666 unsafe {
667 let val0 = in_mat.rb().get_unchecked(cycle.0, col).clone();
668 let val1 = in_mat.rb().get_unchecked(cycle.1, col).clone();
669 *(in_mat.rb_mut().get_mut_unchecked(cycle.1, col)) = val0;
670 *(in_mat.rb_mut().get_mut_unchecked(cycle.0, col)) = val1;
671 }
672 }
673 }
674 }
675
676 /// Swap the rows of a matrix from an input into an output buffer.
677 ///
678 /// This is an allocation free version of `swap_rows`.
679 ///
680 /// # Arguments
681 ///
682 /// * `a` - The matrix to be permuted.
683 ///
684 /// * `b` - The buffer to write the permuted matrix into.
685 ///
686 /// # Panics
687 ///
688 /// * If the matrix dimensions do not match the dimension of the permutation.
689 ///
690 /// # See Also
691 ///
692 /// * [Self::swap_rows] - Swaps the rows of a matrix and returns a new matrix.
693 /// * [Self::swap_rows_in_place] - Swaps the rows of a matrix in place.
694 /// * [Self::swap_cols_to_buf] - Swaps the columns of a matrix without allocations.
695 /// * [Self::apply_to_buf] - Applies the permutation to a matrix and writes the result into a buffer.
696 pub fn swap_rows_to_buf<E: ComplexField>(
697 &self,
698 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
699 mut b: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
700 ) {
701 let p_mat = self.as_faer();
702 permute_rows(b.as_mat_mut(), a.as_mat_ref(), p_mat.as_ref());
703 }
704
705 /// Swap the columns of a matrix according to the permutation.
706 ///
707 /// This function allocates and returns a new matrix. The permutation of
708 /// the columns is done in index space.
709 ///
710 /// # Arguments
711 ///
712 /// * `a` - The matrix to be permuted.
713 ///
714 /// # Returns
715 ///
716 /// A new matrix with the columns permuted.
717 ///
718 /// # Examples
719 ///
720 /// ```
721 /// use qudit_core::{Radices, QuditPermutation};
722 /// use faer::{mat, Mat};
723 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
724 /// let mat = mat![
725 /// [1., 2., 3., 4.],
726 /// [5., 6., 7., 8.],
727 /// [9., 10., 11., 12.],
728 /// [13., 14., 15., 16.]
729 /// ];
730 /// let permuted_mat = qubit_swap.swap_cols(&mat);
731 /// assert_eq!(permuted_mat, mat![
732 /// [1., 3., 2., 4.],
733 /// [5., 7., 6., 8.],
734 /// [9., 11., 10., 12.],
735 /// [13., 15., 14., 16.]
736 /// ]);
737 /// ```
738 ///
739 /// # See Also
740 ///
741 /// * [Self::swap_cols_to_buf] - Swaps the columns of a matrix without allocations.
742 /// * [Self::swap_rows] - Swaps the rows of a matrix.
743 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
744 pub fn swap_cols<E: ComplexField>(
745 &self,
746 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
747 ) -> Mat<E> {
748 let in_mat = a.as_mat_ref();
749 let p_mat = self.as_faer();
750 let mut out = Mat::zeros(in_mat.nrows(), in_mat.ncols());
751 permute_cols(out.as_mut(), in_mat, p_mat.as_ref());
752 out
753 }
754
755 /// Swap the columns of a matrix in place according to the permutation.
756 ///
757 /// # Arguments
758 ///
759 /// * `a` - The matrix to be permuted.
760 ///
761 /// # Panics
762 ///
763 /// * If the matrix dimensions do not match the dimension of the permutation.
764 ///
765 /// # Examples
766 ///
767 /// ```
768 /// use qudit_core::{Radices, QuditPermutation};
769 /// use faer::{mat, Mat};
770 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
771 /// let mut mat = mat![
772 /// [1., 2., 3., 4.],
773 /// [5., 6., 7., 8.],
774 /// [9., 10., 11., 12.],
775 /// [13., 14., 15., 16.]
776 /// ];
777 /// qubit_swap.swap_cols_in_place(mat.as_mut());
778 /// assert_eq!(mat, mat![
779 /// [1., 3., 2., 4.],
780 /// [5., 7., 6., 8.],
781 /// [9., 11., 10., 12.],
782 /// [13., 15., 14., 16.]
783 /// ]);
784 /// ```
785 ///
786 /// # See Also
787 ///
788 /// * [Self::swap_rows] - Swaps the rows of a matrix and returns a new matrix.
789 /// * [Self::swap_rows_to_buf] - Swaps the rows of a matrix without allocations.
790 /// * [Self::swap_rows_in_place] - Swaps the rows of a matrix in place.
791 /// * [Self::swap_cols] - Swaps the columns of a matrix with allocations.
792 /// * [Self::swap_cols_to_buf] - Swaps the columns of a matrix without allocations.
793 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
794 ///
795 /// # Notes
796 ///
797 /// This is not entirely allocation free. The list of transpositions is calculated
798 /// resulting in a small allocation. To be entirely allocation free, one should
799 /// use the `swap_cols_to_buf` method instead.
800 pub fn swap_cols_in_place<E: ComplexField>(
801 &self,
802 mut a: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
803 ) {
804 let mut in_mat = a.as_mat_mut();
805 assert_eq!(in_mat.nrows(), self.dimension());
806 assert_eq!(in_mat.ncols(), self.dimension());
807 for cycle in self.index_transpositions() {
808 for row in 0..in_mat.nrows() {
809 // SAFETY: The matrix dimensions have been checked.
810 unsafe {
811 let val0 = in_mat.rb().get_unchecked(row, cycle.0).clone();
812 let val1 = in_mat.rb().get_unchecked(row, cycle.1).clone();
813 *(in_mat.rb_mut().get_mut_unchecked(row, cycle.1)) = val0;
814 *(in_mat.rb_mut().get_mut_unchecked(row, cycle.0)) = val1;
815 }
816 }
817 }
818 }
819
820 /// Swap the columns of a matrix from an input into an output buffer.
821 ///
822 /// This is an allocation free version of `swap_cols`.
823 ///
824 /// # Arguments
825 ///
826 /// * `a` - The matrix to be permuted.
827 ///
828 /// * `b` - The buffer to write the permuted matrix into.
829 ///
830 /// # Panics
831 ///
832 /// * If the matrix dimensions do not match the dimension of the permutation.
833 ///
834 /// # See Also
835 ///
836 /// * [Self::swap_cols] - Swaps the columns of a matrix and returns a new matrix.
837 /// * [Self::swap_cols_in_place] - Swaps the columns of a matrix in place.
838 /// * [Self::swap_rows_to_buf] - Swaps the rows of a matrix without allocations.
839 /// * [Self::apply_to_buf] - Applies the permutation to a matrix and writes the result into a buffer.
840 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
841 pub fn swap_cols_to_buf<E: ComplexField>(
842 &self,
843 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
844 mut b: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
845 ) {
846 let p_mat = self.as_faer();
847 permute_cols(b.as_mat_mut(), a.as_mat_ref(), p_mat.as_ref());
848 }
849
850 /// Apply the permutation to a matrix and return a new matrix.
851 ///
852 /// This is equivalent to calling `swap_rows` and then `swap_cols`.
853 ///
854 /// # Arguments
855 ///
856 /// * `a` - The matrix to be permuted.
857 ///
858 /// # Returns
859 ///
860 /// A new matrix with the rows and columns permuted.
861 ///
862 /// # Examples
863 ///
864 /// ```
865 /// use qudit_core::{Radices, QuditPermutation};
866 /// use faer::{mat, Mat};
867 /// let qubit_swap = QuditPermutation::from_qubit_location(&vec![1, 0]);
868 /// let mat = mat![
869 /// [1., 2., 3., 4.],
870 /// [5., 6., 7., 8.],
871 /// [9., 10., 11., 12.],
872 /// [13., 14., 15., 16.]
873 /// ];
874 /// let permuted_mat = qubit_swap.apply(&mat);
875 /// assert_eq!(permuted_mat, mat![
876 /// [1., 3., 2., 4.],
877 /// [9., 11., 10., 12.],
878 /// [5., 7., 6., 8.],
879 /// [13., 15., 14., 16.]
880 /// ]);
881 ///
882 /// // Applying qudit permutations can help convert between little
883 /// // and big endian gates.
884 /// let big_endian_cnot = mat![
885 /// [1., 0., 0., 0.],
886 /// [0., 1., 0., 0.],
887 /// [0., 0., 0., 1.],
888 /// [0., 0., 1., 0.]
889 /// ];
890 /// let little_endian_cnot = mat![
891 /// [1., 0., 0., 0.],
892 /// [0., 0., 0., 1.],
893 /// [0., 0., 1., 0.],
894 /// [0., 1., 0., 0.]
895 /// ];
896 /// assert_eq!(qubit_swap.apply(&big_endian_cnot), little_endian_cnot);
897 /// ```
898 ///
899 /// # See Also
900 ///
901 /// * [Self::swap_rows] - Swaps the rows of a matrix and returns a new matrix.
902 /// * [Self::swap_cols] - Swaps the columns of a matrix and returns a new matrix.
903 /// * [Self::apply_to_buf] - Applies the permutation to a matrix and writes the result into a buffer.
904 /// * [Self::apply_in_place] - Swaps the rows of a matrix in place.
905 pub fn apply<E: ComplexField>(
906 &self,
907 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
908 ) -> Mat<E> {
909 self.swap_cols(self.swap_rows(a))
910 }
911
912 /// Apply the permutation to a matrix in place.
913 ///
914 /// # Arguments
915 ///
916 /// * `a` - The matrix to be permuted.
917 ///
918 /// # Returns
919 ///
920 /// A new matrix with the rows and columns permuted.
921 ///
922 /// # See Also
923 ///
924 /// * [Self::swap_rows_in_place] - Swaps the rows of a matrix in place.
925 /// * [Self::swap_cols_in_place] - Swaps the columns of a matrix in place.
926 /// * [Self::apply_to_buf] - Applies the permutation to a matrix and writes the result into a buffer.
927 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
928 ///
929 /// # Notes
930 ///
931 /// This is not entirely allocation free. The list of transpositions is calculated
932 /// resulting in a small allocation. To be entirely allocation free, one should
933 /// use the `apply_to_buf` method instead.
934 pub fn apply_in_place<E: ComplexField>(
935 &self,
936 mut a: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
937 ) {
938 self.swap_rows_in_place(a.as_mat_mut());
939 self.swap_cols_in_place(a.as_mat_mut());
940 }
941
942 /// Apply the permutation to a matrix and write the result into a buffer.
943 ///
944 /// This is an allocation free version of `apply`.
945 ///
946 /// # Arguments
947 ///
948 /// * `a` - The matrix to be permuted.
949 ///
950 /// * `b` - The buffer to write the permuted matrix into.
951 ///
952 /// # See Also
953 ///
954 /// * [Self::swap_rows_to_buf] - Swaps the rows of a matrix without allocations.
955 /// * [Self::swap_cols_to_buf] - Swaps the columns of a matrix without allocations.
956 /// * [Self::apply] - Applies the permutation to a matrix and returns a new matrix.
957 /// * [Self::apply_in_place] - Swaps the rows of a matrix in place.
958 pub fn apply_to_buf<E: ComplexField>(
959 &self,
960 a: impl AsMatRef<T = E, Rows = usize, Cols = usize>,
961 mut b: impl AsMatMut<T = E, Rows = usize, Cols = usize>,
962 ) {
963 let p_mat = self.as_faer();
964 permute_rows(b.as_mat_mut(), a.as_mat_ref(), p_mat.as_ref());
965 permute_cols(b.as_mat_mut(), a.as_mat_ref(), p_mat.as_ref());
966 }
967
968 /// Returns the permutation as an faer object.
969 fn as_faer(&self) -> Perm<usize> {
970 Perm::new_checked(
971 self.index_perm.clone().into(),
972 self.inverse_index_perm.clone().into(),
973 self.dimension(),
974 )
975 }
976
977 // Returns the permutation as a unitary matrix.
978 // // TODO: Update
979 // pub fn get_matrix<E: Entity>(&self) -> UnitaryMatrix<E> {
980 // todo!()
981 // }
982}
983
984/// QuditPermutation can be dereferenced as a usize slice.
985impl core::ops::Deref for QuditPermutation {
986 type Target = [usize];
987
988 fn deref(&self) -> &Self::Target {
989 &self.perm
990 }
991}
992
993/// QuditPermutations permute a qudit system.
994impl QuditSystem for QuditPermutation {
995 /// Returns the radices of the system before being permuted.
996 fn radices(&self) -> Radices {
997 self.radices.clone()
998 }
999
1000 /// Returns the number of qudits being permuted.
1001 fn num_qudits(&self) -> usize {
1002 self.num_qudits
1003 }
1004
1005 /// Returns the dimension of the system being permuted.
1006 fn dimension(&self) -> usize {
1007 self.index_perm.len()
1008 }
1009}
1010
1011impl core::fmt::Display for QuditPermutation {
1012 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1013 write!(f, "[")?;
1014 for r in &self.perm {
1015 write!(f, "{}", r).unwrap();
1016 }
1017 write!(f, "]")?;
1018 Ok(())
1019 }
1020}
1021
1022#[cfg(test)]
1023pub mod strategies {
1024 // use super::*;
1025 // // use crate::radices::strategies::fixed_length_radices;
1026 // use crate::radices::Radices;
1027 // use proptest::prelude::*;
1028
1029 // pub fn perms(num_qudits: usize) -> impl Strategy<Value =
1030 // QuditPermutation> { (
1031 // fixed_length_radices(num_qudits, 5),
1032 // Just(Vec::from_iter(0..num_qudits)).prop_shuffle(),
1033 // )
1034 // .prop_map(|(radices, perm)| QuditPermutation::new(radices, perm))
1035 // }
1036
1037 // pub fn perms_with_radices(radices: Radices) -> impl Strategy<Value =
1038 // QuditPermutation> { Just(Vec::from_iter(0..radices.
1039 // get_num_qudits())) .prop_shuffle()
1040 // .prop_map(move |perm| QuditPermutation::new(radices.clone(),
1041 // perm)) }
1042
1043 // pub fn physical_perms(num_qudits: usize) -> impl Strategy<Value =
1044 // QuditPermutation> { (
1045 // fixed_length_radices(num_qudits, 5),
1046 // Just(Vec::from_iter(0..num_qudits)).prop_shuffle(),
1047 // )
1048 // .prop_map(|(radices, perm)| QuditPermutation::new(radices, perm))
1049 // .prop_filter("Permutation is not physical", |perm| {
1050 // perm.has_physical_meaning()
1051 // })
1052 // }
1053
1054 // pub fn qubit_perms(num_qudits: usize) -> impl Strategy<Value =
1055 // QuditPermutation> { Just(Vec::from_iter(0..num_qudits))
1056 // .prop_shuffle()
1057 // .prop_map(move |perm| {
1058 // QuditPermutation::new(Radices::new(vec![2; num_qudits]),
1059 // perm) })
1060 // }
1061
1062 // pub fn qudit_perms(num_qudits: usize, radix: usize) -> impl
1063 // Strategy<Value = QuditPermutation> { Just(Vec::from_iter(0..
1064 // num_qudits)) .prop_shuffle()
1065 // .prop_map(move |perm| {
1066 // QuditPermutation::new(Radices::new(vec![radix;
1067 // num_qudits]), perm) })
1068 // }
1069
1070 // pub fn arbitrary_qudit_perms(num_qudits: usize) -> impl Strategy<Value =
1071 // QuditPermutation> { (
1072 // 2..5usize,
1073 // Just(Vec::from_iter(0..num_qudits)).prop_shuffle(),
1074 // )
1075 // .prop_map(move |(radix, perm)| {
1076 // QuditPermutation::new(Radices::new(vec![radix;
1077 // num_qudits]), perm) })
1078 // }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 // use super::QuditPermutation;
1084 // // use crate::math::hsd;
1085 // use crate::Gate;
1086 // use crate::Radices;
1087
1088 // #[test]
1089 // fn test_swap_as_perm() {
1090 // for radix in 2..5 {
1091 // let radices = Radices::new(vec![radix, radix]);
1092 // let perm = QuditPermutation::new(radices, vec![1, 0]);
1093 // let perm_mat = perm.get_matrix();
1094 // assert_eq!(Gate::QuditSwap(radix).get_unitary(&[]), perm_mat);
1095 // }
1096 // }
1097
1098 // #[test]
1099 // fn test_double_swap_as_perm() {
1100 // for radix in 2..5 {
1101 // let radices = Radices::new(vec![radix; 4]);
1102 // let perm = QuditPermutation::new(radices, vec![1, 0, 3, 2]);
1103 // let perm_mat = perm.get_matrix();
1104 // let swap_utry = Gate::QuditSwap(radix).get_unitary(&[]);
1105 // assert_eq!(kron(&swap_utry, &swap_utry), perm_mat);
1106 // }
1107 // }
1108
1109 // #[test]
1110 // fn test_complicated_perm() {
1111 // for radix in 2..3 {
1112 // let radices = Radices::new(vec![radix; 4]);
1113 // let perm = QuditPermutation::new(radices, vec![1, 3, 0, 2]);
1114 // let perm_mat = perm.get_matrix();
1115 // // for r in perm_mat.outer_iter() {
1116 // // let mut line_str = String::new();
1117 // // for c in r.iter() {
1118 // // line_str += &format!("{} + {}i, ", c.re, c.im);
1119 // // }
1120 // // println!("{}", line_str);
1121 // // }
1122 // let swap_utry = Gate::QuditSwap(radix).get_unitary(&[]);
1123 // let id = Array::eye(radix);
1124 // let c1 = kron(&id, &kron(&swap_utry, &id));
1125 // let c2 = kron(&swap_utry, &swap_utry);
1126 // println!("{}", hsd(c1.dot(&c2).view(), perm_mat.view()));
1127 // assert!(hsd(c1.dot(&c2).view(), perm_mat.view()) < 1e-7);
1128 // }
1129 // }
1130}