use crate::circuit::*;
use crate::fscalar::*;
use crate::graph::*;
use crate::phase::Phase;
use ndarray::parallel::prelude::*;
use ndarray::prelude::*;
use ndarray::*;
use num::{Complex, Rational64};
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::iter::FromIterator;
pub type Tensor<A> = Array<A, IxDyn>;
pub type TensorF = Tensor<FScalar>;
pub type Tensor64 = Tensor<Complex<f64>>;
impl Sqrt2 for Complex<f64> {
fn sqrt2_pow(p: i32) -> Complex<f64> {
let rt2 = Complex::new(f64::sqrt(2.0), 0.0);
if p == 1 {
rt2
} else {
rt2.powi(p)
}
}
}
impl FromPhase for Complex<f64> {
fn from_phase(p: impl Into<Phase>) -> Complex<f64> {
let p = p.into().to_rational();
let exp = (*p.numer() as f64) / (*p.denom() as f64);
Complex::new(-1.0, 0.0).powf(exp)
}
fn minus_one() -> Complex<f64> {
Self::from_phase(Phase::one())
}
}
pub trait TensorElem:
Copy
+ Send
+ Sync
+ PartialEq
+ Zero
+ One
+ Sqrt2
+ FromPhase
+ From<FScalar>
+ ScalarOperand
+ std::ops::MulAssign
+ std::fmt::Debug
{
}
impl<T> TensorElem for T where
T: Copy
+ Send
+ Sync
+ PartialEq
+ Zero
+ One
+ Sqrt2
+ FromPhase
+ From<FScalar>
+ ScalarOperand
+ std::ops::MulAssign
+ std::fmt::Debug
{
}
pub trait ToTensor {
fn to_tensor<A: TensorElem>(&self) -> Tensor<A>;
fn to_tensorf(&self) -> TensorF {
self.to_tensor()
}
fn to_tensor64(&self) -> Tensor64 {
self.to_tensor()
}
}
pub trait QubitOps<A: TensorElem> {
fn ident(q: usize) -> Self;
fn delta(q: usize) -> Self;
fn cphase(p: impl Into<Phase>, q: usize) -> Self;
fn hadamard() -> Self;
fn delta_at(&mut self, qs: &[usize]);
fn cphase_at(&mut self, p: impl Into<Phase>, qs: &[usize]);
fn hadamard_at(&mut self, i: usize);
fn slice_qubit_mut(&mut self, q: usize) -> (ArrayViewMut<A, IxDyn>, ArrayViewMut<A, IxDyn>);
fn plug_n_qubits(self, n: usize, other: &Tensor<A>) -> Tensor<A>;
}
pub trait CompareTensors {
fn scalar_eq(t0: &Self, t1: &Self) -> bool;
fn compare(x0: &impl ToTensor, x1: &impl ToTensor) -> bool;
fn scalar_compare(x0: &impl ToTensor, x1: &impl ToTensor) -> bool;
}
impl<A: TensorElem> CompareTensors for Tensor<A> {
fn scalar_eq(t0: &Tensor<A>, t1: &Tensor<A>) -> bool {
if t0.dim() != t1.dim() {
return false;
}
let a0 = t0.iter().find(|s| !s.is_zero());
let a1 = t1.iter().find(|s| !s.is_zero());
match (a0, a1) {
(Some(b0), Some(b1)) => {
if b0 == b1 {
t0 == t1
}
else {
t0 * *b1 == t1 * *b0
}
}
(None, None) => true,
_ => false,
}
}
fn scalar_compare(x0: &impl ToTensor, x1: &impl ToTensor) -> bool {
let t0 = x0.to_tensor::<A>();
let t1 = x1.to_tensor::<A>();
Tensor::scalar_eq(&t0, &t1)
}
fn compare(x0: &impl ToTensor, x1: &impl ToTensor) -> bool {
x0.to_tensor::<A>() == x1.to_tensor::<A>()
}
}
impl<A: TensorElem> QubitOps<A> for Tensor<A> {
fn slice_qubit_mut(&mut self, q: usize) -> (ArrayViewMut<A, IxDyn>, ArrayViewMut<A, IxDyn>) {
let slice0: SliceInfo<_, IxDyn, IxDyn> =
SliceInfo::try_from(Vec::from_iter((0..self.ndim()).map(|i| {
if i == q {
SliceInfoElem::from(0)
} else {
SliceInfoElem::from(..)
}
})))
.unwrap();
let slice1: SliceInfo<_, IxDyn, IxDyn> =
SliceInfo::try_from(Vec::from_iter((0..self.ndim()).map(|i| {
if i == q {
SliceInfoElem::from(1)
} else {
SliceInfoElem::from(..)
}
})))
.unwrap();
self.multi_slice_mut((slice0.as_ref(), slice1.as_ref()))
}
fn ident(q: usize) -> Tensor<A> {
Tensor::from_shape_fn(vec![2; q * 2], |ix| {
if (0..q).all(|i| ix[i] == ix[q + i]) {
A::one()
} else {
A::zero()
}
})
}
fn delta(q: usize) -> Tensor<A> {
Tensor::from_shape_fn(vec![2; q], |ix| {
if (0..q).all(|i| ix[i] == 0) || (0..q).all(|i| ix[i] == 1) {
A::one()
} else {
A::zero()
}
})
}
fn cphase(p: impl Into<Phase>, q: usize) -> Tensor<A> {
let mut t = Tensor::ident(q);
let qs: Vec<_> = (0..q).collect();
t.cphase_at(p, &qs);
t
}
fn hadamard() -> Tensor<A> {
let n = A::one_over_sqrt2();
let minus = A::from_phase(1);
array![[n, n], [n, minus * n]].into_dyn()
}
fn delta_at(&mut self, qs: &[usize]) {
let mut shape: Vec<usize> = vec![1; self.ndim()];
for &q in qs {
shape[q] = 2;
}
let del: Tensor<A> = Tensor::delta(qs.len())
.into_shape_with_order(shape)
.expect("Bad indices for delta_at");
*self *= &del;
}
fn cphase_at(&mut self, p: impl Into<Phase>, qs: &[usize]) {
let p = p.into();
let mut shape: Vec<usize> = vec![1; self.ndim()];
for &q in qs {
shape[q] = 2;
}
let cp: Tensor<A> = Tensor::from_shape_fn(vec![2; qs.len()], |ix| {
if (0..qs.len()).all(|i| ix[i] == 1) {
A::from_phase(p)
} else {
A::one()
}
})
.into_shape_with_order(shape)
.expect("Bad indices for cphase_at");
*self *= &cp;
}
fn hadamard_at(&mut self, q: usize) {
let n = A::one_over_sqrt2();
let minus = A::from_phase(1);
let (mut ma, mut mb) = self.slice_qubit_mut(q);
par_azip!((a in &mut ma, b in &mut mb) {
let a1 = *a;
*a = n * (*a + *b);
*b = n * (a1 + minus * *b);
});
}
fn plug_n_qubits(self, n: usize, other: &Tensor<A>) -> Tensor<A> {
let d1 = self.shape().len();
let d2 = other.shape().len();
let shape1: Vec<usize> = (0..(d1 + d2 - n))
.map(|i| if i < d1 { 2 } else { 1 })
.collect();
let shape2: Vec<usize> = (0..(d1 + d2 - n))
.map(|i| if i < d1 - n { 1 } else { 2 })
.collect();
let t1 = self
.into_shared()
.into_shape_with_order(shape1)
.expect("Invalid tensor reshape");
let t1p = t1.broadcast(vec![2; d1 + n]).unwrap();
let t2 = other
.clone()
.into_shared()
.into_shape_with_order(shape2)
.expect("Invalid tensor reshape");
let mut t3 = &t1p * &t2;
for _ in 0..n {
t3 = t3.sum_axis(Axis(d1 - n));
}
t3
}
}
impl<G: GraphLike + Clone> ToTensor for G {
fn to_tensor<A: TensorElem>(&self) -> Tensor<A> {
let mut g = self.clone();
g.x_to_z();
for v in g.vertices() {
let t = g.vertex_type(v);
if t != VType::B && t != VType::Z {
panic!("Vertex type currently unsupported: {t:?}");
}
}
let mut a = Tensor::from_shape_vec(vec![], vec![A::one()]).unwrap();
let inp = g.inputs().iter().copied();
let mid = g.vertices().filter(|&v| g.vertex_type(v) != VType::B);
let outp = g.outputs().iter().copied();
let mut vs: Vec<V> = inp.chain(mid.chain(outp)).collect();
if vs.len() < g.num_vertices() {
panic!("All boundary vertices must be an input or an output");
}
vs.reverse();
let mut indexv: VecDeque<V> = VecDeque::new();
let mut seenv: FxHashMap<V, usize> = FxHashMap::default();
for v in vs {
let p = g.phase(v);
if p.is_zero() {
a = stack![Axis(0), a, a];
} else {
let f = A::from_phase(p);
a = stack![Axis(0), a, &a * f];
}
indexv.push_front(v);
let mut deg_v = 0;
for (w, et) in g.incident_edges(v) {
if let Some(deg_w) = seenv.get_mut(&w) {
deg_v += 1;
*deg_w += 1;
let wi = indexv
.iter()
.position(|x| *x == w)
.expect("w should be in indexv");
if et == EType::N {
a.delta_at(&[0, wi]);
} else {
a.cphase_at(1, &[0, wi]);
a *= A::one_over_sqrt2();
}
if g.vertex_type(w) != VType::B && g.degree(w) == *deg_w {
a = a.sum_axis(Axis(wi));
indexv.remove(wi);
}
}
}
if g.vertex_type(v) != VType::B && g.degree(v) == deg_v {
a = a.sum_axis(Axis(0));
indexv.remove(0);
}
seenv.insert(v, deg_v);
}
let s = A::from(*g.scalar()); a * s
}
}
impl ToTensor for Circuit {
fn to_tensor<A: TensorElem>(&self) -> Tensor<A> {
use crate::gate::GType::*;
let q = self.num_qubits();
let mut a = Tensor::ident(q);
for g in self.gates.iter().rev() {
match g.t {
ZPhase => a.cphase_at(g.phase, &g.qs),
Z | CZ | CCZ => a.cphase_at(1, &g.qs),
S => a.cphase_at(Rational64::new(1, 2), &g.qs),
T => a.cphase_at(Rational64::new(1, 4), &g.qs),
Sdg => a.cphase_at(Rational64::new(-1, 2), &g.qs),
Tdg => a.cphase_at(Rational64::new(-1, 4), &g.qs),
HAD => a.hadamard_at(g.qs[0]),
NOT => {
a.hadamard_at(g.qs[0]);
a.cphase_at(Rational64::one(), &g.qs);
a.hadamard_at(g.qs[0]);
}
XPhase => {
a.hadamard_at(g.qs[0]);
a.cphase_at(g.phase, &g.qs);
a.hadamard_at(g.qs[0]);
}
CNOT => {
a.hadamard_at(g.qs[1]);
a.cphase_at(Rational64::one(), &g.qs);
a.hadamard_at(g.qs[1]);
}
TOFF => {
a.hadamard_at(g.qs[2]);
a.cphase_at(Rational64::one(), &g.qs);
a.hadamard_at(g.qs[2]);
}
SWAP => a.swap_axes(g.qs[0], g.qs[1]),
XCX => {
a.hadamard_at(g.qs[0]);
a.hadamard_at(g.qs[1]);
a.cphase_at(g.phase, &g.qs);
a.hadamard_at(g.qs[0]);
a.hadamard_at(g.qs[1]);
}
ParityPhase => {
panic!("Unsupported gate: ParityPhase")
}
InitAncilla => {
panic!("Unsupported gate: InitAncilla")
}
PostSelect => {
panic!("Unsupported gate: PostSelect")
}
Measure => {
panic!("Unsupported gate: Measure")
}
MeasureReset => {
panic!("Unsupported gate: MeasureReset")
}
UnknownGate => {} }
}
a
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vec_graph::Graph;
#[test]
fn tensor_1() {
let mut g = Graph::new();
g.add_vertex(VType::Z);
g.add_vertex(VType::Z);
g.add_edge(0, 1);
let t: Tensor<FScalar> = g.to_tensor();
println!("{t}");
}
#[test]
fn tensor_id() {
let mut g = Graph::new();
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_edge(0, 1);
g.set_inputs(vec![0]);
g.set_outputs(vec![1]);
let t: Tensor<FScalar> = g.to_tensor();
assert_eq!(t, Tensor::ident(1));
let mut g = Graph::new();
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_vertex(VType::Z);
g.add_edge(0, 2);
g.add_edge(2, 1);
g.set_inputs(vec![0]);
g.set_outputs(vec![1]);
let t: Tensor<FScalar> = g.to_tensor();
assert_eq!(t, Tensor::ident(1));
}
#[test]
fn tensor_delta() {
let mut g = Graph::new();
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_vertex(VType::Z);
g.add_vertex(VType::Z);
g.add_edge(0, 4);
g.add_edge(1, 5);
g.add_edge_with_type(4, 5, EType::N);
g.add_edge(2, 4);
g.add_edge(3, 5);
g.set_inputs(vec![0, 1]);
g.set_outputs(vec![2, 3]);
let t = g.to_tensorf();
println!("{t}");
assert_eq!(t, Tensor::delta(4));
}
#[test]
fn tensor_cz() {
let mut g = Graph::new();
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_vertex(VType::Z);
g.add_vertex(VType::Z);
g.add_vertex(VType::B);
g.add_vertex(VType::B);
g.add_edge(0, 2);
g.add_edge(1, 3);
g.add_edge_with_type(2, 3, EType::H);
g.add_edge(2, 4);
g.add_edge(3, 5);
g.set_inputs(vec![0, 1]);
g.set_outputs(vec![4, 5]);
g.scalar_mut().mul_sqrt2_pow(1);
let t = g.to_tensorf();
println!("{t}");
assert_eq!(t, Tensor::cphase(Rational64::one(), 2));
}
#[test]
fn had_at() {
let mut arr: Tensor<FScalar> = Tensor::ident(1);
arr.hadamard_at(0);
assert_eq!(arr, Tensor::hadamard());
let mut arr: Tensor<FScalar> = Tensor::ident(2);
arr.hadamard_at(0);
arr.hadamard_at(1);
arr.hadamard_at(0);
arr.hadamard_at(1);
assert_eq!(arr, Tensor::ident(2));
}
#[test]
fn circuit_eqs() {
let c1 = Circuit::from_qasm(
r#"
qreg q[2];
cx q[0], q[1];
cx q[1], q[0];
cx q[0], q[1];
"#,
)
.unwrap();
let c2 = Circuit::from_qasm(
r#"
qreg q[2];
swap q[0], q[1];
"#,
)
.unwrap();
println!("{}", c1.to_tensorf());
println!("{}", c2.to_tensorf());
assert_eq!(c1.to_tensorf(), c2.to_tensorf());
}
#[test]
fn tensor_plug() {
let c1 = Circuit::from_qasm(
r#"
qreg q[2];
cz q[0], q[1];
"#,
)
.unwrap();
let c2 = Circuit::from_qasm(
r#"
qreg q[2];
cx q[0], q[1];
"#,
)
.unwrap();
let c3 = &c1 + &c2;
let t1 = c1.to_tensorf();
let t2 = c2.to_tensorf();
let t3 = t1.plug_n_qubits(2, &t2);
println!("{t3}");
println!("{}", c3.to_tensorf());
assert_eq!(t3, c3.to_tensorf());
}
}