Skip to main content

quantrs2_sim/
mps_simulator.rs

1//! Matrix Product State (MPS) quantum simulator
2//!
3//! This module implements an efficient quantum simulator using the Matrix Product State
4//! representation, which is particularly effective for simulating quantum systems with
5//! limited entanglement.
6
7use quantrs2_circuit::builder::{Circuit, Simulator};
8use quantrs2_core::{
9    error::{QuantRS2Error, QuantRS2Result},
10    gate::GateOp,
11    prelude::QubitId,
12    register::Register,
13};
14use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView2};
15use scirs2_core::Complex64;
16
17/// MPS tensor for a single qubit
18#[derive(Debug, Clone)]
19struct MPSTensor {
20    /// The tensor data: `left_bond` x physical x `right_bond`
21    data: Array3<Complex64>,
22    /// Left bond dimension
23    left_dim: usize,
24    /// Right bond dimension
25    right_dim: usize,
26}
27
28impl MPSTensor {
29    /// Create a new MPS tensor
30    fn new(data: Array3<Complex64>) -> Self {
31        let shape = data.shape();
32        Self {
33            left_dim: shape[0],
34            right_dim: shape[2],
35            data,
36        }
37    }
38
39    /// Create initial tensor for |0> state
40    fn zero_state(is_first: bool, is_last: bool) -> Self {
41        let data = if is_first && is_last {
42            // Single qubit: 1x2x1 tensor
43            let mut tensor = Array3::zeros((1, 2, 1));
44            tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
45            tensor
46        } else if is_first {
47            // First qubit: 1x2xD tensor
48            let mut tensor = Array3::zeros((1, 2, 2));
49            tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
50            tensor
51        } else if is_last {
52            // Last qubit: Dx2x1 tensor
53            let mut tensor = Array3::zeros((2, 2, 1));
54            tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
55            tensor
56        } else {
57            // Middle qubit: Dx2xD tensor
58            let mut tensor = Array3::zeros((2, 2, 2));
59            tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
60            tensor
61        };
62        Self::new(data)
63    }
64}
65
66/// Matrix Product State representation of a quantum state
67pub struct MPS {
68    /// MPS tensors for each qubit
69    tensors: Vec<MPSTensor>,
70    /// Number of qubits
71    num_qubits: usize,
72    /// Maximum allowed bond dimension
73    max_bond_dim: usize,
74    /// SVD truncation threshold
75    truncation_threshold: f64,
76    /// Current orthogonality center (-1 if not in canonical form)
77    orthogonality_center: i32,
78}
79
80impl MPS {
81    /// Create a new MPS in the |0...0> state
82    #[must_use]
83    pub fn new(num_qubits: usize, max_bond_dim: usize) -> Self {
84        let tensors = (0..num_qubits)
85            .map(|i| MPSTensor::zero_state(i == 0, i == num_qubits - 1))
86            .collect();
87
88        Self {
89            tensors,
90            num_qubits,
91            max_bond_dim,
92            truncation_threshold: 1e-10,
93            orthogonality_center: -1,
94        }
95    }
96
97    /// Set the truncation threshold for SVD
98    pub const fn set_truncation_threshold(&mut self, threshold: f64) {
99        self.truncation_threshold = threshold;
100    }
101
102    /// Move orthogonality center to specified position
103    pub fn move_orthogonality_center(&mut self, target: usize) -> QuantRS2Result<()> {
104        if target >= self.num_qubits {
105            return Err(QuantRS2Error::InvalidQubitId(target as u32));
106        }
107
108        // If no current center, canonicalize from left
109        if self.orthogonality_center < 0 {
110            self.left_canonicalize_up_to(target)?;
111            self.orthogonality_center = target as i32;
112            return Ok(());
113        }
114
115        let current = self.orthogonality_center as usize;
116
117        if current < target {
118            // Move right
119            for i in current..target {
120                self.move_center_right(i)?;
121            }
122        } else if current > target {
123            // Move left
124            for i in (target + 1..=current).rev() {
125                self.move_center_left(i)?;
126            }
127        }
128
129        self.orthogonality_center = target as i32;
130        Ok(())
131    }
132
133    /// Left-canonicalize tensors up to position
134    fn left_canonicalize_up_to(&mut self, position: usize) -> QuantRS2Result<()> {
135        for i in 0..position {
136            let tensor = &self.tensors[i];
137            let (left_dim, phys_dim, right_dim) = (tensor.left_dim, 2, tensor.right_dim);
138
139            // Reshape to matrix for QR decomposition
140            let matrix = tensor
141                .data
142                .view()
143                .into_shape((left_dim * phys_dim, right_dim))?;
144
145            // QR decomposition
146            let (q, r) = qr_decomposition(&matrix)?;
147
148            // Update current tensor with Q
149            let new_shape = (left_dim, phys_dim, q.shape()[1]);
150            self.tensors[i].data = q.into_shape(new_shape)?;
151            self.tensors[i].right_dim = new_shape.2;
152
153            // Absorb R into next tensor
154            if i + 1 < self.num_qubits {
155                let next = &mut self.tensors[i + 1];
156                let next_matrix = next
157                    .data
158                    .view()
159                    .into_shape((next.left_dim, 2 * next.right_dim))?;
160                let new_matrix = r.dot(&next_matrix);
161                next.data = new_matrix.into_shape((r.shape()[0], 2, next.right_dim))?;
162                next.left_dim = r.shape()[0];
163            }
164        }
165        Ok(())
166    }
167
168    /// Move orthogonality center one position to the right
169    fn move_center_right(&mut self, position: usize) -> QuantRS2Result<()> {
170        let tensor = &self.tensors[position];
171        let (left_dim, phys_dim, right_dim) = (tensor.left_dim, 2, tensor.right_dim);
172
173        // Reshape and QR decompose
174        let matrix = tensor
175            .data
176            .view()
177            .into_shape((left_dim * phys_dim, right_dim))?;
178        let (q, r) = qr_decomposition(&matrix)?;
179
180        // Update current tensor
181        let q_cols = q.shape()[1];
182        self.tensors[position].data = q.into_shape((left_dim, phys_dim, q_cols))?;
183        self.tensors[position].right_dim = q_cols;
184
185        // Update next tensor
186        if position + 1 < self.num_qubits {
187            let next = &mut self.tensors[position + 1];
188            let next_matrix = next
189                .data
190                .view()
191                .into_shape((next.left_dim, 2 * next.right_dim))?;
192            let new_matrix = r.dot(&next_matrix);
193            next.data = new_matrix.into_shape((r.shape()[0], 2, next.right_dim))?;
194            next.left_dim = r.shape()[0];
195        }
196
197        Ok(())
198    }
199
200    /// Move orthogonality center one position to the left
201    fn move_center_left(&mut self, position: usize) -> QuantRS2Result<()> {
202        let tensor = &self.tensors[position];
203        let (left_dim, phys_dim, right_dim) = (tensor.left_dim, 2, tensor.right_dim);
204
205        // Reshape and QR decompose from right
206        let matrix = tensor
207            .data
208            .view()
209            .permuted_axes([2, 1, 0])
210            .into_shape((right_dim * phys_dim, left_dim))?;
211        let (q, r) = qr_decomposition(&matrix)?;
212
213        // Update current tensor
214        let q_cols = q.shape()[1];
215        let q_reshaped = q.into_shape((right_dim, phys_dim, q_cols))?;
216        self.tensors[position].data = q_reshaped.permuted_axes([2, 1, 0]);
217        self.tensors[position].left_dim = q_cols;
218
219        // Update previous tensor
220        if position > 0 {
221            let prev = &mut self.tensors[position - 1];
222            let prev_matrix = prev
223                .data
224                .view()
225                .into_shape((prev.left_dim * 2, prev.right_dim))?;
226            let new_matrix = prev_matrix.dot(&r.t());
227            prev.data = new_matrix.into_shape((prev.left_dim, 2, r.shape()[0]))?;
228            prev.right_dim = r.shape()[0];
229        }
230
231        Ok(())
232    }
233
234    /// Apply single-qubit gate
235    pub fn apply_single_qubit_gate(
236        &mut self,
237        gate: &dyn GateOp,
238        qubit: usize,
239    ) -> QuantRS2Result<()> {
240        if qubit >= self.num_qubits {
241            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
242        }
243
244        // Get gate matrix
245        let gate_matrix = gate.matrix()?;
246        let gate_array = Array2::from_shape_vec((2, 2), gate_matrix)?;
247
248        // Apply gate to tensor
249        let tensor = &mut self.tensors[qubit];
250        let mut new_data = Array3::zeros(tensor.data.dim());
251
252        for left in 0..tensor.left_dim {
253            for right in 0..tensor.right_dim {
254                for i in 0..2 {
255                    for j in 0..2 {
256                        new_data[[left, i, right]] +=
257                            gate_array[[i, j]] * tensor.data[[left, j, right]];
258                    }
259                }
260            }
261        }
262
263        tensor.data = new_data;
264        Ok(())
265    }
266
267    /// Apply two-qubit gate using SVD compression
268    pub fn apply_two_qubit_gate(
269        &mut self,
270        gate: &dyn GateOp,
271        qubit1: usize,
272        qubit2: usize,
273    ) -> QuantRS2Result<()> {
274        // Ensure qubits are adjacent
275        if (qubit1 as i32 - qubit2 as i32).abs() != 1 {
276            return Err(QuantRS2Error::ComputationError(
277                "MPS simulator requires adjacent qubits for two-qubit gates".to_string(),
278            ));
279        }
280
281        let (left_qubit, right_qubit) = if qubit1 < qubit2 {
282            (qubit1, qubit2)
283        } else {
284            (qubit2, qubit1)
285        };
286
287        // Move orthogonality center to left qubit
288        self.move_orthogonality_center(left_qubit)?;
289
290        // Get gate matrix
291        let gate_matrix = gate.matrix()?;
292        let gate_array = Array2::from_shape_vec((4, 4), gate_matrix)?;
293
294        // Contract the two tensors
295        let left_tensor = &self.tensors[left_qubit];
296        let right_tensor = &self.tensors[right_qubit];
297
298        let left_dim = left_tensor.left_dim;
299        let right_dim = right_tensor.right_dim;
300
301        // Combine tensors
302        let mut combined = Array3::<Complex64>::zeros((left_dim, 4, right_dim));
303        for l in 0..left_dim {
304            for r in 0..right_dim {
305                for i in 0..2 {
306                    for j in 0..2 {
307                        for k in 0..left_tensor.right_dim {
308                            combined[[l, i * 2 + j, r]] +=
309                                left_tensor.data[[l, i, k]] * right_tensor.data[[k, j, r]];
310                        }
311                    }
312                }
313            }
314        }
315
316        // Apply gate
317        let mut gated = Array3::<Complex64>::zeros((left_dim, 4, right_dim));
318        for l in 0..left_dim {
319            for r in 0..right_dim {
320                for out_idx in 0..4 {
321                    for in_idx in 0..4 {
322                        gated[[l, out_idx, r]] +=
323                            gate_array[[out_idx, in_idx]] * combined[[l, in_idx, r]];
324                    }
325                }
326            }
327        }
328
329        // Decompose back using SVD
330        let matrix = gated.into_shape((left_dim * 2, 2 * right_dim))?;
331        let (u, s, vt) = svd_decomposition(&matrix, self.max_bond_dim, self.truncation_threshold)?;
332
333        // Update tensors
334        let new_bond = s.len();
335        self.tensors[left_qubit].data = u.into_shape((left_dim, 2, new_bond))?;
336        self.tensors[left_qubit].right_dim = new_bond;
337
338        // Convert s to complex diagonal matrix and multiply with vt
339        let mut sv = Array2::<Complex64>::zeros((new_bond, vt.shape()[1]));
340        for i in 0..new_bond {
341            for j in 0..vt.shape()[1] {
342                sv[[i, j]] = Complex64::new(s[i], 0.0) * vt[[i, j]];
343            }
344        }
345        self.tensors[right_qubit].data = sv.t().to_owned().into_shape((new_bond, 2, right_dim))?;
346        self.tensors[right_qubit].left_dim = new_bond;
347
348        self.orthogonality_center = right_qubit as i32;
349
350        Ok(())
351    }
352
353    /// Compute amplitude of a basis state
354    pub fn get_amplitude(&self, bitstring: &[bool]) -> QuantRS2Result<Complex64> {
355        if bitstring.len() != self.num_qubits {
356            return Err(QuantRS2Error::ComputationError(format!(
357                "Bitstring length {} doesn't match qubit count {}",
358                bitstring.len(),
359                self.num_qubits
360            )));
361        }
362
363        // Contract from left to right
364        let mut result = Array2::eye(1);
365
366        for (i, &bit) in bitstring.iter().enumerate() {
367            let tensor = &self.tensors[i];
368            let idx = i32::from(bit);
369
370            // Extract the matrix for this bit value
371            let matrix = tensor.data.slice(s![.., idx, ..]);
372            result = result.dot(&matrix);
373        }
374
375        Ok(result[[0, 0]])
376    }
377
378    /// Sample from the MPS
379    #[must_use]
380    pub fn sample(&self) -> Vec<bool> {
381        use scirs2_core::random::prelude::*;
382        let mut rng = thread_rng();
383        let mut result = vec![false; self.num_qubits];
384        let mut accumulated_matrix = Array2::eye(1);
385
386        for (i, tensor) in self.tensors.iter().enumerate() {
387            // Compute probabilities for this qubit
388            let mut prob0 = Complex64::new(0.0, 0.0);
389            let mut prob1 = Complex64::new(0.0, 0.0);
390
391            // Probability of |0>
392            let matrix0 = tensor.data.slice(s![.., 0, ..]);
393            let temp0: Array2<Complex64> = accumulated_matrix.dot(&matrix0);
394
395            // Contract with remaining tensors
396            let mut right_contract = Array2::eye(temp0.shape()[1]);
397            for j in (i + 1)..self.num_qubits {
398                let sum_matrix = self.tensors[j].data.slice(s![.., 0, ..]).to_owned()
399                    + self.tensors[j].data.slice(s![.., 1, ..]).to_owned();
400                right_contract = right_contract.dot(&sum_matrix);
401            }
402
403            prob0 = temp0.dot(&right_contract)[[0, 0]];
404
405            // Similar for |1>
406            let matrix1 = tensor.data.slice(s![.., 1, ..]);
407            let temp1: Array2<Complex64> = accumulated_matrix.dot(&matrix1);
408            prob1 = temp1.dot(&right_contract)[[0, 0]];
409
410            // Normalize and sample
411            let total = prob0.norm_sqr() + prob1.norm_sqr();
412            let threshold = prob0.norm_sqr() / total;
413
414            if rng.random::<f64>() < threshold {
415                result[i] = false;
416                accumulated_matrix = temp0;
417            } else {
418                result[i] = true;
419                accumulated_matrix = temp1;
420            }
421        }
422
423        result
424    }
425
426    /// Contract the MPS into a dense state vector of `2^n` complex amplitudes.
427    ///
428    /// The amplitudes use the little-endian convention `amplitude[index]` where bit `q`
429    /// of `index` is the computational-basis value of qubit `q`. Each amplitude is the
430    /// genuine MPS contraction `A^{s_0} A^{s_1} ... A^{s_{n-1}}` evaluated as a chain of
431    /// matrix products, so an entangled MPS produces the corresponding entangled
432    /// amplitudes rather than a fabricated product state.
433    fn to_statevector(&self) -> QuantRS2Result<Vec<Complex64>> {
434        let dim = 1usize << self.num_qubits;
435        let mut amplitudes = vec![Complex64::new(0.0, 0.0); dim];
436
437        for (index, amplitude) in amplitudes.iter_mut().enumerate() {
438            // Contract the per-qubit slices selected by the bits of `index`.
439            let mut accumulated: Array2<Complex64> = Array2::eye(1);
440            for qubit in 0..self.num_qubits {
441                let bit = (index >> qubit) & 1;
442                let slice = self.tensors[qubit].data.slice(s![.., bit as i32, ..]);
443                accumulated = accumulated.dot(&slice);
444            }
445            *amplitude = accumulated[[0, 0]];
446        }
447
448        Ok(amplitudes)
449    }
450}
451
452/// QR decomposition helper
453fn qr_decomposition(
454    matrix: &ArrayView2<Complex64>,
455) -> QuantRS2Result<(Array2<Complex64>, Array2<Complex64>)> {
456    // Simple Gram-Schmidt QR decomposition
457    let (m, n) = matrix.dim();
458    let mut q = Array2::zeros((m, n.min(m)));
459    let mut r = Array2::zeros((n.min(m), n));
460
461    for j in 0..n.min(m) {
462        let mut v = matrix.column(j).to_owned();
463
464        // Orthogonalize against previous columns
465        for i in 0..j {
466            let proj = q.column(i).dot(&v);
467            r[[i, j]] = proj;
468            v -= &(proj * &q.column(i).to_owned());
469        }
470
471        let norm = (v.dot(&v)).sqrt();
472        if norm.norm() > 1e-10 {
473            r[[j, j]] = norm;
474            q.column_mut(j).assign(&(v / norm));
475        }
476    }
477
478    // Copy remaining columns of R
479    if n > m {
480        for j in m..n {
481            for i in 0..m {
482                r[[i, j]] = q.column(i).dot(&matrix.column(j));
483            }
484        }
485    }
486
487    Ok((q, r))
488}
489
490/// Full (untruncated) reduced complex SVD `A = U · diag(S) · Vt`.
491///
492/// Implements the one-sided Jacobi SVD algorithm directly on complex matrices. Jacobi
493/// rotations are applied to pairs of columns of a working copy of `A` until all columns
494/// are mutually orthogonal; the resulting column norms are the singular values, the
495/// normalised columns form `U`, and the accumulated rotations form `V` (returned as
496/// `Vt = V^H`). This method is numerically robust for all matrix sizes and converges to
497/// machine precision, unlike eigendecomposition-of-`A^H A` approaches that can yield
498/// non-orthonormal vectors or `NaN` singular values for ill-conditioned inputs.
499///
500/// Returns `(U, S, Vt)` with `U` of shape `(m, r)`, `S` of length `r`, `Vt` of shape
501/// `(r, n)` and `r = min(m, n)`. Singular values are real, non-negative and sorted in
502/// descending order.
503pub(crate) fn complex_jacobi_svd(
504    matrix: &Array2<Complex64>,
505) -> QuantRS2Result<(Array2<Complex64>, Array1<f64>, Array2<Complex64>)> {
506    let (m, n) = matrix.dim();
507    if m == 0 || n == 0 {
508        return Err(QuantRS2Error::ComputationError(
509            "Cannot compute SVD of an empty matrix".to_string(),
510        ));
511    }
512
513    // The one-sided Jacobi method orthogonalises the columns of the matrix with the larger
514    // number of rows. When n > m we transpose, decompose, and swap U and V at the end so
515    // the algorithm always works on a tall-or-square matrix.
516    let transposed = n > m;
517    let mut work = if transposed {
518        // Work on A^H (shape n x m), then U_work plays the role of V and vice versa.
519        let mut ah = Array2::<Complex64>::zeros((n, m));
520        for i in 0..m {
521            for j in 0..n {
522                ah[[j, i]] = matrix[[i, j]].conj();
523            }
524        }
525        ah
526    } else {
527        matrix.clone()
528    };
529
530    let rows = work.nrows();
531    let cols = work.ncols();
532
533    // Accumulate the right rotations into v (cols x cols), starting from the identity.
534    let mut v = Array2::<Complex64>::eye(cols);
535
536    let tolerance = 1e-14_f64;
537    let max_sweeps = 60;
538
539    for _sweep in 0..max_sweeps {
540        let mut off_diagonal = 0.0_f64;
541
542        for p in 0..cols {
543            for q in (p + 1)..cols {
544                // Compute the 2x2 Hermitian block of the column Gram matrix:
545                //   alpha = <col_p, col_p>, beta = <col_q, col_q>, gamma = <col_p, col_q>.
546                let mut alpha = 0.0_f64;
547                let mut beta = 0.0_f64;
548                let mut gamma = Complex64::new(0.0, 0.0);
549                for i in 0..rows {
550                    let cp = work[[i, p]];
551                    let cq = work[[i, q]];
552                    alpha += cp.norm_sqr();
553                    beta += cq.norm_sqr();
554                    gamma += cp.conj() * cq;
555                }
556
557                let gamma_abs = gamma.norm();
558                off_diagonal = off_diagonal.max(gamma_abs);
559                if gamma_abs <= tolerance * (alpha.sqrt() * beta.sqrt()).max(f64::MIN_POSITIVE) {
560                    continue;
561                }
562
563                // Complex one-sided Jacobi rotation. Factor out the phase of gamma so the
564                // remaining 2x2 problem is real-symmetric, then apply the standard Jacobi
565                // angle. The rotation is unitary, preserving the decomposition.
566                let phase = if gamma_abs > 0.0 {
567                    gamma / Complex64::new(gamma_abs, 0.0)
568                } else {
569                    Complex64::new(1.0, 0.0)
570                };
571
572                let zeta = (beta - alpha) / (2.0 * gamma_abs);
573                let sign = if zeta >= 0.0 { 1.0 } else { -1.0 };
574                let t = sign / (zeta.abs() + (zeta * zeta + 1.0).sqrt());
575                let cosine = 1.0 / (t * t + 1.0).sqrt();
576                let sine = cosine * t;
577
578                let c = Complex64::new(cosine, 0.0);
579                let s_pq = phase * Complex64::new(sine, 0.0);
580                let s_pq_conj = s_pq.conj();
581
582                // Rotate columns p and q of the working matrix.
583                for i in 0..rows {
584                    let cp = work[[i, p]];
585                    let cq = work[[i, q]];
586                    work[[i, p]] = c * cp - s_pq_conj * cq;
587                    work[[i, q]] = s_pq * cp + c * cq;
588                }
589                // Apply the same rotation to the accumulated right-singular-vector matrix.
590                for i in 0..cols {
591                    let vp = v[[i, p]];
592                    let vq = v[[i, q]];
593                    v[[i, p]] = c * vp - s_pq_conj * vq;
594                    v[[i, q]] = s_pq * vp + c * vq;
595                }
596            }
597        }
598
599        if off_diagonal <= tolerance {
600            break;
601        }
602    }
603
604    // Column norms of the orthogonalised working matrix are the singular values; the
605    // normalised columns are the left singular vectors.
606    let rank = rows.min(cols);
607    let mut singular = Vec::with_capacity(cols);
608    let mut u_full = Array2::<Complex64>::zeros((rows, cols));
609    for j in 0..cols {
610        let mut norm_sq = 0.0_f64;
611        for i in 0..rows {
612            norm_sq += work[[i, j]].norm_sqr();
613        }
614        let norm = norm_sq.sqrt();
615        singular.push(norm);
616        if norm > tolerance {
617            for i in 0..rows {
618                u_full[[i, j]] = work[[i, j]] / Complex64::new(norm, 0.0);
619            }
620        }
621    }
622
623    // Sort singular values (and corresponding U, V columns) in descending order.
624    let mut order: Vec<usize> = (0..cols).collect();
625    order.sort_by(|&a, &b| {
626        singular[b]
627            .partial_cmp(&singular[a])
628            .unwrap_or(std::cmp::Ordering::Equal)
629    });
630
631    let mut s = Array1::<f64>::zeros(rank);
632    let mut u = Array2::<Complex64>::zeros((rows, rank));
633    let mut v_sorted = Array2::<Complex64>::zeros((cols, rank));
634    for (new_idx, &old_idx) in order.iter().take(rank).enumerate() {
635        s[new_idx] = singular[old_idx];
636        for i in 0..rows {
637            u[[i, new_idx]] = u_full[[i, old_idx]];
638        }
639        for i in 0..cols {
640            v_sorted[[i, new_idx]] = v[[i, old_idx]];
641        }
642    }
643
644    // For columns with (near-)zero singular value the corresponding U column is zero; fill
645    // it with a vector orthonormal to the others so U has orthonormal columns. This keeps
646    // the factorisation well-formed for rank-deficient inputs.
647    for j in 0..rank {
648        if s[j] <= tolerance {
649            let mut candidate = Array1::<Complex64>::zeros(rows);
650            candidate[j % rows] = Complex64::new(1.0, 0.0);
651            for prev in 0..rank {
652                if prev == j {
653                    continue;
654                }
655                let mut proj = Complex64::new(0.0, 0.0);
656                for i in 0..rows {
657                    proj += u[[i, prev]].conj() * candidate[i];
658                }
659                for i in 0..rows {
660                    candidate[i] -= proj * u[[i, prev]];
661                }
662            }
663            let mut norm_sq = 0.0_f64;
664            for i in 0..rows {
665                norm_sq += candidate[i].norm_sqr();
666            }
667            let norm = norm_sq.sqrt();
668            if norm > tolerance {
669                for i in 0..rows {
670                    u[[i, j]] = candidate[i] / Complex64::new(norm, 0.0);
671                }
672            }
673        }
674    }
675
676    if transposed {
677        // We decomposed A^H = U_work · S · V_work^H, hence A = V_work · S · U_work^H.
678        // So the true U is v_sorted and the true Vt is u^H.
679        let true_u = v_sorted;
680        let mut vt = Array2::<Complex64>::zeros((rank, n));
681        for i in 0..rank {
682            for j in 0..n {
683                vt[[i, j]] = u[[j, i]].conj();
684            }
685        }
686        Ok((true_u, s, vt))
687    } else {
688        // A = U · S · V^H, so Vt = V^H.
689        let mut vt = Array2::<Complex64>::zeros((rank, n));
690        for i in 0..rank {
691            for j in 0..n {
692                vt[[i, j]] = v_sorted[[j, i]].conj();
693            }
694        }
695        Ok((u, s, vt))
696    }
697}
698
699/// SVD decomposition with bond-dimension truncation.
700///
701/// Computes a real, complex-valued singular value decomposition `A = U · diag(S) · Vt`
702/// via [`complex_jacobi_svd`] and truncates the bond to keep the largest singular values
703/// (those above `threshold`, capped at `max_bond`). This decomposition controls MPS
704/// entanglement representation: returning anything other than the genuine factorization
705/// silently corrupts every downstream amplitude.
706///
707/// Returns `(U, S, Vt)` where `U` has shape `(m, k)`, `S` is the vector of `k` retained
708/// singular values (real, non-negative, descending) and `Vt` has shape `(k, n)` such that
709/// `U · diag(S) · Vt` reconstructs `A` within numerical tolerance.
710fn svd_decomposition(
711    matrix: &Array2<Complex64>,
712    max_bond: usize,
713    threshold: f64,
714) -> QuantRS2Result<(Array2<Complex64>, Array1<f64>, Array2<Complex64>)> {
715    let (full_u, full_s, full_vt) = complex_jacobi_svd(matrix)?;
716    let full_rank = full_s.len();
717
718    // Determine how many singular values to keep: drop those at or below the truncation
719    // threshold, then cap at the maximum bond dimension. Always keep at least one so the
720    // resulting tensors stay well-formed even for (near-)zero states.
721    let mut kept = full_s.iter().filter(|&&value| value > threshold).count();
722    kept = kept.min(max_bond).min(full_rank);
723    if kept == 0 {
724        kept = full_rank.max(1);
725    }
726
727    let u = full_u.slice(s![.., ..kept]).to_owned();
728    let truncated_s = full_s.slice(s![..kept]).to_owned();
729    let vt = full_vt.slice(s![..kept, ..]).to_owned();
730
731    Ok((u, truncated_s, vt))
732}
733
734/// MPS quantum simulator
735pub struct MPSSimulator {
736    /// Maximum bond dimension
737    max_bond_dimension: usize,
738    /// SVD truncation threshold
739    truncation_threshold: f64,
740}
741
742impl MPSSimulator {
743    /// Create a new MPS simulator
744    #[must_use]
745    pub const fn new(max_bond_dimension: usize) -> Self {
746        Self {
747            max_bond_dimension,
748            truncation_threshold: 1e-10,
749        }
750    }
751
752    /// Set the truncation threshold
753    pub const fn set_truncation_threshold(&mut self, threshold: f64) {
754        self.truncation_threshold = threshold;
755    }
756}
757
758impl<const N: usize> Simulator<N> for MPSSimulator {
759    fn run(&self, circuit: &Circuit<N>) -> QuantRS2Result<Register<N>> {
760        // Create initial MPS state in |0...0>.
761        let mut mps = MPS::new(N, self.max_bond_dimension);
762        mps.set_truncation_threshold(self.truncation_threshold);
763
764        // Apply each circuit gate to the MPS. Single-qubit gates act on the local tensor;
765        // two-qubit gates contract, apply, and re-split via the real SVD truncation. Gates
766        // acting on more than two qubits or on non-adjacent qubits are not representable by
767        // this nearest-neighbour MPS, so we surface an honest error instead of silently
768        // skipping them (which would corrupt the resulting state).
769        for gate in circuit.gates() {
770            let qubits = gate.qubits();
771            match qubits.as_slice() {
772                [target] => {
773                    mps.apply_single_qubit_gate(gate.as_ref(), target.id() as usize)?;
774                }
775                [first, second] => {
776                    mps.apply_two_qubit_gate(
777                        gate.as_ref(),
778                        first.id() as usize,
779                        second.id() as usize,
780                    )?;
781                }
782                _ => {
783                    return Err(QuantRS2Error::UnsupportedOperation(format!(
784                        "MPS simulator supports only one- and two-qubit gates, but '{}' acts on {} qubits",
785                        gate.name(),
786                        qubits.len()
787                    )));
788                }
789            }
790        }
791
792        // Contract the MPS into a dense state vector and build the register from it.
793        let amplitudes = mps.to_statevector()?;
794        Register::<N>::with_amplitudes(amplitudes)
795    }
796}
797
798#[cfg(test)]
799mod tests {
800    use super::*;
801    use quantrs2_core::gate::single::Hadamard;
802
803    #[test]
804    fn test_mps_creation() {
805        let mps = MPS::new(4, 10);
806        assert_eq!(mps.num_qubits, 4);
807        assert_eq!(mps.tensors.len(), 4);
808    }
809
810    #[test]
811    fn test_single_qubit_gate() {
812        let mut mps = MPS::new(1, 10);
813        let h = Hadamard {
814            target: QubitId::new(0),
815        };
816
817        mps.apply_single_qubit_gate(&h, 0)
818            .expect("Failed to apply single qubit gate");
819
820        // Check amplitudes
821        let amp0 = mps
822            .get_amplitude(&[false])
823            .expect("Failed to get amplitude for |0>");
824        let amp1 = mps
825            .get_amplitude(&[true])
826            .expect("Failed to get amplitude for |1>");
827
828        let expected = 1.0 / 2.0_f64.sqrt();
829        assert!((amp0.re - expected).abs() < 1e-10);
830        assert!((amp1.re - expected).abs() < 1e-10);
831    }
832
833    #[test]
834    fn test_orthogonality_center() {
835        let mut mps = MPS::new(5, 10);
836
837        mps.move_orthogonality_center(2)
838            .expect("Failed to move orthogonality center to 2");
839        assert_eq!(mps.orthogonality_center, 2);
840
841        mps.move_orthogonality_center(4)
842            .expect("Failed to move orthogonality center to 4");
843        assert_eq!(mps.orthogonality_center, 4);
844
845        mps.move_orthogonality_center(0)
846            .expect("Failed to move orthogonality center to 0");
847        assert_eq!(mps.orthogonality_center, 0);
848    }
849
850    #[test]
851    fn test_svd_decomposition_reconstructs_matrix() {
852        // A non-trivial, non-Hermitian complex matrix whose SVD is clearly not identity.
853        let matrix = Array2::from_shape_vec(
854            (3, 3),
855            vec![
856                Complex64::new(1.0, 0.5),
857                Complex64::new(2.0, -1.0),
858                Complex64::new(0.0, 3.0),
859                Complex64::new(-1.0, 2.0),
860                Complex64::new(0.5, 0.5),
861                Complex64::new(1.0, 0.0),
862                Complex64::new(2.0, 1.0),
863                Complex64::new(-3.0, 0.0),
864                Complex64::new(0.0, -2.0),
865            ],
866        )
867        .expect("failed to build test matrix");
868
869        let (u, s, vt) =
870            svd_decomposition(&matrix, 16, 1e-14).expect("real SVD decomposition should succeed");
871
872        // Reconstruct U * diag(S) * Vt and compare to the original within tight tolerance.
873        let k = s.len();
874        let mut reconstructed = Array2::<Complex64>::zeros(matrix.dim());
875        for i in 0..matrix.nrows() {
876            for j in 0..matrix.ncols() {
877                let mut acc = Complex64::new(0.0, 0.0);
878                for r in 0..k {
879                    acc += u[[i, r]] * Complex64::new(s[r], 0.0) * vt[[r, j]];
880                }
881                reconstructed[[i, j]] = acc;
882            }
883        }
884
885        let mut max_err = 0.0_f64;
886        for i in 0..matrix.nrows() {
887            for j in 0..matrix.ncols() {
888                max_err = max_err.max((reconstructed[[i, j]] - matrix[[i, j]]).norm());
889            }
890        }
891        assert!(
892            max_err < 1e-8,
893            "SVD reconstruction error too large: {max_err}"
894        );
895
896        // Singular values must be non-negative and sorted descending.
897        for r in 0..k {
898            assert!(s[r] >= -1e-12, "singular value {r} is negative: {}", s[r]);
899            if r > 0 {
900                assert!(s[r - 1] + 1e-12 >= s[r], "singular values not descending");
901            }
902        }
903
904        // U must have orthonormal columns (genuine left singular vectors), proving this is
905        // a real SVD rather than an arbitrary factorization.
906        for a in 0..k {
907            for b in 0..k {
908                let mut inner = Complex64::new(0.0, 0.0);
909                for i in 0..u.nrows() {
910                    inner += u[[i, a]].conj() * u[[i, b]];
911                }
912                let expected = if a == b { 1.0 } else { 0.0 };
913                assert!(
914                    (inner.re - expected).abs() < 1e-8 && inner.im.abs() < 1e-8,
915                    "U columns not orthonormal at ({a},{b}): {inner:?}"
916                );
917            }
918        }
919
920        // The decomposition must NOT be the fabricated identity-like result: at least one
921        // singular value differs from 1 and U is not the identity.
922        assert!(
923            s.iter().any(|&value| (value - 1.0).abs() > 1e-6),
924            "singular values are all ~1 (identity fabrication not fixed)"
925        );
926        let identity = Array2::<Complex64>::eye(u.nrows());
927        let mut differs_from_identity = false;
928        for i in 0..u.nrows() {
929            for j in 0..k {
930                if (u[[i, j]] - identity[[i, j]]).norm() > 1e-6 {
931                    differs_from_identity = true;
932                }
933            }
934        }
935        assert!(
936            differs_from_identity,
937            "U equals identity (identity fabrication not fixed)"
938        );
939    }
940
941    #[test]
942    fn test_to_statevector_bell_state() {
943        // Build a Bell state (|00> + |11>)/sqrt(2) via H on qubit 0 then CNOT(0, 1).
944        let mut mps = MPS::new(2, 16);
945        let h = Hadamard {
946            target: QubitId::new(0),
947        };
948        mps.apply_single_qubit_gate(&h, 0)
949            .expect("failed to apply Hadamard");
950
951        let cnot = quantrs2_core::gate::multi::CNOT {
952            control: QubitId::new(0),
953            target: QubitId::new(1),
954        };
955        mps.apply_two_qubit_gate(&cnot, 0, 1)
956            .expect("failed to apply CNOT");
957
958        let state = mps
959            .to_statevector()
960            .expect("contraction to state vector should succeed");
961
962        let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
963        assert!((state[0].re - inv_sqrt2).abs() < 1e-10, "amp(|00>) wrong");
964        assert!(state[1].norm() < 1e-10, "amp(|01>) should vanish");
965        assert!(state[2].norm() < 1e-10, "amp(|10>) should vanish");
966        assert!((state[3].re - inv_sqrt2).abs() < 1e-10, "amp(|11>) wrong");
967
968        // Honest check: the contracted state is genuinely entangled, NOT the |00> placeholder.
969        let zero_state = [
970            Complex64::new(1.0, 0.0),
971            Complex64::new(0.0, 0.0),
972            Complex64::new(0.0, 0.0),
973            Complex64::new(0.0, 0.0),
974        ];
975        let is_zero_state = state
976            .iter()
977            .zip(zero_state.iter())
978            .all(|(a, b)| (a - b).norm() < 1e-9);
979        assert!(
980            !is_zero_state,
981            "contraction fabricated the |00> placeholder"
982        );
983    }
984}