1use num_complex::Complex;
44
45use crate::error::{QuantumError, QuantumResult};
46use crate::statevec::state::StateVector;
47
48type Complex32 = Complex<f32>;
49
50#[inline]
51fn c0() -> Complex32 {
52 Complex32::new(0.0, 0.0)
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
57pub struct MpsConfig {
58 pub n_qubits: usize,
60 pub max_bond_dim: usize,
62 pub svd_cutoff: f32,
64}
65
66impl MpsConfig {
67 pub fn new(n_qubits: usize, max_bond_dim: usize, svd_cutoff: f32) -> QuantumResult<Self> {
69 if n_qubits == 0 || n_qubits > 30 {
70 return Err(QuantumError::InvalidQubitCount { n: n_qubits });
71 }
72 if max_bond_dim == 0 {
73 return Err(QuantumError::InvalidParameter {
74 name: "max_bond_dim must be >= 1".into(),
75 });
76 }
77 if !(svd_cutoff.is_finite() && svd_cutoff >= 0.0) {
78 return Err(QuantumError::InvalidParameter {
79 name: "svd_cutoff must be finite and >= 0".into(),
80 });
81 }
82 Ok(Self {
83 n_qubits,
84 max_bond_dim,
85 svd_cutoff,
86 })
87 }
88}
89
90#[derive(Debug, Clone)]
92struct SiteTensor {
93 left: usize,
94 right: usize,
95 data: Vec<Complex32>,
97}
98
99impl SiteTensor {
100 fn zeros(left: usize, right: usize) -> Self {
101 Self {
102 left,
103 right,
104 data: vec![c0(); left * 2 * right],
105 }
106 }
107
108 #[inline]
109 fn idx(&self, l: usize, p: usize, r: usize) -> usize {
110 (l * 2 + p) * self.right + r
111 }
112
113 #[inline]
114 fn get(&self, l: usize, p: usize, r: usize) -> Complex32 {
115 self.data[self.idx(l, p, r)]
116 }
117
118 #[inline]
119 fn set(&mut self, l: usize, p: usize, r: usize, v: Complex32) {
120 let i = self.idx(l, p, r);
121 self.data[i] = v;
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct MatrixProductState {
128 config: MpsConfig,
129 sites: Vec<SiteTensor>,
130}
131
132impl MatrixProductState {
133 pub fn new_zero_state(config: MpsConfig) -> QuantumResult<Self> {
135 let n = config.n_qubits;
136 let mut sites = Vec::with_capacity(n);
137 for _ in 0..n {
138 let mut t = SiteTensor::zeros(1, 1);
140 t.set(0, 0, 0, Complex32::new(1.0, 0.0));
141 sites.push(t);
142 }
143 Ok(Self { config, sites })
144 }
145
146 #[must_use]
148 pub fn n_qubits(&self) -> usize {
149 self.config.n_qubits
150 }
151
152 #[must_use]
154 pub fn config(&self) -> MpsConfig {
155 self.config
156 }
157
158 #[must_use]
163 pub fn bond_dims(&self) -> Vec<usize> {
164 self.sites.iter().map(|s| s.right).collect()
165 }
166
167 pub fn apply_1q(&mut self, gate: &[[Complex32; 2]; 2], qubit: usize) -> QuantumResult<()> {
171 if qubit >= self.config.n_qubits {
172 return Err(QuantumError::QubitIndexOutOfRange {
173 index: qubit,
174 n_qubits: self.config.n_qubits,
175 });
176 }
177 let site = &mut self.sites[qubit];
178 let left = site.left;
179 let right = site.right;
180 for l in 0..left {
181 for r in 0..right {
182 let a0 = site.get(l, 0, r);
183 let a1 = site.get(l, 1, r);
184 let n0 = gate[0][0] * a0 + gate[0][1] * a1;
185 let n1 = gate[1][0] * a0 + gate[1][1] * a1;
186 site.set(l, 0, r, n0);
187 site.set(l, 1, r, n1);
188 }
189 }
190 Ok(())
191 }
192
193 pub fn apply_2q(
204 &mut self,
205 gate: &[[Complex32; 4]; 4],
206 q: usize,
207 q_plus_1: usize,
208 ) -> QuantumResult<()> {
209 let n = self.config.n_qubits;
210 if q >= n {
211 return Err(QuantumError::QubitIndexOutOfRange {
212 index: q,
213 n_qubits: n,
214 });
215 }
216 if q_plus_1 >= n {
217 return Err(QuantumError::QubitIndexOutOfRange {
218 index: q_plus_1,
219 n_qubits: n,
220 });
221 }
222 if q_plus_1 != q + 1 {
223 return Err(QuantumError::InvalidParameter {
224 name: "apply_2q requires adjacent qubits with q_plus_1 == q + 1".into(),
225 });
226 }
227
228 let left = self.sites[q].left;
232 let mid = self.sites[q].right; let right = self.sites[q_plus_1].right;
234 debug_assert_eq!(mid, self.sites[q_plus_1].left);
235
236 let rows = left * 2;
241 let cols = 2 * right;
242 let mut theta = vec![c0(); rows * cols];
243 {
244 let sq = &self.sites[q];
245 let sq1 = &self.sites[q_plus_1];
246 for l in 0..left {
247 for p in 0..2 {
248 for p2 in 0..2 {
249 for r in 0..right {
250 let mut acc = c0();
251 for m in 0..mid {
252 acc += sq.get(l, p, m) * sq1.get(m, p2, r);
253 }
254 let row = l * 2 + p;
255 let col = p2 * right + r;
256 theta[row * cols + col] = acc;
257 }
258 }
259 }
260 }
261 }
262
263 let mut gated = vec![c0(); rows * cols];
267 for l in 0..left {
268 for r in 0..right {
269 let v00 = theta[(l * 2) * cols + r];
271 let v01 = theta[(l * 2) * cols + (right + r)];
272 let v10 = theta[(l * 2 + 1) * cols + r];
273 let v11 = theta[(l * 2 + 1) * cols + (right + r)];
274 let input = [v00, v01, v10, v11];
276 for (big, grow) in gate.iter().enumerate() {
277 let mut acc = c0();
278 for (small, &iv) in input.iter().enumerate() {
279 acc += grow[small] * iv;
280 }
281 let big_p = big >> 1; let big_p2 = big & 1; let row = l * 2 + big_p;
284 let col = if big_p2 == 1 { right + r } else { r };
285 gated[row * cols + col] = acc;
286 }
287 }
288 }
289
290 let svd = svd_dense(&gated, rows, cols)?;
292 let full_rank = svd.singular_values.len();
293
294 let mut keep = 0usize;
296 for &s in &svd.singular_values {
297 if s >= self.config.svd_cutoff && keep < self.config.max_bond_dim {
298 keep += 1;
299 } else {
300 break;
301 }
302 }
303 if keep == 0 {
304 keep = if full_rank == 0 { 0 } else { 1 };
306 }
307 let new_bond = keep;
308
309 let mut left_tensor = SiteTensor::zeros(left, new_bond);
312 for l in 0..left {
313 for p in 0..2 {
314 let row = l * 2 + p;
315 for k in 0..new_bond {
316 let u = svd.u[row * full_rank + k];
317 let val = u * svd.singular_values[k];
318 left_tensor.set(l, p, k, val);
319 }
320 }
321 }
322
323 let mut right_tensor = SiteTensor::zeros(new_bond, right);
326 for k in 0..new_bond {
327 for p2 in 0..2 {
328 for r in 0..right {
329 let col = p2 * right + r;
330 let v = svd.v[col * full_rank + k];
331 right_tensor.set(k, p2, r, v.conj());
332 }
333 }
334 }
335
336 self.sites[q] = left_tensor;
337 self.sites[q_plus_1] = right_tensor;
338 Ok(())
339 }
340
341 pub fn to_statevector(&self) -> QuantumResult<StateVector> {
345 let n = self.config.n_qubits;
346 let s0 = &self.sites[0];
352 if s0.left != 1 {
353 return Err(QuantumError::Internal {
354 msg: "left boundary bond must be 1".into(),
355 });
356 }
357 let mut acc_dim = 2usize;
358 let mut acc_right = s0.right;
359 let mut acc = vec![c0(); acc_dim * acc_right];
360 for p in 0..2 {
361 for r in 0..acc_right {
362 acc[p * acc_right + r] = s0.get(0, p, r);
363 }
364 }
365
366 for site in self.sites.iter().skip(1) {
367 if site.left != acc_right {
368 return Err(QuantumError::Internal {
369 msg: "bond mismatch during contraction".into(),
370 });
371 }
372 let new_right = site.right;
373 let new_dim = acc_dim * 2;
374 let mut next = vec![c0(); new_dim * new_right];
375 for phys_old in 0..acc_dim {
380 for p_new in 0..2 {
381 let new_phys = phys_old + p_new * acc_dim;
382 for r2 in 0..new_right {
383 let mut val = c0();
384 for m in 0..acc_right {
385 val += acc[phys_old * acc_right + m] * site.get(m, p_new, r2);
386 }
387 next[new_phys * new_right + r2] = val;
388 }
389 }
390 }
391 acc = next;
392 acc_dim = new_dim;
393 acc_right = new_right;
394 }
395
396 if acc_right != 1 {
397 return Err(QuantumError::Internal {
398 msg: "right boundary bond must be 1".into(),
399 });
400 }
401 let dim = 1usize << n;
403 if acc_dim != dim {
404 return Err(QuantumError::DimensionMismatch {
405 expected: dim,
406 got: acc_dim,
407 });
408 }
409 let mut amps = vec![c0(); dim];
410 for (i, slot) in amps.iter_mut().enumerate() {
411 *slot = acc[i];
412 }
413 Ok(StateVector { amps, n_qubits: n })
414 }
415
416 #[must_use]
418 pub fn norm_sq(&self) -> f32 {
419 let mut env = vec![Complex32::new(1.0, 0.0); 1]; let mut dim_a = 1usize; let mut dim_b = 1usize; for site in &self.sites {
426 let l = site.left;
427 let r = site.right;
428 let mut new_env = vec![c0(); r * r];
430 for ap in 0..r {
431 for bp in 0..r {
432 let mut acc = c0();
433 for p in 0..2 {
434 for a in 0..l.min(dim_a) {
435 let bra = site.get(a, p, ap).conj();
436 for b in 0..l.min(dim_b) {
437 acc += bra * env[a * dim_b + b] * site.get(b, p, bp);
438 }
439 }
440 }
441 new_env[ap * r + bp] = acc;
442 }
443 }
444 env = new_env;
445 dim_a = r;
446 dim_b = r;
447 }
448 if env.is_empty() {
450 return 0.0;
451 }
452 env[0].re.max(0.0)
453 }
454
455 #[must_use]
457 pub fn norm(&self) -> f32 {
458 self.norm_sq().sqrt()
459 }
460
461 pub fn normalize(&mut self) {
463 let nrm = self.norm();
464 if nrm > 1e-12 {
465 let inv = 1.0 / nrm;
466 if let Some(first) = self.sites.first_mut() {
467 for v in &mut first.data {
468 *v *= inv;
469 }
470 }
471 }
472 }
473
474 pub fn expectation_z(&self, qubit: usize) -> QuantumResult<f32> {
476 if qubit >= self.config.n_qubits {
477 return Err(QuantumError::QubitIndexOutOfRange {
478 index: qubit,
479 n_qubits: self.config.n_qubits,
480 });
481 }
482 let mut env = vec![Complex32::new(1.0, 0.0); 1];
485 let mut dim_a = 1usize;
486 let mut dim_b = 1usize;
487 for (idx, site) in self.sites.iter().enumerate() {
488 let l = site.left;
489 let r = site.right;
490 let mut new_env = vec![c0(); r * r];
491 for ap in 0..r {
492 for bp in 0..r {
493 let mut acc = c0();
494 for p in 0..2 {
495 let z_phase = if idx == qubit && p == 1 { -1.0 } else { 1.0 };
496 for a in 0..l.min(dim_a) {
497 let bra = site.get(a, p, ap).conj() * z_phase;
498 for b in 0..l.min(dim_b) {
499 acc += bra * env[a * dim_b + b] * site.get(b, p, bp);
500 }
501 }
502 }
503 new_env[ap * r + bp] = acc;
504 }
505 }
506 env = new_env;
507 dim_a = r;
508 dim_b = r;
509 }
510 let numer = if env.is_empty() { 0.0 } else { env[0].re };
511 let denom = self.norm_sq();
512 if denom <= 1e-12 {
513 return Err(QuantumError::MeasurementFailed);
514 }
515 Ok(numer / denom)
516 }
517}
518
519struct SvdResult {
523 u: Vec<Complex32>,
525 v: Vec<Complex32>,
527 singular_values: Vec<f32>,
529}
530
531fn svd_dense(mat: &[Complex32], m: usize, n: usize) -> QuantumResult<SvdResult> {
538 if m == 0 || n == 0 || mat.len() != m * n {
539 return Err(QuantumError::DimensionMismatch {
540 expected: m * n,
541 got: mat.len(),
542 });
543 }
544 let k = m.min(n);
545
546 if m >= n {
547 let g = gram_ata(mat, m, n);
549 let (evals, evecs) = jacobi_hermitian(&g, n)?;
550 let order = sorted_desc_indices(&evals);
552 let mut singular_values = Vec::with_capacity(k);
553 let mut v = vec![c0(); n * k]; let mut u = vec![c0(); m * k]; for (col, &ei) in order.iter().take(k).enumerate() {
556 let sigma = evals[ei].max(0.0).sqrt();
557 singular_values.push(sigma);
558 for row in 0..n {
560 v[row * k + col] = evecs[row * n + ei];
561 }
562 if sigma > 1e-12 {
564 let inv = 1.0 / sigma;
565 for i in 0..m {
566 let mut acc = c0();
567 for j in 0..n {
568 acc += mat[i * n + j] * v[j * k + col];
569 }
570 u[i * k + col] = acc * inv;
571 }
572 }
573 }
574 fill_orthonormal_u(&mut u, &singular_values, m, k);
575 Ok(SvdResult {
576 u,
577 v,
578 singular_values,
579 })
580 } else {
581 let g = gram_aat(mat, m, n);
583 let (evals, evecs) = jacobi_hermitian(&g, m)?;
584 let order = sorted_desc_indices(&evals);
585 let mut singular_values = Vec::with_capacity(k);
586 let mut u = vec![c0(); m * k]; let mut v = vec![c0(); n * k]; for (col, &ei) in order.iter().take(k).enumerate() {
589 let sigma = evals[ei].max(0.0).sqrt();
590 singular_values.push(sigma);
591 for row in 0..m {
592 u[row * k + col] = evecs[row * m + ei];
593 }
594 if sigma > 1e-12 {
596 let inv = 1.0 / sigma;
597 for j in 0..n {
598 let mut acc = c0();
599 for i in 0..m {
600 acc += mat[i * n + j].conj() * u[i * k + col];
601 }
602 v[j * k + col] = acc * inv;
603 }
604 }
605 }
606 Ok(SvdResult {
607 u,
608 v,
609 singular_values,
610 })
611 }
612}
613
614fn fill_orthonormal_u(u: &mut [Complex32], sigma: &[f32], m: usize, k: usize) {
619 for col in 0..k.min(sigma.len()) {
620 if sigma[col] > 1e-12 {
621 continue;
622 }
623 let mut placed = false;
626 for seed in 0..m {
627 let mut cand = vec![c0(); m];
628 cand[seed] = Complex32::new(1.0, 0.0);
629 for prev in 0..col {
630 let mut dot = c0();
632 for i in 0..m {
633 dot += u[i * k + prev].conj() * cand[i];
634 }
635 for i in 0..m {
636 cand[i] -= dot * u[i * k + prev];
637 }
638 }
639 let nrm: f32 = cand.iter().map(|x| x.norm_sqr()).sum::<f32>().sqrt();
640 if nrm > 1e-6 {
641 let inv = 1.0 / nrm;
642 for i in 0..m {
643 u[i * k + col] = cand[i] * inv;
644 }
645 placed = true;
646 break;
647 }
648 }
649 if !placed {
650 for i in 0..m {
652 u[i * k + col] = c0();
653 }
654 }
655 }
656}
657
658fn gram_ata(mat: &[Complex32], m: usize, n: usize) -> Vec<Complex32> {
660 let mut g = vec![c0(); n * n];
661 for a in 0..n {
662 for b in 0..n {
663 let mut acc = c0();
664 for i in 0..m {
665 acc += mat[i * n + a].conj() * mat[i * n + b];
666 }
667 g[a * n + b] = acc;
668 }
669 }
670 g
671}
672
673fn gram_aat(mat: &[Complex32], m: usize, n: usize) -> Vec<Complex32> {
675 let mut g = vec![c0(); m * m];
676 for a in 0..m {
677 for b in 0..m {
678 let mut acc = c0();
679 for j in 0..n {
680 acc += mat[a * n + j] * mat[b * n + j].conj();
681 }
682 g[a * m + b] = acc;
683 }
684 }
685 g
686}
687
688fn sorted_desc_indices(vals: &[f32]) -> Vec<usize> {
690 let mut idx: Vec<usize> = (0..vals.len()).collect();
691 idx.sort_by(|&i, &j| {
692 vals[j]
693 .partial_cmp(&vals[i])
694 .unwrap_or(std::cmp::Ordering::Equal)
695 });
696 idx
697}
698
699fn jacobi_hermitian(a_in: &[Complex32], n: usize) -> QuantumResult<(Vec<f32>, Vec<Complex32>)> {
710 if a_in.len() != n * n {
711 return Err(QuantumError::DimensionMismatch {
712 expected: n * n,
713 got: a_in.len(),
714 });
715 }
716 let mut a = a_in.to_vec();
717 let mut v = vec![c0(); n * n];
719 for i in 0..n {
720 v[i * n + i] = Complex32::new(1.0, 0.0);
721 }
722 if n == 1 {
723 return Ok((vec![a[0].re], v));
724 }
725
726 let max_sweeps = 100usize;
727 for _sweep in 0..max_sweeps {
728 let mut off = 0.0f32;
730 for p in 0..n {
731 for q in (p + 1)..n {
732 off += a[p * n + q].norm_sqr();
733 }
734 }
735 if off.sqrt() < 1e-12 {
736 break;
737 }
738
739 for p in 0..n {
740 for q in (p + 1)..n {
741 let apq = a[p * n + q];
742 if apq.norm() < 1e-18 {
743 continue;
744 }
745 let app = a[p * n + p].re;
746 let aqq = a[q * n + q].re;
747
748 let phi = apq.im.atan2(apq.re);
750 let (sin_phi, cos_phi) = phi.sin_cos();
751 let e_neg = Complex32::new(cos_phi, -sin_phi); let e_pos = Complex32::new(cos_phi, sin_phi); let abs_apq = apq.norm();
754
755 let tau = (aqq - app) / (2.0 * abs_apq);
758 let t = if tau >= 0.0 {
759 1.0 / (tau + (1.0 + tau * tau).sqrt())
760 } else {
761 -1.0 / (-tau + (1.0 + tau * tau).sqrt())
762 };
763 let cos_t = 1.0 / (1.0 + t * t).sqrt();
764 let sin_t = t * cos_t;
765
766 let c = cos_t;
770 let s_em = sin_phi_combine(sin_t, e_neg);
771 let s_ep = sin_phi_combine(sin_t, e_pos);
772
773 apply_jacobi_rotation(&mut a, n, p, q, c, s_em, s_ep);
776 accumulate_eigenvectors(&mut v, n, p, q, c, s_em, s_ep);
778 }
779 }
780 }
781
782 let eigenvalues: Vec<f32> = (0..n).map(|i| a[i * n + i].re).collect();
783 Ok((eigenvalues, v))
784}
785
786#[inline]
787fn sin_phi_combine(sin_t: f32, e: Complex32) -> Complex32 {
788 Complex32::new(sin_t * e.re, sin_t * e.im)
789}
790
791fn apply_jacobi_rotation(
795 a: &mut [Complex32],
796 n: usize,
797 p: usize,
798 q: usize,
799 c: f32,
800 s_em: Complex32,
801 s_ep: Complex32,
802) {
803 let cc = Complex32::new(c, 0.0);
804 for j in 0..n {
807 let rp = a[p * n + j];
808 let rq = a[q * n + j];
809 a[p * n + j] = cc * rp + s_em * rq;
810 a[q * n + j] = -s_ep * rp + cc * rq;
811 }
812 let s_em_c = s_em.conj();
817 let s_ep_c = s_ep.conj();
818 for i in 0..n {
819 let cp = a[i * n + p];
820 let cq = a[i * n + q];
821 a[i * n + p] = cc * cp + s_em_c * cq;
822 a[i * n + q] = -s_ep_c * cp + cc * cq;
823 }
824}
825
826fn accumulate_eigenvectors(
828 v: &mut [Complex32],
829 n: usize,
830 p: usize,
831 q: usize,
832 c: f32,
833 s_em: Complex32,
834 s_ep: Complex32,
835) {
836 let cc = Complex32::new(c, 0.0);
837 let s_em_c = s_em.conj();
838 let s_ep_c = s_ep.conj();
839 for i in 0..n {
840 let cp = v[i * n + p];
841 let cq = v[i * n + q];
842 v[i * n + p] = cc * cp + s_em_c * cq;
843 v[i * n + q] = -s_ep_c * cp + cc * cq;
844 }
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850 use crate::gates::hadamard::gate_h;
851 use crate::gates::parametric::gate_ry;
852 use crate::gates::pauli::{gate_x, gate_z};
853 use crate::handle::LcgRng;
854 use crate::statevec::apply_1q::{apply_1q_controlled, apply_1q_inplace};
855 use crate::statevec::state::StateVector;
856
857 fn cfg(n: usize, chi: usize) -> MpsConfig {
858 MpsConfig::new(n, chi, 1e-12).expect("test config parameters are valid")
859 }
860
861 fn c(re: f32, im: f32) -> Complex32 {
862 Complex32::new(re, im)
863 }
864
865 fn cnot_4x4() -> [[Complex32; 4]; 4] {
868 let o = c(1.0, 0.0);
869 let z = c0();
870 [[o, z, z, z], [z, o, z, z], [z, z, z, o], [z, z, o, z]]
872 }
873
874 fn max_abs_diff(a: &[Complex32], b: &[Complex32]) -> f32 {
875 a.iter()
876 .zip(b.iter())
877 .map(|(x, y)| (x - y).norm())
878 .fold(0.0f32, f32::max)
879 }
880
881 #[test]
882 fn t01_product_state_is_zero_ket() {
883 let mps = MatrixProductState::new_zero_state(cfg(3, 4))
884 .expect("valid config produces zero state");
885 let sv = mps.to_statevector().expect("MPS converts to statevector");
886 assert!((sv.amps[0].re - 1.0).abs() < 1e-6);
887 for a in &sv.amps[1..] {
888 assert!(a.norm() < 1e-6);
889 }
890 }
891
892 #[test]
893 fn t02_pauli_x_flips_amplitude() {
894 let mut mps = MatrixProductState::new_zero_state(cfg(1, 2))
895 .expect("valid config produces zero state");
896 mps.apply_1q(&gate_x(), 0)
897 .expect("qubit 0 is in range for 1-qubit MPS");
898 let sv = mps.to_statevector().expect("MPS converts to statevector");
899 assert!(sv.amps[0].norm() < 1e-6);
900 assert!((sv.amps[1].re - 1.0).abs() < 1e-6);
901 }
902
903 #[test]
904 fn t03_hadamard_uniform_superposition() {
905 let mut mps = MatrixProductState::new_zero_state(cfg(1, 2))
906 .expect("valid config produces zero state");
907 mps.apply_1q(&gate_h(), 0)
908 .expect("qubit 0 is in range for 1-qubit MPS");
909 let sv = mps.to_statevector().expect("MPS converts to statevector");
910 let inv = std::f32::consts::FRAC_1_SQRT_2;
911 assert!((sv.amps[0].re - inv).abs() < 1e-5);
912 assert!((sv.amps[1].re - inv).abs() < 1e-5);
913 }
914
915 #[test]
916 fn t04_cnot_on_plus_zero_makes_bell_bond2() {
917 let mut mps = MatrixProductState::new_zero_state(cfg(2, 4))
921 .expect("valid config produces zero state");
922 mps.apply_1q(&gate_h(), 0)
923 .expect("qubit 0 is in range for 2-qubit MPS");
924 mps.apply_2q(&cnot_4x4(), 0, 1)
925 .expect("qubits 0 and 1 are adjacent and in range");
926 let sv = mps.to_statevector().expect("MPS converts to statevector");
927 let inv = std::f32::consts::FRAC_1_SQRT_2;
928 assert!((sv.amps[0].re - inv).abs() < 1e-5, "amps={:?}", sv.amps);
930 assert!((sv.amps[3].re - inv).abs() < 1e-5, "amps={:?}", sv.amps);
931 assert!(sv.amps[1].norm() < 1e-5);
932 assert!(sv.amps[2].norm() < 1e-5);
933 assert_eq!(mps.bond_dims()[0], 2);
935 }
936
937 #[test]
938 fn t05_full_chi_matches_statevector_random_circuit() {
939 let n = 5usize;
942 let chi = 1usize << n; let mut mps = MatrixProductState::new_zero_state(cfg(n, chi))
944 .expect("valid config produces zero state");
945 let mut sv =
946 StateVector::new_zero_state(n).expect("valid qubit count produces zero statevector");
947
948 let mut rng = LcgRng::new(12345);
949 let angles = [0.3f32, 1.1, -0.7, 2.0, 0.45, -1.3];
950 for (q, &ang) in angles.iter().take(n).enumerate() {
952 let ry = gate_ry(ang);
953 mps.apply_1q(&ry, q)
954 .expect("qubit index q is within the n-qubit MPS");
955 apply_1q_inplace(&mut sv, q, &ry)
956 .expect("qubit index q is within the n-qubit statevector");
957 mps.apply_1q(&gate_h(), q)
958 .expect("qubit index q is within the n-qubit MPS");
959 apply_1q_inplace(&mut sv, q, &gate_h())
960 .expect("qubit index q is within the n-qubit statevector");
961 }
962 for q in 0..(n - 1) {
964 mps.apply_2q(&cnot_4x4(), q, q + 1)
965 .expect("adjacent qubits q and q+1 are in range");
966 apply_1q_controlled(&mut sv, q, q + 1, &gate_x())
968 .expect("adjacent qubits q and q+1 are valid for controlled gate");
969 }
970 for (q, &ang) in angles.iter().take(n).enumerate() {
972 let ry = gate_ry(ang * 0.5 + 0.2);
973 mps.apply_1q(&ry, q)
974 .expect("qubit index q is within the n-qubit MPS");
975 apply_1q_inplace(&mut sv, q, &ry)
976 .expect("qubit index q is within the n-qubit statevector");
977 }
978 let _ = &mut rng;
979
980 let mps_sv = mps.to_statevector().expect("MPS converts to statevector");
981 let diff = max_abs_diff(&mps_sv.amps, &sv.amps);
982 assert!(diff < 1e-4, "max amplitude diff {diff}");
983 }
984
985 #[test]
986 fn t06_truncation_reduces_bond() {
987 let mut mps = MatrixProductState::new_zero_state(cfg(2, 1))
990 .expect("valid config produces zero state");
991 mps.apply_1q(&gate_h(), 0)
992 .expect("qubit 0 is in range for 2-qubit MPS");
993 mps.apply_2q(&cnot_4x4(), 0, 1)
995 .expect("qubits 0 and 1 are adjacent and in range");
996 assert_eq!(mps.bond_dims()[0], 1, "bond should be truncated to χ=1");
997 }
998
999 #[test]
1000 fn t07_expectation_z_matches_statevector() {
1001 let n = 3usize;
1002 let chi = 1usize << n;
1003 let mut mps = MatrixProductState::new_zero_state(cfg(n, chi))
1004 .expect("valid config produces zero state");
1005 let mut sv =
1006 StateVector::new_zero_state(n).expect("valid qubit count produces zero statevector");
1007 let angs = [0.6f32, -1.2, 0.9];
1008 for (q, &a) in angs.iter().enumerate() {
1009 let g = gate_ry(a);
1010 mps.apply_1q(&g, q)
1011 .expect("qubit index q is within the n-qubit MPS");
1012 apply_1q_inplace(&mut sv, q, &g)
1013 .expect("qubit index q is within the n-qubit statevector");
1014 }
1015 for q in 0..n {
1016 let z_mps = mps
1017 .expectation_z(q)
1018 .expect("qubit index q is within the n-qubit MPS");
1019 let mask = 1usize << q;
1021 let z_sv: f32 = sv
1022 .amps
1023 .iter()
1024 .enumerate()
1025 .map(|(i, a)| {
1026 let sign = if i & mask != 0 { -1.0 } else { 1.0 };
1027 sign * a.norm_sqr()
1028 })
1029 .sum();
1030 assert!((z_mps - z_sv).abs() < 1e-4, "q={q} mps={z_mps} sv={z_sv}");
1031 }
1032 }
1033
1034 #[test]
1035 fn t08_unitary_gates_preserve_norm() {
1036 let mut mps = MatrixProductState::new_zero_state(cfg(3, 8))
1037 .expect("n=3 and chi=8 are valid MPS configuration parameters");
1038 mps.apply_1q(&gate_h(), 0)
1039 .expect("qubit 0 is within the 3-qubit MPS range");
1040 mps.apply_2q(&cnot_4x4(), 0, 1)
1041 .expect("qubits 0 and 1 are adjacent and both within the 3-qubit MPS range");
1042 mps.apply_1q(&gate_ry(0.8), 2)
1043 .expect("qubit 2 is within the 3-qubit MPS range");
1044 mps.apply_2q(&cnot_4x4(), 1, 2)
1045 .expect("qubits 1 and 2 are adjacent and both within the 3-qubit MPS range");
1046 let nrm = mps.norm();
1047 assert!((nrm - 1.0).abs() < 1e-4, "norm={nrm}");
1048 }
1049
1050 #[test]
1051 fn t09_svd_reconstruction() {
1052 let m = 4usize;
1054 let n = 3usize;
1055 let mut mat = vec![c0(); m * n];
1056 let mut rng = LcgRng::new(99);
1057 for v in &mut mat {
1058 *v = c(rng.next_f32() - 0.5, rng.next_f32() - 0.5);
1059 }
1060 let svd = svd_dense(&mat, m, n)
1061 .expect("mat has exactly m*n=12 elements and m=4, n=3 are both positive");
1062 let k = svd.singular_values.len();
1063 let mut recon = vec![c0(); m * n];
1065 for i in 0..m {
1066 for j in 0..n {
1067 let mut acc = c0();
1068 for t in 0..k {
1069 acc += svd.u[i * k + t]
1070 * Complex32::new(svd.singular_values[t], 0.0)
1071 * svd.v[j * k + t].conj();
1072 }
1073 recon[i * n + j] = acc;
1074 }
1075 }
1076 let diff = max_abs_diff(&recon, &mat);
1077 assert!(diff < 1e-4, "reconstruction diff {diff}");
1078 for w in svd.singular_values.windows(2) {
1080 assert!(w[0] >= w[1] - 1e-6);
1081 assert!(w[1] >= -1e-6);
1082 }
1083 }
1084
1085 #[test]
1086 fn t10_svd_reconstruction_wide() {
1087 let m = 2usize;
1089 let n = 5usize;
1090 let mut mat = vec![c0(); m * n];
1091 let mut rng = LcgRng::new(7);
1092 for v in &mut mat {
1093 *v = c(rng.next_f32() - 0.5, rng.next_f32() - 0.5);
1094 }
1095 let svd = svd_dense(&mat, m, n)
1096 .expect("mat has exactly m*n=10 elements and m=2, n=5 are both positive");
1097 let k = svd.singular_values.len();
1098 let mut recon = vec![c0(); m * n];
1099 for i in 0..m {
1100 for j in 0..n {
1101 let mut acc = c0();
1102 for t in 0..k {
1103 acc += svd.u[i * k + t]
1104 * Complex32::new(svd.singular_values[t], 0.0)
1105 * svd.v[j * k + t].conj();
1106 }
1107 recon[i * n + j] = acc;
1108 }
1109 }
1110 let diff = max_abs_diff(&recon, &mat);
1111 assert!(diff < 1e-4, "wide reconstruction diff {diff}");
1112 }
1113
1114 #[test]
1115 fn t11_non_adjacent_apply_2q_errors() {
1116 let mut mps = MatrixProductState::new_zero_state(cfg(3, 4))
1117 .expect("n=3 and chi=4 are valid MPS configuration parameters");
1118 assert!(mps.apply_2q(&cnot_4x4(), 0, 2).is_err());
1119 }
1120
1121 #[test]
1122 fn t12_bond_never_exceeds_chi() {
1123 let n = 6usize;
1124 let chi = 3usize;
1125 let mut mps = MatrixProductState::new_zero_state(cfg(n, chi))
1126 .expect("n=6 and chi=3 are valid MPS configuration parameters");
1127 for q in 0..n {
1128 mps.apply_1q(&gate_h(), q)
1129 .expect("q iterates over 0..n so it is always within the n-qubit MPS range");
1130 }
1131 for q in 0..(n - 1) {
1132 mps.apply_2q(&cnot_4x4(), q, q + 1)
1133 .expect("q < n-1 so both q and q+1 are within range and adjacent");
1134 mps.apply_2q(&cnot_4x4(), q, q + 1)
1135 .expect("q < n-1 so both q and q+1 are within range and adjacent");
1136 }
1137 for b in mps.bond_dims() {
1138 assert!(b <= chi, "bond {b} exceeds χ={chi}");
1139 }
1140 }
1141
1142 #[test]
1143 fn t13_product_state_bonds_all_one() {
1144 let mps = MatrixProductState::new_zero_state(cfg(5, 4))
1145 .expect("n=5 and chi=4 are valid MPS configuration parameters");
1146 for b in mps.bond_dims() {
1147 assert_eq!(b, 1);
1148 }
1149 }
1150
1151 #[test]
1152 fn t14_out_of_range_qubit_errors() {
1153 let mut mps = MatrixProductState::new_zero_state(cfg(2, 4))
1154 .expect("n=2 and chi=4 are valid MPS configuration parameters");
1155 assert!(mps.apply_1q(&gate_x(), 5).is_err());
1156 assert!(mps.expectation_z(9).is_err());
1157 }
1158
1159 #[test]
1160 fn t15_zero_qubits_config_errors() {
1161 assert!(MpsConfig::new(0, 4, 1e-12).is_err());
1162 assert!(MpsConfig::new(2, 0, 1e-12).is_err());
1163 }
1164
1165 #[test]
1166 fn t16_normalize_restores_unit_norm() {
1167 let mut mps = MatrixProductState::new_zero_state(cfg(2, 4))
1168 .expect("n=2 and chi=4 are valid MPS configuration parameters");
1169 mps.apply_1q(&gate_h(), 0)
1170 .expect("qubit 0 is within the 2-qubit MPS range");
1171 if let Some(first) = mps.sites.first_mut() {
1173 for v in &mut first.data {
1174 *v *= 3.0;
1175 }
1176 }
1177 assert!((mps.norm() - 3.0).abs() < 1e-4);
1178 mps.normalize();
1179 assert!((mps.norm() - 1.0).abs() < 1e-4, "norm={}", mps.norm());
1180 }
1181
1182 #[test]
1183 fn t17_expectation_z_on_basis_states() {
1184 let mut mps = MatrixProductState::new_zero_state(cfg(1, 2))
1186 .expect("n=1 and chi=2 are valid MPS configuration parameters");
1187 assert!(
1188 (mps.expectation_z(0)
1189 .expect("qubit 0 is the only qubit in this 1-qubit MPS so it is always in range")
1190 - 1.0)
1191 .abs()
1192 < 1e-5
1193 );
1194 mps.apply_1q(&gate_x(), 0)
1195 .expect("qubit 0 is the only qubit in this 1-qubit MPS so it is always in range");
1196 assert!(
1197 (mps.expectation_z(0)
1198 .expect("qubit 0 is the only qubit in this 1-qubit MPS so it is always in range")
1199 + 1.0)
1200 .abs()
1201 < 1e-5
1202 );
1203 }
1204
1205 #[test]
1206 fn t18_gate_z_phase_then_statevector() {
1207 let mut mps = MatrixProductState::new_zero_state(cfg(2, 4))
1209 .expect("n=2 and chi=4 are valid MPS configuration parameters");
1210 let mut sv = StateVector::new_zero_state(2)
1211 .expect("n=2 is a valid positive qubit count for a statevector");
1212 mps.apply_1q(&gate_h(), 1)
1213 .expect("qubit 1 is within the 2-qubit MPS range");
1214 apply_1q_inplace(&mut sv, 1, &gate_h())
1215 .expect("qubit 1 is within the 2-qubit statevector range");
1216 mps.apply_1q(&gate_z(), 1)
1217 .expect("qubit 1 is within the 2-qubit MPS range");
1218 apply_1q_inplace(&mut sv, 1, &gate_z())
1219 .expect("qubit 1 is within the 2-qubit statevector range");
1220 let mps_sv = mps
1221 .to_statevector()
1222 .expect("MPS was constructed with valid boundary bonds so contraction must succeed");
1223 let diff = max_abs_diff(&mps_sv.amps, &sv.amps);
1224 assert!(diff < 1e-5, "diff={diff}");
1225 }
1226}