1use super::{GpuBackend, GpuBuffer, GpuKernel};
7use crate::{
8 error::{QuantRS2Error, QuantRS2Result},
9 qubit::QubitId,
10};
11use scirs2_core::ndarray::Array2;
12use scirs2_core::Complex64;
13use std::sync::{Arc, Mutex};
14
15pub struct CpuBuffer {
17 data: Arc<Mutex<Vec<Complex64>>>,
18}
19
20impl CpuBuffer {
21 pub fn new(size: usize) -> Self {
23 Self {
24 data: Arc::new(Mutex::new(vec![Complex64::new(0.0, 0.0); size])),
25 }
26 }
27
28 pub fn data(&self) -> std::sync::MutexGuard<'_, Vec<Complex64>> {
30 self.data.lock().unwrap_or_else(|e| e.into_inner())
31 }
32}
33
34impl GpuBuffer for CpuBuffer {
35 fn size(&self) -> usize {
36 self.data.lock().unwrap_or_else(|e| e.into_inner()).len() * std::mem::size_of::<Complex64>()
37 }
38
39 fn upload(&mut self, data: &[Complex64]) -> QuantRS2Result<()> {
40 let mut buffer = self.data.lock().unwrap_or_else(|e| e.into_inner());
41 if buffer.len() != data.len() {
42 return Err(QuantRS2Error::InvalidInput(format!(
43 "Buffer size mismatch: {} != {}",
44 buffer.len(),
45 data.len()
46 )));
47 }
48 buffer.copy_from_slice(data);
49 Ok(())
50 }
51
52 fn download(&self, data: &mut [Complex64]) -> QuantRS2Result<()> {
53 let buffer = self.data.lock().unwrap_or_else(|e| e.into_inner());
54 if buffer.len() != data.len() {
55 return Err(QuantRS2Error::InvalidInput(format!(
56 "Buffer size mismatch: {} != {}",
57 buffer.len(),
58 data.len()
59 )));
60 }
61 data.copy_from_slice(&buffer);
62 Ok(())
63 }
64
65 fn sync(&self) -> QuantRS2Result<()> {
66 Ok(())
68 }
69
70 fn as_any(&self) -> &dyn std::any::Any {
71 self
72 }
73
74 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
75 self
76 }
77}
78
79pub struct CpuKernel;
81
82impl CpuKernel {
83 fn apply_gate_to_indices(state: &mut [Complex64], gate: &[Complex64], indices: &[usize]) {
85 let gate_size = indices.len();
86 let mut temp = vec![Complex64::new(0.0, 0.0); gate_size];
87
88 for (i, &idx) in indices.iter().enumerate() {
90 temp[i] = state[idx];
91 }
92
93 for (i, &idx) in indices.iter().enumerate() {
95 let mut sum = Complex64::new(0.0, 0.0);
96 for j in 0..gate_size {
97 sum += gate[i * gate_size + j] * temp[j];
98 }
99 state[idx] = sum;
100 }
101 }
102}
103
104impl GpuKernel for CpuKernel {
105 fn apply_single_qubit_gate(
106 &self,
107 state: &mut dyn GpuBuffer,
108 gate_matrix: &[Complex64; 4],
109 qubit: QubitId,
110 n_qubits: usize,
111 ) -> QuantRS2Result<()> {
112 let cpu_buffer = state
113 .as_any_mut()
114 .downcast_mut::<CpuBuffer>()
115 .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
116
117 let mut data = cpu_buffer.data();
118 let qubit_idx = qubit.0 as usize;
119 let stride = 1 << qubit_idx;
120 let pairs = 1 << (n_qubits - 1);
121
122 for i in 0..pairs {
124 let i0 = ((i >> qubit_idx) << (qubit_idx + 1)) | (i & ((1 << qubit_idx) - 1));
125 let i1 = i0 | stride;
126
127 let a = data[i0];
128 let b = data[i1];
129
130 data[i0] = gate_matrix[0] * a + gate_matrix[1] * b;
131 data[i1] = gate_matrix[2] * a + gate_matrix[3] * b;
132 }
133
134 Ok(())
135 }
136
137 fn apply_two_qubit_gate(
138 &self,
139 state: &mut dyn GpuBuffer,
140 gate_matrix: &[Complex64; 16],
141 control: QubitId,
142 target: QubitId,
143 n_qubits: usize,
144 ) -> QuantRS2Result<()> {
145 let cpu_buffer = state
146 .as_any_mut()
147 .downcast_mut::<CpuBuffer>()
148 .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
149
150 let mut data = cpu_buffer.data();
151 let control_idx = control.0 as usize;
152 let target_idx = target.0 as usize;
153
154 if control_idx >= n_qubits || target_idx >= n_qubits {
155 return Err(QuantRS2Error::InvalidInput(format!(
156 "Qubit index out of range: control={control_idx}, target={target_idx}, \
157 n_qubits={n_qubits}"
158 )));
159 }
160 if control_idx == target_idx {
161 return Err(QuantRS2Error::InvalidInput(format!(
162 "Control and target must differ, both are qubit {control_idx}"
163 )));
164 }
165
166 let control_stride = 1usize << control_idx;
173 let target_stride = 1usize << target_idx;
174
175 let (high_idx, low_idx) = if control_idx > target_idx {
176 (control_idx, target_idx)
177 } else {
178 (target_idx, control_idx)
179 };
180
181 let low_mask = (1usize << low_idx) - 1;
184 let mid_mask = ((1usize << (high_idx - 1)) - 1) ^ low_mask;
185 let groups = 1usize << (n_qubits - 2);
186
187 for group in 0..groups {
188 let base = (group & low_mask)
189 | ((group & mid_mask) << 1)
190 | ((group >> (high_idx - 1)) << (high_idx + 1));
191
192 let indices = [
193 base,
194 base | target_stride,
195 base | control_stride,
196 base | control_stride | target_stride,
197 ];
198
199 Self::apply_gate_to_indices(&mut data, gate_matrix, &indices);
200 }
201
202 Ok(())
203 }
204
205 fn apply_multi_qubit_gate(
206 &self,
207 state: &mut dyn GpuBuffer,
208 gate_matrix: &Array2<Complex64>,
209 qubits: &[QubitId],
210 n_qubits: usize,
211 ) -> QuantRS2Result<()> {
212 let cpu_buffer = state
213 .as_any_mut()
214 .downcast_mut::<CpuBuffer>()
215 .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
216
217 let mut data = cpu_buffer.data();
218 let gate_qubits = qubits.len();
219 let gate_dim = 1 << gate_qubits;
220
221 if gate_matrix.dim() != (gate_dim, gate_dim) {
222 return Err(QuantRS2Error::InvalidInput(format!(
223 "Gate matrix dimension mismatch: {:?} != ({}, {})",
224 gate_matrix.dim(),
225 gate_dim,
226 gate_dim
227 )));
228 }
229
230 let operand_indices: Vec<usize> = qubits.iter().map(|q| q.0 as usize).collect();
236
237 if let Some(&bad) = operand_indices.iter().find(|&&q| q >= n_qubits) {
238 return Err(QuantRS2Error::InvalidInput(format!(
239 "Qubit index {bad} out of range for a {n_qubits}-qubit state"
240 )));
241 }
242
243 let mut sorted_indices = operand_indices.clone();
244 sorted_indices.sort_unstable();
245 if sorted_indices.windows(2).any(|w| w[0] == w[1]) {
246 return Err(QuantRS2Error::InvalidInput(format!(
247 "Gate operands must be distinct qubits, got {operand_indices:?}"
248 )));
249 }
250
251 let gate_flat: Vec<Complex64> = gate_matrix.iter().copied().collect();
253
254 let affected_states = 1usize << gate_qubits;
256 let unaffected_qubits = n_qubits - gate_qubits;
257 let iterations = 1usize << unaffected_qubits;
258
259 let mut indices = vec![0usize; affected_states];
260
261 for i in 0..iterations {
263 let mut base = 0usize;
266 let mut remaining = i;
267 let mut qubit_pos = 0;
268
269 for bit in 0..n_qubits {
270 if qubit_pos < gate_qubits && bit == sorted_indices[qubit_pos] {
271 qubit_pos += 1;
272 } else {
273 if remaining & 1 == 1 {
274 base |= 1 << bit;
275 }
276 remaining >>= 1;
277 }
278 }
279
280 for (j, slot) in indices.iter_mut().enumerate() {
284 let mut idx = base;
285 for (position, &qubit_idx) in operand_indices.iter().enumerate() {
286 if (j >> (gate_qubits - 1 - position)) & 1 == 1 {
287 idx |= 1 << qubit_idx;
288 }
289 }
290 *slot = idx;
291 }
292
293 Self::apply_gate_to_indices(&mut data, &gate_flat, &indices);
294 }
295
296 Ok(())
297 }
298
299 fn measure_qubit(
300 &self,
301 state: &dyn GpuBuffer,
302 qubit: QubitId,
303 n_qubits: usize,
304 ) -> QuantRS2Result<(bool, f64)> {
305 let cpu_buffer = state
306 .as_any()
307 .downcast_ref::<CpuBuffer>()
308 .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
309
310 let data = cpu_buffer.data();
311 let qubit_idx = qubit.0 as usize;
312 let mut prob_one = 0.0;
316 for i in 0..(1 << n_qubits) {
317 if (i >> qubit_idx) & 1 == 1 {
318 prob_one += data[i].norm_sqr();
319 }
320 }
321
322 use scirs2_core::random::prelude::*;
324 let outcome = thread_rng().random::<f64>() < prob_one;
325
326 Ok((outcome, if outcome { prob_one } else { 1.0 - prob_one }))
327 }
328
329 fn expectation_value(
330 &self,
331 state: &dyn GpuBuffer,
332 observable: &Array2<Complex64>,
333 qubits: &[QubitId],
334 n_qubits: usize,
335 ) -> QuantRS2Result<f64> {
336 let cpu_buffer = state
337 .as_any()
338 .downcast_ref::<CpuBuffer>()
339 .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
340
341 let data = cpu_buffer.data();
342
343 if qubits.len() != 1 || observable.dim() != (2, 2) {
345 return Err(QuantRS2Error::UnsupportedOperation(
346 "Only single-qubit observables supported currently".to_string(),
347 ));
348 }
349
350 let qubit_idx = qubits[0].0 as usize;
351 let stride = 1 << qubit_idx;
352 let pairs = 1 << (n_qubits - 1);
353
354 let mut expectation = Complex64::new(0.0, 0.0);
355
356 for i in 0..pairs {
357 let i0 = ((i >> qubit_idx) << (qubit_idx + 1)) | (i & ((1 << qubit_idx) - 1));
358 let i1 = i0 | stride;
359
360 let a = data[i0];
361 let b = data[i1];
362
363 expectation += a.conj() * (observable[(0, 0)] * a + observable[(0, 1)] * b);
364 expectation += b.conj() * (observable[(1, 0)] * a + observable[(1, 1)] * b);
365 }
366
367 if expectation.im.abs() > 1e-10 {
368 return Err(QuantRS2Error::InvalidInput(
369 "Observable expectation value is not real".to_string(),
370 ));
371 }
372
373 Ok(expectation.re)
374 }
375}
376
377pub struct CpuBackend {
379 kernel: CpuKernel,
380}
381
382impl CpuBackend {
383 pub const fn new() -> Self {
385 Self { kernel: CpuKernel }
386 }
387}
388
389impl Default for CpuBackend {
390 fn default() -> Self {
391 Self::new()
392 }
393}
394
395impl GpuBackend for CpuBackend {
396 fn is_available() -> bool {
397 true }
399
400 fn name(&self) -> &'static str {
401 "CPU"
402 }
403
404 fn device_info(&self) -> String {
405 use scirs2_core::parallel_ops::current_num_threads;
407 format!("CPU backend with {} threads", current_num_threads())
408 }
409
410 fn allocate_state_vector(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
411 let size = 1 << n_qubits;
412 Ok(Box::new(CpuBuffer::new(size)))
413 }
414
415 fn allocate_density_matrix(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
416 let size = 1 << (2 * n_qubits);
417 Ok(Box::new(CpuBuffer::new(size)))
418 }
419
420 fn kernel(&self) -> &dyn GpuKernel {
421 &self.kernel
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 #[test]
430 fn test_cpu_buffer() {
431 let mut buffer = CpuBuffer::new(4);
432 let data = vec![
433 Complex64::new(1.0, 0.0),
434 Complex64::new(0.0, 1.0),
435 Complex64::new(-1.0, 0.0),
436 Complex64::new(0.0, -1.0),
437 ];
438
439 buffer
440 .upload(&data)
441 .expect("Failed to upload data to buffer");
442
443 let mut downloaded = vec![Complex64::new(0.0, 0.0); 4];
444 buffer
445 .download(&mut downloaded)
446 .expect("Failed to download data from buffer");
447
448 assert_eq!(data, downloaded);
449 }
450
451 #[test]
452 fn test_cpu_backend() {
453 let backend = CpuBackend::new();
454 assert!(CpuBackend::is_available());
455 assert_eq!(backend.name(), "CPU");
456
457 let buffer = backend
459 .allocate_state_vector(3)
460 .expect("Failed to allocate state vector");
461 assert_eq!(buffer.size(), 8 * std::mem::size_of::<Complex64>());
462 }
463
464 fn cnot_matrix() -> [Complex64; 16] {
467 let one = Complex64::new(1.0, 0.0);
468 let zero = Complex64::new(0.0, 0.0);
469 [
470 one, zero, zero, zero, zero, one, zero, zero, zero, zero, zero, one, zero, zero, one, zero,
474 ]
475 }
476
477 fn state_after_cnot(
478 n_qubits: usize,
479 control: u32,
480 target: u32,
481 initial: &[Complex64],
482 ) -> Vec<Complex64> {
483 let backend = CpuBackend::new();
484 let mut buffer = backend
485 .allocate_state_vector(n_qubits)
486 .expect("Failed to allocate state vector");
487 buffer.upload(initial).expect("Failed to upload state");
488
489 backend
490 .kernel()
491 .apply_two_qubit_gate(
492 buffer.as_mut(),
493 &cnot_matrix(),
494 QubitId(control),
495 QubitId(target),
496 n_qubits,
497 )
498 .expect("Failed to apply CNOT");
499
500 let mut out = vec![Complex64::new(0.0, 0.0); initial.len()];
501 buffer.download(&mut out).expect("Failed to download state");
502 out
503 }
504
505 #[test]
510 fn test_cnot_respects_control_target_order() {
511 let zero = Complex64::new(0.0, 0.0);
512 let one = Complex64::new(1.0, 0.0);
513
514 let mut basis_q0_set = vec![zero; 4];
516 basis_q0_set[1] = one;
517
518 let flipped = state_after_cnot(2, 0, 1, &basis_q0_set);
520 assert_eq!(flipped[3], one, "CNOT(q0->q1) must flip the target qubit");
521 assert_eq!(flipped[1], zero);
522
523 let unchanged = state_after_cnot(2, 1, 0, &basis_q0_set);
525 assert_eq!(
526 unchanged[1], one,
527 "CNOT(q1->q0) must be the identity when q1 = 0"
528 );
529 }
530
531 #[test]
532 fn test_cnot_builds_bell_state() {
533 let backend = CpuBackend::new();
534 let mut buffer = backend
535 .allocate_state_vector(2)
536 .expect("Failed to allocate state vector");
537
538 let inv_sqrt2 = std::f64::consts::FRAC_1_SQRT_2;
539 let h = [
540 Complex64::new(inv_sqrt2, 0.0),
541 Complex64::new(inv_sqrt2, 0.0),
542 Complex64::new(inv_sqrt2, 0.0),
543 Complex64::new(-inv_sqrt2, 0.0),
544 ];
545
546 let mut initial = vec![Complex64::new(0.0, 0.0); 4];
547 initial[0] = Complex64::new(1.0, 0.0);
548 buffer.upload(&initial).expect("Failed to upload state");
549
550 backend
551 .kernel()
552 .apply_single_qubit_gate(buffer.as_mut(), &h, QubitId(0), 2)
553 .expect("Failed to apply H");
554 backend
555 .kernel()
556 .apply_two_qubit_gate(buffer.as_mut(), &cnot_matrix(), QubitId(0), QubitId(1), 2)
557 .expect("Failed to apply CNOT");
558
559 let mut out = vec![Complex64::new(0.0, 0.0); 4];
560 buffer.download(&mut out).expect("Failed to download state");
561
562 let probs: Vec<f64> = out.iter().map(|c| c.norm_sqr()).collect();
563 assert!((probs[0] - 0.5).abs() < 1e-12, "probs = {probs:?}");
564 assert!(probs[1].abs() < 1e-12, "probs = {probs:?}");
565 assert!(probs[2].abs() < 1e-12, "probs = {probs:?}");
566 assert!((probs[3] - 0.5).abs() < 1e-12, "probs = {probs:?}");
567 }
568
569 #[test]
572 fn test_cnot_on_non_adjacent_qubits_preserves_spectators() {
573 let zero = Complex64::new(0.0, 0.0);
574 let one = Complex64::new(1.0, 0.0);
575
576 let n_qubits = 5;
578 let spectators = (1 << 0) | (1 << 2) | (1 << 3);
579 let start_index = spectators | (1 << 1); let mut initial = vec![zero; 1 << n_qubits];
582 initial[start_index] = one;
583
584 let out = state_after_cnot(n_qubits, 1, 4, &initial);
585
586 let expected_index = start_index | (1 << 4);
587 assert_eq!(out[expected_index], one, "target q4 should have flipped");
588 assert_eq!(out[start_index], zero);
589 assert_eq!(
590 out.iter().filter(|c| c.norm_sqr() > 1e-24).count(),
591 1,
592 "exactly one basis state should carry amplitude"
593 );
594 }
595
596 #[test]
599 fn test_multi_qubit_gate_respects_operand_order() {
600 use scirs2_core::ndarray::Array2;
601
602 let zero = Complex64::new(0.0, 0.0);
603 let one = Complex64::new(1.0, 0.0);
604
605 let mut toffoli = Array2::from_elem((8, 8), zero);
607 for i in 0..6 {
608 toffoli[(i, i)] = one;
609 }
610 toffoli[(6, 7)] = one;
611 toffoli[(7, 6)] = one;
612
613 let backend = CpuBackend::new();
614
615 let mut initial = vec![zero; 8];
617 initial[5] = one;
618
619 let mut buffer = backend
620 .allocate_state_vector(3)
621 .expect("Failed to allocate state vector");
622 buffer.upload(&initial).expect("Failed to upload state");
623
624 backend
625 .kernel()
626 .apply_multi_qubit_gate(
627 buffer.as_mut(),
628 &toffoli,
629 &[QubitId(2), QubitId(0), QubitId(1)],
630 3,
631 )
632 .expect("Failed to apply Toffoli");
633
634 let mut out = vec![zero; 8];
635 buffer.download(&mut out).expect("Failed to download state");
636
637 assert_eq!(out[7], one, "target q1 should have flipped, got {out:?}");
638 assert_eq!(out[5], zero);
639 }
640
641 #[test]
642 fn test_two_qubit_gate_rejects_invalid_operands() {
643 let backend = CpuBackend::new();
644 let mut buffer = backend
645 .allocate_state_vector(2)
646 .expect("Failed to allocate state vector");
647
648 assert!(backend
649 .kernel()
650 .apply_two_qubit_gate(buffer.as_mut(), &cnot_matrix(), QubitId(0), QubitId(0), 2)
651 .is_err());
652 assert!(backend
653 .kernel()
654 .apply_two_qubit_gate(buffer.as_mut(), &cnot_matrix(), QubitId(0), QubitId(5), 2)
655 .is_err());
656 }
657}