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