1use crate::distributed_simulator::{
16 CommunicationConfig, CommunicationPattern, DistributedSimulatorConfig, DistributionStrategy,
17 FaultToleranceConfig, LoadBalancingConfig, LoadBalancingStrategy, NetworkConfig,
18};
19use crate::large_scale_simulator::{LargeScaleSimulatorConfig, QuantumStateRepresentation};
20use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
21use scirs2_core::ndarray::{Array1, Array2, ArrayView1, Axis};
22use scirs2_core::parallel_ops::{IndexedParallelIterator, ParallelIterator};
23use scirs2_core::Complex64;
24use serde::{Deserialize, Serialize};
25use std::collections::{BTreeMap, HashMap};
26use std::sync::{Arc, Condvar, Mutex, RwLock};
27use std::time::{Duration, Instant};
28
29#[derive(Debug)]
35pub struct MPIQuantumSimulator {
36 communicator: MPICommunicator,
38 local_state: Arc<RwLock<LocalQuantumState>>,
40 config: MPISimulatorConfig,
42 stats: Arc<Mutex<MPISimulatorStats>>,
44 sync_manager: StateSynchronizationManager,
46 gate_handler: GateDistributionHandler,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MPISimulatorConfig {
53 pub total_qubits: usize,
55 pub distribution_strategy: MPIDistributionStrategy,
57 pub collective_optimization: CollectiveOptimization,
59 pub overlap_config: CommunicationOverlapConfig,
61 pub checkpoint_config: CheckpointConfig,
63 pub memory_config: MemoryConfig,
65}
66
67impl Default for MPISimulatorConfig {
68 fn default() -> Self {
69 Self {
70 total_qubits: 20,
71 distribution_strategy: MPIDistributionStrategy::AmplitudePartition,
72 collective_optimization: CollectiveOptimization::default(),
73 overlap_config: CommunicationOverlapConfig::default(),
74 checkpoint_config: CheckpointConfig::default(),
75 memory_config: MemoryConfig::default(),
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum MPIDistributionStrategy {
83 AmplitudePartition,
85 QubitPartition,
87 HybridPartition,
89 GateAwarePartition,
91 HilbertCurvePartition,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct CollectiveOptimization {
98 pub use_nonblocking: bool,
100 pub enable_fusion: bool,
102 pub buffer_size: usize,
104 pub allreduce_algorithm: AllreduceAlgorithm,
106 pub broadcast_algorithm: BroadcastAlgorithm,
108}
109
110impl Default for CollectiveOptimization {
111 fn default() -> Self {
112 Self {
113 use_nonblocking: true,
114 enable_fusion: true,
115 buffer_size: 16 * 1024 * 1024, allreduce_algorithm: AllreduceAlgorithm::RecursiveDoubling,
117 broadcast_algorithm: BroadcastAlgorithm::BinomialTree,
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124pub enum AllreduceAlgorithm {
125 Ring,
127 RecursiveDoubling,
129 Rabenseifner,
131 Automatic,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137pub enum BroadcastAlgorithm {
138 BinomialTree,
140 ScatterAllgather,
142 Pipeline,
144 Automatic,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct CommunicationOverlapConfig {
151 pub enable_overlap: bool,
153 pub pipeline_stages: usize,
155 pub prefetch_distance: usize,
157}
158
159impl Default for CommunicationOverlapConfig {
160 fn default() -> Self {
161 Self {
162 enable_overlap: true,
163 pipeline_stages: 4,
164 prefetch_distance: 2,
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct CheckpointConfig {
172 pub enable: bool,
174 pub interval: usize,
176 pub storage_path: String,
178 pub use_compression: bool,
180}
181
182impl Default for CheckpointConfig {
183 fn default() -> Self {
184 Self {
185 enable: false,
186 interval: 1000,
187 storage_path: "/tmp/quantum_checkpoint".to_string(),
188 use_compression: true,
189 }
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct MemoryConfig {
196 pub max_memory_per_node: usize,
198 pub enable_pooling: bool,
200 pub pool_size: usize,
202}
203
204impl Default for MemoryConfig {
205 fn default() -> Self {
206 Self {
207 max_memory_per_node: 64 * 1024 * 1024 * 1024, enable_pooling: true,
209 pool_size: 1024 * 1024 * 1024, }
211 }
212}
213
214#[derive(Debug)]
216pub struct MPICommunicator {
217 rank: usize,
219 size: usize,
221 backend: MPIBackend,
223 buffer_pool: Arc<Mutex<Vec<Vec<u8>>>>,
225 pending_requests: Arc<Mutex<Vec<MPIRequest>>>,
227}
228
229#[derive(Debug, Clone)]
231pub enum MPIBackend {
232 Simulated(SimulatedMPIBackend),
234 #[cfg(feature = "mpi")]
236 Native(NativeMPIBackend),
237 TCP(TCPMPIBackend),
239}
240
241#[derive(Debug, Clone)]
252pub struct SimulatedMPIBackend {
253 shared_state: Arc<(Mutex<SimulatedMPIState>, Condvar)>,
256}
257
258#[derive(Default)]
266struct CollectiveRound {
267 generation: usize,
269 payloads: BTreeMap<usize, Box<dyn std::any::Any + Send>>,
271 completed_generation: usize,
273 completed_payloads: BTreeMap<usize, Box<dyn std::any::Any + Send>>,
276}
277
278#[derive(Default)]
281pub struct SimulatedMPIState {
282 mailbox: HashMap<(usize, usize), Vec<u8>>,
284 rounds: HashMap<String, CollectiveRound>,
287}
288
289impl std::fmt::Debug for SimulatedMPIState {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 f.debug_struct("SimulatedMPIState")
292 .field("pending_messages", &self.mailbox.len())
293 .field("active_rounds", &self.rounds.len())
294 .finish()
295 }
296}
297
298impl SimulatedMPIState {
299 fn arrive(
303 &mut self,
304 key: &str,
305 rank: usize,
306 size: usize,
307 payload: Box<dyn std::any::Any + Send>,
308 ) -> usize {
309 let round = self.rounds.entry(key.to_string()).or_default();
310 round.payloads.insert(rank, payload);
311 let my_generation = round.generation;
312 if round.payloads.len() >= size {
313 let payloads = std::mem::take(&mut round.payloads);
314 round.completed_generation = my_generation;
315 round.completed_payloads = payloads;
316 round.generation += 1;
317 }
318 my_generation
319 }
320
321 fn round_ready(&self, key: &str, generation: usize) -> bool {
323 self.rounds.get(key).is_some_and(|round| {
324 round.completed_generation == generation && round.generation == generation + 1
325 })
326 }
327}
328
329#[derive(Debug, Clone)]
331pub struct TCPMPIBackend {
332 connections: Arc<RwLock<HashMap<usize, std::net::SocketAddr>>>,
334}
335
336#[cfg(feature = "mpi")]
338#[derive(Debug, Clone)]
339pub struct NativeMPIBackend {
340 comm_handle: usize,
342}
343
344#[derive(Debug)]
346pub struct MPIRequest {
347 id: usize,
349 request_type: MPIRequestType,
351 completed: Arc<Mutex<bool>>,
353}
354
355#[derive(Debug, Clone)]
357pub enum MPIRequestType {
358 Send { dest: usize, tag: i32 },
359 Recv { source: usize, tag: i32 },
360 Collective { operation: String },
361}
362
363#[derive(Debug)]
365pub struct LocalQuantumState {
366 amplitudes: Array1<Complex64>,
368 global_offset: usize,
370 local_qubits: Vec<usize>,
372 ghost_cells: GhostCells,
374}
375
376#[derive(Debug, Clone, Default)]
378pub struct GhostCells {
379 left: Vec<Complex64>,
381 right: Vec<Complex64>,
383 width: usize,
385}
386
387#[derive(Debug, Clone, Default)]
389pub struct MPISimulatorStats {
390 pub gates_executed: u64,
392 pub communication_time: Duration,
394 pub computation_time: Duration,
396 pub sync_count: u64,
398 pub bytes_sent: u64,
400 pub bytes_received: u64,
402 pub load_imbalance: f64,
404}
405
406#[derive(Debug)]
408pub struct StateSynchronizationManager {
409 strategy: SyncStrategy,
411 pending: Arc<Mutex<Vec<SyncOperation>>>,
413}
414
415#[derive(Debug, Clone, Copy)]
417pub enum SyncStrategy {
418 Eager,
420 Lazy,
422 Adaptive,
424}
425
426#[derive(Debug, Clone)]
428pub struct SyncOperation {
429 qubits: Vec<usize>,
431 op_type: SyncOpType,
433}
434
435#[derive(Debug, Clone)]
437pub enum SyncOpType {
438 BoundaryExchange,
439 GlobalReduction,
440 PartitionSwap,
441}
442
443#[derive(Debug)]
445pub struct GateDistributionHandler {
446 routing_table: Arc<RwLock<HashMap<usize, usize>>>,
448 gate_classifier: GateClassifier,
450}
451
452#[derive(Debug)]
454pub struct GateClassifier {
455 local_qubits: Vec<usize>,
457}
458
459impl MPIQuantumSimulator {
460 pub fn new(config: MPISimulatorConfig) -> QuantRS2Result<Self> {
462 let communicator = MPICommunicator::new()?;
464
465 let total_amplitudes = 1usize << config.total_qubits;
467 let local_size = total_amplitudes / communicator.size;
468 let global_offset = communicator.rank * local_size;
469
470 let local_state = LocalQuantumState {
472 amplitudes: Array1::zeros(local_size),
473 global_offset,
474 local_qubits: Self::calculate_local_qubits(
475 config.total_qubits,
476 communicator.rank,
477 communicator.size,
478 ),
479 ghost_cells: GhostCells::default(),
480 };
481
482 let sync_manager = StateSynchronizationManager {
484 strategy: SyncStrategy::Adaptive,
485 pending: Arc::new(Mutex::new(Vec::new())),
486 };
487
488 let gate_handler = GateDistributionHandler {
490 routing_table: Arc::new(RwLock::new(HashMap::new())),
491 gate_classifier: GateClassifier {
492 local_qubits: local_state.local_qubits.clone(),
493 },
494 };
495
496 Ok(Self {
497 communicator,
498 local_state: Arc::new(RwLock::new(local_state)),
499 config,
500 stats: Arc::new(Mutex::new(MPISimulatorStats::default())),
501 sync_manager,
502 gate_handler,
503 })
504 }
505
506 fn calculate_local_qubits(total_qubits: usize, rank: usize, size: usize) -> Vec<usize> {
508 let partition_bits = (size as f64).log2().ceil() as usize;
510 let local_bits = total_qubits - partition_bits;
511
512 (0..local_bits).collect()
514 }
515
516 pub fn initialize(&mut self) -> QuantRS2Result<()> {
518 let mut state = self
519 .local_state
520 .write()
521 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
522
523 state.amplitudes.fill(Complex64::new(0.0, 0.0));
525
526 if self.communicator.rank == 0 {
528 state.amplitudes[0] = Complex64::new(1.0, 0.0);
529 }
530
531 Ok(())
532 }
533
534 pub fn apply_single_qubit_gate(
536 &mut self,
537 qubit: usize,
538 gate_matrix: &Array2<Complex64>,
539 ) -> QuantRS2Result<()> {
540 let start = Instant::now();
541
542 let state = self
544 .local_state
545 .read()
546 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
547
548 if state.local_qubits.contains(&qubit) {
549 drop(state);
551 self.apply_local_single_qubit_gate(qubit, gate_matrix)?;
552 } else {
553 drop(state);
555 self.apply_distributed_single_qubit_gate(qubit, gate_matrix)?;
556 }
557
558 let mut stats = self
560 .stats
561 .lock()
562 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire stats lock".to_string()))?;
563 stats.gates_executed += 1;
564 stats.computation_time += start.elapsed();
565
566 Ok(())
567 }
568
569 fn apply_local_single_qubit_gate(
571 &self,
572 qubit: usize,
573 gate_matrix: &Array2<Complex64>,
574 ) -> QuantRS2Result<()> {
575 let mut state = self
576 .local_state
577 .write()
578 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
579
580 let n = state.amplitudes.len();
581 let stride = 1 << qubit;
582
583 let amplitudes = state.amplitudes.as_slice_mut().ok_or_else(|| {
585 QuantRS2Error::InvalidInput("Failed to get mutable slice".to_string())
586 })?;
587
588 for i in 0..n / 2 {
590 let i0 = (i / stride) * (2 * stride) + (i % stride);
591 let i1 = i0 + stride;
592
593 let a0 = amplitudes[i0];
594 let a1 = amplitudes[i1];
595
596 amplitudes[i0] = gate_matrix[[0, 0]] * a0 + gate_matrix[[0, 1]] * a1;
597 amplitudes[i1] = gate_matrix[[1, 0]] * a0 + gate_matrix[[1, 1]] * a1;
598 }
599
600 Ok(())
601 }
602
603 fn apply_distributed_single_qubit_gate(
605 &self,
606 qubit: usize,
607 gate_matrix: &Array2<Complex64>,
608 ) -> QuantRS2Result<()> {
609 let partition_bit = qubit - self.gate_handler.gate_classifier.local_qubits.len();
611 let partner = self.communicator.rank ^ (1 << partition_bit);
612
613 self.exchange_boundary_data(partner)?;
615
616 let mut state = self
618 .local_state
619 .write()
620 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
621
622 let n = state.amplitudes.len();
623 let local_qubits = state.local_qubits.len();
624 let local_stride = 1 << local_qubits;
625
626 let is_lower = (self.communicator.rank >> partition_bit) & 1 == 0;
628
629 for i in 0..n {
630 let global_i = state.global_offset + i;
631 let partner_i = global_i ^ local_stride;
632
633 let partner_amp = if is_lower {
635 state
636 .ghost_cells
637 .right
638 .get(i)
639 .copied()
640 .unwrap_or(Complex64::new(0.0, 0.0))
641 } else {
642 state
643 .ghost_cells
644 .left
645 .get(i)
646 .copied()
647 .unwrap_or(Complex64::new(0.0, 0.0))
648 };
649
650 let local_amp = state.amplitudes[i];
651
652 let (a0, a1) = if is_lower {
654 (local_amp, partner_amp)
655 } else {
656 (partner_amp, local_amp)
657 };
658
659 let new_amp = if is_lower {
660 gate_matrix[[0, 0]] * a0 + gate_matrix[[0, 1]] * a1
661 } else {
662 gate_matrix[[1, 0]] * a0 + gate_matrix[[1, 1]] * a1
663 };
664
665 state.amplitudes[i] = new_amp;
666 }
667
668 Ok(())
669 }
670
671 fn exchange_boundary_data(&self, partner: usize) -> QuantRS2Result<()> {
673 let state = self
674 .local_state
675 .read()
676 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
677
678 let send_data: Vec<Complex64> = state.amplitudes.iter().copied().collect();
680 drop(state);
681
682 let recv_data = self.communicator.sendrecv(&send_data, partner)?;
684
685 let mut state = self
687 .local_state
688 .write()
689 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
690
691 if self.communicator.rank < partner {
692 state.ghost_cells.right = recv_data;
693 } else {
694 state.ghost_cells.left = recv_data;
695 }
696
697 Ok(())
698 }
699
700 pub fn apply_two_qubit_gate(
702 &mut self,
703 control: usize,
704 target: usize,
705 gate_matrix: &Array2<Complex64>,
706 ) -> QuantRS2Result<()> {
707 let start = Instant::now();
708
709 let state = self
710 .local_state
711 .read()
712 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
713
714 let control_local = state.local_qubits.contains(&control);
715 let target_local = state.local_qubits.contains(&target);
716 drop(state);
717
718 match (control_local, target_local) {
719 (true, true) => {
720 self.apply_local_two_qubit_gate(control, target, gate_matrix)?;
722 }
723 (true, false) | (false, true) => {
724 self.apply_partial_distributed_gate(control, target, gate_matrix)?;
726 }
727 (false, false) => {
728 self.apply_full_distributed_gate(control, target, gate_matrix)?;
730 }
731 }
732
733 let mut stats = self
735 .stats
736 .lock()
737 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire stats lock".to_string()))?;
738 stats.gates_executed += 1;
739 stats.computation_time += start.elapsed();
740
741 Ok(())
742 }
743
744 fn apply_local_two_qubit_gate(
746 &self,
747 control: usize,
748 target: usize,
749 gate_matrix: &Array2<Complex64>,
750 ) -> QuantRS2Result<()> {
751 let mut state = self
752 .local_state
753 .write()
754 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
755
756 let n = state.amplitudes.len();
757 let control_stride = 1 << control;
758 let target_stride = 1 << target;
759
760 let (low_stride, high_stride) = if control < target {
762 (control_stride, target_stride)
763 } else {
764 (target_stride, control_stride)
765 };
766
767 for i in 0..n / 4 {
769 let base = (i / low_stride) * (2 * low_stride) + (i % low_stride);
771 let base = (base / high_stride) * (2 * high_stride) + (base % high_stride);
772
773 let i00 = base;
775 let i01 = base + target_stride;
776 let i10 = base + control_stride;
777 let i11 = base + control_stride + target_stride;
778
779 let a00 = state.amplitudes[i00];
781 let a01 = state.amplitudes[i01];
782 let a10 = state.amplitudes[i10];
783 let a11 = state.amplitudes[i11];
784
785 state.amplitudes[i00] = gate_matrix[[0, 0]] * a00
787 + gate_matrix[[0, 1]] * a01
788 + gate_matrix[[0, 2]] * a10
789 + gate_matrix[[0, 3]] * a11;
790 state.amplitudes[i01] = gate_matrix[[1, 0]] * a00
791 + gate_matrix[[1, 1]] * a01
792 + gate_matrix[[1, 2]] * a10
793 + gate_matrix[[1, 3]] * a11;
794 state.amplitudes[i10] = gate_matrix[[2, 0]] * a00
795 + gate_matrix[[2, 1]] * a01
796 + gate_matrix[[2, 2]] * a10
797 + gate_matrix[[2, 3]] * a11;
798 state.amplitudes[i11] = gate_matrix[[3, 0]] * a00
799 + gate_matrix[[3, 1]] * a01
800 + gate_matrix[[3, 2]] * a10
801 + gate_matrix[[3, 3]] * a11;
802 }
803
804 Ok(())
805 }
806
807 fn apply_partial_distributed_gate(
809 &self,
810 control: usize,
811 target: usize,
812 gate_matrix: &Array2<Complex64>,
813 ) -> QuantRS2Result<()> {
814 let state = self
816 .local_state
817 .read()
818 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
819
820 let (local_qubit, remote_qubit) = if state.local_qubits.contains(&control) {
821 (control, target)
822 } else {
823 (target, control)
824 };
825 drop(state);
826
827 let partition_bit = remote_qubit - self.gate_handler.gate_classifier.local_qubits.len();
829 let partner = self.communicator.rank ^ (1 << partition_bit);
830
831 self.exchange_boundary_data(partner)?;
833
834 let mut state = self
838 .local_state
839 .write()
840 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
841
842 let n = state.amplitudes.len();
843 let local_stride = 1 << local_qubit;
844
845 for i in 0..n / 2 {
846 let i0 = (i / local_stride) * (2 * local_stride) + (i % local_stride);
847 let i1 = i0 + local_stride;
848
849 let a0 = state.amplitudes[i0];
850 let a1 = state.amplitudes[i1];
851
852 state.amplitudes[i0] = gate_matrix[[0, 0]] * a0 + gate_matrix[[0, 1]] * a1;
855 state.amplitudes[i1] = gate_matrix[[1, 0]] * a0 + gate_matrix[[1, 1]] * a1;
856 }
857
858 Ok(())
859 }
860
861 fn apply_full_distributed_gate(
873 &self,
874 control: usize,
875 target: usize,
876 gate_matrix: &Array2<Complex64>,
877 ) -> QuantRS2Result<()> {
878 let local_qubits_len = self.gate_handler.gate_classifier.local_qubits.len();
879
880 let control_bit = control - local_qubits_len;
881 let target_bit = target - local_qubits_len;
882 if control_bit == target_bit {
883 return Err(QuantRS2Error::InvalidInput(
884 "apply_full_distributed_gate: control and target map to the same partition bit"
885 .to_string(),
886 ));
887 }
888
889 let rank = self.communicator.rank;
890 let own_control_val = (rank >> control_bit) & 1;
891 let own_target_val = (rank >> target_bit) & 1;
892
893 let control_partner = rank ^ (1 << control_bit);
894 let target_partner = rank ^ (1 << target_bit);
895 let diag_partner = rank ^ (1 << control_bit) ^ (1 << target_bit);
896
897 let local_amplitudes: Vec<Complex64> = {
900 let state = self.local_state.read().map_err(|_| {
901 QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string())
902 })?;
903 state.amplitudes.iter().copied().collect()
904 };
905
906 let control_flipped = self
907 .communicator
908 .sendrecv(&local_amplitudes, control_partner)?;
909 let target_flipped = self
910 .communicator
911 .sendrecv(&local_amplitudes, target_partner)?;
912 let both_flipped = self
913 .communicator
914 .sendrecv(&local_amplitudes, diag_partner)?;
915
916 let mut amps_by_bits: [Option<&[Complex64]>; 4] = [None; 4];
921 amps_by_bits[(own_control_val << 1) | own_target_val] = Some(local_amplitudes.as_slice());
922 amps_by_bits[((1 - own_control_val) << 1) | own_target_val] =
923 Some(control_flipped.as_slice());
924 amps_by_bits[(own_control_val << 1) | (1 - own_target_val)] =
925 Some(target_flipped.as_slice());
926 amps_by_bits[((1 - own_control_val) << 1) | (1 - own_target_val)] =
927 Some(both_flipped.as_slice());
928
929 let own_row = (own_control_val << 1) | own_target_val;
930
931 let mut state = self
932 .local_state
933 .write()
934 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
935
936 let n = state.amplitudes.len();
937 for i in 0..n {
938 let mut acc = Complex64::new(0.0, 0.0);
939 for (col, amps) in amps_by_bits.iter().enumerate() {
940 let amps = amps.ok_or_else(|| {
941 QuantRS2Error::InvalidInput(
942 "apply_full_distributed_gate: missing peer amplitude data".to_string(),
943 )
944 })?;
945 let value = amps.get(i).copied().unwrap_or(Complex64::new(0.0, 0.0));
946 acc += gate_matrix[[own_row, col]] * value;
947 }
948 state.amplitudes[i] = acc;
949 }
950
951 Ok(())
952 }
953
954 pub fn barrier(&self) -> QuantRS2Result<()> {
956 self.communicator.barrier()
957 }
958
959 pub fn get_probability_distribution(&self) -> QuantRS2Result<Vec<f64>> {
961 let state = self
962 .local_state
963 .read()
964 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
965
966 let local_probs: Vec<f64> = state.amplitudes.iter().map(|a| (a * a.conj()).re).collect();
968
969 drop(state);
970
971 let global_probs = self.communicator.gather(&local_probs, 0)?;
973
974 Ok(global_probs)
975 }
976
977 pub fn measure_all(&self) -> QuantRS2Result<Vec<bool>> {
979 let probs = self.get_probability_distribution()?;
981
982 if self.communicator.rank == 0 {
984 let mut rng = scirs2_core::random::thread_rng();
986 let random: f64 = rng.random();
987
988 let mut cumulative = 0.0;
989 let mut result_idx = 0;
990
991 for (i, &prob) in probs.iter().enumerate() {
992 cumulative += prob;
993 if random < cumulative {
994 result_idx = i;
995 break;
996 }
997 }
998
999 let result: Vec<bool> = (0..self.config.total_qubits)
1001 .map(|i| (result_idx >> i) & 1 == 1)
1002 .collect();
1003
1004 self.communicator.broadcast(&result, 0)
1006 } else {
1007 self.communicator.broadcast(&[], 0)
1009 }
1010 }
1011
1012 pub fn get_local_state(&self) -> QuantRS2Result<Array1<Complex64>> {
1014 let state = self
1015 .local_state
1016 .read()
1017 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire state lock".to_string()))?;
1018 Ok(state.amplitudes.clone())
1019 }
1020
1021 pub fn get_stats(&self) -> QuantRS2Result<MPISimulatorStats> {
1023 let stats = self
1024 .stats
1025 .lock()
1026 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire stats lock".to_string()))?;
1027 Ok(stats.clone())
1028 }
1029
1030 pub fn reset(&mut self) -> QuantRS2Result<()> {
1032 self.initialize()?;
1033
1034 let mut stats = self
1036 .stats
1037 .lock()
1038 .map_err(|_| QuantRS2Error::InvalidInput("Failed to acquire stats lock".to_string()))?;
1039 *stats = MPISimulatorStats::default();
1040
1041 Ok(())
1042 }
1043}
1044
1045impl MPICommunicator {
1046 pub fn new() -> QuantRS2Result<Self> {
1048 let shared_state = Arc::new((Mutex::new(SimulatedMPIState::default()), Condvar::new()));
1050 let backend = MPIBackend::Simulated(SimulatedMPIBackend { shared_state });
1051
1052 Ok(Self {
1053 rank: 0,
1054 size: 1,
1055 backend,
1056 buffer_pool: Arc::new(Mutex::new(Vec::new())),
1057 pending_requests: Arc::new(Mutex::new(Vec::new())),
1058 })
1059 }
1060
1061 #[must_use]
1063 pub fn with_config(rank: usize, size: usize, backend: MPIBackend) -> Self {
1064 Self {
1065 rank,
1066 size,
1067 backend,
1068 buffer_pool: Arc::new(Mutex::new(Vec::new())),
1069 pending_requests: Arc::new(Mutex::new(Vec::new())),
1070 }
1071 }
1072
1073 #[must_use]
1075 pub const fn rank(&self) -> usize {
1076 self.rank
1077 }
1078
1079 #[must_use]
1081 pub const fn size(&self) -> usize {
1082 self.size
1083 }
1084
1085 fn unsupported_backend(backend_name: &str, op: &str) -> QuantRS2Error {
1089 QuantRS2Error::UnsupportedOperation(format!(
1090 "{op}: the {backend_name} MPI backend requires a real connection to peer \
1091 process(es)/node(s), which is not available in this build; use \
1092 MPIBackend::Simulated for in-process multi-rank testing, or link a real \
1093 transport for {backend_name}"
1094 ))
1095 }
1096
1097 fn lock_poisoned() -> QuantRS2Error {
1098 QuantRS2Error::InvalidInput("MPI simulated backend lock was poisoned".to_string())
1099 }
1100
1101 pub fn barrier(&self) -> QuantRS2Result<()> {
1104 match &self.backend {
1105 MPIBackend::Simulated(sim) => {
1106 let (lock, cvar) = &*sim.shared_state;
1107 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1108 let my_generation = state.arrive("barrier", self.rank, self.size, Box::new(()));
1109 cvar.notify_all();
1110 loop {
1111 if state.round_ready("barrier", my_generation) {
1112 return Ok(());
1113 }
1114 state = cvar.wait(state).map_err(|_| Self::lock_poisoned())?;
1115 }
1116 }
1117 MPIBackend::TCP(_) => Err(Self::unsupported_backend("TCP", "barrier")),
1118 #[cfg(feature = "mpi")]
1119 MPIBackend::Native(_) => Err(Self::unsupported_backend("Native", "barrier")),
1120 }
1121 }
1122
1123 pub fn sendrecv(
1126 &self,
1127 send_data: &[Complex64],
1128 partner: usize,
1129 ) -> QuantRS2Result<Vec<Complex64>> {
1130 match &self.backend {
1131 MPIBackend::Simulated(sim) => {
1132 let (lock, cvar) = &*sim.shared_state;
1133 let mut bytes = Vec::with_capacity(send_data.len() * 16);
1134 for c in send_data {
1135 bytes.extend_from_slice(&c.re.to_le_bytes());
1136 bytes.extend_from_slice(&c.im.to_le_bytes());
1137 }
1138 {
1139 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1140 state.mailbox.insert((self.rank, partner), bytes);
1141 }
1142 cvar.notify_all();
1143
1144 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1145 let recv_bytes = loop {
1146 if let Some(bytes) = state.mailbox.remove(&(partner, self.rank)) {
1147 break bytes;
1148 }
1149 state = cvar.wait(state).map_err(|_| Self::lock_poisoned())?;
1150 };
1151 drop(state);
1152
1153 let recv = recv_bytes
1154 .chunks_exact(16)
1155 .map(|chunk| {
1156 let re = f64::from_le_bytes(chunk[0..8].try_into().unwrap_or([0u8; 8]));
1157 let im = f64::from_le_bytes(chunk[8..16].try_into().unwrap_or([0u8; 8]));
1158 Complex64::new(re, im)
1159 })
1160 .collect();
1161 Ok(recv)
1162 }
1163 MPIBackend::TCP(_) => Err(Self::unsupported_backend("TCP", "sendrecv")),
1164 #[cfg(feature = "mpi")]
1165 MPIBackend::Native(_) => Err(Self::unsupported_backend("Native", "sendrecv")),
1166 }
1167 }
1168
1169 pub fn gather<T: Clone + Send + 'static>(
1173 &self,
1174 local_data: &[T],
1175 root: usize,
1176 ) -> QuantRS2Result<Vec<T>> {
1177 match &self.backend {
1178 MPIBackend::Simulated(sim) => {
1179 let (lock, cvar) = &*sim.shared_state;
1180 let key = format!("gather:{root}");
1181 let payload: Box<dyn std::any::Any + Send> = Box::new(local_data.to_vec());
1182 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1183 let my_generation = state.arrive(&key, self.rank, self.size, payload);
1184 cvar.notify_all();
1185 loop {
1186 if state.round_ready(&key, my_generation) {
1187 break;
1188 }
1189 state = cvar.wait(state).map_err(|_| Self::lock_poisoned())?;
1190 }
1191
1192 if self.rank != root {
1193 return Ok(local_data.to_vec());
1194 }
1195
1196 let round = state.rounds.get(&key).ok_or_else(|| {
1197 QuantRS2Error::InvalidInput(
1198 "gather: collective round missing after completion".to_string(),
1199 )
1200 })?;
1201 let mut result = Vec::new();
1202 for boxed in round.completed_payloads.values() {
1203 let chunk = boxed.downcast_ref::<Vec<T>>().ok_or_else(|| {
1204 QuantRS2Error::InvalidInput(
1205 "gather: mismatched payload type across ranks".to_string(),
1206 )
1207 })?;
1208 result.extend(chunk.iter().cloned());
1209 }
1210 Ok(result)
1211 }
1212 MPIBackend::TCP(_) => Err(Self::unsupported_backend("TCP", "gather")),
1213 #[cfg(feature = "mpi")]
1214 MPIBackend::Native(_) => Err(Self::unsupported_backend("Native", "gather")),
1215 }
1216 }
1217
1218 pub fn broadcast<T: Clone + Send + 'static>(
1220 &self,
1221 data: &[T],
1222 root: usize,
1223 ) -> QuantRS2Result<Vec<T>> {
1224 match &self.backend {
1225 MPIBackend::Simulated(sim) => {
1226 let (lock, cvar) = &*sim.shared_state;
1227 let key = format!("broadcast:{root}");
1228 let payload: Box<dyn std::any::Any + Send> = Box::new(data.to_vec());
1229 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1230 let my_generation = state.arrive(&key, self.rank, self.size, payload);
1231 cvar.notify_all();
1232 loop {
1233 if state.round_ready(&key, my_generation) {
1234 break;
1235 }
1236 state = cvar.wait(state).map_err(|_| Self::lock_poisoned())?;
1237 }
1238
1239 let round = state.rounds.get(&key).ok_or_else(|| {
1240 QuantRS2Error::InvalidInput(
1241 "broadcast: collective round missing after completion".to_string(),
1242 )
1243 })?;
1244 let boxed = round.completed_payloads.get(&root).ok_or_else(|| {
1245 QuantRS2Error::InvalidInput(format!(
1246 "broadcast: root rank {root} did not participate in this round"
1247 ))
1248 })?;
1249 let result = boxed
1250 .downcast_ref::<Vec<T>>()
1251 .ok_or_else(|| {
1252 QuantRS2Error::InvalidInput(
1253 "broadcast: mismatched payload type across ranks".to_string(),
1254 )
1255 })?
1256 .clone();
1257 Ok(result)
1258 }
1259 MPIBackend::TCP(_) => Err(Self::unsupported_backend("TCP", "broadcast")),
1260 #[cfg(feature = "mpi")]
1261 MPIBackend::Native(_) => Err(Self::unsupported_backend("Native", "broadcast")),
1262 }
1263 }
1264
1265 pub fn allreduce(&self, local_data: &[f64], op: ReduceOp) -> QuantRS2Result<Vec<f64>> {
1268 match &self.backend {
1269 MPIBackend::Simulated(sim) => {
1270 let (lock, cvar) = &*sim.shared_state;
1271 let key = "allreduce".to_string();
1272 let payload: Box<dyn std::any::Any + Send> = Box::new(local_data.to_vec());
1273 let mut state = lock.lock().map_err(|_| Self::lock_poisoned())?;
1274 let my_generation = state.arrive(&key, self.rank, self.size, payload);
1275 cvar.notify_all();
1276 loop {
1277 if state.round_ready(&key, my_generation) {
1278 break;
1279 }
1280 state = cvar.wait(state).map_err(|_| Self::lock_poisoned())?;
1281 }
1282
1283 let round = state.rounds.get(&key).ok_or_else(|| {
1284 QuantRS2Error::InvalidInput(
1285 "allreduce: collective round missing after completion".to_string(),
1286 )
1287 })?;
1288 let mut reduced: Option<Vec<f64>> = None;
1289 for boxed in round.completed_payloads.values() {
1290 let v = boxed.downcast_ref::<Vec<f64>>().ok_or_else(|| {
1291 QuantRS2Error::InvalidInput(
1292 "allreduce: mismatched payload type across ranks".to_string(),
1293 )
1294 })?;
1295 reduced = Some(match reduced {
1296 None => v.clone(),
1297 Some(acc) => acc
1298 .iter()
1299 .zip(v.iter())
1300 .map(|(a, b)| match op {
1301 ReduceOp::Sum => a + b,
1302 ReduceOp::Max => a.max(*b),
1303 ReduceOp::Min => a.min(*b),
1304 ReduceOp::Prod => a * b,
1305 })
1306 .collect(),
1307 });
1308 }
1309 reduced.ok_or_else(|| {
1310 QuantRS2Error::InvalidInput("allreduce: empty collective round".to_string())
1311 })
1312 }
1313 MPIBackend::TCP(_) => Err(Self::unsupported_backend("TCP", "allreduce")),
1314 #[cfg(feature = "mpi")]
1315 MPIBackend::Native(_) => Err(Self::unsupported_backend("Native", "allreduce")),
1316 }
1317 }
1318}
1319
1320#[derive(Debug, Clone, Copy)]
1322pub enum ReduceOp {
1323 Sum,
1324 Max,
1325 Min,
1326 Prod,
1327}
1328
1329#[derive(Debug, Clone)]
1331pub struct MPISimulationResult {
1332 pub measurements: Vec<bool>,
1334 pub probabilities: Vec<f64>,
1336 pub stats: MPISimulatorStats,
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342 use super::*;
1343
1344 #[test]
1345 fn test_mpi_simulator_creation() {
1346 let config = MPISimulatorConfig {
1347 total_qubits: 4,
1348 ..Default::default()
1349 };
1350 let simulator = MPIQuantumSimulator::new(config);
1351 assert!(simulator.is_ok());
1352 }
1353
1354 #[test]
1355 fn test_mpi_simulator_initialization() {
1356 let config = MPISimulatorConfig {
1357 total_qubits: 4,
1358 ..Default::default()
1359 };
1360 let mut simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1361 assert!(simulator.initialize().is_ok());
1362
1363 let state = simulator
1364 .get_local_state()
1365 .expect("failed to get local state");
1366 assert_eq!(state[0], Complex64::new(1.0, 0.0));
1367 }
1368
1369 #[test]
1370 fn test_mpi_communicator_creation() {
1371 let comm = MPICommunicator::new();
1372 assert!(comm.is_ok());
1373
1374 let comm = comm.expect("failed to create communicator");
1375 assert_eq!(comm.rank(), 0);
1376 assert_eq!(comm.size(), 1);
1377 }
1378
1379 #[test]
1380 fn test_single_qubit_gate() {
1381 let config = MPISimulatorConfig {
1382 total_qubits: 4,
1383 ..Default::default()
1384 };
1385 let mut simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1386 simulator.initialize().expect("failed to initialize");
1387
1388 let x_gate = Array2::from_shape_vec(
1390 (2, 2),
1391 vec![
1392 Complex64::new(0.0, 0.0),
1393 Complex64::new(1.0, 0.0),
1394 Complex64::new(1.0, 0.0),
1395 Complex64::new(0.0, 0.0),
1396 ],
1397 )
1398 .expect("valid 2x2 matrix shape");
1399
1400 let result = simulator.apply_single_qubit_gate(0, &x_gate);
1401 assert!(result.is_ok());
1402 }
1403
1404 #[test]
1405 fn test_probability_distribution() {
1406 let config = MPISimulatorConfig {
1407 total_qubits: 2,
1408 ..Default::default()
1409 };
1410 let mut simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1411 simulator.initialize().expect("failed to initialize");
1412
1413 let probs = simulator
1414 .get_probability_distribution()
1415 .expect("failed to get probability distribution");
1416 assert_eq!(probs.len(), 4);
1417 assert!((probs[0] - 1.0).abs() < 1e-10);
1418 }
1419
1420 #[test]
1421 fn test_mpi_stats() {
1422 let config = MPISimulatorConfig {
1423 total_qubits: 4,
1424 ..Default::default()
1425 };
1426 let simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1427
1428 let stats = simulator.get_stats().expect("failed to get stats");
1429 assert_eq!(stats.gates_executed, 0);
1430 }
1431
1432 #[test]
1433 fn test_distribution_strategies() {
1434 let strategies = vec![
1435 MPIDistributionStrategy::AmplitudePartition,
1436 MPIDistributionStrategy::QubitPartition,
1437 MPIDistributionStrategy::HybridPartition,
1438 MPIDistributionStrategy::GateAwarePartition,
1439 MPIDistributionStrategy::HilbertCurvePartition,
1440 ];
1441
1442 for strategy in strategies {
1443 let config = MPISimulatorConfig {
1444 total_qubits: 4,
1445 distribution_strategy: strategy,
1446 ..Default::default()
1447 };
1448 let simulator = MPIQuantumSimulator::new(config);
1449 assert!(simulator.is_ok());
1450 }
1451 }
1452
1453 #[test]
1454 fn test_reset() {
1455 let config = MPISimulatorConfig {
1456 total_qubits: 4,
1457 ..Default::default()
1458 };
1459 let mut simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1460 simulator.initialize().expect("failed to initialize");
1461
1462 let h_gate = Array2::from_shape_vec(
1464 (2, 2),
1465 vec![
1466 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
1467 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
1468 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
1469 Complex64::new(-1.0 / 2.0_f64.sqrt(), 0.0),
1470 ],
1471 )
1472 .expect("valid 2x2 matrix shape");
1473 simulator
1474 .apply_single_qubit_gate(0, &h_gate)
1475 .expect("failed to apply gate");
1476
1477 simulator.reset().expect("failed to reset");
1479
1480 let state = simulator
1482 .get_local_state()
1483 .expect("failed to get local state");
1484 assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1485 }
1486
1487 #[test]
1488 fn test_collective_optimization_config() {
1489 let config = CollectiveOptimization {
1490 use_nonblocking: true,
1491 enable_fusion: true,
1492 buffer_size: 32 * 1024 * 1024,
1493 allreduce_algorithm: AllreduceAlgorithm::Ring,
1494 broadcast_algorithm: BroadcastAlgorithm::Pipeline,
1495 };
1496
1497 assert!(config.use_nonblocking);
1498 assert!(config.enable_fusion);
1499 assert_eq!(config.buffer_size, 32 * 1024 * 1024);
1500 }
1501
1502 #[test]
1503 fn test_checkpoint_config() {
1504 let config = CheckpointConfig {
1505 enable: true,
1506 interval: 500,
1507 storage_path: "/custom/path".to_string(),
1508 use_compression: false,
1509 };
1510
1511 assert!(config.enable);
1512 assert_eq!(config.interval, 500);
1513 assert!(!config.use_compression);
1514 }
1515
1516 #[test]
1517 fn test_two_qubit_gate() {
1518 let config = MPISimulatorConfig {
1519 total_qubits: 4,
1520 ..Default::default()
1521 };
1522 let mut simulator = MPIQuantumSimulator::new(config).expect("failed to create simulator");
1523 simulator.initialize().expect("failed to initialize");
1524
1525 let cnot_gate = Array2::from_shape_vec(
1527 (4, 4),
1528 vec![
1529 Complex64::new(1.0, 0.0),
1530 Complex64::new(0.0, 0.0),
1531 Complex64::new(0.0, 0.0),
1532 Complex64::new(0.0, 0.0),
1533 Complex64::new(0.0, 0.0),
1534 Complex64::new(1.0, 0.0),
1535 Complex64::new(0.0, 0.0),
1536 Complex64::new(0.0, 0.0),
1537 Complex64::new(0.0, 0.0),
1538 Complex64::new(0.0, 0.0),
1539 Complex64::new(0.0, 0.0),
1540 Complex64::new(1.0, 0.0),
1541 Complex64::new(0.0, 0.0),
1542 Complex64::new(0.0, 0.0),
1543 Complex64::new(1.0, 0.0),
1544 Complex64::new(0.0, 0.0),
1545 ],
1546 )
1547 .expect("valid 4x4 matrix shape");
1548
1549 let result = simulator.apply_two_qubit_gate(0, 1, &cnot_gate);
1550 assert!(result.is_ok());
1551 }
1552
1553 #[test]
1559 fn test_simulated_backend_sendrecv_exchanges_real_data() {
1560 let shared = Arc::new((Mutex::new(SimulatedMPIState::default()), Condvar::new()));
1561 let comm0 = MPICommunicator::with_config(
1562 0,
1563 2,
1564 MPIBackend::Simulated(SimulatedMPIBackend {
1565 shared_state: shared.clone(),
1566 }),
1567 );
1568 let comm1 = MPICommunicator::with_config(
1569 1,
1570 2,
1571 MPIBackend::Simulated(SimulatedMPIBackend {
1572 shared_state: shared.clone(),
1573 }),
1574 );
1575
1576 let handle0 = std::thread::spawn(move || {
1577 comm0.sendrecv(&[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)], 1)
1578 });
1579 let handle1 = std::thread::spawn(move || {
1580 comm1.sendrecv(&[Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)], 0)
1581 });
1582
1583 let recv0 = handle0
1584 .join()
1585 .expect("rank0 thread panicked")
1586 .expect("rank0 sendrecv failed");
1587 let recv1 = handle1
1588 .join()
1589 .expect("rank1 thread panicked")
1590 .expect("rank1 sendrecv failed");
1591
1592 assert_eq!(
1594 recv0,
1595 vec![Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)]
1596 );
1597 assert_eq!(
1598 recv1,
1599 vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)]
1600 );
1601 }
1602
1603 #[test]
1610 fn test_simulated_backend_collectives_are_real() {
1611 let shared = Arc::new((Mutex::new(SimulatedMPIState::default()), Condvar::new()));
1612 let num_ranks = 3usize;
1613 let mut handles = Vec::new();
1614 for rank in 0..num_ranks {
1615 let comm = MPICommunicator::with_config(
1616 rank,
1617 num_ranks,
1618 MPIBackend::Simulated(SimulatedMPIBackend {
1619 shared_state: shared.clone(),
1620 }),
1621 );
1622 handles.push(std::thread::spawn(move || {
1623 let gathered = comm
1624 .gather(&[rank as f64], 0)
1625 .expect("gather should succeed");
1626 let broadcasted = comm
1627 .broadcast(if rank == 0 { &[42i64] } else { &[] }, 0)
1628 .expect("broadcast should succeed");
1629 let reduced = comm
1630 .allreduce(&[(rank + 1) as f64], ReduceOp::Sum)
1631 .expect("allreduce should succeed");
1632 (rank, gathered, broadcasted, reduced)
1633 }));
1634 }
1635
1636 for handle in handles {
1637 let (rank, gathered, broadcasted, reduced) = handle.join().expect("thread panicked");
1638 if rank == 0 {
1639 assert_eq!(gathered, vec![0.0, 1.0, 2.0]);
1640 } else {
1641 assert_eq!(gathered, vec![rank as f64]);
1644 }
1645 assert_eq!(broadcasted, vec![42i64]);
1646 assert_eq!(reduced, vec![6.0]);
1648 }
1649 }
1650
1651 #[test]
1659 fn test_full_distributed_two_qubit_gate_applies_real_math() {
1660 let total_qubits = 3usize;
1661 let num_ranks = 4usize;
1662 let shared = Arc::new((Mutex::new(SimulatedMPIState::default()), Condvar::new()));
1663
1664 let initial_index = 2usize;
1667 let cnot = Array2::from_shape_vec(
1670 (4, 4),
1671 vec![
1672 Complex64::new(1.0, 0.0),
1673 Complex64::new(0.0, 0.0),
1674 Complex64::new(0.0, 0.0),
1675 Complex64::new(0.0, 0.0),
1676 Complex64::new(0.0, 0.0),
1677 Complex64::new(1.0, 0.0),
1678 Complex64::new(0.0, 0.0),
1679 Complex64::new(0.0, 0.0),
1680 Complex64::new(0.0, 0.0),
1681 Complex64::new(0.0, 0.0),
1682 Complex64::new(0.0, 0.0),
1683 Complex64::new(1.0, 0.0),
1684 Complex64::new(0.0, 0.0),
1685 Complex64::new(0.0, 0.0),
1686 Complex64::new(1.0, 0.0),
1687 Complex64::new(0.0, 0.0),
1688 ],
1689 )
1690 .expect("valid 4x4 CNOT shape");
1691
1692 let local_size = (1usize << total_qubits) / num_ranks;
1693 let mut handles = Vec::new();
1694 for rank in 0..num_ranks {
1695 let mut amplitudes = Array1::zeros(local_size);
1696 let global_offset = rank * local_size;
1697 if initial_index >= global_offset && initial_index < global_offset + local_size {
1698 amplitudes[initial_index - global_offset] = Complex64::new(1.0, 0.0);
1699 }
1700
1701 let local_state = LocalQuantumState {
1702 amplitudes,
1703 global_offset,
1704 local_qubits: MPIQuantumSimulator::calculate_local_qubits(
1705 total_qubits,
1706 rank,
1707 num_ranks,
1708 ),
1709 ghost_cells: GhostCells::default(),
1710 };
1711
1712 let communicator = MPICommunicator::with_config(
1713 rank,
1714 num_ranks,
1715 MPIBackend::Simulated(SimulatedMPIBackend {
1716 shared_state: shared.clone(),
1717 }),
1718 );
1719 let gate_handler = GateDistributionHandler {
1720 routing_table: Arc::new(RwLock::new(HashMap::new())),
1721 gate_classifier: GateClassifier {
1722 local_qubits: local_state.local_qubits.clone(),
1723 },
1724 };
1725 let mut simulator = MPIQuantumSimulator {
1726 communicator,
1727 local_state: Arc::new(RwLock::new(local_state)),
1728 config: MPISimulatorConfig {
1729 total_qubits,
1730 ..Default::default()
1731 },
1732 stats: Arc::new(Mutex::new(MPISimulatorStats::default())),
1733 sync_manager: StateSynchronizationManager {
1734 strategy: SyncStrategy::Adaptive,
1735 pending: Arc::new(Mutex::new(Vec::new())),
1736 },
1737 gate_handler,
1738 };
1739
1740 let cnot = cnot.clone();
1741 handles.push(std::thread::spawn(move || {
1742 simulator
1743 .apply_two_qubit_gate(1, 2, &cnot)
1744 .expect("apply_two_qubit_gate should succeed");
1745 (rank, simulator.get_local_state().expect("get_local_state"))
1746 }));
1747 }
1748
1749 let expected_index = 6usize;
1752
1753 for handle in handles {
1754 let (rank, state) = handle.join().expect("rank thread panicked");
1755 let global_offset = rank * local_size;
1756 for (i, amp) in state.iter().enumerate() {
1757 let global_i = global_offset + i;
1758 if global_i == expected_index {
1759 assert!(
1760 (amp.norm() - 1.0).abs() < 1e-10,
1761 "expected amplitude 1.0 at global index {expected_index}, got {amp:?} on rank {rank}"
1762 );
1763 } else {
1764 assert!(
1765 amp.norm() < 1e-10,
1766 "expected zero amplitude at global index {global_i}, got {amp:?} on rank {rank}"
1767 );
1768 }
1769 }
1770 }
1771 }
1772}