-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Improved MCXVChain with dirty auxiliary qubits #9687
Changes from all commits
087d986
5ec3c55
bd76eeb
5f2c6f7
f5e2c3a
ab6a4c4
41d0db9
e7ed2c1
e460a83
13fd6b0
013c8a5
0480af4
0f1c253
e61eda3
17da83e
95264ed
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 |
---|---|---|
|
@@ -1409,6 +1409,8 @@ def __new__( | |
duration=None, | ||
unit="dt", | ||
_base_label=None, | ||
relative_phase: bool = False, # pylint: disable=unused-argument | ||
action_only: bool = False, # pylint: disable=unused-argument | ||
): | ||
"""Create a new MCX instance. | ||
|
||
|
@@ -1434,7 +1436,24 @@ def __init__( | |
duration=None, | ||
unit="dt", | ||
_base_label=None, | ||
relative_phase: bool = False, | ||
action_only: bool = False, | ||
): | ||
""" | ||
Args: | ||
dirty_ancillas: when set to ``True``, the method applies an optimized multicontrolled-X gate | ||
up to a relative phase using dirty ancillary qubits with the properties of lemmas 7 and 8 | ||
from arXiv:1501.06911, with at most 8*k - 6 CNOT gates. | ||
For k within the range {1, ..., ceil(n/2)}. And for n representing the total number of | ||
qubits. | ||
relative_phase: when set to ``True``, the method applies the optimized multicontrolled-X gate | ||
up to a relative phase, in a way that, by lemma 7 of arXiv:1501.06911, the relative | ||
phases of the ``action part`` cancel out with the phases of the ``reset part``. | ||
|
||
action_only: when set to ``True``, the method applies only the action part of lemma 8 | ||
from arXiv:1501.06911. | ||
|
||
""" | ||
super().__init__( | ||
num_ctrl_qubits, | ||
label=label, | ||
|
@@ -1445,6 +1464,9 @@ def __init__( | |
unit=unit, | ||
) | ||
self._dirty_ancillas = dirty_ancillas | ||
self._relative_phase = relative_phase | ||
self._action_only = action_only | ||
super().__init__(num_ctrl_qubits, label=label, ctrl_state=ctrl_state, _name="mcx_vchain") | ||
|
||
def inverse(self, annotated: bool = False): | ||
"""Invert this gate. The MCX is its own inverse. | ||
|
@@ -1462,6 +1484,8 @@ def inverse(self, annotated: bool = False): | |
num_ctrl_qubits=self.num_ctrl_qubits, | ||
dirty_ancillas=self._dirty_ancillas, | ||
ctrl_state=self.ctrl_state, | ||
relative_phase=self._relative_phase, | ||
Cryoris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
action_only=self._action_only, | ||
) | ||
|
||
@staticmethod | ||
|
@@ -1473,73 +1497,90 @@ def _define(self): | |
"""Define the MCX gate using a V-chain of CX gates.""" | ||
# pylint: disable=cyclic-import | ||
from qiskit.circuit.quantumcircuit import QuantumCircuit | ||
from .u1 import U1Gate | ||
from .u2 import U2Gate | ||
|
||
q = QuantumRegister(self.num_qubits, name="q") | ||
qc = QuantumCircuit(q, name=self.name) | ||
q_controls = q[: self.num_ctrl_qubits] | ||
q_target = q[self.num_ctrl_qubits] | ||
q_ancillas = q[self.num_ctrl_qubits + 1 :] | ||
|
||
definition = [] | ||
|
||
if self._dirty_ancillas: | ||
i = self.num_ctrl_qubits - 3 | ||
ancilla_pre_rule = [ | ||
(U2Gate(0, numpy.pi), [q_target], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
] | ||
for inst in ancilla_pre_rule: | ||
definition.append(inst) | ||
if self.num_ctrl_qubits < 3: | ||
qc.mcx(q_controls, q_target) | ||
elif not self._relative_phase and self.num_ctrl_qubits == 3: | ||
qc._append(C3XGate(), [*q_controls, q_target], []) | ||
else: | ||
num_ancillas = self.num_ctrl_qubits - 2 | ||
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. To make this code a little easier to read and make it easier to potentially move the code to 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. In addition, it would be useful to keep the current synthesis code in another internal function. 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. When calling the private function on the 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. In the meanwhile, you don't need to move the code to 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. The idea is that not all this code will be inside the |
||
targets = [q_target] + q_ancillas[:num_ancillas][::-1] | ||
|
||
for j in range(2): | ||
for i in range(self.num_ctrl_qubits): # action part | ||
if i < self.num_ctrl_qubits - 2: | ||
if targets[i] != q_target or self._relative_phase: | ||
# gate cancelling | ||
|
||
# cancel rightmost gates of action part | ||
# with leftmost gates of reset part | ||
if self._relative_phase and targets[i] == q_target and j == 1: | ||
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) | ||
qc.t(targets[i]) | ||
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) | ||
qc.tdg(targets[i]) | ||
qc.h(targets[i]) | ||
else: | ||
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. This code looks much clearer now, but it seems that it contains many |
||
qc.h(targets[i]) | ||
qc.t(targets[i]) | ||
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) | ||
qc.tdg(targets[i]) | ||
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) | ||
else: | ||
controls = [ | ||
q_controls[self.num_ctrl_qubits - i - 1], | ||
q_ancillas[num_ancillas - i - 1], | ||
] | ||
|
||
qc.ccx(controls[0], controls[1], targets[i]) | ||
else: | ||
# implements an optimized toffoli operation | ||
# up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911 | ||
qc.h(targets[i]) | ||
qc.t(targets[i]) | ||
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) | ||
qc.tdg(targets[i]) | ||
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) | ||
qc.t(targets[i]) | ||
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) | ||
qc.tdg(targets[i]) | ||
qc.h(targets[i]) | ||
|
||
break | ||
|
||
for i in range(num_ancillas - 1): # reset part | ||
qc.cx(q_ancillas[i], q_ancillas[i + 1]) | ||
qc.t(q_ancillas[i + 1]) | ||
qc.cx(q_controls[2 + i], q_ancillas[i + 1]) | ||
qc.tdg(q_ancillas[i + 1]) | ||
qc.h(q_ancillas[i + 1]) | ||
|
||
if self._action_only: | ||
qc.ccx(q_controls[-1], q_ancillas[-1], q_target) | ||
|
||
break | ||
else: | ||
qc.rccx(q_controls[0], q_controls[1], q_ancillas[0]) | ||
i = 0 | ||
for j in range(2, self.num_ctrl_qubits - 1): | ||
qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1]) | ||
|
||
for j in reversed(range(2, self.num_ctrl_qubits - 1)): | ||
definition.append( | ||
(RCCXGate(), [q_controls[j], q_ancillas[i - 1], q_ancillas[i]], []) | ||
) | ||
i -= 1 | ||
i += 1 | ||
|
||
definition.append((RCCXGate(), [q_controls[0], q_controls[1], q_ancillas[0]], [])) | ||
i = 0 | ||
for j in range(2, self.num_ctrl_qubits - 1): | ||
definition.append((RCCXGate(), [q_controls[j], q_ancillas[i], q_ancillas[i + 1]], [])) | ||
i += 1 | ||
qc.ccx(q_controls[-1], q_ancillas[i], q_target) | ||
|
||
if self._dirty_ancillas: | ||
ancilla_post_rule = [ | ||
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U2Gate(0, numpy.pi), [q_target], []), | ||
] | ||
for inst in ancilla_post_rule: | ||
definition.append(inst) | ||
else: | ||
definition.append((CCXGate(), [q_controls[-1], q_ancillas[i], q_target], [])) | ||
for j in reversed(range(2, self.num_ctrl_qubits - 1)): | ||
qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i]) | ||
|
||
for j in reversed(range(2, self.num_ctrl_qubits - 1)): | ||
definition.append((RCCXGate(), [q_controls[j], q_ancillas[i - 1], q_ancillas[i]], [])) | ||
i -= 1 | ||
definition.append((RCCXGate(), [q_controls[0], q_controls[1], q_ancillas[i]], [])) | ||
i -= 1 | ||
|
||
if self._dirty_ancillas: | ||
for i, j in enumerate(list(range(2, self.num_ctrl_qubits - 1))): | ||
definition.append( | ||
(RCCXGate(), [q_controls[j], q_ancillas[i], q_ancillas[i + 1]], []) | ||
) | ||
qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) | ||
|
||
for instr, qargs, cargs in definition: | ||
qc._append(instr, qargs, cargs) | ||
self.definition = qc |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
features: | ||
- | | ||
:class:`.MCXVChain` has two new Boolean parameters `relative_phase` and `action_only`. | ||
If `action_only` the circuit does not clean the dirty qubits. If `relative_phase` | ||
the gate is implemented up to a global phase. Both parameters are used to optimize the | ||
decomposition of MCXVChain. | ||
fixes: | ||
- | | ||
:class:`.MCXVChain` with k controls and k-2 dirty auxiliary qubits now requires 8k-6 cx gates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add proper documentation on all of these parameters so that the user will know what they are doing and what to choose?
It will be good to extend the docstring of the
MCXVChain
class, add a reference to the relevant paper and theorem.Perhaps even some examples if possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that there are 3 boolean parameters:
dirty_ancillas, relative_phase, action_only
Could we have all 8 options for these parameters? or only part of the options are possible?
If so, please add it to the docs, check and raise a
QiskitError
if the user chose a bad option.Otherwise, please add tests for all relevant options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there some bounds on the number of
CX
gates for these options?If so, could you please add this to the docs, as well as add proper tests.