-
Notifications
You must be signed in to change notification settings - Fork 92
[unitaryHack 2025] Refactor gate decomposition to support controlled gates #1653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 58 commits
1d88449
30008d9
36e732a
edf33b3
762509b
48106cb
ff20fb4
97763de
d4c7174
667d242
aa0ccd3
f4e2acb
aba148b
3e1e2c8
dd83065
b5e8751
7f5ba81
a050f90
584f825
48b15ae
dd1e2aa
36e36d1
b070413
c3a5f5c
7f7687f
193a7f1
78a88e3
c174204
f1f7e72
dcc4637
18cf16a
93d7647
a6cd78f
740fd78
772016a
340daef
6bdd3cc
2805919
5c3f558
1cdcebe
a7476bb
0fe9c47
2706ef6
4b4ac68
b57a1cf
4a585cf
0de4c70
34bcb2b
ad69291
1d07ce8
313dddb
096e572
70e6c3d
67f6dd0
f8fc6ba
a5fc90d
420a6f3
48496e6
8706a25
ee436ef
9b01855
fddeb46
bf26719
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import collections | ||
| import json | ||
| from math import pi | ||
| from typing import List, Sequence, Tuple | ||
|
|
||
| import sympy | ||
|
|
@@ -359,18 +360,114 @@ def controlled_by(self, *qubits: int) -> "Gate": | |
| self.control_qubits = qubits | ||
| return self | ||
|
|
||
| def decompose(self, *free) -> List["Gate"]: | ||
| def _base_decompose(self, *free, use_toffolis=True) -> List["Gate"]: | ||
| """Base decomposition for gates. | ||
|
|
||
| Returns a list containing the gate itself. Should be overridden by | ||
| subclasses that support decomposition to simpler gates. | ||
|
|
||
| Args: | ||
| free: Ids of free qubits to use for the gate decomposition. | ||
| use_toffolis: If ``True`` the decomposition contains only ``TOFFOLI`` gates. | ||
| If ``False`` a congruent representation is used for ``TOFFOLI`` gates. | ||
| See :class:`qibo.gates.TOFFOLI` for more details on this representation. | ||
|
|
||
| Returns: | ||
| list: Synthesis of the original gate in another gate set. | ||
| """ | ||
| return [self.__class__(*self.init_args, **self.init_kwargs)] | ||
|
|
||
| @staticmethod | ||
| def _gates_cancel(g1, g2): | ||
| """Determines if two gates cancel each other. | ||
|
|
||
| Two gates are considered to cancel if: | ||
| - They are of the same type (class). | ||
| - They act on the same target and control qubits. | ||
| - For fixed gates (like H, CX, X, Y, Z, SWAP), they always cancel in pairs. | ||
| - For parametrized rotation gates (subclasses of _Rn_), their parameters sum to a multiple of 2π. | ||
|
|
||
| Note: | ||
| Multi-parameter gates are not currently supported by this check. | ||
|
|
||
| Args: | ||
| g1, g2: Gate instances to compare. | ||
|
|
||
| Returns: | ||
| bool: True if the gates cancel each other, False otherwise. | ||
| """ | ||
| if g1.__class__ != g2.__class__: | ||
| return False | ||
GiacomoFrn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if g1.target_qubits != g2.target_qubits: | ||
| return False | ||
renatomello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if g1.control_qubits != g2.control_qubits: | ||
| return False | ||
|
|
||
| # Identity conditions for fixed gates | ||
| name = g1.name | ||
| if name in ("h", "cx", "x", "y", "z", "swap", "ecr", "ccx", "ccz"): | ||
| return True | ||
GiacomoFrn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Check for parametrized rotation gates | ||
| if "_Rn_" in [base.__name__ for base in g1.__class__.__bases__]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better thanks, however, this is still not supporting multi parameters gates, which might be fine for the moment, maybe just point this out in the docstring.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is true, I thought that in general I could assume that multi parameters gates wouldn't appear at this stage of the decomposition, but I am probably wrong. I will update the docstring. |
||
| theta1 = g1.parameters[0] | ||
| theta2 = g2.parameters[0] | ||
| # Check if theta1 + theta2 is a multiple of 2π | ||
| return bool((theta1 + theta2) % (2 * pi) < 1e-8) | ||
|
|
||
| return False | ||
GiacomoFrn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def _control_mask_after_stripping(self, gates: List["Gate"]) -> List[bool]: | ||
| """Returns a mask indicating which gates should be controlled.""" | ||
| left = 0 | ||
| right = len(gates) - 1 | ||
| mask = [True] * len(gates) | ||
| while left < right: | ||
| g1, g2 = gates[left], gates[right] | ||
| if self._gates_cancel(g1, g2): | ||
| mask[left] = False | ||
| mask[right] = False | ||
renatomello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| left += 1 | ||
| right -= 1 | ||
| return mask | ||
|
|
||
| def decompose(self, *free, use_toffolis: bool = True) -> List["Gate"]: | ||
| """Decomposes multi-control gates to gates supported by OpenQASM. | ||
|
|
||
| Decompositions are based on `arXiv:9503016 <https://arxiv.org/abs/quant-ph/9503016>`_. | ||
| If the gate is already controlled, it recursively decomposes the base gate and updates | ||
| the control qubits accordingly. | ||
|
|
||
| Args: | ||
| free: Ids of free qubits to use for the gate decomposition. | ||
GiacomoFrn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| use_toffolis(bool, optional): If ``True``, the decomposition contains only | ||
| :class:`qibo.gates.TOFFOLI` gates. If ``False``, a congruent | ||
| representation is used for :class:`qibo.gates.TOFFOLI` gates. | ||
| See :class:`qibo.gates.TOFFOLI` for more details on this representation. | ||
|
|
||
| Returns: | ||
| list: gates that have the same effect as applying the original gate. | ||
| """ | ||
| return [self.__class__(*self.init_args, **self.init_kwargs)] | ||
| if self.is_controlled_by: | ||
| # Step 1: Error check with all controls/targets | ||
| if set(free) & set(self.qubits): | ||
| raise_error( | ||
| ValueError, | ||
| "Cannot decompose multi-controlled ``X`` gate if free " | ||
| "qubits coincide with target or controls.", | ||
| ) | ||
| # Step 2: Decompose base gate without controls | ||
| base_gate = self.__class__(*self.init_args, **self.init_kwargs) | ||
| decomposed = base_gate._base_decompose(*free, use_toffolis=use_toffolis) | ||
| mask = self._control_mask_after_stripping(decomposed) | ||
| for m, g in zip(mask, decomposed): | ||
| if m: | ||
| g.is_controlled_by = True | ||
| g.control_qubits += self.control_qubits | ||
renatomello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return decomposed | ||
| return self._base_decompose(*free, use_toffolis=use_toffolis) | ||
|
|
||
| def matrix(self, backend=None): | ||
| """Returns the matrix representation of the gate. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.