microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
source/paulimer/src/outcome_specific_simulation.rs
316lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use rand::{thread_rng, Rng}; |
| 5 | |
| 6 | use crate::{ |
| 7 | bits::{Bitwise, IndexSet}, |
| 8 | clifford::{ |
| 9 | Clifford, CliffordMutable, CliffordUnitary, ControlledPauli, Hadamard, PauliExponent, Swap, |
| 10 | }, |
| 11 | pauli::{anti_commutes_with, generic::PhaseExponent, Pauli, PauliBits, PauliUnitary, Phase}, |
| 12 | quantum_core, Simulation, UnitaryOp, |
| 13 | }; |
| 14 | |
| 15 | type SparsePauli = PauliUnitary<IndexSet, u8>; |
| 16 | |
| 17 | #[must_use] |
| 18 | pub struct OutcomeSpecificSimulation { |
| 19 | clifford: CliffordUnitary, // R |
| 20 | outcome_vector: Vec<bool>, |
| 21 | random_outcome_indicator: Vec<bool>, // vec(p), [j] is true iff vec(p)_j = 1/2 |
| 22 | num_random_bits: usize, |
| 23 | use_all_zeros: bool, |
| 24 | } |
| 25 | |
| 26 | impl OutcomeSpecificSimulation { |
| 27 | pub fn new(num_qubits: usize, num_outcomes: usize) -> Self { |
| 28 | OutcomeSpecificSimulation { |
| 29 | clifford: CliffordUnitary::identity(num_qubits), |
| 30 | outcome_vector: Vec::<bool>::with_capacity(num_outcomes), |
| 31 | random_outcome_indicator: Vec::<bool>::with_capacity(num_outcomes), |
| 32 | num_random_bits: 0, |
| 33 | use_all_zeros: false, |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | pub fn new_with_random_outcomes(num_qubits: usize, num_outcomes: usize) -> Self { |
| 38 | Self::new(num_qubits, num_outcomes) |
| 39 | } |
| 40 | |
| 41 | pub fn new_with_zero_outcomes(num_qubits: usize, num_outcomes: usize) -> Self { |
| 42 | let mut result = Self::new(num_qubits, num_outcomes); |
| 43 | result.use_all_zeros = true; |
| 44 | result |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | pub fn new_outcome_specific_simulation( |
| 49 | num_qubits: usize, |
| 50 | num_outcomes: usize, |
| 51 | ) -> OutcomeSpecificSimulation { |
| 52 | OutcomeSpecificSimulation::new_with_random_outcomes(num_qubits, num_outcomes) |
| 53 | } |
| 54 | |
| 55 | impl OutcomeSpecificSimulation { |
| 56 | pub fn clifford(&self) -> &CliffordUnitary { |
| 57 | &self.clifford |
| 58 | } |
| 59 | |
| 60 | #[must_use] |
| 61 | pub fn outcome_vector(&self) -> &Vec<bool> { |
| 62 | &self.outcome_vector |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | pub fn apply_hadamard(simulation: &mut OutcomeSpecificSimulation, qubit_index: usize) { |
| 67 | Hadamard(qubit_index) * &mut simulation.clifford; |
| 68 | } |
| 69 | |
| 70 | pub fn apply_cx(simulation: &mut OutcomeSpecificSimulation, control_id: usize, target_id: usize) { |
| 71 | let control = PauliUnitary::from_bits(IndexSet::new(), IndexSet::from_iter([control_id]), 0u8); |
| 72 | let target = PauliUnitary::from_bits(IndexSet::from_iter([target_id]), IndexSet::new(), 0u8); |
| 73 | ControlledPauli::new(control, target) * &mut simulation.clifford; |
| 74 | } |
| 75 | |
| 76 | pub fn apply_cz(simulation: &mut OutcomeSpecificSimulation, control_id: usize, target_id: usize) { |
| 77 | let control = PauliUnitary::from_bits(IndexSet::new(), IndexSet::from_iter([control_id]), 0u8); |
| 78 | let target = PauliUnitary::from_bits(IndexSet::new(), IndexSet::from_iter([target_id]), 0u8); |
| 79 | ControlledPauli::new(control, target) * &mut simulation.clifford; |
| 80 | } |
| 81 | |
| 82 | pub fn apply_pauli<Bits: PauliBits, Phase: PhaseExponent>( |
| 83 | simulation: &mut OutcomeSpecificSimulation, |
| 84 | pauli: &PauliUnitary<Bits, Phase>, |
| 85 | ) { |
| 86 | pauli * &mut simulation.clifford; |
| 87 | } |
| 88 | |
| 89 | pub fn apply_pauli_exponent<Bits: PauliBits, Phase: PhaseExponent>( |
| 90 | simulation: &mut OutcomeSpecificSimulation, |
| 91 | pauli: PauliUnitary<Bits, Phase>, |
| 92 | ) { |
| 93 | // simulation.clifford = PauliExponent(pauli) * simulation.clifford; |
| 94 | // clifford = PauliExponent(Pauli) * clifford; |
| 95 | PauliExponent::new(pauli) * &mut simulation.clifford; |
| 96 | } |
| 97 | |
| 98 | pub fn apply_controlled_pauli<Bits: PauliBits, Phase: PhaseExponent>( |
| 99 | simulation: &mut OutcomeSpecificSimulation, |
| 100 | control: PauliUnitary<Bits, Phase>, |
| 101 | target: PauliUnitary<Bits, Phase>, |
| 102 | ) { |
| 103 | ControlledPauli::new(control, target) * &mut simulation.clifford; |
| 104 | } |
| 105 | |
| 106 | pub fn apply_swap(simulation: &mut OutcomeSpecificSimulation, qubit_id1: usize, qubit_id2: usize) { |
| 107 | Swap(qubit_id1, qubit_id2) * &mut simulation.clifford; |
| 108 | } |
| 109 | |
| 110 | /// # Panics |
| 111 | /// Panics if `hint` commutes with `observable` |
| 112 | pub fn measure_pauli_with_hint<HintBits: PauliBits, HintPhase: PhaseExponent>( |
| 113 | simulation: &mut OutcomeSpecificSimulation, |
| 114 | observable: &SparsePauli, |
| 115 | hint: &PauliUnitary<HintBits, HintPhase>, |
| 116 | ) { |
| 117 | assert!( |
| 118 | anti_commutes_with(observable, hint), |
| 119 | "observable={observable}, hint={hint}" |
| 120 | ); |
| 121 | let preimage = simulation.clifford.preimage(hint); |
| 122 | |
| 123 | if preimage.x_bits().support().next().is_some() { |
| 124 | // hint is not true |
| 125 | measure_pauli(simulation, observable); |
| 126 | } else { |
| 127 | let mut pauli = observable.clone() * hint; |
| 128 | pauli *= Phase::from_exponent(3u8.wrapping_sub(preimage.xz_phase_exponent().raw_value())); |
| 129 | PauliExponent::new(pauli) * &mut simulation.clifford; |
| 130 | allocate_random_bit(simulation); |
| 131 | apply_conditional_pauli( |
| 132 | simulation, |
| 133 | hint, |
| 134 | &[simulation.outcome_vector.len() - 1], |
| 135 | true, |
| 136 | ); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | pub fn allocate_random_bit(simulation: &mut OutcomeSpecificSimulation) { |
| 141 | simulation.outcome_vector.push(if simulation.use_all_zeros { |
| 142 | false |
| 143 | } else { |
| 144 | thread_rng().gen() |
| 145 | }); |
| 146 | simulation.random_outcome_indicator.push(true); |
| 147 | simulation.num_random_bits += 1; |
| 148 | } |
| 149 | |
| 150 | pub fn measure_pauli(simulation: &mut OutcomeSpecificSimulation, observable: &SparsePauli) { |
| 151 | let preimage = simulation.clifford.preimage(observable); |
| 152 | let non_zero_pos = preimage.x_bits().support().next(); |
| 153 | match non_zero_pos { |
| 154 | Some(pos) => { |
| 155 | let hint = simulation.clifford.image_z(pos); |
| 156 | measure_pauli_with_hint(simulation, observable, &hint); |
| 157 | } |
| 158 | None => { |
| 159 | measure_deterministic(simulation, &preimage); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | fn measure_deterministic<Bits: PauliBits, Phase: PhaseExponent>( |
| 165 | simulation: &mut OutcomeSpecificSimulation, |
| 166 | preimage: &PauliUnitary<Bits, Phase>, |
| 167 | ) { |
| 168 | debug_assert!(preimage.xz_phase_exponent().is_even()); |
| 169 | simulation |
| 170 | .outcome_vector |
| 171 | .push(preimage.xz_phase_exponent().value() == 2); |
| 172 | simulation.random_outcome_indicator.push(false); |
| 173 | } |
| 174 | |
| 175 | fn is_stabilizer<Bits: PauliBits, Phase: PhaseExponent>( |
| 176 | simulation: &OutcomeSpecificSimulation, |
| 177 | pauli: &PauliUnitary<Bits, Phase>, |
| 178 | ) -> bool { |
| 179 | let preimage = simulation.clifford.preimage(pauli); |
| 180 | preimage.x_bits().weight() == 0 && preimage.xz_phase_exponent().value() == 0 |
| 181 | } |
| 182 | |
| 183 | fn is_stabilizer_up_to_sign<Bits: PauliBits, Phase: PhaseExponent>( |
| 184 | simulation: &OutcomeSpecificSimulation, |
| 185 | pauli: &PauliUnitary<Bits, Phase>, |
| 186 | ) -> bool { |
| 187 | let preimage = simulation.clifford.preimage(pauli); |
| 188 | preimage.x_bits().weight() == 0 |
| 189 | } |
| 190 | |
| 191 | pub fn apply_conditional_pauli<Bits: PauliBits, Phase: PhaseExponent>( |
| 192 | simulation: &mut OutcomeSpecificSimulation, |
| 193 | pauli: &PauliUnitary<Bits, Phase>, |
| 194 | outcomes_indicator: &[usize], |
| 195 | parity: bool, |
| 196 | ) { |
| 197 | if total_parity(simulation.outcome_vector(), outcomes_indicator) == parity { |
| 198 | apply_pauli(simulation, pauli); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | fn total_parity(outcome_vector: &[bool], outcomes_indicator: &[usize]) -> bool { |
| 203 | let mut res = false; |
| 204 | for j in outcomes_indicator { |
| 205 | res ^= outcome_vector[*j]; |
| 206 | } |
| 207 | res |
| 208 | } |
| 209 | |
| 210 | #[test] |
| 211 | fn init_test() { |
| 212 | let mut _outcome_specific_simulation = new_outcome_specific_simulation(2, 10); |
| 213 | // println!("{:?}",outcome_specific_simulation.random_outcome_source()) |
| 214 | } |
| 215 | |
| 216 | impl Simulation for OutcomeSpecificSimulation { |
| 217 | fn pauli_exp(&mut self, observable: &[quantum_core::PositionedPauliObservable]) { |
| 218 | let pauli = SparsePauli::from(observable); |
| 219 | apply_pauli_exponent(self, pauli); |
| 220 | } |
| 221 | |
| 222 | fn controlled_pauli( |
| 223 | &mut self, |
| 224 | observable1: &[quantum_core::PositionedPauliObservable], |
| 225 | observable2: &[quantum_core::PositionedPauliObservable], |
| 226 | ) { |
| 227 | let pauli1 = SparsePauli::from(observable1); |
| 228 | let pauli2 = SparsePauli::from(observable2); |
| 229 | apply_controlled_pauli(self, pauli1, pauli2); |
| 230 | } |
| 231 | |
| 232 | fn pauli(&mut self, observable: &[quantum_core::PositionedPauliObservable]) { |
| 233 | let pauli = SparsePauli::from(observable); |
| 234 | apply_pauli(self, &pauli); |
| 235 | } |
| 236 | |
| 237 | fn measure(&mut self, observable: &[quantum_core::PositionedPauliObservable]) -> usize { |
| 238 | let pauli = SparsePauli::from(observable); |
| 239 | measure_pauli(self, &pauli); |
| 240 | self.outcome_vector().len() - 1 |
| 241 | } |
| 242 | |
| 243 | fn measure_sparse(&mut self, observable: &SparsePauli) -> usize { |
| 244 | measure_pauli(self, observable); |
| 245 | self.outcome_vector().len() - 1 |
| 246 | } |
| 247 | |
| 248 | fn measure_with_hint( |
| 249 | &mut self, |
| 250 | observable: &[quantum_core::PositionedPauliObservable], |
| 251 | hint: &[quantum_core::PositionedPauliObservable], |
| 252 | ) -> usize { |
| 253 | let pauli = SparsePauli::from(observable); |
| 254 | let hint = SparsePauli::from(hint); |
| 255 | measure_pauli_with_hint(self, &pauli, &hint); |
| 256 | self.outcome_vector().len() - 1 |
| 257 | } |
| 258 | |
| 259 | fn assert_stabilizer(&self, observable: &[quantum_core::PositionedPauliObservable]) { |
| 260 | let sparse_pauli = SparsePauli::from(observable); |
| 261 | assert!(is_stabilizer(self, &sparse_pauli)); |
| 262 | } |
| 263 | |
| 264 | fn assert_stabilizer_up_to_sign(&self, observable: &[quantum_core::PositionedPauliObservable]) { |
| 265 | let sparse_pauli = SparsePauli::from(observable); |
| 266 | assert!(is_stabilizer_up_to_sign(self, &sparse_pauli)); |
| 267 | } |
| 268 | |
| 269 | fn assert_anti_stabilizer(&self, observable: &[quantum_core::PositionedPauliObservable]) { |
| 270 | let sparse_pauli = SparsePauli::from(observable); |
| 271 | assert!(!is_stabilizer_up_to_sign(self, &sparse_pauli)); |
| 272 | } |
| 273 | |
| 274 | fn with_capacity(num_qubits: usize, num_outcomes: usize, _num_random_outcomes: usize) -> Self { |
| 275 | OutcomeSpecificSimulation::new_with_random_outcomes(num_qubits, num_outcomes) |
| 276 | } |
| 277 | |
| 278 | fn new() -> Self { |
| 279 | Self::with_capacity(1, 1, 1) |
| 280 | } |
| 281 | |
| 282 | fn conditional_pauli( |
| 283 | &mut self, |
| 284 | observable: &[quantum_core::PositionedPauliObservable], |
| 285 | outcomes: &[usize], |
| 286 | parity: bool, |
| 287 | ) { |
| 288 | let pauli = SparsePauli::from(observable); |
| 289 | apply_conditional_pauli(self, &pauli, outcomes, parity); |
| 290 | } |
| 291 | |
| 292 | fn random_bit(&mut self) -> usize { |
| 293 | allocate_random_bit(self); |
| 294 | self.num_random_bits - 1 |
| 295 | } |
| 296 | |
| 297 | fn num_random_outcomes(&self) -> usize { |
| 298 | self.num_random_bits |
| 299 | } |
| 300 | |
| 301 | fn random_outcome_indicator(&self) -> &[bool] { |
| 302 | &self.random_outcome_indicator |
| 303 | } |
| 304 | |
| 305 | fn apply_unitary(&mut self, unitary_op: UnitaryOp, support: &[usize]) { |
| 306 | self.clifford.left_mul(unitary_op, support); |
| 307 | } |
| 308 | |
| 309 | fn apply_clifford(&mut self, clifford: &CliffordUnitary, support: &[usize]) { |
| 310 | self.clifford.left_mul_clifford(clifford, support); |
| 311 | } |
| 312 | |
| 313 | fn apply_permutation(&mut self, permutation: &[usize], support: &[usize]) { |
| 314 | self.clifford.left_mul_permutation(permutation, support); |
| 315 | } |
| 316 | } |
| 317 | |