1mod apply;
5
6use crate::chp_decompositions::ChpOperation;
7use crate::linalg::{extend_one_to_n, extend_two_to_n, zeros_like};
8use crate::processes::ProcessData::{KrausDecomposition, MixedPauli, Unitary};
9use crate::NoiseModel;
10use crate::QubitSized;
11use crate::C64;
12use crate::{AsUnitary, Pauli};
13use itertools::Itertools;
14use ndarray::{Array, Array2, Array3, Axis, NewAxis};
15use num_complex::Complex;
16use num_traits::{One, Zero};
17use serde::{Deserialize, Serialize};
18use std::convert::TryInto;
19use std::ops::Add;
20use std::ops::Mul;
21
22pub type Process = QubitSized<ProcessData>;
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
30pub enum ProcessData {
31 MixedPauli(Vec<(f64, Vec<Pauli>)>),
35
36 Unitary(Array2<C64>),
38
39 KrausDecomposition(Array3<C64>), Sequence(Vec<Process>),
48
49 ChpDecomposition(Vec<ChpOperation>),
52
53 Unsupported,
56}
57
58impl Process {
59 pub fn new_pauli_channel<T: IntoPauliMixture>(data: T) -> Self {
61 let data = data.into_pauli_mixture();
62 let n_qubits = data[0].1.len();
66 Process {
67 n_qubits,
68 data: MixedPauli(data),
69 }
70 }
71 pub fn as_json(&self) -> String {
75 serde_json::to_string(&self).unwrap()
76 }
77
78 pub fn extend_one_to_n(&self, idx_qubit: usize, n_qubits: usize) -> Process {
81 assert_eq!(self.n_qubits, 1);
82 Process {
83 n_qubits,
84 data: match &self.data {
85 Unitary(u) => Unitary(extend_one_to_n(u.view(), idx_qubit, n_qubits)),
86 KrausDecomposition(ks) => {
87 let new_dim = 2usize.pow(n_qubits.try_into().unwrap());
88 let n_kraus = ks.shape()[0];
89 let mut extended: Array3<C64> = Array::zeros((n_kraus, new_dim, new_dim));
90 for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
91 let mut target = extended.index_axis_mut(Axis(0), idx_kraus);
92 let big_kraus = extend_one_to_n(kraus.view(), idx_qubit, n_qubits);
93 target.assign(&big_kraus);
94 }
95 KrausDecomposition(extended)
96 },
97 MixedPauli(paulis) => MixedPauli(
98 paulis.iter()
99 .map(|(pr, pauli)| {
100 if pauli.len() != 1 {
101 panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits.");
102 }
103 let p = pauli[0];
104 let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec();
105 extended[idx_qubit] = p;
106 (*pr, extended)
107 })
108 .collect_vec()
109 ),
110 ProcessData::Unsupported => ProcessData::Unsupported,
111 ProcessData::Sequence(processes) => ProcessData::Sequence(
112 processes.iter().map(|p| p.extend_one_to_n(idx_qubit, n_qubits)).collect()
113 ),
114 ProcessData::ChpDecomposition(_) => todo!(),
115 },
116 }
117 }
118
119 pub fn extend_two_to_n(
122 &self,
123 idx_qubit1: usize,
124 idx_qubit2: usize,
125 n_qubits: usize,
126 ) -> Process {
127 assert_eq!(self.n_qubits, 2);
128 Process {
129 n_qubits,
130 data: match &self.data {
131 Unitary(u) => Unitary(extend_two_to_n(u.view(), idx_qubit1, idx_qubit2, n_qubits)),
132 KrausDecomposition(ks) => {
133 let new_dim = 2usize.pow(n_qubits.try_into().unwrap());
135 let n_kraus = ks.shape()[0];
136 let mut extended: Array3<C64> = Array::zeros((n_kraus, new_dim, new_dim));
137 for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
138 let mut target = extended.index_axis_mut(Axis(0), idx_kraus);
139 let big_kraus = extend_two_to_n(kraus, idx_qubit1, idx_qubit2, n_qubits);
140 target.assign(&big_kraus);
141 }
142 KrausDecomposition(extended)
143 },
144 MixedPauli(paulis) => MixedPauli(
145 paulis.iter()
146 .map(|(pr, pauli)| {
147 if pauli.len() != 2 {
148 panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits.");
149 }
150 let p = (pauli[0], pauli[1]);
151 let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec();
152 extended[idx_qubit1] = p.0;
153 extended[idx_qubit2] = p.1;
154 (*pr, extended)
155 })
156 .collect_vec()
157 ),
158 ProcessData::Unsupported => ProcessData::Unsupported,
159 ProcessData::Sequence(processes) => ProcessData::Sequence(
160 processes.iter().map(|p| p.extend_two_to_n(idx_qubit1, idx_qubit2, n_qubits)).collect()
161 ),
162 ProcessData::ChpDecomposition(_) => todo!(),
163 },
164 }
165 }
166}
167
168impl Mul<&Process> for C64 {
169 type Output = Process;
170
171 fn mul(self, channel: &Process) -> Self::Output {
172 Process {
173 n_qubits: channel.n_qubits,
174 data: match &channel.data {
175 Unitary(u) => KrausDecomposition({
180 let mut ks = Array3::<C64>::zeros((1, u.shape()[0], u.shape()[1]));
181 ks.index_axis_mut(Axis(0), 0).assign(&(self.sqrt() * u));
182 ks
183 }),
184 KrausDecomposition(ks) => KrausDecomposition(self.sqrt() * ks),
185 MixedPauli(paulis) => (self * promote_pauli_channel(paulis)).data,
186 ProcessData::Unsupported => ProcessData::Unsupported,
187 ProcessData::Sequence(processes) => {
188 ProcessData::Sequence(processes.iter().map(|p| self * p).collect())
189 }
190 ProcessData::ChpDecomposition(_) => todo!(),
191 },
192 }
193 }
194}
195
196impl Mul<Process> for C64 {
197 type Output = Process;
198 fn mul(self, channel: Process) -> Self::Output {
199 self * (&channel)
200 }
201}
202
203impl Mul<&Process> for f64 {
204 type Output = Process;
205 fn mul(self, chanel: &Process) -> Self::Output {
206 C64::new(self, 0f64) * chanel
207 }
208}
209
210impl Mul<Process> for f64 {
211 type Output = Process;
212 fn mul(self, channel: Process) -> Self::Output {
213 self * (&channel)
214 }
215}
216
217impl Mul<&Process> for &Process {
219 type Output = Process;
220
221 fn mul(self, rhs: &Process) -> Self::Output {
222 assert_eq!(self.n_qubits, rhs.n_qubits);
223 Process {
224 n_qubits: self.n_qubits,
225 data: match (&self.data, &rhs.data) {
226 (Unitary(u), Unitary(v)) => Unitary(u.dot(v)),
227 (Unitary(u), KrausDecomposition(ks)) => {
228 let mut post = zeros_like(ks);
230 for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() {
231 post.index_axis_mut(Axis(0), idx_kraus)
232 .assign(&u.dot(&kraus));
233 }
234 KrausDecomposition(post)
235 }
236 _ => todo!(),
239 },
240 }
241 }
242}
243
244impl Add<&Process> for &Process {
245 type Output = Process;
246
247 fn add(self, rhs: &Process) -> Self::Output {
248 assert_eq!(self.n_qubits, rhs.n_qubits);
249 Process {
250 n_qubits: self.n_qubits,
251 data: match (&self.data, &rhs.data) {
252 (KrausDecomposition(ks1), KrausDecomposition(ks2)) => {
253 let mut sum = Array::zeros([
254 ks1.shape()[0] + ks2.shape()[0],
255 ks1.shape()[1],
256 ks1.shape()[2],
257 ]);
258 for (idx_kraus, kraus) in ks1.axis_iter(Axis(0)).enumerate() {
259 sum.index_axis_mut(Axis(0), idx_kraus).assign(&kraus);
260 }
261 for (idx_kraus, kraus) in ks2.axis_iter(Axis(0)).enumerate() {
262 sum.index_axis_mut(Axis(0), ks1.shape()[0] + idx_kraus)
263 .assign(&kraus);
264 }
265 KrausDecomposition(sum)
266 }
267 _ => todo!(),
268 },
269 }
270 }
271}
272
273impl Mul<Process> for &Process {
274 type Output = Process;
275
276 fn mul(self, rhs: Process) -> Self::Output {
277 self * &rhs
278 }
279}
280
281impl Mul<&Process> for Process {
282 type Output = Process;
283
284 fn mul(self, rhs: &Process) -> Self::Output {
285 &self * rhs
286 }
287}
288
289impl Mul<Process> for Process {
290 type Output = Process;
291
292 fn mul(self, rhs: Process) -> Self::Output {
293 &self * &rhs
294 }
295}
296
297impl Add<Process> for &Process {
298 type Output = Process;
299
300 fn add(self, rhs: Process) -> Self::Output {
301 self + &rhs
302 }
303}
304
305impl Add<&Process> for Process {
306 type Output = Process;
307
308 fn add(self, rhs: &Process) -> Self::Output {
309 &self + rhs
310 }
311}
312
313impl Add<Process> for Process {
314 type Output = Process;
315
316 fn add(self, rhs: Process) -> Self::Output {
317 &self + &rhs
318 }
319}
320
321pub fn depolarizing_channel(p: f64) -> Process {
324 let ideal = NoiseModel::ideal();
325 (1.0 - p) * ideal.i + p / 3.0 * ideal.x + p / 3.0 * ideal.y + p / 3.0 * ideal.z
326}
327
328pub fn amplitude_damping_channel(gamma: f64) -> Process {
332 Process {
333 n_qubits: 1,
334 data: KrausDecomposition(array![
335 [
336 [C64::one(), C64::zero()],
337 [C64::zero(), C64::one() * (1.0 - gamma).sqrt()]
338 ],
339 [
340 [C64::zero(), C64::one() * gamma.sqrt()],
341 [C64::zero(), C64::zero()]
342 ]
343 ]),
344 }
345}
346
347pub trait IntoPauliMixture {
349 fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)>;
351}
352
353impl IntoPauliMixture for Vec<(f64, Vec<Pauli>)> {
354 fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
355 self
356 }
357}
358
359impl IntoPauliMixture for Vec<Pauli> {
360 fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
361 vec![(1.0, self)]
362 }
363}
364
365impl IntoPauliMixture for Vec<(f64, Pauli)> {
366 fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
367 self.iter().map(|(pr, p)| (*pr, vec![*p])).collect_vec()
368 }
369}
370
371impl IntoPauliMixture for Pauli {
372 fn into_pauli_mixture(self) -> Vec<(f64, Vec<Pauli>)> {
373 vec![(1.0, vec![self])]
374 }
375}
376
377fn promote_pauli_channel(paulis: &[(f64, Vec<Pauli>)]) -> Process {
384 if paulis.len() == 1 {
387 let (_, pauli) = &paulis[0];
389 Process {
391 n_qubits: pauli.len(),
392 data: Unitary(pauli.as_unitary()),
393 }
394 } else {
395 let matrices = paulis
398 .iter()
399 .map(|p| {
400 p.1.as_unitary()
401 * Complex {
402 re: p.0.sqrt(),
403 im: 0.0f64,
404 }
405 })
406 .map(|u| {
407 let x = u.slice(s![NewAxis, .., ..]);
408 x.to_owned()
409 })
410 .collect_vec();
411 Process {
412 n_qubits: paulis[0].1.len(),
413 data: KrausDecomposition(
414 ndarray::concatenate(
415 Axis(0),
416 matrices.iter().map(|u| u.view()).collect_vec().as_slice(),
417 )
418 .unwrap(),
419 ),
420 }
421 }
422}