Skip to main content

quantrs2_sim/
opencl_amd_backend.rs

1//! `OpenCL` Backend for AMD GPU Acceleration
2//!
3//! This module defines the data model and kernel *source templates* for a
4//! quantum circuit simulation backend targeting AMD GPUs via `OpenCL`.
5//!
6//! HONEST-AVAILABILITY NOTE
7//! ------------------------
8//! This build does **not** link any `OpenCL` / AMD `ROCm` runtime. There is no
9//! way here to enumerate real AMD devices, allocate device memory, compile a
10//! kernel, or dispatch GPU work. Consequently the construction / availability
11//! entry points (`AMDOpenCLSimulator::new`, `benchmark_amd_opencl_backend`)
12//! return an honest [`SimulatorError::UnsupportedOperation`] instead of
13//! fabricating device discovery (e.g. a hardcoded "Radeon RX 7900 XTX" with
14//! invented compute-unit / memory figures) or fabricating kernel timings.
15//!
16//! The public type definitions and the `OpenCL` kernel *source strings* are
17//! retained: they are plain data / text and are useful to callers that have a
18//! real `OpenCL` toolchain available outside this build. They never claim that
19//! any silicon compiled or executed them.
20
21use scirs2_core::Complex64;
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24
25use crate::error::{Result, SimulatorError};
26
27/// `OpenCL` platform information
28#[derive(Debug, Clone)]
29pub struct OpenCLPlatform {
30    /// Platform ID
31    pub platform_id: usize,
32    /// Platform name
33    pub name: String,
34    /// Platform vendor
35    pub vendor: String,
36    /// Platform version
37    pub version: String,
38    /// Supported extensions
39    pub extensions: Vec<String>,
40}
41
42/// `OpenCL` device information
43#[derive(Debug, Clone)]
44pub struct OpenCLDevice {
45    /// Device ID
46    pub device_id: usize,
47    /// Device name
48    pub name: String,
49    /// Device vendor
50    pub vendor: String,
51    /// Device type (GPU, CPU, etc.)
52    pub device_type: OpenCLDeviceType,
53    /// Compute units
54    pub compute_units: u32,
55    /// Maximum work group size
56    pub max_work_group_size: usize,
57    /// Maximum work item dimensions
58    pub max_work_item_dimensions: u32,
59    /// Maximum work item sizes
60    pub max_work_item_sizes: Vec<usize>,
61    /// Global memory size
62    pub global_memory_size: u64,
63    /// Local memory size
64    pub local_memory_size: u64,
65    /// Maximum constant buffer size
66    pub max_constant_buffer_size: u64,
67    /// Supports double precision
68    pub supports_double: bool,
69    /// Device extensions
70    pub extensions: Vec<String>,
71}
72
73/// `OpenCL` device types
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum OpenCLDeviceType {
76    GPU,
77    CPU,
78    Accelerator,
79    Custom,
80    All,
81}
82
83/// `OpenCL` backend configuration
84#[derive(Debug, Clone)]
85pub struct OpenCLConfig {
86    /// Preferred platform vendor
87    pub preferred_vendor: Option<String>,
88    /// Preferred device type
89    pub preferred_device_type: OpenCLDeviceType,
90    /// Enable performance profiling
91    pub enable_profiling: bool,
92    /// Maximum memory allocation per buffer
93    pub max_buffer_size: usize,
94    /// Work group size for kernels
95    pub work_group_size: usize,
96    /// Enable kernel caching
97    pub enable_kernel_cache: bool,
98    /// `OpenCL` optimization level
99    pub optimization_level: OptimizationLevel,
100    /// Enable automatic fallback to CPU
101    pub enable_cpu_fallback: bool,
102}
103
104/// `OpenCL` optimization levels
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum OptimizationLevel {
107    /// No optimization (-O0)
108    None,
109    /// Basic optimization (-O1)
110    Basic,
111    /// Standard optimization (-O2)
112    Standard,
113    /// Aggressive optimization (-O3)
114    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, // 1GB
124            work_group_size: 256,
125            enable_kernel_cache: true,
126            optimization_level: OptimizationLevel::Standard,
127            enable_cpu_fallback: true,
128        }
129    }
130}
131
132/// `OpenCL` kernel information
133#[derive(Debug, Clone)]
134pub struct OpenCLKernel {
135    /// Kernel name
136    pub name: String,
137    /// Kernel source code
138    pub source: String,
139    /// Compilation options
140    pub build_options: String,
141    /// Local memory usage
142    pub local_memory_usage: usize,
143    /// Work group size
144    pub work_group_size: usize,
145}
146
147/// `OpenCL` memory buffer descriptor.
148///
149/// This is a host-side *description* of a buffer; in this build no device
150/// allocation backs it (there is no `OpenCL` runtime).
151#[derive(Debug, Clone)]
152pub struct OpenCLBuffer {
153    /// Buffer ID
154    pub buffer_id: usize,
155    /// Buffer size in bytes
156    pub size: usize,
157    /// Memory flags
158    pub flags: MemoryFlags,
159}
160
161/// `OpenCL` memory flags
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum MemoryFlags {
164    ReadWrite,
165    ReadOnly,
166    WriteOnly,
167    UseHostPtr,
168    AllocHostPtr,
169    CopyHostPtr,
170}
171
172/// `OpenCL` performance statistics.
173///
174/// These counters are honest accounting helpers: every value is whatever the
175/// caller measured/recorded. The backend itself does not populate them with
176/// fabricated numbers.
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct OpenCLStats {
179    /// Total kernel executions
180    pub total_kernel_executions: usize,
181    /// Total execution time (ms)
182    pub total_execution_time: f64,
183    /// Average kernel execution time (ms)
184    pub avg_kernel_time: f64,
185    /// Memory transfer time (ms)
186    pub memory_transfer_time: f64,
187    /// Compilation time (ms)
188    pub compilation_time: f64,
189    /// GPU memory usage (bytes)
190    pub gpu_memory_usage: u64,
191    /// GPU utilization percentage
192    pub gpu_utilization: f64,
193    /// Number of state vector operations
194    pub state_vector_operations: usize,
195    /// Number of gate operations
196    pub gate_operations: usize,
197    /// Fallback to CPU count
198    pub cpu_fallback_count: usize,
199}
200
201impl OpenCLStats {
202    /// Update statistics after kernel execution
203    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    /// Calculate performance metrics from recorded counters.
210    ///
211    /// `gpu_efficiency` is `gpu_utilization / 100.0`, i.e. it is derived
212    /// directly from whatever utilization the caller recorded - it is not a
213    /// fabricated constant.
214    #[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/// Kernel argument types
235#[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
245/// AMD GPU-optimized quantum simulator using `OpenCL`.
246///
247/// In this build there is no `OpenCL` runtime, so this type cannot be
248/// constructed (see [`AMDOpenCLSimulator::new`]). The type and its associated
249/// kernel-source templates are retained for callers that link a real `OpenCL`
250/// toolchain elsewhere.
251pub struct AMDOpenCLSimulator {
252    /// Configuration
253    config: OpenCLConfig,
254    /// Selected device (only set when a real runtime is present)
255    device: Option<OpenCLDevice>,
256    /// Compiled kernel sources (text templates)
257    kernels: HashMap<String, OpenCLKernel>,
258    /// Performance statistics
259    stats: OpenCLStats,
260}
261
262impl AMDOpenCLSimulator {
263    /// Create a new AMD `OpenCL` simulator.
264    ///
265    /// HONEST AVAILABILITY GATE: this build links no `OpenCL`/AMD `ROCm`
266    /// runtime, so AMD device discovery and GPU kernel dispatch are impossible.
267    /// Constructing a working simulator here would require fabricating device
268    /// discovery and kernel timings, so we fail loudly instead.
269    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    /// Get device information (only present with a real runtime).
279    #[must_use]
280    pub const fn get_device_info(&self) -> Option<&OpenCLDevice> {
281        self.device.as_ref()
282    }
283
284    /// Get the compiled kernel-source templates.
285    #[must_use]
286    pub const fn get_kernels(&self) -> &HashMap<String, OpenCLKernel> {
287        &self.kernels
288    }
289
290    /// Get performance statistics.
291    #[must_use]
292    pub const fn get_stats(&self) -> &OpenCLStats {
293        &self.stats
294    }
295
296    /// Get the backend configuration.
297    #[must_use]
298    pub const fn config(&self) -> &OpenCLConfig {
299        &self.config
300    }
301
302    /// Build the standard set of quantum `OpenCL` kernel *source templates*.
303    ///
304    /// Returns the kernels as text only; nothing here compiles or executes on a
305    /// device. Exposed as an associated function so the templates remain
306    /// reachable/inspectable even though [`Self::new`] cannot succeed in this
307    /// build.
308    #[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    /// Build `OpenCL` kernel-compilation options for the given configuration.
366    #[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
381/// `OpenCL` single-qubit-gate kernel source (text template).
382const 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
425/// `OpenCL` two-qubit-gate kernel source (text template).
426const 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
491/// `OpenCL` state-vector-operations kernel source (text template).
492const 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
520/// `OpenCL` measurement kernel source (text template).
521const 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
544/// `OpenCL` expectation-value kernel source (text template).
545const 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
589/// Benchmark the AMD `OpenCL` backend.
590///
591/// HONEST GATE: with no `OpenCL` runtime there is nothing real to benchmark, so
592/// this returns an honest [`SimulatorError::UnsupportedOperation`] rather than
593/// fabricating throughput / utilization figures.
594pub 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        // Honest behavior: no OpenCL runtime in this build, so construction fails.
609        let config = OpenCLConfig::default();
610        let result = AMDOpenCLSimulator::new(config);
611        assert!(result.is_err());
612        // Match on `.err()` so the `Ok` simulator value need not be `Debug`.
613        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        // The kernel source templates are plain text and remain inspectable.
630        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        // gpu_efficiency is derived from recorded gpu_utilization, not fabricated.
665        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        // With no recorded utilization, efficiency is honestly 0.
681        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}