From d2aa900fc00757f1e5f98976decab10caec8af4b Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 21 Jan 2021 17:47:41 +0100 Subject: [PATCH 1/3] More efficient C3X implementation (#5668) * update c3x decomp * fix call with "angle" argument * rename to C3SX and fix tests * fix typo * add reno (cherry picked from commit b9c943b17cd209a2db92a504ed99f061f4eb0e96) # Conflicts: # qiskit/circuit/library/standard_gates/x.py --- qiskit/circuit/library/__init__.py | 1 + .../library/standard_gates/__init__.py | 3 +- qiskit/circuit/library/standard_gates/x.py | 165 ++++++++++++++++-- qiskit/qasm/libs/qelib1.inc | 44 +++-- ...ed-c3x-decomposition-aa812a0bc8280c7a.yaml | 7 + test/python/circuit/test_controlled_gate.py | 6 +- 6 files changed, 194 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/improved-c3x-decomposition-aa812a0bc8280c7a.yaml diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index c466535ff244..00097edab4d1 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -25,6 +25,7 @@ Barrier C3XGate + C3SXGate C4XGate CCXGate DCXGate diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 8da0eb854855..8a9482fd8e00 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -19,6 +19,7 @@ :toctree: ../stubs/ C3XGate + C3SXGate C4XGate CCXGate DCXGate @@ -89,7 +90,7 @@ from .u1 import U1Gate, CU1Gate, MCU1Gate from .u2 import U2Gate from .u3 import U3Gate, CU3Gate -from .x import XGate, CXGate, CCXGate, C3XGate, C4XGate, RCCXGate, RC3XGate +from .x import XGate, CXGate, CCXGate, C3XGate, C3SXGate, C4XGate, RCCXGate, RC3XGate from .x import MCXGate, MCXGrayCode, MCXRecursive, MCXVChain from .y import YGate, CYGate from .z import ZGate, CZGate diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 7641a9930378..11e790750dbb 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -12,6 +12,7 @@ """X, CX, CCX and multi-controlled X gates.""" +import warnings from math import ceil import numpy from qiskit.circuit.controlledgate import ControlledGate @@ -23,6 +24,7 @@ from .t import TGate, TdgGate from .u1 import U1Gate from .u2 import U2Gate +from .sx import SXGate class XGate(Gate): @@ -413,8 +415,8 @@ def to_matrix(self): [0, 0, 0, 1j, 0, 0, 0, 0]], dtype=complex) -class C3XGate(ControlledGate): - """The 3-qubit controlled X gate. +class C3SXGate(ControlledGate): + """The 3-qubit controlled sqrt-X gate. This implementation is based on Page 17 of [1]. @@ -422,44 +424,53 @@ class C3XGate(ControlledGate): [1] Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf """ - def __init__(self, angle=numpy.pi/4, label=None, ctrl_state=None): - """Create a new 3-qubit controlled X gate. + def __init__(self, label=None, ctrl_state=None, *, angle=None): + """Create a new 3-qubit controlled sqrt-X gate. Args: - angle (float): The angle used in the controlled-U1 gates. An angle of π/4 yields the - 3-qubit controlled X gate, an angle of π/8 the 3-qubit controlled sqrt(X) gate. label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + angle (float): DEPRECATED. The angle used in the controlled-U1 gates. An angle of π/8 + yields the sqrt(X) gates, an angle of π/4 the 3-qubit controlled X gate. """ - super().__init__('mcx', 4, [], num_ctrl_qubits=3, label=label, - ctrl_state=ctrl_state, base_gate=XGate()) + super().__init__('c3sx', 4, [], num_ctrl_qubits=3, label=label, + ctrl_state=ctrl_state, base_gate=SXGate()) + + if angle is not None: + warnings.warn('The angle argument is deprecated as of Qiskit Terra 0.17.0 and will ' + 'be removed no earlier than 3 months after the release date.', + DeprecationWarning, stacklevel=2) + + if angle is None: + angle = numpy.pi / 8 self._angle = angle def _define(self): """ - gate c3x a,b,c,d + gate c3sqrtx a,b,c,d { - h d; cu1(-pi/4) a,d; h d; + h d; cu1(-pi/8) a,d; h d; cx a,b; - h d; cu1(pi/4) b,d; h d; + h d; cu1(pi/8) b,d; h d; cx a,b; - h d; cu1(-pi/4) b,d; h d; + h d; cu1(-pi/8) b,d; h d; cx b,c; - h d; cu1(pi/4) c,d; h d; + h d; cu1(pi/8) c,d; h d; cx a,c; - h d; cu1(-pi/4) c,d; h d; + h d; cu1(-pi/8) c,d; h d; cx b,c; - h d; cu1(pi/4) c,d; h d; + h d; cu1(pi/8) c,d; h d; cx a,c; - h d; cu1(-pi/4) c,d; h d; + h d; cu1(-pi/8) c,d; h d; } """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit from .u1 import CU1Gate q = QuantumRegister(4, name='q') + # pylint: disable=invalid-unary-operand-type rules = [ (HGate(), [q[3]], []), (CU1Gate(-self._angle), [q[0], q[3]], []), @@ -495,6 +506,109 @@ def _define(self): self.definition = qc + def inverse(self): + """Invert this gate. The C3X is its own inverse.""" + # pylint: disable=invalid-unary-operand-type + if self._angle is not None: + angle = -self._angle + else: + angle = None + + return C3SXGate(angle=angle, ctrl_state=self.ctrl_state) + + +class C3XGate(ControlledGate): + r"""The 4-qubit controlled X gate. + + This implementation uses :math:`\sqrt{T}` and 14 CNOT gates. + """ + + def __new__(cls, angle=None, label=None, ctrl_state=None): + if angle is not None: + return C3SXGate(label, ctrl_state, angle=angle) + + instance = super().__new__(cls) + instance.__init__(None, label, ctrl_state) + return instance + + # pylint: disable=unused-argument + def __init__(self, angle=None, label=None, ctrl_state=None): + """Create a new 3-qubit controlled X gate.""" + super().__init__('mcx', 4, [], num_ctrl_qubits=3, label=label, + ctrl_state=ctrl_state, base_gate=XGate()) + + # seems like open controls not hapening? + def _define(self): + """ + gate c3x a,b,c,d + { + h d; + p(pi/8) a; + p(pi/8) b; + p(pi/8) c; + p(pi/8) d; + cx a, b; + p(-pi/8) b; + cx a, b; + cx b, c; + p(-pi/8) c; + cx a, c; + p(pi/8) c; + cx b, c; + p(-pi/8) c; + cx a, c; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + h d; + } + """ + from qiskit.circuit.quantumcircuit import QuantumCircuit + q = QuantumRegister(4, name='q') + qc = QuantumCircuit(q, name=self.name) + qc.h(3) + qc.p(pi / 8, [0, 1, 2, 3]) + qc.cx(0, 1) + qc.p(-pi / 8, 1) + qc.cx(0, 1) + qc.cx(1, 2) + qc.p(-pi / 8, 2) + qc.cx(0, 2) + qc.p(pi / 8, 2) + qc.cx(1, 2) + qc.p(-pi / 8, 2) + qc.cx(0, 2) + qc.cx(2, 3) + qc.p(-pi / 8, 3) + qc.cx(1, 3) + qc.p(pi / 8, 3) + qc.cx(2, 3) + qc.p(-pi / 8, 3) + qc.cx(0, 3) + qc.p(pi / 8, 3) + qc.cx(2, 3) + qc.p(-pi / 8, 3) + qc.cx(1, 3) + qc.p(pi / 8, 3) + qc.cx(2, 3) + qc.p(-pi / 8, 3) + qc.cx(0, 3) + qc.h(3) + + self.definition = qc + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. @@ -514,6 +628,7 @@ def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): return gate def inverse(self): +<<<<<<< HEAD """Invert this gate. The C3X is its own inverse.""" return C3XGate(angle=-self._angle, ctrl_state=self.ctrl_state) @@ -536,6 +651,19 @@ def inverse(self): # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], # [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=complex) +======= + """Invert this gate. The C4X is its own inverse.""" + return C3XGate(ctrl_state=self.ctrl_state) + + def __array__(self, dtype=None): + """Return a numpy.array for the C4X gate.""" + mat = _compute_control_matrix(self.base_gate.to_matrix(), + self.num_ctrl_qubits, + ctrl_state=self.ctrl_state) + if dtype: + return numpy.asarray(mat, dtype=dtype) + return mat +>>>>>>> b9c943b17... More efficient C3X implementation (#5668) class RC3XGate(Gate): @@ -680,8 +808,13 @@ def _define(self): (HGate(), [q[4]], []), (CU1Gate(numpy.pi / 2), [q[3], q[4]], []), (HGate(), [q[4]], []), +<<<<<<< HEAD (C3XGate(), [q[0], q[1], q[2], q[3]], []), (C3XGate(numpy.pi / 8), [q[0], q[1], q[2], q[4]], []), +======= + (RC3XGate().inverse(), [q[0], q[1], q[2], q[3]], []), + (C3SXGate(), [q[0], q[1], q[2], q[4]], []), +>>>>>>> b9c943b17... More efficient C3X implementation (#5668) ] for instr, qargs, cargs in rules: qc._append(instr, qargs, cargs) diff --git a/qiskit/qasm/libs/qelib1.inc b/qiskit/qasm/libs/qelib1.inc index c99fb6e2cc8b..66b6b8c6b0e9 100644 --- a/qiskit/qasm/libs/qelib1.inc +++ b/qiskit/qasm/libs/qelib1.inc @@ -206,19 +206,37 @@ gate rc3x a,b,c,d // 3-controlled X gate gate c3x a,b,c,d { - h d; cu1(-pi/4) a,d; h d; - cx a,b; - h d; cu1(pi/4) b,d; h d; - cx a,b; - h d; cu1(-pi/4) b,d; h d; - cx b,c; - h d; cu1(pi/4) c,d; h d; - cx a,c; - h d; cu1(-pi/4) c,d; h d; - cx b,c; - h d; cu1(pi/4) c,d; h d; - cx a,c; - h d; cu1(-pi/4) c,d; h d; + h d; + p(pi/8) a; + p(pi/8) b; + p(pi/8) c; + p(pi/8) d; + cx a, b; + p(-pi/8) b; + cx a, b; + cx b, c; + p(-pi/8) c; + cx a, c; + p(pi/8) c; + cx b, c; + p(-pi/8) c; + cx a, c; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + h d; } // 3-controlled sqrt(X) gate, this equals the C3X gate where the CU1 rotations are -pi/8 not -pi/4 gate c3sqrtx a,b,c,d diff --git a/releasenotes/notes/improved-c3x-decomposition-aa812a0bc8280c7a.yaml b/releasenotes/notes/improved-c3x-decomposition-aa812a0bc8280c7a.yaml new file mode 100644 index 000000000000..557911c6946b --- /dev/null +++ b/releasenotes/notes/improved-c3x-decomposition-aa812a0bc8280c7a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Reduce the number of CX gates in the decomposition of the 3-controlled + X gate, :class:`~qiskit.circuit.library.C3XGate`. Compiled and optimized + in the `U CX` basis, now only 14 CX and 16 U gates are used instead of + 20 and 22, respectively. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 1b961674d98e..2f6421c743d3 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -38,8 +38,8 @@ U3Gate, CHGate, CRZGate, CU3Gate, CUGate, SXGate, CSXGate, MSGate, Barrier, RCCXGate, RC3XGate, MCU1Gate, MCXGate, MCXGrayCode, - MCXRecursive, MCXVChain, C3XGate, C4XGate, - MCPhaseGate) + MCXRecursive, MCXVChain, C3XGate, C3SXGate, + C4XGate, MCPhaseGate) from qiskit.circuit._utils import _compute_control_matrix import qiskit.circuit.library.standard_gates as allGates from qiskit.extensions import UnitaryGate @@ -1208,6 +1208,7 @@ def test_controlled_standard_gates(self, num_ctrl_qubits, gate_class): base_mat = (np.cos(0.5 * theta) * iden - 1j * np.sin(0.5 * theta) * zgen).data else: base_mat = Operator(gate).data + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) self.assertEqual(Operator(cgate), Operator(target_mat)) @@ -1251,6 +1252,7 @@ class TestControlledGateLabel(QiskitTestCase): (CXGate, []), (CCXGate, []), (C3XGate, []), + (C3SXGate, []), (C4XGate, []), (MCXGate, [5]), (PhaseGate, [0.1]), From 06e74417ac31f88d1161fdb83005eb3ac7b2315a Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 21 Jan 2021 18:23:22 +0100 Subject: [PATCH 2/3] fix merge conflicts --- qiskit/circuit/library/standard_gates/x.py | 30 ---------------------- 1 file changed, 30 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 11e790750dbb..16fb44f0ce91 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -628,30 +628,6 @@ def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): return gate def inverse(self): -<<<<<<< HEAD - """Invert this gate. The C3X is its own inverse.""" - return C3XGate(angle=-self._angle, ctrl_state=self.ctrl_state) - - # This matrix is only correct if the angle is pi/4 - # def to_matrix(self): - # """Return a numpy.array for the C3X gate.""" - # return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], - # [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], - # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], - # [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=complex) -======= """Invert this gate. The C4X is its own inverse.""" return C3XGate(ctrl_state=self.ctrl_state) @@ -663,7 +639,6 @@ def __array__(self, dtype=None): if dtype: return numpy.asarray(mat, dtype=dtype) return mat ->>>>>>> b9c943b17... More efficient C3X implementation (#5668) class RC3XGate(Gate): @@ -808,13 +783,8 @@ def _define(self): (HGate(), [q[4]], []), (CU1Gate(numpy.pi / 2), [q[3], q[4]], []), (HGate(), [q[4]], []), -<<<<<<< HEAD - (C3XGate(), [q[0], q[1], q[2], q[3]], []), - (C3XGate(numpy.pi / 8), [q[0], q[1], q[2], q[4]], []), -======= (RC3XGate().inverse(), [q[0], q[1], q[2], q[3]], []), (C3SXGate(), [q[0], q[1], q[2], q[4]], []), ->>>>>>> b9c943b17... More efficient C3X implementation (#5668) ] for instr, qargs, cargs in rules: qc._append(instr, qargs, cargs) From d238300ea8c470a3c502a17e9d9130bf8e8ea5de Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 21 Jan 2021 18:45:51 +0100 Subject: [PATCH 3/3] fix test --- qiskit/circuit/library/standard_gates/x.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 16fb44f0ce91..1711d113d045 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -779,7 +779,7 @@ def _define(self): (HGate(), [q[4]], []), (CU1Gate(-numpy.pi / 2), [q[3], q[4]], []), (HGate(), [q[4]], []), - (C3XGate(), [q[0], q[1], q[2], q[3]], []), + (RC3XGate(), [q[0], q[1], q[2], q[3]], []), (HGate(), [q[4]], []), (CU1Gate(numpy.pi / 2), [q[3], q[4]], []), (HGate(), [q[4]], []),