Skip to main content

qudit_expr/library/
gates.rs

1use crate::UnitaryExpression;
2use crate::UnitarySystemExpression;
3#[cfg(feature = "python")]
4use pyo3_stub_gen::derive::gen_stub_pyfunction;
5use qudit_core::QuditSystem;
6use qudit_core::Radices;
7
8/// The identity or no-op gate.
9#[cfg_attr(
10    feature = "python",
11    gen_stub_pyfunction(module = "openqudit.expressions")
12)]
13#[cfg_attr(feature = "python", pyo3::pyfunction)]
14#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
15pub fn IGate(radix: usize) -> UnitaryExpression {
16    let proto = format!("I<{}>()", radix);
17    let mut body = "".to_string();
18    body += "[";
19    for i in 0..radix {
20        body += "[";
21        for j in 0..radix {
22            if i == j {
23                body += "1,";
24            } else {
25                body += "0,";
26            }
27        }
28        body += "],";
29    }
30    body += "]";
31
32    UnitaryExpression::new(proto + "{" + &body + "}")
33}
34
35/// The one-qudit Hadamard gate. This is a Clifford/Weyl-Heisenberg gate.
36///
37/// The qubit (radix = 2) Hadamard gate is given by the following matrix:
38///
39/// $$
40/// \begin{pmatrix}
41///     \frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2} \\\\
42///     \frac{\sqrt{2}}{2} & -\frac{\sqrt{2}}{2} \\\\
43/// \end{pmatrix}
44/// $$
45///
46/// However, generally it is given by the following formula:
47///
48/// $$
49/// H = \frac{1}{\sqrt{d}} \sum_{ij} \omega^{ij} \ket{i}\bra{j}
50/// $$
51///
52/// where
53///
54/// $$
55/// \omega = \exp\Big(\frac{2\pi i}{d}\Big)
56/// $$
57///
58/// and $d$ is the number of levels (2 levels is a qubit, 3 levels is a qutrit,
59/// etc.)
60///
61/// References:
62/// - <https://www.frontiersin.org/articles/10.3389/fphy.2020.589504/full>
63/// - <https://pubs.aip.org/aip/jmp/article-abstract/56/3/032202/763827>
64/// - <https://arxiv.org/pdf/1701.07902.pdf>
65#[cfg_attr(
66    feature = "python",
67    gen_stub_pyfunction(module = "openqudit.expressions")
68)]
69#[cfg_attr(feature = "python", pyo3::pyfunction)]
70#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
71pub fn HGate(radix: usize) -> UnitaryExpression {
72    let proto = format!("H<{}>()", radix);
73    let mut body = "".to_string();
74    if radix == 2 {
75        body += "[[1/sqrt(2), 1/sqrt(2)], [1/sqrt(2), ~1/sqrt(2)]]";
76        return UnitaryExpression::new(proto + "{" + &body + "}");
77    }
78    let omega = format!("e^(2*π*i/{})", radix);
79    let invsqrt = format!("1/sqrt({})", radix);
80    body += invsqrt.as_str();
81    body += " * ";
82    body += "[";
83    for i in 0..radix {
84        body += "[";
85        for j in 0..radix {
86            body += &format!("{}^({}*{}), ", omega, i, j);
87        }
88        body += "],";
89    }
90    body += "]";
91
92    UnitaryExpression::new(proto + "{" + &body + "}")
93}
94
95/// The qudit swap gate. This is a two-qudit Clifford/Weyl-Heisenberg gate
96/// that swaps the state of two qudits.
97///
98/// The qubit (radix = 2) version is given by the following matrix:
99///
100/// $$
101/// \begin{pmatrix}
102///     1 & 0 & 0 & 0 \\\\
103///     0 & 0 & 1 & 0 \\\\
104///     0 & 1 & 0 & 0 \\\\
105///     0 & 0 & 0 & 1 \\\\
106/// \end{pmatrix}
107/// $$
108///
109/// The qutrit (radix = 3) version is given by the following matrix:
110///
111/// $$
112/// \begin{pmatrix}
113///     1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\
114///     0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\
115///     0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\\\
116///     0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\
117///     0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\
118///     0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\
119///     0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\
120///     0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\
121///     0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\\\
122/// \end{pmatrix}
123/// $$
124///
125/// However, generally it is given by the following formula:
126///
127/// $$
128/// SWAP_d = \sum_{a, b} \ket{ab}\bra{ba}
129/// $$
130///
131/// where $d$ is the number of levels (2 levels is a qubit, 3 levels is a
132/// qutrit, etc.)
133///
134/// References:
135/// - <https://link.springer.com/article/10.1007/s11128-013-0621-x>
136/// - <https://arxiv.org/pdf/1105.5485.pdf>
137#[cfg_attr(
138    feature = "python",
139    gen_stub_pyfunction(module = "openqudit.expressions")
140)]
141#[cfg_attr(feature = "python", pyo3::pyfunction)]
142#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
143pub fn SwapGate(radix: usize) -> UnitaryExpression {
144    let proto = format!("Swap<{}, {}>()", radix, radix);
145    let dim = radix * radix;
146    let mut body = "".to_string();
147    body += "[";
148    for i in 0..dim {
149        body += "[";
150        let a_i = i / radix;
151        let b_i = i % radix;
152        for j in 0..dim {
153            let a_j = j / radix;
154            let b_j = j % radix;
155            if a_i == b_j && b_i == a_j {
156                body += "1,";
157            } else {
158                body += "0,";
159            }
160        }
161        body += "],";
162    }
163    body += "]";
164
165    UnitaryExpression::new(proto + "{" + &body + "}")
166}
167
168/// The one-qudit shift (X) gate. This is a Weyl-Heisenberg gate.
169///
170/// This gate shifts the state of a qudit up by one level modulo. For
171/// example, the shift gate on a qubit is the Pauli-X gate. The shift
172/// gate on a qutrit is the following matrix:
173///
174/// $$
175/// \begin{pmatrix}
176///     0 & 0 & 1 \\\\
177///     1 & 0 & 0 \\\\
178///     0 & 1 & 0 \\\\
179/// \end{pmatrix}
180/// $$
181///
182/// The shift gate is generally given by the following formula:
183///
184/// $$
185/// \begin{equation}
186///     X = \sum_a |a + 1 mod d ><a|
187/// \end{equation}
188/// $$
189///
190/// where d is the number of levels (2 levels is a qubit, 3 levels is
191/// a qutrit, etc.)
192///
193/// References:
194///     - <https://arxiv.org/pdf/2302.07966.pdf>
195#[cfg_attr(
196    feature = "python",
197    gen_stub_pyfunction(module = "openqudit.expressions")
198)]
199#[cfg_attr(feature = "python", pyo3::pyfunction)]
200#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
201pub fn XGate(radix: usize) -> UnitaryExpression {
202    let proto = format!("X<{}>()", radix);
203    let mut body = "[".to_string();
204    for i in 0..radix {
205        body += "[";
206        for j in 0..radix {
207            if (j + 1) % radix == i {
208                body += "1, ";
209            } else {
210                body += "0, ";
211            }
212        }
213        body += "],";
214    }
215    body += "]";
216
217    UnitaryExpression::new(proto + "{" + &body + "}")
218}
219
220/// The one-qudit clock (Z) gate. This is a Weyl-Heisenberg gate.
221///
222/// This gate shifts the state of a qudit up by one level modulo. For
223/// example, the clock gate on a qubit is the Pauli-Z gate. The clock
224/// gate on a qutrit is the following matrix:
225///
226/// $$
227/// \begin{pmatrix}
228///     1 & 0 & 0 \\\\
229///     0 & e^{\frac{2\pi i}{3} & 0 \\\\
230///     0 & 0 & e^{\frac{4\pi i}{3} \\\\
231/// \end{pmatrix}
232/// $$
233///
234/// The clock gate is generally given by the following formula:
235///
236/// $$
237/// \begin{equation}
238///     X = \sum_a e^{\frac{2a\pi i}{d}|a><a|
239/// \end{equation}
240/// $$
241///
242/// where d is the number of levels (2 levels is a qubit, 3 levels is
243/// a qutrit, etc.)
244///
245/// References:
246///     - <https://arxiv.org/pdf/2302.07966.pdf>
247#[cfg_attr(
248    feature = "python",
249    gen_stub_pyfunction(module = "openqudit.expressions")
250)]
251#[cfg_attr(feature = "python", pyo3::pyfunction)]
252#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
253pub fn ZGate(radix: usize) -> UnitaryExpression {
254    let proto = format!("Z<{}>()", radix);
255    let mut body = "[".to_string();
256    for i in 0..radix {
257        body += "[";
258        for j in 0..radix {
259            if i == j {
260                if i == 0 {
261                    body += "1, ";
262                } else {
263                    body += &format!("e^((2*{}*π*i)/{}), ", j, radix);
264                }
265            } else {
266                body += "0, ";
267            }
268        }
269        body += "],";
270    }
271    body += "]";
272
273    UnitaryExpression::new(proto + "{" + &body + "}")
274}
275
276/// The one-qudit Y gate. This is a Weyl-Heisenberg gate.
277///
278/// Defined as:
279///
280/// $$
281/// Y = \omega^{(d-1)/2} X Z
282/// $$
283///
284/// where $\omega = e^{2\pi i / d}$, $X$ is the shift gate, and $Z$ is the
285/// clock gate. This choice of global phase is the natural qudit generalization
286/// of the Pauli Y gate: for $d = 2$ it reproduces
287///
288/// $$
289/// Y = \begin{pmatrix} 0 & -i \\\\ i & 0 \end{pmatrix}
290/// $$
291///
292/// In general the non-zero entry at row $(j+1 \bmod d)$, column $j$ is
293/// $\omega^{(d-1)/2} \cdot \omega^{j} = e^{2\pi i (2j + d - 1)/(2d)}$.
294#[cfg_attr(
295    feature = "python",
296    gen_stub_pyfunction(module = "openqudit.expressions")
297)]
298#[cfg_attr(feature = "python", pyo3::pyfunction)]
299#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
300pub fn YGate(radix: usize) -> UnitaryExpression {
301    let proto = format!("Y<{}>()", radix);
302    let mut body = "[".to_string();
303    for i in 0..radix {
304        body += "[";
305        for j in 0..radix {
306            if (j + 1) % radix == i {
307                // ω^((d-1)/2 + j) = e^(2πi*(2j + d - 1)/(2d))
308                body += &format!("e^(2*π*i*(2*{}+{}-1)/(2*{})), ", j, radix, radix);
309            } else {
310                body += "0, ";
311            }
312        }
313        body += "],";
314    }
315    body += "]";
316    UnitaryExpression::new(proto + "{" + &body + "}")
317}
318
319/// The one-qudit S gate (Clifford level 2).
320///
321/// Defined as:
322///
323/// $$
324/// S|k\rangle = e^{i\pi k^2 / d}|k\rangle
325/// $$
326///
327/// where $d$ is the number of levels. For qubits ($d = 2$) this reproduces
328/// the standard S gate $S = \text{diag}(1, i)$. Note that $S^2 = T^2\cdot T^2$
329/// and $T^2 = S$, i.e. the T gate defined below is the square root of S.
330///
331/// This gate is Clifford (level 2 of the Clifford hierarchy): conjugation
332/// by S maps every Pauli to another Pauli.
333///
334/// For qutrits ($d = 3$), up to the global-phase convention of Yeh & van de
335/// Wetering (ζ⁸ with ζ = e^{2πi/9}), this agrees with their Definition 3.
336///
337/// References:
338/// - Gheorghiu, V. (2014). Standard form of qudit stabilizer groups.
339///   Physics Letters A, 378(30–31), 2016–2021.
340/// - Yeh, L. & van de Wetering, J. (2023). Completeness of the ZH-calculus.
341#[cfg_attr(
342    feature = "python",
343    gen_stub_pyfunction(module = "openqudit.expressions")
344)]
345#[cfg_attr(feature = "python", pyo3::pyfunction)]
346#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
347pub fn SGate(radix: usize) -> UnitaryExpression {
348    let proto = format!("S<{}>()", radix);
349    let mut body = "[".to_string();
350    for i in 0..radix {
351        body += "[";
352        for j in 0..radix {
353            if i == j {
354                if i == 0 {
355                    body += "1, ";
356                } else {
357                    // e^(iπk²/d)
358                    body += &format!("e^(π*i*{}*{}/{}), ", i, i, radix);
359                }
360            } else {
361                body += "0, ";
362            }
363        }
364        body += "],";
365    }
366    body += "]";
367    UnitaryExpression::new(proto + "{" + &body + "}")
368}
369
370/// The one-qudit T gate (Clifford level 3).
371///
372/// Defined as the square root of the S gate:
373///
374/// $$
375/// T|k\rangle = e^{i\pi k^2 / (2d)}|k\rangle
376/// $$
377///
378/// so that $T^2 = S$. For qubits ($d = 2$) this reproduces the standard
379/// T gate $T = \text{diag}(1,\, e^{i\pi/4})$. For qutrits ($d = 3$),
380/// with ζ = e^{2πi/9}, this gives $T = \text{diag}(1, e^{i\pi/6}, e^{2i\pi/3})$;
381/// the Yeh–van de Wetering convention $T = \text{diag}(1, \zeta, \zeta^8)$
382/// (Definition 8 ibid.) differs by a global phase, and both are in level 3
383/// of the Clifford hierarchy.
384///
385/// The T gate is the standard resource gate for universal quantum computation
386/// beyond the Clifford group: it can be injected via magic states and its
387/// magic states are distillable.
388///
389/// References:
390/// - Howard, M. & Vala, J. (2012). Qudit versions of the qubit π/8 gate.
391///   Physical Review A, 86(2), 022316.
392/// - Cui, S. X., Gottesman, D. & Krishna, A. (2017). Diagonal gates in the
393///   Clifford hierarchy. Physical Review A, 95(1), 012329.
394#[cfg_attr(
395    feature = "python",
396    gen_stub_pyfunction(module = "openqudit.expressions")
397)]
398#[cfg_attr(feature = "python", pyo3::pyfunction)]
399#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
400pub fn TGate(radix: usize) -> UnitaryExpression {
401    let proto = format!("T<{}>()", radix);
402    let mut body = "[".to_string();
403    for i in 0..radix {
404        body += "[";
405        for j in 0..radix {
406            if i == j {
407                if i == 0 {
408                    body += "1, ";
409                } else {
410                    // e^(iπk²/(2d))
411                    body += &format!("e^(π*i*{}*{}/(2*{})), ", i, i, radix);
412                }
413            } else {
414                body += "0, ";
415            }
416        }
417        body += "],";
418    }
419    body += "]";
420    UnitaryExpression::new(proto + "{" + &body + "}")
421}
422
423/// The single-qudit phase gate.
424///
425/// The common qubit phase gate is given by the following matrix:
426///
427/// $$
428/// \begin{pmatrix}
429///     1 & 0 \\\\
430///     0 & \exp({i\theta}) \\\\
431/// \end{pmatrix}
432/// $$
433///
434/// The qutrit phase gate has two parameterized relative phases:
435///
436/// $$
437/// \begin{pmatrix}
438///     1 & 0 & 0 \\\\
439///     0 & \exp({i\theta_0}) & 0 \\\\
440///    0 & 0 & \exp({i\theta_1}) \\\\
441/// \end{pmatrix}
442/// $$
443///
444/// The d-level phase gate has d-1 parameterized relative phases. This
445/// gate is Clifford iff all of the relative phases are powers of roots
446/// of unity.
447///
448/// References:
449/// - <https://www.nature.com/articles/s41467-022-34851-z>
450/// - <https://arxiv.org/pdf/2204.13681.pdf>
451#[cfg_attr(
452    feature = "python",
453    gen_stub_pyfunction(module = "openqudit.expressions")
454)]
455#[cfg_attr(feature = "python", pyo3::pyfunction)]
456#[cfg_attr(feature = "python", pyo3(signature = (radix = 2)))]
457pub fn PGate(radix: usize) -> UnitaryExpression {
458    let mut proto = format!("P<{}>(", radix);
459    for i in 0..radix - 1 {
460        proto += "θ";
461        proto += &i.to_string();
462        proto += ", ";
463    }
464    proto += ")";
465
466    let mut body = "".to_string();
467    body += "[";
468    for i in 0..radix {
469        body += "[";
470        for j in 0..radix {
471            if i == j {
472                if i == 0 {
473                    body += "1, ";
474                } else {
475                    body += &format!("e^(i*θ{}), ", i - 1);
476                }
477            } else {
478                body += "0, ";
479            }
480        }
481        body += "],";
482    }
483    body += "]";
484
485    UnitaryExpression::new(proto + "{" + &body + "}")
486}
487
488#[cfg_attr(
489    feature = "python",
490    gen_stub_pyfunction(module = "openqudit.expressions")
491)]
492#[cfg_attr(feature = "python", pyo3::pyfunction)]
493pub fn U3Gate() -> UnitaryExpression {
494    let proto = "U(θ0, θ1, θ2)";
495    let body = "[
496            [cos(θ0/2), ~e^(i*θ2)*sin(θ0/2)],
497            [e^(i*θ1)*sin(θ0/2), e^(i*(θ1+θ2))*cos(θ0/2)]
498    ]";
499    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
500}
501
502/// The single-qubit U1 gate (phase gate up to global phase).
503///
504/// $$
505/// U1(\lambda) = \begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\lambda} \end{pmatrix}
506/// $$
507#[cfg_attr(
508    feature = "python",
509    gen_stub_pyfunction(module = "openqudit.expressions")
510)]
511#[cfg_attr(feature = "python", pyo3::pyfunction)]
512pub fn U1Gate() -> UnitaryExpression {
513    let proto = "U1(θ0)";
514    let body = "[[1, 0], [0, e^(i*θ0)]]";
515    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
516}
517
518/// The single-qubit U2 gate.
519///
520/// $$
521/// U2(\phi, \lambda) = \frac{1}{\sqrt{2}}
522/// \begin{pmatrix} 1 & -e^{i\lambda} \\\\ e^{i\phi} & e^{i(\phi+\lambda)} \end{pmatrix}
523/// $$
524#[cfg_attr(
525    feature = "python",
526    gen_stub_pyfunction(module = "openqudit.expressions")
527)]
528#[cfg_attr(feature = "python", pyo3::pyfunction)]
529pub fn U2Gate() -> UnitaryExpression {
530    let proto = "U2(θ0, θ1)";
531    let body = "1/sqrt(2) * [[1, ~e^(i*θ1)], [e^(i*θ0), e^(i*(θ0+θ1))]]";
532    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
533}
534
535/// The single-qubit SX gate (square-root of X / square-root of Pauli-X).
536///
537/// $$
538/// SX = \sqrt{X} = \frac{1}{2}\begin{pmatrix} 1+i & 1-i \\\\ 1-i & 1+i \end{pmatrix}
539/// $$
540///
541/// Note: a qudit generalization of this gate as $X^{1/2}$ is mathematically
542/// well-defined but its matrix entries involve geometric sums with non-integer
543/// exponents that do not reduce to a simple closed-form expression in QGL.
544#[cfg_attr(
545    feature = "python",
546    gen_stub_pyfunction(module = "openqudit.expressions")
547)]
548#[cfg_attr(feature = "python", pyo3::pyfunction)]
549pub fn SXGate() -> UnitaryExpression {
550    let proto = "SX()";
551    let body = "1/2 * [[1+i, 1-i], [1-i, 1+i]]";
552    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
553}
554
555/// The single-qubit RX gate (rotation around the X-axis).
556///
557/// $$
558/// RX(\theta) = \begin{pmatrix} \cos(\theta/2) & -i\sin(\theta/2) \\\\ -i\sin(\theta/2) & \cos(\theta/2) \end{pmatrix}
559/// $$
560#[cfg_attr(
561    feature = "python",
562    gen_stub_pyfunction(module = "openqudit.expressions")
563)]
564#[cfg_attr(feature = "python", pyo3::pyfunction)]
565pub fn RXGate() -> UnitaryExpression {
566    let proto = "RX(θ0)";
567    let body = "[[cos(θ0/2), ~i*sin(θ0/2)], [~i*sin(θ0/2), cos(θ0/2)]]";
568    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
569}
570
571/// The single-qubit RY gate (rotation around the Y-axis).
572///
573/// $$
574/// RY(\theta) = \begin{pmatrix} \cos(\theta/2) & -\sin(\theta/2) \\\\ \sin(\theta/2) & \cos(\theta/2) \end{pmatrix}
575/// $$
576#[cfg_attr(
577    feature = "python",
578    gen_stub_pyfunction(module = "openqudit.expressions")
579)]
580#[cfg_attr(feature = "python", pyo3::pyfunction)]
581pub fn RYGate() -> UnitaryExpression {
582    let proto = "RY(θ0)";
583    let body = "[[cos(θ0/2), ~sin(θ0/2)], [sin(θ0/2), cos(θ0/2)]]";
584    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
585}
586
587/// The single-qubit RZ gate (rotation around the Z-axis).
588///
589/// $$
590/// RZ(\theta) = \begin{pmatrix} e^{-i\theta/2} & 0 \\\\ 0 & e^{i\theta/2} \end{pmatrix}
591/// $$
592#[cfg_attr(
593    feature = "python",
594    gen_stub_pyfunction(module = "openqudit.expressions")
595)]
596#[cfg_attr(feature = "python", pyo3::pyfunction)]
597pub fn RZGate() -> UnitaryExpression {
598    let proto = "RZ(θ0)";
599    let body = "[[e^(~i*θ0/2), 0], [0, e^(i*θ0/2)]]";
600    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
601}
602
603fn generate_embedded_two_param_su2(dimension: usize, i: usize, j: usize) -> UnitaryExpression {
604    let proto = format!("U<{dimension}>(θ0, θ1)");
605    let mut body = String::from("[");
606    for row in 0..dimension {
607        body += "[";
608        for col in 0..dimension {
609            if row == i && col == i {
610                body += "e^(i*θ1)*cos(θ0/2)";
611            } else if row == i && col == j {
612                body += "~sin(θ0/2)";
613            } else if row == j && col == i {
614                body += "sin(θ0/2)";
615            } else if row == j && col == j {
616                body += "e^(i*(~θ1))*cos(θ0/2)";
617            } else if row == col {
618                body += "1";
619            } else {
620                body += "0";
621            }
622            body += ",";
623        }
624        body += "]";
625    }
626    body += "]";
627    UnitaryExpression::new(proto.to_owned() + "{" + &body + "}")
628}
629
630fn generate_embedded_su2(dimension: usize, i: usize, j: usize) -> UnitaryExpression {
631    let proto = format!("U<{dimension}>(θ0, θ1, θ2)");
632    let mut body = String::from("[");
633    for row in 0..dimension {
634        body += "[";
635        for col in 0..dimension {
636            if row == i && col == i {
637                body += "cos(θ0/2)";
638            } else if row == i && col == j {
639                body += "~e^(i*θ2)*sin(θ0/2)";
640            } else if row == j && col == i {
641                body += "e^(i*θ1)*sin(θ0/2)";
642            } else if row == j && col == j {
643                body += "e^(i*(θ1+θ2))*cos(θ0/2)";
644            } else if row == col {
645                body += "1";
646            } else {
647                body += "0";
648            }
649            body += ",";
650        }
651        body += "]";
652    }
653    body += "]";
654    UnitaryExpression::new(proto.to_owned() + "{" + &body + "}")
655}
656
657/// The two-qubit RXX gate (Ising XX coupling).
658///
659/// $$
660/// RXX(\theta) = e^{-i\theta/2 \, X \otimes X} =
661/// \begin{pmatrix}
662///     \cos(\theta/2) & 0 & 0 & -i\sin(\theta/2) \\\\
663///     0 & \cos(\theta/2) & -i\sin(\theta/2) & 0 \\\\
664///     0 & -i\sin(\theta/2) & \cos(\theta/2) & 0 \\\\
665///     -i\sin(\theta/2) & 0 & 0 & \cos(\theta/2)
666/// \end{pmatrix}
667/// $$
668#[cfg_attr(
669    feature = "python",
670    gen_stub_pyfunction(module = "openqudit.expressions")
671)]
672#[cfg_attr(feature = "python", pyo3::pyfunction)]
673pub fn RXXGate() -> UnitaryExpression {
674    let proto = "RXX<2, 2>(θ0)";
675    let body = "[\
676        [cos(θ0/2), 0, 0, ~i*sin(θ0/2)],\
677        [0, cos(θ0/2), ~i*sin(θ0/2), 0],\
678        [0, ~i*sin(θ0/2), cos(θ0/2), 0],\
679        [~i*sin(θ0/2), 0, 0, cos(θ0/2)]\
680    ]";
681    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
682}
683
684/// The two-qubit RYY gate (Ising YY coupling).
685///
686/// $$
687/// RYY(\theta) = e^{-i\theta/2 \, Y \otimes Y} =
688/// \begin{pmatrix}
689///     \cos(\theta/2) & 0 & 0 & i\sin(\theta/2) \\\\
690///     0 & \cos(\theta/2) & -i\sin(\theta/2) & 0 \\\\
691///     0 & -i\sin(\theta/2) & \cos(\theta/2) & 0 \\\\
692///     i\sin(\theta/2) & 0 & 0 & \cos(\theta/2)
693/// \end{pmatrix}
694/// $$
695#[cfg_attr(
696    feature = "python",
697    gen_stub_pyfunction(module = "openqudit.expressions")
698)]
699#[cfg_attr(feature = "python", pyo3::pyfunction)]
700pub fn RYYGate() -> UnitaryExpression {
701    let proto = "RYY<2, 2>(θ0)";
702    let body = "[\
703        [cos(θ0/2), 0, 0, i*sin(θ0/2)],\
704        [0, cos(θ0/2), ~i*sin(θ0/2), 0],\
705        [0, ~i*sin(θ0/2), cos(θ0/2), 0],\
706        [i*sin(θ0/2), 0, 0, cos(θ0/2)]\
707    ]";
708    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
709}
710
711/// The two-qubit RZZ gate (Ising ZZ coupling).
712///
713/// $$
714/// RZZ(\theta) = e^{-i\theta/2 \, Z \otimes Z} =
715/// \begin{pmatrix}
716///     e^{-i\theta/2} & 0 & 0 & 0 \\\\
717///     0 & e^{i\theta/2} & 0 & 0 \\\\
718///     0 & 0 & e^{i\theta/2} & 0 \\\\
719///     0 & 0 & 0 & e^{-i\theta/2}
720/// \end{pmatrix}
721/// $$
722#[cfg_attr(
723    feature = "python",
724    gen_stub_pyfunction(module = "openqudit.expressions")
725)]
726#[cfg_attr(feature = "python", pyo3::pyfunction)]
727pub fn RZZGate() -> UnitaryExpression {
728    let proto = "RZZ<2, 2>(θ0)";
729    let body = "[\
730        [e^(~i*θ0/2), 0, 0, 0],\
731        [0, e^(i*θ0/2), 0, 0],\
732        [0, 0, e^(i*θ0/2), 0],\
733        [0, 0, 0, e^(~i*θ0/2)]\
734    ]";
735    UnitaryExpression::new(proto.to_owned() + "{" + body + "}")
736}
737
738fn embed_one_larger(unitary: UnitaryExpression) -> UnitaryExpression {
739    let dimension = unitary.dimension() + 1;
740    let mut one_larger = UnitaryExpression::identity(unitary.name(), [dimension]);
741    one_larger.embed(unitary, 1, 1);
742    one_larger
743}
744
745/// Generates a fully parameterized unitary expression
746///
747/// References:
748/// - de Guise, Hubert, Olivia Di Matteo, and Luis L. Sánchez-Soto.
749///   "Simple factorization of unitary transformations."
750///   Physical Review A 97.2 (2018): 022328.
751#[cfg_attr(
752    feature = "python",
753    gen_stub_pyfunction(module = "openqudit.expressions")
754)]
755#[cfg_attr(feature = "python", pyo3::pyfunction)]
756#[cfg_attr(feature = "python", pyo3(signature = (radices = Radices::new([2]))))]
757pub fn ParameterizedUnitary(radices: Radices) -> UnitaryExpression {
758    let dimension = radices.dimension();
759
760    if dimension < 2 {
761        panic!("Cannot generate parameterized unitary for dimension 1 or less");
762    }
763
764    if dimension == 2 {
765        return U3Gate();
766    }
767
768    let right = {
769        let one_smaller = ParameterizedUnitary(Radices::new([dimension - 1]));
770        embed_one_larger(one_smaller)
771    };
772
773    let left = {
774        let mut acm = generate_embedded_su2(dimension, dimension - 2, dimension - 1);
775        for i in (0..=(dimension - 3)).rev() {
776            let j = i + 1;
777            let two = generate_embedded_two_param_su2(dimension, i, j);
778            acm = acm.dot(two)
779        }
780        acm
781    };
782
783    let mut expression = left.dot(right);
784    expression.set_radices(radices);
785    expression
786}
787
788/// Invert an expression
789#[cfg_attr(
790    feature = "python",
791    gen_stub_pyfunction(module = "openqudit.expressions")
792)]
793#[cfg_attr(feature = "python", pyo3::pyfunction)]
794pub fn Invert(mut expr: UnitaryExpression) -> UnitaryExpression {
795    expr.dagger();
796    expr
797}
798
799/// Apply the conjugate transpose (dagger / adjoint) to a unitary expression.
800///
801/// For any unitary $U$ this produces $U^\dagger$. Use this transformer to
802/// obtain the inverse of any gate without defining a separate gate function;
803/// for example, `Dagger(SGate(2))` gives $S^\dagger$ and `Dagger(TGate(2))`
804/// gives $T^\dagger$.
805#[cfg_attr(
806    feature = "python",
807    gen_stub_pyfunction(module = "openqudit.expressions")
808)]
809#[cfg_attr(feature = "python", pyo3::pyfunction)]
810pub fn Dagger(mut expr: UnitaryExpression) -> UnitaryExpression {
811    let new_name = format!("Dagger({})", expr.name());
812    expr.dagger();
813    expr.set_name(new_name);
814    expr
815}
816
817/// Calculates the cartesian product of the control levels.
818///
819/// # Examples
820///
821/// ```ignore
822/// let control_levels = vec![vec![0, 1], vec![0, 1]];
823/// let prod = cartesian_product(control_levels);
824/// assert_eq!(prod, vec![
825///    vec![0, 0],
826///    vec![1, 0],
827///    vec![0, 1],
828///    vec![1, 1],
829/// ]);
830/// ```
831fn cartesian_product(control_levels: Vec<Vec<usize>>) -> Vec<Vec<usize>> {
832    let mut prod = vec![];
833    for level in control_levels.into_iter() {
834        if prod.is_empty() {
835            for l in level.into_iter() {
836                prod.push(vec![l]);
837            }
838        } else {
839            let mut new_prod = vec![];
840            for l in level.into_iter() {
841                for v in prod.iter() {
842                    let mut v_new = v.clone();
843                    v_new.push(l);
844                    new_prod.push(v_new);
845                }
846            }
847            prod = new_prod;
848        }
849    }
850    prod
851}
852
853/// An arbitrary controlled gate.
854///
855/// Given any gate, ControlledGate can add control qudits.
856///
857/// A controlled gate adds arbitrarily controls, and is generalized
858/// for qudit or even mixed-qudit representation.
859///
860/// A controlled gate has a circuit structure as follows:
861///
862/// ```text
863///     controls ----/----■----
864///                       |
865///                      .-.
866///     targets  ----/---|G|---
867///                      '-'
868/// ```
869///
870/// Where $G$ is the gate being controlled.
871///
872/// To calculate the unitary for a controlled gate, given the unitary of
873/// the gate being controlled, we can use the following equation:
874///
875/// $$U_{control} = P_i \otimes I + P_c \otimes G$$
876///
877/// Where $P_i$ is the projection matrix for the states that don't
878/// activate the gate, $P_c$ is the projection matrix for the
879/// states that do activate the gate, $I$ is the identity matrix
880/// of dimension equal to the gate being controlled, and $G$ is
881/// the unitary matrix of the gate being controlled.
882///
883/// In the simple case of a normal qubit CNOT ($G = X$), $P_i$ and $P_c$
884/// are defined as follows:
885///
886/// $$
887///     P_i = \ket{0}\bra{0}
888///     P_c = \ket{1}\bra{1}
889/// $$
890///
891/// This is because the $\ket{0}$ state is the state that doesn't
892/// activate the gate, and the $\ket{1}$ state is the state that
893/// does activate the gate.
894///
895/// We can also decide to invert this, and have the $\ket{0}$
896/// state activate the gate, and the $\ket{1}$ state not activate
897/// the gate. This is equivalent to swapping $P_i$ and $P_c$,
898/// and usually drawn diagrammatically as follows:
899///
900/// ```text
901///     controls ----/----□----
902///                       |
903///                      .-.
904///     targets  ----/---|G|---
905///                      '-'
906/// ```
907///
908/// When we add more controls the projection matrices become more complex,
909/// but the basic idea stays the same: we have a projection matrix for
910/// the states that activate the gate, and a projection matrix for the
911/// states that don't activate the gate. As in the case of a toffoli gate,
912/// the projection matrices are defined as follows:
913///
914/// $$
915///     P_i = \ket{00}\bra{00} + \ket{01}\bra{01} + \ket{10}\bra{10}
916///     P_c = \ket{11}\bra{11}
917/// $$
918///
919/// This is because the $\ket{00}$, $\ket{01}$, and
920/// $\ket{10}$ states are the states that don't activate the
921/// gate, and the $\ket{11}$ state is the state that does
922/// activate the gate.
923///
924/// With qudits, we have more states and as such, more complex
925/// projection matrices; however, the basic idea is the same.
926/// For example, a qutrit controlled-not gate that is activated by
927/// the $\ket{2}$ state and not activated by the $\ket{0}$
928/// and $\ket{1}$ states is defined as follows:
929///
930/// $$
931///     P_i = \ket{0}\bra{0} + \ket{1}\bra{1}
932///     P_c = \ket{2}\bra{2}
933/// $$
934///
935/// One interesting concept with qudits is that we can have multiple
936/// active control levels. For example, a qutrit controlled-not gate that
937/// is activated by the $\ket{1}$ and $\ket{2}$ states
938/// and not activated by the $\ket{0}$ state is defined similarly
939/// as follows:
940///
941/// $$
942///     P_i = \ket{0}\bra{0}
943///     P_c = \ket{1}\bra{1} + \ket{2}\bra{2}
944/// $$
945///
946/// Note that we can always define $P_i$ simply from $P_c$:
947///
948/// $$P_i = I_p - P_c$$
949///
950/// Where $I_p$ is the identity matrix of dimension equal to the
951/// dimension of the control qudits. This leaves us with out final
952/// equation:
953///
954///
955/// $$U_{control} = (I_p - P_c) \otimes I + P_c \otimes G$$
956///
957/// If, G is a unitary-valued function of real parameters, then the
958/// gradient of the controlled gate simply discards the constant half
959/// of the equation:
960///
961/// $$
962///     \frac{\partial U_{control}}{\partial \theta} =
963///         P_c \otimes \frac{\partial G}{\partial \theta}
964/// $$
965///
966/// # Arguments
967///
968/// * `expr` - The gate to control.
969///
970/// * `control_radixes` - The number of levels for each control qudit.
971///
972/// * `control_levels` - The levels of the control qudits that activate the
973///   gate. If more than one level is selected, the subspace spanned by the
974///   levels acts as a control subspace. If all levels are selected for a
975///   given qudit, the operation is equivalent to the original gate without
976///   controls.
977/// # Panics
978///
979/// * If `control_radixes` and `control_levels` have different lengths.
980///
981/// * If `control_levels` contains an empty level.
982///
983/// * If any level in `control_levels` is greater than or equal to the
984///   corresponding radix in `control_radixes`.
985///
986/// * If any level in `control_levels` is not unique.
987#[cfg_attr(
988    feature = "python",
989    gen_stub_pyfunction(module = "openqudit.expressions")
990)]
991#[cfg_attr(feature = "python", pyo3::pyfunction)]
992#[cfg_attr(feature = "python", pyo3(signature = (expr, control_radices = Radices::new([2]), control_levels = None)))]
993pub fn Controlled(
994    expr: UnitaryExpression,
995    control_radices: Radices,
996    control_levels: Option<Vec<Vec<usize>>>,
997) -> UnitaryExpression {
998    let control_levels = match control_levels {
999        Some(levels) => levels,
1000        None => {
1001            // Generate default control_levels: each control qudit activated by its highest level
1002            control_radices
1003                .iter()
1004                .map(|&radix| vec![(usize::from(radix) - 1)])
1005                .collect()
1006        }
1007    };
1008
1009    if control_radices.len() != control_levels.len() {
1010        panic!("control_radices and control_levels must have the same length");
1011    }
1012
1013    if control_levels.iter().any(|levels| levels.is_empty()) {
1014        panic!("control_levels must not contain empty levels");
1015    }
1016
1017    if control_levels
1018        .iter()
1019        .map(|levels| levels.iter().copied())
1020        .zip(control_radices.iter())
1021        .any(|(mut levels, radix)| levels.any(|level| level >= usize::from(*radix)))
1022    {
1023        panic!("Expected control levels to be less than the number of levels.");
1024    }
1025
1026    // Check that all levels in control_levels are unique
1027    let mut control_level_sets = control_levels.clone();
1028    for level in control_level_sets.iter_mut() {
1029        level.sort();
1030        level.dedup();
1031    }
1032    if control_level_sets
1033        .iter()
1034        .zip(control_levels.iter())
1035        .any(|(level_dedup, level)| level.len() != level_dedup.len())
1036    {
1037        panic!("Expected control levels to be unique.");
1038    }
1039
1040    let gate_expr = expr;
1041    let gate_dim = gate_expr.dimension();
1042
1043    // Build appropriately sized identity expression
1044    let name = format!("Controlled({})", gate_expr.name());
1045    let radices = control_radices.concat(&gate_expr.radices());
1046    let mut expr = UnitaryExpression::identity(&name, radices);
1047
1048    // Embed gate expression into identity expression at correct spots
1049    let diagonal_indices: Vec<usize> = cartesian_product(control_levels)
1050        .into_iter()
1051        .map(|block_idx_expansion| control_radices.compress(&block_idx_expansion))
1052        .map(|block_diag_idx| block_diag_idx * gate_dim)
1053        .collect();
1054
1055    for diagonal_idx in diagonal_indices.iter() {
1056        expr.embed(gate_expr.clone(), *diagonal_idx, *diagonal_idx);
1057    }
1058
1059    expr
1060}
1061
1062#[cfg_attr(
1063    feature = "python",
1064    gen_stub_pyfunction(module = "openqudit.expressions")
1065)]
1066#[cfg_attr(feature = "python", pyo3::pyfunction)]
1067#[cfg_attr(feature = "python", pyo3(signature = (expr, control_radices = Radices::new([2]), control_levels = None)))]
1068pub fn ClassicallyControlled(
1069    expr: UnitaryExpression,
1070    control_radices: Radices,
1071    control_levels: Option<Vec<Vec<usize>>>,
1072) -> UnitarySystemExpression {
1073    let control_levels = match control_levels {
1074        Some(levels) => levels,
1075        None => {
1076            // Generate default control_levels: each control qudit activated by its highest level
1077            control_radices
1078                .iter()
1079                .map(|&radix| vec![(usize::from(radix) - 1)])
1080                .collect()
1081        }
1082    };
1083
1084    if control_radices.len() != control_levels.len() {
1085        panic!("control_radices and control_levels must have the same length");
1086    }
1087
1088    if control_levels.iter().any(|levels| levels.is_empty()) {
1089        panic!("control_levels must not contain empty levels");
1090    }
1091
1092    if control_levels
1093        .iter()
1094        .map(|levels| levels.iter().copied())
1095        .zip(control_radices.iter())
1096        .any(|(mut levels, radix)| levels.any(|level| level >= usize::from(*radix)))
1097    {
1098        panic!("Expected control levels to be less than the number of levels.");
1099    }
1100
1101    // Check that all levels in control_levels are unique
1102    let mut control_level_sets = control_levels.clone();
1103    for level in control_level_sets.iter_mut() {
1104        level.sort();
1105        level.dedup();
1106    }
1107    if control_level_sets
1108        .iter()
1109        .zip(control_levels.iter())
1110        .any(|(level_dedup, level)| level.len() != level_dedup.len())
1111    {
1112        panic!("Expected control levels to be unique.");
1113    }
1114
1115    let diagonal_indices: Vec<usize> = cartesian_product(control_levels)
1116        .into_iter()
1117        .map(|block_idx_expansion| control_radices.compress(&block_idx_expansion))
1118        .collect();
1119
1120    let new_radices = control_radices
1121        .iter()
1122        .map(|&r| r.into())
1123        .collect::<Vec<_>>();
1124    expr.classically_control(&diagonal_indices, &new_radices)
1125}
1126
1127#[cfg(feature = "python")]
1128mod python {
1129    use crate::python::PyExpressionRegistrar;
1130    use pyo3::prelude::*;
1131
1132    /// Registers the gate library with the Python module.
1133    fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
1134        parent_module.add_function(wrap_pyfunction!(super::IGate, parent_module)?)?;
1135        parent_module.add_function(wrap_pyfunction!(super::HGate, parent_module)?)?;
1136        parent_module.add_function(wrap_pyfunction!(super::SwapGate, parent_module)?)?;
1137        parent_module.add_function(wrap_pyfunction!(super::XGate, parent_module)?)?;
1138        parent_module.add_function(wrap_pyfunction!(super::ZGate, parent_module)?)?;
1139        parent_module.add_function(wrap_pyfunction!(super::YGate, parent_module)?)?;
1140        parent_module.add_function(wrap_pyfunction!(super::SGate, parent_module)?)?;
1141        parent_module.add_function(wrap_pyfunction!(super::TGate, parent_module)?)?;
1142        parent_module.add_function(wrap_pyfunction!(super::PGate, parent_module)?)?;
1143        parent_module.add_function(wrap_pyfunction!(super::U3Gate, parent_module)?)?;
1144        parent_module.add_function(wrap_pyfunction!(super::U1Gate, parent_module)?)?;
1145        parent_module.add_function(wrap_pyfunction!(super::U2Gate, parent_module)?)?;
1146        parent_module.add_function(wrap_pyfunction!(super::SXGate, parent_module)?)?;
1147        parent_module.add_function(wrap_pyfunction!(super::RXGate, parent_module)?)?;
1148        parent_module.add_function(wrap_pyfunction!(super::RYGate, parent_module)?)?;
1149        parent_module.add_function(wrap_pyfunction!(super::RZGate, parent_module)?)?;
1150        parent_module.add_function(wrap_pyfunction!(super::RXXGate, parent_module)?)?;
1151        parent_module.add_function(wrap_pyfunction!(super::RYYGate, parent_module)?)?;
1152        parent_module.add_function(wrap_pyfunction!(super::RZZGate, parent_module)?)?;
1153        parent_module.add_function(wrap_pyfunction!(
1154            super::ParameterizedUnitary,
1155            parent_module
1156        )?)?;
1157        parent_module.add_function(wrap_pyfunction!(super::Invert, parent_module)?)?;
1158        parent_module.add_function(wrap_pyfunction!(super::Dagger, parent_module)?)?;
1159        parent_module.add_function(wrap_pyfunction!(super::Controlled, parent_module)?)?;
1160        parent_module.add_function(wrap_pyfunction!(
1161            super::ClassicallyControlled,
1162            parent_module
1163        )?)?;
1164        Ok(())
1165    }
1166    inventory::submit!(PyExpressionRegistrar { func: register });
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171    use super::*;
1172
1173    #[test]
1174    fn dagger_renames_s() {
1175        let dg = Dagger(SGate(2));
1176        assert_eq!(dg.name(), "Dagger(S)");
1177    }
1178
1179    #[test]
1180    fn dagger_renames_t() {
1181        let dg = Dagger(TGate(2));
1182        assert_eq!(dg.name(), "Dagger(T)");
1183    }
1184}