Skip to content

Commit

Permalink
add global_phase to QuantumCircuit class (#4565)
Browse files Browse the repository at this point in the history
* minor commit

* rz agrees with u1

* synthesis tests pass

* passing tests except open controlled random unitary

* resolve most errors in test_unroller

* passing tests

* move global phase change to circuit_to_gate

* add missing to_matrix to [most] gates

* revert base gate of c3x

* definition changed to circuit

some multi-qubit controlled gate tests are still failing.

* resolve c_if register conversion

* revert removal of _define in standard gates

* linting

* resolve qreg bug in transpiler

* simplify unroller passes a bit

* linting

* resolve doc forward ref

* linting

* update u3 cx equivalence_library

* put phase back into gate class definitions

This is allowed now that gate definitions are circuits.

* added checks for circuit inverse, compose, and state.evolve.

* passing tests

* linting

* revert phase accurate one_qubit_decompose for future pr.

* linting

* Update test/python/circuit/test_gate_definitions.py

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>

* add release notes

* revert equiv test

_self._gate_classes can't be accessed from @DaTa and putting it into the
class level, like done in other tests, causes gate code to be "tested"
outside of tests.

* update release notes

* Update qiskit/circuit/quantumcircuit.py

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>

* don't unroll global phase to single qubit gates

* typeo

* linting

* linting

* update cx_global_phase test

* minor linting

* Update releasenotes/notes/add_quantumcircuit_phase-5006d1e930348d2e.yaml

* Update releasenotes/notes/add_quantumcircuit_phase-5006d1e930348d2e.yaml

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 25, 2020
1 parent 127b5ab commit 8f717d9
Show file tree
Hide file tree
Showing 37 changed files with 860 additions and 358 deletions.
104 changes: 61 additions & 43 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,57 +99,75 @@ def control(operation: Union[Gate, ControlledGate],
from math import pi
# pylint: disable=cyclic-import
import qiskit.circuit.controlledgate as controlledgate
# pylint: disable=unused-import
import qiskit.circuit.library.standard_gates.multi_control_rotation_gates

q_control = QuantumRegister(num_ctrl_qubits, name='control')
q_target = QuantumRegister(operation.num_qubits, name='target')
q_ancillae = None # TODO: add
qc = QuantumCircuit(q_control, q_target)

controlled_circ = QuantumCircuit(q_control, q_target,
name='c_{}'.format(operation.name))
global_phase = 0
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], q_ancillae)
elif operation.name == 'rx':
qc.mcrx(operation.definition.data[0][0].params[0], q_control, q_target[0],
use_basis_gates=True)
elif operation.name == 'ry':
qc.mcry(operation.definition.data[0][0].params[0], q_control, q_target[0],
q_ancillae, mode='noancilla', use_basis_gates=True)
elif operation.name == 'rz':
qc.mcrz(operation.definition.data[0][0].params[0], q_control, q_target[0],
use_basis_gates=True)
controlled_circ.mct(q_control[:] + q_target[:-1], q_target[-1], q_ancillae)
if operation.definition is not None and operation.definition.global_phase:
global_phase += operation.definition.global_phase
else:
bgate = _unroll_gate(operation, ['u1', 'u3', 'cx'])
# now we have a bunch of single qubit rotation gates and cx
for rule in bgate.definition.data:
if rule[0].name == 'u3':
theta, phi, lamb = rule[0].params
basis = ['u1', 'u3', 'x', 'rx', 'ry', 'rz', 'cx']
unrolled_gate = _unroll_gate(operation, basis_gates=basis)
for gate, qreg, _ in unrolled_gate.definition.data:
if gate.name == 'x':
controlled_circ.mct(q_control, q_target[qreg[0].index],
q_ancillae)
elif gate.name == 'rx':
controlled_circ.mcrx(gate.definition.data[0][0].params[0],
q_control, q_target[qreg[0].index],
use_basis_gates=True)
elif gate.name == 'ry':
controlled_circ.mcry(gate.definition.data[0][0].params[0],
q_control, q_target[qreg[0].index],
q_ancillae, mode='noancilla',
use_basis_gates=True)
elif gate.name == 'rz':
controlled_circ.mcrz(gate.definition.data[0][0].params[0],
q_control, q_target[qreg[0].index],
use_basis_gates=True)
elif gate.name == 'u1':
controlled_circ.mcu1(gate.params[0], q_control, q_target[qreg[0].index])
elif gate.name == 'cx':
controlled_circ.mct(q_control[:] + [q_target[qreg[0].index]],
q_target[qreg[1].index],
q_ancillae)
elif gate.name == 'u3':
theta, phi, lamb = gate.params
if phi == -pi / 2 and lamb == pi / 2:
qc.mcrx(theta, q_control, q_target[rule[1][0].index],
use_basis_gates=True)
controlled_circ.mcrx(theta, q_control, q_target[qreg[0].index],
use_basis_gates=True)
elif phi == 0 and lamb == 0:
qc.mcry(theta, q_control, q_target[rule[1][0].index],
q_ancillae, use_basis_gates=True)
controlled_circ.mcry(theta, q_control, q_target[qreg[0].index],
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)
controlled_circ.mcrz(lamb, q_control, q_target[qreg[0].index],
use_basis_gates=True)
else:
qc.mcrz(lamb, q_control, q_target[rule[1][0].index],
use_basis_gates=True)
qc.mcry(theta, q_control, q_target[rule[1][0].index],
q_ancillae, use_basis_gates=True)
qc.mcrz(phi, q_control, q_target[rule[1][0].index],
use_basis_gates=True)
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],
q_ancillae)
controlled_circ.mcrz(lamb, q_control, q_target[qreg[0].index],
use_basis_gates=True)
controlled_circ.mcry(theta, q_control, q_target[qreg[0].index],
q_ancillae, use_basis_gates=True)
controlled_circ.mcrz(phi, q_control, q_target[qreg[0].index],
use_basis_gates=True)
else:
raise CircuitError('gate contains non-controllable instructions')

raise CircuitError('gate contains non-controllable instructions: {}'.format(
gate.name))
if gate.definition is not None and gate.definition.global_phase:
global_phase += gate.definition.global_phase
# apply controlled global phase
if ((operation.definition is not None and operation.definition.global_phase) or global_phase):
if len(q_control) < 2:
controlled_circ.u1(operation.definition.global_phase + global_phase, q_control)
else:
controlled_circ.mcu1(operation.definition.global_phase + global_phase,
q_control[:-1], q_control[-1])
if isinstance(operation, controlledgate.ControlledGate):
new_num_ctrl_qubits = num_ctrl_qubits + operation.num_ctrl_qubits
new_ctrl_state = operation.ctrl_state << num_ctrl_qubits | ctrl_state
Expand All @@ -170,11 +188,11 @@ def control(operation: Union[Gate, ControlledGate],
ctrl_substr = ('{0}' * new_num_ctrl_qubits).format('c')
new_name = '{0}{1}'.format(ctrl_substr, base_name)
cgate = controlledgate.ControlledGate(new_name,
qc.num_qubits,
controlled_circ.num_qubits,
operation.params,
label=label,
num_ctrl_qubits=new_num_ctrl_qubits,
definition=qc,
definition=controlled_circ,
ctrl_state=new_ctrl_state)
cgate.base_gate = base_gate
return cgate
Expand All @@ -196,7 +214,7 @@ def _gate_to_circuit(operation):

def _gate_to_dag(operation):
from qiskit.converters.circuit_to_dag import circuit_to_dag
if hasattr(operation, 'definition') and operation.definition:
if hasattr(operation, 'definition') and operation.definition is not None:
return circuit_to_dag(operation.definition)
else:
qr = QuantumRegister(operation.num_qubits)
Expand All @@ -210,5 +228,5 @@ def _unroll_gate(operation, basis_gates):
from qiskit.transpiler.passes import Unroller
unroller = Unroller(basis_gates)
dag = _gate_to_dag(operation)
qc = dag_to_circuit(unroller.run(dag))
return qc.to_gate()
opqc = dag_to_circuit(unroller.run(dag))
return opqc.to_gate()
3 changes: 2 additions & 1 deletion qiskit/circuit/controlledgate.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,5 @@ def __eq__(self, other) -> bool:

def inverse(self) -> 'ControlledGate':
"""Invert this gate by calling inverse on the base gate."""
return self.base_gate.inverse().control(self.num_ctrl_qubits)
return self.base_gate.inverse().control(self.num_ctrl_qubits,
ctrl_state=self.ctrl_state)
1 change: 0 additions & 1 deletion qiskit/circuit/equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ def _raise_if_shape_mismatch(gate, circuit):

def _rebind_equiv(equiv, query_params):
equiv_params, equiv_circuit = equiv

param_map = dict(zip(equiv_params, query_params))
equiv = equiv_circuit.assign_parameters(param_map, inplace=False)

Expand Down
29 changes: 23 additions & 6 deletions qiskit/circuit/library/standard_gates/equivalence_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
RZGate,
CRZGate,
RZZGate,
RZXGate,
SGate,
SdgGate,
SwapGate,
Expand Down Expand Up @@ -158,14 +159,30 @@
(HGate(), [q[0]], []),
(HGate(), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(theta), [q[1]], []),
(RZGate(theta), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(HGate(), [q[1]], []),
(HGate(), [q[0]], []),
]:
def_rxx.append(inst, qargs, cargs)
_sel.add_equivalence(RXXGate(theta), def_rxx)

# RZXGate

q = QuantumRegister(2, 'q')
theta = Parameter('theta')
def_rzx = QuantumCircuit(q)
for inst, qargs, cargs in [
(HGate(), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(RZGate(theta), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(HGate(), [q[1]], []),
]:
def_rzx.append(inst, qargs, cargs)
_sel.add_equivalence(RZXGate(theta), def_rzx)


# RYGate

q = QuantumRegister(1, 'q')
Expand Down Expand Up @@ -209,7 +226,7 @@

q = QuantumRegister(1, 'q')
theta = Parameter('theta')
def_rz = QuantumCircuit(q)
def_rz = QuantumCircuit(q, global_phase=-theta / 2)
def_rz.append(U1Gate(theta), [q[0]], [])
_sel.add_equivalence(RZGate(theta), def_rz)

Expand Down Expand Up @@ -242,7 +259,7 @@
def_rzz = QuantumCircuit(q)
for inst, qargs, cargs in [
(CXGate(), [q[0], q[1]], []),
(U1Gate(theta), [q[1]], []),
(RZGate(theta), [q[1]], []),
(CXGate(), [q[0], q[1]], [])
]:
def_rzz.append(inst, qargs, cargs)
Expand Down Expand Up @@ -338,7 +355,7 @@
def_tdg.append(U1Gate(-pi / 4), [q[0]], [])
_sel.add_equivalence(TdgGate(), def_tdg)

# U1Gate
# U2Gate

q = QuantumRegister(1, 'q')
phi = Parameter('phi')
Expand Down Expand Up @@ -376,7 +393,7 @@
theta = Parameter('theta')
phi = Parameter('phi')
lam = Parameter('lam')
u3_qasm_def = QuantumCircuit(q)
u3_qasm_def = QuantumCircuit(q, global_phase=(lam + phi) / 2)
u3_qasm_def.rz(lam, 0)
u3_qasm_def.rx(pi/2, 0)
u3_qasm_def.rz(theta+pi, 0)
Expand Down Expand Up @@ -427,7 +444,7 @@
_sel.add_equivalence(CXGate(), cx_to_cz)

q = QuantumRegister(2, 'q')
cx_to_iswap = QuantumCircuit(q)
cx_to_iswap = QuantumCircuit(q, global_phase=3*pi/4)
for inst, qargs, cargs in [
(HGate(), [q[0]], []),
(XGate(), [q[1]], []),
Expand Down
3 changes: 2 additions & 1 deletion qiskit/circuit/library/standard_gates/ms.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit
from .rxx import RXXGate
theta = self.params[0]
q = QuantumRegister(self.num_qubits, 'q')
qc = QuantumCircuit(q, name=self.name)
rules = []
for i in range(self.num_qubits):
for j in range(i + 1, self.num_qubits):
rules += [(RXXGate(self.params[0]), [q[i], q[j]], [])]
rules += [(RXXGate(theta), [q[i], q[j]], [])]
qc._data = rules
self.definition = qc
29 changes: 17 additions & 12 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,23 @@ def inverse(self):
"""Return inverse RX gate (i.e. with the negative rotation angle)."""
return CRXGate(-self.params[0])

# TODO: this is the correct definition but has a global phase with respect
# to the decomposition above. Restore after allowing phase on circuits.
# def to_matrix(self):
# """Return a numpy.array for the CRX gate."""
# half_theta = self.params[0] / 2
# cos = numpy.cos(half_theta)
# isin = 1j * numpy.sin(half_theta)
# return numpy.array([[1, 0, 0, 0],
# [0, cos, 0, -isin],
# [0, 0, 1, 0],
# [0, -isin, 0, cos]],
# dtype=complex)
def to_matrix(self):
"""Return a numpy.array for the CRX gate."""
half_theta = self.params[0] / 2
cos = numpy.cos(half_theta)
isin = 1j * numpy.sin(half_theta)
if self.ctrl_state:
return numpy.array([[1, 0, 0, 0],
[0, cos, 0, -isin],
[0, 0, 1, 0],
[0, -isin, 0, cos]],
dtype=complex)
else:
return numpy.array([[cos, 0, -isin, 0],
[0, 1, 0, 0],
[-isin, 0, cos, 0],
[0, 0, 0, 1]],
dtype=complex)


class CrxGate(CRXGate, metaclass=CRXMeta):
Expand Down
28 changes: 14 additions & 14 deletions qiskit/circuit/library/standard_gates/rxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit
from .x import CXGate
from .u1 import U1Gate
from .h import HGate
from .rz import RZGate
theta = self.params[0]
q = QuantumRegister(2, 'q')
qc = QuantumCircuit(q, name=self.name)
theta = self.params[0]
rules = [
(HGate(), [q[0]], []),
(HGate(), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(U1Gate(theta), [q[1]], []),
(RZGate(theta), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(HGate(), [q[1]], []),
(HGate(), [q[0]], []),
Expand All @@ -98,14 +98,14 @@ def inverse(self):
"""Return inverse RXX gate (i.e. with the negative rotation angle)."""
return RXXGate(-self.params[0])

# NOTE: we should use the following as the canonical matrix
# definition but we don't include it yet since it differs from
# the circuit decomposition matrix by a global phase
# def to_matrix(self):
# """Return a Numpy.array for the RXX gate."""
# theta = float(self.params[0])
# return np.array([
# [np.cos(theta / 2), 0, 0, -1j * np.sin(theta / 2)],
# [0, np.cos(theta / 2), -1j * np.sin(theta / 2), 0],
# [0, -1j * np.sin(theta / 2), np.cos(theta / 2), 0],
# [-1j * np.sin(theta / 2), 0, 0, np.cos(theta / 2)]], dtype=complex)
def to_matrix(self):
"""Return a Numpy.array for the RXX gate."""
import numpy
theta2 = float(self.params[0]) / 2
cos = numpy.cos(theta2)
isin = 1j * numpy.sin(theta2)
return numpy.array([
[cos, 0, 0, -isin],
[0, cos, -isin, 0],
[0, -isin, cos, 0],
[-isin, 0, 0, cos]], dtype=complex)
29 changes: 17 additions & 12 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,23 @@ def inverse(self):
"""Return inverse RY gate (i.e. with the negative rotation angle)."""
return CRYGate(-self.params[0])

# TODO: this is the correct definition but has a global phase with respect
# to the decomposition above. Restore after allowing phase on circuits.
# def to_matrix(self):
# """Return a numpy.array for the CRY gate."""
# half_theta = self.params[0] / 2
# cos = numpy.cos(half_theta)
# sin = numpy.sin(half_theta)
# return numpy.array([[1, 0, 0, 0],
# [0, cos, 0, -sin],
# [0, 0, 1, 0],
# [0, sin, 0, cos]],
# dtype=complex)
def to_matrix(self):
"""Return a numpy.array for the CRY gate."""
half_theta = self.params[0] / 2
cos = numpy.cos(half_theta)
sin = numpy.sin(half_theta)
if self.ctrl_state:
return numpy.array([[1, 0, 0, 0],
[0, cos, 0, -sin],
[0, 0, 1, 0],
[0, sin, 0, cos]],
dtype=complex)
else:
return numpy.array([[cos, 0, -sin, 0],
[0, 1, 0, 0],
[sin, 0, cos, 0],
[0, 0, 0, 1]],
dtype=complex)


class CryGate(CRYGate, metaclass=CRYMeta):
Expand Down
Loading

0 comments on commit 8f717d9

Please sign in to comment.