Skip to main content

quantrs2_sim/
dynamic.rs

1//! Dynamic circuit dispatch for circuits of varying qubit counts.
2//!
3//! [`DynamicCircuit`] wraps concrete const-generic circuit types behind an
4//! enum so circuits of different sizes can be handled uniformly at runtime
5//! without heap allocation overhead.
6
7use crate::simulator::Simulator; // Local simulator trait
8#[cfg(feature = "python")]
9use pyo3::exceptions::PyValueError;
10#[cfg(feature = "python")]
11use pyo3::PyResult;
12use quantrs2_circuit::builder::Circuit;
13use quantrs2_circuit::builder::Simulator as CircuitSimulator; // Circuit simulator trait
14#[cfg(feature = "python")]
15use quantrs2_core::gate::multi::{CRX, CRY, CRZ};
16#[cfg(feature = "python")]
17use quantrs2_core::gate::single::{RotationX, RotationY, RotationZ};
18use quantrs2_core::{
19    error::{QuantRS2Error, QuantRS2Result},
20    gate::GateOp,
21};
22use scirs2_core::Complex64;
23
24// Unused imports
25#[allow(unused_imports)]
26use crate::simulator::SimulatorResult;
27use crate::statevector::StateVectorSimulator;
28#[allow(unused_imports)]
29use quantrs2_core::qubit::QubitId;
30#[allow(unused_imports)]
31use std::collections::HashMap;
32
33#[cfg(all(feature = "gpu", not(target_os = "macos")))]
34use crate::gpu::GpuStateVectorSimulator;
35
36/// A dynamic circuit that encapsulates circuits of different qubit counts
37///
38/// Wraps the const-generic [`quantrs2_circuit::builder::Circuit`] types behind an
39/// enum so circuits of different sizes can be handled at runtime without heap
40/// allocation overhead.
41///
42/// # Examples
43///
44/// ```rust
45/// use quantrs2_sim::dynamic::DynamicCircuit;
46/// use quantrs2_core::gate::functions::single::Hadamard;
47/// use quantrs2_core::qubit::QubitId;
48///
49/// // Create a 2-qubit circuit dynamically
50/// let mut dc = DynamicCircuit::new(2).expect("2 qubits supported");
51/// assert_eq!(dc.num_qubits(), 2);
52///
53/// // Apply a Hadamard gate
54/// let q0 = QubitId::new(0);
55/// dc.apply_gate(Hadamard { target: q0 }).expect("H gate applied");
56/// assert_eq!(dc.gates().len(), 1);
57/// ```
58pub enum DynamicCircuit {
59    /// 2-qubit circuit
60    Q2(Circuit<2>),
61    /// 3-qubit circuit
62    Q3(Circuit<3>),
63    /// 4-qubit circuit
64    Q4(Circuit<4>),
65    /// 5-qubit circuit
66    Q5(Circuit<5>),
67    /// 6-qubit circuit
68    Q6(Circuit<6>),
69    /// 7-qubit circuit
70    Q7(Circuit<7>),
71    /// 8-qubit circuit
72    Q8(Circuit<8>),
73    /// 9-qubit circuit
74    Q9(Circuit<9>),
75    /// 10-qubit circuit
76    Q10(Circuit<10>),
77    /// 12-qubit circuit
78    Q12(Circuit<12>),
79    /// 16-qubit circuit
80    Q16(Circuit<16>),
81    /// 20-qubit circuit
82    Q20(Circuit<20>),
83    /// 24-qubit circuit
84    Q24(Circuit<24>),
85    /// 32-qubit circuit
86    Q32(Circuit<32>),
87}
88
89impl DynamicCircuit {
90    /// Create a new dynamic circuit with the specified number of qubits
91    pub fn new(n_qubits: usize) -> QuantRS2Result<Self> {
92        match n_qubits {
93            2 => Ok(Self::Q2(Circuit::<2>::new())),
94            3 => Ok(Self::Q3(Circuit::<3>::new())),
95            4 => Ok(Self::Q4(Circuit::<4>::new())),
96            5 => Ok(Self::Q5(Circuit::<5>::new())),
97            6 => Ok(Self::Q6(Circuit::<6>::new())),
98            7 => Ok(Self::Q7(Circuit::<7>::new())),
99            8 => Ok(Self::Q8(Circuit::<8>::new())),
100            9 => Ok(Self::Q9(Circuit::<9>::new())),
101            10 => Ok(Self::Q10(Circuit::<10>::new())),
102            12 => Ok(Self::Q12(Circuit::<12>::new())),
103            16 => Ok(Self::Q16(Circuit::<16>::new())),
104            20 => Ok(Self::Q20(Circuit::<20>::new())),
105            24 => Ok(Self::Q24(Circuit::<24>::new())),
106            32 => Ok(Self::Q32(Circuit::<32>::new())),
107            _ => Err(QuantRS2Error::UnsupportedQubits(
108                n_qubits,
109                "Supported qubit counts are 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16, 20, 24, and 32."
110                    .to_string(),
111            )),
112        }
113    }
114
115    /// Get the list of gate names in the circuit
116    #[must_use]
117    pub fn gates(&self) -> Vec<String> {
118        self.get_gate_names()
119    }
120
121    // This method is duplicated later in the file, removing it here
122
123    // This method is duplicated later in the file, removing it here
124
125    // This method is duplicated later in the file, removing it here
126
127    // This method is duplicated later in the file, removing it here
128
129    // This method is duplicated later in the file, removing it here
130
131    /// Get the number of qubits in the circuit
132    #[must_use]
133    pub const fn num_qubits(&self) -> usize {
134        match self {
135            Self::Q2(_) => 2,
136            Self::Q3(_) => 3,
137            Self::Q4(_) => 4,
138            Self::Q5(_) => 5,
139            Self::Q6(_) => 6,
140            Self::Q7(_) => 7,
141            Self::Q8(_) => 8,
142            Self::Q9(_) => 9,
143            Self::Q10(_) => 10,
144            Self::Q12(_) => 12,
145            Self::Q16(_) => 16,
146            Self::Q20(_) => 20,
147            Self::Q24(_) => 24,
148            Self::Q32(_) => 32,
149        }
150    }
151
152    /// Get the gate names in the circuit
153    #[must_use]
154    pub fn get_gate_names(&self) -> Vec<String> {
155        match self {
156            Self::Q2(c) => c
157                .gates()
158                .iter()
159                .map(|gate| gate.name().to_string())
160                .collect(),
161            Self::Q3(c) => c
162                .gates()
163                .iter()
164                .map(|gate| gate.name().to_string())
165                .collect(),
166            Self::Q4(c) => c
167                .gates()
168                .iter()
169                .map(|gate| gate.name().to_string())
170                .collect(),
171            Self::Q5(c) => c
172                .gates()
173                .iter()
174                .map(|gate| gate.name().to_string())
175                .collect(),
176            Self::Q6(c) => c
177                .gates()
178                .iter()
179                .map(|gate| gate.name().to_string())
180                .collect(),
181            Self::Q7(c) => c
182                .gates()
183                .iter()
184                .map(|gate| gate.name().to_string())
185                .collect(),
186            Self::Q8(c) => c
187                .gates()
188                .iter()
189                .map(|gate| gate.name().to_string())
190                .collect(),
191            Self::Q9(c) => c
192                .gates()
193                .iter()
194                .map(|gate| gate.name().to_string())
195                .collect(),
196            Self::Q10(c) => c
197                .gates()
198                .iter()
199                .map(|gate| gate.name().to_string())
200                .collect(),
201            Self::Q12(c) => c
202                .gates()
203                .iter()
204                .map(|gate| gate.name().to_string())
205                .collect(),
206            Self::Q16(c) => c
207                .gates()
208                .iter()
209                .map(|gate| gate.name().to_string())
210                .collect(),
211            Self::Q20(c) => c
212                .gates()
213                .iter()
214                .map(|gate| gate.name().to_string())
215                .collect(),
216            Self::Q24(c) => c
217                .gates()
218                .iter()
219                .map(|gate| gate.name().to_string())
220                .collect(),
221            Self::Q32(c) => c
222                .gates()
223                .iter()
224                .map(|gate| gate.name().to_string())
225                .collect(),
226        }
227    }
228
229    /// Get a reference to the `flat_index`-th gate in this circuit,
230    /// regardless of which concrete qubit-count variant it is. This is
231    /// what lets the introspection getters below give real (not
232    /// hardcoded) answers for every circuit size, not just [`Self::Q2`].
233    #[cfg(feature = "python")]
234    fn get_gate_by_flat_index(&self, flat_index: usize) -> Option<&(dyn GateOp + Send + Sync)> {
235        match self {
236            Self::Q2(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
237            Self::Q3(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
238            Self::Q4(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
239            Self::Q5(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
240            Self::Q6(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
241            Self::Q7(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
242            Self::Q8(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
243            Self::Q9(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
244            Self::Q10(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
245            Self::Q12(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
246            Self::Q16(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
247            Self::Q20(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
248            Self::Q24(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
249            Self::Q32(c) => c.gates().get(flat_index).map(|g| g.as_ref()),
250        }
251    }
252
253    /// Find the `index`-th gate named `gate_type` and return a reference
254    /// to it, or an honest `PyErr` if there is no such occurrence.
255    #[cfg(feature = "python")]
256    fn find_nth_gate(
257        &self,
258        gate_type: &str,
259        index: usize,
260    ) -> PyResult<&(dyn GateOp + Send + Sync)> {
261        let gates = self.get_gate_names();
262        let mut count = 0;
263        for (i, name) in gates.iter().enumerate() {
264            if name == gate_type {
265                if count == index {
266                    return self.get_gate_by_flat_index(i).ok_or_else(|| {
267                        PyValueError::new_err(format!(
268                            "Gate {gate_type} at index {index} vanished while reading it"
269                        ))
270                    });
271                }
272                count += 1;
273            }
274        }
275        Err(PyValueError::new_err(format!(
276            "Gate {gate_type} at index {index} not found"
277        )))
278    }
279
280    /// Get the qubit for a single-qubit gate. Works for every circuit
281    /// size (previously only [`Self::Q2`] returned the real qubit; every
282    /// other variant returned a hardcoded `0`).
283    #[cfg(feature = "python")]
284    pub fn get_single_qubit_for_gate(&self, gate_type: &str, index: usize) -> PyResult<u32> {
285        let gate = self.find_nth_gate(gate_type, index)?;
286        let qubits = gate.qubits();
287        if qubits.len() != 1 {
288            return Err(PyValueError::new_err(format!(
289                "Gate {gate_type} at index {index} acts on {} qubit(s), expected 1",
290                qubits.len()
291            )));
292        }
293        Ok(qubits[0].id())
294    }
295
296    /// Get the real `(qubit, angle)` parameters for a single-qubit
297    /// rotation gate (RX/RY/RZ) by downcasting to the concrete gate
298    /// struct and reading its actual `theta` field -- not a hardcoded
299    /// `(0, 0.0)`.
300    #[cfg(feature = "python")]
301    pub fn get_rotation_params_for_gate(
302        &self,
303        gate_type: &str,
304        index: usize,
305    ) -> PyResult<(u32, f64)> {
306        let gate = self.find_nth_gate(gate_type, index)?;
307        let qubits = gate.qubits();
308        if qubits.is_empty() {
309            return Err(PyValueError::new_err(format!(
310                "Gate {gate_type} at index {index} has no qubits"
311            )));
312        }
313
314        let theta = if let Some(g) = gate.as_any().downcast_ref::<RotationX>() {
315            g.theta
316        } else if let Some(g) = gate.as_any().downcast_ref::<RotationY>() {
317            g.theta
318        } else if let Some(g) = gate.as_any().downcast_ref::<RotationZ>() {
319            g.theta
320        } else {
321            return Err(PyValueError::new_err(format!(
322                "Gate {gate_type} at index {index} is not a recognized single-qubit rotation \
323                 gate (RX/RY/RZ); cannot extract its rotation angle"
324            )));
325        };
326
327        Ok((qubits[0].id(), theta))
328    }
329
330    /// Get the real `(qubit1, qubit2)` for a two-qubit gate (CNOT, CZ,
331    /// SWAP, etc.), for every circuit size.
332    #[cfg(feature = "python")]
333    pub fn get_two_qubit_params_for_gate(
334        &self,
335        gate_type: &str,
336        index: usize,
337    ) -> PyResult<(u32, u32)> {
338        let gate = self.find_nth_gate(gate_type, index)?;
339        let qubits = gate.qubits();
340        if qubits.len() != 2 {
341            return Err(PyValueError::new_err(format!(
342                "Gate {gate_type} at index {index} acts on {} qubit(s), expected 2",
343                qubits.len()
344            )));
345        }
346        Ok((qubits[0].id(), qubits[1].id()))
347    }
348
349    /// Get the real `(control, target, angle)` for a controlled rotation
350    /// gate (CRX/CRY/CRZ) by downcasting to the concrete gate struct and
351    /// reading its actual `theta` field -- not a hardcoded
352    /// `(0, 1, 0.0)`.
353    #[cfg(feature = "python")]
354    pub fn get_controlled_rotation_params_for_gate(
355        &self,
356        gate_type: &str,
357        index: usize,
358    ) -> PyResult<(u32, u32, f64)> {
359        let gate = self.find_nth_gate(gate_type, index)?;
360        let qubits = gate.qubits();
361        if qubits.len() != 2 {
362            return Err(PyValueError::new_err(format!(
363                "Gate {gate_type} at index {index} acts on {} qubit(s), expected 2 (control, target)",
364                qubits.len()
365            )));
366        }
367
368        let theta = if let Some(g) = gate.as_any().downcast_ref::<CRX>() {
369            g.theta
370        } else if let Some(g) = gate.as_any().downcast_ref::<CRY>() {
371            g.theta
372        } else if let Some(g) = gate.as_any().downcast_ref::<CRZ>() {
373            g.theta
374        } else {
375            return Err(PyValueError::new_err(format!(
376                "Gate {gate_type} at index {index} is not a recognized controlled rotation gate \
377                 (CRX/CRY/CRZ); cannot extract its rotation angle"
378            )));
379        };
380
381        Ok((qubits[0].id(), qubits[1].id(), theta))
382    }
383
384    /// Get the real `(qubit1, qubit2, qubit3)` for a three-qubit gate
385    /// (Toffoli, Fredkin, etc.), for every circuit size.
386    #[cfg(feature = "python")]
387    pub fn get_three_qubit_params_for_gate(
388        &self,
389        gate_type: &str,
390        index: usize,
391    ) -> PyResult<(u32, u32, u32)> {
392        let gate = self.find_nth_gate(gate_type, index)?;
393        let qubits = gate.qubits();
394        if qubits.len() != 3 {
395            return Err(PyValueError::new_err(format!(
396                "Gate {gate_type} at index {index} acts on {} qubit(s), expected 3",
397                qubits.len()
398            )));
399        }
400        Ok((qubits[0].id(), qubits[1].id(), qubits[2].id()))
401    }
402
403    /// Apply a gate to the circuit
404    pub fn apply_gate<G: GateOp + Clone + Send + Sync + 'static>(
405        &mut self,
406        gate: G,
407    ) -> QuantRS2Result<()> {
408        match self {
409            Self::Q2(c) => c.add_gate(gate).map(|_| ()),
410            Self::Q3(c) => c.add_gate(gate).map(|_| ()),
411            Self::Q4(c) => c.add_gate(gate).map(|_| ()),
412            Self::Q5(c) => c.add_gate(gate).map(|_| ()),
413            Self::Q6(c) => c.add_gate(gate).map(|_| ()),
414            Self::Q7(c) => c.add_gate(gate).map(|_| ()),
415            Self::Q8(c) => c.add_gate(gate).map(|_| ()),
416            Self::Q9(c) => c.add_gate(gate).map(|_| ()),
417            Self::Q10(c) => c.add_gate(gate).map(|_| ()),
418            Self::Q12(c) => c.add_gate(gate).map(|_| ()),
419            Self::Q16(c) => c.add_gate(gate).map(|_| ()),
420            Self::Q20(c) => c.add_gate(gate).map(|_| ()),
421            Self::Q24(c) => c.add_gate(gate).map(|_| ()),
422            Self::Q32(c) => c.add_gate(gate).map(|_| ()),
423        }
424    }
425
426    /// Run the circuit on a CPU simulator
427    pub fn run(&self, simulator: &StateVectorSimulator) -> QuantRS2Result<DynamicResult> {
428        match self {
429            Self::Q2(c) => {
430                let result = simulator.run(c)?;
431                Ok(DynamicResult {
432                    amplitudes: result.amplitudes().to_vec(),
433                    num_qubits: 2,
434                })
435            }
436            Self::Q3(c) => {
437                let result = simulator.run(c)?;
438                Ok(DynamicResult {
439                    amplitudes: result.amplitudes().to_vec(),
440                    num_qubits: 3,
441                })
442            }
443            Self::Q4(c) => {
444                let result = simulator.run(c)?;
445                Ok(DynamicResult {
446                    amplitudes: result.amplitudes().to_vec(),
447                    num_qubits: 4,
448                })
449            }
450            Self::Q5(c) => {
451                let result = simulator.run(c)?;
452                Ok(DynamicResult {
453                    amplitudes: result.amplitudes().to_vec(),
454                    num_qubits: 5,
455                })
456            }
457            Self::Q6(c) => {
458                let result = simulator.run(c)?;
459                Ok(DynamicResult {
460                    amplitudes: result.amplitudes().to_vec(),
461                    num_qubits: 6,
462                })
463            }
464            Self::Q7(c) => {
465                let result = simulator.run(c)?;
466                Ok(DynamicResult {
467                    amplitudes: result.amplitudes().to_vec(),
468                    num_qubits: 7,
469                })
470            }
471            Self::Q8(c) => {
472                let result = simulator.run(c)?;
473                Ok(DynamicResult {
474                    amplitudes: result.amplitudes().to_vec(),
475                    num_qubits: 8,
476                })
477            }
478            Self::Q9(c) => {
479                let result = simulator.run(c)?;
480                Ok(DynamicResult {
481                    amplitudes: result.amplitudes().to_vec(),
482                    num_qubits: 9,
483                })
484            }
485            Self::Q10(c) => {
486                let result = simulator.run(c)?;
487                Ok(DynamicResult {
488                    amplitudes: result.amplitudes().to_vec(),
489                    num_qubits: 10,
490                })
491            }
492            Self::Q12(c) => {
493                let result = simulator.run(c)?;
494                Ok(DynamicResult {
495                    amplitudes: result.amplitudes().to_vec(),
496                    num_qubits: 12,
497                })
498            }
499            Self::Q16(c) => {
500                let result = simulator.run(c)?;
501                Ok(DynamicResult {
502                    amplitudes: result.amplitudes().to_vec(),
503                    num_qubits: 16,
504                })
505            }
506            Self::Q20(c) => {
507                let result = simulator.run(c)?;
508                Ok(DynamicResult {
509                    amplitudes: result.amplitudes().to_vec(),
510                    num_qubits: 20,
511                })
512            }
513            Self::Q24(c) => {
514                let result = simulator.run(c)?;
515                Ok(DynamicResult {
516                    amplitudes: result.amplitudes().to_vec(),
517                    num_qubits: 24,
518                })
519            }
520            Self::Q32(c) => {
521                let result = simulator.run(c)?;
522                Ok(DynamicResult {
523                    amplitudes: result.amplitudes().to_vec(),
524                    num_qubits: 32,
525                })
526            }
527        }
528    }
529
530    /// Check if GPU acceleration is available
531    #[cfg(all(feature = "gpu", not(target_os = "macos")))]
532    pub fn is_gpu_available() -> bool {
533        GpuStateVectorSimulator::is_available()
534    }
535
536    /// Run the circuit on a GPU simulator
537    #[cfg(all(feature = "gpu", not(target_os = "macos")))]
538    pub fn run_gpu(&self) -> QuantRS2Result<DynamicResult> {
539        // Try to create the GPU simulator
540        let mut gpu_simulator = match GpuStateVectorSimulator::new_blocking() {
541            Ok(sim) => sim,
542            Err(e) => {
543                return Err(QuantRS2Error::BackendExecutionFailed(format!(
544                    "Failed to create GPU simulator: {}",
545                    e
546                )))
547            }
548        };
549
550        // Run the circuit on the GPU
551        match self {
552            DynamicCircuit::Q2(c) => {
553                let result = gpu_simulator.run(c).map_err(|e| {
554                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
555                })?;
556                Ok(DynamicResult {
557                    amplitudes: result.amplitudes.clone(),
558                    num_qubits: 2,
559                })
560            }
561            DynamicCircuit::Q3(c) => {
562                let result = gpu_simulator.run(c).map_err(|e| {
563                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
564                })?;
565                Ok(DynamicResult {
566                    amplitudes: result.amplitudes.clone(),
567                    num_qubits: 3,
568                })
569            }
570            DynamicCircuit::Q4(c) => {
571                let result = gpu_simulator.run(c).map_err(|e| {
572                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
573                })?;
574                Ok(DynamicResult {
575                    amplitudes: result.amplitudes.clone(),
576                    num_qubits: 4,
577                })
578            }
579            DynamicCircuit::Q5(c) => {
580                let result = gpu_simulator.run(c).map_err(|e| {
581                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
582                })?;
583                Ok(DynamicResult {
584                    amplitudes: result.amplitudes.clone(),
585                    num_qubits: 5,
586                })
587            }
588            DynamicCircuit::Q6(c) => {
589                let result = gpu_simulator.run(c).map_err(|e| {
590                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
591                })?;
592                Ok(DynamicResult {
593                    amplitudes: result.amplitudes.clone(),
594                    num_qubits: 6,
595                })
596            }
597            DynamicCircuit::Q7(c) => {
598                let result = gpu_simulator.run(c).map_err(|e| {
599                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
600                })?;
601                Ok(DynamicResult {
602                    amplitudes: result.amplitudes.clone(),
603                    num_qubits: 7,
604                })
605            }
606            DynamicCircuit::Q8(c) => {
607                let result = gpu_simulator.run(c).map_err(|e| {
608                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
609                })?;
610                Ok(DynamicResult {
611                    amplitudes: result.amplitudes.clone(),
612                    num_qubits: 8,
613                })
614            }
615            DynamicCircuit::Q9(c) => {
616                let result = gpu_simulator.run(c).map_err(|e| {
617                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
618                })?;
619                Ok(DynamicResult {
620                    amplitudes: result.amplitudes.clone(),
621                    num_qubits: 9,
622                })
623            }
624            DynamicCircuit::Q10(c) => {
625                let result = gpu_simulator.run(c).map_err(|e| {
626                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
627                })?;
628                Ok(DynamicResult {
629                    amplitudes: result.amplitudes.clone(),
630                    num_qubits: 10,
631                })
632            }
633            DynamicCircuit::Q12(c) => {
634                let result = gpu_simulator.run(c).map_err(|e| {
635                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
636                })?;
637                Ok(DynamicResult {
638                    amplitudes: result.amplitudes.clone(),
639                    num_qubits: 12,
640                })
641            }
642            DynamicCircuit::Q16(c) => {
643                let result = gpu_simulator.run(c).map_err(|e| {
644                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
645                })?;
646                Ok(DynamicResult {
647                    amplitudes: result.amplitudes.clone(),
648                    num_qubits: 16,
649                })
650            }
651            DynamicCircuit::Q20(c) => {
652                let result = gpu_simulator.run(c).map_err(|e| {
653                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
654                })?;
655                Ok(DynamicResult {
656                    amplitudes: result.amplitudes.clone(),
657                    num_qubits: 20,
658                })
659            }
660            DynamicCircuit::Q24(c) => {
661                let result = gpu_simulator.run(c).map_err(|e| {
662                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
663                })?;
664                Ok(DynamicResult {
665                    amplitudes: result.amplitudes.clone(),
666                    num_qubits: 24,
667                })
668            }
669            DynamicCircuit::Q32(c) => {
670                let result = gpu_simulator.run(c).map_err(|e| {
671                    QuantRS2Error::BackendExecutionFailed(format!("GPU simulation failed: {}", e))
672                })?;
673                Ok(DynamicResult {
674                    amplitudes: result.amplitudes.clone(),
675                    num_qubits: 32,
676                })
677            }
678        }
679    }
680
681    /// Check if GPU acceleration is available (stub for macOS)
682    #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
683    #[must_use]
684    pub const fn is_gpu_available() -> bool {
685        false
686    }
687
688    /// Run the circuit on a GPU simulator (stub for macOS)
689    #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
690    pub fn run_gpu(&self) -> QuantRS2Result<DynamicResult> {
691        Err(QuantRS2Error::BackendExecutionFailed(
692            "GPU acceleration is not available on this platform".to_string(),
693        ))
694    }
695
696    /// Run the circuit on the best available simulator (GPU if available, CPU otherwise)
697    #[cfg(all(feature = "gpu", not(target_os = "macos")))]
698    pub fn run_best(&self) -> QuantRS2Result<DynamicResult> {
699        if Self::is_gpu_available() && self.num_qubits() >= 4 {
700            self.run_gpu()
701        } else {
702            let simulator = StateVectorSimulator::new();
703            self.run(&simulator)
704        }
705    }
706
707    /// Run the circuit on the best available simulator (CPU only on macOS with GPU feature)
708    #[cfg(all(feature = "gpu", target_os = "macos"))]
709    pub fn run_best(&self) -> QuantRS2Result<DynamicResult> {
710        let simulator = StateVectorSimulator::new();
711        self.run(&simulator)
712    }
713
714    /// Run the circuit on the best available simulator (CPU only if GPU feature is disabled)
715    #[cfg(not(feature = "gpu"))]
716    pub fn run_best(&self) -> QuantRS2Result<DynamicResult> {
717        let simulator = StateVectorSimulator::new();
718        self.run(&simulator)
719    }
720}
721
722/// Dynamic simulation result that can handle any qubit count
723pub struct DynamicResult {
724    /// State vector amplitudes
725    pub amplitudes: Vec<Complex64>,
726    /// Number of qubits
727    pub num_qubits: usize,
728}
729
730impl DynamicResult {
731    /// Get the state vector amplitudes
732    #[must_use]
733    pub fn amplitudes(&self) -> &[Complex64] {
734        &self.amplitudes
735    }
736
737    /// Get the probabilities for each basis state
738    #[must_use]
739    pub fn probabilities(&self) -> Vec<f64> {
740        self.amplitudes
741            .iter()
742            .map(scirs2_core::Complex::norm_sqr)
743            .collect()
744    }
745
746    /// Get the number of qubits
747    #[must_use]
748    pub const fn num_qubits(&self) -> usize {
749        self.num_qubits
750    }
751}
752
753#[cfg(all(test, feature = "python"))]
754mod python_introspection_tests {
755    use super::*;
756    use quantrs2_core::gate::multi::CRY;
757    use quantrs2_core::gate::single::RotationY;
758    use quantrs2_core::qubit::QubitId;
759
760    /// Regression test for the P1 finding: the Python-exposed introspection
761    /// getters used to return hardcoded placeholder tuples (e.g. `(0, 0.0)`)
762    /// for every `DynamicCircuit` variant other than `Q2`. A `Q3` circuit
763    /// (three qubits) must now report the gate's *real* qubit index and
764    /// angle, not the `Q2`-only placeholder.
765    #[test]
766    fn test_single_qubit_and_rotation_params_for_q3_circuit() {
767        let mut dc = DynamicCircuit::new(3).expect("3 qubits supported");
768        // Place the rotation on qubit 2, which the old hardcoded fallback
769        // (`_ => return Ok(0)` / `Ok((0, 0.0))`) could never report.
770        dc.apply_gate(RotationY {
771            target: QubitId::new(2),
772            theta: 1.2345,
773        })
774        .expect("RY gate applied");
775
776        let qubit = dc
777            .get_single_qubit_for_gate("RY", 0)
778            .expect("real qubit for RY gate");
779        assert_eq!(
780            qubit, 2,
781            "expected the gate's real target qubit (2), not a hardcoded 0"
782        );
783
784        let (qubit, theta) = dc
785            .get_rotation_params_for_gate("RY", 0)
786            .expect("real rotation params for RY gate");
787        assert_eq!(qubit, 2);
788        assert!(
789            (theta - 1.2345).abs() < 1e-12,
790            "expected the gate's real angle (1.2345), not a hardcoded 0.0, got {theta}"
791        );
792    }
793
794    /// Regression test: controlled-rotation params on a `Q4` circuit must
795    /// report the real (control, target, angle), not the hardcoded
796    /// `(0, 1, 0.0)` placeholder that every non-`Q2` variant used to return.
797    #[test]
798    fn test_controlled_rotation_params_for_q4_circuit() {
799        let mut dc = DynamicCircuit::new(4).expect("4 qubits supported");
800        dc.apply_gate(CRY {
801            control: QubitId::new(3),
802            target: QubitId::new(1),
803            theta: 0.4321,
804        })
805        .expect("CRY gate applied");
806
807        let (control, target, theta) = dc
808            .get_controlled_rotation_params_for_gate("CRY", 0)
809            .expect("real controlled-rotation params for CRY gate");
810        assert_eq!(
811            control, 3,
812            "expected the gate's real control qubit (3), not a hardcoded 0"
813        );
814        assert_eq!(
815            target, 1,
816            "expected the gate's real target qubit (1), not a hardcoded 1-by-coincidence"
817        );
818        assert!(
819            (theta - 0.4321).abs() < 1e-12,
820            "expected the gate's real angle (0.4321), not a hardcoded 0.0, got {theta}"
821        );
822    }
823
824    /// Regression test: a gate lookup that legitimately doesn't exist must
825    /// return an honest `PyErr`, not a fabricated placeholder success.
826    #[test]
827    fn test_missing_gate_returns_honest_error() {
828        let dc = DynamicCircuit::new(3).expect("3 qubits supported");
829        let result = dc.get_single_qubit_for_gate("RY", 0);
830        assert!(
831            result.is_err(),
832            "expected an honest error for a nonexistent gate"
833        );
834    }
835}