1use crate::math::{c64, int_to_state, real_arr_to_complex};
29use ndarray::linalg;
30use ndarray::{array, Array2};
31use std::{f64::consts, vec};
32
33pub trait QuantumOperation {
35 fn matrix(&self) -> Array2<c64>;
36 fn targets(&self) -> Vec<usize>;
37 fn arity(&self) -> usize;
38}
39
40#[derive(Clone, Debug)]
49pub struct Operation {
50 matrix: Array2<c64>,
51 targets: Vec<usize>,
52}
53
54impl Operation {
55 pub fn new(matrix: Array2<c64>, targets: Vec<usize>) -> Option<Operation> {
61 let shape = matrix.shape();
62 let len = targets.len();
63
64 if shape[0] != 2_usize.pow(len as u32) || shape[1] != 2_usize.pow(len as u32) {
65 return None;
66 }
67
68 Some(Operation { matrix, targets })
69 }
70}
71
72impl QuantumOperation for Operation {
74 fn matrix(&self) -> Array2<c64> {
75 self.matrix.clone()
76 }
77
78 fn targets(&self) -> Vec<usize> {
79 self.targets.to_vec()
80 }
81
82 fn arity(&self) -> usize {
83 self.targets().len()
84 }
85}
86
87pub fn identity(target: usize) -> Operation {
89 Operation {
90 matrix: real_arr_to_complex(array![[1.0, 0.0], [0.0, 1.0]]),
91 targets: vec![target],
92 }
93}
94
95pub fn hadamard(target: usize) -> Operation {
99 Operation {
100 matrix: real_arr_to_complex(consts::FRAC_1_SQRT_2 * array![[1.0, 1.0], [1.0, -1.0]]),
101 targets: vec![target],
102 }
103}
104
105pub fn hadamard_transform(targets: Vec<usize>) -> Operation {
107 let mut matrix = hadamard(targets[0]).matrix();
108 let len = targets.len();
109
110 for t in targets.iter().take(len).skip(1) {
111 matrix = linalg::kron(&hadamard(*t).matrix(), &matrix);
112 }
113 Operation { matrix, targets }
114}
115
116pub fn cnot(control: usize, target: usize) -> Operation {
121 Operation {
122 matrix: real_arr_to_complex(array![
123 [1.0, 0.0, 0.0, 0.0],
124 [0.0, 1.0, 0.0, 0.0],
125 [0.0, 0.0, 0.0, 1.0],
126 [0.0, 0.0, 1.0, 0.0]
127 ]),
128 targets: vec![target, control],
129 }
130}
131
132pub fn to_quantum_gate(f: &dyn Fn(usize) -> usize, targets: Vec<usize>) -> Operation {
135 let t_len = targets.len();
136 let len: usize = 1 << t_len;
137 let mut matrix: Array2<c64> = Array2::zeros((len, len));
138 for c in 0..len {
140 let val = f(c);
141 let res_state = int_to_state(val, len);
142
143 for r in 0..len {
145 matrix[(r, c)] = res_state[(r, 0)];
146 }
147 }
148 Operation { matrix, targets }
149}
150
151pub fn to_controlled(op: Operation, control: usize) -> Operation {
155 let old_sz = 1 << op.arity();
156 let mut matrix = Array2::zeros((2 * old_sz, 2 * old_sz));
157 for i in 0..old_sz {
158 matrix[(i, i)] = c64::new(1.0, 0.0);
159 }
160 for i in 0..old_sz {
161 for j in 0..old_sz {
162 matrix[(i + old_sz, j + old_sz)] = op.matrix[(i, j)];
163 }
164 }
165 let mut targets = op.targets();
166
167 targets.push(control);
169 Operation { matrix, targets }
170}
171
172pub fn swap(target_1: usize, target_2: usize) -> Operation {
176 Operation {
177 matrix: real_arr_to_complex(array![
178 [1.0, 0.0, 0.0, 0.0],
179 [0.0, 0.0, 1.0, 0.0],
180 [0.0, 1.0, 0.0, 0.0],
181 [0.0, 0.0, 0.0, 1.0]
182 ]),
183 targets: vec![target_1, target_2],
184 }
185}
186
187pub fn phase(target: usize) -> Operation {
192 Operation {
193 matrix: array![
194 [c64::new(1.0, 0.0), c64::new(0.0, 0.0)],
195 [c64::new(0.0, 0.0), c64::new(0.0, 1.0)]
196 ],
197 targets: vec![target],
198 }
199}
200
201pub fn not(target: usize) -> Operation {
207 Operation {
208 matrix: real_arr_to_complex(array![[0.0, 1.0], [1.0, 0.0]]),
209 targets: vec![target],
210 }
211}
212
213pub fn pauli_y(target: usize) -> Operation {
217 Operation {
218 matrix: array![
219 [c64::new(0.0, 0.0), c64::new(0.0, -1.0)],
220 [c64::new(0.0, 1.0), c64::new(0.0, 0.0)]
221 ],
222 targets: vec![target],
223 }
224}
225
226pub fn pauli_z(target: usize) -> Operation {
230 Operation {
231 matrix: real_arr_to_complex(array![[1.0, 0.0], [0.0, -1.0]]),
232 targets: vec![target],
233 }
234}
235
236pub fn cnx(controls: &[usize], target: usize) -> Operation {
240 let mut targets = vec![target];
241 targets.append(&mut controls.to_owned());
242
243 let n: usize = 2_usize.pow(targets.len() as u32);
245
246 let mut matrix: Array2<f64> = Array2::<f64>::zeros((n, n));
248 for i in 0..n - 2 {
249 matrix.row_mut(i)[i] = 1.0;
251 }
252
253 matrix.row_mut(n - 1)[n - 2] = 1.0;
255 matrix.row_mut(n - 2)[n - 1] = 1.0;
256
257 Operation {
258 matrix: real_arr_to_complex(matrix),
259 targets,
260 }
261}
262
263pub fn cnz(controls: &[usize], target: usize) -> Operation {
269 let mut targets = vec![target];
270 targets.append(&mut controls.to_owned());
271
272 let n: usize = 2_usize.pow(targets.len() as u32);
273
274 let mut matrix: Array2<f64> = Array2::<f64>::zeros((n, n));
275 for i in 0..n - 1 {
276 matrix.row_mut(i)[i] = 1.0;
277 }
278 matrix.row_mut(n - 1)[n - 1] = -1.0;
279
280 Operation {
281 matrix: real_arr_to_complex(matrix),
282 targets,
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::{
289 cnot, cnx, hadamard, identity, not, pauli_y, pauli_z, phase, swap, QuantumOperation,
290 };
291 use crate::math::c64;
292 use ndarray::Array2;
293
294 fn all_ops() -> Vec<Box<dyn QuantumOperation>> {
295 vec![
296 Box::new(identity(0)),
297 Box::new(hadamard(0)),
298 Box::new(cnot(0, 1)),
299 Box::new(swap(0, 1)),
300 Box::new(phase(0)),
301 Box::new(not(0)),
302 Box::new(pauli_y(0)),
303 Box::new(pauli_z(0)),
304 Box::new(cnx(&[0], 1)),
305 Box::new(cnx(&[0, 1], 2)),
306 Box::new(cnx(&[0, 1, 2], 3)),
307 Box::new(cnx(&[0, 1, 2, 3], 4)),
308 Box::new(cnx(&[0, 1, 2, 3, 4], 5)),
309 ]
310 }
311
312 #[test]
313 fn sz_matches() {
314 for op in all_ops() {
315 assert_eq!(op.matrix().dim().0, op.matrix().dim().1);
316 assert_eq!(op.matrix().dim().0, 1 << op.arity())
317 }
318 }
319
320 #[test]
321 fn unitary() {
322 for op in all_ops() {
324 let conj_transpose = op.matrix().t().map(|e| e.conj());
325 assert!(matrix_is_equal(
326 op.matrix().dot(&conj_transpose),
327 Array2::eye(op.matrix().dim().0),
328 1e-8
329 ))
330 }
331 }
332
333 #[test]
334 fn toffoli2_equals_cnot() {
335 let toffoli_generated_cnot = cnx(&[0], 1);
336 assert!(matrix_is_equal(
337 toffoli_generated_cnot.matrix(),
338 cnot(0, 1).matrix(),
339 1e-8
340 ));
341 }
342
343 fn matrix_is_equal(a: Array2<c64>, b: Array2<c64>, tolerance: f64) -> bool {
344 (a - b).iter().all(|e| e.norm() < tolerance)
345 }
346}