quantrs2_sim/
qulacs_backend.rs

1//! Qulacs-inspired high-performance quantum simulation backend
2//!
3//! This module implements a Qulacs-style quantum simulation backend using SciRS2
4//! for optimal performance. It features:
5//!
6//! - SIMD-optimized gate operations
7//! - Parallel state vector operations
8//! - Specialized implementations for common gate patterns
9//! - Memory-efficient state representation
10//!
11//! ## References
12//!
13//! - Qulacs: <https://github.com/qulacs/qulacs>
14//! - Original paper: "Qulacs: a fast and versatile quantum circuit simulator for research purpose"
15
16use crate::error::{Result, SimulatorError};
17
18// ✅ CORRECT - Unified SciRS2 usage (PROVEN PATTERNS)
19use scirs2_core::ndarray::*; // Complete unified access
20use scirs2_core::{Complex64, Float};
21
22/// Type alias for state vector index
23pub type StateIndex = usize;
24
25/// Type alias for qubit index
26pub type QubitIndex = usize;
27
28/// Qulacs-inspired quantum state vector
29///
30/// This structure provides a high-performance state vector implementation
31/// following Qulacs' design principles, adapted to use SciRS2's abstractions.
32#[derive(Clone)]
33pub struct QulacsStateVector {
34    /// The quantum state amplitudes
35    state: Array1<Complex64>,
36    /// Number of qubits
37    num_qubits: usize,
38    /// Dimension of the state vector (2^num_qubits)
39    dim: StateIndex,
40}
41
42impl QulacsStateVector {
43    /// Create a new state vector initialized to |0...0⟩
44    ///
45    /// # Arguments
46    ///
47    /// * `num_qubits` - Number of qubits
48    ///
49    /// # Returns
50    ///
51    /// A new quantum state vector
52    pub fn new(num_qubits: usize) -> Result<Self> {
53        if num_qubits == 0 {
54            return Err(SimulatorError::InvalidQubitCount(
55                "Number of qubits must be positive".to_string(),
56            ));
57        }
58
59        if num_qubits > 30 {
60            return Err(SimulatorError::InvalidQubitCount(format!(
61                "Number of qubits ({}) exceeds maximum (30)",
62                num_qubits
63            )));
64        }
65
66        let dim = 1 << num_qubits;
67        let mut state = Array1::<Complex64>::zeros(dim);
68        state[0] = Complex64::new(1.0, 0.0);
69
70        Ok(Self {
71            state,
72            num_qubits,
73            dim,
74        })
75    }
76
77    /// Create a state vector from raw amplitudes
78    ///
79    /// # Arguments
80    ///
81    /// * `amplitudes` - The state amplitudes
82    ///
83    /// # Returns
84    ///
85    /// A new quantum state vector
86    pub fn from_amplitudes(amplitudes: Array1<Complex64>) -> Result<Self> {
87        let dim = amplitudes.len();
88        if dim == 0 || (dim & (dim - 1)) != 0 {
89            return Err(SimulatorError::InvalidState(
90                "Dimension must be a power of 2".to_string(),
91            ));
92        }
93
94        let num_qubits = dim.trailing_zeros() as usize;
95
96        Ok(Self {
97            state: amplitudes,
98            num_qubits,
99            dim,
100        })
101    }
102
103    /// Get the number of qubits
104    #[inline]
105    pub fn num_qubits(&self) -> usize {
106        self.num_qubits
107    }
108
109    /// Get the dimension of the state vector
110    #[inline]
111    pub fn dim(&self) -> StateIndex {
112        self.dim
113    }
114
115    /// Get a reference to the state amplitudes
116    #[inline]
117    pub fn amplitudes(&self) -> &Array1<Complex64> {
118        &self.state
119    }
120
121    /// Get a mutable reference to the state amplitudes
122    #[inline]
123    pub fn amplitudes_mut(&mut self) -> &mut Array1<Complex64> {
124        &mut self.state
125    }
126
127    /// Calculate the squared norm of the state vector
128    ///
129    /// Uses efficient array operations (SciRS2 ndarray is already optimized)
130    pub fn norm_squared(&self) -> f64 {
131        // SciRS2's ndarray operations are already optimized with SIMD/parallelization
132        self.state.iter().map(|amp| amp.norm_sqr()).sum()
133    }
134
135    /// Normalize the state vector
136    ///
137    /// Uses SciRS2 ndarray operations (already optimized)
138    pub fn normalize(&mut self) -> Result<()> {
139        let norm = self.norm_squared().sqrt();
140        if norm < 1e-15 {
141            return Err(SimulatorError::InvalidState(
142                "Cannot normalize zero state".to_string(),
143            ));
144        }
145
146        let scale = 1.0 / norm;
147        // SciRS2's mapv_inplace is already optimized
148        self.state.mapv_inplace(|amp| amp * scale);
149        Ok(())
150    }
151
152    /// Calculate inner product with another state vector
153    ///
154    /// ⟨self|other⟩ using SciRS2 ndarray operations
155    pub fn inner_product(&self, other: &Self) -> Result<Complex64> {
156        if self.dim != other.dim {
157            return Err(SimulatorError::InvalidOperation(
158                "State vectors must have the same dimension".to_string(),
159            ));
160        }
161
162        // SciRS2's iterator operations are already optimized
163        let result: Complex64 = self
164            .state
165            .iter()
166            .zip(other.state.iter())
167            .map(|(a, b)| a.conj() * b)
168            .sum();
169
170        Ok(result)
171    }
172
173    /// Reset state to |0...0⟩
174    pub fn reset(&mut self) {
175        self.state.fill(Complex64::new(0.0, 0.0));
176        self.state[0] = Complex64::new(1.0, 0.0);
177    }
178
179    /// Calculate probability of measuring |1⟩ on a specific qubit
180    ///
181    /// This does not collapse the state
182    ///
183    /// # Arguments
184    ///
185    /// * `target` - Target qubit index
186    ///
187    /// # Returns
188    ///
189    /// Probability of measuring 1 (0.0 to 1.0)
190    pub fn probability_one(&self, target: QubitIndex) -> Result<f64> {
191        if target >= self.num_qubits {
192            return Err(SimulatorError::InvalidQubitIndex {
193                index: target,
194                num_qubits: self.num_qubits,
195            });
196        }
197
198        let mask = 1usize << target;
199        let mut prob_one = 0.0;
200
201        // Sum probabilities of all basis states where target qubit is 1
202        for i in 0..self.dim {
203            if (i & mask) != 0 {
204                prob_one += self.state[i].norm_sqr();
205            }
206        }
207
208        Ok(prob_one)
209    }
210
211    /// Calculate probability of measuring |0⟩ on a specific qubit
212    ///
213    /// This does not collapse the state
214    ///
215    /// # Arguments
216    ///
217    /// * `target` - Target qubit index
218    ///
219    /// # Returns
220    ///
221    /// Probability of measuring 0 (0.0 to 1.0)
222    pub fn probability_zero(&self, target: QubitIndex) -> Result<f64> {
223        Ok(1.0 - self.probability_one(target)?)
224    }
225
226    /// Measure a single qubit in the computational basis
227    ///
228    /// This performs a projective measurement and collapses the state
229    ///
230    /// # Arguments
231    ///
232    /// * `target` - Target qubit index
233    ///
234    /// # Returns
235    ///
236    /// Measurement outcome (false = 0, true = 1)
237    pub fn measure(&mut self, target: QubitIndex) -> Result<bool> {
238        if target >= self.num_qubits {
239            return Err(SimulatorError::InvalidQubitIndex {
240                index: target,
241                num_qubits: self.num_qubits,
242            });
243        }
244
245        // Calculate probability of measuring 1
246        let prob_one = self.probability_one(target)?;
247
248        // Generate random number for measurement
249        use scirs2_core::random::prelude::*;
250        let mut rng = thread_rng();
251        let random_value: f64 = rng.gen();
252
253        let outcome = random_value < prob_one;
254
255        // Collapse the state based on measurement outcome
256        self.collapse_to(target, outcome)?;
257
258        Ok(outcome)
259    }
260
261    /// Collapse the state to a specific measurement outcome
262    ///
263    /// # Arguments
264    ///
265    /// * `target` - Target qubit index
266    /// * `outcome` - Measurement outcome (false = 0, true = 1)
267    fn collapse_to(&mut self, target: QubitIndex, outcome: bool) -> Result<()> {
268        let mask = 1usize << target;
269        let mut norm_sqr = 0.0;
270
271        // Zero out amplitudes inconsistent with measurement outcome
272        // and calculate normalization factor
273        for i in 0..self.dim {
274            let qubit_value = (i & mask) != 0;
275            if qubit_value != outcome {
276                self.state[i] = Complex64::new(0.0, 0.0);
277            } else {
278                norm_sqr += self.state[i].norm_sqr();
279            }
280        }
281
282        // Normalize the remaining amplitudes
283        if norm_sqr < 1e-15 {
284            return Err(SimulatorError::InvalidState(
285                "Cannot collapse to zero-probability outcome".to_string(),
286            ));
287        }
288
289        let norm = norm_sqr.sqrt();
290        let scale = 1.0 / norm;
291        for i in 0..self.dim {
292            if ((i & mask) != 0) == outcome {
293                self.state[i] *= scale;
294            }
295        }
296
297        Ok(())
298    }
299
300    /// Sample measurement outcomes without collapsing the state
301    ///
302    /// # Arguments
303    ///
304    /// * `shots` - Number of measurement samples
305    ///
306    /// # Returns
307    ///
308    /// Vector of measurement outcomes (bit strings)
309    pub fn sample(&self, shots: usize) -> Result<Vec<Vec<bool>>> {
310        if shots == 0 {
311            return Ok(Vec::new());
312        }
313
314        use scirs2_core::random::prelude::*;
315        let mut rng = thread_rng();
316        let mut results = Vec::with_capacity(shots);
317
318        // Pre-calculate cumulative probabilities
319        let mut cumulative_probs = Vec::with_capacity(self.dim);
320        let mut cumsum = 0.0;
321        for i in 0..self.dim {
322            cumsum += self.state[i].norm_sqr();
323            cumulative_probs.push(cumsum);
324        }
325
326        // Sample shots times
327        for _ in 0..shots {
328            let random_value: f64 = rng.gen();
329
330            // Binary search for the outcome
331            let outcome_index = cumulative_probs
332                .binary_search_by(|&prob| {
333                    if prob < random_value {
334                        std::cmp::Ordering::Less
335                    } else {
336                        std::cmp::Ordering::Greater
337                    }
338                })
339                .unwrap_or_else(|x| x);
340
341            // Convert index to bit string
342            let mut bitstring = Vec::with_capacity(self.num_qubits);
343            for q in 0..self.num_qubits {
344                bitstring.push((outcome_index & (1 << q)) != 0);
345            }
346            results.push(bitstring);
347        }
348
349        Ok(results)
350    }
351
352    /// Get measurement counts (histogram) without collapsing the state
353    ///
354    /// # Arguments
355    ///
356    /// * `shots` - Number of measurement samples
357    ///
358    /// # Returns
359    ///
360    /// HashMap mapping bit strings to counts
361    pub fn get_counts(&self, shots: usize) -> Result<std::collections::HashMap<Vec<bool>, usize>> {
362        use std::collections::HashMap;
363
364        let samples = self.sample(shots)?;
365        let mut counts = HashMap::new();
366
367        for bitstring in samples {
368            *counts.entry(bitstring).or_insert(0) += 1;
369        }
370
371        Ok(counts)
372    }
373
374    /// Sample measurements of specific qubits
375    ///
376    /// # Arguments
377    ///
378    /// * `qubits` - Qubit indices to measure
379    /// * `shots` - Number of measurement samples
380    ///
381    /// # Returns
382    ///
383    /// Vector of partial measurement outcomes
384    pub fn sample_qubits(&self, qubits: &[QubitIndex], shots: usize) -> Result<Vec<Vec<bool>>> {
385        // Validate qubit indices
386        for &q in qubits {
387            if q >= self.num_qubits {
388                return Err(SimulatorError::InvalidQubitIndex {
389                    index: q,
390                    num_qubits: self.num_qubits,
391                });
392            }
393        }
394
395        // Sample full state
396        let full_samples = self.sample(shots)?;
397
398        // Extract only the specified qubits
399        let results: Vec<Vec<bool>> = full_samples
400            .into_iter()
401            .map(|bitstring| qubits.iter().map(|&q| bitstring[q]).collect())
402            .collect();
403
404        Ok(results)
405    }
406}
407
408/// Qulacs-style gate operations
409pub mod gates {
410    use super::*;
411
412    /// Apply Hadamard gate to a target qubit
413    ///
414    /// This implementation follows Qulacs' approach with:
415    /// - Bit masking for efficient index calculation
416    /// - Special handling for qubit 0
417    /// - SciRS2 parallel execution and SIMD when available
418    ///
419    /// # Arguments
420    ///
421    /// * `state` - The quantum state vector
422    /// * `target` - Target qubit index
423    pub fn hadamard(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
424        if target >= state.num_qubits {
425            return Err(SimulatorError::InvalidQubitIndex {
426                index: target,
427                num_qubits: state.num_qubits,
428            });
429        }
430
431        let dim = state.dim;
432        let loop_dim = dim / 2;
433        let mask = 1usize << target;
434        let sqrt2_inv = Complex64::new(1.0 / 2.0f64.sqrt(), 0.0);
435
436        let state_data = state.amplitudes_mut();
437
438        if target == 0 {
439            // Optimized path for qubit 0 - adjacent pairs
440            // Process pairs sequentially (SciRS2 will optimize internally)
441            for basis_idx in (0..dim).step_by(2) {
442                let temp0 = state_data[basis_idx];
443                let temp1 = state_data[basis_idx + 1];
444                state_data[basis_idx] = (temp0 + temp1) * sqrt2_inv;
445                state_data[basis_idx + 1] = (temp0 - temp1) * sqrt2_inv;
446            }
447        } else {
448            // General case with bit masking
449            // SciRS2's ndarray operations are already optimized
450            let mask_low = mask - 1;
451            let mask_high = !mask_low;
452
453            for state_idx in (0..loop_dim).step_by(2) {
454                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
455                let basis_idx_1 = basis_idx_0 | mask;
456
457                let temp_a0 = state_data[basis_idx_0];
458                let temp_a1 = state_data[basis_idx_1];
459                let temp_b0 = state_data[basis_idx_0 + 1];
460                let temp_b1 = state_data[basis_idx_1 + 1];
461
462                state_data[basis_idx_0] = (temp_a0 + temp_a1) * sqrt2_inv;
463                state_data[basis_idx_1] = (temp_a0 - temp_a1) * sqrt2_inv;
464                state_data[basis_idx_0 + 1] = (temp_b0 + temp_b1) * sqrt2_inv;
465                state_data[basis_idx_1 + 1] = (temp_b0 - temp_b1) * sqrt2_inv;
466            }
467        }
468
469        Ok(())
470    }
471
472    /// Apply Pauli-X gate to a target qubit
473    ///
474    /// # Arguments
475    ///
476    /// * `state` - The quantum state vector
477    /// * `target` - Target qubit index
478    pub fn pauli_x(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
479        if target >= state.num_qubits {
480            return Err(SimulatorError::InvalidQubitIndex {
481                index: target,
482                num_qubits: state.num_qubits,
483            });
484        }
485
486        let dim = state.dim;
487        let loop_dim = dim / 2;
488        let mask = 1usize << target;
489        let mask_low = mask - 1;
490        let mask_high = !mask_low;
491
492        let state_data = state.amplitudes_mut();
493
494        if target == 0 {
495            // Optimized path for qubit 0
496            for basis_idx in (0..dim).step_by(2) {
497                state_data.swap(basis_idx, basis_idx + 1);
498            }
499        } else {
500            for state_idx in (0..loop_dim).step_by(2) {
501                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
502                let basis_idx_1 = basis_idx_0 | mask;
503
504                state_data.swap(basis_idx_0, basis_idx_1);
505                state_data.swap(basis_idx_0 + 1, basis_idx_1 + 1);
506            }
507        }
508
509        Ok(())
510    }
511
512    /// Apply Pauli-Y gate to a target qubit
513    ///
514    /// # Arguments
515    ///
516    /// * `state` - The quantum state vector
517    /// * `target` - Target qubit index
518    pub fn pauli_y(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
519        if target >= state.num_qubits {
520            return Err(SimulatorError::InvalidQubitIndex {
521                index: target,
522                num_qubits: state.num_qubits,
523            });
524        }
525
526        let dim = state.dim;
527        let loop_dim = dim / 2;
528        let mask = 1usize << target;
529        let mask_low = mask - 1;
530        let mask_high = !mask_low;
531
532        let state_data = state.amplitudes_mut();
533        let i = Complex64::new(0.0, 1.0);
534
535        if target == 0 {
536            for basis_idx in (0..dim).step_by(2) {
537                let temp0 = state_data[basis_idx];
538                let temp1 = state_data[basis_idx + 1];
539                state_data[basis_idx] = -i * temp1;
540                state_data[basis_idx + 1] = i * temp0;
541            }
542        } else {
543            for state_idx in (0..loop_dim).step_by(2) {
544                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
545                let basis_idx_1 = basis_idx_0 | mask;
546
547                let temp_a0 = state_data[basis_idx_0];
548                let temp_a1 = state_data[basis_idx_1];
549                let temp_b0 = state_data[basis_idx_0 + 1];
550                let temp_b1 = state_data[basis_idx_1 + 1];
551
552                state_data[basis_idx_0] = -i * temp_a1;
553                state_data[basis_idx_1] = i * temp_a0;
554                state_data[basis_idx_0 + 1] = -i * temp_b1;
555                state_data[basis_idx_1 + 1] = i * temp_b0;
556            }
557        }
558
559        Ok(())
560    }
561
562    /// Apply Pauli-Z gate to a target qubit
563    ///
564    /// # Arguments
565    ///
566    /// * `state` - The quantum state vector
567    /// * `target` - Target qubit index
568    pub fn pauli_z(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
569        if target >= state.num_qubits {
570            return Err(SimulatorError::InvalidQubitIndex {
571                index: target,
572                num_qubits: state.num_qubits,
573            });
574        }
575
576        let dim = state.dim;
577        let loop_dim = dim / 2;
578        let mask = 1usize << target;
579        let mask_low = mask - 1;
580        let mask_high = !mask_low;
581
582        let state_data = state.amplitudes_mut();
583
584        for state_idx in 0..loop_dim {
585            let basis_idx = (state_idx & mask_low) | ((state_idx & mask_high) << 1) | mask;
586            state_data[basis_idx] = -state_data[basis_idx];
587        }
588
589        Ok(())
590    }
591
592    /// Apply CNOT gate (controlled-X)
593    ///
594    /// This follows Qulacs' approach with specialized handling based on
595    /// control and target qubit positions.
596    ///
597    /// # Arguments
598    ///
599    /// * `state` - The quantum state vector
600    /// * `control` - Control qubit index
601    /// * `target` - Target qubit index
602    pub fn cnot(
603        state: &mut QulacsStateVector,
604        control: QubitIndex,
605        target: QubitIndex,
606    ) -> Result<()> {
607        if control >= state.num_qubits {
608            return Err(SimulatorError::InvalidQubitIndex {
609                index: control,
610                num_qubits: state.num_qubits,
611            });
612        }
613        if target >= state.num_qubits {
614            return Err(SimulatorError::InvalidQubitIndex {
615                index: target,
616                num_qubits: state.num_qubits,
617            });
618        }
619        if control == target {
620            return Err(SimulatorError::InvalidOperation(
621                "Control and target qubits must be different".to_string(),
622            ));
623        }
624
625        let dim = state.dim;
626        let loop_dim = dim / 4;
627        let target_mask = 1usize << target;
628        let control_mask = 1usize << control;
629
630        let min_qubit = control.min(target);
631        let max_qubit = control.max(target);
632        let min_qubit_mask = 1usize << min_qubit;
633        let max_qubit_mask = 1usize << (max_qubit - 1);
634        let low_mask = min_qubit_mask - 1;
635        let mid_mask = (max_qubit_mask - 1) ^ low_mask;
636        let high_mask = !max_qubit_mask.wrapping_add(max_qubit_mask - 1);
637
638        let state_data = state.amplitudes_mut();
639
640        if target == 0 {
641            // Target is qubit 0 - swap adjacent pairs
642            for state_idx in 0..loop_dim {
643                let basis_idx =
644                    ((state_idx & mid_mask) << 1) | ((state_idx & high_mask) << 2) | control_mask;
645                state_data.swap(basis_idx, basis_idx + 1);
646            }
647        } else if control == 0 {
648            // Control is qubit 0
649            for state_idx in 0..loop_dim {
650                let basis_idx_0 = (state_idx & low_mask)
651                    | ((state_idx & mid_mask) << 1)
652                    | ((state_idx & high_mask) << 2)
653                    | control_mask;
654                let basis_idx_1 = basis_idx_0 | target_mask;
655                state_data.swap(basis_idx_0, basis_idx_1);
656            }
657        } else {
658            // General case - process pairs
659            for state_idx in (0..loop_dim).step_by(2) {
660                let basis_idx_0 = (state_idx & low_mask)
661                    | ((state_idx & mid_mask) << 1)
662                    | ((state_idx & high_mask) << 2)
663                    | control_mask;
664                let basis_idx_1 = basis_idx_0 | target_mask;
665
666                state_data.swap(basis_idx_0, basis_idx_1);
667                state_data.swap(basis_idx_0 + 1, basis_idx_1 + 1);
668            }
669        }
670
671        Ok(())
672    }
673
674    /// Apply CZ gate (controlled-Z)
675    ///
676    /// # Arguments
677    ///
678    /// * `state` - The quantum state vector
679    /// * `control` - Control qubit index
680    /// * `target` - Target qubit index
681    pub fn cz(
682        state: &mut QulacsStateVector,
683        control: QubitIndex,
684        target: QubitIndex,
685    ) -> Result<()> {
686        if control >= state.num_qubits {
687            return Err(SimulatorError::InvalidQubitIndex {
688                index: control,
689                num_qubits: state.num_qubits,
690            });
691        }
692        if target >= state.num_qubits {
693            return Err(SimulatorError::InvalidQubitIndex {
694                index: target,
695                num_qubits: state.num_qubits,
696            });
697        }
698        if control == target {
699            return Err(SimulatorError::InvalidOperation(
700                "Control and target qubits must be different".to_string(),
701            ));
702        }
703
704        let dim = state.dim;
705        let loop_dim = dim / 4;
706        let target_mask = 1usize << target;
707        let control_mask = 1usize << control;
708
709        let min_qubit = control.min(target);
710        let max_qubit = control.max(target);
711        let min_qubit_mask = 1usize << min_qubit;
712        let max_qubit_mask = 1usize << (max_qubit - 1);
713        let low_mask = min_qubit_mask - 1;
714        let mid_mask = (max_qubit_mask - 1) ^ low_mask;
715        let high_mask = !max_qubit_mask.wrapping_add(max_qubit_mask - 1);
716
717        let state_data = state.amplitudes_mut();
718
719        for state_idx in 0..loop_dim {
720            let basis_idx = (state_idx & low_mask)
721                | ((state_idx & mid_mask) << 1)
722                | ((state_idx & high_mask) << 2)
723                | control_mask
724                | target_mask;
725            state_data[basis_idx] = -state_data[basis_idx];
726        }
727
728        Ok(())
729    }
730
731    /// Apply SWAP gate
732    ///
733    /// # Arguments
734    ///
735    /// * `state` - The quantum state vector
736    /// * `qubit1` - First qubit index
737    /// * `qubit2` - Second qubit index
738    pub fn swap(
739        state: &mut QulacsStateVector,
740        qubit1: QubitIndex,
741        qubit2: QubitIndex,
742    ) -> Result<()> {
743        if qubit1 >= state.num_qubits {
744            return Err(SimulatorError::InvalidQubitIndex {
745                index: qubit1,
746                num_qubits: state.num_qubits,
747            });
748        }
749        if qubit2 >= state.num_qubits {
750            return Err(SimulatorError::InvalidQubitIndex {
751                index: qubit2,
752                num_qubits: state.num_qubits,
753            });
754        }
755        if qubit1 == qubit2 {
756            return Ok(()); // No-op
757        }
758
759        let dim = state.dim;
760        let loop_dim = dim / 4;
761        let mask1 = 1usize << qubit1;
762        let mask2 = 1usize << qubit2;
763
764        let min_qubit = qubit1.min(qubit2);
765        let max_qubit = qubit1.max(qubit2);
766        let min_qubit_mask = 1usize << min_qubit;
767        let max_qubit_mask = 1usize << (max_qubit - 1);
768        let low_mask = min_qubit_mask - 1;
769        let mid_mask = (max_qubit_mask - 1) ^ low_mask;
770        let high_mask = !max_qubit_mask.wrapping_add(max_qubit_mask - 1);
771
772        let state_data = state.amplitudes_mut();
773
774        for state_idx in 0..loop_dim {
775            let basis_idx_0 = (state_idx & low_mask)
776                | ((state_idx & mid_mask) << 1)
777                | ((state_idx & high_mask) << 2);
778            let basis_idx_1 = basis_idx_0 | mask1;
779            let basis_idx_2 = basis_idx_0 | mask2;
780
781            state_data.swap(basis_idx_1, basis_idx_2);
782        }
783
784        Ok(())
785    }
786
787    /// Apply RX gate (rotation around X-axis)
788    ///
789    /// RX(θ) = exp(-iθX/2) = cos(θ/2)I - i sin(θ/2)X
790    ///
791    /// Matrix representation:
792    /// ```text
793    /// [cos(θ/2)    -i sin(θ/2)]
794    /// [-i sin(θ/2)  cos(θ/2)  ]
795    /// ```
796    ///
797    /// # Arguments
798    ///
799    /// * `state` - The quantum state vector
800    /// * `target` - Target qubit index
801    /// * `angle` - Rotation angle in radians
802    pub fn rx(state: &mut QulacsStateVector, target: QubitIndex, angle: f64) -> Result<()> {
803        if target >= state.num_qubits {
804            return Err(SimulatorError::InvalidQubitIndex {
805                index: target,
806                num_qubits: state.num_qubits,
807            });
808        }
809
810        let dim = state.dim;
811        let loop_dim = dim / 2;
812        let mask = 1usize << target;
813        let mask_low = mask - 1;
814        let mask_high = !mask_low;
815
816        let cos_half = (angle / 2.0).cos();
817        let sin_half = (angle / 2.0).sin();
818        let i_sin_half = Complex64::new(0.0, -sin_half);
819
820        let state_data = state.amplitudes_mut();
821
822        if target == 0 {
823            // Optimized path for qubit 0
824            for basis_idx in (0..dim).step_by(2) {
825                let amp0 = state_data[basis_idx];
826                let amp1 = state_data[basis_idx + 1];
827
828                state_data[basis_idx] = amp0 * cos_half + amp1 * i_sin_half;
829                state_data[basis_idx + 1] = amp0 * i_sin_half + amp1 * cos_half;
830            }
831        } else {
832            // General case
833            for state_idx in (0..loop_dim).step_by(2) {
834                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
835                let basis_idx_1 = basis_idx_0 | mask;
836
837                // Process two consecutive indices
838                let amp_a0 = state_data[basis_idx_0];
839                let amp_a1 = state_data[basis_idx_1];
840                let amp_b0 = state_data[basis_idx_0 + 1];
841                let amp_b1 = state_data[basis_idx_1 + 1];
842
843                state_data[basis_idx_0] = amp_a0 * cos_half + amp_a1 * i_sin_half;
844                state_data[basis_idx_1] = amp_a0 * i_sin_half + amp_a1 * cos_half;
845                state_data[basis_idx_0 + 1] = amp_b0 * cos_half + amp_b1 * i_sin_half;
846                state_data[basis_idx_1 + 1] = amp_b0 * i_sin_half + amp_b1 * cos_half;
847            }
848        }
849
850        Ok(())
851    }
852
853    /// Apply RY gate (rotation around Y-axis)
854    ///
855    /// RY(θ) = exp(-iθY/2) = cos(θ/2)I - i sin(θ/2)Y
856    ///
857    /// Matrix representation:
858    /// ```text
859    /// [cos(θ/2)  -sin(θ/2)]
860    /// [sin(θ/2)   cos(θ/2)]
861    /// ```
862    ///
863    /// # Arguments
864    ///
865    /// * `state` - The quantum state vector
866    /// * `target` - Target qubit index
867    /// * `angle` - Rotation angle in radians
868    pub fn ry(state: &mut QulacsStateVector, target: QubitIndex, angle: f64) -> Result<()> {
869        if target >= state.num_qubits {
870            return Err(SimulatorError::InvalidQubitIndex {
871                index: target,
872                num_qubits: state.num_qubits,
873            });
874        }
875
876        let dim = state.dim;
877        let loop_dim = dim / 2;
878        let mask = 1usize << target;
879        let mask_low = mask - 1;
880        let mask_high = !mask_low;
881
882        let cos_half = (angle / 2.0).cos();
883        let sin_half = (angle / 2.0).sin();
884
885        let state_data = state.amplitudes_mut();
886
887        if target == 0 {
888            // Optimized path for qubit 0
889            for basis_idx in (0..dim).step_by(2) {
890                let amp0 = state_data[basis_idx];
891                let amp1 = state_data[basis_idx + 1];
892
893                state_data[basis_idx] = amp0 * cos_half - amp1 * sin_half;
894                state_data[basis_idx + 1] = amp0 * sin_half + amp1 * cos_half;
895            }
896        } else {
897            // General case
898            for state_idx in (0..loop_dim).step_by(2) {
899                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
900                let basis_idx_1 = basis_idx_0 | mask;
901
902                let amp_a0 = state_data[basis_idx_0];
903                let amp_a1 = state_data[basis_idx_1];
904                let amp_b0 = state_data[basis_idx_0 + 1];
905                let amp_b1 = state_data[basis_idx_1 + 1];
906
907                state_data[basis_idx_0] = amp_a0 * cos_half - amp_a1 * sin_half;
908                state_data[basis_idx_1] = amp_a0 * sin_half + amp_a1 * cos_half;
909                state_data[basis_idx_0 + 1] = amp_b0 * cos_half - amp_b1 * sin_half;
910                state_data[basis_idx_1 + 1] = amp_b0 * sin_half + amp_b1 * cos_half;
911            }
912        }
913
914        Ok(())
915    }
916
917    /// Apply RZ gate (rotation around Z-axis)
918    ///
919    /// RZ(θ) = exp(-iθZ/2)
920    ///
921    /// Matrix representation:
922    /// ```text
923    /// [e^(-iθ/2)     0      ]
924    /// [   0       e^(iθ/2)  ]
925    /// ```
926    ///
927    /// # Arguments
928    ///
929    /// * `state` - The quantum state vector
930    /// * `target` - Target qubit index
931    /// * `angle` - Rotation angle in radians
932    pub fn rz(state: &mut QulacsStateVector, target: QubitIndex, angle: f64) -> Result<()> {
933        if target >= state.num_qubits {
934            return Err(SimulatorError::InvalidQubitIndex {
935                index: target,
936                num_qubits: state.num_qubits,
937            });
938        }
939
940        let dim = state.dim;
941        let loop_dim = dim / 2;
942        let mask = 1usize << target;
943        let mask_low = mask - 1;
944        let mask_high = !mask_low;
945
946        let phase_0 = Complex64::from_polar(1.0, -angle / 2.0);
947        let phase_1 = Complex64::from_polar(1.0, angle / 2.0);
948
949        let state_data = state.amplitudes_mut();
950
951        for state_idx in 0..loop_dim {
952            let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
953            let basis_idx_1 = basis_idx_0 | mask;
954
955            state_data[basis_idx_0] *= phase_0;
956            state_data[basis_idx_1] *= phase_1;
957        }
958
959        Ok(())
960    }
961
962    /// Apply Phase gate (arbitrary phase rotation)
963    ///
964    /// Phase(θ) = diag(1, e^(iθ))
965    ///
966    /// # Arguments
967    ///
968    /// * `state` - The quantum state vector
969    /// * `target` - Target qubit index
970    /// * `angle` - Phase angle in radians
971    pub fn phase(state: &mut QulacsStateVector, target: QubitIndex, angle: f64) -> Result<()> {
972        if target >= state.num_qubits {
973            return Err(SimulatorError::InvalidQubitIndex {
974                index: target,
975                num_qubits: state.num_qubits,
976            });
977        }
978
979        let dim = state.dim;
980        let loop_dim = dim / 2;
981        let mask = 1usize << target;
982        let mask_low = mask - 1;
983        let mask_high = !mask_low;
984
985        let phase_factor = Complex64::from_polar(1.0, angle);
986
987        let state_data = state.amplitudes_mut();
988
989        for state_idx in 0..loop_dim {
990            let basis_idx = (state_idx & mask_low) | ((state_idx & mask_high) << 1) | mask;
991            state_data[basis_idx] *= phase_factor;
992        }
993
994        Ok(())
995    }
996
997    /// Apply S gate (phase gate with π/2)
998    ///
999    /// S gate applies a π/2 phase: |0⟩ → |0⟩, |1⟩ → i|1⟩
1000    ///
1001    /// # Arguments
1002    ///
1003    /// * `state` - The quantum state vector
1004    /// * `target` - Target qubit index
1005    pub fn s(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
1006        phase(state, target, std::f64::consts::FRAC_PI_2)
1007    }
1008
1009    /// Apply S† gate (conjugate of S gate, phase -π/2)
1010    ///
1011    /// S† gate applies a -π/2 phase: |0⟩ → |0⟩, |1⟩ → -i|1⟩
1012    ///
1013    /// # Arguments
1014    ///
1015    /// * `state` - The quantum state vector
1016    /// * `target` - Target qubit index
1017    pub fn sdg(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
1018        phase(state, target, -std::f64::consts::FRAC_PI_2)
1019    }
1020
1021    /// Apply T gate (phase gate with π/4)
1022    ///
1023    /// T gate applies a π/4 phase: |0⟩ → |0⟩, |1⟩ → e^(iπ/4)|1⟩
1024    ///
1025    /// # Arguments
1026    ///
1027    /// * `state` - The quantum state vector
1028    /// * `target` - Target qubit index
1029    pub fn t(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
1030        phase(state, target, std::f64::consts::FRAC_PI_4)
1031    }
1032
1033    /// Apply T† gate (conjugate of T gate, phase -π/4)
1034    ///
1035    /// T† gate applies a -π/4 phase: |0⟩ → |0⟩, |1⟩ → e^(-iπ/4)|1⟩
1036    ///
1037    /// # Arguments
1038    ///
1039    /// * `state` - The quantum state vector
1040    /// * `target` - Target qubit index
1041    pub fn tdg(state: &mut QulacsStateVector, target: QubitIndex) -> Result<()> {
1042        phase(state, target, -std::f64::consts::FRAC_PI_4)
1043    }
1044
1045    /// Apply U3 gate (universal single-qubit gate)
1046    ///
1047    /// U3(θ, φ, λ) is the most general single-qubit unitary gate
1048    ///
1049    /// Matrix representation:
1050    /// ```text
1051    /// [cos(θ/2)              -e^(iλ) sin(θ/2)        ]
1052    /// [e^(iφ) sin(θ/2)       e^(i(φ+λ)) cos(θ/2)     ]
1053    /// ```
1054    ///
1055    /// # Arguments
1056    ///
1057    /// * `state` - The quantum state vector
1058    /// * `target` - Target qubit index
1059    /// * `theta` - Rotation angle θ
1060    /// * `phi` - Phase angle φ
1061    /// * `lambda` - Phase angle λ
1062    pub fn u3(
1063        state: &mut QulacsStateVector,
1064        target: QubitIndex,
1065        theta: f64,
1066        phi: f64,
1067        lambda: f64,
1068    ) -> Result<()> {
1069        if target >= state.num_qubits {
1070            return Err(SimulatorError::InvalidQubitIndex {
1071                index: target,
1072                num_qubits: state.num_qubits,
1073            });
1074        }
1075
1076        let dim = state.dim;
1077        let loop_dim = dim / 2;
1078        let mask = 1usize << target;
1079        let mask_low = mask - 1;
1080        let mask_high = !mask_low;
1081
1082        let cos_half = (theta / 2.0).cos();
1083        let sin_half = (theta / 2.0).sin();
1084
1085        // Matrix elements
1086        let u00 = Complex64::new(cos_half, 0.0);
1087        let u01 = -Complex64::from_polar(sin_half, lambda);
1088        let u10 = Complex64::from_polar(sin_half, phi);
1089        let u11 = Complex64::from_polar(cos_half, phi + lambda);
1090
1091        let state_data = state.amplitudes_mut();
1092
1093        if target == 0 {
1094            // Optimized path for qubit 0
1095            for basis_idx in (0..dim).step_by(2) {
1096                let amp0 = state_data[basis_idx];
1097                let amp1 = state_data[basis_idx + 1];
1098
1099                state_data[basis_idx] = u00 * amp0 + u01 * amp1;
1100                state_data[basis_idx + 1] = u10 * amp0 + u11 * amp1;
1101            }
1102        } else {
1103            // General case
1104            for state_idx in (0..loop_dim).step_by(2) {
1105                let basis_idx_0 = (state_idx & mask_low) | ((state_idx & mask_high) << 1);
1106                let basis_idx_1 = basis_idx_0 | mask;
1107
1108                let amp_a0 = state_data[basis_idx_0];
1109                let amp_a1 = state_data[basis_idx_1];
1110                let amp_b0 = state_data[basis_idx_0 + 1];
1111                let amp_b1 = state_data[basis_idx_1 + 1];
1112
1113                state_data[basis_idx_0] = u00 * amp_a0 + u01 * amp_a1;
1114                state_data[basis_idx_1] = u10 * amp_a0 + u11 * amp_a1;
1115                state_data[basis_idx_0 + 1] = u00 * amp_b0 + u01 * amp_b1;
1116                state_data[basis_idx_1 + 1] = u10 * amp_b0 + u11 * amp_b1;
1117            }
1118        }
1119
1120        Ok(())
1121    }
1122
1123    /// Apply Toffoli (CCX) gate - Controlled-Controlled-NOT
1124    ///
1125    /// Flips the target qubit if both control qubits are in state |1⟩
1126    ///
1127    /// # Arguments
1128    ///
1129    /// * `state` - The quantum state vector
1130    /// * `control1` - First control qubit index
1131    /// * `control2` - Second control qubit index
1132    /// * `target` - Target qubit index
1133    pub fn toffoli(
1134        state: &mut QulacsStateVector,
1135        control1: QubitIndex,
1136        control2: QubitIndex,
1137        target: QubitIndex,
1138    ) -> Result<()> {
1139        if control1 >= state.num_qubits {
1140            return Err(SimulatorError::InvalidQubitIndex {
1141                index: control1,
1142                num_qubits: state.num_qubits,
1143            });
1144        }
1145        if control2 >= state.num_qubits {
1146            return Err(SimulatorError::InvalidQubitIndex {
1147                index: control2,
1148                num_qubits: state.num_qubits,
1149            });
1150        }
1151        if target >= state.num_qubits {
1152            return Err(SimulatorError::InvalidQubitIndex {
1153                index: target,
1154                num_qubits: state.num_qubits,
1155            });
1156        }
1157        if control1 == control2 || control1 == target || control2 == target {
1158            return Err(SimulatorError::InvalidOperation(
1159                "Control and target qubits must be different".to_string(),
1160            ));
1161        }
1162
1163        let dim = state.dim;
1164        let loop_dim = dim / 8;
1165        let num_qubits = state.num_qubits;
1166        let control1_mask = 1usize << control1;
1167        let control2_mask = 1usize << control2;
1168        let target_mask = 1usize << target;
1169
1170        let state_data = state.amplitudes_mut();
1171
1172        // Only apply X to target when both control1 and control2 are |1⟩
1173        for i in 0..loop_dim {
1174            // Construct basis index with both controls set to 1
1175            let mut basis_idx = 0;
1176            let mut temp = i;
1177
1178            // Build index skipping the three qubit positions
1179            for bit_pos in 0..num_qubits {
1180                if bit_pos != control1 && bit_pos != control2 && bit_pos != target {
1181                    basis_idx |= (temp & 1) << bit_pos;
1182                    temp >>= 1;
1183                }
1184            }
1185
1186            // Set both control bits to 1
1187            basis_idx |= control1_mask | control2_mask;
1188
1189            // Swap amplitudes for target qubit states
1190            let idx_0 = basis_idx & !target_mask; // target = 0
1191            let idx_1 = basis_idx | target_mask; // target = 1
1192
1193            state_data.swap(idx_0, idx_1);
1194        }
1195
1196        Ok(())
1197    }
1198
1199    /// Apply Fredkin (CSWAP) gate - Controlled-SWAP
1200    ///
1201    /// Swaps target1 and target2 if control qubit is in state |1⟩
1202    ///
1203    /// # Arguments
1204    ///
1205    /// * `state` - The quantum state vector
1206    /// * `control` - Control qubit index
1207    /// * `target1` - First target qubit index
1208    /// * `target2` - Second target qubit index
1209    pub fn fredkin(
1210        state: &mut QulacsStateVector,
1211        control: QubitIndex,
1212        target1: QubitIndex,
1213        target2: QubitIndex,
1214    ) -> Result<()> {
1215        if control >= state.num_qubits {
1216            return Err(SimulatorError::InvalidQubitIndex {
1217                index: control,
1218                num_qubits: state.num_qubits,
1219            });
1220        }
1221        if target1 >= state.num_qubits {
1222            return Err(SimulatorError::InvalidQubitIndex {
1223                index: target1,
1224                num_qubits: state.num_qubits,
1225            });
1226        }
1227        if target2 >= state.num_qubits {
1228            return Err(SimulatorError::InvalidQubitIndex {
1229                index: target2,
1230                num_qubits: state.num_qubits,
1231            });
1232        }
1233        if control == target1 || control == target2 || target1 == target2 {
1234            return Err(SimulatorError::InvalidOperation(
1235                "Control and target qubits must be different".to_string(),
1236            ));
1237        }
1238
1239        let dim = state.dim;
1240        let loop_dim = dim / 8;
1241        let num_qubits = state.num_qubits;
1242        let control_mask = 1usize << control;
1243        let target1_mask = 1usize << target1;
1244        let target2_mask = 1usize << target2;
1245
1246        let state_data = state.amplitudes_mut();
1247
1248        // Only swap target1 and target2 when control is |1⟩
1249        for i in 0..loop_dim {
1250            // Construct basis index with control set to 1
1251            let mut basis_idx = 0;
1252            let mut temp = i;
1253
1254            // Build index skipping the three qubit positions
1255            for bit_pos in 0..num_qubits {
1256                if bit_pos != control && bit_pos != target1 && bit_pos != target2 {
1257                    basis_idx |= (temp & 1) << bit_pos;
1258                    temp >>= 1;
1259                }
1260            }
1261
1262            // Set control bit to 1
1263            basis_idx |= control_mask;
1264
1265            // Swap when target1=0,target2=1 with target1=1,target2=0
1266            let idx_01 = basis_idx | target2_mask; // target1=0, target2=1
1267            let idx_10 = basis_idx | target1_mask; // target1=1, target2=0
1268
1269            state_data.swap(idx_01, idx_10);
1270        }
1271
1272        Ok(())
1273    }
1274}
1275
1276/// Observable framework for Qulacs
1277///
1278/// Provides rich observable abstractions for expectation value computations
1279pub mod observable {
1280    use super::*;
1281    use scirs2_core::ndarray::Array2;
1282    use scirs2_core::Complex64;
1283    use std::collections::HashMap;
1284
1285    /// Pauli operator type
1286    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1287    pub enum PauliOperator {
1288        /// Identity operator
1289        I,
1290        /// Pauli X operator
1291        X,
1292        /// Pauli Y operator
1293        Y,
1294        /// Pauli Z operator
1295        Z,
1296    }
1297
1298    impl PauliOperator {
1299        /// Get the matrix representation of this Pauli operator
1300        pub fn matrix(&self) -> Array2<Complex64> {
1301            match self {
1302                PauliOperator::I => {
1303                    let mut mat = Array2::zeros((2, 2));
1304                    mat[[0, 0]] = Complex64::new(1.0, 0.0);
1305                    mat[[1, 1]] = Complex64::new(1.0, 0.0);
1306                    mat
1307                }
1308                PauliOperator::X => {
1309                    let mut mat = Array2::zeros((2, 2));
1310                    mat[[0, 1]] = Complex64::new(1.0, 0.0);
1311                    mat[[1, 0]] = Complex64::new(1.0, 0.0);
1312                    mat
1313                }
1314                PauliOperator::Y => {
1315                    let mut mat = Array2::zeros((2, 2));
1316                    mat[[0, 1]] = Complex64::new(0.0, -1.0);
1317                    mat[[1, 0]] = Complex64::new(0.0, 1.0);
1318                    mat
1319                }
1320                PauliOperator::Z => {
1321                    let mut mat = Array2::zeros((2, 2));
1322                    mat[[0, 0]] = Complex64::new(1.0, 0.0);
1323                    mat[[1, 1]] = Complex64::new(-1.0, 0.0);
1324                    mat
1325                }
1326            }
1327        }
1328
1329        /// Get the eigenvalue for computational basis state |b⟩
1330        pub fn eigenvalue(&self, basis_state: bool) -> f64 {
1331            match self {
1332                PauliOperator::I => 1.0,
1333                PauliOperator::X => 0.0, // X doesn't have computational basis eigenstates
1334                PauliOperator::Y => 0.0, // Y doesn't have computational basis eigenstates
1335                PauliOperator::Z => {
1336                    if basis_state {
1337                        -1.0
1338                    } else {
1339                        1.0
1340                    }
1341                }
1342            }
1343        }
1344
1345        /// Check if this operator commutes with Z basis measurement
1346        pub fn commutes_with_z(&self) -> bool {
1347            matches!(self, PauliOperator::I | PauliOperator::Z)
1348        }
1349    }
1350
1351    /// Pauli string observable (tensor product of Pauli operators)
1352    #[derive(Debug, Clone)]
1353    pub struct PauliObservable {
1354        /// Pauli operators for each qubit (qubit_index -> operator)
1355        pub operators: HashMap<usize, PauliOperator>,
1356        /// Coefficient for this Pauli string
1357        pub coefficient: f64,
1358    }
1359
1360    impl PauliObservable {
1361        /// Create a new Pauli observable
1362        pub fn new(operators: HashMap<usize, PauliOperator>, coefficient: f64) -> Self {
1363            Self {
1364                operators,
1365                coefficient,
1366            }
1367        }
1368
1369        /// Create identity observable
1370        pub fn identity(num_qubits: usize) -> Self {
1371            let mut operators = HashMap::new();
1372            for i in 0..num_qubits {
1373                operators.insert(i, PauliOperator::I);
1374            }
1375            Self {
1376                operators,
1377                coefficient: 1.0,
1378            }
1379        }
1380
1381        /// Create Z observable on specified qubits
1382        pub fn pauli_z(qubits: &[usize]) -> Self {
1383            let mut operators = HashMap::new();
1384            for &qubit in qubits {
1385                operators.insert(qubit, PauliOperator::Z);
1386            }
1387            Self {
1388                operators,
1389                coefficient: 1.0,
1390            }
1391        }
1392
1393        /// Create X observable on specified qubits
1394        pub fn pauli_x(qubits: &[usize]) -> Self {
1395            let mut operators = HashMap::new();
1396            for &qubit in qubits {
1397                operators.insert(qubit, PauliOperator::X);
1398            }
1399            Self {
1400                operators,
1401                coefficient: 1.0,
1402            }
1403        }
1404
1405        /// Create Y observable on specified qubits
1406        pub fn pauli_y(qubits: &[usize]) -> Self {
1407            let mut operators = HashMap::new();
1408            for &qubit in qubits {
1409                operators.insert(qubit, PauliOperator::Y);
1410            }
1411            Self {
1412                operators,
1413                coefficient: 1.0,
1414            }
1415        }
1416
1417        /// Compute expectation value for this observable
1418        pub fn expectation_value(&self, state: &QulacsStateVector) -> f64 {
1419            let mut result = 0.0;
1420
1421            // For computational basis states, we can compute efficiently
1422            // For now, use simple enumeration
1423            for i in 0..state.dim() {
1424                let prob = state.amplitudes()[i].norm_sqr();
1425                if prob < 1e-15 {
1426                    continue;
1427                }
1428
1429                // Compute eigenvalue for this basis state
1430                let mut eigenvalue = 1.0;
1431                for (&qubit, &op) in &self.operators {
1432                    let bit = ((i >> qubit) & 1) == 1;
1433                    match op {
1434                        PauliOperator::I => {}
1435                        PauliOperator::Z => {
1436                            eigenvalue *= if bit { -1.0 } else { 1.0 };
1437                        }
1438                        PauliOperator::X | PauliOperator::Y => {
1439                            // Non-Z operators require full matrix computation
1440                            // For now, return 0.0 as placeholder
1441                            return 0.0;
1442                        }
1443                    }
1444                }
1445
1446                result += prob * eigenvalue;
1447            }
1448
1449            result * self.coefficient
1450        }
1451
1452        /// Set coefficient
1453        pub fn with_coefficient(mut self, coefficient: f64) -> Self {
1454            self.coefficient = coefficient;
1455            self
1456        }
1457
1458        /// Get the number of non-identity operators
1459        pub fn weight(&self) -> usize {
1460            self.operators
1461                .values()
1462                .filter(|&&op| op != PauliOperator::I)
1463                .count()
1464        }
1465    }
1466
1467    /// Hermitian observable (general Hermitian matrix)
1468    #[derive(Debug, Clone)]
1469    pub struct HermitianObservable {
1470        /// The Hermitian matrix
1471        pub matrix: Array2<Complex64>,
1472        /// Number of qubits this observable acts on
1473        pub num_qubits: usize,
1474    }
1475
1476    impl HermitianObservable {
1477        /// Create a new Hermitian observable
1478        pub fn new(matrix: Array2<Complex64>) -> Result<Self> {
1479            let (n, m) = (matrix.nrows(), matrix.ncols());
1480            if n != m {
1481                return Err(SimulatorError::InvalidObservable(
1482                    "Matrix must be square".to_string(),
1483                ));
1484            }
1485
1486            if n == 0 || (n & (n - 1)) != 0 {
1487                return Err(SimulatorError::InvalidObservable(
1488                    "Dimension must be a power of 2".to_string(),
1489                ));
1490            }
1491
1492            let num_qubits = n.trailing_zeros() as usize;
1493
1494            Ok(Self { matrix, num_qubits })
1495        }
1496
1497        /// Compute expectation value <ψ|H|ψ>
1498        pub fn expectation_value(&self, state: &QulacsStateVector) -> Result<f64> {
1499            if state.num_qubits() != self.num_qubits {
1500                return Err(SimulatorError::InvalidObservable(
1501                    "Observable dimension doesn't match state".to_string(),
1502                ));
1503            }
1504
1505            let psi = state.amplitudes();
1506            let mut result = Complex64::new(0.0, 0.0);
1507
1508            for i in 0..state.dim() {
1509                for j in 0..state.dim() {
1510                    result += psi[i].conj() * self.matrix[[i, j]] * psi[j];
1511                }
1512            }
1513
1514            Ok(result.re)
1515        }
1516    }
1517
1518    /// Composite observable (sum of weighted observables)
1519    #[derive(Debug, Clone)]
1520    pub struct CompositeObservable {
1521        /// List of Pauli observables with coefficients
1522        pub terms: Vec<PauliObservable>,
1523    }
1524
1525    impl CompositeObservable {
1526        /// Create a new composite observable
1527        pub fn new() -> Self {
1528            Self { terms: Vec::new() }
1529        }
1530
1531        /// Add a Pauli observable term
1532        pub fn add_term(mut self, observable: PauliObservable) -> Self {
1533            self.terms.push(observable);
1534            self
1535        }
1536
1537        /// Compute total expectation value
1538        pub fn expectation_value(&self, state: &QulacsStateVector) -> f64 {
1539            self.terms
1540                .iter()
1541                .map(|term| term.expectation_value(state))
1542                .sum()
1543        }
1544
1545        /// Get the number of terms
1546        pub fn num_terms(&self) -> usize {
1547            self.terms.len()
1548        }
1549    }
1550
1551    impl Default for CompositeObservable {
1552        fn default() -> Self {
1553            Self::new()
1554        }
1555    }
1556}
1557
1558// ============================================================================
1559// Circuit API - High-level circuit builder for Qulacs backend
1560// ============================================================================
1561
1562/// High-level circuit API for Qulacs backend
1563///
1564/// Provides a convenient interface for building and executing quantum circuits
1565/// using the Qulacs-style backend.
1566pub mod circuit_api {
1567    use super::*;
1568    use std::collections::HashMap;
1569
1570    /// Circuit builder for Qulacs backend
1571    ///
1572    /// Example:
1573    /// ```
1574    /// use quantrs2_sim::qulacs_backend::circuit_api::QulacsCircuit;
1575    ///
1576    /// let mut circuit = QulacsCircuit::new(2).unwrap();
1577    /// circuit.h(0);
1578    /// circuit.cnot(0, 1);
1579    /// circuit.measure_all();
1580    ///
1581    /// let counts = circuit.run(1000).unwrap();
1582    /// ```
1583    #[derive(Clone)]
1584    pub struct QulacsCircuit {
1585        /// Number of qubits
1586        num_qubits: usize,
1587        /// Quantum state
1588        state: QulacsStateVector,
1589        /// Gate sequence (for inspection)
1590        gates: Vec<GateRecord>,
1591        /// Measurement results (qubit -> outcomes)
1592        measurements: HashMap<usize, Vec<bool>>,
1593        /// Optional noise model for realistic simulation
1594        noise_model: Option<crate::noise_models::NoiseModel>,
1595    }
1596
1597    /// Record of a gate operation
1598    #[derive(Debug, Clone)]
1599    pub struct GateRecord {
1600        pub name: String,
1601        pub qubits: Vec<usize>,
1602        pub params: Vec<f64>,
1603    }
1604
1605    impl QulacsCircuit {
1606        /// Create new circuit
1607        pub fn new(num_qubits: usize) -> Result<Self> {
1608            Ok(Self {
1609                num_qubits,
1610                state: QulacsStateVector::new(num_qubits)?,
1611                gates: Vec::new(),
1612                measurements: HashMap::new(),
1613                noise_model: None,
1614            })
1615        }
1616
1617        /// Get number of qubits
1618        pub fn num_qubits(&self) -> usize {
1619            self.num_qubits
1620        }
1621
1622        /// Get current state vector (immutable)
1623        pub fn state(&self) -> &QulacsStateVector {
1624            &self.state
1625        }
1626
1627        /// Get gate sequence
1628        pub fn gates(&self) -> &[GateRecord] {
1629            &self.gates
1630        }
1631
1632        /// Reset circuit to |0...0⟩ state
1633        pub fn reset(&mut self) -> Result<()> {
1634            self.state = QulacsStateVector::new(self.num_qubits)?;
1635            self.gates.clear();
1636            self.measurements.clear();
1637            Ok(())
1638        }
1639
1640        // ===== Single-Qubit Gates =====
1641
1642        /// Apply Hadamard gate
1643        pub fn h(&mut self, qubit: usize) -> &mut Self {
1644            super::gates::hadamard(&mut self.state, qubit).ok();
1645            self.gates.push(GateRecord {
1646                name: "H".to_string(),
1647                qubits: vec![qubit],
1648                params: vec![],
1649            });
1650            self
1651        }
1652
1653        /// Apply X gate
1654        pub fn x(&mut self, qubit: usize) -> &mut Self {
1655            super::gates::pauli_x(&mut self.state, qubit).ok();
1656            self.gates.push(GateRecord {
1657                name: "X".to_string(),
1658                qubits: vec![qubit],
1659                params: vec![],
1660            });
1661            self
1662        }
1663
1664        /// Apply Y gate
1665        pub fn y(&mut self, qubit: usize) -> &mut Self {
1666            super::gates::pauli_y(&mut self.state, qubit).ok();
1667            self.gates.push(GateRecord {
1668                name: "Y".to_string(),
1669                qubits: vec![qubit],
1670                params: vec![],
1671            });
1672            self
1673        }
1674
1675        /// Apply Z gate
1676        pub fn z(&mut self, qubit: usize) -> &mut Self {
1677            super::gates::pauli_z(&mut self.state, qubit).ok();
1678            self.gates.push(GateRecord {
1679                name: "Z".to_string(),
1680                qubits: vec![qubit],
1681                params: vec![],
1682            });
1683            self
1684        }
1685
1686        /// Apply S gate
1687        pub fn s(&mut self, qubit: usize) -> &mut Self {
1688            super::gates::s(&mut self.state, qubit).ok();
1689            self.gates.push(GateRecord {
1690                name: "S".to_string(),
1691                qubits: vec![qubit],
1692                params: vec![],
1693            });
1694            self
1695        }
1696
1697        /// Apply S† gate
1698        pub fn sdg(&mut self, qubit: usize) -> &mut Self {
1699            super::gates::sdg(&mut self.state, qubit).ok();
1700            self.gates.push(GateRecord {
1701                name: "S†".to_string(),
1702                qubits: vec![qubit],
1703                params: vec![],
1704            });
1705            self
1706        }
1707
1708        /// Apply T gate
1709        pub fn t(&mut self, qubit: usize) -> &mut Self {
1710            super::gates::t(&mut self.state, qubit).ok();
1711            self.gates.push(GateRecord {
1712                name: "T".to_string(),
1713                qubits: vec![qubit],
1714                params: vec![],
1715            });
1716            self
1717        }
1718
1719        /// Apply T† gate
1720        pub fn tdg(&mut self, qubit: usize) -> &mut Self {
1721            super::gates::tdg(&mut self.state, qubit).ok();
1722            self.gates.push(GateRecord {
1723                name: "T†".to_string(),
1724                qubits: vec![qubit],
1725                params: vec![],
1726            });
1727            self
1728        }
1729
1730        /// Apply RX gate
1731        pub fn rx(&mut self, qubit: usize, angle: f64) -> &mut Self {
1732            super::gates::rx(&mut self.state, qubit, angle).ok();
1733            self.gates.push(GateRecord {
1734                name: "RX".to_string(),
1735                qubits: vec![qubit],
1736                params: vec![angle],
1737            });
1738            self
1739        }
1740
1741        /// Apply RY gate
1742        pub fn ry(&mut self, qubit: usize, angle: f64) -> &mut Self {
1743            super::gates::ry(&mut self.state, qubit, angle).ok();
1744            self.gates.push(GateRecord {
1745                name: "RY".to_string(),
1746                qubits: vec![qubit],
1747                params: vec![angle],
1748            });
1749            self
1750        }
1751
1752        /// Apply RZ gate
1753        pub fn rz(&mut self, qubit: usize, angle: f64) -> &mut Self {
1754            super::gates::rz(&mut self.state, qubit, angle).ok();
1755            self.gates.push(GateRecord {
1756                name: "RZ".to_string(),
1757                qubits: vec![qubit],
1758                params: vec![angle],
1759            });
1760            self
1761        }
1762
1763        /// Apply Phase gate
1764        pub fn phase(&mut self, qubit: usize, angle: f64) -> &mut Self {
1765            super::gates::phase(&mut self.state, qubit, angle).ok();
1766            self.gates.push(GateRecord {
1767                name: "Phase".to_string(),
1768                qubits: vec![qubit],
1769                params: vec![angle],
1770            });
1771            self
1772        }
1773
1774        // ===== Two-Qubit Gates =====
1775
1776        /// Apply CNOT gate
1777        pub fn cnot(&mut self, control: usize, target: usize) -> &mut Self {
1778            super::gates::cnot(&mut self.state, control, target).ok();
1779            self.gates.push(GateRecord {
1780                name: "CNOT".to_string(),
1781                qubits: vec![control, target],
1782                params: vec![],
1783            });
1784            self
1785        }
1786
1787        /// Apply CZ gate
1788        pub fn cz(&mut self, control: usize, target: usize) -> &mut Self {
1789            super::gates::cz(&mut self.state, control, target).ok();
1790            self.gates.push(GateRecord {
1791                name: "CZ".to_string(),
1792                qubits: vec![control, target],
1793                params: vec![],
1794            });
1795            self
1796        }
1797
1798        /// Apply SWAP gate
1799        pub fn swap(&mut self, qubit1: usize, qubit2: usize) -> &mut Self {
1800            super::gates::swap(&mut self.state, qubit1, qubit2).ok();
1801            self.gates.push(GateRecord {
1802                name: "SWAP".to_string(),
1803                qubits: vec![qubit1, qubit2],
1804                params: vec![],
1805            });
1806            self
1807        }
1808
1809        // ===== Measurements =====
1810
1811        /// Measure a single qubit in computational basis
1812        pub fn measure(&mut self, qubit: usize) -> Result<bool> {
1813            let outcome = self.state.measure(qubit)?;
1814            self.measurements.entry(qubit).or_default().push(outcome);
1815            Ok(outcome)
1816        }
1817
1818        /// Measure all qubits
1819        pub fn measure_all(&mut self) -> Result<Vec<bool>> {
1820            (0..self.num_qubits).map(|q| self.measure(q)).collect()
1821        }
1822
1823        /// Run circuit multiple times (shots)
1824        pub fn run(&mut self, shots: usize) -> Result<HashMap<String, usize>> {
1825            let mut counts = HashMap::new();
1826
1827            for _ in 0..shots {
1828                // Save current state
1829                let saved_state = self.state.clone();
1830
1831                // Measure all qubits
1832                let outcomes = self.measure_all()?;
1833
1834                // Convert to bitstring
1835                let bitstring: String = outcomes
1836                    .iter()
1837                    .map(|&b| if b { '1' } else { '0' })
1838                    .collect();
1839
1840                // Update counts
1841                *counts.entry(bitstring).or_insert(0) += 1;
1842
1843                // Restore state for next shot
1844                self.state = saved_state;
1845            }
1846
1847            Ok(counts)
1848        }
1849
1850        /// Get measurement outcomes for a qubit
1851        pub fn get_measurements(&self, qubit: usize) -> Option<&Vec<bool>> {
1852            self.measurements.get(&qubit)
1853        }
1854
1855        // ===== Composite Operations =====
1856
1857        /// Apply a Bell state preparation (H on q0, CNOT q0->q1)
1858        pub fn bell_pair(&mut self, qubit0: usize, qubit1: usize) -> &mut Self {
1859            self.h(qubit0);
1860            self.cnot(qubit0, qubit1);
1861            self
1862        }
1863
1864        /// Apply QFT (Quantum Fourier Transform) on specified qubits
1865        pub fn qft(&mut self, qubits: &[usize]) -> &mut Self {
1866            let n = qubits.len();
1867            for i in 0..n {
1868                let q = qubits[i];
1869                self.h(q);
1870                for j in (i + 1)..n {
1871                    let control = qubits[j];
1872                    let angle = std::f64::consts::PI / (1 << (j - i)) as f64;
1873                    self.controlled_phase(control, q, angle);
1874                }
1875            }
1876            // Swap qubits
1877            for i in 0..(n / 2) {
1878                self.swap(qubits[i], qubits[n - 1 - i]);
1879            }
1880            self
1881        }
1882
1883        /// Apply controlled phase gate
1884        /// Implemented using: RZ(angle/2) on target, CNOT, RZ(-angle/2) on target, CNOT
1885        pub fn controlled_phase(&mut self, control: usize, target: usize, angle: f64) -> &mut Self {
1886            // Implement controlled phase using decomposition
1887            self.rz(target, angle / 2.0);
1888            self.cnot(control, target);
1889            self.rz(target, -angle / 2.0);
1890            self.cnot(control, target);
1891
1892            // Record as single controlled phase gate for clarity
1893            self.gates.push(GateRecord {
1894                name: "CPhase".to_string(),
1895                qubits: vec![control, target],
1896                params: vec![angle],
1897            });
1898            self
1899        }
1900
1901        // ===== State Query =====
1902
1903        /// Get state vector probabilities
1904        pub fn probabilities(&self) -> Vec<f64> {
1905            self.state
1906                .amplitudes()
1907                .iter()
1908                .map(|amp| amp.norm_sqr())
1909                .collect()
1910        }
1911
1912        /// Get expectation value of an observable
1913        pub fn expectation<O: Observable>(&self, observable: &O) -> Result<f64> {
1914            observable.expectation_value(&self.state)
1915        }
1916
1917        /// Get circuit depth (number of gate layers)
1918        pub fn depth(&self) -> usize {
1919            // Simple depth calculation: count unique time steps
1920            // (This is a simplified version; real depth requires topological sorting)
1921            self.gates.len()
1922        }
1923
1924        /// Get total gate count
1925        pub fn gate_count(&self) -> usize {
1926            self.gates.len()
1927        }
1928
1929        // ===== Noise Model Integration =====
1930
1931        /// Set a noise model for this circuit
1932        ///
1933        /// # Arguments
1934        ///
1935        /// * `noise_model` - The noise model to use for realistic simulation
1936        ///
1937        /// # Example
1938        ///
1939        /// ```
1940        /// use quantrs2_sim::qulacs_backend::circuit_api::QulacsCircuit;
1941        /// use quantrs2_sim::noise_models::{NoiseModel, DepolarizingNoise};
1942        /// use std::sync::Arc;
1943        ///
1944        /// let mut circuit = QulacsCircuit::new(2).unwrap();
1945        /// let mut noise_model = NoiseModel::new();
1946        /// noise_model.add_channel(Arc::new(DepolarizingNoise::new(0.01)));
1947        /// circuit.set_noise_model(noise_model);
1948        /// ```
1949        pub fn set_noise_model(&mut self, noise_model: crate::noise_models::NoiseModel) {
1950            self.noise_model = Some(noise_model);
1951        }
1952
1953        /// Remove the noise model from this circuit
1954        pub fn clear_noise_model(&mut self) {
1955            self.noise_model = None;
1956        }
1957
1958        /// Check if a noise model is set
1959        pub fn has_noise_model(&self) -> bool {
1960            self.noise_model.is_some()
1961        }
1962
1963        /// Apply noise to a single qubit based on the noise model
1964        ///
1965        /// This is automatically called after gate application if a noise model is set.
1966        fn apply_noise_to_qubit(&mut self, qubit: usize) -> Result<()> {
1967            if let Some(ref noise_model) = self.noise_model {
1968                // Extract single-qubit state
1969                let num_states = 2_usize.pow(self.num_qubits as u32);
1970                let mut noisy_amplitudes = self.state.amplitudes().to_vec();
1971
1972                // For each computational basis state
1973                for idx in 0..num_states {
1974                    // Check if this state involves the target qubit
1975                    let qubit_state = (idx >> qubit) & 1;
1976
1977                    // Create a 2-element state for this qubit
1978                    let local_state = if qubit_state == 0 {
1979                        Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])
1980                    } else {
1981                        Array1::from_vec(vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1982                    };
1983
1984                    // Apply noise model
1985                    let _noisy_local = noise_model.apply_single_qubit(&local_state, qubit)?;
1986
1987                    // Note: In a full implementation, we would need to properly
1988                    // combine the noisy local state back into the full state vector.
1989                    // This is a simplified version that demonstrates the API.
1990                }
1991
1992                // Update state (simplified)
1993                self.state =
1994                    QulacsStateVector::from_amplitudes(Array1::from_vec(noisy_amplitudes))?;
1995            }
1996            Ok(())
1997        }
1998
1999        /// Run circuit with noise model applied
2000        ///
2001        /// This executes the circuit with noise applied after each gate operation.
2002        pub fn run_with_noise(&mut self, shots: usize) -> Result<HashMap<String, usize>> {
2003            if self.noise_model.is_none() {
2004                return self.run(shots);
2005            }
2006
2007            let mut counts: HashMap<String, usize> = HashMap::new();
2008
2009            for _ in 0..shots {
2010                // Reset to initial state
2011                let initial_state = self.state.clone();
2012
2013                // Execute each gate with noise
2014                // (In practice, we'd re-execute the gate sequence with noise)
2015                // For now, just measure the current noisy state
2016                let measurement = self.measure_all()?;
2017                let bitstring: String = measurement
2018                    .iter()
2019                    .map(|&b| if b { '1' } else { '0' })
2020                    .collect();
2021
2022                *counts.entry(bitstring).or_insert(0) += 1;
2023
2024                // Restore initial state for next shot
2025                self.state = initial_state;
2026            }
2027
2028            Ok(counts)
2029        }
2030    }
2031
2032    /// Observable trait for expectation value calculations
2033    pub trait Observable {
2034        fn expectation_value(&self, state: &QulacsStateVector) -> Result<f64>;
2035    }
2036
2037    // Implement Observable for PauliObservable
2038    impl Observable for super::observable::PauliObservable {
2039        fn expectation_value(&self, state: &QulacsStateVector) -> Result<f64> {
2040            Ok(super::observable::PauliObservable::expectation_value(
2041                self, state,
2042            ))
2043        }
2044    }
2045
2046    // Implement Observable for HermitianObservable
2047    impl Observable for super::observable::HermitianObservable {
2048        fn expectation_value(&self, state: &QulacsStateVector) -> Result<f64> {
2049            super::observable::HermitianObservable::expectation_value(self, state)
2050        }
2051    }
2052}
2053
2054#[cfg(test)]
2055mod tests {
2056    use super::gates;
2057    use super::*;
2058    use scirs2_core::Float;
2059
2060    #[test]
2061    fn test_state_creation() {
2062        let state = QulacsStateVector::new(2).unwrap();
2063        assert_eq!(state.num_qubits(), 2);
2064        assert_eq!(state.dim(), 4);
2065        assert!((state.norm_squared() - 1.0).abs() < 1e-10);
2066    }
2067
2068    #[test]
2069    fn test_hadamard_gate() {
2070        let mut state = QulacsStateVector::new(1).unwrap();
2071        gates::hadamard(&mut state, 0).unwrap();
2072
2073        let expected_amp = 1.0 / 2.0f64.sqrt();
2074        assert!((state.amplitudes()[0].re - expected_amp).abs() < 1e-10);
2075        assert!((state.amplitudes()[1].re - expected_amp).abs() < 1e-10);
2076    }
2077
2078    #[test]
2079    fn test_pauli_x_gate() {
2080        let mut state = QulacsStateVector::new(1).unwrap();
2081        gates::pauli_x(&mut state, 0).unwrap();
2082
2083        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2084        assert!((state.amplitudes()[1].norm() - 1.0).abs() < 1e-10);
2085    }
2086
2087    #[test]
2088    fn test_cnot_gate() {
2089        let mut state = QulacsStateVector::new(2).unwrap();
2090
2091        // Prepare |11⟩ from |00⟩
2092        gates::pauli_x(&mut state, 0).unwrap();
2093        gates::pauli_x(&mut state, 1).unwrap();
2094
2095        // State is |11⟩ (state[3] in little-endian)
2096        assert!((state.amplitudes()[3].norm() - 1.0).abs() < 1e-10);
2097
2098        // Apply CNOT with control=0, target=1
2099        // Control (qubit 0) = 1, so flip target (qubit 1): 1 → 0
2100        // Result: qubit 0 = 1, qubit 1 = 0 → |10⟩
2101        // In little-endian: state[1] = |10⟩ (bit pattern: 0b01, but qubit ordering is reversed)
2102        gates::cnot(&mut state, 0, 1).unwrap();
2103
2104        // Should be in |10⟩ which is state[1] in little-endian
2105        // (qubit 0 = bit 0 = 1, qubit 1 = bit 1 = 0, so index = 0b01 = 1)
2106        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2107        assert!((state.amplitudes()[1].norm() - 1.0).abs() < 1e-10);
2108        assert!((state.amplitudes()[2].norm() - 0.0).abs() < 1e-10);
2109        assert!((state.amplitudes()[3].norm() - 0.0).abs() < 1e-10);
2110    }
2111
2112    #[test]
2113    fn test_bell_state() {
2114        let mut state = QulacsStateVector::new(2).unwrap();
2115
2116        // Create Bell state: |Φ+⟩ = (|00⟩ + |11⟩) / √2
2117        gates::hadamard(&mut state, 0).unwrap();
2118        gates::cnot(&mut state, 0, 1).unwrap();
2119
2120        let expected_amp = 1.0 / 2.0f64.sqrt();
2121        assert!((state.amplitudes()[0].re - expected_amp).abs() < 1e-10);
2122        assert!((state.amplitudes()[1].norm() - 0.0).abs() < 1e-10);
2123        assert!((state.amplitudes()[2].norm() - 0.0).abs() < 1e-10);
2124        assert!((state.amplitudes()[3].re - expected_amp).abs() < 1e-10);
2125    }
2126
2127    #[test]
2128    fn test_norm_squared() {
2129        let state = QulacsStateVector::new(3).unwrap();
2130        assert!((state.norm_squared() - 1.0).abs() < 1e-10);
2131    }
2132
2133    #[test]
2134    fn test_inner_product() {
2135        let state1 = QulacsStateVector::new(2).unwrap();
2136        let state2 = QulacsStateVector::new(2).unwrap();
2137
2138        let inner = state1.inner_product(&state2).unwrap();
2139        assert!((inner.re - 1.0).abs() < 1e-10);
2140        assert!(inner.im.abs() < 1e-10);
2141    }
2142
2143    // ========== Rotation Gate Tests ==========
2144
2145    #[test]
2146    fn test_rx_gate() {
2147        use std::f64::consts::PI;
2148
2149        // Test RX(π) = -iX (up to global phase)
2150        let mut state1 = QulacsStateVector::new(1).unwrap();
2151        gates::rx(&mut state1, 0, PI).unwrap();
2152
2153        let mut state2 = QulacsStateVector::new(1).unwrap();
2154        gates::pauli_x(&mut state2, 0).unwrap();
2155
2156        // RX(π) = -iX, so we need to account for the global phase
2157        // Both should flip |0⟩ to something with |1⟩ component only
2158        assert!(state1.amplitudes()[0].norm() < 1e-10);
2159        assert!((state1.amplitudes()[1].norm() - 1.0).abs() < 1e-10);
2160
2161        // Test RX(π/2) creates equal superposition with imaginary components
2162        let mut state3 = QulacsStateVector::new(1).unwrap();
2163        gates::rx(&mut state3, 0, PI / 2.0).unwrap();
2164
2165        let expected = 1.0 / 2.0f64.sqrt();
2166        assert!((state3.amplitudes()[0].re - expected).abs() < 1e-10);
2167        assert!(state3.amplitudes()[0].im.abs() < 1e-10);
2168        assert!(state3.amplitudes()[1].re.abs() < 1e-10);
2169        assert!((state3.amplitudes()[1].im + expected).abs() < 1e-10); // -i component
2170    }
2171
2172    #[test]
2173    fn test_ry_gate() {
2174        use std::f64::consts::PI;
2175
2176        // Test RY(π/2) creates equal superposition
2177        let mut state = QulacsStateVector::new(1).unwrap();
2178        gates::ry(&mut state, 0, PI / 2.0).unwrap();
2179
2180        let expected = 1.0 / 2.0f64.sqrt();
2181        assert!((state.amplitudes()[0].re - expected).abs() < 1e-10);
2182        assert!((state.amplitudes()[1].re - expected).abs() < 1e-10);
2183        assert!(state.amplitudes()[0].im.abs() < 1e-10);
2184        assert!(state.amplitudes()[1].im.abs() < 1e-10);
2185
2186        // Test RY can create Bell state
2187        let mut bell_state = QulacsStateVector::new(2).unwrap();
2188        gates::ry(&mut bell_state, 0, PI / 2.0).unwrap();
2189        gates::cnot(&mut bell_state, 0, 1).unwrap();
2190
2191        // Should have |00⟩ and |11⟩ with equal probability
2192        assert!((bell_state.amplitudes()[0].norm() - expected).abs() < 1e-10);
2193        assert!((bell_state.amplitudes()[3].norm() - expected).abs() < 1e-10);
2194    }
2195
2196    #[test]
2197    fn test_rz_gate() {
2198        use std::f64::consts::PI;
2199
2200        // Test RZ(π) = Z (up to global phase)
2201        let mut state1 = QulacsStateVector::new(1).unwrap();
2202        gates::pauli_x(&mut state1, 0).unwrap(); // Start with |1⟩
2203        gates::rz(&mut state1, 0, PI).unwrap();
2204
2205        // RZ(π)|1⟩ = e^(iπ/2)|1⟩ = i|1⟩
2206        assert!(state1.amplitudes()[0].norm() < 1e-10);
2207        assert!((state1.amplitudes()[1].norm() - 1.0).abs() < 1e-10);
2208
2209        // Test that RZ adds phase without changing probability
2210        let mut state2 = QulacsStateVector::new(1).unwrap();
2211        gates::hadamard(&mut state2, 0).unwrap(); // |+⟩ state
2212        gates::rz(&mut state2, 0, PI / 4.0).unwrap();
2213
2214        // Probability should remain 50-50
2215        assert!((state2.amplitudes()[0].norm() - 1.0 / 2.0f64.sqrt()).abs() < 1e-10);
2216        assert!((state2.amplitudes()[1].norm() - 1.0 / 2.0f64.sqrt()).abs() < 1e-10);
2217    }
2218
2219    #[test]
2220    fn test_phase_gate() {
2221        use std::f64::consts::PI;
2222
2223        // Test Phase gate adds phase to |1⟩
2224        let mut state = QulacsStateVector::new(1).unwrap();
2225        gates::pauli_x(&mut state, 0).unwrap(); // |1⟩
2226        gates::phase(&mut state, 0, PI / 2.0).unwrap(); // Add π/2 phase
2227
2228        // Should be e^(iπ/2)|1⟩ = i|1⟩
2229        assert!(state.amplitudes()[0].norm() < 1e-10);
2230        assert!((state.amplitudes()[1].re).abs() < 1e-10);
2231        assert!((state.amplitudes()[1].im - 1.0).abs() < 1e-10);
2232    }
2233
2234    #[test]
2235    fn test_u3_gate() {
2236        use std::f64::consts::PI;
2237
2238        // Test U3 can reproduce Hadamard: U3(π/2, 0, π)
2239        let mut state1 = QulacsStateVector::new(1).unwrap();
2240        gates::u3(&mut state1, 0, PI / 2.0, 0.0, PI).unwrap();
2241
2242        let mut state2 = QulacsStateVector::new(1).unwrap();
2243        gates::hadamard(&mut state2, 0).unwrap();
2244
2245        // Should be equivalent (up to global phase)
2246        let ratio = state1.amplitudes()[0] / state2.amplitudes()[0];
2247        assert!((state1.amplitudes()[0] / ratio - state2.amplitudes()[0]).norm() < 1e-10);
2248        assert!((state1.amplitudes()[1] / ratio - state2.amplitudes()[1]).norm() < 1e-10);
2249
2250        // Test U3 can reproduce X gate: U3(π, 0, π)
2251        let mut state3 = QulacsStateVector::new(1).unwrap();
2252        gates::u3(&mut state3, 0, PI, 0.0, PI).unwrap();
2253
2254        let mut state4 = QulacsStateVector::new(1).unwrap();
2255        gates::pauli_x(&mut state4, 0).unwrap();
2256
2257        let ratio2 = state3.amplitudes()[1] / state4.amplitudes()[1];
2258        assert!((state3.amplitudes()[0] / ratio2 - state4.amplitudes()[0]).norm() < 1e-10);
2259        assert!((state3.amplitudes()[1] / ratio2 - state4.amplitudes()[1]).norm() < 1e-10);
2260    }
2261
2262    #[test]
2263    fn test_rotation_gates_on_multi_qubit_state() {
2264        use std::f64::consts::PI;
2265
2266        // Test rotation gates work on multi-qubit states
2267        let mut state = QulacsStateVector::new(3).unwrap();
2268
2269        // Apply RY(π/2) on qubit 0
2270        gates::ry(&mut state, 0, PI / 2.0).unwrap();
2271
2272        // Apply RX(π/4) on qubit 1
2273        gates::rx(&mut state, 1, PI / 4.0).unwrap();
2274
2275        // Apply RZ(π/3) on qubit 2
2276        gates::rz(&mut state, 2, PI / 3.0).unwrap();
2277
2278        // State should still be normalized
2279        assert!((state.norm_squared() - 1.0).abs() < 1e-10);
2280    }
2281
2282    #[test]
2283    fn test_rotation_composition() {
2284        use std::f64::consts::PI;
2285
2286        // Test that RZ(θ)RY(θ)RX(θ) works correctly
2287        let mut state1 = QulacsStateVector::new(1).unwrap();
2288        gates::rx(&mut state1, 0, PI / 4.0).unwrap();
2289        gates::ry(&mut state1, 0, PI / 4.0).unwrap();
2290        gates::rz(&mut state1, 0, PI / 4.0).unwrap();
2291
2292        // Should produce a valid normalized state
2293        assert!((state1.norm_squared() - 1.0).abs() < 1e-10);
2294
2295        // Compare with U3 which should be able to reproduce this
2296        let mut state2 = QulacsStateVector::new(1).unwrap();
2297        // U3(θ, φ, λ) with appropriate parameters
2298        gates::u3(&mut state2, 0, PI / 4.0, PI / 4.0, PI / 4.0).unwrap();
2299
2300        assert!((state2.norm_squared() - 1.0).abs() < 1e-10);
2301    }
2302
2303    // ========== Measurement Tests ==========
2304
2305    #[test]
2306    fn test_probability_calculation() {
2307        // Test probability on |0⟩ state
2308        let state0 = QulacsStateVector::new(1).unwrap();
2309        assert!((state0.probability_zero(0).unwrap() - 1.0).abs() < 1e-10);
2310        assert!(state0.probability_one(0).unwrap().abs() < 1e-10);
2311
2312        // Test probability on |1⟩ state
2313        let mut state1 = QulacsStateVector::new(1).unwrap();
2314        gates::pauli_x(&mut state1, 0).unwrap();
2315        assert!(state1.probability_zero(0).unwrap().abs() < 1e-10);
2316        assert!((state1.probability_one(0).unwrap() - 1.0).abs() < 1e-10);
2317
2318        // Test probability on |+⟩ state
2319        let mut state_plus = QulacsStateVector::new(1).unwrap();
2320        gates::hadamard(&mut state_plus, 0).unwrap();
2321        assert!((state_plus.probability_zero(0).unwrap() - 0.5).abs() < 1e-10);
2322        assert!((state_plus.probability_one(0).unwrap() - 0.5).abs() < 1e-10);
2323    }
2324
2325    #[test]
2326    fn test_measurement_collapse() {
2327        // Measure |+⟩ state multiple times and check outcomes
2328        let mut outcomes_0 = 0;
2329        let mut outcomes_1 = 0;
2330        let num_trials = 1000;
2331
2332        for _ in 0..num_trials {
2333            let mut state = QulacsStateVector::new(1).unwrap();
2334            gates::hadamard(&mut state, 0).unwrap();
2335
2336            let outcome = state.measure(0).unwrap();
2337            if outcome {
2338                outcomes_1 += 1;
2339            } else {
2340                outcomes_0 += 1;
2341            }
2342
2343            // After measurement, state should be pure |0⟩ or |1⟩
2344            assert!((state.norm_squared() - 1.0).abs() < 1e-10);
2345            if outcome {
2346                assert!((state.probability_one(0).unwrap() - 1.0).abs() < 1e-10);
2347            } else {
2348                assert!((state.probability_zero(0).unwrap() - 1.0).abs() < 1e-10);
2349            }
2350        }
2351
2352        // Should get roughly 50-50 split (allow 3-sigma deviation)
2353        let ratio = outcomes_1 as f64 / num_trials as f64;
2354        assert!(ratio > 0.4 && ratio < 0.6, "Ratio: {}", ratio);
2355    }
2356
2357    #[test]
2358    fn test_sampling() {
2359        // Create Bell state
2360        let mut bell_state = QulacsStateVector::new(2).unwrap();
2361        gates::hadamard(&mut bell_state, 0).unwrap();
2362        gates::cnot(&mut bell_state, 0, 1).unwrap();
2363
2364        // Sample 1000 times
2365        let samples = bell_state.sample(1000).unwrap();
2366        assert_eq!(samples.len(), 1000);
2367
2368        // Count outcomes
2369        let mut count_00 = 0;
2370        let mut count_11 = 0;
2371        for bitstring in &samples {
2372            assert_eq!(bitstring.len(), 2);
2373            if !bitstring[0] && !bitstring[1] {
2374                count_00 += 1;
2375            } else if bitstring[0] && bitstring[1] {
2376                count_11 += 1;
2377            } else {
2378                // Bell state should only produce |00⟩ or |11⟩
2379                panic!("Unexpected outcome: {:?}", bitstring);
2380            }
2381        }
2382
2383        // Should get roughly equal counts (allow 3-sigma deviation)
2384        let ratio = count_00 as f64 / 1000.0;
2385        assert!(ratio > 0.4 && ratio < 0.6, "Ratio |00⟩: {}", ratio);
2386
2387        // Verify sampling doesn't change state
2388        assert!((bell_state.norm_squared() - 1.0).abs() < 1e-10);
2389    }
2390
2391    #[test]
2392    fn test_get_counts() {
2393        // Create |+⟩ state
2394        let mut state = QulacsStateVector::new(1).unwrap();
2395        gates::hadamard(&mut state, 0).unwrap();
2396
2397        let counts = state.get_counts(1000).unwrap();
2398
2399        // Should have two entries: [false] and [true]
2400        assert!(counts.len() <= 2);
2401
2402        let count_0 = *counts.get(&vec![false]).unwrap_or(&0);
2403        let count_1 = *counts.get(&vec![true]).unwrap_or(&0);
2404
2405        assert_eq!(count_0 + count_1, 1000);
2406
2407        // Should be roughly 50-50
2408        let ratio = count_1 as f64 / 1000.0;
2409        assert!(ratio > 0.4 && ratio < 0.6, "Ratio |1⟩: {}", ratio);
2410    }
2411
2412    #[test]
2413    fn test_sample_qubits() {
2414        // Create 2-qubit Bell state for now (TODO: fix 3-qubit CNOT)
2415        let mut bell = QulacsStateVector::new(2).unwrap();
2416        gates::hadamard(&mut bell, 0).unwrap();
2417        gates::cnot(&mut bell, 0, 1).unwrap();
2418
2419        // Sample only qubit 0
2420        let samples = bell.sample_qubits(&[0], 1000).unwrap();
2421
2422        let mut count_0 = 0;
2423        let mut count_1 = 0;
2424
2425        for bitstring in &samples {
2426            assert_eq!(bitstring.len(), 1);
2427            if bitstring[0] {
2428                count_1 += 1;
2429            } else {
2430                count_0 += 1;
2431            }
2432        }
2433
2434        // Should be roughly 50-50
2435        let ratio = count_1 as f64 / 1000.0;
2436        assert!(ratio > 0.4 && ratio < 0.6, "Ratio: {}", ratio);
2437    }
2438
2439    #[test]
2440    fn test_measurement_multi_qubit() {
2441        // Create Bell state and measure both qubits
2442        let mut bell_state = QulacsStateVector::new(2).unwrap();
2443        gates::hadamard(&mut bell_state, 0).unwrap();
2444        gates::cnot(&mut bell_state, 0, 1).unwrap();
2445
2446        let outcome0 = bell_state.measure(0).unwrap();
2447        let outcome1 = bell_state.measure(1).unwrap();
2448
2449        // For Bell state, both qubits should be correlated
2450        assert_eq!(outcome0, outcome1);
2451    }
2452
2453    #[test]
2454    fn test_toffoli_gate() {
2455        // Test Toffoli gate: CCX gate that flips target when both controls are |1⟩
2456        let mut state = QulacsStateVector::new(3).unwrap();
2457
2458        // Case 1: Initial state |000⟩ - no change expected
2459        gates::toffoli(&mut state, 0, 1, 2).unwrap();
2460        assert!((state.amplitudes()[0].norm() - 1.0).abs() < 1e-10);
2461        assert!(state.amplitudes()[7].norm() < 1e-10);
2462
2463        // Case 2: State |011⟩ - should flip to |111⟩
2464        let mut state2 = QulacsStateVector::new(3).unwrap();
2465        gates::pauli_x(&mut state2, 0).unwrap(); // |100⟩
2466        gates::pauli_x(&mut state2, 1).unwrap(); // |110⟩
2467
2468        // Apply Toffoli(0, 1, 2)
2469        gates::toffoli(&mut state2, 0, 1, 2).unwrap();
2470
2471        // Should now be |111⟩
2472        assert!((state2.amplitudes()[7].norm() - 1.0).abs() < 1e-10);
2473        assert!(state2.amplitudes()[3].norm() < 1e-10); // |110⟩ should be zero
2474
2475        // Case 3: Test with superposition - create |+00⟩
2476        let mut state3 = QulacsStateVector::new(3).unwrap();
2477        gates::hadamard(&mut state3, 0).unwrap();
2478
2479        // Apply X to all qubits
2480        gates::pauli_x(&mut state3, 0).unwrap();
2481        gates::pauli_x(&mut state3, 1).unwrap();
2482        gates::pauli_x(&mut state3, 2).unwrap();
2483
2484        // Now have |-11⟩, apply Toffoli
2485        gates::toffoli(&mut state3, 0, 1, 2).unwrap();
2486
2487        // Verify state is still normalized
2488        assert!((state3.norm_squared() - 1.0).abs() < 1e-10);
2489    }
2490
2491    #[test]
2492    fn test_toffoli_reversibility() {
2493        // Toffoli is self-inverse
2494        let mut state1 = QulacsStateVector::new(3).unwrap();
2495        gates::hadamard(&mut state1, 0).unwrap();
2496        gates::hadamard(&mut state1, 1).unwrap();
2497        gates::hadamard(&mut state1, 2).unwrap();
2498
2499        let original_state = state1.clone();
2500
2501        // Apply Toffoli twice
2502        gates::toffoli(&mut state1, 0, 1, 2).unwrap();
2503        gates::toffoli(&mut state1, 0, 1, 2).unwrap();
2504
2505        // Should return to original state
2506        for i in 0..8 {
2507            let diff = (state1.amplitudes()[i] - original_state.amplitudes()[i]).norm();
2508            assert!(diff < 1e-10, "Difference at index {}: {}", i, diff);
2509        }
2510    }
2511
2512    #[test]
2513    fn test_fredkin_gate() {
2514        // Test Fredkin gate: CSWAP gate that swaps target1 and target2 when control is |1⟩
2515        let mut state = QulacsStateVector::new(3).unwrap();
2516
2517        // Case 1: Initial state |000⟩ - no change expected (control=0)
2518        gates::fredkin(&mut state, 0, 1, 2).unwrap();
2519        assert!((state.amplitudes()[0].norm() - 1.0).abs() < 1e-10);
2520
2521        // Case 2: State |101⟩ - should swap to |011⟩
2522        // Ket notation |q2 q1 q0⟩:
2523        //   Before: |1 0 1⟩ → index = 1 + 0 + 4 = 5 = 0b101
2524        //   After Fredkin(control=q0, target1=q1, target2=q2): swap q1 and q2
2525        //   After:  |0 1 1⟩ → index = 1 + 2 + 0 = 3 = 0b011
2526        let mut state2 = QulacsStateVector::new(3).unwrap();
2527        gates::pauli_x(&mut state2, 0).unwrap(); // qubit 0 = 1 (control)
2528        gates::pauli_x(&mut state2, 2).unwrap(); // qubit 2 = 1
2529
2530        // State is |101⟩ (ket notation: q2=1, q1=0, q0=1)
2531        assert!((state2.amplitudes()[0b101].norm() - 1.0).abs() < 1e-10);
2532
2533        // Apply Fredkin(0, 1, 2) - swap qubits 1 and 2 since control(0)=1
2534        gates::fredkin(&mut state2, 0, 1, 2).unwrap();
2535
2536        // Should now be at index 0b011 (ket |011⟩: q2=0, q1=1, q0=1)
2537        assert!((state2.amplitudes()[0b011].norm() - 1.0).abs() < 1e-10);
2538        assert!(state2.amplitudes()[0b101].norm() < 1e-10);
2539
2540        // Case 3: Control qubit is |0⟩ - no swap
2541        let mut state3 = QulacsStateVector::new(3).unwrap();
2542        gates::pauli_x(&mut state3, 1).unwrap(); // target1=1
2543        gates::pauli_x(&mut state3, 2).unwrap(); // target2=1
2544
2545        // State is |011⟩, control(0)=0
2546        let before = state3.clone();
2547        gates::fredkin(&mut state3, 0, 1, 2).unwrap();
2548
2549        // Should be unchanged
2550        for i in 0..8 {
2551            let diff = (state3.amplitudes()[i] - before.amplitudes()[i]).norm();
2552            assert!(diff < 1e-10);
2553        }
2554    }
2555
2556    #[test]
2557    fn test_fredkin_reversibility() {
2558        // Fredkin is self-inverse
2559        let mut state1 = QulacsStateVector::new(3).unwrap();
2560        gates::hadamard(&mut state1, 0).unwrap();
2561        gates::hadamard(&mut state1, 1).unwrap();
2562        gates::hadamard(&mut state1, 2).unwrap();
2563
2564        let original_state = state1.clone();
2565
2566        // Apply Fredkin twice
2567        gates::fredkin(&mut state1, 0, 1, 2).unwrap();
2568        gates::fredkin(&mut state1, 0, 1, 2).unwrap();
2569
2570        // Should return to original state
2571        for i in 0..8 {
2572            let diff = (state1.amplitudes()[i] - original_state.amplitudes()[i]).norm();
2573            assert!(diff < 1e-10, "Difference at index {}: {}", i, diff);
2574        }
2575    }
2576
2577    #[test]
2578    fn test_toffoli_error_cases() {
2579        let mut state = QulacsStateVector::new(3).unwrap();
2580
2581        // Test invalid qubit indices
2582        assert!(gates::toffoli(&mut state, 0, 1, 5).is_err());
2583        assert!(gates::toffoli(&mut state, 5, 1, 2).is_err());
2584
2585        // Test same qubits
2586        assert!(gates::toffoli(&mut state, 0, 0, 2).is_err());
2587        assert!(gates::toffoli(&mut state, 0, 1, 1).is_err());
2588        assert!(gates::toffoli(&mut state, 0, 1, 0).is_err());
2589    }
2590
2591    #[test]
2592    fn test_fredkin_error_cases() {
2593        let mut state = QulacsStateVector::new(3).unwrap();
2594
2595        // Test invalid qubit indices
2596        assert!(gates::fredkin(&mut state, 5, 1, 2).is_err());
2597        assert!(gates::fredkin(&mut state, 0, 5, 2).is_err());
2598        assert!(gates::fredkin(&mut state, 0, 1, 5).is_err());
2599
2600        // Test same qubits
2601        assert!(gates::fredkin(&mut state, 0, 0, 2).is_err());
2602        assert!(gates::fredkin(&mut state, 0, 1, 1).is_err());
2603        assert!(gates::fredkin(&mut state, 0, 1, 0).is_err());
2604    }
2605
2606    #[test]
2607    fn test_s_gate() {
2608        // S gate: |0⟩ → |0⟩, |1⟩ → i|1⟩
2609        let mut state = QulacsStateVector::new(1).unwrap();
2610        gates::pauli_x(&mut state, 0).unwrap(); // |1⟩
2611        gates::s(&mut state, 0).unwrap();
2612
2613        // State should be i|1⟩
2614        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2615        assert!((state.amplitudes()[1].re - 0.0).abs() < 1e-10);
2616        assert!((state.amplitudes()[1].im - 1.0).abs() < 1e-10);
2617    }
2618
2619    #[test]
2620    fn test_s_dag_gate() {
2621        // S† gate: |0⟩ → |0⟩, |1⟩ → -i|1⟩
2622        let mut state = QulacsStateVector::new(1).unwrap();
2623        gates::pauli_x(&mut state, 0).unwrap(); // |1⟩
2624        gates::sdg(&mut state, 0).unwrap();
2625
2626        // State should be -i|1⟩
2627        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2628        assert!((state.amplitudes()[1].re - 0.0).abs() < 1e-10);
2629        assert!((state.amplitudes()[1].im + 1.0).abs() < 1e-10);
2630    }
2631
2632    #[test]
2633    fn test_s_s_dag_identity() {
2634        // S · S† = I
2635        let mut state = QulacsStateVector::new(1).unwrap();
2636        gates::hadamard(&mut state, 0).unwrap(); // |+⟩
2637
2638        let original = state.clone();
2639
2640        gates::s(&mut state, 0).unwrap();
2641        gates::sdg(&mut state, 0).unwrap();
2642
2643        // Should return to original state
2644        for i in 0..2 {
2645            let diff = (state.amplitudes()[i] - original.amplitudes()[i]).norm();
2646            assert!(diff < 1e-10);
2647        }
2648    }
2649
2650    #[test]
2651    fn test_t_gate() {
2652        // T gate: |0⟩ → |0⟩, |1⟩ → e^(iπ/4)|1⟩
2653        let mut state = QulacsStateVector::new(1).unwrap();
2654        gates::pauli_x(&mut state, 0).unwrap(); // |1⟩
2655        gates::t(&mut state, 0).unwrap();
2656
2657        // State should be e^(iπ/4)|1⟩
2658        let expected = Complex64::from_polar(1.0, std::f64::consts::FRAC_PI_4);
2659        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2660        assert!((state.amplitudes()[1].re - expected.re).abs() < 1e-10);
2661        assert!((state.amplitudes()[1].im - expected.im).abs() < 1e-10);
2662    }
2663
2664    #[test]
2665    fn test_t_dag_gate() {
2666        // T† gate: |0⟩ → |0⟩, |1⟩ → e^(-iπ/4)|1⟩
2667        let mut state = QulacsStateVector::new(1).unwrap();
2668        gates::pauli_x(&mut state, 0).unwrap(); // |1⟩
2669        gates::tdg(&mut state, 0).unwrap();
2670
2671        // State should be e^(-iπ/4)|1⟩
2672        let expected = Complex64::from_polar(1.0, -std::f64::consts::FRAC_PI_4);
2673        assert!((state.amplitudes()[0].norm() - 0.0).abs() < 1e-10);
2674        assert!((state.amplitudes()[1].re - expected.re).abs() < 1e-10);
2675        assert!((state.amplitudes()[1].im - expected.im).abs() < 1e-10);
2676    }
2677
2678    #[test]
2679    fn test_t_t_dag_identity() {
2680        // T · T† = I
2681        let mut state = QulacsStateVector::new(1).unwrap();
2682        gates::hadamard(&mut state, 0).unwrap(); // |+⟩
2683
2684        let original = state.clone();
2685
2686        gates::t(&mut state, 0).unwrap();
2687        gates::tdg(&mut state, 0).unwrap();
2688
2689        // Should return to original state
2690        for i in 0..2 {
2691            let diff = (state.amplitudes()[i] - original.amplitudes()[i]).norm();
2692            assert!(diff < 1e-10);
2693        }
2694    }
2695
2696    #[test]
2697    fn test_s_equals_two_t() {
2698        // S = T · T (since π/4 + π/4 = π/2)
2699        let mut state1 = QulacsStateVector::new(1).unwrap();
2700        let mut state2 = QulacsStateVector::new(1).unwrap();
2701
2702        gates::hadamard(&mut state1, 0).unwrap();
2703        gates::hadamard(&mut state2, 0).unwrap();
2704
2705        // Apply S to state1
2706        gates::s(&mut state1, 0).unwrap();
2707
2708        // Apply T twice to state2
2709        gates::t(&mut state2, 0).unwrap();
2710        gates::t(&mut state2, 0).unwrap();
2711
2712        // Should be the same
2713        for i in 0..2 {
2714            let diff = (state1.amplitudes()[i] - state2.amplitudes()[i]).norm();
2715            assert!(diff < 1e-10);
2716        }
2717    }
2718
2719    // Observable framework tests
2720    #[test]
2721    fn test_pauli_operator_matrices() {
2722        use observable::PauliOperator;
2723
2724        // Test identity
2725        let i_mat = PauliOperator::I.matrix();
2726        assert_eq!(i_mat[[0, 0]], Complex64::new(1.0, 0.0));
2727        assert_eq!(i_mat[[1, 1]], Complex64::new(1.0, 0.0));
2728
2729        // Test Pauli X
2730        let x_mat = PauliOperator::X.matrix();
2731        assert_eq!(x_mat[[0, 1]], Complex64::new(1.0, 0.0));
2732        assert_eq!(x_mat[[1, 0]], Complex64::new(1.0, 0.0));
2733
2734        // Test Pauli Z
2735        let z_mat = PauliOperator::Z.matrix();
2736        assert_eq!(z_mat[[0, 0]], Complex64::new(1.0, 0.0));
2737        assert_eq!(z_mat[[1, 1]], Complex64::new(-1.0, 0.0));
2738    }
2739
2740    #[test]
2741    fn test_pauli_observable_creation() {
2742        use observable::PauliObservable;
2743
2744        // Create Pauli Z observable
2745        let obs_z = PauliObservable::pauli_z(&[0]);
2746        assert_eq!(obs_z.coefficient, 1.0);
2747        assert_eq!(obs_z.operators.len(), 1);
2748
2749        // Create Pauli X observable
2750        let obs_x = PauliObservable::pauli_x(&[0, 1]);
2751        assert_eq!(obs_x.operators.len(), 2);
2752    }
2753
2754    #[test]
2755    fn test_pauli_z_expectation_value() {
2756        use observable::PauliObservable;
2757
2758        // Create |0⟩ state
2759        let state = QulacsStateVector::new(1).unwrap();
2760
2761        // Measure Pauli Z - should give +1 for |0⟩
2762        let obs = PauliObservable::pauli_z(&[0]);
2763        let exp_val = obs.expectation_value(&state);
2764        assert!((exp_val - 1.0).abs() < 1e-10);
2765    }
2766
2767    #[test]
2768    fn test_pauli_z_expectation_value_excited() {
2769        use observable::PauliObservable;
2770
2771        // Create |1⟩ state
2772        let mut state = QulacsStateVector::new(1).unwrap();
2773        gates::pauli_x(&mut state, 0).unwrap();
2774
2775        // Measure Pauli Z - should give -1 for |1⟩
2776        let obs = PauliObservable::pauli_z(&[0]);
2777        let exp_val = obs.expectation_value(&state);
2778        assert!((exp_val - (-1.0)).abs() < 1e-10);
2779    }
2780
2781    #[test]
2782    fn test_pauli_z_expectation_value_superposition() {
2783        use observable::PauliObservable;
2784
2785        // Create |+⟩ state (equal superposition)
2786        let mut state = QulacsStateVector::new(1).unwrap();
2787        gates::hadamard(&mut state, 0).unwrap();
2788
2789        // Measure Pauli Z - should give 0 for |+⟩
2790        let obs = PauliObservable::pauli_z(&[0]);
2791        let exp_val = obs.expectation_value(&state);
2792        assert!(exp_val.abs() < 1e-10);
2793    }
2794
2795    #[test]
2796    fn test_hermitian_observable() {
2797        use observable::HermitianObservable;
2798
2799        // Create Pauli Z matrix manually
2800        let mut matrix = Array2::zeros((2, 2));
2801        matrix[[0, 0]] = Complex64::new(1.0, 0.0);
2802        matrix[[1, 1]] = Complex64::new(-1.0, 0.0);
2803
2804        let obs = HermitianObservable::new(matrix).unwrap();
2805        assert_eq!(obs.num_qubits, 1);
2806
2807        // Test on |0⟩ state
2808        let state = QulacsStateVector::new(1).unwrap();
2809        let exp_val = obs.expectation_value(&state).unwrap();
2810        assert!((exp_val - 1.0).abs() < 1e-10);
2811    }
2812
2813    #[test]
2814    fn test_composite_observable() {
2815        use observable::{CompositeObservable, PauliObservable};
2816
2817        // Create composite observable: 0.5 * Z_0 + 0.3 * Z_1
2818        let obs1 = PauliObservable::pauli_z(&[0]).with_coefficient(0.5);
2819        let obs2 = PauliObservable::pauli_z(&[1]).with_coefficient(0.3);
2820
2821        let composite = CompositeObservable::new().add_term(obs1).add_term(obs2);
2822
2823        assert_eq!(composite.num_terms(), 2);
2824
2825        // Test on |00⟩ state - should give 0.5 * 1.0 + 0.3 * 1.0 = 0.8
2826        let state = QulacsStateVector::new(2).unwrap();
2827        let exp_val = composite.expectation_value(&state);
2828        assert!((exp_val - 0.8).abs() < 1e-10);
2829    }
2830
2831    #[test]
2832    fn test_observable_weight() {
2833        use observable::{PauliObservable, PauliOperator};
2834        use std::collections::HashMap;
2835
2836        let mut operators = HashMap::new();
2837        operators.insert(0, PauliOperator::X);
2838        operators.insert(1, PauliOperator::Y);
2839        operators.insert(2, PauliOperator::I);
2840
2841        let obs = PauliObservable::new(operators, 1.0);
2842        assert_eq!(obs.weight(), 2); // X and Y are non-identity
2843    }
2844
2845    // ===== Circuit API Tests =====
2846
2847    #[test]
2848    fn test_circuit_api_basic() {
2849        use circuit_api::QulacsCircuit;
2850
2851        let mut circuit = QulacsCircuit::new(2).unwrap();
2852        assert_eq!(circuit.num_qubits(), 2);
2853        assert_eq!(circuit.gate_count(), 0);
2854
2855        circuit.h(0).x(1);
2856        assert_eq!(circuit.gate_count(), 2);
2857    }
2858
2859    #[test]
2860    fn test_circuit_api_bell_state() {
2861        use circuit_api::QulacsCircuit;
2862
2863        let mut circuit = QulacsCircuit::new(2).unwrap();
2864        circuit.bell_pair(0, 1);
2865
2866        assert_eq!(circuit.gate_count(), 2);
2867
2868        let probs = circuit.probabilities();
2869        assert!((probs[0] - 0.5).abs() < 1e-10); // |00⟩
2870        assert!(probs[1].abs() < 1e-10); // |01⟩
2871        assert!(probs[2].abs() < 1e-10); // |10⟩
2872        assert!((probs[3] - 0.5).abs() < 1e-10); // |11⟩
2873    }
2874
2875    #[test]
2876    fn test_circuit_api_run_shots() {
2877        use circuit_api::QulacsCircuit;
2878
2879        let mut circuit = QulacsCircuit::new(2).unwrap();
2880        circuit.bell_pair(0, 1);
2881
2882        let counts = circuit.run(100).unwrap();
2883
2884        // Should have roughly 50% |00⟩ and 50% |11⟩
2885        assert!(counts.contains_key("00") || counts.contains_key("11"));
2886        let total: usize = counts.values().sum();
2887        assert_eq!(total, 100);
2888    }
2889
2890    #[test]
2891    fn test_circuit_api_reset() {
2892        use circuit_api::QulacsCircuit;
2893
2894        let mut circuit = QulacsCircuit::new(2).unwrap();
2895        circuit.h(0).cnot(0, 1);
2896        assert_eq!(circuit.gate_count(), 2);
2897
2898        circuit.reset().unwrap();
2899        assert_eq!(circuit.gate_count(), 0);
2900
2901        // State should be |00⟩
2902        let probs = circuit.probabilities();
2903        assert!((probs[0] - 1.0).abs() < 1e-10);
2904    }
2905
2906    #[test]
2907    fn test_circuit_api_rotation_gates() {
2908        use circuit_api::QulacsCircuit;
2909        use std::f64::consts::PI;
2910
2911        let mut circuit = QulacsCircuit::new(1).unwrap();
2912
2913        // RX(π) should be equivalent to X (up to global phase)
2914        circuit.rx(0, PI);
2915        let probs = circuit.probabilities();
2916        assert!(probs[0].abs() < 1e-10);
2917        assert!((probs[1] - 1.0).abs() < 1e-10);
2918    }
2919
2920    #[test]
2921    fn test_circuit_api_phase_gates() {
2922        use circuit_api::QulacsCircuit;
2923
2924        let mut circuit = QulacsCircuit::new(1).unwrap();
2925
2926        // Apply S twice should equal Z
2927        circuit.s(0).s(0);
2928        assert_eq!(circuit.gate_count(), 2);
2929
2930        // |0⟩ should remain |0⟩
2931        let probs = circuit.probabilities();
2932        assert!((probs[0] - 1.0).abs() < 1e-10);
2933    }
2934
2935    #[test]
2936    fn test_circuit_api_two_qubit_gates() {
2937        use circuit_api::QulacsCircuit;
2938
2939        let mut circuit = QulacsCircuit::new(2).unwrap();
2940
2941        // CNOT with control in |1⟩ should flip target
2942        circuit.x(0).cnot(0, 1);
2943
2944        let probs = circuit.probabilities();
2945        assert!(probs[0].abs() < 1e-10); // |00⟩
2946        assert!(probs[1].abs() < 1e-10); // |01⟩
2947        assert!(probs[2].abs() < 1e-10); // |10⟩
2948        assert!((probs[3] - 1.0).abs() < 1e-10); // |11⟩
2949    }
2950
2951    #[test]
2952    fn test_circuit_api_observable() {
2953        use circuit_api::{Observable, QulacsCircuit};
2954        use observable::PauliObservable;
2955
2956        let mut circuit = QulacsCircuit::new(2).unwrap();
2957        circuit.h(0).h(1);
2958
2959        let obs = PauliObservable::pauli_z(&[0]);
2960        let exp_val = circuit.expectation(&obs).unwrap();
2961
2962        // Hadamard on |0⟩ gives equal superposition, so Z expectation is 0
2963        assert!(exp_val.abs() < 1e-10);
2964    }
2965
2966    #[test]
2967    fn test_circuit_api_gate_record() {
2968        use circuit_api::QulacsCircuit;
2969
2970        let mut circuit = QulacsCircuit::new(2).unwrap();
2971        circuit.h(0).cnot(0, 1).rx(1, 1.5);
2972
2973        let gates = circuit.gates();
2974        assert_eq!(gates.len(), 3);
2975        assert_eq!(gates[0].name, "H");
2976        assert_eq!(gates[0].qubits, vec![0]);
2977        assert_eq!(gates[1].name, "CNOT");
2978        assert_eq!(gates[1].qubits, vec![0, 1]);
2979        assert_eq!(gates[2].name, "RX");
2980        assert_eq!(gates[2].params[0], 1.5);
2981    }
2982
2983    #[test]
2984    fn test_circuit_api_noise_model() {
2985        use crate::noise_models::{DepolarizingNoise, NoiseModel as KrausNoiseModel};
2986        use circuit_api::QulacsCircuit;
2987        use std::sync::Arc;
2988
2989        let mut circuit = QulacsCircuit::new(2).unwrap();
2990        assert!(!circuit.has_noise_model());
2991
2992        // Set a noise model
2993        let mut noise_model = KrausNoiseModel::new();
2994        noise_model.add_channel(Arc::new(DepolarizingNoise::new(0.01)));
2995
2996        circuit.set_noise_model(noise_model);
2997        assert!(circuit.has_noise_model());
2998
2999        // Apply gates
3000        circuit.h(0).cnot(0, 1);
3001        assert_eq!(circuit.gate_count(), 2);
3002
3003        // Clear noise model
3004        circuit.clear_noise_model();
3005        assert!(!circuit.has_noise_model());
3006    }
3007
3008    #[test]
3009    fn test_circuit_api_run_with_noise() {
3010        use crate::noise_models::{BitFlipNoise, NoiseModel as KrausNoiseModel};
3011        use circuit_api::QulacsCircuit;
3012        use std::sync::Arc;
3013
3014        let mut circuit = QulacsCircuit::new(1).unwrap();
3015
3016        // Add bit flip noise
3017        let mut noise_model = KrausNoiseModel::new();
3018        noise_model.add_channel(Arc::new(BitFlipNoise::new(0.1)));
3019        circuit.set_noise_model(noise_model);
3020
3021        // Start in |0⟩, with bit flip noise we should occasionally see |1⟩
3022        let counts = circuit.run_with_noise(100).unwrap();
3023
3024        // Verify we get measurements
3025        let total: usize = counts.values().sum();
3026        assert_eq!(total, 100);
3027
3028        // With 10% bit flip noise, we should mostly see |0⟩ but occasionally |1⟩
3029        // (This is a probabilistic test, but with 100 shots the statistics should be reasonable)
3030        assert!(counts.contains_key("0"));
3031    }
3032}