Skip to content
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

add global_phase to QuantumCircuit class #4565

Merged
merged 47 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7a50b4c
minor commit
ewinston Jun 4, 2020
bc056a3
rz agrees with u1
ewinston Jun 11, 2020
174a9f7
synthesis tests pass
ewinston Jun 16, 2020
0a72b1c
passing tests except open controlled random unitary
ewinston Jun 18, 2020
9a9a474
resolve most errors in test_unroller
ewinston Jun 18, 2020
7d3cc36
passing tests
ewinston Jun 24, 2020
aa63b20
move global phase change to circuit_to_gate
ewinston Jun 24, 2020
92d9314
add missing to_matrix to [most] gates
ewinston Jun 24, 2020
8dcc2dc
Merge branch 'master' into qc_phase_def
ewinston Jun 24, 2020
e89f14c
revert base gate of c3x
ewinston Jun 24, 2020
82e5e98
definition changed to circuit
ewinston Jun 26, 2020
4a636fa
resolve c_if register conversion
ewinston Jun 30, 2020
202d433
revert removal of _define in standard gates
ewinston Jul 1, 2020
87db632
linting
ewinston Jul 1, 2020
24d31b3
Merge branch 'master' into def_to_circuit
ewinston Jul 1, 2020
c3b3776
resolve qreg bug in transpiler
ewinston Jul 3, 2020
2320aa5
simplify unroller passes a bit
ewinston Jul 3, 2020
3be2fdd
linting
ewinston Jul 3, 2020
56a21a5
resolve doc forward ref
ewinston Jul 3, 2020
c977819
linting
ewinston Jul 3, 2020
269f307
Merge branch 'def_to_circuit' into qc_phase_def2
ewinston Jul 3, 2020
905fb43
Merge branch 'master' into qc_phase_def2
ewinston Jul 5, 2020
3847b34
Merge branch 'qc_phase_def2' into qc_phase_def
ewinston Jul 5, 2020
9448094
update u3 cx equivalence_library
ewinston Jul 5, 2020
8e1fe8c
put phase back into gate class definitions
ewinston Jul 7, 2020
0068dea
added checks for circuit inverse, compose, and state.evolve.
ewinston Jul 10, 2020
456f69e
passing tests
ewinston Jul 10, 2020
ec29cd5
Merge branch 'master' into qc_phase
ewinston Jul 10, 2020
5a6b37e
linting
ewinston Jul 10, 2020
171811f
revert phase accurate one_qubit_decompose for future pr.
ewinston Jul 10, 2020
f9ec746
linting
ewinston Jul 10, 2020
b4b0229
Update test/python/circuit/test_gate_definitions.py
ewinston Jul 10, 2020
3b29519
add release notes
ewinston Jul 10, 2020
e7deb96
revert equiv test
ewinston Jul 10, 2020
f424ea8
update release notes
ewinston Jul 10, 2020
1c7d2ca
Update qiskit/circuit/quantumcircuit.py
ewinston Jul 14, 2020
73509d4
don't unroll global phase to single qubit gates
ewinston Jul 21, 2020
5a62063
typeo
ewinston Jul 21, 2020
9c3a613
linting
ewinston Jul 22, 2020
ae5b636
Merge branch 'master' into qc_phase
ewinston Jul 22, 2020
3e5532b
linting
ewinston Jul 22, 2020
b1b746d
update cx_global_phase test
ewinston Jul 22, 2020
d2921eb
Merge branch 'master' into qc_phase
ewinston Jul 23, 2020
96b2240
minor linting
ewinston Jul 23, 2020
5ca9f3b
Update releasenotes/notes/add_quantumcircuit_phase-5006d1e930348d2e.yaml
ajavadia Jul 23, 2020
79779b7
Update releasenotes/notes/add_quantumcircuit_phase-5006d1e930348d2e.yaml
ajavadia Jul 23, 2020
999728a
Merge branch 'master' into qc_phase
mergify[bot] Jul 25, 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
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
6 changes: 3 additions & 3 deletions qiskit/circuit/library/standard_gates/equivalence_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@

q = QuantumRegister(2, 'q')
theta = Parameter('theta')
def_ryy = QuantumCircuit(q)
def_ryy = QuantumCircuit(q, phase=theta / 2)
for inst, qargs, cargs in [
(RXGate(pi / 2), [q[0]], []),
(RXGate(pi / 2), [q[1]], []),
Expand All @@ -293,7 +293,7 @@
q = QuantumRegister(1, 'q')
theta = Parameter('theta')
def_rz = QuantumCircuit(q)
def_rz.append(U1Gate(theta), [q[0]], [])
def_rz.append(U1Gate(theta), [q[0]], [], phase=-theta / 2)
_sel.add_equivalence(RZGate(theta), def_rz)

# CRZGate
Expand Down Expand Up @@ -413,7 +413,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
46 changes: 17 additions & 29 deletions qiskit/circuit/library/standard_gates/ryy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import numpy as np
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister


class RYYGate(Gate):
Expand Down Expand Up @@ -75,38 +74,27 @@ def __init__(self, theta):

def _define(self):
"""Calculate a subcircuit that implements this unitary."""
from .x import CXGate
from .rx import RXGate
from .rz import RZGate

definition = []
q = QuantumRegister(2, 'q')
theta = self.params[0]
rule = [
(RXGate(np.pi / 2), [q[0]], []),
(RXGate(np.pi / 2), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(RZGate(theta), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(RXGate(-np.pi / 2), [q[0]], []),
(RXGate(-np.pi / 2), [q[1]], []),
]
for inst in rule:
definition.append(inst)
self.definition = definition
circ = self.decompositions[0]
phase = circ.phase / len(circ.qregs[0])
if circ.phase:
circ.u3(np.pi, phase, phase - np.pi, circ.qregs[0])
circ.x(circ.qregs[0])
ewinston marked this conversation as resolved.
Show resolved Hide resolved
self.definition = circ.to_gate().definition

def inverse(self):
"""Return inverse RYY gate (i.e. with the negative rotation angle)."""
return RYYGate(-self.params[0])

# TODO: this is the correct matrix and is equal to the definition above,
# however the control mechanism cannot distinguish U1 and RZ yet.
# def to_matrix(self):
# """Return a numpy.array for the RYY gate."""
# theta = self.params[0]
# return np.exp(0.5j * theta) * 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 RYY gate."""
theta = self.params[0]
halfcos = np.cos(theta / 2)
halfsin = np.sin(theta / 2)
return np.exp(0.5j * theta) * np.array([
[halfcos, 0, 0, 1j * halfsin],
[0, halfcos, -1j * halfsin, 0],
[0, -1j * halfsin, halfcos, 0],
[1j * halfsin, 0, 0, halfcos]
], dtype=complex)
49 changes: 27 additions & 22 deletions qiskit/circuit/library/standard_gates/rz.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,12 @@ def _define(self):
"""
gate rz(phi) a { u1(phi) a; }
"""
from .u1 import U1Gate
definition = []
q = QuantumRegister(1, 'q')
rule = [
(U1Gate(self.params[0]), [q[0]], [])
]
for inst in rule:
definition.append(inst)
self.definition = definition
import numpy as np
circ = self.decompositions[0]
if circ.phase:
circ.u3(np.pi, circ.phase, circ.phase - np.pi, circ.qregs[0])
circ.x(circ.qregs[0])
self.definition = circ.to_gate().definition

def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
"""Return a (mutli-)controlled-RZ gate.
Expand Down Expand Up @@ -101,12 +98,12 @@ def inverse(self):

# TODO: this is the correct matrix however the control mechanism
# cannot distinguish U1 and RZ yet.
# def to_matrix(self):
# """Return a numpy.array for the RZ gate."""
# import numpy
# lam = float(self.params[0])
# return numpy.array([[numpy.exp(-1j * lam / 2), 0],
# [0, numpy.exp(1j * lam / 2)]], dtype=complex)
def to_matrix(self):
"""Return a numpy.array for the RZ gate."""
import numpy as np
ilam2 = 0.5j * float(self.params[0])
return np.array([[np.exp(-ilam2), 0],
[0, np.exp(ilam2)]], dtype=complex)


class CRZMeta(type):
Expand Down Expand Up @@ -214,13 +211,21 @@ def inverse(self):
# 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 CRZ gate."""
# arg = 1j * self.params[0] / 2
# return numpy.array([[1, 0, 0, 0],
# [0, numpy.exp(-arg), 0, 0],
# [0, 0, 1, 0],
# [0, 0, 0, numpy.exp(arg)]],
# dtype=complex)
# """Return a numpy.array for the CRZ gate."""
# import numpy
# arg = 1j * self.params[0] / 2
# if self.ctrl_state:
# return numpy.array([[1, 0, 0, 0],
# [0, numpy.exp(-arg), 0, 0],
# [0, 0, 1, 0],
# [0, 0, 0, numpy.exp(arg)]],
# dtype=complex)
# else:
# return numpy.array([[numpy.exp(-arg), 0, 0, 0],
# [ 0, 1, 0, 0],
# [ 0, 0, numpy.exp(arg), 0],
# [ 0, 0, 0, 1]],
# dtype=complex)


class CrzGate(CRZGate, metaclass=CRZMeta):
Expand Down
8 changes: 5 additions & 3 deletions qiskit/circuit/library/standard_gates/rzx.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from .rz import RZGate
from .h import HGate
from .x import CXGate

Expand Down Expand Up @@ -125,11 +124,13 @@ def _define(self):
"""
gate rzx(theta) a, b { h b; cx a, b; u1(theta) b; cx a, b; h b;}
"""
# pylint: disable=cyclic-import
from qiskit.circuit.library.standard_gates import U1Gate
q = QuantumRegister(2, 'q')
self.definition = [
(HGate(), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(RZGate(self.params[0]), [q[1]], []),
(U1Gate(self.params[0]), [q[1]], []), # Should be RZGate
(CXGate(), [q[0], q[1]], []),
(HGate(), [q[1]], [])
]
Expand All @@ -147,6 +148,7 @@ def inverse(self):
# isin = 1j * numpy.sin(half_theta)
# return numpy.array([[ cos, 0, -isin, 0],
# [ 0, cos, 0, isin],
# [-1j*sin, 0, cos, 0],
# [-isin, 0, cos, 0],
# [ 0, isin, 0, cos]],
# [0, 1j*halfsin, 0, halfcos]],
# dtype=complex)
26 changes: 16 additions & 10 deletions qiskit/circuit/library/standard_gates/u1.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,22 @@ def inverse(self):
r"""Return inverted CU1 gate (:math:`CU1(\lambda){\dagger} = CU1(-\lambda)`)"""
return CU1Gate(-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 CU1 gate."""
# eith = numpy.exp(1j * self.params[0])
# return numpy.array([[1, 0, 0, 0],
# [0, 1, 0, 0],
# [0, 0, 1, 0],
# [0, 0, 0, eith]],
# dtype=complex)
def to_matrix(self):
"""Return a numpy.array for the CU1 gate."""

eith = numpy.exp(1j * float(self.params[0]))
if self.ctrl_state:
return numpy.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, eith]],
dtype=complex)
else:
return numpy.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, eith, 0],
[0, 0, 0, 1]],
dtype=complex)


class Cu1Gate(CU1Gate, metaclass=CU1Meta):
Expand Down
12 changes: 4 additions & 8 deletions qiskit/circuit/library/standard_gates/u3.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,11 @@ def to_matrix(self):
"""Return a Numpy.array for the U3 gate."""
theta, phi, lam = self.params
theta, phi, lam = float(theta), float(phi), float(lam)
cos = numpy.cos(theta / 2)
sin = numpy.sin(theta / 2)
return numpy.array([
[
numpy.cos(theta / 2),
-numpy.exp(1j * lam) * numpy.sin(theta / 2)
],
[
numpy.exp(1j * phi) * numpy.sin(theta / 2),
numpy.exp(1j * (phi + lam)) * numpy.cos(theta / 2)
]
[cos, -numpy.exp(1j * lam) * sin],
[numpy.exp(1j * phi) * sin, numpy.exp(1j * (phi + lam)) * cos]
], dtype=complex)


Expand Down
43 changes: 39 additions & 4 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class QuantumCircuit:

name (str): the name of the quantum circuit. If not set, an
automatically generated string will be assigned.
phase (float): The global phase of the circuit.
ewinston marked this conversation as resolved.
Show resolved Hide resolved

Raises:
CircuitError: if the circuit name, if given, is not valid.
Expand Down Expand Up @@ -134,7 +135,7 @@ class QuantumCircuit:
header = "OPENQASM 2.0;"
extension_lib = "include \"qelib1.inc\";"

def __init__(self, *regs, name=None):
def __init__(self, *regs, name=None, phase=0):
if any([not isinstance(reg, (QuantumRegister, ClassicalRegister)) for reg in regs]):
try:
regs = tuple(int(reg) for reg in regs)
Expand Down Expand Up @@ -167,6 +168,8 @@ def __init__(self, *regs, name=None):
self._parameter_table = ParameterTable()

self._layout = None
self._phase = 0
self.phase = phase

@property
def data(self):
Expand Down Expand Up @@ -271,7 +274,7 @@ def inverse(self):
CircuitError: if the circuit cannot be inverted.
"""
inverse_circ = QuantumCircuit(*self.qregs, *self.cregs,
name=self.name + '_dg')
name=self.name + '_dg', phase=-self.phase)

for inst, qargs, cargs in reversed(self._data):
inverse_circ._append(inst.inverse(), qargs, cargs)
Expand All @@ -287,7 +290,8 @@ def repeat(self, reps):
QuantumCircuit: A circuit containing ``reps`` repetitions of this circuit.
"""
repeated_circ = QuantumCircuit(*self.qregs, *self.cregs,
name=self.name + '**{}'.format(reps))
name=self.name + '**{}'.format(reps),
phase=reps * self.phase)

# benefit of appending instructions: decomposing shows the subparts, i.e. the power
# is actually `reps` times this circuit, and it is currently much faster than `compose`.
Expand Down Expand Up @@ -545,14 +549,15 @@ def cbit_argument_conversion(self, clbit_representation):
"""
return QuantumCircuit._bit_argument_conversion(clbit_representation, self.clbits)

def append(self, instruction, qargs=None, cargs=None):
def append(self, instruction, qargs=None, cargs=None, phase=0):
ewinston marked this conversation as resolved.
Show resolved Hide resolved
"""Append one or more instructions to the end of the circuit, modifying
the circuit in place. Expands qargs and cargs.

Args:
instruction (qiskit.circuit.Instruction): Instruction instance to append
qargs (list(argument)): qubits to attach instruction to
cargs (list(argument)): clbits to attach instruction to
phase (float): The global phase in radians of instruction.

Returns:
qiskit.circuit.Instruction: a handle to the instruction that was just added
Expand All @@ -578,6 +583,7 @@ def append(self, instruction, qargs=None, cargs=None):
instructions = InstructionSet()
for (qarg, carg) in instruction.broadcast_arguments(expanded_qargs, expanded_cargs):
instructions.add(self._append(instruction, qarg, carg), qarg, carg)
self.phase += phase
return instructions

def _append(self, instruction, qargs, cargs):
Expand Down Expand Up @@ -1420,6 +1426,30 @@ def from_qasm_str(qasm_str):
qasm = Qasm(data=qasm_str)
return _circuit_from_qasm(qasm)

@property
def phase(self):
"""Return the phase of the circuit."""
return self._phase

@phase.setter
def phase(self, angle):
"""Set the phase of the circuit.

Args:
angle (float, ParameterExpression)
"""
if isinstance(angle, ParameterExpression):
self._phase = angle
else:
# Set the phase to the [-2 * pi, 2 * pi] interval
angle = float(angle)
if not angle:
self._phase = 0
elif angle < 0:
self._phase = angle % (-2 * np.pi)
else:
self._phase = angle % (2 * np.pi)

@property
def parameters(self):
"""Convenience function to get the parameters defined in the parameter table."""
Expand Down Expand Up @@ -1559,6 +1589,9 @@ def _bind_parameter(self, parameter, value):
# instructions), search the definition for instances of the
# parameter which also need to be bound.
self._rebind_definition(instr, parameter, value)
# bind circuit's phase
if isinstance(self.phase, ParameterExpression):
self.phase = self.phase.bind({parameter: value})

def _substitute_parameter(self, old_parameter, new_parameter_expr):
"""Substitute an existing parameter in all circuit instructions and the parameter table."""
Expand All @@ -1570,6 +1603,8 @@ def _substitute_parameter(self, old_parameter, new_parameter_expr):
entry = self._parameter_table.pop(old_parameter)
for new_parameter in new_parameter_expr.parameters:
self._parameter_table[new_parameter] = entry
if isinstance(self.phase, ParameterExpression):
self.phase = self.phase.subs({old_parameter: new_parameter_expr})

def _rebind_definition(self, instruction, parameter, value):
if instruction._definition:
Expand Down
2 changes: 2 additions & 0 deletions qiskit/converters/circuit_to_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def circuit_to_dag(circuit):
"""
dagcircuit = DAGCircuit()
dagcircuit.name = circuit.name
dagcircuit.phase = circuit.phase
for register in circuit.qregs:
dagcircuit.add_qreg(register)
for register in circuit.cregs:
Expand All @@ -55,4 +56,5 @@ def circuit_to_dag(circuit):
for instruction, qargs, cargs in circuit.data:
dagcircuit.apply_operation_back(instruction.copy(), qargs, cargs,
instruction.condition)

return dagcircuit
3 changes: 2 additions & 1 deletion qiskit/converters/dag_to_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def dag_to_circuit(dag):
"""

name = dag.name or None
circuit = QuantumCircuit(*dag.qregs.values(), *dag.cregs.values(), name=name)
circuit = QuantumCircuit(*dag.qregs.values(), *dag.cregs.values(), name=name,
phase=dag.phase)

for node in dag.topological_op_nodes():
# Get arguments for classical control (if any)
Expand Down
Loading