1#![allow(clippy::should_implement_trait, clippy::needless_range_loop, clippy::manual_range_contains)]
23
24use crate::quantum::{BaseGate, Circuit, Gate, StateVector};
25
26pub use crate::linalg::C;
27use crate::linalg::jacobi_svd;
28
29pub struct Mps {
36 pub n: u8,
37 a: Vec<Vec<C>>,
38 dl: Vec<usize>,
39 dr: Vec<usize>,
40 chi_max: usize,
41 max_bond: usize,
42 retained: f64, }
44
45#[inline]
46fn idx(l: usize, s: usize, r: usize, dr: usize) -> usize {
47 (l * 2 + s) * dr + r
48}
49
50impl Mps {
51 pub fn zero(n: u8, chi_max: usize) -> Mps {
53 let a: Vec<Vec<C>> = (0..n).map(|_| vec![C::ONE, C::ZERO]).collect(); Mps {
55 n,
56 a,
57 dl: vec![1; n as usize],
58 dr: vec![1; n as usize],
59 chi_max: chi_max.max(1),
60 max_bond: 1,
61 retained: 1.0,
62 }
63 }
64
65 fn apply1(&mut self, i: usize, g: [[C; 2]; 2]) {
67 let (dl, dr) = (self.dl[i], self.dr[i]);
68 let ai = &mut self.a[i];
69 for l in 0..dl {
70 for r in 0..dr {
71 let a0 = ai[idx(l, 0, r, dr)];
72 let a1 = ai[idx(l, 1, r, dr)];
73 ai[idx(l, 0, r, dr)] = g[0][0].mul(a0).add(g[0][1].mul(a1));
74 ai[idx(l, 1, r, dr)] = g[1][0].mul(a0).add(g[1][1].mul(a1));
75 }
76 }
77 }
78
79 fn apply2_adjacent(&mut self, i: usize, g: [[C; 4]; 4]) {
82 let (dl, dc, dr) = (self.dl[i], self.dr[i], self.dr[i + 1]);
83 debug_assert_eq!(dc, self.dl[i + 1]);
84 let rows = dl * 2;
88 let cols = 2 * dr;
89 let mut mcols: Vec<Vec<C>> = vec![vec![C::ZERO; rows]; cols];
90 for l in 0..dl {
91 for r in 0..dr {
92 let mut th = [C::ZERO; 4];
94 for s1 in 0..2 {
95 for s2 in 0..2 {
96 let mut acc = C::ZERO;
97 for c in 0..dc {
98 acc = acc.add(self.a[i][idx(l, s1, c, dc)].mul(self.a[i + 1][idx(c, s2, r, dr)]));
99 }
100 th[s1 * 2 + s2] = acc;
101 }
102 }
103 for t1 in 0..2 {
104 for t2 in 0..2 {
105 let mut val = C::ZERO;
106 for s in 0..4 {
107 val = val.add(g[t1 * 2 + t2][s].mul(th[s]));
108 }
109 let row = l * 2 + t1;
110 let col = t2 * dr + r;
111 mcols[col][row] = val;
112 }
113 }
114 }
115 }
116 let (u, s, v) = jacobi_svd(mcols, rows, cols);
117 let total: f64 = s.iter().map(|x| x * x).sum();
119 let mut chi = 0;
120 for (k, sv) in s.iter().enumerate() {
121 if k >= self.chi_max || *sv <= 1e-12 * s[0].max(1e-300) {
122 break;
123 }
124 chi += 1;
125 }
126 let chi = chi.max(1).min(s.len());
127 let kept: f64 = s.iter().take(chi).map(|x| x * x).sum();
128 if total > 0.0 {
129 self.retained *= kept / total;
130 }
131 self.max_bond = self.max_bond.max(chi);
132 let mut na = vec![C::ZERO; dl * 2 * chi];
134 for l in 0..dl {
135 for t1 in 0..2 {
136 for k in 0..chi {
137 na[idx(l, t1, k, chi)] = u[k][l * 2 + t1];
138 }
139 }
140 }
141 let mut nb = vec![C::ZERO; chi * 2 * dr];
143 for k in 0..chi {
144 for t2 in 0..2 {
145 for r in 0..dr {
146 nb[idx(k, t2, r, dr)] = v[k][t2 * dr + r].conj().scale(s[k]);
147 }
148 }
149 }
150 self.a[i] = na;
151 self.dr[i] = chi;
152 self.a[i + 1] = nb;
153 self.dl[i + 1] = chi;
154 }
155
156 fn swap_adjacent(&mut self, i: usize) {
158 self.apply2_adjacent(i, swap_gate());
159 }
160
161 fn apply2(&mut self, a: usize, b: usize, g4: [[C; 4]; 4]) {
164 let (lo, hi) = (a.min(b), a.max(b));
165 if hi == lo + 1 {
166 self.apply2_adjacent(lo, g4);
167 return;
168 }
169 for k in (lo + 1..hi).rev() {
171 self.swap_adjacent(k);
172 }
173 self.apply2_adjacent(lo, g4);
174 for k in lo + 1..hi {
175 self.swap_adjacent(k);
176 }
177 }
178
179 pub fn to_statevector(&self) -> Vec<C> {
182 let dim = 1usize << self.n;
183 let mut out = vec![C::ZERO; dim];
184 for k in 0..dim {
185 let mut vec = vec![C::ONE]; for i in 0..self.n as usize {
188 let s = (k >> i) & 1;
189 let (dl, dr) = (self.dl[i], self.dr[i]);
190 let mut nv = vec![C::ZERO; dr];
191 for r in 0..dr {
192 let mut acc = C::ZERO;
193 for l in 0..dl {
194 acc = acc.add(vec[l].mul(self.a[i][idx(l, s, r, dr)]));
195 }
196 nv[r] = acc;
197 }
198 vec = nv;
199 }
200 out[k] = vec[0];
201 }
202 let norm: f64 = out.iter().map(|c| c.norm2()).sum::<f64>().sqrt();
204 if norm > 0.0 {
205 for c in out.iter_mut() {
206 *c = c.scale(1.0 / norm);
207 }
208 }
209 out
210 }
211
212 pub fn max_bond(&self) -> usize {
213 self.max_bond
214 }
215 pub fn retained_weight(&self) -> f64 {
221 self.retained
222 }
223}
224
225fn m1(base: BaseGate, param: u16) -> [[C; 2]; 2] {
230 let s = std::f64::consts::FRAC_1_SQRT_2;
231 let z = C::ZERO;
232 let o = C::ONE;
233 let i = C::new(0.0, 1.0);
234 let ni = C::new(0.0, -1.0);
235 let no = C::new(-1.0, 0.0);
236 let phase = |k: u16| {
237 let th = 2.0 * std::f64::consts::PI / (1u64 << k) as f64;
238 let (s, c) = crate::repro::sin_cos(th);
239 C::new(c, s)
240 };
241 match base {
242 BaseGate::I => [[o, z], [z, o]],
243 BaseGate::X => [[z, o], [o, z]],
244 BaseGate::Y => [[z, ni], [i, z]],
245 BaseGate::Z => [[o, z], [z, no]],
246 BaseGate::H => [[C::new(s, 0.0), C::new(s, 0.0)], [C::new(s, 0.0), C::new(-s, 0.0)]],
247 BaseGate::S => [[o, z], [z, i]],
248 BaseGate::Sdg => [[o, z], [z, ni]],
249 BaseGate::T => [[o, z], [z, phase(3)]],
250 BaseGate::Tdg => [[o, z], [z, phase(3).conj()]],
251 BaseGate::P => [[o, z], [z, phase(param)]],
252 }
253}
254
255fn controlled4(control_is_lo: bool, u: [[C; 2]; 2]) -> [[C; 4]; 4] {
258 let mut g = [[C::ZERO; 4]; 4];
259 for a in 0..2 {
260 for b in 0..2 {
261 let inb = a * 2 + b;
262 let (ctrl, tgt) = if control_is_lo { (a, b) } else { (b, a) };
263 if ctrl == 0 {
264 g[inb][inb] = C::ONE; } else {
266 for tt in 0..2 {
268 let (oa, ob) = if control_is_lo { (a, tt) } else { (tt, b) };
269 let out = oa * 2 + ob;
270 g[out][inb] = u[tt][tgt];
271 }
272 }
273 }
274 }
275 g
276}
277
278fn swap_gate() -> [[C; 4]; 4] {
279 let mut g = [[C::ZERO; 4]; 4];
280 g[0][0] = C::ONE;
281 g[3][3] = C::ONE;
282 g[1][2] = C::ONE; g[2][1] = C::ONE;
284 g
285}
286
287fn decompose_cc(g: &Gate) -> Option<Vec<Gate>> {
290 if g.controls.len() != 2 {
291 return None;
292 }
293 let (a, b, c) = (g.controls[0], g.controls[1], g.target);
294 let g1 = |base, t| Gate { base, controls: vec![], target: t, param: 0 };
295 let cx = |ctrl, t| Gate { base: BaseGate::X, controls: vec![ctrl], target: t, param: 0 };
296 let mut ops = Vec::new();
297 if g.base == BaseGate::Z {
298 ops.push(g1(BaseGate::H, c));
299 }
300 ops.push(g1(BaseGate::H, c));
302 ops.push(cx(b, c));
303 ops.push(g1(BaseGate::Tdg, c));
304 ops.push(cx(a, c));
305 ops.push(g1(BaseGate::T, c));
306 ops.push(cx(b, c));
307 ops.push(g1(BaseGate::Tdg, c));
308 ops.push(cx(a, c));
309 ops.push(g1(BaseGate::T, b));
310 ops.push(g1(BaseGate::T, c));
311 ops.push(cx(a, b));
312 ops.push(g1(BaseGate::H, c));
313 ops.push(g1(BaseGate::T, a));
314 ops.push(g1(BaseGate::Tdg, b));
315 ops.push(cx(a, b));
316 if g.base == BaseGate::Z {
317 ops.push(g1(BaseGate::H, c));
318 }
319 Some(ops)
320}
321
322pub struct MpsRun {
324 pub max_bond: usize,
325 pub retained_weight: f64,
326 pub fidelity_vs_dense: Option<f64>,
329}
330
331#[derive(Debug)]
333pub enum MpsError {
334 Unsupported(String),
336}
337
338pub fn run(circuit: &Circuit, chi_max: usize) -> Result<(Mps, MpsRun), MpsError> {
341 let mut mps = Mps::zero(circuit.n_qubits, chi_max);
342 for g in &circuit.ops {
343 apply_gate(&mut mps, g)?;
344 }
345 let run = MpsRun {
346 max_bond: mps.max_bond(),
347 retained_weight: mps.retained_weight(),
348 fidelity_vs_dense: None,
349 };
350 Ok((mps, run))
351}
352
353fn apply_gate(mps: &mut Mps, g: &Gate) -> Result<(), MpsError> {
354 match g.controls.len() {
355 0 => mps.apply1(g.target as usize, m1(g.base, g.param)),
356 1 => {
357 let ctrl = g.controls[0] as usize;
358 let tgt = g.target as usize;
359 let control_is_lo = ctrl < tgt;
360 let g4 = controlled4(control_is_lo, m1(g.base, g.param));
361 mps.apply2(ctrl, tgt, g4);
362 }
363 2 => {
364 let ops = decompose_cc(g)
365 .ok_or_else(|| MpsError::Unsupported(format!("{:?} with 2 controls", g.base)))?;
366 for sub in &ops {
367 apply_gate(mps, sub)?;
368 }
369 }
370 k => return Err(MpsError::Unsupported(format!("{k} controls"))),
371 }
372 Ok(())
373}
374
375pub fn fidelity(mps: &Mps, dense: &StateVector) -> f64 {
378 let a = mps.to_statevector();
379 let scale = (1u64 << crate::quantum::FRAC) as f64;
380 let mut overlap = C::ZERO;
381 let mut nd = 0.0;
382 for (k, amp) in dense.amps.iter().enumerate() {
383 let d = C::new(amp.re as f64 / scale, amp.im as f64 / scale);
384 overlap = overlap.add(a[k].conj().mul(d));
385 nd += d.norm2();
386 }
387 if nd <= 0.0 {
388 return 0.0;
389 }
390 overlap.norm2() / nd }
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 fn approx(a: f64, b: f64, tol: f64) -> bool {
398 (a - b).abs() < tol
399 }
400 fn ghz(n: u8) -> Circuit {
401 let mut c = Circuit::new(n);
402 c.h(0);
403 for i in 1..n {
404 c.cx(i - 1, i);
405 }
406 c
407 }
408 fn bell() -> Circuit {
409 let mut c = Circuit::new(2);
410 c.h(0);
411 c.cx(0, 1);
412 c
413 }
414 fn entangling(n: u8) -> Circuit {
416 let mut c = Circuit::new(n);
417 for _ in 0..3 {
418 for q in 0..n {
419 c.h(q);
420 c.t(q);
421 }
422 let mut q = 0;
423 while q + 1 < n {
424 c.cx(q, q + 1);
425 q += 2;
426 }
427 let mut q = 1;
428 while q + 1 < n {
429 c.cx(q, q + 1);
430 q += 2;
431 }
432 }
433 c
434 }
435
436 #[test]
437 fn product_state_stays_bond_one() {
438 let mut c = Circuit::new(5);
440 for q in 0..5 {
441 c.h(q);
442 }
443 let (mps, run) = run(&c, 16).unwrap();
444 assert_eq!(run.max_bond, 1, "product state must stay bond-1");
445 assert!(approx(fidelity(&mps, &c.simulate().unwrap()), 1.0, 1e-9));
446 }
447
448 #[test]
449 fn ghz_is_exact_at_bond_two() {
450 let c = ghz(6);
451 let (mps, run) = run(&c, 8).unwrap();
452 assert_eq!(run.max_bond, 2, "GHZ is a bond-2 state");
453 assert!(approx(fidelity(&mps, &c.simulate().unwrap()), 1.0, 1e-9), "GHZ must be exact");
454 }
455
456 #[test]
457 fn bell_and_small_circuits_match_dense() {
458 for c in [bell(), ghz(4)] {
459 let (mps, _) = run(&c, 16).unwrap();
460 let f = fidelity(&mps, &c.simulate().unwrap());
461 assert!(approx(f, 1.0, 1e-8), "MPS should match dense, got {f}");
462 }
463 }
464
465 #[test]
466 fn qft_matches_dense_at_full_bond() {
467 let c = Circuit::qft(6);
469 let (mps, _) = run(&c, 64).unwrap();
470 let f = fidelity(&mps, &c.simulate().unwrap());
471 assert!(approx(f, 1.0, 1e-7), "QFT full-bond fidelity {f}");
472 }
473
474 #[test]
475 fn ccz_decomposition_matches_dense() {
476 let mut c = Circuit::new(3);
479 c.h(0);
480 c.h(1);
481 c.h(2);
482 c.ops.push(Gate { base: BaseGate::Z, controls: vec![0, 1], target: 2, param: 0 });
483 let (mps, _) = run(&c, 32).unwrap();
484 let f = fidelity(&mps, &c.simulate().unwrap());
485 assert!(approx(f, 1.0, 1e-6), "CCZ decomposition fidelity {f}");
486 }
487
488 #[test]
489 fn nonadjacent_two_qubit_gate() {
490 let mut c = Circuit::new(5);
492 c.h(0);
493 c.cx(0, 4);
494 let (mps, _) = run(&c, 8).unwrap();
495 assert!(approx(fidelity(&mps, &c.simulate().unwrap()), 1.0, 1e-8));
496 }
497
498 #[test]
499 fn truncation_is_measured_not_hidden() {
500 let c = entangling(6);
504 let dense = c.simulate().unwrap();
505 let (mps, st) = run(&c, 2).unwrap();
506 assert!(st.retained_weight < 0.999, "squeezed bond must report lost weight: {}", st.retained_weight);
507 assert!(st.retained_weight > 0.0, "some weight is kept");
508 assert!(st.max_bond <= 2, "bond cap respected");
509 assert!(fidelity(&mps, &dense) < 0.999, "aggressive truncation must measurably reduce fidelity");
510 let (mps2, st2) = run(&c, 8).unwrap();
512 assert!(approx(st2.retained_weight, 1.0, 1e-6), "full bond keeps all weight: {}", st2.retained_weight);
513 assert!(approx(fidelity(&mps2, &dense), 1.0, 1e-6));
514 }
515
516 #[test]
517 fn deterministic() {
518 let c = Circuit::qft(6);
519 let a = run(&c, 8).unwrap().0.to_statevector();
520 let b = run(&c, 8).unwrap().0.to_statevector();
521 assert!(a.iter().zip(&b).all(|(x, y)| x.re.to_bits() == y.re.to_bits() && x.im.to_bits() == y.im.to_bits()),
522 "MPS must be bit-reproducible");
523 }
524}