diff --git a/spinoza/examples/u.rs b/spinoza/examples/u.rs index 435422b..622a697 100644 --- a/spinoza/examples/u.rs +++ b/spinoza/examples/u.rs @@ -11,7 +11,7 @@ fn u(n: usize, show_results: bool) { let mut state = State::new(n); for i in 0..n { - apply(Gate::U((1.0, 2.0, 3.0)), &mut state, i); + apply(Gate::U(1.0, 2.0, 3.0), &mut state, i); } let elapsed = now.elapsed().as_micros(); diff --git a/spinoza/examples/unitaries.rs b/spinoza/examples/unitaries.rs index 0bfcb84..e7f8ec2 100644 --- a/spinoza/examples/unitaries.rs +++ b/spinoza/examples/unitaries.rs @@ -7,7 +7,7 @@ use spinoza::{ fn main() { let n = 16; let state = State::new(n); - let u = Unitary::from_single_qubit_gate(&state, Gate::U((1.0, 2.0, 3.0)), 0); + let u = Unitary::from_single_qubit_gate(&state, Gate::U(1.0, 2.0, 3.0), 0); let now = std::time::Instant::now(); let _s = apply_unitary(&state, &u); diff --git a/spinoza/src/circuit.rs b/spinoza/src/circuit.rs index c576dd4..16cf591 100644 --- a/spinoza/src/circuit.rs +++ b/spinoza/src/circuit.rs @@ -223,7 +223,7 @@ impl QuantumCircuit { #[inline] pub fn swap(&mut self, t0: usize, t1: usize) { self.add(QuantumTransformation { - gate: Gate::SWAP((t0, t1)), + gate: Gate::SWAP(t0, t1), target: 0, controls: Controls::None, }); @@ -401,7 +401,21 @@ impl QuantumCircuit { #[inline] pub fn u(&mut self, theta: Float, phi: Float, lambda: Float, target: usize) { self.add(QuantumTransformation { - gate: Gate::U((theta, phi, lambda)), + gate: Gate::U(theta, phi, lambda), + target, + controls: Controls::None, + }); + } + + /// Add the bit flip noise gate for a given target qubit, given a probability + // TODO(saveliy) this can potentially be optimized by checking the probability here, + // and then add the X gate, if it evaluates the true. The advantage is for the case + // where the overall number of `QuantumTransformation`s is large, and/or the + // probability of bitflip bit is trivial. + #[inline] + pub fn bit_flip_noise(&mut self, prob: Float, target: usize) { + self.add(QuantumTransformation { + gate: Gate::BitFlipNoise(prob), target, controls: Controls::None, }); @@ -778,7 +792,7 @@ mod tests { apply(Gate::RX(PI), &mut state, 5); apply(Gate::RY(PI), &mut state, 6); apply(Gate::RZ(PI), &mut state, 7); - apply(Gate::U((PI, PI, PI)), &mut state, 8); + apply(Gate::U(PI, PI, PI), &mut state, 8); c_apply(Gate::Y, &mut state, 9, 10); c_apply(Gate::RX(PI), &mut state, 11, 12); c_apply(Gate::RY(PI), &mut state, 13, 14); @@ -1164,4 +1178,44 @@ mod tests { }); println!("{}", to_table(&qc.state)); } + + #[test] + fn bit_flip_noise() { + const N: usize = 1; + let state = gen_random_state(N); + + let mut qc1 = QuantumCircuit { + state: state.clone(), + transformations: Vec::new(), + qubit_tracker: QubitTracker::new(), + quantum_registers_info: Vec::new(), + }; + + let mut qc2 = QuantumCircuit { + state, + transformations: Vec::new(), + qubit_tracker: QubitTracker::new(), + quantum_registers_info: Vec::new(), + }; + + // Sanity checks + assert_eq!(qc1.state.reals, qc2.state.reals); + assert_eq!(qc1.state.imags, qc2.state.imags); + + for target in 0..N { + qc2.bit_flip_noise(0.0, target); + } + + // BitFlipNoise with prob==0.0 should not change anything + assert_eq!(qc1.state.reals, qc2.state.reals); + assert_eq!(qc1.state.imags, qc2.state.imags); + + // BitFlipNoise with prob==1.0 should be equivalent to applying the X gate + for target in 0..N { + qc1.x(target); + qc2.bit_flip_noise(1.0, target); + } + assert_eq!(qc1.state.reals, qc2.state.reals); + assert_eq!(qc1.state.imags, qc2.state.imags); + } } diff --git a/spinoza/src/gates.rs b/spinoza/src/gates.rs index d953d6e..494b01f 100644 --- a/spinoza/src/gates.rs +++ b/spinoza/src/gates.rs @@ -1,4 +1,5 @@ //! Abstractions for quantum logic gates +use rand::Rng; use rayon::prelude::*; use std::collections::HashSet; @@ -63,24 +64,26 @@ pub enum Gate { /// Rz gate for rotation about the z-axis. See RZ(Float), /// Swap gate swaps two qubits. See - SWAP((usize, usize)), + SWAP(usize, usize), /// General single qubit rotation. See - U((Float, Float, Float)), + U(Float, Float, Float), /// A Unitary matrix. See Unitary(Unitary), + /// A gate to simulate a bit flip based on the provided probability. + BitFlipNoise(Float), } impl Gate { /// Return the inverted gate pub fn inverse(self) -> Self { match self { - Self::H | Self::X | Self::Y | Self::Z | Self::SWAP((_, _)) => self, + Self::H | Self::X | Self::Y | Self::Z | Self::SWAP(_, _) => self, Self::P(theta) => Self::P(-theta), Self::RX(theta) => Self::RX(-theta), Self::RY(theta) => Self::RY(-theta), Self::RZ(theta) => Self::RZ(-theta), - Self::U((theta, phi, lambda)) => Self::U((-theta, -lambda, -phi)), - Self::M => unimplemented!(), + Self::U(theta, phi, lambda) => Self::U(-theta, -lambda, -phi), + Self::M | Self::BitFlipNoise(_) => unimplemented!(), Self::Unitary(_) => todo!(), } } @@ -158,7 +161,7 @@ impl Gate { }, ] } - Self::U((theta, phi, lambda)) => { + Self::U(theta, phi, lambda) => { let theta = theta / 2.0; [ Amplitude { @@ -195,8 +198,11 @@ pub fn apply(gate: Gate, state: &mut State, target: usize) { Gate::RX(theta) => rx_apply(state, target, theta), Gate::RY(theta) => ry_apply(state, target, theta), Gate::RZ(theta) => rz_apply(state, target, theta), - Gate::SWAP((t0, t1)) => swap_apply(state, t0, t1), - Gate::U((theta, phi, lambda)) => u_apply(state, target, theta, phi, lambda), + Gate::SWAP(t0, t1) => swap_apply(state, t0, t1), + Gate::U(theta, phi, lambda) => u_apply(state, target, theta, phi, lambda), + Gate::BitFlipNoise(prob) => { + bit_flip_noise_apply(state, prob, target); + } _ => unimplemented!(), } } @@ -1256,6 +1262,17 @@ fn u_apply(state: &mut State, target: usize, theta: Float, phi: Float, lambda: F } } +fn bit_flip_noise_apply(state: &mut State, prob: Float, target: usize) -> bool { + let mut rng = rand::thread_rng(); + let epsilon: Float = rng.gen(); + + if epsilon <= prob { + x_apply(state, target); + return true; + } + false +} + fn swap_apply(state: &mut State, t0: usize, t1: usize) { assert!(usize::from(state.n) > t0 && usize::from(state.n) > t1); @@ -1698,7 +1715,7 @@ mod tests { let mut state = State::new(n); for i in 0..n { - apply(Gate::U((1.0, 1.0, 1.0)), &mut state, i); + apply(Gate::U(1.0, 1.0, 1.0), &mut state, i); } assert_float_closeness(state.reals[0], 0.6758712218347053, 1e-10); @@ -1734,7 +1751,7 @@ mod tests { let theta = 2.0; let phi = 3.0; - apply(Gate::U((theta, phi, lambda)), &mut state, 0); + apply(Gate::U(theta, phi, lambda), &mut state, 0); assert_float_closeness(state.reals[0], 0.5403023058681398, 1e-10); assert_float_closeness(state.imags[0], 0.0, 1e-10); @@ -1744,7 +1761,7 @@ mod tests { // Check other base vector let mut state = State::new(1); apply(Gate::X, &mut state, 0); - apply(Gate::U((theta, phi, lambda)), &mut state, 0); + apply(Gate::U(theta, phi, lambda), &mut state, 0); assert_float_closeness(state.reals[0], -0.4546487134128409, 1e-10); assert_float_closeness(state.imags[0], -0.7080734182735712, 1e-10); @@ -1914,8 +1931,8 @@ mod tests { #[test] fn u_inverse() { - let u = Gate::U((1.0, 2.0, 3.0)).to_matrix(); - let u_inv = Gate::U((1.0, 2.0, 3.0)).inverse().to_matrix(); + let u = Gate::U(1.0, 2.0, 3.0).to_matrix(); + let u_inv = Gate::U(1.0, 2.0, 3.0).inverse().to_matrix(); let identity = mat_mul_2x2(u, u_inv); assert_float_closeness(identity[0].re, 1.0, 0.001); @@ -1938,8 +1955,8 @@ mod tests { #[test] #[should_panic] fn swap_inverse() { - let _swap = Gate::SWAP((0, 1)); - let _swap_inv = Gate::SWAP((0, 1)).inverse().to_matrix(); + let _swap = Gate::SWAP(0, 1); + let _swap_inv = Gate::SWAP(0, 1).inverse().to_matrix(); } #[test] @@ -2050,4 +2067,28 @@ mod tests { i += 4; } } + + #[test] + fn bit_flip_noise() { + const N: usize = 1; + let state0 = gen_random_state(N); + let mut state1 = state0.clone(); + + apply(Gate::BitFlipNoise(0.0), &mut state1, 0); + assert_eq!( + state0.reals, state1.reals, + "BitFlipNoise with prob=0.0 should have no effect" + ); + assert_eq!( + state0.imags, state1.imags, + "BitFlipNoise with prob=0.0 should have no effect" + ); + + // BitFlipNoise with prob=1.0 is equivalent to just applying the X gate to the provided target qubit + apply(Gate::BitFlipNoise(1.0), &mut state1, 0); + assert_float_closeness(state1.reals[0], state0.reals[1], 0.00001); + assert_float_closeness(state1.reals[1], state0.reals[0], 0.00001); + assert_float_closeness(state1.imags[0], state0.imags[1], 0.00001); + assert_float_closeness(state1.imags[1], state0.imags[0], 0.00001); + } } diff --git a/spinoza/src/unitaries.rs b/spinoza/src/unitaries.rs index ddd19af..0d9d002 100644 --- a/spinoza/src/unitaries.rs +++ b/spinoza/src/unitaries.rs @@ -248,12 +248,12 @@ mod tests { fn test_u() { let n = 2; let state = State::new(n); - let u = Unitary::from_single_qubit_gate(&state, Gate::U((1.0, 2.0, 3.0)), 0); + let u = Unitary::from_single_qubit_gate(&state, Gate::U(1.0, 2.0, 3.0), 0); let s = apply_unitary(&state, &u); let mut s1 = State::new(n); - apply(Gate::U((1.0, 2.0, 3.0)), &mut s1, 0); + apply(Gate::U(1.0, 2.0, 3.0), &mut s1, 0); assert_eq!(s.n, s1.n); assert_eq!(s.reals, s1.reals); diff --git a/spynoza/src/lib.rs b/spynoza/src/lib.rs index e0e37c1..3d443c9 100644 --- a/spynoza/src/lib.rs +++ b/spynoza/src/lib.rs @@ -151,7 +151,7 @@ impl From for PyQuantumTransformation { Gate::RX(angle) => ("rx", Some((angle, 0.0, 0.0))), Gate::RY(angle) => ("ry", Some((angle, 0.0, 0.0))), Gate::RZ(angle) => ("rz", Some((angle, 0.0, 0.0))), - Gate::U((a, b, c)) => ("u", Some((a, b, c))), + Gate::U(a, b, c) => ("u", Some((a, b, c))), _ => todo!(), };