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

Merge u1, u2 and p gate together if they contain parameters. #7309

Closed
wants to merge 13 commits into from
67 changes: 41 additions & 26 deletions qiskit/transpiler/passes/optimization/optimize_1q_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from qiskit.circuit.library.standard_gates.u1 import U1Gate
from qiskit.circuit.library.standard_gates.u2 import U2Gate
from qiskit.circuit.library.standard_gates.u3 import U3Gate
from qiskit.circuit import ParameterExpression
from qiskit.circuit.gate import Gate
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info.synthesis import Quaternion
Expand Down Expand Up @@ -97,9 +98,15 @@ def run(self, dag):
and current_node.op.definition.global_phase
):
right_global_phase += current_node.op.definition.global_phase

# If there are any sympy objects coming from the gate convert
# to numpy.
left_parameters = tuple(float(x) for x in left_parameters)
rafal-pracht marked this conversation as resolved.
Show resolved Hide resolved
try:
left_parameters = tuple(float(x) for x in left_parameters)
except TypeError:
# If left_parameters contained any unbound Parameters
pass

# Compose gates
name_tuple = (left_name, right_name)
if name_tuple in (("u1", "u1"), ("p", "p")):
Expand Down Expand Up @@ -200,7 +207,8 @@ def run(self, dag):

# Y rotation is 0 mod 2*pi, so the gate is a u1
if (
abs(np.mod(right_parameters[0], (2 * np.pi))) < self.eps
not isinstance(right_parameters[0], ParameterExpression)
and abs(np.mod(right_parameters[0], (2 * np.pi))) < self.eps
and right_name != "u1"
and right_name != "p"
):
Expand All @@ -215,31 +223,34 @@ def run(self, dag):
)
# Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2
if right_name in ("u3", "u"):
# theta = pi/2 + 2*k*pi
right_angle = right_parameters[0] - np.pi / 2
if abs(right_angle) < self.eps:
right_angle = 0
if abs(np.mod((right_angle), 2 * np.pi)) < self.eps:
right_name = "u2"
right_parameters = (
np.pi / 2,
right_parameters[1],
right_parameters[2] + (right_parameters[0] - np.pi / 2),
)
# theta = -pi/2 + 2*k*pi
right_angle = right_parameters[0] + np.pi / 2
if abs(right_angle) < self.eps:
right_angle = 0
if abs(np.mod(right_angle, 2 * np.pi)) < self.eps:
right_name = "u2"
right_parameters = (
np.pi / 2,
right_parameters[1] + np.pi,
right_parameters[2] - np.pi + (right_parameters[0] + np.pi / 2),
)
if not isinstance(right_parameters[0], ParameterExpression):
# theta = pi/2 + 2*k*pi
right_angle = right_parameters[0] - np.pi / 2
if abs(right_angle) < self.eps:
right_angle = 0
if abs(np.mod((right_angle), 2 * np.pi)) < self.eps:
right_name = "u2"
right_parameters = (
np.pi / 2,
right_parameters[1],
right_parameters[2] + (right_parameters[0] - np.pi / 2),
)
# theta = -pi/2 + 2*k*pi
right_angle = right_parameters[0] + np.pi / 2
if abs(right_angle) < self.eps:
right_angle = 0
if abs(np.mod(right_angle, 2 * np.pi)) < self.eps:
right_name = "u2"
right_parameters = (
np.pi / 2,
right_parameters[1] + np.pi,
right_parameters[2] - np.pi + (right_parameters[0] + np.pi / 2),
)

# u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase)
if (
right_name in ("u1", "p")
not isinstance(right_parameters[2], ParameterExpression)
and right_name in ("u1", "p")
and abs(np.mod(right_parameters[2], 2 * np.pi)) < self.eps
):
right_name = "nop"
Expand Down Expand Up @@ -335,7 +346,11 @@ def _split_runs_on_parameters(runs):

out = []
for run in runs:
groups = groupby(run, lambda x: x.op.is_parameterized())
# We exclude only u3 and u gate because for u1 and u2 we can really straightforward
# merge two gate with parameters.
# It would be great to combine all gate with parameters but this requires
# support parameters in qiskit.quantum_info.synthesis.Quaternion.
groups = groupby(run, lambda x: x.op.is_parameterized() and x.op.name in ("u3", "u"))

for group_is_parameterized, gates in groups:
if not group_is_parameterized:
Expand Down
39 changes: 25 additions & 14 deletions test/python/transpiler/test_optimize_1q_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,7 @@ def test_single_parameterized_circuit(self):
dag = circuit_to_dag(qc)

expected = QuantumCircuit(qr)
expected.append(U1Gate(0.7), [qr])
expected.append(U1Gate(theta), [qr])
expected.append(U1Gate(0.3), [qr])
expected.append(U1Gate(theta + 1.0), [qr])

after = Optimize1qGates().run(dag)

Expand All @@ -257,11 +255,7 @@ def test_parameterized_circuits(self):
dag = circuit_to_dag(qc)

expected = QuantumCircuit(qr)
expected.append(U1Gate(0.7), [qr])
expected.append(U1Gate(theta), [qr])
expected.append(U1Gate(0.3), [qr])
expected.append(U1Gate(theta), [qr])
expected.append(U1Gate(0.5), [qr])
expected.append(U1Gate(2 * theta + 1.5), [qr])

after = Optimize1qGates().run(dag)

Expand All @@ -288,12 +282,7 @@ def test_parameterized_expressions_in_circuits(self):
dag = circuit_to_dag(qc)

expected = QuantumCircuit(qr)
expected.append(U1Gate(0.7), [qr])
expected.append(U1Gate(theta), [qr])
expected.append(U1Gate(phi), [qr])
expected.append(U1Gate(sum_), [qr])
expected.append(U1Gate(product_), [qr])
expected.append(U1Gate(0.5), [qr])
expected.append(U1Gate(2 * theta + 2 * phi + product_ + 1.2), [qr])

after = Optimize1qGates().run(dag)

Expand Down Expand Up @@ -647,6 +636,28 @@ def test_optimize_u_basis_phase_gate(self):

self.assertEqual(expected, result)

def test_optimize_u3_with_parameters(self):
"""Test correct behavior for u3 gates."""
phi = Parameter("φ")
alpha = Parameter("α")
qr = QuantumRegister(1, "qr")

qc = QuantumCircuit(qr)
qc.ry(2 * phi, qr[0])
qc.ry(alpha, qr[0])
qc.ry(0.1, qr[0])
qc.ry(0.2, qr[0])

passmanager = PassManager([Unroller(["u3"]), Optimize1qGates()])
result = passmanager.run(qc)

expected = QuantumCircuit(qr)
expected.append(U3Gate(2 * phi, 0, 0), [qr[0]])
expected.append(U3Gate(alpha, 0, 0), [qr[0]])
expected.append(U3Gate(0.3, 0, 0), [qr[0]])

self.assertEqual(expected, result)


if __name__ == "__main__":
unittest.main()