diff --git a/qiskit/quantum_info/synthesis/one_qubit_decompose.py b/qiskit/quantum_info/synthesis/one_qubit_decompose.py index bceb1cb8c16f..cb1036a8e6ab 100644 --- a/qiskit/quantum_info/synthesis/one_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/one_qubit_decompose.py @@ -19,8 +19,8 @@ import scipy.linalg as la from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit.library.standard_gates import (PhaseGate, U3Gate, - U1Gate, RXGate, RYGate, +from qiskit.circuit.library.standard_gates import (UGate, PhaseGate, U3Gate, + U2Gate, U1Gate, RXGate, RYGate, RZGate, RGate, SXGate) from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.predicates import is_unitary_matrix @@ -29,6 +29,7 @@ ONE_QUBIT_EULER_BASIS_GATES = { 'U3': ['u3'], + 'U321': ['u3', 'u2', 'u1'], 'U': ['u'], 'PSX': ['p', 'sx'], 'U1X': ['u1', 'rx'], @@ -69,6 +70,9 @@ class OneQubitEulerDecomposer: * - 'U3' - :math:`Z(\phi) Y(\theta) Z(\lambda)` - :math:`e^{i\gamma} U_3(\theta,\phi,\lambda)` + * - 'U321' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma} U_3(\theta,\phi,\lambda)` * - 'U' - :math:`Z(\phi) Y(\theta) Z(\lambda)` - :math:`e^{i\gamma} U_3(\theta,\phi,\lambda)` @@ -93,7 +97,7 @@ class OneQubitEulerDecomposer: def __init__(self, basis='U3'): """Initialize decomposer - Supported bases are: 'U', 'PSX', 'ZSX', 'U3', 'U1X', 'RR', 'ZYZ', 'ZXZ', 'XYX'. + Supported bases are: 'U', 'PSX', 'ZSX', 'U321', 'U3', 'U1X', 'RR', 'ZYZ', 'ZXZ', 'XYX'. Args: basis (str): the decomposition basis [Default: 'U3'] @@ -155,6 +159,7 @@ def basis(self): def basis(self, basis): """Set the decomposition basis.""" basis_methods = { + 'U321': (self._params_u3, self._circuit_u321), 'U3': (self._params_u3, self._circuit_u3), 'U': (self._params_u3, self._circuit_u), 'PSX': (self._params_u1x, self._circuit_psx), @@ -280,13 +285,12 @@ def _circuit_zxz(theta, phi, lam, phase, - simplify=False, + simplify=True, atol=DEFAULT_ATOL): + circuit = QuantumCircuit(1, global_phase=phase) if simplify and np.isclose(theta, 0.0, atol=atol): - circuit = QuantumCircuit(1, global_phase=phase) circuit.append(RZGate(phi + lam), [0]) return circuit - circuit = QuantumCircuit(1, global_phase=phase) if not simplify or not np.isclose(lam, 0.0, atol=atol): circuit.append(RZGate(lam), [0]) if not simplify or not np.isclose(theta, 0.0, atol=atol): @@ -326,6 +330,24 @@ def _circuit_u3(theta, circuit.append(U3Gate(theta, phi, lam), [0]) return circuit + @staticmethod + def _circuit_u321(theta, + phi, + lam, + phase, + simplify=True, + atol=DEFAULT_ATOL): + rtol = 1e-9 # default is 1e-5, too far from atol=1e-12 + circuit = QuantumCircuit(1, global_phase=phase) + if simplify and (np.isclose(theta, 0.0, atol=atol, rtol=rtol)): + if not np.isclose(phi+lam, [0.0, 2*np.pi], atol=atol, rtol=rtol).any(): + circuit.append(U1Gate(_mod2pi(phi+lam)), [0]) + elif simplify and np.isclose(theta, np.pi/2, atol=atol, rtol=rtol): + circuit.append(U2Gate(phi, lam), [0]) + else: + circuit.append(U3Gate(theta, phi, lam), [0]) + return circuit + @staticmethod def _circuit_u(theta, phi, @@ -335,7 +357,7 @@ def _circuit_u(theta, atol=DEFAULT_ATOL): # pylint: disable=unused-argument circuit = QuantumCircuit(1, global_phase=phase) - circuit.u(theta, phi, lam, 0) + circuit.append(UGate(theta, phi, lam), [0]) return circuit @staticmethod diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 1bee642f39f2..eb4c4adce2cb 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -13,12 +13,14 @@ """Optimize chains of single-qubit gates using Euler 1q decomposer""" import logging +import copy import numpy as np from qiskit.quantum_info import Operator from qiskit.transpiler.basepasses import TransformationPass from qiskit.quantum_info.synthesis import one_qubit_decompose +from qiskit.circuit.library.standard_gates import U3Gate from qiskit.converters import circuit_to_dag logger = logging.getLogger(__name__) @@ -40,9 +42,22 @@ def __init__(self, basis=None): if basis: self.basis = [] basis_set = set(basis) - for basis_name, gates in one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES.items(): + euler_basis_gates = one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES + for euler_basis_name, gates in euler_basis_gates.items(): if set(gates).issubset(basis_set): - self.basis.append(one_qubit_decompose.OneQubitEulerDecomposer(basis_name)) + basis_copy = copy.copy(self.basis) + for base in basis_copy: + # check if gates are a superset of another basis + # and if so, remove that basis + if set(euler_basis_gates[base.basis]).issubset(set(gates)): + self.basis.remove(base) + # check if the gates are a subset of another basis + elif set(gates).issubset(set(euler_basis_gates[base.basis])): + break + # if not a subset, add it to the list + else: + self.basis.append(one_qubit_decompose.OneQubitEulerDecomposer( + euler_basis_name)) def run(self, dag): """Run the Optimize1qGatesDecomposition pass on `dag`. @@ -59,14 +74,21 @@ def run(self, dag): runs = dag.collect_1q_runs() identity_matrix = np.eye(2) for run in runs: - # Don't try to optimize a single 1q gate + single_u3 = False + # Don't try to optimize a single 1q gate, except for U3 if len(run) <= 1: params = run[0].op.params # Remove single identity gates if len(params) > 0 and np.array_equal(run[0].op.to_matrix(), identity_matrix): dag.remove_op_node(run[0]) - continue + continue + if (isinstance(run[0].op, U3Gate) and + np.isclose(float(params[0]), [0, np.pi/2], + atol=1e-12, rtol=0).any()): + single_u3 = True + else: + continue new_circs = [] operator = Operator(run[0].op) @@ -76,7 +98,8 @@ def run(self, dag): new_circs.append(decomposer(operator)) if new_circs: new_circ = min(new_circs, key=len) - if len(run) > len(new_circ): + if (len(run) > len(new_circ) or (single_u3 and + new_circ.data[0][0].name != 'u3')): new_dag = circuit_to_dag(new_circ) dag.substitute_node_with_dag(run[0], new_dag) # Delete the other nodes in the run diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 80ca694473cf..bc59d3c7cbd7 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -40,7 +40,6 @@ from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import RemoveResetInZeroState -from qiskit.transpiler.passes import Optimize1qGates from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection @@ -179,11 +178,7 @@ def _direction_condition(property_set): def _opt_control(property_set): return not property_set['depth_fixed_point'] - if basis_gates and ('u1' in basis_gates or 'u2' in basis_gates or - 'u3' in basis_gates): - _opt = [Optimize1qGates(basis_gates), CXCancellation()] - else: - _opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()] + _opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()] # 10. Schedule the circuit only when scheduling_method is supplied if scheduling_method: diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index e250cbefe071..0a8efe7b3bc6 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -41,7 +41,6 @@ from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import RemoveResetInZeroState -from qiskit.transpiler.passes import Optimize1qGates from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import CommutativeCancellation from qiskit.transpiler.passes import ApplyLayout @@ -175,11 +174,7 @@ def _direction_condition(property_set): def _opt_control(property_set): return not property_set['depth_fixed_point'] - if basis_gates and ('u1' in basis_gates or 'u2' in basis_gates or - 'u3' in basis_gates): - _opt = [Optimize1qGates(basis_gates), CommutativeCancellation()] - else: - _opt = [Optimize1qGatesDecomposition(basis_gates), CommutativeCancellation()] + _opt = [Optimize1qGatesDecomposition(basis_gates), CommutativeCancellation()] # 9. Schedule the circuit only when scheduling_method is supplied if scheduling_method: diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 0e828fce84bb..85f6a827e999 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -42,7 +42,6 @@ from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import RemoveResetInZeroState -from qiskit.transpiler.passes import Optimize1qGates from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import CommutativeCancellation from qiskit.transpiler.passes import OptimizeSwapBeforeMeasure @@ -180,23 +179,13 @@ def _opt_control(property_set): _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] - if basis_gates and ('u1' in basis_gates or 'u2' in basis_gates or - 'u3' in basis_gates): - _opt = [ - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates), - Optimize1qGates(basis_gates), - CommutativeCancellation(), - ] - else: - _opt = [ - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates), - Optimize1qGatesDecomposition(basis_gates), - CommutativeCancellation(), - ] + _opt = [ + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=basis_gates), + UnitarySynthesis(basis_gates), + Optimize1qGatesDecomposition(basis_gates), + CommutativeCancellation(), + ] # Schedule the circuit only when scheduling_method is supplied if scheduling_method: diff --git a/releasenotes/notes/1q_euler_decomp_u3_to_u1_u2-ca3bea2ca55ca961.yaml b/releasenotes/notes/1q_euler_decomp_u3_to_u1_u2-ca3bea2ca55ca961.yaml new file mode 100644 index 000000000000..7e46c23d5762 --- /dev/null +++ b/releasenotes/notes/1q_euler_decomp_u3_to_u1_u2-ca3bea2ca55ca961.yaml @@ -0,0 +1,9 @@ +--- +upgrade: + - | + Previously the optimization phase of the transpiler would use + :class:`~qiskit.transpiler.passes.optimization.Optimize1qGates` for one qubit gates if the basis + contained ``u1``, ``u2``, or ``u3``. In this upgrade, the transpiler will use + :class:`~qiskit.transpiler.passes.optimization.Optimize1qGatesDecomposition` for all bases. + As part of this change, ``u3`` gates will be reduced to ``u1`` or ``u2`` gates if possible in + :class:`~qiskit.quantum_info.synthesis.OneQubitEulerDecomposer`. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 5c8f2d455d24..e8068421df4e 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -536,9 +536,9 @@ def test_optimize_to_nothing(self): circ.cx(qr[0], qr[1]) after = transpile(circ, coupling_map=[[0, 1], [1, 0]], - basis_gates=['u3', 'cx']) + basis_gates=['u3', 'u2', 'u1', 'cx']) - expected = QuantumCircuit(QuantumRegister(2, 'q')) + expected = QuantumCircuit(QuantumRegister(2, 'q'), global_phase=-np.pi/2) self.assertEqual(after, expected) def test_pass_manager_empty(self): diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index 50f6687f233d..3fd909845297 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -522,7 +522,8 @@ def test_seed_289(self): self.check_exact_decomposition(unitary.data, two_qubit_cnot_decompose) @combine(seed=range(10), - euler_bases=[('U3', ['u3']), ('U', ['u']), ('U1X', ['u1', 'rx']), ('RR', ['r']), + euler_bases=[('U321', ['u3', 'u2', 'u1']), ('U3', ['u3']), ('U', ['u']), + ('U1X', ['u1', 'rx']), ('RR', ['r']), ('PSX', ['p', 'sx']), ('ZYZ', ['rz', 'ry']), ('ZXZ', ['rz', 'rx']), ('XYX', ['rx', 'ry']), ('ZSX', ['rz', 'sx'])], kak_gates=[(CXGate(), 'cx'), (CZGate(), 'cz'), (iSwapGate(), 'iswap'), diff --git a/test/python/transpiler/test_optimize_1q_decomposition.py b/test/python/transpiler/test_optimize_1q_decomposition.py index 620be85b0358..3e72dccfe69c 100644 --- a/test/python/transpiler/test_optimize_1q_decomposition.py +++ b/test/python/transpiler/test_optimize_1q_decomposition.py @@ -18,7 +18,8 @@ import numpy as np from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister -from qiskit.circuit.library.standard_gates import U3Gate +from qiskit.circuit.library.standard_gates import UGate, SXGate, PhaseGate +from qiskit.circuit.library.standard_gates import U3Gate, U2Gate, U1Gate from qiskit.circuit.random import random_circuit from qiskit.transpiler import PassManager from qiskit.transpiler.passes import Optimize1qGatesDecomposition @@ -61,6 +62,7 @@ def test_optimize_h_gates_pass_manager(self, basis): passmanager.append(BasisTranslator(sel, basis)) passmanager.append(Optimize1qGatesDecomposition(basis)) result = passmanager.run(circuit) + self.assertTrue(Operator(circuit).equiv(Operator(result))) @ddt.data( @@ -344,6 +346,76 @@ def test_euler_decomposition_worse(self): # assert optimization pass doesn't use it. self.assertEqual(result, circuit) + def test_optimize_u_to_phase_gate(self): + """U(0, 0, pi/4) -> p(pi/4). Basis [p, sx].""" + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.append(UGate(0, 0, np.pi / 4), [qr[0]]) + + expected = QuantumCircuit(qr) + expected.append(PhaseGate(np.pi / 4), [qr[0]]) + + basis = ['p', 'sx'] + passmanager = PassManager() + passmanager.append(BasisTranslator(sel, basis)) + passmanager.append(Optimize1qGatesDecomposition(basis)) + result = passmanager.run(circuit) + + self.assertEqual(expected, result) + + def test_optimize_u_to_p_sx_p(self): + """U(pi/2, 0, pi/4) -> p(-pi/4)-sx-p(p/2). Basis [p, sx].""" + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.append(UGate(np.pi / 2, 0, np.pi / 4), [qr[0]]) + + expected = QuantumCircuit(qr, global_phase=-np.pi/4) + expected.append(PhaseGate(-np.pi / 4), [qr[0]]) + expected.append(SXGate(), [qr[0]]) + expected.append(PhaseGate(np.pi / 2), [qr[0]]) + + basis = ['p', 'sx'] + passmanager = PassManager() + passmanager.append(BasisTranslator(sel, basis)) + passmanager.append(Optimize1qGatesDecomposition(basis)) + result = passmanager.run(circuit) + + self.assertEqual(expected, result) + + def test_optimize_u3_to_u1(self): + """U3(0, 0, pi/4) -> U1(pi/4). Basis [u1, u2, u3].""" + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.append(U3Gate(0, 0, np.pi / 4), [qr[0]]) + + expected = QuantumCircuit(qr) + expected.append(U1Gate(np.pi / 4), [qr[0]]) + + basis = ['u1', 'u2', 'u3'] + passmanager = PassManager() + passmanager.append(BasisTranslator(sel, basis)) + passmanager.append(Optimize1qGatesDecomposition(basis)) + result = passmanager.run(circuit) + + self.assertEqual(expected, result) + + def test_optimize_u3_to_u2(self): + """U3(pi/2, 0, pi/4) -> U2(0, pi/4). Basis [u1, u2, u3].""" + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.append(U3Gate(np.pi / 2, 0, np.pi / 4), [qr[0]]) + + expected = QuantumCircuit(qr) + expected.append(U2Gate(0, np.pi / 4), [qr[0]]) + + basis = ['u1', 'u2', 'u3'] + passmanager = PassManager() + passmanager.append(BasisTranslator(sel, basis)) + passmanager.append(Optimize1qGatesDecomposition(basis)) + result = passmanager.run(circuit) + + self.assertEqual(expected, result) + if __name__ == '__main__': unittest.main()