Skip to main content

quantrs2_sim/
tpu_acceleration.rs

1//! TPU (Tensor Processing Unit) Acceleration for Quantum Simulation
2//!
3//! This module provides high-performance quantum circuit simulation using Google's
4//! Tensor Processing Units (TPUs) and TPU-like architectures. It leverages the massive
5//! parallelism and specialized tensor operations of TPUs to accelerate quantum state
6//! vector operations, gate applications, and quantum algorithm computations.
7//!
8//! Key features:
9//! - TPU-optimized tensor operations for quantum states
10//! - Batch processing of quantum circuits
11//! - JAX/XLA integration for automatic differentiation
12//! - Distributed quantum simulation across TPU pods
13//! - Memory-efficient state representation using TPU HBM
14//! - Quantum machine learning acceleration
15//! - Variational quantum algorithm optimization
16//! - Cloud TPU integration and resource management
17
18use scirs2_core::ndarray::{Array1, Array2};
19use scirs2_core::Complex64;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22
23use crate::circuit_interfaces::{InterfaceCircuit, InterfaceGate, InterfaceGateType};
24use crate::error::{Result, SimulatorError};
25
26/// TPU device types
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum TPUDeviceType {
29    /// TPU v2 (Cloud TPU v2)
30    TPUv2,
31    /// TPU v3 (Cloud TPU v3)
32    TPUv3,
33    /// TPU v4 (Cloud TPU v4)
34    TPUv4,
35    /// TPU v5e (Edge TPU)
36    TPUv5e,
37    /// TPU v5p (Pod slice)
38    TPUv5p,
39    /// Simulated TPU (for testing)
40    Simulated,
41}
42
43/// TPU configuration
44#[derive(Debug, Clone)]
45pub struct TPUConfig {
46    /// TPU device type
47    pub device_type: TPUDeviceType,
48    /// Number of TPU cores
49    pub num_cores: usize,
50    /// Memory per core (GB)
51    pub memory_per_core: f64,
52    /// Enable mixed precision
53    pub enable_mixed_precision: bool,
54    /// Batch size for circuit execution
55    pub batch_size: usize,
56    /// Enable XLA compilation
57    pub enable_xla_compilation: bool,
58    /// TPU topology (for multi-core setups)
59    pub topology: TPUTopology,
60    /// Enable distributed execution
61    pub enable_distributed: bool,
62    /// Maximum tensor size per operation
63    pub max_tensor_size: usize,
64    /// Memory optimization level
65    pub memory_optimization: MemoryOptimization,
66}
67
68/// TPU topology configuration
69#[derive(Debug, Clone)]
70pub struct TPUTopology {
71    /// Number of TPU chips
72    pub num_chips: usize,
73    /// Chips per host
74    pub chips_per_host: usize,
75    /// Number of hosts
76    pub num_hosts: usize,
77    /// Interconnect bandwidth (GB/s)
78    pub interconnect_bandwidth: f64,
79}
80
81/// Memory optimization strategies
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum MemoryOptimization {
84    /// No optimization
85    None,
86    /// Basic gradient checkpointing
87    Checkpointing,
88    /// Activation recomputation
89    Recomputation,
90    /// Memory-efficient attention
91    EfficientAttention,
92    /// Aggressive optimization
93    Aggressive,
94}
95
96impl Default for TPUConfig {
97    fn default() -> Self {
98        Self {
99            device_type: TPUDeviceType::TPUv4,
100            num_cores: 8,
101            memory_per_core: 16.0, // 16 GB HBM per core
102            enable_mixed_precision: true,
103            batch_size: 32,
104            enable_xla_compilation: true,
105            topology: TPUTopology {
106                num_chips: 4,
107                chips_per_host: 4,
108                num_hosts: 1,
109                interconnect_bandwidth: 100.0, // 100 GB/s
110            },
111            enable_distributed: false,
112            max_tensor_size: 1 << 28, // 256M elements
113            memory_optimization: MemoryOptimization::Checkpointing,
114        }
115    }
116}
117
118/// TPU device information
119#[derive(Debug, Clone)]
120pub struct TPUDeviceInfo {
121    /// Device ID
122    pub device_id: usize,
123    /// Device type
124    pub device_type: TPUDeviceType,
125    /// Core count
126    pub core_count: usize,
127    /// Memory size (GB)
128    pub memory_size: f64,
129    /// Peak FLOPS (operations per second)
130    pub peak_flops: f64,
131    /// Memory bandwidth (GB/s)
132    pub memory_bandwidth: f64,
133    /// Supports bfloat16
134    pub supports_bfloat16: bool,
135    /// Supports complex arithmetic
136    pub supports_complex: bool,
137    /// XLA version
138    pub xla_version: String,
139}
140
141impl TPUDeviceInfo {
142    /// Return the published reference specifications for a given TPU type.
143    ///
144    /// IMPORTANT: this is a static *reference spec table* (vendor datasheet
145    /// figures such as peak FLOPS), NOT a hardware-detection result. It does
146    /// not probe for, or assert the presence of, any physical TPU. The
147    /// `peak_flops` values are theoretical peaks used only as reference
148    /// denominators in metrics; nothing here measures an achieved rate.
149    ///
150    /// Only [`TPUDeviceType::Simulated`] corresponds to something this build can
151    /// actually run: a CPU-side numerical simulation of the device math (see
152    /// [`TPUQuantumSimulator::new`]). The real-device rows exist purely as
153    /// reference data for callers that have such hardware elsewhere.
154    #[must_use]
155    pub fn for_device_type(device_type: TPUDeviceType) -> Self {
156        match device_type {
157            TPUDeviceType::TPUv2 => Self {
158                device_id: 0,
159                device_type,
160                core_count: 2,
161                memory_size: 8.0,
162                peak_flops: 45e12, // 45 TFLOPS
163                memory_bandwidth: 300.0,
164                supports_bfloat16: true,
165                supports_complex: false,
166                xla_version: "2.8.0".to_string(),
167            },
168            TPUDeviceType::TPUv3 => Self {
169                device_id: 0,
170                device_type,
171                core_count: 2,
172                memory_size: 16.0,
173                peak_flops: 420e12, // 420 TFLOPS
174                memory_bandwidth: 900.0,
175                supports_bfloat16: true,
176                supports_complex: false,
177                xla_version: "2.11.0".to_string(),
178            },
179            TPUDeviceType::TPUv4 => Self {
180                device_id: 0,
181                device_type,
182                core_count: 2,
183                memory_size: 32.0,
184                peak_flops: 1100e12, // 1.1 PFLOPS
185                memory_bandwidth: 1200.0,
186                supports_bfloat16: true,
187                supports_complex: true,
188                xla_version: "2.15.0".to_string(),
189            },
190            TPUDeviceType::TPUv5e => Self {
191                device_id: 0,
192                device_type,
193                core_count: 1,
194                memory_size: 16.0,
195                peak_flops: 197e12, // 197 TFLOPS
196                memory_bandwidth: 400.0,
197                supports_bfloat16: true,
198                supports_complex: true,
199                xla_version: "2.17.0".to_string(),
200            },
201            TPUDeviceType::TPUv5p => Self {
202                device_id: 0,
203                device_type,
204                core_count: 2,
205                memory_size: 95.0,
206                peak_flops: 459e12, // 459 TFLOPS
207                memory_bandwidth: 2765.0,
208                supports_bfloat16: true,
209                supports_complex: true,
210                xla_version: "2.17.0".to_string(),
211            },
212            TPUDeviceType::Simulated => Self {
213                device_id: 0,
214                device_type,
215                core_count: 8,
216                memory_size: 64.0,
217                peak_flops: 100e12, // 100 TFLOPS (simulated)
218                memory_bandwidth: 1000.0,
219                supports_bfloat16: true,
220                supports_complex: true,
221                xla_version: "2.17.0".to_string(),
222            },
223        }
224    }
225}
226
227/// TPU-accelerated quantum simulator
228pub struct TPUQuantumSimulator {
229    /// Configuration
230    config: TPUConfig,
231    /// Device information
232    device_info: TPUDeviceInfo,
233    /// Compiled XLA computations
234    xla_computations: HashMap<String, XLAComputation>,
235    /// Tensor buffers on TPU
236    tensor_buffers: HashMap<String, TPUTensorBuffer>,
237    /// Performance statistics
238    stats: TPUStats,
239    /// Distributed execution context
240    distributed_context: Option<DistributedContext>,
241    /// Memory manager
242    memory_manager: TPUMemoryManager,
243}
244
245/// XLA computation representation
246#[derive(Debug, Clone)]
247pub struct XLAComputation {
248    /// Computation name
249    pub name: String,
250    /// Input shapes
251    pub input_shapes: Vec<Vec<usize>>,
252    /// Output shapes
253    pub output_shapes: Vec<Vec<usize>>,
254    /// Measured XLA compilation time (ms).
255    ///
256    /// In the CPU `Simulated` backend nothing is compiled to XLA, so this is
257    /// `0.0` (no compilation was measured). It is populated only by a real XLA
258    /// toolchain.
259    pub compilation_time: f64,
260    /// Estimated FLOPS for one execution of this computation (analytic
261    /// reference figure derived from the shapes; not a measured count).
262    pub estimated_flops: u64,
263    /// Memory usage (bytes)
264    pub memory_usage: usize,
265}
266
267/// TPU tensor buffer
268#[derive(Debug, Clone)]
269pub struct TPUTensorBuffer {
270    /// Buffer ID
271    pub buffer_id: usize,
272    /// Shape
273    pub shape: Vec<usize>,
274    /// Data type
275    pub dtype: TPUDataType,
276    /// Size in bytes
277    pub size_bytes: usize,
278    /// Device placement
279    pub device_id: usize,
280    /// Is resident on device
281    pub on_device: bool,
282}
283
284/// TPU data types
285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub enum TPUDataType {
287    Float32,
288    Float64,
289    BFloat16,
290    Complex64,
291    Complex128,
292    Int32,
293    Int64,
294}
295
296impl TPUDataType {
297    /// Get size in bytes
298    #[must_use]
299    pub const fn size_bytes(&self) -> usize {
300        match self {
301            Self::Float32 => 4,
302            Self::Float64 => 8,
303            Self::BFloat16 => 2,
304            Self::Complex64 => 8,
305            Self::Complex128 => 16,
306            Self::Int32 => 4,
307            Self::Int64 => 8,
308        }
309    }
310}
311
312/// Distributed execution context
313#[derive(Debug, Clone)]
314pub struct DistributedContext {
315    /// Number of hosts
316    pub num_hosts: usize,
317    /// Host ID
318    pub host_id: usize,
319    /// Global device count
320    pub global_device_count: usize,
321    /// Local device count
322    pub local_device_count: usize,
323    /// Communication backend
324    pub communication_backend: CommunicationBackend,
325}
326
327/// Communication backends for distributed execution
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum CommunicationBackend {
330    GRPC,
331    MPI,
332    NCCL,
333    GLOO,
334}
335
336/// TPU memory manager
337#[derive(Debug, Clone)]
338pub struct TPUMemoryManager {
339    /// Total available memory (bytes)
340    pub total_memory: usize,
341    /// Used memory (bytes)
342    pub used_memory: usize,
343    /// Memory pools
344    pub memory_pools: HashMap<String, MemoryPool>,
345    /// Garbage collection enabled
346    pub gc_enabled: bool,
347    /// Memory fragmentation ratio
348    pub fragmentation_ratio: f64,
349}
350
351/// Memory pool for efficient allocation
352#[derive(Debug, Clone)]
353pub struct MemoryPool {
354    /// Pool name
355    pub name: String,
356    /// Pool size (bytes)
357    pub size: usize,
358    /// Used memory (bytes)
359    pub used: usize,
360    /// Free chunks
361    pub free_chunks: Vec<(usize, usize)>, // (offset, size)
362    /// Allocated chunks
363    pub allocated_chunks: HashMap<usize, usize>, // buffer_id -> offset
364}
365
366/// TPU performance statistics
367#[derive(Debug, Clone, Default, Serialize, Deserialize)]
368pub struct TPUStats {
369    /// Total operations executed
370    pub total_operations: usize,
371    /// Total execution time (ms)
372    pub total_execution_time: f64,
373    /// Average operation time (ms)
374    pub avg_operation_time: f64,
375    /// Total FLOPS performed
376    pub total_flops: u64,
377    /// Peak FLOPS utilization
378    pub peak_flops_utilization: f64,
379    /// Memory transfers (host to device)
380    pub h2d_transfers: usize,
381    /// Memory transfers (device to host)
382    pub d2h_transfers: usize,
383    /// Total transfer time (ms)
384    pub total_transfer_time: f64,
385    /// Compilation time (ms)
386    pub total_compilation_time: f64,
387    /// Memory usage peak (bytes)
388    pub peak_memory_usage: usize,
389    /// XLA compilation cache hits
390    pub xla_cache_hits: usize,
391    /// XLA compilation cache misses
392    pub xla_cache_misses: usize,
393}
394
395impl TPUStats {
396    /// Update statistics after operation
397    pub fn update_operation(&mut self, execution_time: f64, flops: u64) {
398        self.total_operations += 1;
399        self.total_execution_time += execution_time;
400        self.avg_operation_time = self.total_execution_time / self.total_operations as f64;
401        self.total_flops += flops;
402    }
403
404    /// Calculate performance metrics
405    #[must_use]
406    pub fn get_performance_metrics(&self) -> HashMap<String, f64> {
407        let mut metrics = HashMap::new();
408
409        if self.total_execution_time > 0.0 {
410            metrics.insert(
411                "flops_per_second".to_string(),
412                self.total_flops as f64 / (self.total_execution_time / 1000.0),
413            );
414            metrics.insert(
415                "operations_per_second".to_string(),
416                self.total_operations as f64 / (self.total_execution_time / 1000.0),
417            );
418        }
419
420        metrics.insert(
421            "cache_hit_rate".to_string(),
422            self.xla_cache_hits as f64
423                / (self.xla_cache_hits + self.xla_cache_misses).max(1) as f64,
424        );
425        metrics.insert(
426            "peak_flops_utilization".to_string(),
427            self.peak_flops_utilization,
428        );
429
430        metrics
431    }
432}
433
434/// Resolve the unitary matrix for a gate honestly.
435///
436/// `InterfaceGate::unitary_matrix` only recognizes the spelled-out gate names
437/// (`Hadamard`, `PauliX`, ...); it does not yet know that the short-form
438/// aliases `H`/`X` are the exact same gates. Rather than let a real,
439/// well-defined gate be silently rejected as "unsupported" on this honest CPU
440/// math path, canonicalize the alias to its spelled-out equivalent before
441/// asking for its matrix. This changes no math: `H` and `Hadamard`
442/// (respectively `X` and `PauliX`) have identical unitaries.
443fn resolve_gate_unitary(gate: &InterfaceGate) -> Result<Array2<Complex64>> {
444    let canonical_type = match &gate.gate_type {
445        InterfaceGateType::H => Some(InterfaceGateType::Hadamard),
446        InterfaceGateType::X => Some(InterfaceGateType::PauliX),
447        _ => None,
448    };
449    match canonical_type {
450        Some(gate_type) => InterfaceGate::new(gate_type, gate.qubits.clone()).unitary_matrix(),
451        None => gate.unitary_matrix(),
452    }
453}
454
455impl TPUQuantumSimulator {
456    /// Create a new TPU quantum simulator.
457    ///
458    /// HONEST AVAILABILITY GATE: this build links no TPU runtime (no
459    /// JAX/XLA/`libtpu`), so it cannot place tensors on, or dispatch work to, a
460    /// physical Cloud/Edge TPU. Requesting a *real* device type
461    /// (`TPUv2`..`TPUv5p`) therefore fails loudly rather than fabricating that
462    /// the silicon is present.
463    ///
464    /// [`TPUDeviceType::Simulated`] is explicitly supported: it is a CPU-side
465    /// numerical simulation of the device math (the gate applications below
466    /// compute the exact state-vector linear algebra on the CPU). It never
467    /// claims a TPU executed anything.
468    pub fn new(config: TPUConfig) -> Result<Self> {
469        if config.device_type != TPUDeviceType::Simulated {
470            return Err(SimulatorError::UnsupportedOperation(format!(
471                "TPU backend: no TPU runtime available in this build \
472                 (no JAX/XLA/libtpu linked); cannot target real device {:?}. \
473                 Use TPUDeviceType::Simulated for CPU-side numerical simulation.",
474                config.device_type
475            )));
476        }
477        let device_info = TPUDeviceInfo::for_device_type(config.device_type);
478
479        // Initialize memory manager
480        let total_memory = (config.memory_per_core * config.num_cores as f64 * 1e9) as usize;
481        let memory_manager = TPUMemoryManager {
482            total_memory,
483            used_memory: 0,
484            memory_pools: HashMap::new(),
485            gc_enabled: true,
486            fragmentation_ratio: 0.0,
487        };
488
489        // Initialize distributed context if enabled
490        let distributed_context = if config.enable_distributed {
491            Some(DistributedContext {
492                num_hosts: config.topology.num_hosts,
493                host_id: 0,
494                global_device_count: config.topology.num_chips,
495                local_device_count: config.topology.chips_per_host,
496                communication_backend: CommunicationBackend::GRPC,
497            })
498        } else {
499            None
500        };
501
502        let mut simulator = Self {
503            config,
504            device_info,
505            xla_computations: HashMap::new(),
506            tensor_buffers: HashMap::new(),
507            stats: TPUStats::default(),
508            distributed_context,
509            memory_manager,
510        };
511
512        // Compile standard quantum operations
513        simulator.compile_standard_operations()?;
514
515        Ok(simulator)
516    }
517
518    /// Compile standard quantum operations to XLA
519    fn compile_standard_operations(&mut self) -> Result<()> {
520        let start_time = std::time::Instant::now();
521
522        // Single qubit gate operations
523        self.compile_single_qubit_gates()?;
524
525        // Two qubit gate operations
526        self.compile_two_qubit_gates()?;
527
528        // State vector operations
529        self.compile_state_vector_operations()?;
530
531        // Measurement operations
532        self.compile_measurement_operations()?;
533
534        // Expectation value computations
535        self.compile_expectation_operations()?;
536
537        // Quantum machine learning operations
538        self.compile_qml_operations()?;
539
540        self.stats.total_compilation_time = start_time.elapsed().as_secs_f64() * 1000.0;
541
542        Ok(())
543    }
544
545    /// Compile single qubit gate operations
546    fn compile_single_qubit_gates(&mut self) -> Result<()> {
547        // Batched single qubit gate application
548        let computation = XLAComputation {
549            name: "batched_single_qubit_gates".to_string(),
550            input_shapes: vec![
551                vec![self.config.batch_size, 1 << 20], // State vectors
552                vec![2, 2],                            // Gate matrix
553                vec![1],                               // Target qubit
554            ],
555            output_shapes: vec![
556                vec![self.config.batch_size, 1 << 20], // Updated state vectors
557            ],
558            compilation_time: 0.0, // CPU simulation: nothing compiled to XLA
559            estimated_flops: (self.config.batch_size * (1 << 20) * 8) as u64,
560            memory_usage: self.config.batch_size * (1 << 20) * 16, // Complex128
561        };
562
563        self.xla_computations
564            .insert("batched_single_qubit_gates".to_string(), computation);
565
566        // Fused rotation gates (RX, RY, RZ)
567        let fused_rotations = XLAComputation {
568            name: "fused_rotation_gates".to_string(),
569            input_shapes: vec![
570                vec![self.config.batch_size, 1 << 20], // State vectors
571                vec![3],                               // Rotation angles (x, y, z)
572                vec![1],                               // Target qubit
573            ],
574            output_shapes: vec![
575                vec![self.config.batch_size, 1 << 20], // Updated state vectors
576            ],
577            compilation_time: 0.0,
578            estimated_flops: (self.config.batch_size * (1 << 20) * 12) as u64,
579            memory_usage: self.config.batch_size * (1 << 20) * 16,
580        };
581
582        self.xla_computations
583            .insert("fused_rotation_gates".to_string(), fused_rotations);
584
585        Ok(())
586    }
587
588    /// Compile two qubit gate operations
589    fn compile_two_qubit_gates(&mut self) -> Result<()> {
590        // Batched CNOT gates
591        let cnot_computation = XLAComputation {
592            name: "batched_cnot_gates".to_string(),
593            input_shapes: vec![
594                vec![self.config.batch_size, 1 << 20], // State vectors
595                vec![1],                               // Control qubit
596                vec![1],                               // Target qubit
597            ],
598            output_shapes: vec![
599                vec![self.config.batch_size, 1 << 20], // Updated state vectors
600            ],
601            compilation_time: 0.0,
602            estimated_flops: (self.config.batch_size * (1 << 20) * 4) as u64,
603            memory_usage: self.config.batch_size * (1 << 20) * 16,
604        };
605
606        self.xla_computations
607            .insert("batched_cnot_gates".to_string(), cnot_computation);
608
609        // General two-qubit gates
610        let general_two_qubit = XLAComputation {
611            name: "general_two_qubit_gates".to_string(),
612            input_shapes: vec![
613                vec![self.config.batch_size, 1 << 20], // State vectors
614                vec![4, 4],                            // Gate matrix
615                vec![2],                               // Qubit indices
616            ],
617            output_shapes: vec![
618                vec![self.config.batch_size, 1 << 20], // Updated state vectors
619            ],
620            compilation_time: 0.0,
621            estimated_flops: (self.config.batch_size * (1 << 20) * 16) as u64,
622            memory_usage: self.config.batch_size * (1 << 20) * 16,
623        };
624
625        self.xla_computations
626            .insert("general_two_qubit_gates".to_string(), general_two_qubit);
627
628        Ok(())
629    }
630
631    /// Compile state vector operations
632    fn compile_state_vector_operations(&mut self) -> Result<()> {
633        // Batch normalization
634        let normalization = XLAComputation {
635            name: "batch_normalize".to_string(),
636            input_shapes: vec![
637                vec![self.config.batch_size, 1 << 20], // State vectors
638            ],
639            output_shapes: vec![
640                vec![self.config.batch_size, 1 << 20], // Normalized state vectors
641                vec![self.config.batch_size],          // Norms
642            ],
643            compilation_time: 0.0,
644            estimated_flops: (self.config.batch_size * (1 << 20) * 3) as u64,
645            memory_usage: self.config.batch_size * (1 << 20) * 16,
646        };
647
648        self.xla_computations
649            .insert("batch_normalize".to_string(), normalization);
650
651        // Inner product computation
652        let inner_product = XLAComputation {
653            name: "batch_inner_product".to_string(),
654            input_shapes: vec![
655                vec![self.config.batch_size, 1 << 20], // State vectors 1
656                vec![self.config.batch_size, 1 << 20], // State vectors 2
657            ],
658            output_shapes: vec![
659                vec![self.config.batch_size], // Inner products
660            ],
661            compilation_time: 0.0,
662            estimated_flops: (self.config.batch_size * (1 << 20) * 6) as u64,
663            memory_usage: self.config.batch_size * (1 << 20) * 32,
664        };
665
666        self.xla_computations
667            .insert("batch_inner_product".to_string(), inner_product);
668
669        Ok(())
670    }
671
672    /// Compile measurement operations
673    fn compile_measurement_operations(&mut self) -> Result<()> {
674        // Probability computation
675        let probabilities = XLAComputation {
676            name: "compute_probabilities".to_string(),
677            input_shapes: vec![
678                vec![self.config.batch_size, 1 << 20], // State vectors
679            ],
680            output_shapes: vec![
681                vec![self.config.batch_size, 1 << 20], // Probabilities
682            ],
683            compilation_time: 0.0,
684            estimated_flops: (self.config.batch_size * (1 << 20) * 2) as u64,
685            memory_usage: self.config.batch_size * (1 << 20) * 24,
686        };
687
688        self.xla_computations
689            .insert("compute_probabilities".to_string(), probabilities);
690
691        // Sampling operation
692        let sampling = XLAComputation {
693            name: "quantum_sampling".to_string(),
694            input_shapes: vec![
695                vec![self.config.batch_size, 1 << 20], // Probabilities
696                vec![self.config.batch_size],          // Random numbers
697            ],
698            output_shapes: vec![
699                vec![self.config.batch_size], // Sample results
700            ],
701            compilation_time: 0.0,
702            estimated_flops: (self.config.batch_size * (1 << 20)) as u64,
703            memory_usage: self.config.batch_size * (1 << 20) * 8,
704        };
705
706        self.xla_computations
707            .insert("quantum_sampling".to_string(), sampling);
708
709        Ok(())
710    }
711
712    /// Compile expectation value operations
713    fn compile_expectation_operations(&mut self) -> Result<()> {
714        // Pauli expectation values
715        let pauli_expectation = XLAComputation {
716            name: "pauli_expectation_values".to_string(),
717            input_shapes: vec![
718                vec![self.config.batch_size, 1 << 20], // State vectors
719                vec![20],                              // Pauli strings (encoded)
720            ],
721            output_shapes: vec![
722                vec![self.config.batch_size, 20], // Expectation values
723            ],
724            compilation_time: 0.0,
725            estimated_flops: (self.config.batch_size * (1 << 20) * 20 * 4) as u64,
726            memory_usage: self.config.batch_size * (1 << 20) * 16,
727        };
728
729        self.xla_computations
730            .insert("pauli_expectation_values".to_string(), pauli_expectation);
731
732        // Hamiltonian expectation
733        let hamiltonian_expectation = XLAComputation {
734            name: "hamiltonian_expectation".to_string(),
735            input_shapes: vec![
736                vec![self.config.batch_size, 1 << 20], // State vectors
737                vec![1 << 20, 1 << 20],                // Hamiltonian matrix
738            ],
739            output_shapes: vec![
740                vec![self.config.batch_size], // Expectation values
741            ],
742            compilation_time: 0.0,
743            estimated_flops: (self.config.batch_size * (1 << 40)) as u64,
744            memory_usage: (1 << 40) * 16 + self.config.batch_size * (1 << 20) * 16,
745        };
746
747        self.xla_computations.insert(
748            "hamiltonian_expectation".to_string(),
749            hamiltonian_expectation,
750        );
751
752        Ok(())
753    }
754
755    /// Compile quantum machine learning operations
756    fn compile_qml_operations(&mut self) -> Result<()> {
757        // Variational circuit execution
758        let variational_circuit = XLAComputation {
759            name: "variational_circuit_batch".to_string(),
760            input_shapes: vec![
761                vec![self.config.batch_size, 1 << 20], // Initial states
762                vec![100],                             // Parameters
763                vec![50],                              // Circuit structure
764            ],
765            output_shapes: vec![
766                vec![self.config.batch_size, 1 << 20], // Final states
767            ],
768            compilation_time: 0.0,
769            estimated_flops: (self.config.batch_size * 100 * (1 << 20) * 8) as u64,
770            memory_usage: self.config.batch_size * (1 << 20) * 16,
771        };
772
773        self.xla_computations
774            .insert("variational_circuit_batch".to_string(), variational_circuit);
775
776        // Gradient computation using parameter shift
777        let parameter_shift_gradients = XLAComputation {
778            name: "parameter_shift_gradients".to_string(),
779            input_shapes: vec![
780                vec![self.config.batch_size, 1 << 20], // States
781                vec![100],                             // Parameters
782                vec![50],                              // Circuit structure
783                vec![20],                              // Observables
784            ],
785            output_shapes: vec![
786                vec![self.config.batch_size, 100], // Gradients
787            ],
788            compilation_time: 0.0,
789            estimated_flops: (self.config.batch_size * 100 * 20 * (1 << 20) * 16) as u64,
790            memory_usage: self.config.batch_size * (1 << 20) * 16 * 4, // 4 evaluations per gradient
791        };
792
793        self.xla_computations.insert(
794            "parameter_shift_gradients".to_string(),
795            parameter_shift_gradients,
796        );
797
798        Ok(())
799    }
800
801    /// Execute batched quantum circuit
802    pub fn execute_batch_circuit(
803        &mut self,
804        circuits: &[InterfaceCircuit],
805        initial_states: &[Array1<Complex64>],
806    ) -> Result<Vec<Array1<Complex64>>> {
807        let start_time = std::time::Instant::now();
808
809        if circuits.len() != initial_states.len() {
810            return Err(SimulatorError::InvalidInput(
811                "Circuit and state count mismatch".to_string(),
812            ));
813        }
814
815        if circuits.len() > self.config.batch_size {
816            return Err(SimulatorError::InvalidInput(
817                "Batch size exceeded".to_string(),
818            ));
819        }
820
821        // Allocate device memory for batch
822        self.allocate_batch_memory(circuits.len(), initial_states[0].len())?;
823
824        // Transfer initial states to device
825        self.transfer_states_to_device(initial_states)?;
826
827        // Execute circuits in batch
828        let mut final_states = Vec::with_capacity(circuits.len());
829
830        for (i, circuit) in circuits.iter().enumerate() {
831            let mut current_state = initial_states[i].clone();
832
833            // Process gates sequentially (could be optimized for parallel execution)
834            for gate in &circuit.gates {
835                current_state = self.apply_gate_tpu(&current_state, gate)?;
836            }
837
838            final_states.push(current_state);
839        }
840
841        // Transfer results back to host
842        self.transfer_states_to_host(&final_states)?;
843
844        let execution_time = start_time.elapsed().as_secs_f64() * 1000.0;
845        let estimated_flops = circuits.len() as u64 * 1000; // Rough estimate
846        self.stats.update_operation(execution_time, estimated_flops);
847
848        Ok(final_states)
849    }
850
851    /// Apply a quantum gate (CPU numerical simulation of the device math).
852    ///
853    /// This computes the exact state-vector transformation on the CPU using the
854    /// gate's canonical unitary. It is the honest `Simulated`-device path: it
855    /// performs the real linear algebra and never pretends a TPU executed it.
856    fn apply_gate_tpu(
857        &mut self,
858        state: &Array1<Complex64>,
859        gate: &InterfaceGate,
860    ) -> Result<Array1<Complex64>> {
861        let start_time = std::time::Instant::now();
862        let unitary = resolve_gate_unitary(gate)?;
863        let result = match gate.qubits.len() {
864            1 => Self::apply_single_qubit_unitary(state, gate.qubits[0], &unitary)?,
865            2 => Self::apply_two_qubit_unitary(state, gate.qubits[0], gate.qubits[1], &unitary)?,
866            n => {
867                return Err(SimulatorError::UnsupportedOperation(format!(
868                    "TPU Simulated backend: {n}-qubit gate application is not implemented"
869                )));
870            }
871        };
872        let execution_time = start_time.elapsed().as_secs_f64() * 1000.0;
873        let flops = (state.len() * 8 * gate.qubits.len()) as u64;
874        self.stats.update_operation(execution_time, flops);
875        Ok(result)
876    }
877
878    /// Apply a 2x2 unitary to `target_qubit` of the state vector (exact math).
879    fn apply_single_qubit_unitary(
880        state: &Array1<Complex64>,
881        target_qubit: usize,
882        unitary: &Array2<Complex64>,
883    ) -> Result<Array1<Complex64>> {
884        let num_qubits = state.len().trailing_zeros() as usize;
885        if state.len() != 1usize << num_qubits {
886            return Err(SimulatorError::DimensionMismatch(format!(
887                "State length {} is not a power of two",
888                state.len()
889            )));
890        }
891        if target_qubit >= num_qubits {
892            return Err(SimulatorError::IndexOutOfBounds(target_qubit));
893        }
894        let mut result = state.clone();
895        let target_mask = 1usize << target_qubit;
896        for i in 0..state.len() {
897            if i & target_mask == 0 {
898                let j = i | target_mask;
899                let amp_0 = state[i];
900                let amp_1 = state[j];
901                result[i] = unitary[[0, 0]] * amp_0 + unitary[[0, 1]] * amp_1;
902                result[j] = unitary[[1, 0]] * amp_0 + unitary[[1, 1]] * amp_1;
903            }
904        }
905        Ok(result)
906    }
907
908    /// Apply a 4x4 unitary to `(q0, q1)` of the state vector (exact math).
909    ///
910    /// Basis ordering for the 4x4 matrix is `|q0 q1>` with `q0` the high bit,
911    /// matching [`InterfaceGate::unitary_matrix`].
912    fn apply_two_qubit_unitary(
913        state: &Array1<Complex64>,
914        q0: usize,
915        q1: usize,
916        unitary: &Array2<Complex64>,
917    ) -> Result<Array1<Complex64>> {
918        let num_qubits = state.len().trailing_zeros() as usize;
919        if state.len() != 1usize << num_qubits {
920            return Err(SimulatorError::DimensionMismatch(format!(
921                "State length {} is not a power of two",
922                state.len()
923            )));
924        }
925        if q0 >= num_qubits || q1 >= num_qubits || q0 == q1 {
926            return Err(SimulatorError::InvalidInput(format!(
927                "Invalid two-qubit indices ({q0}, {q1}) for {num_qubits} qubits"
928            )));
929        }
930        let mut result = state.clone();
931        let mask0 = 1usize << q0;
932        let mask1 = 1usize << q1;
933        for i in 0..state.len() {
934            // Process each 2-qubit subspace once, anchored at the element where
935            // both target bits are zero.
936            if (i & mask0) == 0 && (i & mask1) == 0 {
937                let idx = [i, i | mask1, i | mask0, i | mask0 | mask1];
938                let amps = [state[idx[0]], state[idx[1]], state[idx[2]], state[idx[3]]];
939                for (row, &out_idx) in idx.iter().enumerate() {
940                    let mut acc = Complex64::new(0.0, 0.0);
941                    for (col, &amp) in amps.iter().enumerate() {
942                        acc += unitary[[row, col]] * amp;
943                    }
944                    result[out_idx] = acc;
945                }
946            }
947        }
948        Ok(result)
949    }
950
951    /// Allocate batch memory on TPU
952    fn allocate_batch_memory(&mut self, batch_size: usize, state_size: usize) -> Result<()> {
953        let total_size = batch_size * state_size * 16; // Complex128
954
955        if total_size > self.memory_manager.total_memory {
956            return Err(SimulatorError::MemoryError(
957                "Insufficient TPU memory".to_string(),
958            ));
959        }
960
961        // Create tensor buffer
962        let buffer = TPUTensorBuffer {
963            buffer_id: self.tensor_buffers.len(),
964            shape: vec![batch_size, state_size],
965            dtype: TPUDataType::Complex128,
966            size_bytes: total_size,
967            device_id: 0,
968            on_device: true,
969        };
970
971        self.tensor_buffers
972            .insert("batch_states".to_string(), buffer);
973        self.memory_manager.used_memory += total_size;
974
975        if self.memory_manager.used_memory > self.stats.peak_memory_usage {
976            self.stats.peak_memory_usage = self.memory_manager.used_memory;
977        }
978
979        Ok(())
980    }
981
982    /// Stage input states for the batch (CPU simulation: no device boundary).
983    ///
984    /// In the `Simulated` backend the data already lives in host memory, so
985    /// there is no real host-to-device copy and no fabricated latency. We only
986    /// record the (real, typically ~0) time and count the staging event.
987    fn transfer_states_to_device(&mut self, _states: &[Array1<Complex64>]) -> Result<()> {
988        let start_time = std::time::Instant::now();
989        self.stats.h2d_transfers += 1;
990        self.stats.total_transfer_time += start_time.elapsed().as_secs_f64() * 1000.0;
991        Ok(())
992    }
993
994    /// Retrieve output states for the batch (CPU simulation: no device boundary).
995    fn transfer_states_to_host(&mut self, _states: &[Array1<Complex64>]) -> Result<()> {
996        let start_time = std::time::Instant::now();
997        self.stats.d2h_transfers += 1;
998        self.stats.total_transfer_time += start_time.elapsed().as_secs_f64() * 1000.0;
999        Ok(())
1000    }
1001
1002    /// Compute Pauli-observable expectation values (CPU numerical simulation).
1003    ///
1004    /// Each observable is a compact single-qubit Pauli string of the form
1005    /// `"<P><qubit>"`, e.g. `"Z0"`, `"X1"`, `"Y2"` (identity on every other
1006    /// qubit). The result `<psi|P|psi>` is computed exactly from the amplitudes;
1007    /// no value is fabricated.
1008    pub fn compute_expectation_values_tpu(
1009        &mut self,
1010        states: &[Array1<Complex64>],
1011        observables: &[String],
1012    ) -> Result<Array2<f64>> {
1013        let start_time = std::time::Instant::now();
1014
1015        let batch_size = states.len();
1016        let num_observables = observables.len();
1017        let mut results = Array2::zeros((batch_size, num_observables));
1018
1019        for (i, state) in states.iter().enumerate() {
1020            for (j, observable) in observables.iter().enumerate() {
1021                results[[i, j]] = Self::single_pauli_expectation(state, observable)?;
1022            }
1023        }
1024
1025        let state_len = states.first().map_or(0, Array1::len);
1026        let execution_time = start_time.elapsed().as_secs_f64() * 1000.0;
1027        let flops = (batch_size * num_observables * state_len * 4) as u64;
1028        self.stats.update_operation(execution_time, flops);
1029
1030        Ok(results)
1031    }
1032
1033    /// Compute `<psi|P_q|psi>` for a single-qubit Pauli observable `"<P><qubit>"`.
1034    fn single_pauli_expectation(state: &Array1<Complex64>, observable: &str) -> Result<f64> {
1035        let trimmed = observable.trim();
1036        let mut chars = trimmed.chars();
1037        let pauli = chars.next().ok_or_else(|| {
1038            SimulatorError::InvalidObservable("empty observable string".to_string())
1039        })?;
1040        let qubit: usize = chars.as_str().parse().map_err(|_| {
1041            SimulatorError::InvalidObservable(format!(
1042                "could not parse qubit index from observable '{observable}'"
1043            ))
1044        })?;
1045
1046        let num_qubits = state.len().trailing_zeros() as usize;
1047        if state.len() != 1usize << num_qubits {
1048            return Err(SimulatorError::DimensionMismatch(format!(
1049                "State length {} is not a power of two",
1050                state.len()
1051            )));
1052        }
1053        if qubit >= num_qubits {
1054            return Err(SimulatorError::IndexOutOfBounds(qubit));
1055        }
1056
1057        let mask = 1usize << qubit;
1058        let mut expectation = Complex64::new(0.0, 0.0);
1059        match pauli {
1060            'I' => {
1061                for amp in state.iter() {
1062                    expectation += amp.conj() * amp;
1063                }
1064            }
1065            'Z' => {
1066                for (idx, amp) in state.iter().enumerate() {
1067                    let sign = if idx & mask != 0 { -1.0 } else { 1.0 };
1068                    expectation += amp.conj() * amp * sign;
1069                }
1070            }
1071            'X' => {
1072                for idx in 0..state.len() {
1073                    let partner = idx ^ mask;
1074                    expectation += state[idx].conj() * state[partner];
1075                }
1076            }
1077            'Y' => {
1078                for idx in 0..state.len() {
1079                    let partner = idx ^ mask;
1080                    // Y|0> = i|1>, Y|1> = -i|0>; coefficient depends on the bit.
1081                    let coeff = if idx & mask == 0 {
1082                        Complex64::new(0.0, -1.0)
1083                    } else {
1084                        Complex64::new(0.0, 1.0)
1085                    };
1086                    expectation += state[idx].conj() * coeff * state[partner];
1087                }
1088            }
1089            other => {
1090                return Err(SimulatorError::InvalidObservable(format!(
1091                    "unsupported Pauli operator '{other}' in observable '{observable}'"
1092                )));
1093            }
1094        }
1095
1096        Ok(expectation.re)
1097    }
1098
1099    /// Get device information
1100    #[must_use]
1101    pub const fn get_device_info(&self) -> &TPUDeviceInfo {
1102        &self.device_info
1103    }
1104
1105    /// Get performance statistics
1106    #[must_use]
1107    pub const fn get_stats(&self) -> &TPUStats {
1108        &self.stats
1109    }
1110
1111    /// Reset performance statistics
1112    pub fn reset_stats(&mut self) {
1113        self.stats = TPUStats::default();
1114    }
1115
1116    /// Whether a *real* TPU runtime is available.
1117    ///
1118    /// HONEST: this build links no TPU runtime, so a physical TPU is never
1119    /// available. This returns `false` even though a CPU `Simulated` device is
1120    /// in use — that is a numerical model, not real silicon. Use
1121    /// [`Self::is_simulated`] to check for the CPU-simulation device.
1122    #[must_use]
1123    pub const fn is_tpu_available(&self) -> bool {
1124        false
1125    }
1126
1127    /// Whether this simulator is the CPU-side numerical `Simulated` device.
1128    #[must_use]
1129    pub fn is_simulated(&self) -> bool {
1130        self.device_info.device_type == TPUDeviceType::Simulated
1131    }
1132
1133    /// Get memory usage
1134    #[must_use]
1135    pub const fn get_memory_usage(&self) -> (usize, usize) {
1136        (
1137            self.memory_manager.used_memory,
1138            self.memory_manager.total_memory,
1139        )
1140    }
1141
1142    /// Reclaim memory held by tensor buffers that are no longer device-resident.
1143    ///
1144    /// HONEST: this frees exactly the bytes of buffers whose `on_device` flag is
1145    /// `false` (i.e. genuinely releasable in the model) and updates the
1146    /// accounting accordingly. It does not fabricate a fixed "freed 10%" figure.
1147    /// Returns the number of bytes actually reclaimed.
1148    pub fn garbage_collect(&mut self) -> Result<usize> {
1149        if !self.memory_manager.gc_enabled {
1150            return Ok(0);
1151        }
1152
1153        let mut freed_memory = 0usize;
1154        self.tensor_buffers.retain(|_, buffer| {
1155            if buffer.on_device {
1156                true
1157            } else {
1158                freed_memory += buffer.size_bytes;
1159                false
1160            }
1161        });
1162        self.memory_manager.used_memory =
1163            self.memory_manager.used_memory.saturating_sub(freed_memory);
1164
1165        Ok(freed_memory)
1166    }
1167}
1168
1169/// Benchmark the CPU-simulated TPU backend.
1170///
1171/// HONEST: only [`TPUDeviceType::Simulated`] can run in this build (no TPU
1172/// runtime is linked), so every configuration benchmarked here is the CPU
1173/// numerical simulation. The reported times are real `Instant`-measured CPU
1174/// timings of actual state-vector work; no throughput figure is fabricated.
1175pub fn benchmark_tpu_acceleration() -> Result<HashMap<String, f64>> {
1176    let mut results = HashMap::new();
1177
1178    // All configurations use the CPU `Simulated` device (the only runnable one),
1179    // varying batch size and core count to exercise different work sizes.
1180    let configs = vec![
1181        TPUConfig {
1182            device_type: TPUDeviceType::Simulated,
1183            num_cores: 8,
1184            batch_size: 16,
1185            ..Default::default()
1186        },
1187        TPUConfig {
1188            device_type: TPUDeviceType::Simulated,
1189            num_cores: 16,
1190            batch_size: 32,
1191            ..Default::default()
1192        },
1193        TPUConfig {
1194            device_type: TPUDeviceType::Simulated,
1195            num_cores: 32,
1196            batch_size: 64,
1197            enable_mixed_precision: true,
1198            ..Default::default()
1199        },
1200    ];
1201
1202    for (i, config) in configs.into_iter().enumerate() {
1203        let start = std::time::Instant::now();
1204
1205        let mut simulator = TPUQuantumSimulator::new(config)?;
1206
1207        // Create test circuits
1208        let mut circuits = Vec::new();
1209        let mut initial_states = Vec::new();
1210
1211        for _ in 0..simulator.config.batch_size.min(8) {
1212            let mut circuit = InterfaceCircuit::new(10, 0);
1213
1214            // Add some gates
1215            circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![0]));
1216            circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![0, 1]));
1217            circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.5), vec![2]));
1218            circuit.add_gate(InterfaceGate::new(InterfaceGateType::CZ, vec![1, 2]));
1219
1220            circuits.push(circuit);
1221
1222            // Create initial state
1223            let mut state = Array1::zeros(1 << 10);
1224            state[0] = Complex64::new(1.0, 0.0);
1225            initial_states.push(state);
1226        }
1227
1228        // Execute batch
1229        let _final_states = simulator.execute_batch_circuit(&circuits, &initial_states)?;
1230
1231        // Test expectation values
1232        let observables = vec!["Z0".to_string(), "X1".to_string(), "Y2".to_string()];
1233        let _expectations =
1234            simulator.compute_expectation_values_tpu(&initial_states, &observables)?;
1235
1236        let time = start.elapsed().as_secs_f64() * 1000.0;
1237        results.insert(format!("tpu_config_{i}"), time);
1238
1239        // Add performance metrics
1240        let stats = simulator.get_stats();
1241        results.insert(
1242            format!("tpu_config_{i}_operations"),
1243            stats.total_operations as f64,
1244        );
1245        results.insert(format!("tpu_config_{i}_avg_time"), stats.avg_operation_time);
1246        results.insert(
1247            format!("tpu_config_{i}_total_flops"),
1248            stats.total_flops as f64,
1249        );
1250
1251        let performance_metrics = stats.get_performance_metrics();
1252        for (key, value) in performance_metrics {
1253            results.insert(format!("tpu_config_{i}_{key}"), value);
1254        }
1255    }
1256
1257    Ok(results)
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262    use super::*;
1263    use approx::assert_abs_diff_eq;
1264
1265    /// Build a CPU `Simulated` config (the only runnable device in this build).
1266    fn sim_config() -> TPUConfig {
1267        TPUConfig {
1268            device_type: TPUDeviceType::Simulated,
1269            ..Default::default()
1270        }
1271    }
1272
1273    #[test]
1274    fn test_real_tpu_device_unavailable() {
1275        // Honest behavior: requesting a real TPU device fails loudly.
1276        let config = TPUConfig::default(); // default is TPUv4 (a real device)
1277        let result = TPUQuantumSimulator::new(config);
1278        assert!(result.is_err());
1279        // Match on `.err()` so the `Ok` simulator value need not be `Debug`.
1280        match result.err() {
1281            Some(SimulatorError::UnsupportedOperation(msg)) => assert!(msg.contains("TPU")),
1282            other => panic!("expected UnsupportedOperation, got {other:?}"),
1283        }
1284    }
1285
1286    #[test]
1287    fn test_simulated_device_creation() {
1288        let simulator = TPUQuantumSimulator::new(sim_config());
1289        assert!(simulator.is_ok());
1290        let simulator = simulator.expect("simulated device should construct");
1291        assert!(simulator.is_simulated());
1292        // No *real* TPU is ever available in this build.
1293        assert!(!simulator.is_tpu_available());
1294    }
1295
1296    #[test]
1297    fn test_device_info_reference_specs() {
1298        // for_device_type is a reference spec table, not a detection result.
1299        let device_info = TPUDeviceInfo::for_device_type(TPUDeviceType::TPUv4);
1300        assert_eq!(device_info.device_type, TPUDeviceType::TPUv4);
1301        assert_eq!(device_info.core_count, 2);
1302        assert_abs_diff_eq!(device_info.memory_size, 32.0, epsilon = 1e-10);
1303        assert!(device_info.supports_complex);
1304    }
1305
1306    #[test]
1307    fn test_xla_compilation() {
1308        let simulator =
1309            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1310
1311        assert!(simulator
1312            .xla_computations
1313            .contains_key("batched_single_qubit_gates"));
1314        assert!(simulator
1315            .xla_computations
1316            .contains_key("batched_cnot_gates"));
1317        assert!(simulator.xla_computations.contains_key("batch_normalize"));
1318        // total_compilation_time is the real measured wall-time of building the
1319        // computation descriptors (non-negative); per-computation
1320        // compilation_time is 0.0 because nothing is compiled to XLA on CPU.
1321        assert!(simulator.stats.total_compilation_time >= 0.0);
1322        assert_abs_diff_eq!(
1323            simulator.xla_computations["batch_normalize"].compilation_time,
1324            0.0,
1325            epsilon = 1e-12
1326        );
1327    }
1328
1329    #[test]
1330    fn test_memory_allocation() {
1331        let mut simulator =
1332            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1333
1334        let result = simulator.allocate_batch_memory(4, 1024);
1335        assert!(result.is_ok());
1336        assert!(simulator.tensor_buffers.contains_key("batch_states"));
1337        assert!(simulator.memory_manager.used_memory > 0);
1338    }
1339
1340    #[test]
1341    fn test_memory_limit() {
1342        let config = TPUConfig {
1343            device_type: TPUDeviceType::Simulated,
1344            memory_per_core: 0.001, // Very small memory
1345            num_cores: 1,
1346            ..Default::default()
1347        };
1348        let mut simulator =
1349            TPUQuantumSimulator::new(config).expect("Failed to create TPU simulator");
1350
1351        let result = simulator.allocate_batch_memory(1000, 1_000_000); // Large allocation
1352        assert!(result.is_err());
1353    }
1354
1355    #[test]
1356    fn test_single_qubit_gate_application_real_math() {
1357        let mut state = Array1::zeros(4);
1358        state[0] = Complex64::new(1.0, 0.0);
1359
1360        let gate = InterfaceGate::new(InterfaceGateType::H, vec![0]);
1361        let unitary = resolve_gate_unitary(&gate).expect("hadamard matrix");
1362        let result =
1363            TPUQuantumSimulator::apply_single_qubit_unitary(&state, 0, &unitary).expect("apply H");
1364
1365        // After Hadamard, |0> becomes (|0> + |1>)/sqrt(2)
1366        assert_abs_diff_eq!(result[0].norm(), 1.0 / 2.0_f64.sqrt(), epsilon = 1e-10);
1367        assert_abs_diff_eq!(result[1].norm(), 1.0 / 2.0_f64.sqrt(), epsilon = 1e-10);
1368    }
1369
1370    #[test]
1371    fn test_rotation_uses_real_angle() {
1372        // Regression: rotations must use the gate's actual angle, not a constant.
1373        let mut state = Array1::zeros(2);
1374        state[0] = Complex64::new(1.0, 0.0);
1375        let mut simulator =
1376            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1377        // RY(pi) maps |0> -> |1>.
1378        let gate = InterfaceGate::new(InterfaceGateType::RY(std::f64::consts::PI), vec![0]);
1379        let result = simulator
1380            .apply_gate_tpu(&state, &gate)
1381            .expect("apply RY(pi)");
1382        assert_abs_diff_eq!(result[0].norm(), 0.0, epsilon = 1e-10);
1383        assert_abs_diff_eq!(result[1].norm(), 1.0, epsilon = 1e-10);
1384    }
1385
1386    #[test]
1387    fn test_two_qubit_gate_application_real_math() {
1388        // Standard little-endian convention (bit position == qubit index,
1389        // matching `statevector.rs::apply_cnot`'s `(i >> control_idx) & 1`):
1390        // global index 0b01 has qubit0(control)=1, qubit1(target)=0, i.e.
1391        // |q0=1, q1=0>. With control=qubit0 asserted, CNOT(control=0,
1392        // target=1) flips the target, giving |q0=1, q1=1> = index 0b11.
1393        let mut state = Array1::zeros(4);
1394        state[0b01] = Complex64::new(1.0, 0.0);
1395
1396        let gate = InterfaceGate::new(InterfaceGateType::CNOT, vec![0, 1]);
1397        let unitary = gate.unitary_matrix().expect("cnot matrix");
1398        let result = TPUQuantumSimulator::apply_two_qubit_unitary(&state, 0, 1, &unitary)
1399            .expect("apply CNOT");
1400
1401        assert_eq!(result.len(), 4);
1402        assert_abs_diff_eq!(result[0b11].norm(), 1.0, epsilon = 1e-10);
1403        assert_abs_diff_eq!(result[0b01].norm(), 0.0, epsilon = 1e-10);
1404    }
1405
1406    #[test]
1407    fn test_batch_circuit_execution() {
1408        let config = TPUConfig {
1409            device_type: TPUDeviceType::Simulated,
1410            batch_size: 2,
1411            ..Default::default()
1412        };
1413        let mut simulator =
1414            TPUQuantumSimulator::new(config).expect("Failed to create TPU simulator");
1415
1416        let mut circuit1 = InterfaceCircuit::new(2, 0);
1417        circuit1.add_gate(InterfaceGate::new(InterfaceGateType::H, vec![0]));
1418
1419        let mut circuit2 = InterfaceCircuit::new(2, 0);
1420        circuit2.add_gate(InterfaceGate::new(InterfaceGateType::X, vec![1]));
1421
1422        let circuits = vec![circuit1, circuit2];
1423
1424        let mut state1 = Array1::zeros(4);
1425        state1[0] = Complex64::new(1.0, 0.0);
1426        let mut state2 = Array1::zeros(4);
1427        state2[0] = Complex64::new(1.0, 0.0);
1428        let initial_states = vec![state1, state2];
1429
1430        let final_states = simulator
1431            .execute_batch_circuit(&circuits, &initial_states)
1432            .expect("Failed to execute batch circuit");
1433        assert_eq!(final_states.len(), 2);
1434
1435        // circuit2 applies X to qubit 1 of |00> -> |10> (index 0b10 = 2).
1436        assert_abs_diff_eq!(final_states[1][0b10].norm(), 1.0, epsilon = 1e-10);
1437    }
1438
1439    #[test]
1440    fn test_expectation_value_computation_real() {
1441        let mut simulator =
1442            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1443
1444        // state1 = |00>, state2 = |11>
1445        let mut state1 = Array1::zeros(4);
1446        state1[0] = Complex64::new(1.0, 0.0);
1447        let mut state2 = Array1::zeros(4);
1448        state2[3] = Complex64::new(1.0, 0.0);
1449
1450        let states = vec![state1, state2];
1451        let observables = vec!["Z0".to_string(), "Z1".to_string()];
1452
1453        let expectations = simulator
1454            .compute_expectation_values_tpu(&states, &observables)
1455            .expect("Failed to compute expectation values");
1456        assert_eq!(expectations.shape(), &[2, 2]);
1457        // <00|Z0|00> = +1, <00|Z1|00> = +1
1458        assert_abs_diff_eq!(expectations[[0, 0]], 1.0, epsilon = 1e-10);
1459        assert_abs_diff_eq!(expectations[[0, 1]], 1.0, epsilon = 1e-10);
1460        // <11|Z0|11> = -1, <11|Z1|11> = -1
1461        assert_abs_diff_eq!(expectations[[1, 0]], -1.0, epsilon = 1e-10);
1462        assert_abs_diff_eq!(expectations[[1, 1]], -1.0, epsilon = 1e-10);
1463    }
1464
1465    #[test]
1466    fn test_expectation_x_observable() {
1467        let mut simulator =
1468            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1469        // (|0> + |1>)/sqrt(2) is +1 eigenstate of X.
1470        let mut state = Array1::zeros(2);
1471        state[0] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1472        state[1] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1473        let exps = simulator
1474            .compute_expectation_values_tpu(&[state], &["X0".to_string()])
1475            .expect("expectation");
1476        assert_abs_diff_eq!(exps[[0, 0]], 1.0, epsilon = 1e-10);
1477    }
1478
1479    #[test]
1480    fn test_stats_tracking() {
1481        let mut stats = TPUStats::default();
1482        stats.update_operation(10.0, 1000);
1483        stats.update_operation(20.0, 2000);
1484        assert_eq!(stats.total_operations, 2);
1485        assert_abs_diff_eq!(stats.total_execution_time, 30.0, epsilon = 1e-10);
1486        assert_abs_diff_eq!(stats.avg_operation_time, 15.0, epsilon = 1e-10);
1487        assert_eq!(stats.total_flops, 3000);
1488    }
1489
1490    #[test]
1491    fn test_performance_metrics() {
1492        let stats = TPUStats {
1493            total_operations: 100,
1494            total_execution_time: 1000.0,
1495            total_flops: 1_000_000,
1496            xla_cache_hits: 80,
1497            xla_cache_misses: 20,
1498            ..Default::default()
1499        };
1500
1501        let metrics = stats.get_performance_metrics();
1502        assert!(metrics.contains_key("flops_per_second"));
1503        assert!(metrics.contains_key("operations_per_second"));
1504        assert!(metrics.contains_key("cache_hit_rate"));
1505        assert_abs_diff_eq!(metrics["operations_per_second"], 100.0, epsilon = 1e-10);
1506        assert_abs_diff_eq!(metrics["cache_hit_rate"], 0.8, epsilon = 1e-10);
1507    }
1508
1509    #[test]
1510    fn test_garbage_collection_only_frees_releasable() {
1511        let mut simulator =
1512            TPUQuantumSimulator::new(sim_config()).expect("Failed to create TPU simulator");
1513
1514        // A device-resident buffer is NOT freed.
1515        simulator.tensor_buffers.insert(
1516            "resident".to_string(),
1517            TPUTensorBuffer {
1518                buffer_id: 0,
1519                shape: vec![10],
1520                dtype: TPUDataType::Complex128,
1521                size_bytes: 1000,
1522                device_id: 0,
1523                on_device: true,
1524            },
1525        );
1526        // A released buffer IS freed.
1527        simulator.tensor_buffers.insert(
1528            "released".to_string(),
1529            TPUTensorBuffer {
1530                buffer_id: 1,
1531                shape: vec![10],
1532                dtype: TPUDataType::Complex128,
1533                size_bytes: 500,
1534                device_id: 0,
1535                on_device: false,
1536            },
1537        );
1538        simulator.memory_manager.used_memory = 1500;
1539
1540        let freed = simulator.garbage_collect().expect("gc");
1541        assert_eq!(freed, 500);
1542        assert_eq!(simulator.memory_manager.used_memory, 1000);
1543        assert!(simulator.tensor_buffers.contains_key("resident"));
1544        assert!(!simulator.tensor_buffers.contains_key("released"));
1545    }
1546
1547    #[test]
1548    fn test_benchmark_simulated_runs() {
1549        // Honest: benchmark only runs the CPU Simulated device and times real work.
1550        let results = benchmark_tpu_acceleration().expect("benchmark should run on CPU sim");
1551        assert!(results.contains_key("tpu_config_0"));
1552    }
1553
1554    #[test]
1555    fn test_tpu_data_types() {
1556        assert_eq!(TPUDataType::Float32.size_bytes(), 4);
1557        assert_eq!(TPUDataType::Float64.size_bytes(), 8);
1558        assert_eq!(TPUDataType::BFloat16.size_bytes(), 2);
1559        assert_eq!(TPUDataType::Complex64.size_bytes(), 8);
1560        assert_eq!(TPUDataType::Complex128.size_bytes(), 16);
1561    }
1562}