1use itertools::Itertools;
8use ndarray::{Array, Array2, Array3, ArrayView2, Axis};
9use rand::{distributions::WeightedIndex, prelude::Distribution, thread_rng};
10
11use crate::{
12 chp_decompositions::ChpOperation, linalg::ConjBy, log, log_as_err, Pauli, Process,
13 ProcessData::*, State, StateData::*, Tableau, C64,
14};
15
16use super::promote_pauli_channel;
17
18impl Process {
19 pub fn apply(&self, state: &State) -> Result<State, String> {
22 if state.n_qubits != self.n_qubits {
23 return Err(format!(
24 "Channel acts on {} qubits, but was applied to {}-qubit state.",
25 self.n_qubits, state.n_qubits
26 ));
27 }
28
29 match &self.data {
30 Unitary(u) => apply_unitary(u, state),
31 KrausDecomposition(ks) => apply_kraus_decomposition(ks, state),
32 MixedPauli(paulis) => apply_pauli_channel(paulis, state),
33 Sequence(processes) => {
34 let mut acc_state = state.clone();
36 for process in processes {
37 acc_state = process.apply(state)?;
38 }
39 Ok(acc_state)
40 }
41 ChpDecomposition(_operations) => todo!(),
42 Unsupported => Err("Unsupported quantum process.".to_string()),
43 }
44 }
45
46 pub fn apply_to(&self, idx_qubits: &[usize], state: &State) -> Result<State, String> {
49 if let Sequence(channels) = &self.data {
51 let mut acc_state = state.clone();
53 for channel in channels {
54 acc_state = channel.apply_to(idx_qubits, &acc_state)?;
55 }
56 return Ok(acc_state);
57 }
58
59 if state.n_qubits < self.n_qubits {
61 return log_as_err(format!(
62 "Channel acts on {} qubits, but a state on only {} qubits was given.\n\nChannel:\n{:?}\n\nState:\n{:?}",
63 self.n_qubits, state.n_qubits, self, state
64 ));
65 }
66
67 if idx_qubits.iter().unique().count() < idx_qubits.len() {
69 return log_as_err(format!(
70 "List of qubit indices {:?} contained repeated elements.",
71 idx_qubits
72 ));
73 }
74
75 if idx_qubits.len() != self.n_qubits {
78 return log_as_err(format!(
79 "Qubit indices were specified as {:?}, but this channel only acts on {} qubits.",
80 idx_qubits, self.n_qubits
81 ));
82 }
83
84 if let ChpDecomposition(operations) = &self.data {
103 if let Stabilizer(tableau) = &state.data {
104 return apply_chp_decomposition_to(operations, state.n_qubits, idx_qubits, tableau);
105 }
106 }
107
108 match self.n_qubits {
111 1 => {
112 if state.n_qubits == 1 {
113 self.apply(state)
114 } else {
115 self.extend_one_to_n(idx_qubits[0], state.n_qubits)
116 .apply(state)
117 }
118 }
119 2 => self
122 .extend_two_to_n(idx_qubits[0], idx_qubits[1], state.n_qubits)
123 .apply(state),
124 _ => {
125 log(&format!(
126 "Expanding {}-qubit channels is not yet implemented.",
127 self.n_qubits
128 ));
129 unimplemented!("");
130 }
131 }
132 }
133}
134
135fn apply_chp_decomposition_to(
136 operations: &[ChpOperation],
137 n_qubits: usize,
138 idx_qubits: &[usize],
139 tableau: &Tableau,
140) -> Result<State, String> {
141 let mut new_tableau = tableau.clone();
142 for operation in operations {
143 match *operation {
144 ChpOperation::Phase(idx) => new_tableau.apply_s_mut(idx_qubits[idx]),
145 ChpOperation::AdjointPhase(idx) => new_tableau.apply_s_adj_mut(idx_qubits[idx]),
146 ChpOperation::Hadamard(idx) => new_tableau.apply_h_mut(idx_qubits[idx]),
147 ChpOperation::Cnot(idx_control, idx_target) => {
148 new_tableau.apply_cnot_mut(idx_qubits[idx_control], idx_qubits[idx_target])
149 }
150 };
151 }
152 Ok(State {
153 n_qubits,
154 data: Stabilizer(new_tableau),
155 })
156}
157
158pub(crate) fn apply_unitary(u: &Array2<C64>, state: &State) -> Result<State, String> {
159 Ok(State {
160 n_qubits: state.n_qubits,
161 data: match &state.data {
162 Pure(psi) => Pure(u.dot(psi)),
163 Mixed(rho) => Mixed(rho.conjugate_by(&u.into())),
164 Stabilizer(_tableau) => {
165 return Err(
166 "TODO: Promote stabilizer state to state vector and recurse.".to_string(),
167 )
168 }
169 },
170 })
171}
172
173pub(crate) fn apply_kraus_decomposition(ks: &Array3<C64>, state: &State) -> Result<State, String> {
174 Ok(State {
175 n_qubits: state.n_qubits,
176 data: match &state.data {
177 Pure(psi) => {
178 if ks.shape()[0] == 1 {
182 Pure({
183 let k: ArrayView2<C64> = ks.slice(s![0, .., ..]);
184 k.dot(psi)
185 })
186 } else {
187 apply_kraus_decomposition(ks, &state.to_mixed())?.data
188 }
189 }
190 Mixed(rho) => Mixed({
191 let mut sum: Array2<C64> = Array::zeros((rho.shape()[0], rho.shape()[1]));
192 for k in ks.axis_iter(Axis(0)) {
193 sum = sum + rho.conjugate_by(&k);
194 }
195 sum
196 }),
197 Stabilizer(_tableau) => {
198 return Err(
199 "TODO: Promote stabilizer state to state vector and recurse.".to_string(),
200 )
201 }
202 },
203 })
204}
205
206pub(crate) fn apply_pauli_channel(
207 paulis: &[(f64, Vec<Pauli>)],
208 state: &State,
209) -> Result<State, String> {
210 Ok(State {
211 n_qubits: state.n_qubits,
212 data: match &state.data {
213 Pure(_) | Mixed(_) => {
214 let promoted = promote_pauli_channel(paulis);
216 return promoted.apply(state);
217 }
218 Stabilizer(tableau) => {
219 let mut new_tableau = tableau.clone();
222 let weighted = WeightedIndex::new(paulis.iter().map(|(pr, _)| pr)).unwrap();
224 let idx = weighted.sample(&mut thread_rng());
225 let pauli = &(&paulis)[idx].1;
226 for (idx_qubit, p) in pauli.iter().enumerate() {
229 match p {
230 Pauli::I => (),
231 Pauli::X => new_tableau.apply_x_mut(idx_qubit),
232 Pauli::Y => new_tableau.apply_y_mut(idx_qubit),
233 Pauli::Z => new_tableau.apply_z_mut(idx_qubit),
234 }
235 }
236 Stabilizer(new_tableau)
237 }
238 },
239 })
240}