1use num_complex::Complex;
22
23use crate::error::{QuantumError, QuantumResult};
24
25type Complex32 = Complex<f32>;
26
27pub type Index = u32;
31
32#[derive(Debug, Clone)]
38pub struct Tensor {
39 pub legs: Vec<Index>,
41 pub data: Vec<Complex32>,
43}
44
45impl Tensor {
46 pub fn new(legs: Vec<Index>, data: Vec<Complex32>) -> QuantumResult<Self> {
51 let expected = 1usize << legs.len();
52 if data.len() != expected {
53 return Err(QuantumError::DimensionMismatch {
54 expected,
55 got: data.len(),
56 });
57 }
58 Ok(Self { legs, data })
59 }
60
61 #[must_use]
63 pub fn scalar(value: Complex32) -> Self {
64 Self {
65 legs: Vec::new(),
66 data: vec![value],
67 }
68 }
69
70 #[must_use]
72 pub fn ket_zero(wire: Index) -> Self {
73 Self {
74 legs: vec![wire],
75 data: vec![Complex32::new(1.0, 0.0), Complex32::new(0.0, 0.0)],
76 }
77 }
78
79 #[must_use]
81 pub fn bra_basis(wire: Index, bit: bool) -> Self {
82 let data = if bit {
83 vec![Complex32::new(0.0, 0.0), Complex32::new(1.0, 0.0)]
84 } else {
85 vec![Complex32::new(1.0, 0.0), Complex32::new(0.0, 0.0)]
86 };
87 Self {
88 legs: vec![wire],
89 data,
90 }
91 }
92
93 #[must_use]
98 pub fn gate_1q(out_wire: Index, in_wire: Index, m: &[[Complex32; 2]; 2]) -> Self {
99 let data = vec![m[0][0], m[0][1], m[1][0], m[1][1]];
101 Self {
102 legs: vec![out_wire, in_wire],
103 data,
104 }
105 }
106
107 #[must_use]
113 pub fn gate_2q(
114 out0: Index,
115 out1: Index,
116 in0: Index,
117 in1: Index,
118 m: &[[Complex32; 4]; 4],
119 ) -> Self {
120 let mut data = vec![Complex32::new(0.0, 0.0); 16];
125 for (o, row) in m.iter().enumerate() {
126 for (i, &entry) in row.iter().enumerate() {
127 data[o * 4 + i] = entry;
128 }
129 }
130 Self {
131 legs: vec![out0, out1, in0, in1],
132 data,
133 }
134 }
135
136 #[must_use]
138 pub fn rank(&self) -> usize {
139 self.legs.len()
140 }
141
142 pub fn as_scalar(&self) -> QuantumResult<Complex32> {
147 if self.legs.is_empty() && self.data.len() == 1 {
148 Ok(self.data[0])
149 } else {
150 Err(QuantumError::Internal {
151 msg: format!("tensor is rank {}, not a scalar", self.rank()),
152 })
153 }
154 }
155
156 pub fn contract(&self, other: &Self) -> QuantumResult<Self> {
166 let mut shared: Vec<Index> = Vec::new();
168 for &l in &self.legs {
169 if other.legs.contains(&l) {
170 shared.push(l);
171 }
172 }
173 let a_free: Vec<Index> = self
174 .legs
175 .iter()
176 .copied()
177 .filter(|l| !shared.contains(l))
178 .collect();
179 let b_free: Vec<Index> = other
180 .legs
181 .iter()
182 .copied()
183 .filter(|l| !shared.contains(l))
184 .collect();
185
186 let s = shared.len();
187 let af = a_free.len();
188 let bf = b_free.len();
189
190 let a_pos = leg_positions(&self.legs);
193 let b_pos = leg_positions(&other.legs);
194
195 let mut out_data = vec![Complex32::new(0.0, 0.0); 1usize << (af + bf)];
196
197 let n_af = 1usize << af;
199 let n_bf = 1usize << bf;
200 let n_s = 1usize << s;
201
202 for fa in 0..n_af {
203 for fb in 0..n_bf {
204 let mut acc = Complex32::new(0.0, 0.0);
205 for sv in 0..n_s {
206 let mut a_idx = 0usize;
208 for (d, leg) in a_free.iter().enumerate() {
209 let bit = (fa >> (af - 1 - d)) & 1;
210 a_idx |= bit << (self.legs.len() - 1 - a_pos[leg]);
211 }
212 for (d, leg) in shared.iter().enumerate() {
213 let bit = (sv >> (s - 1 - d)) & 1;
214 a_idx |= bit << (self.legs.len() - 1 - a_pos[leg]);
215 }
216 let mut b_idx = 0usize;
218 for (d, leg) in b_free.iter().enumerate() {
219 let bit = (fb >> (bf - 1 - d)) & 1;
220 b_idx |= bit << (other.legs.len() - 1 - b_pos[leg]);
221 }
222 for (d, leg) in shared.iter().enumerate() {
223 let bit = (sv >> (s - 1 - d)) & 1;
224 b_idx |= bit << (other.legs.len() - 1 - b_pos[leg]);
225 }
226 acc += self.data[a_idx] * other.data[b_idx];
227 }
228 let out_idx = (fa << bf) | fb;
229 out_data[out_idx] = acc;
230 }
231 }
232
233 let mut out_legs = a_free;
234 out_legs.extend(b_free);
235 Ok(Self {
236 legs: out_legs,
237 data: out_data,
238 })
239 }
240}
241
242fn leg_positions(legs: &[Index]) -> std::collections::HashMap<Index, usize> {
244 legs.iter().enumerate().map(|(i, &l)| (l, i)).collect()
245}
246
247#[derive(Debug, Clone, Default)]
249pub struct TensorNetwork {
250 pub tensors: Vec<Tensor>,
251}
252
253impl TensorNetwork {
254 #[must_use]
256 pub fn new() -> Self {
257 Self {
258 tensors: Vec::new(),
259 }
260 }
261
262 pub fn push(&mut self, t: Tensor) {
264 self.tensors.push(t);
265 }
266
267 pub fn contract_all(&self) -> QuantumResult<Tensor> {
277 if self.tensors.is_empty() {
278 return Err(QuantumError::EmptyInput);
279 }
280 let mut work: Vec<Tensor> = self.tensors.clone();
281 while work.len() > 1 {
282 let mut best = (0usize, 1usize);
284 let mut best_shared = -1i64;
285 for i in 0..work.len() {
286 for j in (i + 1)..work.len() {
287 let shared = work[i]
288 .legs
289 .iter()
290 .filter(|l| work[j].legs.contains(l))
291 .count() as i64;
292 if shared > best_shared {
293 best_shared = shared;
294 best = (i, j);
295 }
296 }
297 }
298 let (i, j) = best;
299 let tj = work.remove(j);
301 let ti = work.remove(i);
302 let merged = ti.contract(&tj)?;
303 work.push(merged);
304 }
305 Ok(work.into_iter().next().unwrap_or_else(|| {
306 Tensor::scalar(Complex32::new(0.0, 0.0))
308 }))
309 }
310
311 pub fn contract_to_scalar(&self) -> QuantumResult<Complex32> {
316 self.contract_all()?.as_scalar()
317 }
318}
319
320#[derive(Debug, Clone)]
322pub enum TnGate {
323 OneQ {
325 qubit: usize,
326 m: [[Complex32; 2]; 2],
327 },
328 TwoQ {
330 q0: usize,
331 q1: usize,
332 m: [[Complex32; 4]; 4],
333 },
334}
335
336pub fn amplitude(
348 n_qubits: usize,
349 gates: &[TnGate],
350 bitstring: &[bool],
351) -> QuantumResult<Complex32> {
352 if n_qubits == 0 || n_qubits > 30 {
353 return Err(QuantumError::InvalidQubitCount { n: n_qubits });
354 }
355 if bitstring.len() != n_qubits {
356 return Err(QuantumError::DimensionMismatch {
357 expected: n_qubits,
358 got: bitstring.len(),
359 });
360 }
361
362 let mut net = TensorNetwork::new();
363 let mut next_index: Index = 0;
365 let mut fresh = || {
366 let i = next_index;
367 next_index += 1;
368 i
369 };
370
371 let mut current_wire: Vec<Index> = (0..n_qubits).map(|_| fresh()).collect();
373 for &w in ¤t_wire {
375 net.push(Tensor::ket_zero(w));
376 }
377
378 for g in gates {
379 match g {
380 TnGate::OneQ { qubit, m } => {
381 if *qubit >= n_qubits {
382 return Err(QuantumError::QubitIndexOutOfRange {
383 index: *qubit,
384 n_qubits,
385 });
386 }
387 let in_w = current_wire[*qubit];
388 let out_w = fresh();
389 net.push(Tensor::gate_1q(out_w, in_w, m));
390 current_wire[*qubit] = out_w;
391 }
392 TnGate::TwoQ { q0, q1, m } => {
393 if *q0 >= n_qubits || *q1 >= n_qubits || q0 == q1 {
394 return Err(QuantumError::InvalidParameter {
395 name: "two-qubit gate indices".into(),
396 });
397 }
398 let in0 = current_wire[*q0];
399 let in1 = current_wire[*q1];
400 let out0 = fresh();
401 let out1 = fresh();
402 net.push(Tensor::gate_2q(out0, out1, in0, in1, m));
403 current_wire[*q0] = out0;
404 current_wire[*q1] = out1;
405 }
406 }
407 }
408
409 for (q, &w) in current_wire.iter().enumerate() {
411 net.push(Tensor::bra_basis(w, bitstring[q]));
412 }
413
414 net.contract_to_scalar()
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use crate::gates::controlled::apply_cnot;
421 use crate::gates::hadamard::gate_h;
422 use crate::gates::pauli::gate_x;
423 use crate::handle::LcgRng;
424 use crate::statevec::apply_1q::apply_1q_inplace;
425 use crate::statevec::state::StateVector;
426
427 fn h_mat() -> [[Complex32; 2]; 2] {
428 gate_h()
429 }
430
431 fn cnot_mat() -> [[Complex32; 4]; 4] {
432 let z = Complex32::new(0.0, 0.0);
434 let o = Complex32::new(1.0, 0.0);
435 [[o, z, z, z], [z, o, z, z], [z, z, z, o], [z, z, o, z]]
436 }
437
438 #[test]
439 fn scalar_contract_inner_product() {
440 let ket = Tensor::ket_zero(0);
442 let bra = Tensor::bra_basis(0, false);
443 let s = bra
444 .contract(&ket)
445 .expect("contract")
446 .as_scalar()
447 .expect("scalar");
448 assert!((s.re - 1.0).abs() < 1e-6 && s.im.abs() < 1e-6);
449 let bra1 = Tensor::bra_basis(0, true);
451 let s2 = bra1
452 .contract(&Tensor::ket_zero(0))
453 .expect("c")
454 .as_scalar()
455 .expect("s");
456 assert!(s2.norm() < 1e-6);
457 }
458
459 #[test]
460 fn single_hadamard_amplitudes() {
461 let gates = vec![TnGate::OneQ {
463 qubit: 0,
464 m: h_mat(),
465 }];
466 let a0 = amplitude(1, &gates, &[false]).expect("amp0");
467 let a1 = amplitude(1, &gates, &[true]).expect("amp1");
468 let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
469 assert!((a0.re - inv_sqrt2).abs() < 1e-5, "a0={a0:?}");
470 assert!((a1.re - inv_sqrt2).abs() < 1e-5, "a1={a1:?}");
471 }
472
473 #[test]
474 fn bell_state_amplitudes_match_statevector() {
475 let gates = vec![
477 TnGate::OneQ {
478 qubit: 0,
479 m: h_mat(),
480 },
481 TnGate::TwoQ {
482 q0: 0,
483 q1: 1,
484 m: cnot_mat(),
485 },
486 ];
487
488 let mut sv = StateVector::new_zero_state(2).expect("2q");
490 apply_1q_inplace(&mut sv, 0, &gate_h()).expect("H");
491 apply_cnot(&mut sv, 0, 1).expect("cnot");
492
493 for idx in 0..4usize {
494 let b0 = (idx & 1) != 0; let b1 = (idx & 2) != 0;
496 let amp = amplitude(2, &gates, &[b0, b1]).expect("amp");
497 let reference = sv.amps[idx];
498 assert!(
499 (amp.re - reference.re).abs() < 1e-5 && (amp.im - reference.im).abs() < 1e-5,
500 "idx={idx}: tn={amp:?} sv={reference:?}"
501 );
502 }
503 }
504
505 #[test]
506 fn deep_random_circuit_matches_statevector() {
507 let mut rng = LcgRng::new(123);
509 let mut gates: Vec<TnGate> = Vec::new();
510 let mut sv = StateVector::new_zero_state(3).expect("3q");
511
512 for _ in 0..6 {
513 for q in 0..3usize {
515 let theta = rng.next_f32() * std::f32::consts::TAU;
516 let c = (theta * 0.5).cos();
517 let s = (theta * 0.5).sin();
518 let m = [
519 [Complex32::new(c, 0.0), Complex32::new(-s, 0.0)],
520 [Complex32::new(s, 0.0), Complex32::new(c, 0.0)],
521 ];
522 gates.push(TnGate::OneQ { qubit: q, m });
523 apply_1q_inplace(&mut sv, q, &m).expect("apply ry");
524 }
525 gates.push(TnGate::TwoQ {
527 q0: 0,
528 q1: 1,
529 m: cnot_mat(),
530 });
531 apply_cnot(&mut sv, 0, 1).expect("cnot01");
532 gates.push(TnGate::TwoQ {
533 q0: 1,
534 q1: 2,
535 m: cnot_mat(),
536 });
537 apply_cnot(&mut sv, 1, 2).expect("cnot12");
538 }
539
540 for idx in 0..8usize {
541 let b0 = (idx & 1) != 0;
542 let b1 = (idx & 2) != 0;
543 let b2 = (idx & 4) != 0;
544 let amp = amplitude(3, &gates, &[b0, b1, b2]).expect("amp");
545 let reference = sv.amps[idx];
546 assert!(
547 (amp.re - reference.re).abs() < 1e-3 && (amp.im - reference.im).abs() < 1e-3,
548 "idx={idx}: tn={amp:?} sv={reference:?}"
549 );
550 }
551 }
552
553 #[test]
554 fn amplitude_probabilities_sum_to_one() {
555 let mut rng = LcgRng::new(77);
558 let n = 4usize;
559 let mut gates: Vec<TnGate> = Vec::new();
560 for q in 0..n {
561 let theta = rng.next_f32() * std::f32::consts::TAU;
562 let c = (theta * 0.5).cos();
563 let s = (theta * 0.5).sin();
564 let m = [
565 [Complex32::new(c, 0.0), Complex32::new(-s, 0.0)],
566 [Complex32::new(s, 0.0), Complex32::new(c, 0.0)],
567 ];
568 gates.push(TnGate::OneQ { qubit: q, m });
569 }
570 gates.push(TnGate::TwoQ {
571 q0: 0,
572 q1: 1,
573 m: cnot_mat(),
574 });
575 gates.push(TnGate::TwoQ {
576 q0: 2,
577 q1: 3,
578 m: cnot_mat(),
579 });
580
581 let mut total = 0.0_f32;
582 for idx in 0..(1usize << n) {
583 let bits: Vec<bool> = (0..n).map(|q| (idx >> q) & 1 != 0).collect();
584 let amp = amplitude(n, &gates, &bits).expect("amp");
585 total += amp.norm_sqr();
586 }
587 assert!((total - 1.0).abs() < 1e-4, "total prob={total}");
588 }
589
590 #[test]
591 fn x_gate_flips_amplitude() {
592 let gates = vec![TnGate::OneQ {
593 qubit: 0,
594 m: gate_x(),
595 }];
596 let a1 = amplitude(1, &gates, &[true]).expect("a1");
597 let a0 = amplitude(1, &gates, &[false]).expect("a0");
598 assert!((a1.re - 1.0).abs() < 1e-6);
599 assert!(a0.norm() < 1e-6);
600 }
601
602 #[test]
603 fn empty_network_errors() {
604 let net = TensorNetwork::new();
605 assert!(net.contract_all().is_err());
606 }
607}