1use quantrs2_circuit::builder::{Circuit, Simulator};
6use quantrs2_core::{
7 error::{QuantRS2Error, QuantRS2Result},
8 gate::GateOp,
9 register::Register,
10};
11use scirs2_core::ndarray::{array, s, Array1, Array2, Array3};
12use scirs2_core::random::prelude::*;
13use scirs2_core::random::{thread_rng, Rng};
14use scirs2_core::Complex64;
15use std::f64::consts::SQRT_2;
16
17#[derive(Debug, Clone)]
19pub struct BasicMPSConfig {
20 pub max_bond_dim: usize,
22 pub svd_threshold: f64,
24}
25
26impl Default for BasicMPSConfig {
27 fn default() -> Self {
28 Self {
29 max_bond_dim: 64,
30 svd_threshold: 1e-10,
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
37struct MPSTensor {
38 data: Array3<Complex64>,
40}
41
42impl MPSTensor {
43 fn zero_state(position: usize, num_qubits: usize) -> Self {
45 let is_first = position == 0;
46 let is_last = position == num_qubits - 1;
47
48 let data = if is_first && is_last {
49 let mut tensor = Array3::zeros((1, 2, 1));
51 tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
52 tensor
53 } else if is_first {
54 let mut tensor = Array3::zeros((1, 2, 2));
56 tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
57 tensor
58 } else if is_last {
59 let mut tensor = Array3::zeros((2, 2, 1));
61 tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
62 tensor
63 } else {
64 let mut tensor = Array3::zeros((2, 2, 2));
66 tensor[[0, 0, 0]] = Complex64::new(1.0, 0.0);
67 tensor
68 };
69 Self { data }
70 }
71}
72
73pub struct BasicMPS {
75 tensors: Vec<MPSTensor>,
77 num_qubits: usize,
79 config: BasicMPSConfig,
81}
82
83impl BasicMPS {
84 #[must_use]
86 pub fn new(num_qubits: usize, config: BasicMPSConfig) -> Self {
87 let tensors = (0..num_qubits)
88 .map(|i| MPSTensor::zero_state(i, num_qubits))
89 .collect();
90
91 Self {
92 tensors,
93 num_qubits,
94 config,
95 }
96 }
97
98 pub fn apply_single_qubit_gate(
100 &mut self,
101 gate_matrix: &Array2<Complex64>,
102 qubit: usize,
103 ) -> QuantRS2Result<()> {
104 if qubit >= self.num_qubits {
105 return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
106 }
107
108 let tensor = &mut self.tensors[qubit];
109 let shape = tensor.data.shape();
110 let (left_dim, _, right_dim) = (shape[0], shape[1], shape[2]);
111
112 let mut new_data = Array3::zeros((left_dim, 2, right_dim));
113
114 for l in 0..left_dim {
116 for r in 0..right_dim {
117 for new_phys in 0..2 {
118 for old_phys in 0..2 {
119 new_data[[l, new_phys, r]] +=
120 gate_matrix[[new_phys, old_phys]] * tensor.data[[l, old_phys, r]];
121 }
122 }
123 }
124 }
125
126 tensor.data = new_data;
127 Ok(())
128 }
129
130 pub fn apply_two_qubit_gate(
132 &mut self,
133 gate_matrix: &Array2<Complex64>,
134 qubit1: usize,
135 qubit2: usize,
136 ) -> QuantRS2Result<()> {
137 if (qubit1 as i32 - qubit2 as i32).abs() != 1 {
138 return Err(QuantRS2Error::InvalidInput(
139 "MPS requires adjacent qubits for two-qubit gates".to_string(),
140 ));
141 }
142
143 let (left_q, right_q) = if qubit1 < qubit2 {
144 (qubit1, qubit2)
145 } else {
146 (qubit2, qubit1)
147 };
148
149 let left_shape = self.tensors[left_q].data.shape().to_vec();
153 let right_shape = self.tensors[right_q].data.shape().to_vec();
154
155 let mut combined = Array3::<Complex64>::zeros((left_shape[0], 4, right_shape[2]));
157
158 for l in 0..left_shape[0] {
159 for r in 0..right_shape[2] {
160 for i in 0..2 {
161 for j in 0..2 {
162 for m in 0..left_shape[2] {
163 combined[[l, i * 2 + j, r]] += self.tensors[left_q].data[[l, i, m]]
164 * self.tensors[right_q].data[[m, j, r]];
165 }
166 }
167 }
168 }
169 }
170
171 let mut result = Array3::<Complex64>::zeros((left_shape[0], 4, right_shape[2]));
173 for l in 0..left_shape[0] {
174 for r in 0..right_shape[2] {
175 for out_idx in 0..4 {
176 for in_idx in 0..4 {
177 result[[l, out_idx, r]] +=
178 gate_matrix[[out_idx, in_idx]] * combined[[l, in_idx, r]];
179 }
180 }
181 }
182 }
183
184 let left_dim = left_shape[0];
190 let right_dim = right_shape[2];
191 let mut matrix = Array2::<Complex64>::zeros((left_dim * 2, 2 * right_dim));
192 for l in 0..left_dim {
193 for r in 0..right_dim {
194 for i in 0..2 {
195 for j in 0..2 {
196 matrix[[l * 2 + i, j * right_dim + r]] = result[[l, i * 2 + j, r]];
198 }
199 }
200 }
201 }
202
203 let (u, singular_values, vt) =
204 truncated_svd(&matrix, self.config.max_bond_dim, self.config.svd_threshold)?;
205 let new_bond = singular_values.len();
206
207 let mut left_new = Array3::<Complex64>::zeros((left_dim, 2, new_bond));
209 for l in 0..left_dim {
210 for i in 0..2 {
211 for b in 0..new_bond {
212 left_new[[l, i, b]] = u[[l * 2 + i, b]];
213 }
214 }
215 }
216
217 let mut right_new = Array3::<Complex64>::zeros((new_bond, 2, right_dim));
220 for b in 0..new_bond {
221 let scale = Complex64::new(singular_values[b], 0.0);
222 for j in 0..2 {
223 for r in 0..right_dim {
224 right_new[[b, j, r]] = scale * vt[[b, j * right_dim + r]];
225 }
226 }
227 }
228
229 self.tensors[left_q].data = left_new;
230 self.tensors[right_q].data = right_new;
231
232 Ok(())
233 }
234
235 pub fn get_amplitude(&self, bitstring: &[bool]) -> QuantRS2Result<Complex64> {
237 if bitstring.len() != self.num_qubits {
238 return Err(QuantRS2Error::InvalidInput(format!(
239 "Bitstring length {} doesn't match qubit count {}",
240 bitstring.len(),
241 self.num_qubits
242 )));
243 }
244
245 let mut result = Array2::from_elem((1, 1), Complex64::new(1.0, 0.0));
247
248 for (i, &bit) in bitstring.iter().enumerate() {
249 let tensor = &self.tensors[i];
250 let physical_idx = i32::from(bit);
251
252 let matrix = tensor.data.slice(s![.., physical_idx, ..]);
254
255 result = result.dot(&matrix);
257 }
258
259 Ok(result[[0, 0]])
260 }
261
262 #[must_use]
264 pub fn sample(&self) -> Vec<bool> {
265 let mut rng = thread_rng();
266 let mut result = vec![false; self.num_qubits];
267 let mut accumulated = Array2::from_elem((1, 1), Complex64::new(1.0, 0.0));
268
269 for (i, tensor) in self.tensors.iter().enumerate() {
270 let matrix0 = tensor.data.slice(s![.., 0, ..]);
272 let matrix1 = tensor.data.slice(s![.., 1, ..]);
273
274 let branch0: Array2<Complex64> = accumulated.dot(&matrix0);
275 let branch1: Array2<Complex64> = accumulated.dot(&matrix1);
276
277 let norm0_sq: f64 = branch0.iter().map(scirs2_core::Complex::norm_sqr).sum();
279 let norm1_sq: f64 = branch1.iter().map(scirs2_core::Complex::norm_sqr).sum();
280
281 let total = norm0_sq + norm1_sq;
282 let prob0 = norm0_sq / total;
283
284 if rng.random::<f64>() < prob0 {
285 result[i] = false;
286 accumulated = branch0;
287 } else {
288 result[i] = true;
289 accumulated = branch1;
290 }
291
292 let norm_sq: f64 = accumulated.iter().map(scirs2_core::Complex::norm_sqr).sum();
294 if norm_sq > 0.0 {
295 accumulated /= Complex64::new(norm_sq.sqrt(), 0.0);
296 }
297 }
298
299 result
300 }
301
302 fn to_statevector(&self) -> QuantRS2Result<Vec<Complex64>> {
309 let dim = 1usize << self.num_qubits;
310 let mut amplitudes = vec![Complex64::new(0.0, 0.0); dim];
311
312 for (index, amplitude) in amplitudes.iter_mut().enumerate() {
313 let mut accumulated = Array2::from_elem((1, 1), Complex64::new(1.0, 0.0));
314 for qubit in 0..self.num_qubits {
315 let bit = (index >> qubit) & 1;
316 let slice = self.tensors[qubit].data.slice(s![.., bit as i32, ..]);
317 accumulated = accumulated.dot(&slice);
318 }
319 *amplitude = accumulated[[0, 0]];
320 }
321
322 Ok(amplitudes)
323 }
324}
325
326fn truncated_svd(
334 matrix: &Array2<Complex64>,
335 max_bond: usize,
336 threshold: f64,
337) -> QuantRS2Result<(Array2<Complex64>, Array1<f64>, Array2<Complex64>)> {
338 let (full_u, full_s, full_vt) = crate::mps_simulator::complex_jacobi_svd(matrix)?;
339 let full_rank = full_s.len();
340
341 let mut kept = full_s.iter().filter(|&&value| value > threshold).count();
342 kept = kept.min(max_bond).min(full_rank);
343 if kept == 0 {
344 kept = full_rank.max(1);
345 }
346
347 let u: Array2<Complex64> = full_u.slice(s![.., ..kept]).to_owned();
348 let truncated_s: Array1<f64> = full_s.slice(s![..kept]).to_owned();
349 let vt: Array2<Complex64> = full_vt.slice(s![..kept, ..]).to_owned();
350
351 Ok((u, truncated_s, vt))
352}
353
354pub struct BasicMPSSimulator {
356 config: BasicMPSConfig,
357}
358
359impl BasicMPSSimulator {
360 #[must_use]
362 pub const fn new(config: BasicMPSConfig) -> Self {
363 Self { config }
364 }
365
366 #[must_use]
368 pub fn default() -> Self {
369 Self::new(BasicMPSConfig::default())
370 }
371}
372
373impl<const N: usize> Simulator<N> for BasicMPSSimulator {
374 fn run(&self, circuit: &Circuit<N>) -> QuantRS2Result<Register<N>> {
375 let mut mps = BasicMPS::new(N, self.config.clone());
377
378 for gate in circuit.gates() {
380 match gate.name() {
381 "H" => {
382 let h_matrix = {
383 let h = 1.0 / SQRT_2;
384 array![
385 [Complex64::new(h, 0.), Complex64::new(h, 0.)],
386 [Complex64::new(h, 0.), Complex64::new(-h, 0.)]
387 ]
388 };
389 if let Some(&qubit) = gate.qubits().first() {
390 mps.apply_single_qubit_gate(&h_matrix, qubit.id() as usize)?;
391 }
392 }
393 "X" => {
394 let x_matrix = array![
395 [Complex64::new(0., 0.), Complex64::new(1., 0.)],
396 [Complex64::new(1., 0.), Complex64::new(0., 0.)]
397 ];
398 if let Some(&qubit) = gate.qubits().first() {
399 mps.apply_single_qubit_gate(&x_matrix, qubit.id() as usize)?;
400 }
401 }
402 "CNOT" | "CX" => {
403 let cnot_matrix = array![
404 [
405 Complex64::new(1., 0.),
406 Complex64::new(0., 0.),
407 Complex64::new(0., 0.),
408 Complex64::new(0., 0.)
409 ],
410 [
411 Complex64::new(0., 0.),
412 Complex64::new(1., 0.),
413 Complex64::new(0., 0.),
414 Complex64::new(0., 0.)
415 ],
416 [
417 Complex64::new(0., 0.),
418 Complex64::new(0., 0.),
419 Complex64::new(0., 0.),
420 Complex64::new(1., 0.)
421 ],
422 [
423 Complex64::new(0., 0.),
424 Complex64::new(0., 0.),
425 Complex64::new(1., 0.),
426 Complex64::new(0., 0.)
427 ],
428 ];
429 let qubits = gate.qubits();
430 if qubits.len() == 2 {
431 mps.apply_two_qubit_gate(
432 &cnot_matrix,
433 qubits[0].id() as usize,
434 qubits[1].id() as usize,
435 )?;
436 }
437 }
438 _ => {
439 }
441 }
442 }
443
444 let amplitudes = mps.to_statevector()?;
446 Register::<N>::with_amplitudes(amplitudes)
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_basic_mps_initialization() {
456 let mps = BasicMPS::new(4, BasicMPSConfig::default());
457
458 let amp = mps
460 .get_amplitude(&[false, false, false, false])
461 .expect("Failed to get amplitude for |0000>");
462 assert!((amp.norm() - 1.0).abs() < 1e-10);
463
464 let amp = mps
465 .get_amplitude(&[true, false, false, false])
466 .expect("Failed to get amplitude for |1000>");
467 assert!(amp.norm() < 1e-10);
468 }
469
470 #[test]
471 fn test_single_qubit_gate() {
472 let mut mps = BasicMPS::new(3, BasicMPSConfig::default());
473
474 let x_matrix = array![
476 [Complex64::new(0., 0.), Complex64::new(1., 0.)],
477 [Complex64::new(1., 0.), Complex64::new(0., 0.)]
478 ];
479 mps.apply_single_qubit_gate(&x_matrix, 0)
480 .expect("Failed to apply X gate");
481
482 let amp = mps
484 .get_amplitude(&[true, false, false])
485 .expect("Failed to get amplitude for |100>");
486 assert!((amp.norm() - 1.0).abs() < 1e-10);
487 }
488
489 #[test]
490 fn test_truncated_svd_reconstructs_matrix() {
491 let matrix = Array2::from_shape_vec(
492 (2, 2),
493 vec![
494 Complex64::new(1.0, 1.0),
495 Complex64::new(2.0, -1.0),
496 Complex64::new(-1.0, 0.5),
497 Complex64::new(0.0, 2.0),
498 ],
499 )
500 .expect("failed to build test matrix");
501
502 let (u, s, vt) =
503 truncated_svd(&matrix, 16, 1e-14).expect("real SVD decomposition should succeed");
504
505 let k = s.len();
506 let mut max_err = 0.0_f64;
507 for i in 0..2 {
508 for j in 0..2 {
509 let mut acc = Complex64::new(0.0, 0.0);
510 for r in 0..k {
511 acc += u[[i, r]] * Complex64::new(s[r], 0.0) * vt[[r, j]];
512 }
513 max_err = max_err.max((acc - matrix[[i, j]]).norm());
514 }
515 }
516 assert!(
517 max_err < 1e-8,
518 "SVD reconstruction error too large: {max_err}"
519 );
520 assert!(
522 s.iter().any(|&value| (value - 1.0).abs() > 1e-6),
523 "singular values are all ~1 (identity fabrication)"
524 );
525 }
526
527 #[test]
528 fn test_two_qubit_gate_bell_contraction() {
529 let mut mps = BasicMPS::new(2, BasicMPSConfig::default());
531
532 let h = 1.0 / SQRT_2;
533 let h_matrix = array![
534 [Complex64::new(h, 0.), Complex64::new(h, 0.)],
535 [Complex64::new(h, 0.), Complex64::new(-h, 0.)]
536 ];
537 mps.apply_single_qubit_gate(&h_matrix, 0)
538 .expect("failed to apply H");
539
540 let cnot = array![
541 [
542 Complex64::new(1., 0.),
543 Complex64::new(0., 0.),
544 Complex64::new(0., 0.),
545 Complex64::new(0., 0.)
546 ],
547 [
548 Complex64::new(0., 0.),
549 Complex64::new(1., 0.),
550 Complex64::new(0., 0.),
551 Complex64::new(0., 0.)
552 ],
553 [
554 Complex64::new(0., 0.),
555 Complex64::new(0., 0.),
556 Complex64::new(0., 0.),
557 Complex64::new(1., 0.)
558 ],
559 [
560 Complex64::new(0., 0.),
561 Complex64::new(0., 0.),
562 Complex64::new(1., 0.),
563 Complex64::new(0., 0.)
564 ],
565 ];
566 mps.apply_two_qubit_gate(&cnot, 0, 1)
567 .expect("failed to apply CNOT");
568
569 let state = mps.to_statevector().expect("contraction should succeed");
570
571 let inv_sqrt2 = 1.0 / SQRT_2;
572 assert!((state[0].re - inv_sqrt2).abs() < 1e-10, "amp(|00>) wrong");
573 assert!(state[1].norm() < 1e-10, "amp(|01>) should vanish");
574 assert!(state[2].norm() < 1e-10, "amp(|10>) should vanish");
575 assert!((state[3].re - inv_sqrt2).abs() < 1e-10, "amp(|11>) wrong");
576
577 assert!(
579 state[3].norm() > 1e-3,
580 "two-qubit gate fabricated a non-entangled state"
581 );
582 }
583}