use num_complex::Complex;
use crate::error::{QuantumError, QuantumResult};
type Complex32 = Complex<f32>;
pub type Index = u32;
#[derive(Debug, Clone)]
pub struct Tensor {
pub legs: Vec<Index>,
pub data: Vec<Complex32>,
}
impl Tensor {
pub fn new(legs: Vec<Index>, data: Vec<Complex32>) -> QuantumResult<Self> {
let expected = 1usize << legs.len();
if data.len() != expected {
return Err(QuantumError::DimensionMismatch {
expected,
got: data.len(),
});
}
Ok(Self { legs, data })
}
#[must_use]
pub fn scalar(value: Complex32) -> Self {
Self {
legs: Vec::new(),
data: vec![value],
}
}
#[must_use]
pub fn ket_zero(wire: Index) -> Self {
Self {
legs: vec![wire],
data: vec![Complex32::new(1.0, 0.0), Complex32::new(0.0, 0.0)],
}
}
#[must_use]
pub fn bra_basis(wire: Index, bit: bool) -> Self {
let data = if bit {
vec![Complex32::new(0.0, 0.0), Complex32::new(1.0, 0.0)]
} else {
vec![Complex32::new(1.0, 0.0), Complex32::new(0.0, 0.0)]
};
Self {
legs: vec![wire],
data,
}
}
#[must_use]
pub fn gate_1q(out_wire: Index, in_wire: Index, m: &[[Complex32; 2]; 2]) -> Self {
let data = vec![m[0][0], m[0][1], m[1][0], m[1][1]];
Self {
legs: vec![out_wire, in_wire],
data,
}
}
#[must_use]
pub fn gate_2q(
out0: Index,
out1: Index,
in0: Index,
in1: Index,
m: &[[Complex32; 4]; 4],
) -> Self {
let mut data = vec![Complex32::new(0.0, 0.0); 16];
for (o, row) in m.iter().enumerate() {
for (i, &entry) in row.iter().enumerate() {
data[o * 4 + i] = entry;
}
}
Self {
legs: vec![out0, out1, in0, in1],
data,
}
}
#[must_use]
pub fn rank(&self) -> usize {
self.legs.len()
}
pub fn as_scalar(&self) -> QuantumResult<Complex32> {
if self.legs.is_empty() && self.data.len() == 1 {
Ok(self.data[0])
} else {
Err(QuantumError::Internal {
msg: format!("tensor is rank {}, not a scalar", self.rank()),
})
}
}
pub fn contract(&self, other: &Self) -> QuantumResult<Self> {
let mut shared: Vec<Index> = Vec::new();
for &l in &self.legs {
if other.legs.contains(&l) {
shared.push(l);
}
}
let a_free: Vec<Index> = self
.legs
.iter()
.copied()
.filter(|l| !shared.contains(l))
.collect();
let b_free: Vec<Index> = other
.legs
.iter()
.copied()
.filter(|l| !shared.contains(l))
.collect();
let s = shared.len();
let af = a_free.len();
let bf = b_free.len();
let a_pos = leg_positions(&self.legs);
let b_pos = leg_positions(&other.legs);
let mut out_data = vec![Complex32::new(0.0, 0.0); 1usize << (af + bf)];
let n_af = 1usize << af;
let n_bf = 1usize << bf;
let n_s = 1usize << s;
for fa in 0..n_af {
for fb in 0..n_bf {
let mut acc = Complex32::new(0.0, 0.0);
for sv in 0..n_s {
let mut a_idx = 0usize;
for (d, leg) in a_free.iter().enumerate() {
let bit = (fa >> (af - 1 - d)) & 1;
a_idx |= bit << (self.legs.len() - 1 - a_pos[leg]);
}
for (d, leg) in shared.iter().enumerate() {
let bit = (sv >> (s - 1 - d)) & 1;
a_idx |= bit << (self.legs.len() - 1 - a_pos[leg]);
}
let mut b_idx = 0usize;
for (d, leg) in b_free.iter().enumerate() {
let bit = (fb >> (bf - 1 - d)) & 1;
b_idx |= bit << (other.legs.len() - 1 - b_pos[leg]);
}
for (d, leg) in shared.iter().enumerate() {
let bit = (sv >> (s - 1 - d)) & 1;
b_idx |= bit << (other.legs.len() - 1 - b_pos[leg]);
}
acc += self.data[a_idx] * other.data[b_idx];
}
let out_idx = (fa << bf) | fb;
out_data[out_idx] = acc;
}
}
let mut out_legs = a_free;
out_legs.extend(b_free);
Ok(Self {
legs: out_legs,
data: out_data,
})
}
}
fn leg_positions(legs: &[Index]) -> std::collections::HashMap<Index, usize> {
legs.iter().enumerate().map(|(i, &l)| (l, i)).collect()
}
#[derive(Debug, Clone, Default)]
pub struct TensorNetwork {
pub tensors: Vec<Tensor>,
}
impl TensorNetwork {
#[must_use]
pub fn new() -> Self {
Self {
tensors: Vec::new(),
}
}
pub fn push(&mut self, t: Tensor) {
self.tensors.push(t);
}
pub fn contract_all(&self) -> QuantumResult<Tensor> {
if self.tensors.is_empty() {
return Err(QuantumError::EmptyInput);
}
let mut work: Vec<Tensor> = self.tensors.clone();
while work.len() > 1 {
let mut best = (0usize, 1usize);
let mut best_shared = -1i64;
for i in 0..work.len() {
for j in (i + 1)..work.len() {
let shared = work[i]
.legs
.iter()
.filter(|l| work[j].legs.contains(l))
.count() as i64;
if shared > best_shared {
best_shared = shared;
best = (i, j);
}
}
}
let (i, j) = best;
let tj = work.remove(j);
let ti = work.remove(i);
let merged = ti.contract(&tj)?;
work.push(merged);
}
Ok(work.into_iter().next().unwrap_or_else(|| {
Tensor::scalar(Complex32::new(0.0, 0.0))
}))
}
pub fn contract_to_scalar(&self) -> QuantumResult<Complex32> {
self.contract_all()?.as_scalar()
}
}
#[derive(Debug, Clone)]
pub enum TnGate {
OneQ {
qubit: usize,
m: [[Complex32; 2]; 2],
},
TwoQ {
q0: usize,
q1: usize,
m: [[Complex32; 4]; 4],
},
}
pub fn amplitude(
n_qubits: usize,
gates: &[TnGate],
bitstring: &[bool],
) -> QuantumResult<Complex32> {
if n_qubits == 0 || n_qubits > 30 {
return Err(QuantumError::InvalidQubitCount { n: n_qubits });
}
if bitstring.len() != n_qubits {
return Err(QuantumError::DimensionMismatch {
expected: n_qubits,
got: bitstring.len(),
});
}
let mut net = TensorNetwork::new();
let mut next_index: Index = 0;
let mut fresh = || {
let i = next_index;
next_index += 1;
i
};
let mut current_wire: Vec<Index> = (0..n_qubits).map(|_| fresh()).collect();
for &w in ¤t_wire {
net.push(Tensor::ket_zero(w));
}
for g in gates {
match g {
TnGate::OneQ { qubit, m } => {
if *qubit >= n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: *qubit,
n_qubits,
});
}
let in_w = current_wire[*qubit];
let out_w = fresh();
net.push(Tensor::gate_1q(out_w, in_w, m));
current_wire[*qubit] = out_w;
}
TnGate::TwoQ { q0, q1, m } => {
if *q0 >= n_qubits || *q1 >= n_qubits || q0 == q1 {
return Err(QuantumError::InvalidParameter {
name: "two-qubit gate indices".into(),
});
}
let in0 = current_wire[*q0];
let in1 = current_wire[*q1];
let out0 = fresh();
let out1 = fresh();
net.push(Tensor::gate_2q(out0, out1, in0, in1, m));
current_wire[*q0] = out0;
current_wire[*q1] = out1;
}
}
}
for (q, &w) in current_wire.iter().enumerate() {
net.push(Tensor::bra_basis(w, bitstring[q]));
}
net.contract_to_scalar()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gates::controlled::apply_cnot;
use crate::gates::hadamard::gate_h;
use crate::gates::pauli::gate_x;
use crate::handle::LcgRng;
use crate::statevec::apply_1q::apply_1q_inplace;
use crate::statevec::state::StateVector;
fn h_mat() -> [[Complex32; 2]; 2] {
gate_h()
}
fn cnot_mat() -> [[Complex32; 4]; 4] {
let z = Complex32::new(0.0, 0.0);
let o = Complex32::new(1.0, 0.0);
[[o, z, z, z], [z, o, z, z], [z, z, z, o], [z, z, o, z]]
}
#[test]
fn scalar_contract_inner_product() {
let ket = Tensor::ket_zero(0);
let bra = Tensor::bra_basis(0, false);
let s = bra
.contract(&ket)
.expect("contract")
.as_scalar()
.expect("scalar");
assert!((s.re - 1.0).abs() < 1e-6 && s.im.abs() < 1e-6);
let bra1 = Tensor::bra_basis(0, true);
let s2 = bra1
.contract(&Tensor::ket_zero(0))
.expect("c")
.as_scalar()
.expect("s");
assert!(s2.norm() < 1e-6);
}
#[test]
fn single_hadamard_amplitudes() {
let gates = vec![TnGate::OneQ {
qubit: 0,
m: h_mat(),
}];
let a0 = amplitude(1, &gates, &[false]).expect("amp0");
let a1 = amplitude(1, &gates, &[true]).expect("amp1");
let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
assert!((a0.re - inv_sqrt2).abs() < 1e-5, "a0={a0:?}");
assert!((a1.re - inv_sqrt2).abs() < 1e-5, "a1={a1:?}");
}
#[test]
fn bell_state_amplitudes_match_statevector() {
let gates = vec![
TnGate::OneQ {
qubit: 0,
m: h_mat(),
},
TnGate::TwoQ {
q0: 0,
q1: 1,
m: cnot_mat(),
},
];
let mut sv = StateVector::new_zero_state(2).expect("2q");
apply_1q_inplace(&mut sv, 0, &gate_h()).expect("H");
apply_cnot(&mut sv, 0, 1).expect("cnot");
for idx in 0..4usize {
let b0 = (idx & 1) != 0; let b1 = (idx & 2) != 0;
let amp = amplitude(2, &gates, &[b0, b1]).expect("amp");
let reference = sv.amps[idx];
assert!(
(amp.re - reference.re).abs() < 1e-5 && (amp.im - reference.im).abs() < 1e-5,
"idx={idx}: tn={amp:?} sv={reference:?}"
);
}
}
#[test]
fn deep_random_circuit_matches_statevector() {
let mut rng = LcgRng::new(123);
let mut gates: Vec<TnGate> = Vec::new();
let mut sv = StateVector::new_zero_state(3).expect("3q");
for _ in 0..6 {
for q in 0..3usize {
let theta = rng.next_f32() * std::f32::consts::TAU;
let c = (theta * 0.5).cos();
let s = (theta * 0.5).sin();
let m = [
[Complex32::new(c, 0.0), Complex32::new(-s, 0.0)],
[Complex32::new(s, 0.0), Complex32::new(c, 0.0)],
];
gates.push(TnGate::OneQ { qubit: q, m });
apply_1q_inplace(&mut sv, q, &m).expect("apply ry");
}
gates.push(TnGate::TwoQ {
q0: 0,
q1: 1,
m: cnot_mat(),
});
apply_cnot(&mut sv, 0, 1).expect("cnot01");
gates.push(TnGate::TwoQ {
q0: 1,
q1: 2,
m: cnot_mat(),
});
apply_cnot(&mut sv, 1, 2).expect("cnot12");
}
for idx in 0..8usize {
let b0 = (idx & 1) != 0;
let b1 = (idx & 2) != 0;
let b2 = (idx & 4) != 0;
let amp = amplitude(3, &gates, &[b0, b1, b2]).expect("amp");
let reference = sv.amps[idx];
assert!(
(amp.re - reference.re).abs() < 1e-3 && (amp.im - reference.im).abs() < 1e-3,
"idx={idx}: tn={amp:?} sv={reference:?}"
);
}
}
#[test]
fn amplitude_probabilities_sum_to_one() {
let mut rng = LcgRng::new(77);
let n = 4usize;
let mut gates: Vec<TnGate> = Vec::new();
for q in 0..n {
let theta = rng.next_f32() * std::f32::consts::TAU;
let c = (theta * 0.5).cos();
let s = (theta * 0.5).sin();
let m = [
[Complex32::new(c, 0.0), Complex32::new(-s, 0.0)],
[Complex32::new(s, 0.0), Complex32::new(c, 0.0)],
];
gates.push(TnGate::OneQ { qubit: q, m });
}
gates.push(TnGate::TwoQ {
q0: 0,
q1: 1,
m: cnot_mat(),
});
gates.push(TnGate::TwoQ {
q0: 2,
q1: 3,
m: cnot_mat(),
});
let mut total = 0.0_f32;
for idx in 0..(1usize << n) {
let bits: Vec<bool> = (0..n).map(|q| (idx >> q) & 1 != 0).collect();
let amp = amplitude(n, &gates, &bits).expect("amp");
total += amp.norm_sqr();
}
assert!((total - 1.0).abs() < 1e-4, "total prob={total}");
}
#[test]
fn x_gate_flips_amplitude() {
let gates = vec![TnGate::OneQ {
qubit: 0,
m: gate_x(),
}];
let a1 = amplitude(1, &gates, &[true]).expect("a1");
let a0 = amplitude(1, &gates, &[false]).expect("a0");
assert!((a1.re - 1.0).abs() < 1e-6);
assert!(a0.norm() < 1e-6);
}
#[test]
fn empty_network_errors() {
let net = TensorNetwork::new();
assert!(net.contract_all().is_err());
}
}