1use scirs2_core::Complex64;
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24
25use crate::error::{Result, SimulatorError};
26
27#[derive(Debug, Clone)]
29pub struct OpenCLPlatform {
30 pub platform_id: usize,
32 pub name: String,
34 pub vendor: String,
36 pub version: String,
38 pub extensions: Vec<String>,
40}
41
42#[derive(Debug, Clone)]
44pub struct OpenCLDevice {
45 pub device_id: usize,
47 pub name: String,
49 pub vendor: String,
51 pub device_type: OpenCLDeviceType,
53 pub compute_units: u32,
55 pub max_work_group_size: usize,
57 pub max_work_item_dimensions: u32,
59 pub max_work_item_sizes: Vec<usize>,
61 pub global_memory_size: u64,
63 pub local_memory_size: u64,
65 pub max_constant_buffer_size: u64,
67 pub supports_double: bool,
69 pub extensions: Vec<String>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum OpenCLDeviceType {
76 GPU,
77 CPU,
78 Accelerator,
79 Custom,
80 All,
81}
82
83#[derive(Debug, Clone)]
85pub struct OpenCLConfig {
86 pub preferred_vendor: Option<String>,
88 pub preferred_device_type: OpenCLDeviceType,
90 pub enable_profiling: bool,
92 pub max_buffer_size: usize,
94 pub work_group_size: usize,
96 pub enable_kernel_cache: bool,
98 pub optimization_level: OptimizationLevel,
100 pub enable_cpu_fallback: bool,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum OptimizationLevel {
107 None,
109 Basic,
111 Standard,
113 Aggressive,
115}
116
117impl Default for OpenCLConfig {
118 fn default() -> Self {
119 Self {
120 preferred_vendor: Some("Advanced Micro Devices".to_string()),
121 preferred_device_type: OpenCLDeviceType::GPU,
122 enable_profiling: true,
123 max_buffer_size: 1 << 30, work_group_size: 256,
125 enable_kernel_cache: true,
126 optimization_level: OptimizationLevel::Standard,
127 enable_cpu_fallback: true,
128 }
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct OpenCLKernel {
135 pub name: String,
137 pub source: String,
139 pub build_options: String,
141 pub local_memory_usage: usize,
143 pub work_group_size: usize,
145}
146
147#[derive(Debug, Clone)]
152pub struct OpenCLBuffer {
153 pub buffer_id: usize,
155 pub size: usize,
157 pub flags: MemoryFlags,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum MemoryFlags {
164 ReadWrite,
165 ReadOnly,
166 WriteOnly,
167 UseHostPtr,
168 AllocHostPtr,
169 CopyHostPtr,
170}
171
172#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct OpenCLStats {
179 pub total_kernel_executions: usize,
181 pub total_execution_time: f64,
183 pub avg_kernel_time: f64,
185 pub memory_transfer_time: f64,
187 pub compilation_time: f64,
189 pub gpu_memory_usage: u64,
191 pub gpu_utilization: f64,
193 pub state_vector_operations: usize,
195 pub gate_operations: usize,
197 pub cpu_fallback_count: usize,
199}
200
201impl OpenCLStats {
202 pub fn update_kernel_execution(&mut self, execution_time: f64) {
204 self.total_kernel_executions += 1;
205 self.total_execution_time += execution_time;
206 self.avg_kernel_time = self.total_execution_time / self.total_kernel_executions as f64;
207 }
208
209 #[must_use]
215 pub fn get_performance_metrics(&self) -> HashMap<String, f64> {
216 let mut metrics = HashMap::new();
217 if self.total_execution_time > 0.0 {
218 metrics.insert(
219 "kernel_executions_per_second".to_string(),
220 self.total_kernel_executions as f64 / (self.total_execution_time / 1000.0),
221 );
222 }
223 if self.memory_transfer_time > 0.0 {
224 metrics.insert(
225 "memory_bandwidth_gb_s".to_string(),
226 self.gpu_memory_usage as f64 / (self.memory_transfer_time / 1000.0) / 1e9,
227 );
228 }
229 metrics.insert("gpu_efficiency".to_string(), self.gpu_utilization / 100.0);
230 metrics
231 }
232}
233
234#[derive(Debug, Clone)]
236pub enum KernelArg {
237 Buffer(String),
238 ConstantBuffer(String),
239 Int(i32),
240 Float(f32),
241 Double(f64),
242 LocalMemory(usize),
243}
244
245pub struct AMDOpenCLSimulator {
252 config: OpenCLConfig,
254 device: Option<OpenCLDevice>,
256 kernels: HashMap<String, OpenCLKernel>,
258 stats: OpenCLStats,
260}
261
262impl AMDOpenCLSimulator {
263 pub fn new(_config: OpenCLConfig) -> Result<Self> {
270 Err(SimulatorError::UnsupportedOperation(
271 "AMD OpenCL backend: no OpenCL runtime available in this build \
272 (no OpenCL/ROCm SDK linked); cannot enumerate AMD devices or \
273 dispatch GPU kernels"
274 .to_string(),
275 ))
276 }
277
278 #[must_use]
280 pub const fn get_device_info(&self) -> Option<&OpenCLDevice> {
281 self.device.as_ref()
282 }
283
284 #[must_use]
286 pub const fn get_kernels(&self) -> &HashMap<String, OpenCLKernel> {
287 &self.kernels
288 }
289
290 #[must_use]
292 pub const fn get_stats(&self) -> &OpenCLStats {
293 &self.stats
294 }
295
296 #[must_use]
298 pub const fn config(&self) -> &OpenCLConfig {
299 &self.config
300 }
301
302 #[must_use]
309 pub fn kernel_source_templates(config: &OpenCLConfig) -> HashMap<String, OpenCLKernel> {
310 let build_options = Self::build_options_for(config);
311 let mut kernels = HashMap::new();
312 kernels.insert(
313 "single_qubit_gate".to_string(),
314 OpenCLKernel {
315 name: "single_qubit_gate".to_string(),
316 source: SINGLE_QUBIT_KERNEL_SRC.to_string(),
317 build_options: build_options.clone(),
318 local_memory_usage: 0,
319 work_group_size: config.work_group_size,
320 },
321 );
322 kernels.insert(
323 "two_qubit_gate".to_string(),
324 OpenCLKernel {
325 name: "two_qubit_gate".to_string(),
326 source: TWO_QUBIT_KERNEL_SRC.to_string(),
327 build_options: build_options.clone(),
328 local_memory_usage: 128,
329 work_group_size: config.work_group_size,
330 },
331 );
332 kernels.insert(
333 "state_vector_ops".to_string(),
334 OpenCLKernel {
335 name: "state_vector_ops".to_string(),
336 source: STATE_VECTOR_KERNEL_SRC.to_string(),
337 build_options: build_options.clone(),
338 local_memory_usage: config.work_group_size * 16,
339 work_group_size: config.work_group_size,
340 },
341 );
342 kernels.insert(
343 "measurement".to_string(),
344 OpenCLKernel {
345 name: "measurement".to_string(),
346 source: MEASUREMENT_KERNEL_SRC.to_string(),
347 build_options: build_options.clone(),
348 local_memory_usage: config.work_group_size * 16,
349 work_group_size: config.work_group_size,
350 },
351 );
352 kernels.insert(
353 "expectation_value".to_string(),
354 OpenCLKernel {
355 name: "expectation_value".to_string(),
356 source: EXPECTATION_KERNEL_SRC.to_string(),
357 build_options,
358 local_memory_usage: config.work_group_size * 8,
359 work_group_size: config.work_group_size,
360 },
361 );
362 kernels
363 }
364
365 #[must_use]
367 pub fn build_options_for(config: &OpenCLConfig) -> String {
368 let mut options = Vec::new();
369 match config.optimization_level {
370 OptimizationLevel::None => options.push("-O0"),
371 OptimizationLevel::Basic => options.push("-O1"),
372 OptimizationLevel::Standard => options.push("-O2"),
373 OptimizationLevel::Aggressive => options.push("-O3"),
374 }
375 options.push("-cl-mad-enable");
376 options.push("-cl-fast-relaxed-math");
377 options.join(" ")
378 }
379}
380
381const SINGLE_QUBIT_KERNEL_SRC: &str = r"
383 #pragma OPENCL EXTENSION cl_khr_fp64 : enable
384
385 typedef double2 complex_t;
386
387 complex_t complex_mul(complex_t a, complex_t b) {
388 return (complex_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
389 }
390
391 complex_t complex_add(complex_t a, complex_t b) {
392 return (complex_t)(a.x + b.x, a.y + b.y);
393 }
394
395 __kernel void single_qubit_gate(
396 __global complex_t* state,
397 __global const double* gate_matrix,
398 const int target_qubit,
399 const int num_qubits
400 ) {
401 const int global_id = get_global_id(0);
402 const int total_states = 1 << num_qubits;
403
404 if (global_id >= total_states / 2) return;
405
406 const int target_mask = 1 << target_qubit;
407 const int i = global_id;
408 const int j = i | target_mask;
409
410 if ((i & target_mask) == 0) {
411 complex_t gate_00 = (complex_t)(gate_matrix[0], gate_matrix[1]);
412 complex_t gate_01 = (complex_t)(gate_matrix[2], gate_matrix[3]);
413 complex_t gate_10 = (complex_t)(gate_matrix[4], gate_matrix[5]);
414 complex_t gate_11 = (complex_t)(gate_matrix[6], gate_matrix[7]);
415
416 complex_t state_i = state[i];
417 complex_t state_j = state[j];
418
419 state[i] = complex_add(complex_mul(gate_00, state_i), complex_mul(gate_01, state_j));
420 state[j] = complex_add(complex_mul(gate_10, state_i), complex_mul(gate_11, state_j));
421 }
422 }
423";
424
425const TWO_QUBIT_KERNEL_SRC: &str = r"
427 #pragma OPENCL EXTENSION cl_khr_fp64 : enable
428
429 typedef double2 complex_t;
430
431 complex_t complex_mul(complex_t a, complex_t b) {
432 return (complex_t)(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
433 }
434
435 complex_t complex_add(complex_t a, complex_t b) {
436 return (complex_t)(a.x + b.x, a.y + b.y);
437 }
438
439 __kernel void two_qubit_gate(
440 __global complex_t* state,
441 __global const double* gate_matrix,
442 const int control_qubit,
443 const int target_qubit,
444 const int num_qubits
445 ) {
446 const int global_id = get_global_id(0);
447 const int total_states = 1 << num_qubits;
448
449 if (global_id >= total_states / 4) return;
450
451 const int control_mask = 1 << control_qubit;
452 const int target_mask = 1 << target_qubit;
453 const int both_mask = control_mask | target_mask;
454
455 int base = global_id;
456 if (global_id & (target_mask - 1)) base = (base & ~(target_mask - 1)) << 1 | (base & (target_mask - 1));
457 if (base & (control_mask - 1)) base = (base & ~(control_mask - 1)) << 1 | (base & (control_mask - 1));
458
459 int state_00 = base;
460 int state_01 = base | target_mask;
461 int state_10 = base | control_mask;
462 int state_11 = base | both_mask;
463
464 complex_t gate[4][4];
465 for (int i = 0; i < 4; i++) {
466 for (int j = 0; j < 4; j++) {
467 gate[i][j] = (complex_t)(gate_matrix[(i*4+j)*2], gate_matrix[(i*4+j)*2+1]);
468 }
469 }
470
471 complex_t old_states[4];
472 old_states[0] = state[state_00];
473 old_states[1] = state[state_01];
474 old_states[2] = state[state_10];
475 old_states[3] = state[state_11];
476
477 complex_t new_states[4] = {0};
478 for (int i = 0; i < 4; i++) {
479 for (int j = 0; j < 4; j++) {
480 new_states[i] = complex_add(new_states[i], complex_mul(gate[i][j], old_states[j]));
481 }
482 }
483
484 state[state_00] = new_states[0];
485 state[state_01] = new_states[1];
486 state[state_10] = new_states[2];
487 state[state_11] = new_states[3];
488 }
489";
490
491const STATE_VECTOR_KERNEL_SRC: &str = r"
493 #pragma OPENCL EXTENSION cl_khr_fp64 : enable
494
495 typedef double2 complex_t;
496
497 __kernel void normalize_state(
498 __global complex_t* state,
499 const int num_states,
500 const double norm_factor
501 ) {
502 const int global_id = get_global_id(0);
503 if (global_id >= num_states) return;
504 state[global_id].x *= norm_factor;
505 state[global_id].y *= norm_factor;
506 }
507
508 __kernel void compute_probabilities(
509 __global const complex_t* state,
510 __global double* probabilities,
511 const int num_states
512 ) {
513 const int global_id = get_global_id(0);
514 if (global_id >= num_states) return;
515 complex_t amplitude = state[global_id];
516 probabilities[global_id] = amplitude.x * amplitude.x + amplitude.y * amplitude.y;
517 }
518";
519
520const MEASUREMENT_KERNEL_SRC: &str = r"
522 #pragma OPENCL EXTENSION cl_khr_fp64 : enable
523
524 typedef double2 complex_t;
525
526 __kernel void measure_qubit(
527 __global complex_t* state,
528 const int target_qubit,
529 const int num_qubits,
530 const int measurement_result
531 ) {
532 const int global_id = get_global_id(0);
533 const int total_states = 1 << num_qubits;
534 if (global_id >= total_states) return;
535
536 const int target_mask = 1 << target_qubit;
537 const int qubit_value = (global_id & target_mask) ? 1 : 0;
538 if (qubit_value != measurement_result) {
539 state[global_id] = (complex_t)(0.0, 0.0);
540 }
541 }
542";
543
544const EXPECTATION_KERNEL_SRC: &str = r"
546 #pragma OPENCL EXTENSION cl_khr_fp64 : enable
547
548 typedef double2 complex_t;
549
550 __kernel void expectation_value_pauli(
551 __global const complex_t* state,
552 __global double* partial_results,
553 __local double* local_data,
554 const int pauli_string,
555 const int num_qubits
556 ) {
557 const int global_id = get_global_id(0);
558 const int local_id = get_local_id(0);
559 const int local_size = get_local_size(0);
560 const int group_id = get_group_id(0);
561 const int total_states = 1 << num_qubits;
562
563 double local_expectation = 0.0;
564 if (global_id < total_states) {
565 complex_t amplitude = state[global_id];
566 double sign = 1.0;
567 for (int qubit = 0; qubit < num_qubits; qubit++) {
568 int pauli_op = (pauli_string >> (2 * qubit)) & 3;
569 int qubit_mask = 1 << qubit;
570 if (pauli_op == 3 && (global_id & qubit_mask)) sign *= -1.0;
571 }
572 local_expectation = sign * (amplitude.x * amplitude.x + amplitude.y * amplitude.y);
573 }
574
575 local_data[local_id] = local_expectation;
576 barrier(CLK_LOCAL_MEM_FENCE);
577 for (int stride = local_size / 2; stride > 0; stride /= 2) {
578 if (local_id < stride) {
579 local_data[local_id] += local_data[local_id + stride];
580 }
581 barrier(CLK_LOCAL_MEM_FENCE);
582 }
583 if (local_id == 0) {
584 partial_results[group_id] = local_data[0];
585 }
586 }
587";
588
589pub fn benchmark_amd_opencl_backend() -> Result<HashMap<String, f64>> {
595 Err(SimulatorError::UnsupportedOperation(
596 "AMD OpenCL backend: no OpenCL runtime available in this build; \
597 refusing to report fabricated benchmark numbers"
598 .to_string(),
599 ))
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605
606 #[test]
607 fn test_opencl_simulator_unavailable() {
608 let config = OpenCLConfig::default();
610 let result = AMDOpenCLSimulator::new(config);
611 assert!(result.is_err());
612 match result.err() {
614 Some(SimulatorError::UnsupportedOperation(msg)) => {
615 assert!(msg.contains("OpenCL"));
616 }
617 other => panic!("expected UnsupportedOperation, got {other:?}"),
618 }
619 }
620
621 #[test]
622 fn test_benchmark_unavailable() {
623 let result = benchmark_amd_opencl_backend();
624 assert!(result.is_err());
625 }
626
627 #[test]
628 fn test_kernel_source_templates_present() {
629 let config = OpenCLConfig::default();
631 let kernels = AMDOpenCLSimulator::kernel_source_templates(&config);
632 assert!(kernels.contains_key("single_qubit_gate"));
633 assert!(kernels.contains_key("two_qubit_gate"));
634 assert!(kernels.contains_key("state_vector_ops"));
635 assert!(kernels.contains_key("measurement"));
636 assert!(kernels.contains_key("expectation_value"));
637 assert!(!kernels["single_qubit_gate"].source.is_empty());
638 }
639
640 #[test]
641 fn test_build_options() {
642 let config = OpenCLConfig {
643 optimization_level: OptimizationLevel::Aggressive,
644 ..Default::default()
645 };
646 let build_options = AMDOpenCLSimulator::build_options_for(&config);
647 assert!(build_options.contains("-O3"));
648 assert!(build_options.contains("-cl-mad-enable"));
649 assert!(build_options.contains("-cl-fast-relaxed-math"));
650 }
651
652 #[test]
653 fn test_stats_update() {
654 let mut stats = OpenCLStats::default();
655 stats.update_kernel_execution(10.0);
656 stats.update_kernel_execution(20.0);
657 assert_eq!(stats.total_kernel_executions, 2);
658 assert!((stats.total_execution_time - 30.0).abs() < 1e-10);
659 assert!((stats.avg_kernel_time - 15.0).abs() < 1e-10);
660 }
661
662 #[test]
663 fn test_performance_metrics_from_recorded_counters() {
664 let mut stats = OpenCLStats {
666 total_kernel_executions: 100,
667 total_execution_time: 1000.0,
668 gpu_memory_usage: 1_000_000_000,
669 memory_transfer_time: 100.0,
670 gpu_utilization: 85.0,
671 ..Default::default()
672 };
673 let metrics = stats.get_performance_metrics();
674 assert!(metrics.contains_key("kernel_executions_per_second"));
675 assert!(metrics.contains_key("memory_bandwidth_gb_s"));
676 assert!(metrics.contains_key("gpu_efficiency"));
677 assert!((metrics["kernel_executions_per_second"] - 100.0).abs() < 1e-10);
678 assert!((metrics["gpu_efficiency"] - 0.85).abs() < 1e-10);
679
680 stats.gpu_utilization = 0.0;
682 let metrics0 = stats.get_performance_metrics();
683 assert!((metrics0["gpu_efficiency"] - 0.0).abs() < 1e-10);
684 }
685
686 #[test]
687 fn test_memory_flags_distinct() {
688 assert_ne!(MemoryFlags::ReadWrite, MemoryFlags::ReadOnly);
689 }
690
691 #[test]
692 fn test_buffer_descriptor_fields() {
693 let buffer = OpenCLBuffer {
694 buffer_id: 0,
695 size: 1024,
696 flags: MemoryFlags::ReadWrite,
697 };
698 assert_eq!(buffer.size, 1024);
699 assert_eq!(buffer.buffer_id, 0);
700 }
701}