diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index e3a47731febe..56ccb8bd629c 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -90,7 +90,6 @@ def control(operation: Union[Gate, ControlledGate], # pylint: disable=unused-import import qiskit.extensions.standard.multi_control_rotation_gates import qiskit.extensions.standard.multi_control_toffoli_gate - import qiskit.extensions.standard.multi_control_u1_gate q_control = QuantumRegister(num_ctrl_qubits, name='control') q_target = QuantumRegister(operation.num_qubits, name='target') @@ -100,16 +99,13 @@ def control(operation: Union[Gate, ControlledGate], if operation.name == 'x' or ( isinstance(operation, controlledgate.ControlledGate) and operation.base_gate.name == 'x'): - qc.mct(q_control[:] + q_target[:-1], - q_target[-1], - None, - mode='noancilla') + qc.mct(q_control[:] + q_target[:-1], q_target[-1], q_ancillae) elif operation.name == 'rx': qc.mcrx(operation.definition[0][0].params[0], q_control, q_target[0], use_basis_gates=True) elif operation.name == 'ry': qc.mcry(operation.definition[0][0].params[0], q_control, q_target[0], - q_ancillae, use_basis_gates=True) + q_ancillae, mode='noancilla', use_basis_gates=True) elif operation.name == 'rz': qc.mcrz(operation.definition[0][0].params[0], q_control, q_target[0], use_basis_gates=True) @@ -124,7 +120,7 @@ def control(operation: Union[Gate, ControlledGate], use_basis_gates=True) elif phi == 0 and lamb == 0: qc.mcry(theta, q_control, q_target[rule[1][0].index], - q_ancillae, mode='noancilla', use_basis_gates=True) + q_ancillae, use_basis_gates=True) elif theta == 0 and phi == 0: qc.mcrz(lamb, q_control, q_target[rule[1][0].index], use_basis_gates=True) @@ -138,10 +134,8 @@ def control(operation: Union[Gate, ControlledGate], elif rule[0].name == 'u1': qc.mcu1(rule[0].params[0], q_control, q_target[rule[1][0].index]) elif rule[0].name == 'cx': - qc.mct(q_control[:] + [q_target[rule[1][0].index]], - q_target[rule[1][1].index], - None, - mode='noancilla') + qc.mct(q_control[:] + [q_target[rule[1][0].index]], q_target[rule[1][1].index], + q_ancillae) else: raise CircuitError('gate contains non-controllable instructions') instr = qc.to_instruction() diff --git a/qiskit/extensions/standard/__init__.py b/qiskit/extensions/standard/__init__.py index b8292ee57535..9fb8782cf666 100644 --- a/qiskit/extensions/standard/__init__.py +++ b/qiskit/extensions/standard/__init__.py @@ -33,7 +33,7 @@ from .iswap import iSwapGate from .dcx import DCXGate from .t import TGate, TdgGate -from .u1 import U1Gate, CU1Gate +from .u1 import U1Gate, CU1Gate, MCU1Gate from .u2 import U2Gate from .u3 import U3Gate, CU3Gate from .x import XGate, CXGate, CCXGate @@ -41,7 +41,6 @@ from .z import ZGate, CZGate # to be converted to gates -from .multi_control_u1_gate import mcu1 from .multi_control_toffoli_gate import mct from .multi_control_rotation_gates import mcrx, mcry, mcrz diff --git a/qiskit/extensions/standard/multi_control_rotation_gates.py b/qiskit/extensions/standard/multi_control_rotation_gates.py index b10cf4585b34..8d0100f060ab 100644 --- a/qiskit/extensions/standard/multi_control_rotation_gates.py +++ b/qiskit/extensions/standard/multi_control_rotation_gates.py @@ -19,6 +19,7 @@ from math import pi from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.exceptions import QiskitError +from qiskit.extensions.standard.u3 import _generate_gray_code logger = logging.getLogger(__name__) @@ -35,15 +36,6 @@ def _apply_cu3(circuit, theta, phi, lam, control, target, use_basis_gates=True): circuit.cu3(theta, phi, lam, control, target) -def _generate_gray_code(num_bits): - if num_bits <= 0: - raise QiskitError("Must have at least 1 control in a controlled gate") - result = [0] - for i in range(num_bits): - result += [x + 2**i for x in reversed(result)] - return [format(x, "0%sb" % num_bits) for x in result] - - def _apply_mcu3_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): """Apply multi-controlled u3 gate from ctls to tgt using graycode pattern with single-step angles theta, phi, lam.""" diff --git a/qiskit/extensions/standard/multi_control_u1_gate.py b/qiskit/extensions/standard/multi_control_u1_gate.py deleted file mode 100644 index 3165a77131d6..000000000000 --- a/qiskit/extensions/standard/multi_control_u1_gate.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Multiple-Control U1 gate. Not using ancillary qubits. -""" - -import logging -from numpy import angle - -from qiskit.circuit import QuantumCircuit, QuantumRegister - -logger = logging.getLogger(__name__) - - -def _apply_cu1(circuit, lam, control, target, use_basis_gates=True): - if use_basis_gates: - circuit.u1(lam / 2, control) - circuit.cx(control, target) - circuit.u1(-lam / 2, target) - circuit.cx(control, target) - circuit.u1(lam / 2, target) - else: - circuit.cu1(lam, control, target) - - -def _apply_mcu1(circuit, lam, ctls, tgt, global_phase=0): - """Apply multi-controlled u1 gate from ctls to tgt with angle theta.""" - - n = len(ctls) - - from sympy.combinatorics.graycode import GrayCode - gray_code = list(GrayCode(n).generate_gray()) - last_pattern = None - - lam_angle = lam*(1/(2**(n-1))) - gp_angle = angle(global_phase)*(1/(2**(n-1))) - - for pattern in gray_code: - if '1' not in pattern: - continue - if last_pattern is None: - last_pattern = pattern - # find left most set bit - lm_pos = list(pattern).index('1') - - # find changed bit - comp = [i != j for i, j in zip(pattern, last_pattern)] - if True in comp: - pos = comp.index(True) - else: - pos = None - if pos is not None: - if pos != lm_pos: - circuit.cx(ctls[pos], ctls[lm_pos]) - else: - indices = [i for i, x in enumerate(pattern) if x == '1'] - for idx in indices[1:]: - circuit.cx(ctls[idx], ctls[lm_pos]) - # check parity - if pattern.count('1') % 2 == 0: - # inverse - _apply_cu1(circuit, -lam_angle, ctls[lm_pos], tgt) - if global_phase: - circuit.u1(-gp_angle, ctls[lm_pos]) - else: - _apply_cu1(circuit, lam_angle, ctls[lm_pos], tgt) - if global_phase: - circuit.u1(gp_angle, ctls[lm_pos]) - last_pattern = pattern - - -def mcu1(self, lam, control_qubits, target_qubit): - """ - Apply Multiple-Controlled U1 gate - - Args: - self (QuantumCircuit): The QuantumCircuit object to apply the mcu1 gate on. - lam (float): angle lambda - control_qubits (list(Qubit)): The list of control qubits - target_qubit (Qubit): The target qubit - """ - if isinstance(target_qubit, QuantumRegister) and len(target_qubit) == 1: - target_qubit = target_qubit[0] - temp = [] - - self._check_qargs(control_qubits) - temp += control_qubits - - self._check_qargs([target_qubit]) - temp.append(target_qubit) - - self._check_dups(temp) - n_c = len(control_qubits) - if n_c == 1: # cu1 - _apply_cu1(self, lam, control_qubits[0], target_qubit) - else: - _apply_mcu1(self, lam, control_qubits, target_qubit) - - -QuantumCircuit.mcu1 = mcu1 diff --git a/qiskit/extensions/standard/u1.py b/qiskit/extensions/standard/u1.py index b6df08f8bf3c..9b0bf78c3356 100644 --- a/qiskit/extensions/standard/u1.py +++ b/qiskit/extensions/standard/u1.py @@ -108,7 +108,8 @@ def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """ if ctrl_state is None: if num_ctrl_qubits == 1: - return CU1Gate(*self.params) + return CU1Gate(self.params[0]) + return MCU1Gate(self.params[0], num_ctrl_qubits) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @@ -192,9 +193,9 @@ def _define(self): u1(lambda/2) b; } """ - from qiskit.extensions.standard.x import CXGate definition = [] q = QuantumRegister(2, 'q') + from qiskit.extensions.standard.x import CXGate rule = [ (U1Gate(self.params[0] / 2), [q[0]], []), (CXGate(), [q[0], q[1]], []), @@ -202,10 +203,28 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (U1Gate(self.params[0] / 2), [q[1]], []) ] + for inst in rule: definition.append(inst) self.definition = definition + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): + """Controlled version of this gate. + + Args: + num_ctrl_qubits (int): number of control qubits. + 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. + + Returns: + ControlledGate: controlled version of this gate. + """ + if ctrl_state is None: + return MCU1Gate(self.params[0], num_ctrl_qubits=num_ctrl_qubits + 1) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) + def inverse(self): r"""Return inverted CU1 gate (:math:`CU1(\lambda){\dagger} = CU1(-\lambda)`)""" return CU1Gate(-self.params[0]) @@ -232,3 +251,79 @@ def cu1(self, theta, control_qubit, target_qubit, QuantumCircuit.cu1 = cu1 + + +class MCU1Gate(ControlledGate): + r"""Multi-controlled-U1 gate. + + This is a diagonal and symmetric gate that induces a + phase on the state of the target qubit, depending on the state of the control qubits. + + **Circuit symbol:** + + .. parsed-literal:: + + q_0: ────■──── + │ + . + │ + q_(n-1): ────■──── + ┌───┴───┐ + q_n: ┤ U1(λ) ├ + └───────┘ + + .. seealso:: + + :class:`~qiskit.extensions.standard.CU1Gate`: + The singly-controlled-version of this gate. + """ + + def __init__(self, lam, num_ctrl_qubits): + """Create new MCU1 gate.""" + super().__init__('mcu1', num_ctrl_qubits + 1, [lam], num_ctrl_qubits=num_ctrl_qubits) + self.base_gate = U1Gate(lam) + + def _define(self): + q = QuantumRegister(self.num_qubits, 'q') + + if self.num_ctrl_qubits == 0: + definition = U1Gate(self.params[0]).definition + if self.num_ctrl_qubits == 1: + definition = CU1Gate(self.params[0]).definition + else: + from qiskit.extensions.standard.u3 import _gray_code_chain + scaled_lam = self.params[0] / (2 ** (self.num_ctrl_qubits - 1)) + bottom_gate = CU1Gate(scaled_lam) + definition = _gray_code_chain(q, self.num_ctrl_qubits, bottom_gate) + + self.definition = definition + + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): + """Controlled version of this gate. + + Args: + num_ctrl_qubits (int): number of control qubits. + 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. + + Returns: + ControlledGate: controlled version of this gate. + """ + if ctrl_state is None: + return MCU1Gate(self.params[0], num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) + + def inverse(self): + r"""Return inverted MCU1 gate (:math:`MCU1(\lambda){\dagger} = MCU1(-\lambda)`)""" + return MCU1Gate(-self.params[0], self.num_ctrl_qubits) + + +def mcu1(self, lam, control_qubits, target_qubit): + """Apply :class:`~qiskit.extensions.standard.CU1Gate`.""" + num_ctrl_qubits = len(control_qubits) + return self.append(MCU1Gate(lam, num_ctrl_qubits), control_qubits[:] + [target_qubit], []) + + +QuantumCircuit.mcu1 = mcu1 diff --git a/qiskit/extensions/standard/u3.py b/qiskit/extensions/standard/u3.py index 8d77231a6fd1..6a347394a0b1 100644 --- a/qiskit/extensions/standard/u3.py +++ b/qiskit/extensions/standard/u3.py @@ -248,3 +248,67 @@ def cu3(self, theta, phi, lam, control_qubit, target_qubit, QuantumCircuit.cu3 = cu3 + + +def _generate_gray_code(num_bits): + """Generate the gray code for ``num_bits`` bits.""" + if num_bits <= 0: + raise ValueError('Cannot generate the gray code for less than 1 bit.') + result = [0] + for i in range(num_bits): + result += [x + 2**i for x in reversed(result)] + return [format(x, '0%sb' % num_bits) for x in result] + + +def _gray_code_chain(q, num_ctrl_qubits, gate): + """Apply the gate to the the last qubit in the register ``q``, controlled on all + preceding qubits. This function uses the gray code to propagate down to the last qubit. + + Ported and adapted from Aqua (github.com/Qiskit/qiskit-aqua), + commit 769ca8d, file qiskit/aqua/circuits/gates/multi_control_u1_gate.py. + """ + from qiskit.extensions.standard.x import CXGate + + rule = [] + q_controls, q_target = q[:num_ctrl_qubits], q[num_ctrl_qubits] + gray_code = _generate_gray_code(num_ctrl_qubits) + last_pattern = None + + for pattern in gray_code: + if '1' not in pattern: + continue + if last_pattern is None: + last_pattern = pattern + # find left most set bit + lm_pos = list(pattern).index('1') + + # find changed bit + comp = [i != j for i, j in zip(pattern, last_pattern)] + if True in comp: + pos = comp.index(True) + else: + pos = None + if pos is not None: + if pos != lm_pos: + rule.append( + (CXGate(), [q_controls[pos], q_controls[lm_pos]], []) + ) + else: + indices = [i for i, x in enumerate(pattern) if x == '1'] + for idx in indices[1:]: + rule.append( + (CXGate(), [q_controls[idx], q_controls[lm_pos]], []) + ) + # check parity + if pattern.count('1') % 2 == 0: + # inverse + rule.append( + (gate.inverse(), [q_controls[lm_pos], q_target], []) + ) + else: + rule.append( + (gate, [q_controls[lm_pos], q_target], []) + ) + last_pattern = pattern + + return rule diff --git a/test/python/circuit/gate_utils.py b/test/python/circuit/gate_utils.py new file mode 100644 index 000000000000..4c22cf85a5d3 --- /dev/null +++ b/test/python/circuit/gate_utils.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Utils for testing the standard gates.""" + +from inspect import signature, Parameter + + +def _get_free_params(fun, ignore=None): + """Get the names of the free parameters of the function ``f``. + + Args: + fun (callable): The function to inspect. + ignore (list[str]): A list of argument names (as str) to ignore. + + Returns: + list[str]: The name of the free parameters not listed in ``ignore``. + """ + ignore = ignore or [] + free_params = [] + for name, param in signature(fun).parameters.items(): + if param.default == Parameter.empty and param.kind != Parameter.VAR_POSITIONAL: + if name not in ignore: + free_params.append(name) + return free_params diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 0a03074d4c73..20bc560bc805 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -16,7 +16,6 @@ """Test Qiskit's inverse gate operation.""" import unittest -from inspect import signature from test import combine import numpy as np from numpy import pi @@ -43,6 +42,8 @@ from qiskit.extensions.unitary import _compute_control_matrix import qiskit.extensions.standard as allGates +from .gate_utils import _get_free_params + @ddt class TestControlledGate(QiskitTestCase): @@ -345,9 +346,10 @@ def test_multi_control_toffoli_matrix_clean_ancillas(self, num_controls): qc.add_register(q_ancillas) else: num_ancillas = 0 + q_ancillas = None # apply hadamard on control qubits and toffoli gate - qc.mct(q_controls, q_target[0], None, mode='basic') + qc.mct(q_controls, q_target[0], q_ancillas, mode='basic') # execute the circuit and obtain statevector result backend = BasicAer.get_backend('unitary_simulator') @@ -702,9 +704,11 @@ def test_base_gate_setting(self): """ params = [0.1 * i for i in range(10)] for gate_class in ControlledGate.__subclasses__(): - sig = signature(gate_class.__init__) - free_params = len(sig.parameters) - 1 # subtract "self" - base_gate = gate_class(*params[0:free_params]) + num_free_params = len(_get_free_params(gate_class.__init__, ignore=['self'])) + free_params = params[:num_free_params] + if gate_class in [allGates.MCU1Gate]: + free_params[1] = 3 + base_gate = gate_class(*free_params) cgate = base_gate.control() self.assertEqual(base_gate.base_gate, cgate.base_gate) @@ -722,11 +726,7 @@ def test_all_inverses(self): if issubclass(cls, ControlledGate) or issubclass(cls, allGates.IGate): continue try: - sig = signature(cls) - numargs = len([param for param in sig.parameters.values() - if param.kind == param.POSITIONAL_ONLY - or (param.kind == param.POSITIONAL_OR_KEYWORD - and param.default is param.empty)]) + numargs = len(_get_free_params(cls)) args = [2]*numargs gate = cls(*args) @@ -738,20 +738,18 @@ def test_all_inverses(self): @data(1, 2, 3) def test_controlled_standard_gates(self, num_ctrl_qubits): - """Test the controlled versions of all standard gates.""" - gate_classes = [cls for name, cls in allGates.__dict__.items() - if isinstance(cls, type)] + """Test controlled versions of all standard gates.""" + gate_classes = [cls for cls in allGates.__dict__.values() if isinstance(cls, type)] theta = pi / 2 for cls in gate_classes: with self.subTest(i=cls): - sig = signature(cls) - numargs = len([param for param in sig.parameters.values() - if param.kind == param.POSITIONAL_ONLY or - (param.kind == param.POSITIONAL_OR_KEYWORD and - param.default is param.empty)]) + numargs = len(_get_free_params(cls)) args = [theta] * numargs if cls in [MSGate, Barrier]: args[0] = 2 + elif cls in [allGates.MCU1Gate]: + args[1] = 2 + gate = cls(*args) try: cgate = gate.control(num_ctrl_qubits) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 728d67ac8cfd..3559a5dbd800 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -15,8 +15,6 @@ """Test hardcoded decomposition rules and matrix definitions for standard gates.""" -from inspect import signature - from ddt import ddt, data from qiskit import QuantumCircuit @@ -34,6 +32,8 @@ from qiskit.extensions.standard.equivalence_library import StandardEquivalenceLibrary as std_eqlib +from .gate_utils import _get_free_params + class TestGateDefinitions(QiskitTestCase): """Test the decomposition of a gate in terms of other gates @@ -124,8 +124,7 @@ class TestStandardEquivalenceLibrary(QiskitTestCase): ) def test_definition_parameters(self, gate_class): """Verify decompositions from standard equivalence library match definitions.""" - - n_params = len(set(signature(gate_class.__init__).parameters) - {'label', 'self'}) + n_params = len(_get_free_params(gate_class)) param_vector = ParameterVector('th', n_params) float_vector = [0.1 * i for i in range(n_params)]