Skip to main content

scirs2_core/quantum/
gates.rs

1//! Quantum gate definitions and statevector application.
2//!
3//! # Gate Types
4//!
5//! ## Single-Qubit Gates
6//!
7//! | Gate | Description |
8//! |------|-------------|
9//! | [`PauliX`] | Bit-flip (NOT) |
10//! | [`PauliY`] | Y Pauli |
11//! | [`PauliZ`] | Phase-flip |
12//! | [`Hadamard`] | Equal superposition |
13//! | [`PhaseS`] | S gate (π/2 phase) |
14//! | [`PhaseT`] | T gate (π/4 phase) |
15//! | [`RotX`] | Rotation around X-axis |
16//! | [`RotY`] | Rotation around Y-axis |
17//! | [`RotZ`] | Rotation around Z-axis |
18//! | [`PhaseShift`] | Arbitrary phase shift |
19//! | [`Identity`] | Identity |
20//!
21//! ## Two-Qubit Gates
22//!
23//! | Gate | Description |
24//! |------|-------------|
25//! | [`CNOT`] | Controlled-NOT |
26//! | [`CZ`] | Controlled-Z |
27//! | [`SWAP`] | SWAP |
28//! | [`Toffoli`] | Toffoli (CCX, 3-qubit) |
29//! | [`Fredkin`] | Fredkin (CSWAP, 3-qubit) |
30//! | [`CU`] | Controlled-U (arbitrary 1-qubit gate) |
31//!
32//! # Applying Gates
33//!
34//! Use [`apply_gate`] to apply any [`QuantumGate`] to specific qubits of a
35//! [`QubitRegister`]:
36//!
37//! ```rust
38//! use scirs2_core::quantum::qubits::QubitRegister;
39//! use scirs2_core::quantum::gates::{Hadamard, CNOT, apply_gate};
40//!
41//! let mut reg = QubitRegister::new_zero_state(2).expect("should succeed");
42//! apply_gate(&mut reg, &Hadamard, &[0]).expect("should succeed");
43//! apply_gate(&mut reg, &CNOT, &[0, 1]).expect("should succeed");
44//! // reg is now in the Bell state (|00⟩ + |11⟩) / √2
45//! ```
46
47use ndarray::{s, Array2};
48use num_complex::Complex;
49use std::f64::consts::PI;
50
51use super::error::{QuantumError, QuantumResult};
52use super::qubits::QubitRegister;
53
54// ─────────────────────────────────────────────────────────────────────────────
55// QuantumGate trait
56// ─────────────────────────────────────────────────────────────────────────────
57
58/// A quantum gate represented by a unitary matrix in the computational basis.
59///
60/// Implementors must return the gate's 2^n × 2^n unitary matrix and declare
61/// the number of qubits the gate acts on.
62pub trait QuantumGate: Send + Sync {
63    /// The 2^n × 2^n unitary matrix for this gate.
64    fn matrix(&self) -> Array2<Complex<f64>>;
65
66    /// Number of qubits this gate acts on.
67    fn n_qubits(&self) -> usize;
68
69    /// Human-readable name, used in circuit diagrams / debug output.
70    fn name(&self) -> &str;
71}
72
73// ─────────────────────────────────────────────────────────────────────────────
74// Helper: build 2×2 matrix
75// ─────────────────────────────────────────────────────────────────────────────
76
77/// Build a 2×2 complex matrix from row-major entries.
78fn mat2(
79    a: Complex<f64>,
80    b: Complex<f64>,
81    c: Complex<f64>,
82    d: Complex<f64>,
83) -> Array2<Complex<f64>> {
84    Array2::from_shape_vec((2, 2), vec![a, b, c, d]).expect("2x2 matrix construction is infallible")
85}
86
87fn c(re: f64, im: f64) -> Complex<f64> {
88    Complex::new(re, im)
89}
90
91fn cr(re: f64) -> Complex<f64> {
92    Complex::new(re, 0.0)
93}
94
95fn ci(im: f64) -> Complex<f64> {
96    Complex::new(0.0, im)
97}
98
99// ─────────────────────────────────────────────────────────────────────────────
100// Single-qubit gates
101// ─────────────────────────────────────────────────────────────────────────────
102
103/// Identity gate I.
104pub struct Identity;
105
106impl QuantumGate for Identity {
107    fn matrix(&self) -> Array2<Complex<f64>> {
108        mat2(cr(1.0), cr(0.0), cr(0.0), cr(1.0))
109    }
110    fn n_qubits(&self) -> usize {
111        1
112    }
113    fn name(&self) -> &str {
114        "I"
115    }
116}
117
118/// Pauli-X gate (bit-flip / NOT): |0⟩↔|1⟩.
119pub struct PauliX;
120
121impl QuantumGate for PauliX {
122    fn matrix(&self) -> Array2<Complex<f64>> {
123        mat2(cr(0.0), cr(1.0), cr(1.0), cr(0.0))
124    }
125    fn n_qubits(&self) -> usize {
126        1
127    }
128    fn name(&self) -> &str {
129        "X"
130    }
131}
132
133/// Pauli-Y gate.
134pub struct PauliY;
135
136impl QuantumGate for PauliY {
137    fn matrix(&self) -> Array2<Complex<f64>> {
138        mat2(cr(0.0), ci(-1.0), ci(1.0), cr(0.0))
139    }
140    fn n_qubits(&self) -> usize {
141        1
142    }
143    fn name(&self) -> &str {
144        "Y"
145    }
146}
147
148/// Pauli-Z gate (phase-flip): |1⟩ → −|1⟩.
149pub struct PauliZ;
150
151impl QuantumGate for PauliZ {
152    fn matrix(&self) -> Array2<Complex<f64>> {
153        mat2(cr(1.0), cr(0.0), cr(0.0), cr(-1.0))
154    }
155    fn n_qubits(&self) -> usize {
156        1
157    }
158    fn name(&self) -> &str {
159        "Z"
160    }
161}
162
163/// Hadamard gate H: (|0⟩+|1⟩)/√2 ← |0⟩, (|0⟩−|1⟩)/√2 ← |1⟩.
164pub struct Hadamard;
165
166impl QuantumGate for Hadamard {
167    fn matrix(&self) -> Array2<Complex<f64>> {
168        let s = 1.0 / 2.0_f64.sqrt();
169        mat2(cr(s), cr(s), cr(s), cr(-s))
170    }
171    fn n_qubits(&self) -> usize {
172        1
173    }
174    fn name(&self) -> &str {
175        "H"
176    }
177}
178
179/// S gate (phase gate): |1⟩ → i|1⟩ (π/2 phase shift).
180pub struct PhaseS;
181
182impl QuantumGate for PhaseS {
183    fn matrix(&self) -> Array2<Complex<f64>> {
184        mat2(cr(1.0), cr(0.0), cr(0.0), ci(1.0))
185    }
186    fn n_qubits(&self) -> usize {
187        1
188    }
189    fn name(&self) -> &str {
190        "S"
191    }
192}
193
194/// S† (S-dagger / inverse S) gate: |1⟩ → −i|1⟩.
195pub struct PhaseSdg;
196
197impl QuantumGate for PhaseSdg {
198    fn matrix(&self) -> Array2<Complex<f64>> {
199        mat2(cr(1.0), cr(0.0), cr(0.0), ci(-1.0))
200    }
201    fn n_qubits(&self) -> usize {
202        1
203    }
204    fn name(&self) -> &str {
205        "Sdg"
206    }
207}
208
209/// T gate: |1⟩ → e^{iπ/4}|1⟩ (π/4 phase shift).
210pub struct PhaseT;
211
212impl QuantumGate for PhaseT {
213    fn matrix(&self) -> Array2<Complex<f64>> {
214        let phase = Complex::from_polar(1.0, PI / 4.0);
215        mat2(cr(1.0), cr(0.0), cr(0.0), phase)
216    }
217    fn n_qubits(&self) -> usize {
218        1
219    }
220    fn name(&self) -> &str {
221        "T"
222    }
223}
224
225/// T† (T-dagger / inverse T) gate: |1⟩ → e^{−iπ/4}|1⟩.
226pub struct PhaseTdg;
227
228impl QuantumGate for PhaseTdg {
229    fn matrix(&self) -> Array2<Complex<f64>> {
230        let phase = Complex::from_polar(1.0, -PI / 4.0);
231        mat2(cr(1.0), cr(0.0), cr(0.0), phase)
232    }
233    fn n_qubits(&self) -> usize {
234        1
235    }
236    fn name(&self) -> &str {
237        "Tdg"
238    }
239}
240
241/// Rx(θ): rotation by angle `theta` around the X-axis.
242///
243/// Rx(θ) = cos(θ/2)I − i sin(θ/2)X
244pub struct RotX {
245    /// Rotation angle in radians.
246    pub theta: f64,
247}
248
249impl QuantumGate for RotX {
250    fn matrix(&self) -> Array2<Complex<f64>> {
251        let (s, co) = (self.theta / 2.0).sin_cos();
252        mat2(cr(co), ci(-s), ci(-s), cr(co))
253    }
254    fn n_qubits(&self) -> usize {
255        1
256    }
257    fn name(&self) -> &str {
258        "Rx"
259    }
260}
261
262/// Ry(θ): rotation by angle `theta` around the Y-axis.
263///
264/// Ry(θ) = cos(θ/2)I − i sin(θ/2)Y
265pub struct RotY {
266    /// Rotation angle in radians.
267    pub theta: f64,
268}
269
270impl QuantumGate for RotY {
271    fn matrix(&self) -> Array2<Complex<f64>> {
272        let (s, co) = (self.theta / 2.0).sin_cos();
273        mat2(cr(co), cr(-s), cr(s), cr(co))
274    }
275    fn n_qubits(&self) -> usize {
276        1
277    }
278    fn name(&self) -> &str {
279        "Ry"
280    }
281}
282
283/// Rz(θ): rotation by angle `theta` around the Z-axis.
284///
285/// Rz(θ) = e^{−iθ/2}|0⟩⟨0| + e^{iθ/2}|1⟩⟨1|
286pub struct RotZ {
287    /// Rotation angle in radians.
288    pub theta: f64,
289}
290
291impl QuantumGate for RotZ {
292    fn matrix(&self) -> Array2<Complex<f64>> {
293        let neg = Complex::from_polar(1.0, -self.theta / 2.0);
294        let pos = Complex::from_polar(1.0, self.theta / 2.0);
295        mat2(neg, cr(0.0), cr(0.0), pos)
296    }
297    fn n_qubits(&self) -> usize {
298        1
299    }
300    fn name(&self) -> &str {
301        "Rz"
302    }
303}
304
305/// Arbitrary phase-shift gate P(λ): |1⟩ → e^{iλ}|1⟩.
306pub struct PhaseShift {
307    /// Phase angle λ in radians.
308    pub lambda: f64,
309}
310
311impl QuantumGate for PhaseShift {
312    fn matrix(&self) -> Array2<Complex<f64>> {
313        let phase = Complex::from_polar(1.0, self.lambda);
314        mat2(cr(1.0), cr(0.0), cr(0.0), phase)
315    }
316    fn n_qubits(&self) -> usize {
317        1
318    }
319    fn name(&self) -> &str {
320        "P"
321    }
322}
323
324/// General single-qubit unitary U(θ, φ, λ) — IBM convention:
325/// U = [[cos(θ/2), −e^{iλ}sin(θ/2)], [e^{iφ}sin(θ/2), e^{i(φ+λ)}cos(θ/2)]]
326pub struct Unitary1Q {
327    /// Polar rotation angle.
328    pub theta: f64,
329    /// Azimuthal phase φ.
330    pub phi: f64,
331    /// Phase λ.
332    pub lambda: f64,
333}
334
335impl QuantumGate for Unitary1Q {
336    fn matrix(&self) -> Array2<Complex<f64>> {
337        let (s, co) = (self.theta / 2.0).sin_cos();
338        let eiphi = Complex::from_polar(1.0, self.phi);
339        let eilambda = Complex::from_polar(1.0, self.lambda);
340        let eiphilambda = Complex::from_polar(1.0, self.phi + self.lambda);
341        mat2(cr(co), -eilambda * s, eiphi * s, eiphilambda * co)
342    }
343    fn n_qubits(&self) -> usize {
344        1
345    }
346    fn name(&self) -> &str {
347        "U"
348    }
349}
350
351// ─────────────────────────────────────────────────────────────────────────────
352// Two-qubit gates
353// ─────────────────────────────────────────────────────────────────────────────
354
355/// CNOT (CX) gate: flips target qubit when control qubit is |1⟩.
356///
357/// Matrix in the basis |00⟩, |01⟩, |10⟩, |11⟩ (control=0, target=1):
358/// ```text
359/// 1 0 0 0
360/// 0 1 0 0
361/// 0 0 0 1
362/// 0 0 1 0
363/// ```
364pub struct CNOT;
365
366impl QuantumGate for CNOT {
367    fn matrix(&self) -> Array2<Complex<f64>> {
368        let o = cr(0.0);
369        let i = cr(1.0);
370        Array2::from_shape_vec((4, 4), vec![i, o, o, o, o, i, o, o, o, o, o, i, o, o, i, o])
371            .expect("4x4 matrix construction is infallible")
372    }
373    fn n_qubits(&self) -> usize {
374        2
375    }
376    fn name(&self) -> &str {
377        "CNOT"
378    }
379}
380
381/// CZ (Controlled-Z) gate: applies Z to target when control is |1⟩.
382pub struct CZ;
383
384impl QuantumGate for CZ {
385    fn matrix(&self) -> Array2<Complex<f64>> {
386        let o = cr(0.0);
387        let i = cr(1.0);
388        let m = cr(-1.0);
389        Array2::from_shape_vec((4, 4), vec![i, o, o, o, o, i, o, o, o, o, i, o, o, o, o, m])
390            .expect("4x4 matrix construction is infallible")
391    }
392    fn n_qubits(&self) -> usize {
393        2
394    }
395    fn name(&self) -> &str {
396        "CZ"
397    }
398}
399
400/// SWAP gate: swaps two qubits.
401pub struct SWAP;
402
403impl QuantumGate for SWAP {
404    fn matrix(&self) -> Array2<Complex<f64>> {
405        let o = cr(0.0);
406        let i = cr(1.0);
407        Array2::from_shape_vec((4, 4), vec![i, o, o, o, o, o, i, o, o, i, o, o, o, o, o, i])
408            .expect("4x4 matrix construction is infallible")
409    }
410    fn n_qubits(&self) -> usize {
411        2
412    }
413    fn name(&self) -> &str {
414        "SWAP"
415    }
416}
417
418/// iSWAP gate: SWAP with additional i phase on swapped states.
419pub struct ISWAP;
420
421impl QuantumGate for ISWAP {
422    fn matrix(&self) -> Array2<Complex<f64>> {
423        let o = cr(0.0);
424        let i_re = cr(1.0);
425        let i_im = ci(1.0);
426        Array2::from_shape_vec(
427            (4, 4),
428            vec![i_re, o, o, o, o, o, i_im, o, o, i_im, o, o, o, o, o, i_re],
429        )
430        .expect("4x4 matrix construction is infallible")
431    }
432    fn n_qubits(&self) -> usize {
433        2
434    }
435    fn name(&self) -> &str {
436        "iSWAP"
437    }
438}
439
440/// Controlled-U gate: applies an arbitrary 1-qubit gate `u` to the target
441/// when the control qubit is |1⟩.
442pub struct CU {
443    inner: Box<dyn QuantumGate>,
444}
445
446impl CU {
447    /// Construct a CU gate wrapping any single-qubit gate.
448    pub fn new(gate: impl QuantumGate + 'static) -> QuantumResult<Self> {
449        if gate.n_qubits() != 1 {
450            return Err(QuantumError::GateArityMismatch {
451                gate_qubits: gate.n_qubits(),
452                supplied: 1,
453            });
454        }
455        Ok(Self {
456            inner: Box::new(gate),
457        })
458    }
459}
460
461impl QuantumGate for CU {
462    fn matrix(&self) -> Array2<Complex<f64>> {
463        let u = self.inner.matrix();
464        let o = cr(0.0);
465        let i = cr(1.0);
466        let u00 = u[[0, 0]];
467        let u01 = u[[0, 1]];
468        let u10 = u[[1, 0]];
469        let u11 = u[[1, 1]];
470        Array2::from_shape_vec(
471            (4, 4),
472            vec![i, o, o, o, o, i, o, o, o, o, u00, u01, o, o, u10, u11],
473        )
474        .expect("4x4 matrix construction is infallible")
475    }
476    fn n_qubits(&self) -> usize {
477        2
478    }
479    fn name(&self) -> &str {
480        "CU"
481    }
482}
483
484// ─────────────────────────────────────────────────────────────────────────────
485// Three-qubit gates
486// ─────────────────────────────────────────────────────────────────────────────
487
488/// Toffoli (CCX) gate: flips target when *both* control qubits are |1⟩.
489///
490/// Targets in the gate: `[control0, control1, target]`.
491pub struct Toffoli;
492
493impl QuantumGate for Toffoli {
494    fn matrix(&self) -> Array2<Complex<f64>> {
495        let o = cr(0.0);
496        let i = cr(1.0);
497        // 8×8 matrix in basis |000⟩...|111⟩
498        // Only rows/cols 6 and 7 are swapped (control0=1, control1=1 → flip target)
499        let mut m = Array2::<Complex<f64>>::from_elem((8, 8), o);
500        for k in 0..6usize {
501            m[[k, k]] = i;
502        }
503        m[[6, 7]] = i;
504        m[[7, 6]] = i;
505        m
506    }
507    fn n_qubits(&self) -> usize {
508        3
509    }
510    fn name(&self) -> &str {
511        "Toffoli"
512    }
513}
514
515/// Fredkin (CSWAP) gate: swaps target qubits when control qubit is |1⟩.
516///
517/// Targets in the gate: `[control, target0, target1]`.
518pub struct Fredkin;
519
520impl QuantumGate for Fredkin {
521    fn matrix(&self) -> Array2<Complex<f64>> {
522        let o = cr(0.0);
523        let i = cr(1.0);
524        // 8×8 in basis |000⟩…|111⟩
525        // Rows/cols 5 and 6 are swapped (control=1 → swap the two targets)
526        let mut m = Array2::<Complex<f64>>::from_elem((8, 8), o);
527        for k in 0..8usize {
528            m[[k, k]] = i;
529        }
530        m[[5, 5]] = o;
531        m[[6, 6]] = o;
532        m[[5, 6]] = i;
533        m[[6, 5]] = i;
534        m
535    }
536    fn n_qubits(&self) -> usize {
537        3
538    }
539    fn name(&self) -> &str {
540        "Fredkin"
541    }
542}
543
544// ─────────────────────────────────────────────────────────────────────────────
545// Gate application
546// ─────────────────────────────────────────────────────────────────────────────
547
548/// Apply `gate` to the qubits at positions `target_qubits` in `state`.
549///
550/// The gate's arity must equal `target_qubits.len()`.  All qubit indices must
551/// be distinct and within range.
552///
553/// The statevector is updated in place.
554pub fn apply_gate(
555    state: &mut QubitRegister,
556    gate: &dyn QuantumGate,
557    target_qubits: &[usize],
558) -> QuantumResult<()> {
559    let gate_qubits = gate.n_qubits();
560    if target_qubits.len() != gate_qubits {
561        return Err(QuantumError::GateArityMismatch {
562            gate_qubits,
563            supplied: target_qubits.len(),
564        });
565    }
566
567    // Range check.
568    for &q in target_qubits {
569        if q >= state.n_qubits() {
570            return Err(QuantumError::QubitIndexOutOfRange {
571                index: q,
572                n_qubits: state.n_qubits(),
573            });
574        }
575    }
576
577    // Duplicate check.
578    for i in 0..target_qubits.len() {
579        for j in (i + 1)..target_qubits.len() {
580            if target_qubits[i] == target_qubits[j] {
581                return Err(QuantumError::DuplicateQubitIndex {
582                    index: target_qubits[i],
583                });
584            }
585        }
586    }
587
588    let gate_mat = gate.matrix();
589    let gate_dim = 1usize << gate_qubits;
590    let total_qubits = state.n_qubits();
591    let total_dim = state.dim();
592
593    // For each group of 2^(gate_qubits) basis states that differ only in the
594    // target qubit positions, compute the matrix-vector product.
595    let mut new_amps = state.amplitudes.clone();
596
597    // Iterate over all combinations of the non-target qubit values.
598    let non_target_dim = total_dim / gate_dim;
599
600    for outer in 0..non_target_dim {
601        // Build the full amplitude vector for this sub-space slice.
602        let mut sub_amps = vec![Complex::new(0.0, 0.0); gate_dim];
603
604        // Map gate-space index → full-state index.
605        let indices: Vec<usize> = (0..gate_dim)
606            .map(|g| gate_idx_to_full_idx(g, outer, target_qubits, total_qubits))
607            .collect();
608
609        for (g, &full_idx) in indices.iter().enumerate() {
610            sub_amps[g] = state.amplitudes[full_idx];
611        }
612
613        // Multiply by the gate matrix.
614        let mut result = vec![Complex::new(0.0, 0.0); gate_dim];
615        for row in 0..gate_dim {
616            for col in 0..gate_dim {
617                result[row] += gate_mat[[row, col]] * sub_amps[col];
618            }
619        }
620
621        // Write back.
622        for (g, &full_idx) in indices.iter().enumerate() {
623            new_amps[full_idx] = result[g];
624        }
625    }
626
627    state.amplitudes = new_amps;
628    Ok(())
629}
630
631/// Convert a gate-space index and an "outer" index into the full statevector
632/// index, placing the gate bits at the positions indicated by `target_qubits`.
633///
634/// `total_qubits` is the total number of qubits in the register.
635fn gate_idx_to_full_idx(
636    gate_idx: usize,
637    outer: usize,
638    target_qubits: &[usize],
639    total_qubits: usize,
640) -> usize {
641    // We need to interleave the `gate_qubits` gate-index bits into the
642    // `total_qubits`-bit full index, at the positions given by target_qubits.
643    //
644    // Algorithm:
645    //   1. Start with the `outer` non-target bits spread across the non-target
646    //      positions.
647    //   2. Insert the gate-index bits at the target positions.
648
649    let gate_qubits = target_qubits.len();
650    let mut full = 0usize;
651
652    // Collect target positions as a sorted set for easy "is this bit a target?"
653    // lookups.
654    let mut target_set = [usize::MAX; 64];
655    for (i, &t) in target_qubits.iter().enumerate() {
656        target_set[i] = t;
657    }
658
659    // outer_bit_pos iterates through the non-target qubit positions (0..total_qubits
660    // minus targets) from LSB to MSB.
661    let mut outer_idx = 0usize;
662
663    for bit_pos in 0..total_qubits {
664        // Is this bit position a target?
665        let mut target_local = usize::MAX;
666        for i in 0..gate_qubits {
667            if target_set[i] == bit_pos {
668                target_local = i;
669                break;
670            }
671        }
672        if target_local != usize::MAX {
673            // Extract the corresponding gate-index bit.
674            // The gate matrix is indexed in big-endian order: the first qubit in
675            // target_qubits corresponds to the MSB of gate_idx.
676            let gate_bit = (gate_idx >> (gate_qubits - 1 - target_local)) & 1;
677            full |= gate_bit << bit_pos;
678        } else {
679            // Extract the corresponding outer bit.
680            let outer_bit = (outer >> outer_idx) & 1;
681            full |= outer_bit << bit_pos;
682            outer_idx += 1;
683        }
684    }
685
686    full
687}
688
689// ─────────────────────────────────────────────────────────────────────────────
690// Gate matrix utilities
691// ─────────────────────────────────────────────────────────────────────────────
692
693/// Compute the tensor product of two gate matrices: U₁ ⊗ U₂.
694///
695/// Produces a (2^(n₁+n₂)) × (2^(n₁+n₂)) matrix.
696pub fn tensor_product_matrices(
697    u1: &Array2<Complex<f64>>,
698    u2: &Array2<Complex<f64>>,
699) -> Array2<Complex<f64>> {
700    let (r1, c1) = (u1.nrows(), u1.ncols());
701    let (r2, c2) = (u2.nrows(), u2.ncols());
702    let rows = r1 * r2;
703    let cols = c1 * c2;
704    let mut result = Array2::zeros((rows, cols));
705    for i in 0..r1 {
706        for j in 0..c1 {
707            for k in 0..r2 {
708                for l in 0..c2 {
709                    result[[i * r2 + k, j * c2 + l]] = u1[[i, j]] * u2[[k, l]];
710                }
711            }
712        }
713    }
714    result
715}
716
717/// Verify that a matrix U is unitary: check that U†U ≈ I within `tol`.
718///
719/// Returns `Ok(())` if unitary, or `Err(QuantumError::NonUnitaryGate)` otherwise.
720pub fn check_unitary(u: &Array2<Complex<f64>>, tol: f64) -> QuantumResult<()> {
721    let n = u.nrows();
722    if u.ncols() != n {
723        return Err(QuantumError::DimensionMismatch {
724            expected: n,
725            actual: u.ncols(),
726        });
727    }
728    let mut max_dev: f64 = 0.0;
729    for i in 0..n {
730        for j in 0..n {
731            // (U†U)_{ij} = Σ_k conj(U_{ki}) * U_{kj}
732            let val: Complex<f64> = (0..n).map(|k| u[[k, i]].conj() * u[[k, j]]).sum();
733            let expected = if i == j {
734                Complex::new(1.0, 0.0)
735            } else {
736                Complex::new(0.0, 0.0)
737            };
738            let dev = (val - expected).norm();
739            if dev > max_dev {
740                max_dev = dev;
741            }
742        }
743    }
744    if max_dev > tol {
745        return Err(QuantumError::NonUnitaryGate { deviation: max_dev });
746    }
747    Ok(())
748}
749
750/// Compute the matrix product of two square matrices of the same dimension.
751pub fn matrix_product(
752    a: &Array2<Complex<f64>>,
753    b: &Array2<Complex<f64>>,
754) -> QuantumResult<Array2<Complex<f64>>> {
755    let n = a.nrows();
756    if a.ncols() != n || b.nrows() != n || b.ncols() != n {
757        return Err(QuantumError::DimensionMismatch {
758            expected: n,
759            actual: b.nrows(),
760        });
761    }
762    let mut result = Array2::zeros((n, n));
763    for i in 0..n {
764        for j in 0..n {
765            let val: Complex<f64> = (0..n).map(|k| a[[i, k]] * b[[k, j]]).sum();
766            result[[i, j]] = val;
767        }
768    }
769    Ok(result)
770}
771
772// ─────────────────────────────────────────────────────────────────────────────
773// Tests
774// ─────────────────────────────────────────────────────────────────────────────
775
776#[cfg(test)]
777mod tests {
778    use super::super::qubits::QubitRegister;
779    use super::*;
780
781    const TOL: f64 = 1e-12;
782
783    fn assert_complex_close(a: Complex<f64>, b: Complex<f64>, tol: f64, msg: &str) {
784        assert!(
785            (a - b).norm() < tol,
786            "{}: expected {:?}, got {:?}",
787            msg,
788            b,
789            a
790        );
791    }
792
793    #[test]
794    fn test_pauli_x_unitary() {
795        check_unitary(&PauliX.matrix(), 1e-12).expect("PauliX should be unitary");
796    }
797
798    #[test]
799    fn test_hadamard_unitary() {
800        check_unitary(&Hadamard.matrix(), 1e-12).expect("H should be unitary");
801    }
802
803    #[test]
804    fn test_cnot_unitary() {
805        check_unitary(&CNOT.matrix(), 1e-12).expect("CNOT should be unitary");
806    }
807
808    #[test]
809    fn test_toffoli_unitary() {
810        check_unitary(&Toffoli.matrix(), 1e-12).expect("Toffoli should be unitary");
811    }
812
813    #[test]
814    fn test_x_flips_zero() {
815        let mut reg = QubitRegister::new_zero_state(1).expect("valid");
816        apply_gate(&mut reg, &PauliX, &[0]).expect("apply ok");
817        assert!((reg.probability(1).expect("ok") - 1.0).abs() < TOL);
818    }
819
820    #[test]
821    fn test_x_flips_one() {
822        let mut reg = QubitRegister::new_basis_state(1, 1).expect("valid");
823        apply_gate(&mut reg, &PauliX, &[0]).expect("apply ok");
824        assert!((reg.probability(0).expect("ok") - 1.0).abs() < TOL);
825    }
826
827    #[test]
828    fn test_hadamard_superposition() {
829        let mut reg = QubitRegister::new_zero_state(1).expect("valid");
830        apply_gate(&mut reg, &Hadamard, &[0]).expect("apply ok");
831        let p0 = reg.probability(0).expect("ok");
832        let p1 = reg.probability(1).expect("ok");
833        assert!((p0 - 0.5).abs() < TOL);
834        assert!((p1 - 0.5).abs() < TOL);
835    }
836
837    #[test]
838    fn test_cnot_creates_bell_state() {
839        let mut reg = QubitRegister::new_zero_state(2).expect("valid");
840        apply_gate(&mut reg, &Hadamard, &[0]).expect("H ok");
841        apply_gate(&mut reg, &CNOT, &[0, 1]).expect("CNOT ok");
842        // Bell state |Φ+⟩ = (|00⟩ + |11⟩)/√2
843        let p00 = reg.probability(0).expect("ok");
844        let p11 = reg.probability(3).expect("ok");
845        let p01 = reg.probability(1).expect("ok");
846        let p10 = reg.probability(2).expect("ok");
847        assert!((p00 - 0.5).abs() < TOL, "p00={}", p00);
848        assert!((p11 - 0.5).abs() < TOL, "p11={}", p11);
849        assert!(p01.abs() < TOL, "p01={}", p01);
850        assert!(p10.abs() < TOL, "p10={}", p10);
851    }
852
853    #[test]
854    fn test_z_phase_flip() {
855        // Apply Z to |+⟩ = (|0⟩+|1⟩)/√2 → |−⟩ = (|0⟩−|1⟩)/√2
856        let mut reg = QubitRegister::new_zero_state(1).expect("valid");
857        apply_gate(&mut reg, &Hadamard, &[0]).expect("H ok");
858        apply_gate(&mut reg, &PauliZ, &[0]).expect("Z ok");
859        // Amplitude for |1⟩ should now be negative.
860        let amp1 = reg.amplitude(1).expect("ok");
861        assert!(amp1.re < 0.0);
862    }
863
864    #[test]
865    fn test_duplicate_qubit_error() {
866        let mut reg = QubitRegister::new_zero_state(2).expect("valid");
867        let err = apply_gate(&mut reg, &CNOT, &[0, 0]);
868        assert!(matches!(err, Err(QuantumError::DuplicateQubitIndex { .. })));
869    }
870
871    #[test]
872    fn test_arity_error() {
873        let mut reg = QubitRegister::new_zero_state(2).expect("valid");
874        let err = apply_gate(&mut reg, &PauliX, &[0, 1]);
875        assert!(matches!(err, Err(QuantumError::GateArityMismatch { .. })));
876    }
877
878    #[test]
879    fn test_swap_swaps_qubits() {
880        // |10⟩ → |01⟩ after SWAP
881        // index 2 = |10⟩ (qubit0=0, qubit1=1)
882        let mut reg = QubitRegister::new_basis_state(2, 2).expect("valid");
883        apply_gate(&mut reg, &SWAP, &[0, 1]).expect("SWAP ok");
884        // After swap: index 1 = |01⟩ (qubit0=1, qubit1=0)
885        let p1 = reg.probability(1).expect("ok");
886        assert!(
887            (p1 - 1.0).abs() < TOL,
888            "SWAP should move |10⟩ to |01⟩, got p1={}",
889            p1
890        );
891    }
892
893    #[test]
894    fn test_rot_x_pi_equals_x() {
895        let rx_pi = RotX { theta: PI };
896        let mx = rx_pi.matrix();
897        // Up to global phase, Rx(π) = −iX
898        // |Rx(π)[0,1]| = 1.0
899        assert!((mx[[0, 1]].norm() - 1.0).abs() < 1e-10);
900        assert!((mx[[1, 0]].norm() - 1.0).abs() < 1e-10);
901    }
902
903    #[test]
904    fn test_toffoli_flips_when_both_controls_set() {
905        // |110⟩ → |111⟩
906        // Toffoli(control0, control1, target) with target_qubits = [0, 1, 2]:
907        //   control0 = qubit 0, control1 = qubit 1, target = qubit 2
908        // Start with index 3 = |011⟩: qubit0=1, qubit1=1, qubit2=0 (both controls set).
909        // Toffoli flips qubit2: index 3 -> index 7 = |111⟩
910        let mut reg = QubitRegister::new_basis_state(3, 3).expect("valid");
911        apply_gate(&mut reg, &Toffoli, &[0, 1, 2]).expect("ok");
912        let p7 = reg.probability(7).expect("ok");
913        assert!((p7 - 1.0).abs() < TOL, "p7={}", p7);
914    }
915}