Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1ff2894
port mcu1 from function to gate
Cryoris Feb 24, 2020
b58ad75
update init, remove old implementation
Cryoris Feb 24, 2020
72ae9c9
fix tests
Cryoris Feb 24, 2020
fdb71fd
fix cyclic import by full path
Cryoris Feb 25, 2020
d321650
Merge branch 'master' of github.com:Qiskit/qiskit-terra into implemen…
Cryoris Feb 25, 2020
79571db
make `control` use MCU1
Cryoris Feb 25, 2020
5fdba01
Merge branch 'master' into implement-mcu1-gate
Cryoris Feb 26, 2020
eb81bb2
mcu1 already importet via u1.py
Cryoris Feb 26, 2020
9222c8d
Merge branch 'master' of github.com:Qiskit/qiskit-terra into implemen…
Cryoris Feb 27, 2020
b0ee263
implement review changes and fix tests
Cryoris Feb 27, 2020
0813029
Merge branch 'master' of github.com:Qiskit/qiskit-terra into implemen…
Cryoris Mar 4, 2020
91b6aac
apply changes of the review
Cryoris Mar 4, 2020
9c6cec2
Merge branch 'master' into implement-mcu1-gate
Cryoris Mar 24, 2020
1dfccda
move MCU1 into CU1
Cryoris Mar 24, 2020
7bb7d12
try to fix docstring error?
Cryoris Mar 24, 2020
9194785
move gray code logic to external function
Cryoris Mar 25, 2020
e6a5c5e
circumvent special case distinction for CU1Gate
Cryoris Mar 25, 2020
1e691ee
Merge branch 'master' into implement-mcu1-gate
Cryoris Mar 25, 2020
f78df2f
keep mode 'noancilla'
Cryoris Mar 25, 2020
d07531d
move generate gray code to u3
Cryoris Mar 25, 2020
6191586
move determination of num free params to utils
Cryoris Mar 25, 2020
ce58bcd
Merge branch 'master' into implement-mcu1-gate
Cryoris Mar 26, 2020
bf0035c
Merge branch 'master' into implement-mcu1-gate
Cryoris Mar 26, 2020
d4457d9
Merge branch 'master' into implement-mcu1-gate
Cryoris Apr 2, 2020
60440eb
revert to MCU1 as own class
Cryoris Apr 2, 2020
5991ba8
Merge branch 'implement-mcu1-gate' of github.com:Cryoris/qiskit-terra…
Cryoris Apr 2, 2020
3fb1091
Merge branch 'master' into implement-mcu1-gate
Cryoris Apr 2, 2020
7278b59
Merge branch 'master' into implement-mcu1-gate
mergify[bot] Apr 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -100,10 +99,7 @@ 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)
Expand All @@ -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)
Comment thread
Cryoris marked this conversation as resolved.
elif theta == 0 and phi == 0:
qc.mcrz(lamb, q_control, q_target[rule[1][0].index],
use_basis_gates=True)
Expand All @@ -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()
Expand Down
1 change: 0 additions & 1 deletion qiskit/extensions/standard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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

Expand Down
111 changes: 0 additions & 111 deletions qiskit/extensions/standard/multi_control_u1_gate.py

This file was deleted.

89 changes: 75 additions & 14 deletions qiskit/extensions/standard/u1.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
ControlledGate: controlled version of this gate.
"""
if ctrl_state is None:
if num_ctrl_qubits == 1:
return CU1Gate(*self.params)
return CU1Gate(*self.params, num_ctrl_qubits)
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

Expand Down Expand Up @@ -119,9 +118,9 @@ def __instancecheck__(mcs, inst):
class CU1Gate(ControlledGate, metaclass=CU1Meta):
"""The controlled-u1 gate."""

def __init__(self, theta):
def __init__(self, theta, num_ctrl_qubits=1):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, a CU1Gate should really just mean 1 control. I think a MCU1 is a more appropriate name for more controls (similar to mcrx, mcry, etc. that we have).

Separating them gives you the flexibility to write algorithms for synthesis of multi-control gates (which are non-trivial), whereas for CU1 it's already a 2-qubit gate and is easier to decompose (requires no ancilla, routing, etc.).

I know that ultimately one is the generalization of the other, but it's more about separating out common cases. By this logic we could lump together X, CX, CCX, and MCT (which I think should be called MCX btw). Each has the same base gate, with 0, 1, 2, 3+ controls respectively. But we don't do that since X, CX and CCX are extensively studied and are common.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed, also if we find different ways to synthesize the MCU1 it makes sense to separate this logic from CU1 and have different types derived from MCU1.
@ewinston Do you agree to change this?

"""Create new cu1 gate."""
super().__init__('cu1', 2, [theta], num_ctrl_qubits=1)
super().__init__('cu1', num_ctrl_qubits + 1, [theta], num_ctrl_qubits=num_ctrl_qubits)
self.base_gate = U1Gate(theta)

def _define(self):
Expand All @@ -132,23 +131,57 @@ def _define(self):
u1(lambda/2) b;
}
"""
from qiskit.extensions.standard.x import CXGate
definition = []
q = QuantumRegister(2, 'q')
rule = [
(U1Gate(self.params[0] / 2), [q[0]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(-self.params[0] / 2), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(self.params[0] / 2), [q[1]], [])
]
q = QuantumRegister(self.num_qubits, 'q')
if self.num_ctrl_qubits == 1:
from qiskit.extensions.standard.x import CXGate
rule = [
(U1Gate(self.params[0] / 2), [q[0]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(-self.params[0] / 2), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(self.params[0] / 2), [q[1]], [])
]
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)
rule = _gray_code_chain(q, self.num_ctrl_qubits, bottom_gate)

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 CU1Gate(*self.params, num_ctrl_qubits=self.num_ctrl_qubits + num_ctrl_qubits)
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

def inverse(self):
"""Invert this gate."""
return CU1Gate(-self.params[0])
return CU1Gate(-self.params[0], num_ctrl_qubits=self.num_ctrl_qubits)

def to_matrix(self):
"""Return a numpy.array for the multi-controlled U1 gate."""
lam = self.params[0]
if self.num_ctrl_qubits == 0:
return U1Gate(lam).to_matrix()

from qiskit.extensions.unitary import _compute_control_matrix
base_mat = U1Gate(lam).to_matrix()
return _compute_control_matrix(base_mat, self.num_ctrl_qubits, ctrl_state=self.ctrl_state)
Comment thread
Cryoris marked this conversation as resolved.
Outdated


class Cu1Gate(CU1Gate, metaclass=CU1Meta):
Expand Down Expand Up @@ -191,3 +224,31 @@ def cu1(self, theta, control_qubit, target_qubit,


QuantumCircuit.cu1 = cu1


def mcu1(self, lam, control_qubits, target_qubit):
Comment thread
Cryoris marked this conversation as resolved.
Outdated
r"""Apply multi-cU1 gate.
Comment thread
Cryoris marked this conversation as resolved.
Outdated

Applied from a specified controls ``control_qubits`` to target
``target_qubit`` qubit with angle ``lam``. A multi-cU1 gate implements a
:math:`\lambda` radian rotation of the qubit state vector about the z axis
of the Bloch sphere when all control qubits are in state :math:`|1\rangle`.

Examples:

Circuit Representation:

.. jupyter-execute::

from qiskit.circuit import QuantumCircuit, Parameter

lam = Parameter('λ')
circuit = QuantumCircuit(4)
circuit.mcu1(lam, [0, 1, 2], 3)
circuit.draw()
"""
num_ctrl_qubits = len(control_qubits)
return self.append(CU1Gate(lam, num_ctrl_qubits), control_qubits[:] + [target_qubit], [])


QuantumCircuit.mcu1 = mcu1
55 changes: 55 additions & 0 deletions qiskit/extensions/standard/u3.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,58 @@ def cu3(self, theta, phi, lam, control_qubit, target_qubit,


QuantumCircuit.cu3 = cu3


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
from sympy.combinatorics.graycode import GrayCode
Comment thread
Cryoris marked this conversation as resolved.
Outdated

rule = []
q_controls, q_target = q[:num_ctrl_qubits], q[num_ctrl_qubits]
gray_code = list(GrayCode(num_ctrl_qubits).generate_gray())
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
13 changes: 7 additions & 6 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,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')
Expand Down Expand Up @@ -703,8 +704,8 @@ 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])
free_params = len(set(sig.parameters) - {'self', 'num_ctrl_qubits'})
base_gate = gate_class(*params[:free_params])
cgate = base_gate.control()
self.assertEqual(base_gate.base_gate, cgate.base_gate)

Expand Down Expand Up @@ -738,9 +739,8 @@ 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):
Expand All @@ -752,6 +752,7 @@ def test_controlled_standard_gates(self, num_ctrl_qubits):
args = [theta] * numargs
if cls in [MSGate, Barrier]:
args[0] = 2

gate = cls(*args)
try:
cgate = gate.control(num_ctrl_qubits)
Expand Down
3 changes: 2 additions & 1 deletion test/python/circuit/test_gate_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ 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'})
non_param_attributes = {'self', 'label', 'num_ctrl_qubits'}
n_params = len(set(signature(gate_class.__init__).parameters) - non_param_attributes)
Comment thread
Cryoris marked this conversation as resolved.
Outdated
param_vector = ParameterVector('th', n_params)
float_vector = [0.1 * i for i in range(n_params)]

Expand Down