Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QFT operation. #136

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qoqo/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ use pyo3::prelude::*;
/// PMInteraction
/// ComplexPMInteraction
/// MultiQubitMS
/// QFT
#[pymodule]
pub fn operations(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<SingleQubitGateWrapper>()?;
Expand Down Expand Up @@ -180,5 +181,6 @@ pub fn operations(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PhaseShiftState1Wrapper>()?;
m.add_class::<MultiQubitMSWrapper>()?;
m.add_class::<MultiQubitZZWrapper>()?;
m.add_class::<QFTWrapper>()?;
Ok(())
}
9 changes: 9 additions & 0 deletions qoqo/src/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@ pub struct MultiQubitZZ {
/// The angle of the multi qubit Molmer-Sorensen gate.
theta: CalculatorFloat,
}

#[allow(clippy::upper_case_acronyms)]
/// The quantum Fourier transformation.
pub struct QFT {
/// The qubits involved in the QFT.
qubits: Vec<usize>,
/// Include qubit swaps at the end.
swaps: bool,
}
87 changes: 86 additions & 1 deletion roqoqo/src/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// express or implied. See the License for the specific language governing permissions and
// limitations under the License.

use std::panic;
use std::{f64::consts::PI, panic};

use crate::operations;
use crate::prelude::*;
Expand Down Expand Up @@ -158,3 +158,88 @@ impl OperateMultiQubitGate for MultiQubitZZ {
circuit
}
}

/// The quantum Fourier transformation.
#[allow(clippy::upper_case_acronyms)]
#[derive(
Debug,
Clone,
PartialEq,
roqoqo_derive::InvolveQubits,
roqoqo_derive::Operate,
roqoqo_derive::Substitute,
roqoqo_derive::OperateMultiQubit,
)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[allow(clippy::upper_case_acronyms)]
pub struct QFT {
/// The qubits involved in the QFT.
qubits: Vec<usize>,
/// Include qubit swaps at the end.
swaps: bool,
/// Do inverse QFT.
inverse: bool,
}

const TAGS_QFT: &[&str; 4] = &[
"Operation",
"GateOperation",
"MultiQubitGateOperation",
"QFT",
];

impl OperateGate for QFT {
fn unitary_matrix(&self) -> Result<Array2<Complex64>, RoqoqoError> {
let dim = self.qubits.len();
if !self.swaps && dim > 1 {
return Err(RoqoqoError::GenericError {
msg: "Unitary matrix output is only supported for QFT with swapping.".into(),
});
}
let n = 2_usize.pow(dim as u32);
let mut array = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut theta = 2. * PI * (i as f64 * j as f64) / (n as f64);
if self.inverse {
theta *= -1.;
}
array[[i, j]] = Complex64::from_polar(1. / (n as f64).sqrt(), theta);
}
}
Ok(array)
}
}

impl OperateMultiQubitGate for QFT {
fn circuit(&self) -> Circuit {
let dim = self.qubits.len();
let mut circuit = Circuit::new();

if self.swaps && self.inverse {
for i in 0..dim / 2 {
circuit += operations::SWAP::new(self.qubits[i], self.qubits[dim - i - 1]);
}
}
for i in 0..dim {
circuit += operations::Hadamard::new(self.qubits[i]);
for j in i + 1..dim {
let mut theta = PI / 2.0_f64.powi((j - i) as i32);
if self.inverse {
theta *= -1.;
}
circuit += operations::ControlledPhaseShift::new(
self.qubits[j],
self.qubits[i],
theta.into(),
);
}
}
if self.swaps && !self.inverse {
for i in 0..dim / 2 {
circuit += operations::SWAP::new(self.qubits[i], self.qubits[dim - i - 1]);
}
}
circuit
}
}
189 changes: 188 additions & 1 deletion roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

//! Integration test for public API of multi qubit gate operations

use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
f64::consts::{FRAC_PI_2, FRAC_PI_4, PI},
};

use ndarray::array;
use num_complex::Complex64;
Expand Down Expand Up @@ -565,3 +568,187 @@ fn test_rotatex_powercf_multi_qubit_zz(theta: CalculatorFloat, power: Calculator
assert_eq!(power_gate, test_gate);
assert_eq!(power_gate.theta(), test_gate.theta());
}

#[test_case(vec![0], false, false; "one_qubit")]
#[test_case(vec![0], true, false; "one_qubit_swap")]
#[test_case(vec![0], false, true; "one_qubit_inv")]
#[test_case(vec![0], true, true; "one_qubit_swap_inv")]
#[test_case(vec![0,1], false, false; "two_qubit")]
#[test_case(vec![0,1], true, false; "two_qubit_swap")]
#[test_case(vec![0,1], false, true; "two_qubit_inv")]
#[test_case(vec![0,1], true, true; "two_qubit_swap_inv")]
#[test_case(vec![0,1,2], false, false; "three_qubit")]
#[test_case(vec![0,1,2], true, false; "three_qubit_swap")]
#[test_case(vec![0,1,2], false, true; "three_qubit_inv")]
#[test_case(vec![0,1,2], true, true; "three_qubit_swap_inv")]
fn test_circuit_qft(qubits: Vec<usize>, swap: bool, inverse: bool) {
let gate = QFT::new(qubits.clone(), swap, inverse);
let c = gate.circuit();
let sign = if inverse { -1. } else { 1. };
if qubits.len() == 1 {
let mut comparison_circuit = Circuit::new();
comparison_circuit += Hadamard::new(0);
assert!(c == comparison_circuit);
} else if qubits.len() == 2 {
let mut comparison_circuit = Circuit::new();
if swap && inverse {
comparison_circuit += SWAP::new(0, 1);
}
comparison_circuit += Hadamard::new(0);
comparison_circuit += ControlledPhaseShift::new(1, 0, (sign * FRAC_PI_2).into());
comparison_circuit += Hadamard::new(1);
if swap && !inverse {
comparison_circuit += SWAP::new(0, 1);
}
assert!(c == comparison_circuit);
} else if qubits.len() == 3 {
let mut comparison_circuit = Circuit::new();
if swap && inverse {
comparison_circuit += SWAP::new(0, 2);
}
comparison_circuit += Hadamard::new(0);
comparison_circuit += ControlledPhaseShift::new(1, 0, (sign * FRAC_PI_2).into());
comparison_circuit += ControlledPhaseShift::new(2, 0, (sign * FRAC_PI_4).into());
comparison_circuit += Hadamard::new(1);
comparison_circuit += ControlledPhaseShift::new(2, 1, (sign * FRAC_PI_2).into());
comparison_circuit += Hadamard::new(2);
if swap && !inverse {
comparison_circuit += SWAP::new(0, 2);
}
assert!(c == comparison_circuit);
}
}

#[test_case(vec![0], false; "one_qubit")]
#[test_case(vec![0], true; "one_qubit_inv")]
fn test_matrix_output_qft(qubits: Vec<usize>, inverse: bool) {
let dim = qubits.len();
let r = 1. / 2_f64.powi(dim as i32).sqrt();
let gate = QFT::new(qubits.clone(), true, inverse);
let mut test_array = match qubits.len() {
1 => array![
[Complex64::from_polar(r, 0.), Complex64::from_polar(r, 0.)],
[Complex64::from_polar(r, 0.), Complex64::from_polar(r, PI)],
],
2 => array![
[
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, 0.)
],
[
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, FRAC_PI_2),
Complex64::from_polar(r, PI),
Complex64::from_polar(r, 3. * FRAC_PI_2)
],
[
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, PI),
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, PI)
],
[
Complex64::from_polar(r, 0.),
Complex64::from_polar(r, 3. * FRAC_PI_2),
Complex64::from_polar(r, PI),
Complex64::from_polar(r, FRAC_PI_2)
],
],
_ => unreachable!(),
};
if inverse {
test_array.iter_mut().for_each(|x| *x = x.conj());
}
let unit = gate.unitary_matrix().unwrap();
println!("{:.2}", test_array);
println!("{:.2}", unit);
let should_be_zero = unit - test_array;
assert!(should_be_zero.iter().all(|x| x.norm() < f64::EPSILON));
}

#[test]
fn test_clone_partial_eq_qft() {
let qubits = vec![0, 1, 2];

let gate = QFT::new(qubits.clone(), true, true);
assert_eq!(gate.hqslang(), "QFT");
assert_eq!(
gate.tags(),
&[
"Operation",
"GateOperation",
"MultiQubitGateOperation",
"QFT",
]
);
assert!(!gate.is_parametrized());
let gate2 = gate.clone();
assert_eq!(gate2, gate);
}

#[test]
fn test_operate_qft() {
let qubits = vec![0, 1, 2];
let gate = QFT::new(qubits.clone(), false, false);
assert_eq!(gate.hqslang(), "QFT");
assert_eq!(
gate.tags(),
&[
"Operation",
"GateOperation",
"MultiQubitGateOperation",
"QFT",
]
);
assert_eq!(gate.qubits(), &vec![0, 1, 2]);
assert!(!gate.is_parametrized());
}

#[test]
fn test_substitute_qft() {
let qubits = vec![0, 1, 2];
let gate = QFT::new(qubits.clone(), false, false);
let mut mapping: HashMap<usize, usize> = std::collections::HashMap::new();
let _ = mapping.insert(0, 1);
let _ = mapping.insert(1, 2);
let _ = mapping.insert(2, 0);
let remapped = gate.remap_qubits(&mapping).unwrap();
let qubits = remapped.qubits();
assert_eq!(qubits, &vec![1, 2, 0]);
}

#[test]
fn test_substitute_error_qft() {
let qubits = vec![0, 1, 2];
let gate = QFT::new(qubits.clone(), false, false);
let mut mapping: HashMap<usize, usize> = std::collections::HashMap::new();
let _ = mapping.insert(1, 2);
let _ = mapping.insert(2, 0);
let remapped = gate.remap_qubits(&mapping);
assert!(remapped.is_err());
}

#[test]
fn test_format_error_qft() {
let qubits = vec![0, 1, 2];
let gate = QFT::new(qubits.clone(), false, false);
let string = format!("{:?}", gate);
assert!(string.contains("QFT"));
}

#[test_case(false, false; "qft")]
#[test_case(true, false; "qft_swap")]
#[test_case(false, true; "qft_inv")]
#[test_case(false, false; "qft_swap_inv")]
fn test_involved_qubits_qft(swaps: bool, inverse: bool) {
let qubits = vec![0, 1, 2];
let gate = QFT::new(qubits.clone(), swaps, inverse);
let involved_qubits = gate.involved_qubits();
let mut comp_set: HashSet<usize> = HashSet::new();
let _ = comp_set.insert(0);
let _ = comp_set.insert(1);
let _ = comp_set.insert(2);
assert_eq!(involved_qubits, InvolvedQubits::Set(comp_set));
}