paulistrings/channel/ptm/
unitary.rs1use super::{assert_distinct, sparse_storage, PtmChannel, PtmKind, ONE, ZERO};
4use num_complex::Complex64;
5
6const PTM_EPS: f64 = 1e-12;
8
9const UNITARITY_TOLERANCE: f64 = 1e-10;
11
12impl PtmChannel {
13 pub fn unitary_1q(qubit: usize, u: [[Complex64; 2]; 2]) -> Self {
19 assert_unitary("unitary_1q", &u);
20 let u_dagger = dagger(&u);
21 let mut entries = Vec::new();
22 for s in 0..4 {
23 let conjugated = matmul(&matmul(&u, &pauli_matrix(s)), &u_dagger);
24 for t in 0..4 {
25 let value =
26 trace(&matmul(&pauli_matrix(t), &conjugated)) / Complex64::new(2.0, 0.0);
27 entries.push(((s as u8, t as u8), clean(value)));
28 }
29 }
30 Self::new(&[qubit], sparse_storage(entries), PtmKind::Unitary1q)
31 }
32
33 pub fn unitary_2q(q0: usize, q1: usize, u: [[Complex64; 4]; 4]) -> Self {
39 assert_distinct("unitary_2q", q0, q1);
40 assert_unitary("unitary_2q", &u);
41 let u_dagger = dagger(&u);
42 let two_qubit_pauli = |s: usize| kron(&pauli_matrix(s & 3), &pauli_matrix(s >> 2));
43 let mut entries = Vec::new();
44 for s in 0..16 {
45 let conjugated = matmul(&matmul(&u, &two_qubit_pauli(s)), &u_dagger);
46 for t in 0..16 {
47 let value =
48 trace(&matmul(&two_qubit_pauli(t), &conjugated)) / Complex64::new(4.0, 0.0);
49 entries.push(((s as u8, t as u8), clean(value)));
50 }
51 }
52 Self::new(&[q0, q1], sparse_storage(entries), PtmKind::Unitary2q)
53 }
54}
55
56fn assert_unitary<const N: usize>(constructor: &str, u: &[[Complex64; N]; N]) {
58 let product = matmul(&dagger(u), u);
59 let deviation = (0..N)
60 .flat_map(|i| (0..N).map(move |j| (i, j)))
61 .map(|(i, j)| {
62 let identity = if i == j { ONE } else { ZERO };
63 (product[i][j] - identity).norm()
64 })
65 .fold(
66 0.0,
67 |largest: f64, entry| {
68 if entry > largest || entry.is_nan() {
69 entry
70 } else {
71 largest
72 }
73 },
74 );
75 assert!(
76 deviation <= UNITARITY_TOLERANCE,
77 "PtmChannel::{constructor}: the matrix is not unitary (largest entry of U†U − I is {deviation:e}, tolerance {UNITARITY_TOLERANCE:e})"
78 );
79}
80
81fn pauli_matrix(index: usize) -> [[Complex64; 2]; 2] {
83 let i = Complex64::new(0.0, 1.0);
84 match index {
85 0 => [[ONE, ZERO], [ZERO, ONE]],
86 1 => [[ZERO, ONE], [ONE, ZERO]],
87 2 => [[ONE, ZERO], [ZERO, -ONE]],
88 _ => [[ZERO, -i], [i, ZERO]],
89 }
90}
91
92fn matmul<const N: usize>(a: &[[Complex64; N]; N], b: &[[Complex64; N]; N]) -> [[Complex64; N]; N] {
93 let mut out = [[ZERO; N]; N];
94 for i in 0..N {
95 for k in 0..N {
96 let aik = a[i][k];
97 if aik == ZERO {
98 continue;
99 }
100 for j in 0..N {
101 out[i][j] += aik * b[k][j];
102 }
103 }
104 }
105 out
106}
107
108fn dagger<const N: usize>(a: &[[Complex64; N]; N]) -> [[Complex64; N]; N] {
109 core::array::from_fn(|i| core::array::from_fn(|j| a[j][i].conj()))
110}
111
112fn trace<const N: usize>(a: &[[Complex64; N]; N]) -> Complex64 {
113 a.iter().enumerate().map(|(i, row)| row[i]).sum()
114}
115
116fn kron(a: &[[Complex64; 2]; 2], b: &[[Complex64; 2]; 2]) -> [[Complex64; 4]; 4] {
118 core::array::from_fn(|row| {
119 core::array::from_fn(|column| a[row / 2][column / 2] * b[row % 2][column % 2])
120 })
121}
122
123fn clean(v: Complex64) -> Complex64 {
125 let snap = |part: f64| if part.abs() < PTM_EPS { 0.0 } else { part };
126 Complex64::new(snap(v.re), snap(v.im))
127}
128
129#[cfg(test)]
130mod tests;