Skip to content

Commit

Permalink
Add BitFlipNoise as a Gate, and code refactor...
Browse files Browse the repository at this point in the history
- `Gate::SWAP((a, b))` and `Gate::U((x, y, z))` variants are now:
  ``Gate::Swap(a, b) and `Gate::U(x, y, z)`. Rather than passing a
  tuple of args, the fields are separate which allows for cleaner
  implementatons.

- Add the `BitFlipNoise` gate as a variant in `Gate`. The variant takes
  a single field for the probability of the bitflip taking place.
  `BitFlipNoise` is available in the functional and circuit
  implementations.
  • Loading branch information
smu160 committed Dec 26, 2023
1 parent c032afe commit 3ad424c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 23 deletions.
2 changes: 1 addition & 1 deletion spinoza/examples/u.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion spinoza/examples/unitaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
60 changes: 57 additions & 3 deletions spinoza/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
71 changes: 56 additions & 15 deletions spinoza/src/gates.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Abstractions for quantum logic gates
use rand::Rng;
use rayon::prelude::*;
use std::collections::HashSet;

Expand Down Expand Up @@ -63,24 +64,26 @@ pub enum Gate {
/// Rz gate for rotation about the z-axis. See <https://en.wikipedia.org/wiki/List_of_quantum_logic_gates#Rotation_operator_gates>
RZ(Float),
/// Swap gate swaps two qubits. See <https://en.wikipedia.org/wiki/Quantum_logic_gate#Swap_gate>
SWAP((usize, usize)),
SWAP(usize, usize),
/// General single qubit rotation. See <https://en.wikipedia.org/wiki/List_of_quantum_logic_gates#Other_named_gates>
U((Float, Float, Float)),
U(Float, Float, Float),
/// A Unitary matrix. See <https://mathworld.wolfram.com/UnitaryMatrix.html>
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!(),
}
}
Expand Down Expand Up @@ -158,7 +161,7 @@ impl Gate {
},
]
}
Self::U((theta, phi, lambda)) => {
Self::U(theta, phi, lambda) => {
let theta = theta / 2.0;
[
Amplitude {
Expand Down Expand Up @@ -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!(),
}
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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]
Expand Down Expand Up @@ -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);
}
}
4 changes: 2 additions & 2 deletions spinoza/src/unitaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion spynoza/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl From<QuantumTransformationRS> 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!(),
};

Expand Down

0 comments on commit 3ad424c

Please sign in to comment.