1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum TPUDeviceType {
29 TPUv2,
31 TPUv3,
33 TPUv4,
35 TPUv5e,
37 TPUv5p,
39 Simulated,
41}
42
43#[derive(Debug, Clone)]
45pub struct TPUConfig {
46 pub device_type: TPUDeviceType,
48 pub num_cores: usize,
50 pub memory_per_core: f64,
52 pub enable_mixed_precision: bool,
54 pub batch_size: usize,
56 pub enable_xla_compilation: bool,
58 pub topology: TPUTopology,
60 pub enable_distributed: bool,
62 pub max_tensor_size: usize,
64 pub memory_optimization: MemoryOptimization,
66}
67
68#[derive(Debug, Clone)]
70pub struct TPUTopology {
71 pub num_chips: usize,
73 pub chips_per_host: usize,
75 pub num_hosts: usize,
77 pub interconnect_bandwidth: f64,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum MemoryOptimization {
84 None,
86 Checkpointing,
88 Recomputation,
90 EfficientAttention,
92 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, 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, },
111 enable_distributed: false,
112 max_tensor_size: 1 << 28, memory_optimization: MemoryOptimization::Checkpointing,
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct TPUDeviceInfo {
121 pub device_id: usize,
123 pub device_type: TPUDeviceType,
125 pub core_count: usize,
127 pub memory_size: f64,
129 pub peak_flops: f64,
131 pub memory_bandwidth: f64,
133 pub supports_bfloat16: bool,
135 pub supports_complex: bool,
137 pub xla_version: String,
139}
140
141impl TPUDeviceInfo {
142 #[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, 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, 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, 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, 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, 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, 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
227pub struct TPUQuantumSimulator {
229 config: TPUConfig,
231 device_info: TPUDeviceInfo,
233 xla_computations: HashMap<String, XLAComputation>,
235 tensor_buffers: HashMap<String, TPUTensorBuffer>,
237 stats: TPUStats,
239 distributed_context: Option<DistributedContext>,
241 memory_manager: TPUMemoryManager,
243}
244
245#[derive(Debug, Clone)]
247pub struct XLAComputation {
248 pub name: String,
250 pub input_shapes: Vec<Vec<usize>>,
252 pub output_shapes: Vec<Vec<usize>>,
254 pub compilation_time: f64,
260 pub estimated_flops: u64,
263 pub memory_usage: usize,
265}
266
267#[derive(Debug, Clone)]
269pub struct TPUTensorBuffer {
270 pub buffer_id: usize,
272 pub shape: Vec<usize>,
274 pub dtype: TPUDataType,
276 pub size_bytes: usize,
278 pub device_id: usize,
280 pub on_device: bool,
282}
283
284#[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 #[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#[derive(Debug, Clone)]
314pub struct DistributedContext {
315 pub num_hosts: usize,
317 pub host_id: usize,
319 pub global_device_count: usize,
321 pub local_device_count: usize,
323 pub communication_backend: CommunicationBackend,
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum CommunicationBackend {
330 GRPC,
331 MPI,
332 NCCL,
333 GLOO,
334}
335
336#[derive(Debug, Clone)]
338pub struct TPUMemoryManager {
339 pub total_memory: usize,
341 pub used_memory: usize,
343 pub memory_pools: HashMap<String, MemoryPool>,
345 pub gc_enabled: bool,
347 pub fragmentation_ratio: f64,
349}
350
351#[derive(Debug, Clone)]
353pub struct MemoryPool {
354 pub name: String,
356 pub size: usize,
358 pub used: usize,
360 pub free_chunks: Vec<(usize, usize)>, pub allocated_chunks: HashMap<usize, usize>, }
365
366#[derive(Debug, Clone, Default, Serialize, Deserialize)]
368pub struct TPUStats {
369 pub total_operations: usize,
371 pub total_execution_time: f64,
373 pub avg_operation_time: f64,
375 pub total_flops: u64,
377 pub peak_flops_utilization: f64,
379 pub h2d_transfers: usize,
381 pub d2h_transfers: usize,
383 pub total_transfer_time: f64,
385 pub total_compilation_time: f64,
387 pub peak_memory_usage: usize,
389 pub xla_cache_hits: usize,
391 pub xla_cache_misses: usize,
393}
394
395impl TPUStats {
396 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 #[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
434fn 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 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 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 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 simulator.compile_standard_operations()?;
514
515 Ok(simulator)
516 }
517
518 fn compile_standard_operations(&mut self) -> Result<()> {
520 let start_time = std::time::Instant::now();
521
522 self.compile_single_qubit_gates()?;
524
525 self.compile_two_qubit_gates()?;
527
528 self.compile_state_vector_operations()?;
530
531 self.compile_measurement_operations()?;
533
534 self.compile_expectation_operations()?;
536
537 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 fn compile_single_qubit_gates(&mut self) -> Result<()> {
547 let computation = XLAComputation {
549 name: "batched_single_qubit_gates".to_string(),
550 input_shapes: vec![
551 vec![self.config.batch_size, 1 << 20], vec![2, 2], vec![1], ],
555 output_shapes: vec![
556 vec![self.config.batch_size, 1 << 20], ],
558 compilation_time: 0.0, estimated_flops: (self.config.batch_size * (1 << 20) * 8) as u64,
560 memory_usage: self.config.batch_size * (1 << 20) * 16, };
562
563 self.xla_computations
564 .insert("batched_single_qubit_gates".to_string(), computation);
565
566 let fused_rotations = XLAComputation {
568 name: "fused_rotation_gates".to_string(),
569 input_shapes: vec![
570 vec![self.config.batch_size, 1 << 20], vec![3], vec![1], ],
574 output_shapes: vec![
575 vec![self.config.batch_size, 1 << 20], ],
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 fn compile_two_qubit_gates(&mut self) -> Result<()> {
590 let cnot_computation = XLAComputation {
592 name: "batched_cnot_gates".to_string(),
593 input_shapes: vec![
594 vec![self.config.batch_size, 1 << 20], vec![1], vec![1], ],
598 output_shapes: vec![
599 vec![self.config.batch_size, 1 << 20], ],
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 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], vec![4, 4], vec![2], ],
617 output_shapes: vec![
618 vec![self.config.batch_size, 1 << 20], ],
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 fn compile_state_vector_operations(&mut self) -> Result<()> {
633 let normalization = XLAComputation {
635 name: "batch_normalize".to_string(),
636 input_shapes: vec![
637 vec![self.config.batch_size, 1 << 20], ],
639 output_shapes: vec![
640 vec![self.config.batch_size, 1 << 20], vec![self.config.batch_size], ],
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 let inner_product = XLAComputation {
653 name: "batch_inner_product".to_string(),
654 input_shapes: vec![
655 vec![self.config.batch_size, 1 << 20], vec![self.config.batch_size, 1 << 20], ],
658 output_shapes: vec![
659 vec![self.config.batch_size], ],
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 fn compile_measurement_operations(&mut self) -> Result<()> {
674 let probabilities = XLAComputation {
676 name: "compute_probabilities".to_string(),
677 input_shapes: vec![
678 vec![self.config.batch_size, 1 << 20], ],
680 output_shapes: vec![
681 vec![self.config.batch_size, 1 << 20], ],
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 let sampling = XLAComputation {
693 name: "quantum_sampling".to_string(),
694 input_shapes: vec![
695 vec![self.config.batch_size, 1 << 20], vec![self.config.batch_size], ],
698 output_shapes: vec![
699 vec![self.config.batch_size], ],
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 fn compile_expectation_operations(&mut self) -> Result<()> {
714 let pauli_expectation = XLAComputation {
716 name: "pauli_expectation_values".to_string(),
717 input_shapes: vec![
718 vec![self.config.batch_size, 1 << 20], vec![20], ],
721 output_shapes: vec![
722 vec![self.config.batch_size, 20], ],
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 let hamiltonian_expectation = XLAComputation {
734 name: "hamiltonian_expectation".to_string(),
735 input_shapes: vec![
736 vec![self.config.batch_size, 1 << 20], vec![1 << 20, 1 << 20], ],
739 output_shapes: vec![
740 vec![self.config.batch_size], ],
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 fn compile_qml_operations(&mut self) -> Result<()> {
757 let variational_circuit = XLAComputation {
759 name: "variational_circuit_batch".to_string(),
760 input_shapes: vec![
761 vec![self.config.batch_size, 1 << 20], vec![100], vec![50], ],
765 output_shapes: vec![
766 vec![self.config.batch_size, 1 << 20], ],
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 let parameter_shift_gradients = XLAComputation {
778 name: "parameter_shift_gradients".to_string(),
779 input_shapes: vec![
780 vec![self.config.batch_size, 1 << 20], vec![100], vec![50], vec![20], ],
785 output_shapes: vec![
786 vec![self.config.batch_size, 100], ],
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, };
792
793 self.xla_computations.insert(
794 "parameter_shift_gradients".to_string(),
795 parameter_shift_gradients,
796 );
797
798 Ok(())
799 }
800
801 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 self.allocate_batch_memory(circuits.len(), initial_states[0].len())?;
823
824 self.transfer_states_to_device(initial_states)?;
826
827 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 for gate in &circuit.gates {
835 current_state = self.apply_gate_tpu(¤t_state, gate)?;
836 }
837
838 final_states.push(current_state);
839 }
840
841 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; self.stats.update_operation(execution_time, estimated_flops);
847
848 Ok(final_states)
849 }
850
851 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 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 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 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, &) 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 fn allocate_batch_memory(&mut self, batch_size: usize, state_size: usize) -> Result<()> {
953 let total_size = batch_size * state_size * 16; if total_size > self.memory_manager.total_memory {
956 return Err(SimulatorError::MemoryError(
957 "Insufficient TPU memory".to_string(),
958 ));
959 }
960
961 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 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 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 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 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 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 #[must_use]
1101 pub const fn get_device_info(&self) -> &TPUDeviceInfo {
1102 &self.device_info
1103 }
1104
1105 #[must_use]
1107 pub const fn get_stats(&self) -> &TPUStats {
1108 &self.stats
1109 }
1110
1111 pub fn reset_stats(&mut self) {
1113 self.stats = TPUStats::default();
1114 }
1115
1116 #[must_use]
1123 pub const fn is_tpu_available(&self) -> bool {
1124 false
1125 }
1126
1127 #[must_use]
1129 pub fn is_simulated(&self) -> bool {
1130 self.device_info.device_type == TPUDeviceType::Simulated
1131 }
1132
1133 #[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 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
1169pub fn benchmark_tpu_acceleration() -> Result<HashMap<String, f64>> {
1176 let mut results = HashMap::new();
1177
1178 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 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 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 let mut state = Array1::zeros(1 << 10);
1224 state[0] = Complex64::new(1.0, 0.0);
1225 initial_states.push(state);
1226 }
1227
1228 let _final_states = simulator.execute_batch_circuit(&circuits, &initial_states)?;
1230
1231 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 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 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 let config = TPUConfig::default(); let result = TPUQuantumSimulator::new(config);
1278 assert!(result.is_err());
1279 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 assert!(!simulator.is_tpu_available());
1294 }
1295
1296 #[test]
1297 fn test_device_info_reference_specs() {
1298 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 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, 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); 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 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 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 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 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 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 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 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 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 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 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 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 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}