quantum_sim/
gates.rs

1use ndarray::{Array2, arr2};
2use num_complex::Complex;
3use num_traits::{Float, FromPrimitive, One, Zero};
4
5/// Represents a quantum gate with a name and unitary matrix representation.
6///
7/// # Type Parameters
8/// - `T`: Floating-point type for the matrix elements to generate `Complex<T>`
9///
10/// # Fields
11/// - `name`: String identifier for the gate
12/// - `matrix`: Unitary matrix representing the gate operation (2D array of complex numbers)
13///
14/// # Examples
15/// ```
16/// use quantum_sim::gates::Gate;
17/// use num_complex::Complex;
18/// use num_traits::{One, Zero};
19/// use ndarray::arr2;
20///
21/// let gate = Gate::<f64>::new("Custom".to_string(), arr2(&[[Complex::one(), Complex::zero()],
22///                                                  [Complex::zero(), Complex::one()]]));
23/// ```
24#[derive(Debug, Clone)]
25pub struct Gate<T> {
26    pub name: String,
27    pub matrix: Array2<Complex<T>>,
28}
29
30impl<T> Gate<T>
31where
32    T: Float + FromPrimitive + Copy + PartialOrd + FromPrimitive + std::fmt::LowerExp + 'static,
33{
34    /// Creates a new quantum gate with the given name and matrix.
35    ///
36    /// # Arguments
37    /// * `name` - String identifier for the gate
38    /// * `matrix` - Unitary matrix representing the gate operation
39    ///
40    /// # Panics
41    /// The caller must ensure the matrix is unitary. This is checked internally.
42    ///
43    /// # Examples
44    /// ```
45    /// use quantum_sim::gates::Gate;
46    /// use ndarray::arr2;
47    /// use num_complex::Complex;
48    /// use num_traits::{One, Zero};
49    ///
50    /// let matrix = arr2(&[[Complex::one(), Complex::zero()],
51    ///                     [Complex::zero(), Complex::one()]]);
52    /// let identity = Gate::<f64>::new("Identity".to_string(), matrix);
53    /// ```
54    pub fn new(name: String, matrix: Array2<Complex<T>>) -> Result<Self, String> {
55        let rows = matrix.shape()[0];
56        let cols = matrix.shape()[1];
57
58        // 1. Check if the matrix is square
59        if rows != cols {
60            return Err(format!(
61                "Matrix for gate '{}' must be square, but has dimensions {}x{}.",
62                name, rows, cols
63            ));
64        }
65        // 2. Check if the matrix is unitary
66        if !is_unitary(&matrix) {
67            Err(format!(
68                "Matrix for gate '{}' is not unitary. Max difference from identity:",
69                name
70            ))
71        } else {
72            Ok(Self { name, matrix })
73        }
74    }
75
76    /// Creates an Identity gate (I).
77    ///
78    /// # Examples
79    /// ```
80    /// use quantum_sim::gates::Gate;
81    ///
82    /// let i_gate = Gate::<f64>::i();
83    /// ```
84    pub fn i() -> Self {
85        Self::new(
86            "I".to_string(),
87            arr2(&[
88                [Complex::one(), Complex::zero()],
89                [Complex::zero(), Complex::one()],
90            ]),
91        )
92        .unwrap()
93    }
94
95    /// Creates a Pauli-X gate (NOT gate).
96    /// # Examples
97    /// ```
98    /// use quantum_sim::gates::Gate;
99    ///
100    /// let x_gate = Gate::<f64>::x();
101    /// assert_eq!(x_gate.name, "X");
102    /// ```
103    pub fn x() -> Self {
104        Self::new(
105            "X".to_string(),
106            arr2(&[
107                [Complex::zero(), Complex::one()],
108                [Complex::one(), Complex::zero()],
109            ]),
110        )
111        .unwrap()
112    }
113
114    /// Creates a Pauli-Y gate.
115    /// # Examples
116    /// ```
117    /// use quantum_sim::gates::Gate;
118    ///
119    /// let y_gate = Gate::<f64>::y();
120    /// assert_eq!(y_gate.name, "Y");
121    /// ```
122    pub fn y() -> Self {
123        Self::new(
124            "Y".to_string(),
125            arr2(&[
126                [Complex::zero(), -Complex::i()],
127                [Complex::i(), Complex::zero()],
128            ]),
129        )
130        .unwrap()
131    }
132
133    /// Creates a Pauli-Z gate.
134    /// # Examples
135    /// ```
136    /// use quantum_sim::gates::Gate;
137    ///
138    /// let z_gate = Gate::<f64>::z();
139    /// assert_eq!(z_gate.name, "Z");
140    /// ```
141    pub fn z() -> Self {
142        Self::new(
143            "Z".to_string(),
144            arr2(&[
145                [Complex::one(), Complex::zero()],
146                [Complex::zero(), -Complex::one()],
147            ]),
148        )
149        .unwrap()
150    }
151
152    /// Creates a Hadamard gate (H).
153    ///
154    /// The Hadamard gate creates superposition states.
155    /// Matrix representation:
156    /// ```text
157    /// [1/sqrt(2)  1/sqrt(2)]
158    /// [1/sqrt(2) -1/sqrt(2)]
159    /// ```
160    ///
161    /// # Examples
162    /// ```
163    /// use quantum_sim::gates::Gate;
164    ///
165    /// let h_gate = Gate::<f64>::h();
166    /// assert_eq!(h_gate.name, "H");
167    /// ```
168    pub fn h() -> Self {
169        let factor = T::one() / T::from(2.0).unwrap().sqrt();
170        Self::new(
171            "H".to_string(),
172            arr2(&[
173                [Complex::one() * factor, Complex::one() * factor],
174                [Complex::one() * factor, -Complex::one() * factor],
175            ]),
176        )
177        .unwrap()
178    }
179
180    /// Creates a Phase gate (S gate).
181    ///
182    /// The Phase gate introduces a π/2 phase shift.
183    /// Matrix representation:
184    /// ```text
185    /// [1 0]
186    /// [0 i]
187    /// ```
188    ///
189    /// # Examples
190    /// ```
191    /// use quantum_sim::gates::Gate;
192    ///
193    /// let s_gate = Gate::<f64>::s();
194    /// assert_eq!(s_gate.name, "S");
195    /// ```
196    pub fn s() -> Self {
197        Self::new(
198            "S".to_string(),
199            arr2(&[
200                [Complex::one(), Complex::zero()],
201                [Complex::zero(), Complex::i()],
202            ]),
203        )
204        .unwrap()
205    }
206
207    /// Creates a T gate (π/8 gate).
208    ///
209    /// The T gate introduces a π/4 phase shift.
210    /// Matrix representation:
211    /// ```text
212    /// [1 0]
213    /// [0 e^(iπ/4)]
214    /// ```
215    ///
216    /// # Examples
217    /// ```
218    /// use quantum_sim::gates::Gate;
219    ///
220    /// let t_gate = Gate::<f64>::t();
221    /// assert_eq!(t_gate.name, "T");
222    /// ```
223    pub fn t() -> Self {
224        let pi = T::from(std::f64::consts::PI).unwrap();
225        let angle = pi / T::from(4.0).unwrap();
226        Self::new(
227            "T".to_string(),
228            arr2(&[
229                [Complex::one(), Complex::zero()],
230                [Complex::zero(), Complex::new(angle.cos(), angle.sin())],
231            ]),
232        )
233        .unwrap()
234    }
235
236    /// Creates a Controlled-NOT gate (CNOT).
237    ///
238    /// The CNOT gate flips the target qubit if the control qubit is |1⟩.
239    /// Matrix representation:
240    /// ```text
241    /// [1 0 0 0]
242    /// [0 1 0 0]
243    /// [0 0 0 1]
244    /// [0 0 1 0]
245    /// ```
246    ///
247    /// # Examples
248    /// ```
249    /// use quantum_sim::gates::Gate;
250    ///
251    /// let cnot_gate = Gate::<f64>::cnot();
252    /// assert_eq!(cnot_gate.name, "CNOT");
253    /// ```
254    pub fn cnot() -> Self {
255        Self::new(
256            "CNOT".to_string(),
257            arr2(&[
258                [
259                    Complex::one(),
260                    Complex::zero(),
261                    Complex::zero(),
262                    Complex::zero(),
263                ],
264                [
265                    Complex::zero(),
266                    Complex::one(),
267                    Complex::zero(),
268                    Complex::zero(),
269                ],
270                [
271                    Complex::zero(),
272                    Complex::zero(),
273                    Complex::zero(),
274                    Complex::one(),
275                ],
276                [
277                    Complex::zero(),
278                    Complex::zero(),
279                    Complex::one(),
280                    Complex::zero(),
281                ],
282            ]),
283        )
284        .unwrap()
285    }
286}
287
288fn is_unitary<T>(matrix: &Array2<Complex<T>>) -> bool
289where
290    T: Float + 'static,
291{
292    let product = matrix.dot(&matrix.t().mapv(|c| c.conj()));
293    let identity = Array2::<Complex<T>>::eye(matrix.shape()[0]);
294    let diff = &product - &identity;
295    let max_diff_norm = diff.iter().map(|c| c.norm()).fold(T::zero(), T::max);
296    let epsilon = T::from(1e-6).unwrap(); // Use a small epsilon for floating point comparison
297    max_diff_norm <= epsilon
298}