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}