1use quantrs2_circuit::builder::{Circuit, Simulator};
8use quantrs2_core::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::{multi, single, GateOp},
11 qubit::QubitId,
12 register::Register,
13};
14
15use scirs2_core::ndarray::{Array, ArrayD, Dimension, IxDyn};
16use scirs2_core::ndarray_ext::manipulation;
17use scirs2_core::parallel_ops::*;
18use scirs2_core::Complex64;
19use std::collections::HashMap;
20
21pub mod contraction;
22pub mod opt_contraction;
23pub mod tensor;
24
25use contraction::ContractableNetwork;
26use opt_contraction::{ContractionOptMethod, OptimizedTensorNetwork, PathOptimizer};
27use tensor::{Tensor, TensorIndex};
28
29#[derive(Debug, Clone)]
31pub struct TensorNetworkSimulator {
32 max_bond_dimension: usize,
34
35 optimization_level: u8,
37
38 contraction_strategy: ContractionStrategy,
40
41 path_optimizer: PathOptimizer,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum CircuitType {
48 Linear,
50
51 Star,
53
54 Layered,
56
57 QFT,
59
60 QAOA,
62
63 General,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum ContractionStrategy {
70 Greedy,
72
73 Linear,
75
76 Star,
78
79 QFT,
81
82 QAOA,
84
85 Custom,
87}
88
89impl TensorNetworkSimulator {
90 pub fn new() -> Self {
92 Self {
93 max_bond_dimension: 16,
94 optimization_level: 1,
95 contraction_strategy: ContractionStrategy::Greedy,
96 path_optimizer: PathOptimizer::default(),
97 }
98 }
99
100 pub fn qft() -> Self {
102 Self {
103 max_bond_dimension: 16,
104 optimization_level: 2,
105 contraction_strategy: ContractionStrategy::QFT,
106 path_optimizer: PathOptimizer::default()
107 .with_method(ContractionOptMethod::Hybrid)
108 .with_max_bond_dimension(32),
109 }
110 }
111
112 pub fn qaoa() -> Self {
114 Self {
115 max_bond_dimension: 16,
116 optimization_level: 2,
117 contraction_strategy: ContractionStrategy::QAOA,
118 path_optimizer: PathOptimizer::default()
119 .with_method(ContractionOptMethod::Hybrid)
120 .with_max_bond_dimension(32),
121 }
122 }
123
124 #[must_use]
126 pub const fn with_bond_dimension(mut self, max_bond_dimension: usize) -> Self {
127 self.max_bond_dimension = max_bond_dimension;
128 self.path_optimizer = self
129 .path_optimizer
130 .with_max_bond_dimension(max_bond_dimension);
131 self
132 }
133
134 #[must_use]
141 pub fn with_optimization_level(mut self, level: u8) -> Self {
142 self.optimization_level = level.min(3);
143
144 self.path_optimizer = match level {
146 0 => self
147 .path_optimizer
148 .with_method(ContractionOptMethod::Greedy),
149 1 => self
150 .path_optimizer
151 .with_method(ContractionOptMethod::Greedy),
152 2 => self
153 .path_optimizer
154 .with_method(ContractionOptMethod::DynamicProgramming),
155 3 => self
156 .path_optimizer
157 .with_method(ContractionOptMethod::Hybrid),
158 _ => self
159 .path_optimizer
160 .with_method(ContractionOptMethod::Greedy),
161 };
162
163 self
164 }
165
166 #[must_use]
168 pub fn with_contraction_strategy(mut self, strategy: ContractionStrategy) -> Self {
169 self.contraction_strategy = strategy.clone();
170
171 self.path_optimizer = match &strategy {
173 ContractionStrategy::QFT => self
174 .path_optimizer
175 .with_method(ContractionOptMethod::DynamicProgramming)
176 .with_max_bond_dimension(32),
177 ContractionStrategy::QAOA => self
178 .path_optimizer
179 .with_method(ContractionOptMethod::DynamicProgramming)
180 .with_max_bond_dimension(32),
181 ContractionStrategy::Linear => self
182 .path_optimizer
183 .with_method(ContractionOptMethod::Greedy),
184 ContractionStrategy::Star => self
185 .path_optimizer
186 .with_method(ContractionOptMethod::Greedy),
187 _ => self.path_optimizer,
188 };
189
190 self
191 }
192
193 fn analyze_circuit_structure<const N: usize>(&self, circuit: &Circuit<N>) -> CircuitType {
195 let mut single_qubit_gates = 0;
197 let mut cnot_gates = 0;
198 let mut other_two_qubit_gates = 0;
199 let mut multi_qubit_gates = 0;
200 let mut hadamard_gates = 0;
201 let mut rotation_gates = 0;
202 let mut phase_gates = 0;
203 let mut x_rotation_gates = 0;
204 let mut controlled_phase_gates = 0;
205 let mut swap_gates = 0;
206
207 let mut qubit_connections =
209 std::collections::HashMap::<usize, std::collections::HashSet<usize>>::new();
210
211 for gate in circuit.gates() {
213 let qubits = gate.qubits();
214 let gate_name = gate.name();
215
216 if qubits.len() == 1 {
217 single_qubit_gates += 1;
219
220 match gate_name {
221 "H" => hadamard_gates += 1,
222 "RX" => {
223 rotation_gates += 1;
224 x_rotation_gates += 1;
225 }
226 "RY" | "RZ" => rotation_gates += 1,
227 "S" | "T" | "S†" | "T†" => phase_gates += 1,
228 _ => {}
229 }
230 } else if qubits.len() == 2 {
231 if gate_name == "CNOT" {
233 cnot_gates += 1;
234 } else if gate_name == "SWAP" {
235 swap_gates += 1;
236 } else if gate_name == "CZ" || gate_name == "CS" || gate_name == "CRZ" {
237 controlled_phase_gates += 1;
238 other_two_qubit_gates += 1;
239 } else {
240 other_two_qubit_gates += 1;
241 }
242
243 let q1 = qubits[0].id() as usize;
245 let q2 = qubits[1].id() as usize;
246
247 qubit_connections.entry(q1).or_default().insert(q2);
248 qubit_connections.entry(q2).or_default().insert(q1);
249 } else {
250 multi_qubit_gates += 1;
252 }
253 }
254
255 if self.is_qft_pattern(hadamard_gates, controlled_phase_gates, swap_gates, N) {
257 return CircuitType::QFT;
258 }
259
260 if self.is_qaoa_pattern(x_rotation_gates, cnot_gates) {
262 return CircuitType::QAOA;
263 }
264
265 if is_linear_structure(&qubit_connections, N)
267 && other_two_qubit_gates == 0
268 && multi_qubit_gates == 0
269 {
270 return CircuitType::Linear;
271 }
272
273 if is_star_structure(&qubit_connections, N) {
275 return CircuitType::Star;
276 }
277
278 if is_layered_structure(circuit) {
280 return CircuitType::Layered;
281 }
282
283 CircuitType::General
285 }
286
287 const fn is_qft_pattern(
289 &self,
290 hadamard_count: usize,
291 controlled_phase_count: usize,
292 swap_count: usize,
293 num_qubits: usize,
294 ) -> bool {
295 let expected_controlled_phase = (num_qubits * (num_qubits - 1)) / 2;
301 let expected_swap = num_qubits / 2;
302
303 hadamard_count >= num_qubits
305 && controlled_phase_count >= expected_controlled_phase / 2
306 && (swap_count == 0 || swap_count >= expected_swap / 2)
307 }
308
309 const fn is_qaoa_pattern(&self, x_rotation_count: usize, cnot_count: usize) -> bool {
311 x_rotation_count > 0 && cnot_count > 0
317 }
318}
319
320fn is_linear_structure(
322 qubit_connections: &std::collections::HashMap<usize, std::collections::HashSet<usize>>,
323 num_qubits: usize,
324) -> bool {
325 for i in 0..num_qubits {
327 if let Some(connections) = qubit_connections.get(&i) {
328 if connections.len() > 2 {
329 return false;
330 }
331 }
332 }
333
334 let num_endpoints = (0..num_qubits)
336 .filter(|&i| {
337 qubit_connections
338 .get(&i)
339 .is_some_and(|conns| conns.len() == 1)
340 })
341 .count();
342
343 num_endpoints == 2
345}
346
347fn is_star_structure(
349 qubit_connections: &std::collections::HashMap<usize, std::collections::HashSet<usize>>,
350 num_qubits: usize,
351) -> bool {
352 let mut high_degree_qubits = 0;
354 let mut leaf_qubits = 0;
355
356 for i in 0..num_qubits {
357 if let Some(connections) = qubit_connections.get(&i) {
358 if connections.len() > 2 {
359 high_degree_qubits += 1;
360 } else if connections.len() == 1 {
361 leaf_qubits += 1;
362 }
363 }
364 }
365
366 high_degree_qubits == 1 && leaf_qubits >= 3
368}
369
370fn is_layered_structure<const N: usize>(circuit: &Circuit<N>) -> bool {
372 let mut rotation_gates = 0;
376 let mut controlled_gates = 0;
377
378 for gate in circuit.gates() {
379 match gate.name() {
380 "RZ" | "RY" | "RX" => rotation_gates += 1,
381 "CNOT" | "CZ" | "CY" | "CH" | "CS" | "CRX" | "CRY" | "CRZ" => controlled_gates += 1,
382 _ => {}
383 }
384 }
385
386 rotation_gates >= N / 2 && controlled_gates >= N / 2
388}
389
390impl TensorNetworkSimulator {
391 fn apply_single_qubit_gate<const N: usize>(
393 &self,
394 network: &mut TensorNetwork,
395 gate_matrix: &[Complex64],
396 target: QubitId,
397 ) -> QuantRS2Result<()> {
398 let target_idx = target.id() as usize;
399 if target_idx >= N {
400 return Err(QuantRS2Error::InvalidQubitId(target.id()));
401 }
402
403 let gate_tensor = Tensor::from_matrix(gate_matrix, 2);
405
406 network.apply_gate(gate_tensor, target_idx)?;
408
409 Ok(())
410 }
411
412 fn apply_two_qubit_gate<const N: usize>(
414 &self,
415 network: &mut TensorNetwork,
416 gate_matrix: &[Complex64],
417 control: QubitId,
418 target: QubitId,
419 ) -> QuantRS2Result<()> {
420 let control_idx = control.id() as usize;
421 let target_idx = target.id() as usize;
422
423 if control_idx >= N || target_idx >= N {
424 return Err(QuantRS2Error::InvalidQubitId(if control_idx >= N {
425 control.id()
426 } else {
427 target.id()
428 }));
429 }
430
431 if control_idx == target_idx {
432 return Err(QuantRS2Error::CircuitValidationFailed(
433 "Control and target qubits must be different".into(),
434 ));
435 }
436
437 let gate_tensor = Tensor::from_matrix(gate_matrix, 4);
439
440 network.apply_two_qubit_gate(gate_tensor, control_idx, target_idx)?;
442
443 Ok(())
444 }
445}
446
447impl Default for TensorNetworkSimulator {
448 fn default() -> Self {
449 Self::new()
450 }
451}
452
453impl<const N: usize> Simulator<N> for TensorNetworkSimulator {
454 fn run(&self, circuit: &Circuit<N>) -> QuantRS2Result<Register<N>> {
455 let mut network = TensorNetwork::new(N);
457
458 network.max_bond_dimension = match self.optimization_level {
460 0 => 4, 1 => 16, 2 => 32, 3 => 64, _ => 16, };
466
467 let circuit_type = self.analyze_circuit_structure(circuit);
469
470 let effective_strategy = match &self.contraction_strategy {
473 ContractionStrategy::Greedy => {
474 match circuit_type {
476 CircuitType::QFT => ContractionStrategy::QFT,
477 CircuitType::QAOA => ContractionStrategy::QAOA,
478 CircuitType::Linear => ContractionStrategy::Linear,
479 CircuitType::Star => ContractionStrategy::Star,
480 _ => ContractionStrategy::Greedy,
481 }
482 }
483 _ => self.contraction_strategy.clone(),
485 };
486
487 network.detected_circuit_type = circuit_type;
489
490 match effective_strategy {
492 ContractionStrategy::QFT => {
493 network.max_bond_dimension = network.max_bond_dimension.max(32);
495 network.using_qft_optimization = true;
499 }
500 ContractionStrategy::QAOA => {
501 network.max_bond_dimension = network.max_bond_dimension.max(32);
503 network.using_qaoa_optimization = true;
507 }
508 ContractionStrategy::Linear => {
509 network.max_bond_dimension = network.max_bond_dimension.max(16);
511 network.using_linear_optimization = true;
512 }
513 ContractionStrategy::Star => {
514 network.max_bond_dimension = network.max_bond_dimension.max(16);
516 network.using_star_optimization = true;
517 }
518 _ => {
519 }
522 }
523
524 for gate in circuit.gates() {
526 match gate.name() {
527 "H" => {
529 if let Some(g) = gate.as_any().downcast_ref::<single::Hadamard>() {
530 let matrix = g.matrix()?;
531 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
532 }
533 }
534 "X" => {
535 if let Some(g) = gate.as_any().downcast_ref::<single::PauliX>() {
536 let matrix = g.matrix()?;
537 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
538 }
539 }
540 "Y" => {
541 if let Some(g) = gate.as_any().downcast_ref::<single::PauliY>() {
542 let matrix = g.matrix()?;
543 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
544 }
545 }
546 "Z" => {
547 if let Some(g) = gate.as_any().downcast_ref::<single::PauliZ>() {
548 let matrix = g.matrix()?;
549 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
550 }
551 }
552 "RX" => {
554 if let Some(g) = gate.as_any().downcast_ref::<single::RotationX>() {
555 let matrix = g.matrix()?;
556 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
557 }
558 }
559 "RY" => {
560 if let Some(g) = gate.as_any().downcast_ref::<single::RotationY>() {
561 let matrix = g.matrix()?;
562 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
563 }
564 }
565 "RZ" => {
566 if let Some(g) = gate.as_any().downcast_ref::<single::RotationZ>() {
567 let matrix = g.matrix()?;
568 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
569 }
570 }
571 "S" => {
573 if let Some(g) = gate.as_any().downcast_ref::<single::Phase>() {
574 let matrix = g.matrix()?;
575 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
576 }
577 }
578 "T" => {
579 if let Some(g) = gate.as_any().downcast_ref::<single::T>() {
580 let matrix = g.matrix()?;
581 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
582 }
583 }
584 "S†" => {
585 if let Some(g) = gate.as_any().downcast_ref::<single::PhaseDagger>() {
586 let matrix = g.matrix()?;
587 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
588 }
589 }
590 "T†" => {
591 if let Some(g) = gate.as_any().downcast_ref::<single::TDagger>() {
592 let matrix = g.matrix()?;
593 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
594 }
595 }
596 "√X" => {
597 if let Some(g) = gate.as_any().downcast_ref::<single::SqrtX>() {
598 let matrix = g.matrix()?;
599 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
600 }
601 }
602 "√X†" => {
603 if let Some(g) = gate.as_any().downcast_ref::<single::SqrtXDagger>() {
604 let matrix = g.matrix()?;
605 self.apply_single_qubit_gate::<N>(&mut network, &matrix, g.target)?;
606 }
607 }
608
609 "CNOT" => {
611 if let Some(g) = gate.as_any().downcast_ref::<multi::CNOT>() {
612 let matrix = g.matrix()?;
613 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
614 }
615 }
616 "CZ" => {
617 if let Some(g) = gate.as_any().downcast_ref::<multi::CZ>() {
618 let matrix = g.matrix()?;
619 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
620 }
621 }
622 "SWAP" => {
623 if let Some(g) = gate.as_any().downcast_ref::<multi::SWAP>() {
624 let matrix = g.matrix()?;
625 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.qubit1, g.qubit2)?;
626 }
627 }
628 "CY" => {
629 if let Some(g) = gate.as_any().downcast_ref::<multi::CY>() {
630 let matrix = g.matrix()?;
631 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
632 }
633 }
634 "CH" => {
635 if let Some(g) = gate.as_any().downcast_ref::<multi::CH>() {
636 let matrix = g.matrix()?;
637 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
638 }
639 }
640 "CS" => {
641 if let Some(g) = gate.as_any().downcast_ref::<multi::CS>() {
642 let matrix = g.matrix()?;
643 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
644 }
645 }
646 "CRX" => {
647 if let Some(g) = gate.as_any().downcast_ref::<multi::CRX>() {
648 let matrix = g.matrix()?;
649 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
650 }
651 }
652 "CRY" => {
653 if let Some(g) = gate.as_any().downcast_ref::<multi::CRY>() {
654 let matrix = g.matrix()?;
655 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
656 }
657 }
658 "CRZ" => {
659 if let Some(g) = gate.as_any().downcast_ref::<multi::CRZ>() {
660 let matrix = g.matrix()?;
661 self.apply_two_qubit_gate::<N>(&mut network, &matrix, g.control, g.target)?;
662 }
663 }
664
665 "Toffoli" | "Fredkin" => {
667 return Err(QuantRS2Error::UnsupportedOperation(format!(
668 "Gate {} not yet implemented for tensor network simulator",
669 gate.name()
670 )));
671 }
672
673 _ => {
674 return Err(QuantRS2Error::UnsupportedOperation(format!(
675 "Gate {} not supported",
676 gate.name()
677 )));
678 }
679 }
680 }
681
682 let amplitudes = network.contract_to_statevector()?;
684
685 Register::<N>::with_amplitudes(amplitudes)
687 }
688}
689
690#[derive(Debug, Clone)]
692pub struct TensorNetwork {
693 num_qubits: usize,
695
696 tensors: HashMap<usize, Tensor>,
698
699 connections: Vec<(TensorIndex, TensorIndex)>,
701
702 applied_gates: Vec<(Tensor, Vec<usize>)>,
711
712 cached_contraction_path: Option<contraction::ContractionPath>,
714
715 next_id: usize,
717
718 max_bond_dimension: usize,
720
721 detected_circuit_type: CircuitType,
723
724 using_qft_optimization: bool,
726
727 using_qaoa_optimization: bool,
729
730 using_linear_optimization: bool,
732
733 using_star_optimization: bool,
735}
736
737impl TensorNetwork {
738 pub fn new(num_qubits: usize) -> Self {
740 let mut network = Self {
741 num_qubits,
742 tensors: HashMap::new(),
743 connections: Vec::new(),
744 applied_gates: Vec::new(),
745 cached_contraction_path: None,
746 next_id: 0,
747 max_bond_dimension: 16,
748 detected_circuit_type: CircuitType::General,
749 using_qft_optimization: false,
750 using_qaoa_optimization: false,
751 using_linear_optimization: false,
752 using_star_optimization: false,
753 };
754
755 for i in 0..num_qubits {
757 let qubit_tensor = Tensor::qubit_zero();
758 network.add_tensor(qubit_tensor, i);
759 }
760
761 network
762 }
763
764 fn add_tensor(&mut self, tensor: Tensor, qubit_index: usize) -> usize {
766 let id = self.next_id;
767 self.next_id += 1;
768
769 self.tensors.insert(id, tensor);
770
771 id
772 }
773
774 pub fn apply_gate(&mut self, gate_tensor: Tensor, qubit_index: usize) -> QuantRS2Result<()> {
780 if gate_tensor.rank != 2 {
781 return Err(QuantRS2Error::CircuitValidationFailed(format!(
782 "Single-qubit gate tensor must have rank 2, got {}",
783 gate_tensor.rank
784 )));
785 }
786 self.applied_gates.push((gate_tensor, vec![qubit_index]));
787 Ok(())
788 }
789
790 pub fn apply_two_qubit_gate(
796 &mut self,
797 gate_tensor: Tensor,
798 control_index: usize,
799 target_index: usize,
800 ) -> QuantRS2Result<()> {
801 if gate_tensor.rank != 4 {
802 return Err(QuantRS2Error::CircuitValidationFailed(format!(
803 "Two-qubit gate tensor must have rank 4, got {}",
804 gate_tensor.rank
805 )));
806 }
807 self.applied_gates
808 .push((gate_tensor, vec![control_index, target_index]));
809 Ok(())
810 }
811
812 pub fn contract_to_statevector(&self) -> QuantRS2Result<Vec<Complex64>> {
823 let n = self.num_qubits;
824 if n == 0 {
825 return Ok(vec![Complex64::new(1.0, 0.0)]);
826 }
827
828 let shape = vec![2usize; n];
831 let mut state_data = ArrayD::<Complex64>::zeros(IxDyn(shape.as_slice()));
832 state_data[IxDyn(&vec![0usize; n])] = Complex64::new(1.0, 0.0);
833 let mut state = Tensor::new(state_data);
834
835 for (gate, qubits) in &self.applied_gates {
836 let state_axes: Vec<usize> = qubits.iter().map(|&q| n - 1 - q).collect();
838 state = Self::apply_gate_to_state(&state, gate, &state_axes)?;
839 }
840
841 Ok(state.data.iter().copied().collect())
843 }
844
845 fn apply_gate_to_state(
855 state: &Tensor,
856 gate: &Tensor,
857 state_axes: &[usize],
858 ) -> QuantRS2Result<Tensor> {
859 let n = state.rank;
860 let k = state_axes.len();
861
862 if gate.rank != 2 * k {
863 return Err(QuantRS2Error::CircuitValidationFailed(format!(
864 "Gate rank {} does not match 2×(qubits acted on) = {}",
865 gate.rank,
866 2 * k
867 )));
868 }
869 for &axis in state_axes {
870 if axis >= n {
871 return Err(QuantRS2Error::CircuitValidationFailed(format!(
872 "Gate acts on state axis {axis} but state has rank {n}"
873 )));
874 }
875 }
876
877 let mut result = ArrayD::<Complex64>::zeros(IxDyn(state.data.shape()));
878 for (out_idx, slot) in result.indexed_iter_mut() {
879 let out = out_idx.slice();
880 let mut acc = Complex64::new(0.0, 0.0);
881
882 for in_config in 0..(1usize << k) {
884 let mut gate_idx = vec![0usize; 2 * k];
885 let mut src = out.to_vec();
886 for (i, &axis) in state_axes.iter().enumerate() {
887 gate_idx[i] = out[axis];
889 let bit = (in_config >> (k - 1 - i)) & 1;
891 gate_idx[k + i] = bit;
892 src[axis] = bit;
893 }
894 acc += gate.data[IxDyn(&gate_idx)] * state.data[IxDyn(&src)];
895 }
896 *slot = acc;
897 }
898
899 Ok(Tensor::new(result))
900 }
901}
902
903impl ContractableNetwork for TensorNetwork {
904 fn contract_tensors(&mut self, tensor_id1: usize, tensor_id2: usize) -> QuantRS2Result<usize> {
908 let t1 = self.tensors.get(&tensor_id1).cloned().ok_or_else(|| {
909 QuantRS2Error::CircuitValidationFailed(format!("Tensor {tensor_id1} not found"))
910 })?;
911 let t2 = self.tensors.get(&tensor_id2).cloned().ok_or_else(|| {
912 QuantRS2Error::CircuitValidationFailed(format!("Tensor {tensor_id2} not found"))
913 })?;
914
915 let shared = contraction::shared_axis_pairs(&self.connections, tensor_id1, tensor_id2);
917 let merged = contraction::contract_pair_multi(&t1, &t2, &shared)?;
918
919 let new_id = self.next_id;
920 self.next_id += 1;
921 self.tensors.remove(&tensor_id1);
922 self.tensors.remove(&tensor_id2);
923 self.tensors.insert(new_id, merged);
924
925 self.connections.retain(|(a, b)| {
928 ![tensor_id1, tensor_id2].contains(&a.tensor_id)
929 && ![tensor_id1, tensor_id2].contains(&b.tensor_id)
930 });
931
932 Ok(new_id)
933 }
934
935 fn optimize_contraction_order(&mut self) -> QuantRS2Result<()> {
941 let path =
942 contraction::calculate_greedy_contraction_path(&self.tensors, &self.connections)?;
943 self.cached_contraction_path = Some(path);
944 Ok(())
945 }
946}