Skip to main content

quantrs2_sim/
mpi_distributed_simulation.rs

1//! MPI-based Distributed Quantum Simulation
2//!
3//! This module provides Message Passing Interface (MPI) support for distributed
4//! quantum simulation across multiple compute nodes. It enables simulation of
5//! extremely large quantum systems (50+ qubits) by distributing the quantum state
6//! across multiple nodes and coordinating quantum operations through MPI.
7//!
8//! # Features
9//! - MPI communicator abstraction for quantum simulation
10//! - Distributed quantum state management with automatic partitioning
11//! - Collective operations optimized for quantum state vectors
12//! - Support for both simulated MPI (testing) and real MPI backends
13//! - Integration with `SciRS2` parallel operations
14
15use 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/// MPI-based distributed quantum simulator
30///
31/// This simulator uses MPI for inter-node communication to enable
32/// simulation of quantum systems larger than what can fit in a single
33/// node's memory.
34#[derive(Debug)]
35pub struct MPIQuantumSimulator {
36    /// MPI communicator for quantum operations
37    communicator: MPICommunicator,
38    /// Local quantum state partition
39    local_state: Arc<RwLock<LocalQuantumState>>,
40    /// Configuration for the MPI simulator
41    config: MPISimulatorConfig,
42    /// Performance statistics
43    stats: Arc<Mutex<MPISimulatorStats>>,
44    /// State synchronization manager
45    sync_manager: StateSynchronizationManager,
46    /// Gate distribution handler
47    gate_handler: GateDistributionHandler,
48}
49
50/// Configuration for MPI-based quantum simulation
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MPISimulatorConfig {
53    /// Total number of qubits in the simulation
54    pub total_qubits: usize,
55    /// Distribution strategy for quantum state
56    pub distribution_strategy: MPIDistributionStrategy,
57    /// Collective operation optimization settings
58    pub collective_optimization: CollectiveOptimization,
59    /// Communication overlap settings
60    pub overlap_config: CommunicationOverlapConfig,
61    /// Checkpointing configuration
62    pub checkpoint_config: CheckpointConfig,
63    /// Memory management settings
64    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/// Strategy for distributing quantum state across MPI nodes
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum MPIDistributionStrategy {
83    /// Partition state vector by amplitude indices
84    AmplitudePartition,
85    /// Partition by qubit subsets (for localized operations)
86    QubitPartition,
87    /// Hybrid partitioning based on circuit structure
88    HybridPartition,
89    /// Gate-aware dynamic partitioning
90    GateAwarePartition,
91    /// Hilbert curve space-filling for data locality
92    HilbertCurvePartition,
93}
94
95/// Optimization settings for MPI collective operations
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct CollectiveOptimization {
98    /// Use non-blocking collectives when possible
99    pub use_nonblocking: bool,
100    /// Enable collective operation fusion
101    pub enable_fusion: bool,
102    /// Buffer size for collective operations
103    pub buffer_size: usize,
104    /// Allreduce algorithm selection
105    pub allreduce_algorithm: AllreduceAlgorithm,
106    /// Broadcast algorithm selection
107    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, // 16MB
116            allreduce_algorithm: AllreduceAlgorithm::RecursiveDoubling,
117            broadcast_algorithm: BroadcastAlgorithm::BinomialTree,
118        }
119    }
120}
121
122/// Allreduce algorithm variants
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124pub enum AllreduceAlgorithm {
125    /// Ring-based allreduce (bandwidth optimal)
126    Ring,
127    /// Recursive doubling (latency optimal)
128    RecursiveDoubling,
129    /// Rabenseifner algorithm (hybrid)
130    Rabenseifner,
131    /// Automatic selection based on message size
132    Automatic,
133}
134
135/// Broadcast algorithm variants
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137pub enum BroadcastAlgorithm {
138    /// Binomial tree broadcast
139    BinomialTree,
140    /// Scatter + Allgather
141    ScatterAllgather,
142    /// Pipeline broadcast
143    Pipeline,
144    /// Automatic selection
145    Automatic,
146}
147
148/// Configuration for overlapping communication with computation
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct CommunicationOverlapConfig {
151    /// Enable communication/computation overlap
152    pub enable_overlap: bool,
153    /// Number of pipeline stages
154    pub pipeline_stages: usize,
155    /// Prefetch distance for communication
156    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/// Checkpointing configuration for fault tolerance
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct CheckpointConfig {
172    /// Enable periodic checkpointing
173    pub enable: bool,
174    /// Checkpoint interval (number of operations)
175    pub interval: usize,
176    /// Checkpoint storage path
177    pub storage_path: String,
178    /// Use compression for checkpoints
179    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/// Memory management configuration
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct MemoryConfig {
196    /// Maximum memory per node (bytes)
197    pub max_memory_per_node: usize,
198    /// Enable memory pooling
199    pub enable_pooling: bool,
200    /// Pool size for temporary allocations
201    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, // 64GB
208            enable_pooling: true,
209            pool_size: 1024 * 1024 * 1024, // 1GB pool
210        }
211    }
212}
213
214/// MPI communicator abstraction for quantum operations
215#[derive(Debug)]
216pub struct MPICommunicator {
217    /// MPI rank of this process
218    rank: usize,
219    /// Total number of MPI processes
220    size: usize,
221    /// Communication backend
222    backend: MPIBackend,
223    /// Message buffer pool
224    buffer_pool: Arc<Mutex<Vec<Vec<u8>>>>,
225    /// Pending requests for non-blocking operations
226    pending_requests: Arc<Mutex<Vec<MPIRequest>>>,
227}
228
229/// MPI backend implementations
230#[derive(Debug, Clone)]
231pub enum MPIBackend {
232    /// Simulated MPI for testing (single-process simulation)
233    Simulated(SimulatedMPIBackend),
234    /// Native MPI backend (requires mpi feature)
235    #[cfg(feature = "mpi")]
236    Native(NativeMPIBackend),
237    /// TCP-based fallback implementation
238    TCP(TCPMPIBackend),
239}
240
241/// Simulated MPI backend for testing
242///
243/// Unlike the TCP/Native backends (which need genuinely separate OS
244/// processes/nodes to talk to), this backend performs *real* in-process
245/// message passing between multiple [`MPICommunicator`] instances that
246/// share the same [`SimulatedMPIState`] (via a cloned `Arc`), each
247/// representing one rank and typically driven from its own thread. This
248/// makes it possible to exercise genuine multi-rank collective semantics
249/// (point-to-point exchange, gather, broadcast, allreduce, barrier)
250/// entirely locally, without any real network.
251#[derive(Debug, Clone)]
252pub struct SimulatedMPIBackend {
253    /// Shared state for all "processes", guarded by a condition variable so
254    /// that ranks can rendezvous with each other.
255    shared_state: Arc<(Mutex<SimulatedMPIState>, Condvar)>,
256}
257
258/// One in-progress (or just-completed) collective round.
259///
260/// Uses the classic "sense reversing" barrier pattern: ranks arrive and
261/// stash their payload under the round's current `generation`; once
262/// `size` ranks have arrived the round is closed out (`completed_*` is
263/// populated and `generation` is bumped), so any rank that arrives after
264/// that instant starts a fresh round instead of corrupting the old one.
265#[derive(Default)]
266struct CollectiveRound {
267    /// Generation number currently accepting arrivals.
268    generation: usize,
269    /// Payloads contributed by ranks that have arrived for `generation`.
270    payloads: BTreeMap<usize, Box<dyn std::any::Any + Send>>,
271    /// Generation number of the most recently completed round.
272    completed_generation: usize,
273    /// Payloads collected once `completed_generation` finished, kept
274    /// around (in rank order) so every arriving rank can read the result.
275    completed_payloads: BTreeMap<usize, Box<dyn std::any::Any + Send>>,
276}
277
278/// Shared state for simulated MPI: a real (in-process) mailbox for
279/// point-to-point exchange plus a set of named collective rounds.
280#[derive(Default)]
281pub struct SimulatedMPIState {
282    /// Point-to-point mailbox keyed by `(from_rank, to_rank)`.
283    mailbox: HashMap<(usize, usize), Vec<u8>>,
284    /// Named collective rounds (e.g. `"gather:0"`, `"broadcast:0"`,
285    /// `"allreduce"`, `"barrier"`).
286    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    /// Register `rank`'s arrival (with `payload`) at the named collective
300    /// round, closing the round out if this was the last of `size` ranks.
301    /// Returns the generation number this arrival belongs to.
302    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    /// True once the round `generation` for `key` has fully completed.
322    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/// TCP-based MPI backend
330#[derive(Debug, Clone)]
331pub struct TCPMPIBackend {
332    /// Connections to other ranks
333    connections: Arc<RwLock<HashMap<usize, std::net::SocketAddr>>>,
334}
335
336/// Native MPI backend (placeholder for real MPI integration)
337#[cfg(feature = "mpi")]
338#[derive(Debug, Clone)]
339pub struct NativeMPIBackend {
340    /// MPI communicator handle (placeholder)
341    comm_handle: usize,
342}
343
344/// MPI request handle for non-blocking operations
345#[derive(Debug)]
346pub struct MPIRequest {
347    /// Request ID
348    id: usize,
349    /// Request type
350    request_type: MPIRequestType,
351    /// Completion status
352    completed: Arc<Mutex<bool>>,
353}
354
355/// Types of MPI requests
356#[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/// Local quantum state partition
364#[derive(Debug)]
365pub struct LocalQuantumState {
366    /// State vector partition (local amplitudes)
367    amplitudes: Array1<Complex64>,
368    /// Global index offset for this partition
369    global_offset: usize,
370    /// Qubit indices managed by this partition
371    local_qubits: Vec<usize>,
372    /// Ghost cells for boundary communication
373    ghost_cells: GhostCells,
374}
375
376/// Ghost cells for efficient boundary communication
377#[derive(Debug, Clone, Default)]
378pub struct GhostCells {
379    /// Left ghost region
380    left: Vec<Complex64>,
381    /// Right ghost region
382    right: Vec<Complex64>,
383    /// Ghost cell width
384    width: usize,
385}
386
387/// Statistics for MPI quantum simulator
388#[derive(Debug, Clone, Default)]
389pub struct MPISimulatorStats {
390    /// Total gates executed
391    pub gates_executed: u64,
392    /// Total communication time
393    pub communication_time: Duration,
394    /// Total computation time
395    pub computation_time: Duration,
396    /// Number of synchronization points
397    pub sync_count: u64,
398    /// Bytes sent
399    pub bytes_sent: u64,
400    /// Bytes received
401    pub bytes_received: u64,
402    /// Load imbalance factor
403    pub load_imbalance: f64,
404}
405
406/// State synchronization manager
407#[derive(Debug)]
408pub struct StateSynchronizationManager {
409    /// Synchronization strategy
410    strategy: SyncStrategy,
411    /// Pending sync operations
412    pending: Arc<Mutex<Vec<SyncOperation>>>,
413}
414
415/// Synchronization strategy
416#[derive(Debug, Clone, Copy)]
417pub enum SyncStrategy {
418    /// Synchronize after every gate
419    Eager,
420    /// Batch synchronizations
421    Lazy,
422    /// Adaptive based on circuit structure
423    Adaptive,
424}
425
426/// Pending synchronization operation
427#[derive(Debug, Clone)]
428pub struct SyncOperation {
429    /// Qubits involved
430    qubits: Vec<usize>,
431    /// Operation type
432    op_type: SyncOpType,
433}
434
435/// Types of synchronization operations
436#[derive(Debug, Clone)]
437pub enum SyncOpType {
438    BoundaryExchange,
439    GlobalReduction,
440    PartitionSwap,
441}
442
443/// Gate distribution handler
444#[derive(Debug)]
445pub struct GateDistributionHandler {
446    /// Gate routing table
447    routing_table: Arc<RwLock<HashMap<usize, usize>>>,
448    /// Local vs distributed gate classification
449    gate_classifier: GateClassifier,
450}
451
452/// Gate classifier for local vs distributed execution
453#[derive(Debug)]
454pub struct GateClassifier {
455    /// Local qubit set for this partition
456    local_qubits: Vec<usize>,
457}
458
459impl MPIQuantumSimulator {
460    /// Create a new MPI-based quantum simulator
461    pub fn new(config: MPISimulatorConfig) -> QuantRS2Result<Self> {
462        // Initialize MPI communicator
463        let communicator = MPICommunicator::new()?;
464
465        // Calculate local partition size
466        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        // Initialize local quantum state
471        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        // Initialize synchronization manager
483        let sync_manager = StateSynchronizationManager {
484            strategy: SyncStrategy::Adaptive,
485            pending: Arc::new(Mutex::new(Vec::new())),
486        };
487
488        // Initialize gate distribution handler
489        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    /// Calculate which qubits are local to this partition
507    fn calculate_local_qubits(total_qubits: usize, rank: usize, size: usize) -> Vec<usize> {
508        // For amplitude partitioning, higher qubits determine partition
509        let partition_bits = (size as f64).log2().ceil() as usize;
510        let local_bits = total_qubits - partition_bits;
511
512        // Local qubits are the lower-order bits
513        (0..local_bits).collect()
514    }
515
516    /// Initialize the quantum state to |0...0>
517    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        // Set all amplitudes to 0
524        state.amplitudes.fill(Complex64::new(0.0, 0.0));
525
526        // Only rank 0 has the |0...0> amplitude
527        if self.communicator.rank == 0 {
528            state.amplitudes[0] = Complex64::new(1.0, 0.0);
529        }
530
531        Ok(())
532    }
533
534    /// Apply a single-qubit gate
535    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        // Check if qubit is local
543        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            // Local gate application
550            drop(state);
551            self.apply_local_single_qubit_gate(qubit, gate_matrix)?;
552        } else {
553            // Distributed gate application
554            drop(state);
555            self.apply_distributed_single_qubit_gate(qubit, gate_matrix)?;
556        }
557
558        // Update statistics
559        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    /// Apply a single-qubit gate locally
570    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        // Apply gate in parallel using SciRS2 parallel_ops
584        let amplitudes = state.amplitudes.as_slice_mut().ok_or_else(|| {
585            QuantRS2Error::InvalidInput("Failed to get mutable slice".to_string())
586        })?;
587
588        // Process pairs of amplitudes
589        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    /// Apply a single-qubit gate that requires distribution
604    fn apply_distributed_single_qubit_gate(
605        &self,
606        qubit: usize,
607        gate_matrix: &Array2<Complex64>,
608    ) -> QuantRS2Result<()> {
609        // Determine partner rank for communication
610        let partition_bit = qubit - self.gate_handler.gate_classifier.local_qubits.len();
611        let partner = self.communicator.rank ^ (1 << partition_bit);
612
613        // Exchange boundary data with partner
614        self.exchange_boundary_data(partner)?;
615
616        // Apply gate with boundary data
617        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        // Determine if we're the lower or upper partition
627        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            // Get partner amplitude from ghost cells
634            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            // Apply gate transformation
653            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    /// Exchange boundary data with a partner rank
672    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        // Prepare send buffer
679        let send_data: Vec<Complex64> = state.amplitudes.iter().copied().collect();
680        drop(state);
681
682        // Exchange data with partner
683        let recv_data = self.communicator.sendrecv(&send_data, partner)?;
684
685        // Update ghost cells
686        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    /// Apply a two-qubit gate
701    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                // Both qubits local - local gate application
721                self.apply_local_two_qubit_gate(control, target, gate_matrix)?;
722            }
723            (true, false) | (false, true) => {
724                // One qubit local - partial distribution
725                self.apply_partial_distributed_gate(control, target, gate_matrix)?;
726            }
727            (false, false) => {
728                // Both qubits remote - full distribution
729                self.apply_full_distributed_gate(control, target, gate_matrix)?;
730            }
731        }
732
733        // Update statistics
734        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    /// Apply a two-qubit gate locally
745    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        // Ensure consistent ordering
761        let (low_stride, high_stride) = if control < target {
762            (control_stride, target_stride)
763        } else {
764            (target_stride, control_stride)
765        };
766
767        // Apply gate to all 4-amplitude groups
768        for i in 0..n / 4 {
769            // Calculate base index
770            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            // Calculate all four indices
774            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            // Get amplitudes
780            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            // Apply 4x4 gate matrix
786            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    /// Apply partially distributed gate (one local, one remote qubit)
808    fn apply_partial_distributed_gate(
809        &self,
810        control: usize,
811        target: usize,
812        gate_matrix: &Array2<Complex64>,
813    ) -> QuantRS2Result<()> {
814        // Determine which qubit is local
815        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        // Determine partner for remote qubit
828        let partition_bit = remote_qubit - self.gate_handler.gate_classifier.local_qubits.len();
829        let partner = self.communicator.rank ^ (1 << partition_bit);
830
831        // Exchange partial state
832        self.exchange_boundary_data(partner)?;
833
834        // Apply gate with partial distribution
835        // This is a simplified version - full implementation would need
836        // more sophisticated handling of the 4-amplitude groups
837        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            // Apply conditional transformation based on gate structure
853            // This is simplified - real implementation needs full 4x4 matrix
854            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    /// Apply fully distributed gate (both qubits remote)
862    ///
863    /// Both `control` and `target` live in the partition-selecting (high)
864    /// bits of the global amplitude index: every amplitude *this* rank
865    /// holds shares the same fixed (control, target) bit values, which are
866    /// encoded directly in this rank's own MPI rank index. To apply the
867    /// full 4x4 `gate_matrix` we must combine our own amplitudes with the
868    /// amplitudes held by the three peer ranks that own the other three
869    /// (control, target) combinations -- real point-to-point exchange with
870    /// each of them, then a genuine matrix-vector product per index using
871    /// this rank's row of `gate_matrix`.
872    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        // Real exchange (not a placeholder): send our whole local partition
898        // to each peer and receive theirs in return.
899        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        // Slot the four peers' data by their true (control, target) bit
917        // values so we index `gate_matrix` the same way
918        // `apply_local_two_qubit_gate` does (row/col = 2*control + target),
919        // regardless of which combination this rank happens to own.
920        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    /// Perform a global barrier synchronization
955    pub fn barrier(&self) -> QuantRS2Result<()> {
956        self.communicator.barrier()
957    }
958
959    /// Compute global probability distribution
960    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        // Compute local probabilities
967        let local_probs: Vec<f64> = state.amplitudes.iter().map(|a| (a * a.conj()).re).collect();
968
969        drop(state);
970
971        // Gather all probabilities to rank 0
972        let global_probs = self.communicator.gather(&local_probs, 0)?;
973
974        Ok(global_probs)
975    }
976
977    /// Measure all qubits
978    pub fn measure_all(&self) -> QuantRS2Result<Vec<bool>> {
979        // Get global probability distribution
980        let probs = self.get_probability_distribution()?;
981
982        // Only rank 0 performs measurement
983        if self.communicator.rank == 0 {
984            // Sample from distribution
985            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            // Convert to bit string
1000            let result: Vec<bool> = (0..self.config.total_qubits)
1001                .map(|i| (result_idx >> i) & 1 == 1)
1002                .collect();
1003
1004            // Broadcast result to all ranks
1005            self.communicator.broadcast(&result, 0)
1006        } else {
1007            // Receive result from rank 0
1008            self.communicator.broadcast(&[], 0)
1009        }
1010    }
1011
1012    /// Get local state for debugging/testing
1013    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    /// Get simulator statistics
1022    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    /// Reset the simulator
1031    pub fn reset(&mut self) -> QuantRS2Result<()> {
1032        self.initialize()?;
1033
1034        // Reset statistics
1035        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    /// Create a new MPI communicator
1047    pub fn new() -> QuantRS2Result<Self> {
1048        // Default to a single-rank simulated backend.
1049        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    /// Create communicator with specific configuration
1062    #[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    /// Get rank of this process
1074    #[must_use]
1075    pub const fn rank(&self) -> usize {
1076        self.rank
1077    }
1078
1079    /// Get total number of processes
1080    #[must_use]
1081    pub const fn size(&self) -> usize {
1082        self.size
1083    }
1084
1085    /// Honest error for backends that require real external
1086    /// processes/hardware (a live TCP peer, or a linked native MPI
1087    /// runtime) that this single-process build cannot provide.
1088    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    /// Barrier synchronization: every rank sharing this communicator's
1102    /// `SimulatedMPIState` must call this before any of them proceeds.
1103    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    /// Send and receive data with a partner rank (real rendezvous exchange
1124    /// for the Simulated backend).
1125    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    /// Gather data from all ranks to `root` (root receives every rank's
1170    /// contribution concatenated in rank order; other ranks get their own
1171    /// data back, matching real `MPI_Gather` semantics).
1172    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    /// Broadcast data from `root` to all ranks.
1219    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    /// Allreduce operation: elementwise-reduce `local_data` across every
1266    /// rank and return the reduced vector to all of them.
1267    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/// Reduce operations for allreduce
1321#[derive(Debug, Clone, Copy)]
1322pub enum ReduceOp {
1323    Sum,
1324    Max,
1325    Min,
1326    Prod,
1327}
1328
1329/// Result of MPI quantum simulation
1330#[derive(Debug, Clone)]
1331pub struct MPISimulationResult {
1332    /// Measurement results
1333    pub measurements: Vec<bool>,
1334    /// Probability distribution
1335    pub probabilities: Vec<f64>,
1336    /// Simulation statistics
1337    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        // Apply X gate
1389        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        // Apply some gates
1463        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        // Reset
1478        simulator.reset().expect("failed to reset");
1479
1480        // Check state is back to |0...0>
1481        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        // CNOT gate matrix
1526        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    /// Regression test for the P0 finding: `MPICommunicator::sendrecv` used
1554    /// to just echo the caller's own data back instead of exchanging real
1555    /// data between ranks. With the fixed Simulated backend, two distinct
1556    /// `MPICommunicator`s sharing the same `SimulatedMPIState` must
1557    /// actually swap their payloads.
1558    #[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        // Rank 0 must receive rank 1's data, and vice versa -- not its own.
1593        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    /// Regression test for the P0 finding: `gather`/`broadcast`/`allreduce`
1604    /// used to be no-ops that returned the caller's own local data. With
1605    /// the fixed Simulated backend, root must receive every rank's
1606    /// contribution (gather), every rank must receive root's data
1607    /// (broadcast), and every rank must receive the true elementwise sum
1608    /// (allreduce) -- computed from data it never had locally.
1609    #[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                // Non-root ranks get their own contribution back, matching
1642                // real MPI_Gather semantics (only root has the full result).
1643                assert_eq!(gathered, vec![rank as f64]);
1644            }
1645            assert_eq!(broadcasted, vec![42i64]);
1646            // Sum of (1, 2, 3) across the three ranks.
1647            assert_eq!(reduced, vec![6.0]);
1648        }
1649    }
1650
1651    /// Regression test for the P0 finding: `apply_full_distributed_gate`
1652    /// discarded the gate matrix entirely and applied identity whenever
1653    /// both qubits of a two-qubit gate lived on remote ranks. This builds
1654    /// four ranks (so a 3-qubit state has two genuinely remote qubits),
1655    /// applies a real CNOT(control=qubit1, target=qubit2) across ranks,
1656    /// and checks the amplitude actually moved to the physically correct
1657    /// basis state instead of staying put.
1658    #[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        // Basis index convention: index = qubit0 + 2*qubit1 + 4*qubit2.
1665        // Start in |q2=0, q1=1, q0=0> = index 2.
1666        let initial_index = 2usize;
1667        // CNOT(control=qubit1, target=qubit2) in (control,target) row-major
1668        // order (row/col = 2*control_val + target_val).
1669        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        // Expected final basis state: control qubit1=1 flips target
1750        // qubit2 0 -> 1, giving |q2=1, q1=1, q0=0> = index 6.
1751        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}