use crate::math::{c64, int_to_state, real_arr_to_complex};
use ndarray::linalg;
use ndarray::{array, Array2};
use std::{f64::consts, vec};
pub trait QuantumOperation {
fn matrix(&self) -> Array2<c64>;
fn targets(&self) -> Vec<usize>;
fn arity(&self) -> usize;
}
#[derive(Clone, Debug)]
pub struct Operation {
matrix: Array2<c64>,
targets: Vec<usize>,
}
impl Operation {
pub fn new(matrix: Array2<c64>, targets: Vec<usize>) -> Option<Operation> {
let shape = matrix.shape();
let len = targets.len();
if shape[0] != 2_usize.pow(len as u32) || shape[1] != 2_usize.pow(len as u32) {
return None;
}
Some(Operation { matrix, targets })
}
}
impl QuantumOperation for Operation {
fn matrix(&self) -> Array2<c64> {
self.matrix.clone()
}
fn targets(&self) -> Vec<usize> {
self.targets.to_vec()
}
fn arity(&self) -> usize {
self.targets().len()
}
}
pub fn identity(target: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(array![[1.0, 0.0], [0.0, 1.0]]),
targets: vec![target],
}
}
pub fn hadamard(target: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(consts::FRAC_1_SQRT_2 * array![[1.0, 1.0], [1.0, -1.0]]),
targets: vec![target],
}
}
pub fn hadamard_transform(targets: Vec<usize>) -> Operation {
let mut matrix = hadamard(targets[0]).matrix();
let len = targets.len();
for t in targets.iter().take(len).skip(1) {
matrix = linalg::kron(&hadamard(*t).matrix(), &matrix);
}
Operation { matrix, targets }
}
pub fn cnot(control: usize, target: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(array![
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 0.0]
]),
targets: vec![target, control],
}
}
pub fn to_quantum_gate(f: &dyn Fn(usize) -> usize, targets: Vec<usize>) -> Operation {
let t_len = targets.len();
let len: usize = 1 << t_len;
let mut matrix: Array2<c64> = Array2::zeros((len, len));
for c in 0..len {
let val = f(c);
let res_state = int_to_state(val, len);
for r in 0..len {
matrix[(r, c)] = res_state[(r, 0)];
}
}
Operation { matrix, targets }
}
pub fn to_controlled(op: Operation, control: usize) -> Operation {
let old_sz = 1 << op.arity();
let mut matrix = Array2::zeros((2 * old_sz, 2 * old_sz));
for i in 0..old_sz {
matrix[(i, i)] = c64::new(1.0, 0.0);
}
for i in 0..old_sz {
for j in 0..old_sz {
matrix[(i + old_sz, j + old_sz)] = op.matrix[(i, j)];
}
}
let mut targets = op.targets();
targets.push(control);
Operation { matrix, targets }
}
pub fn swap(target_1: usize, target_2: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(array![
[1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0]
]),
targets: vec![target_1, target_2],
}
}
pub fn phase(target: usize) -> Operation {
Operation {
matrix: array![
[c64::new(1.0, 0.0), c64::new(0.0, 0.0)],
[c64::new(0.0, 0.0), c64::new(0.0, 1.0)]
],
targets: vec![target],
}
}
pub fn not(target: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(array![[0.0, 1.0], [1.0, 0.0]]),
targets: vec![target],
}
}
pub fn pauli_y(target: usize) -> Operation {
Operation {
matrix: array![
[c64::new(0.0, 0.0), c64::new(0.0, -1.0)],
[c64::new(0.0, 1.0), c64::new(0.0, 0.0)]
],
targets: vec![target],
}
}
pub fn pauli_z(target: usize) -> Operation {
Operation {
matrix: real_arr_to_complex(array![[1.0, 0.0], [0.0, -1.0]]),
targets: vec![target],
}
}
pub fn cnx(controls: &[usize], target: usize) -> Operation {
let mut targets = vec![target];
targets.append(&mut controls.to_owned());
let n: usize = 2_usize.pow(targets.len() as u32);
let mut matrix: Array2<f64> = Array2::<f64>::zeros((n, n));
for i in 0..n - 2 {
matrix.row_mut(i)[i] = 1.0;
}
matrix.row_mut(n - 1)[n - 2] = 1.0;
matrix.row_mut(n - 2)[n - 1] = 1.0;
Operation {
matrix: real_arr_to_complex(matrix),
targets,
}
}
pub fn cnz(controls: &[usize], target: usize) -> Operation {
let mut targets = vec![target];
targets.append(&mut controls.to_owned());
let n: usize = 2_usize.pow(targets.len() as u32);
let mut matrix: Array2<f64> = Array2::<f64>::zeros((n, n));
for i in 0..n - 1 {
matrix.row_mut(i)[i] = 1.0;
}
matrix.row_mut(n - 1)[n - 1] = -1.0;
Operation {
matrix: real_arr_to_complex(matrix),
targets,
}
}
#[cfg(test)]
mod tests {
use super::{
cnot, cnx, hadamard, identity, not, pauli_y, pauli_z, phase, swap, QuantumOperation,
};
use crate::math::c64;
use ndarray::Array2;
fn all_ops() -> Vec<Box<dyn QuantumOperation>> {
vec![
Box::new(identity(0)),
Box::new(hadamard(0)),
Box::new(cnot(0, 1)),
Box::new(swap(0, 1)),
Box::new(phase(0)),
Box::new(not(0)),
Box::new(pauli_y(0)),
Box::new(pauli_z(0)),
Box::new(cnx(&[0], 1)),
Box::new(cnx(&[0, 1], 2)),
Box::new(cnx(&[0, 1, 2], 3)),
Box::new(cnx(&[0, 1, 2, 3], 4)),
Box::new(cnx(&[0, 1, 2, 3, 4], 5)),
]
}
#[test]
fn sz_matches() {
for op in all_ops() {
assert_eq!(op.matrix().dim().0, op.matrix().dim().1);
assert_eq!(op.matrix().dim().0, 1 << op.arity())
}
}
#[test]
fn unitary() {
for op in all_ops() {
let conj_transpose = op.matrix().t().map(|e| e.conj());
assert!(matrix_is_equal(
op.matrix().dot(&conj_transpose),
Array2::eye(op.matrix().dim().0),
1e-8
))
}
}
#[test]
fn toffoli2_equals_cnot() {
let toffoli_generated_cnot = cnx(&[0], 1);
assert!(matrix_is_equal(
toffoli_generated_cnot.matrix(),
cnot(0, 1).matrix(),
1e-8
));
}
fn matrix_is_equal(a: Array2<c64>, b: Array2<c64>, tolerance: f64) -> bool {
(a - b).iter().all(|e| e.norm() < tolerance)
}
}