diff --git a/qiskit/extensions/standard/__init__.py b/qiskit/extensions/standard/__init__.py index 0794d9189725..934106d6ac87 100644 --- a/qiskit/extensions/standard/__init__.py +++ b/qiskit/extensions/standard/__init__.py @@ -45,3 +45,4 @@ from .cu3 import Cu3Gate from .rzz import RZZGate from .rxx import RXXGate +from .ms import MSGate diff --git a/qiskit/extensions/standard/ms.py b/qiskit/extensions/standard/ms.py new file mode 100755 index 000000000000..dad447f10d70 --- /dev/null +++ b/qiskit/extensions/standard/ms.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=invalid-name + +""" +Global Mølmer–Sørensen gate. + +The Mølmer–Sørensen gate is native to ion-trap systems. The global MS can be +applied to multiple ions to entangle multiple qubits simultaneously. + +In the two-qubit case, this is equivalent to an XX(theta) interaction, +and is thus reduced to the RXXGate. +""" + + +from qiskit.circuit import Gate +from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumRegister +from qiskit.extensions.standard.rxx import RXXGate + + +class MSGate(Gate): + """Global Molmer-Sorensen gate.""" + + def __init__(self, n_qubits, theta): + """Create new MS gate.""" + super().__init__("ms", n_qubits, [theta]) + + def _define(self): + definition = [] + q = QuantumRegister(self.num_qubits, "q") + rule = [] + for i in range(self.num_qubits): + for j in range(i+1, self.num_qubits): + rule += [(RXXGate(self.params[0]), [q[i], q[j]], [])] + + for inst in rule: + definition.append(inst) + self.definition = definition + + +def ms(self, theta, qubits): + """Apply MS to q1 and q2.""" + return self.append(MSGate(len(qubits), theta), qubits) + + +QuantumCircuit.ms = ms diff --git a/qiskit/transpiler/passes/ms_basis_decomposer.py b/qiskit/transpiler/passes/ms_basis_decomposer.py new file mode 100644 index 000000000000..526876ba9de6 --- /dev/null +++ b/qiskit/transpiler/passes/ms_basis_decomposer.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Pass for converting a circuit targeting U3,CX basis to Rx,Ry,Rxx.""" + +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.exceptions import QiskitError + +from qiskit.converters import circuit_to_dag +from qiskit.extensions.standard import U3Gate, CnotGate + +from qiskit.transpiler.passes import Unroller +from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer +from qiskit.quantum_info.synthesis.ion_decompose import cnot_rxx_decompose + + +class MSBasisDecomposer(TransformationPass): + """ + Convert a circuit in U3,CX to Rx,Ry,Rxx without unrolling or simplification. + """ + + supported_input_gates = (U3Gate, CnotGate) + supported_basis_names = ('rx', 'ry', 'rxx', 'ms') + + def __init__(self, basis): + """ + Args: + basis (list[str]): Target basis names, e.g. `['rx', 'ry', 'rxx', 'ms']` . + + Raises: + QiskitError: if target basis is not [ 'rx', 'ry', 'rxx', 'ms' ] + + """ + super().__init__() + + self.basis = basis + self.requires = [Unroller(list(set(basis).union(['u3', 'cx'])))] + + def run(self, dag): + """Replace U3,CX nodes in input dag with equivalent Rx,Ry,Rxx gates. + + Args: + dag(DAGCircuit): input dag + + Raises: + QiskitError: if input dag includes gates outside U3,CX. + + Returns: + DAGCircuit: output dag + """ + + one_q_decomposer = OneQubitEulerDecomposer(basis='XYX') + cnot_decomposition = cnot_rxx_decompose() + + for node in dag.op_nodes(): + basic_insts = ['measure', 'reset', 'barrier', 'snapshot'] + if node.name in basic_insts: + # TODO: this is legacy behavior. basic_insts should be removed and these + # instructions should be part of the device-reported basis. Currently, no + # backend reports "measure", for example. + continue + if node.name in self.basis: # If already a base, ignore. + continue + + if not isinstance(node.op, self.supported_input_gates): + raise QiskitError("Cannot convert the circuit to the given basis, %s. " + "No rule to expand instruction %s." % + (str(self.basis), node.op.name)) + + if isinstance(node.op, U3Gate): + replacement_circuit = one_q_decomposer(node.op) + elif isinstance(node.op, CnotGate): + # N.B. We can't circuit_to_dag once outside the loop because + # substitute_node_with_dag will modify the input DAG if the + # node to be replaced is conditional. + + replacement_circuit = cnot_decomposition + else: + raise QiskitError("Unable to handle instruction (%s, %s)." + % (node.op.name, type(node.op))) + + replacement_dag = circuit_to_dag(replacement_circuit) + + # N.B. wires kwarg can be omitted for both 1Q and 2Q substitutions. + # For 1Q, one-to-one mapping is always correct. For 2Q, + # cnot_rxx_decompose follows convention of control as q[0], target + # as q[1], which matches qarg order in CX node. + + dag.substitute_node_with_dag(node, replacement_dag) + + return dag diff --git a/qiskit/transpiler/transpile_circuit.py b/qiskit/transpiler/transpile_circuit.py index bbd7208793b8..aed51e61501b 100644 --- a/qiskit/transpiler/transpile_circuit.py +++ b/qiskit/transpiler/transpile_circuit.py @@ -18,6 +18,7 @@ level_1_pass_manager, level_2_pass_manager, level_3_pass_manager) +from qiskit.transpiler.passes.ms_basis_decomposer import MSBasisDecomposer from qiskit.transpiler.exceptions import TranspilerError @@ -40,6 +41,19 @@ def transpile_circuit(circuit, transpile_config): # or we choose an appropriate one based on desired optimization level (default: level 1) else: + # Workaround for ion trap support: If basis gates includes + # Mølmer-Sørensen (rxx) and the circuit includes gates outside the basis, + # first unroll to u3, cx, then run MSBasisDecomposer to target basis. + basic_insts = ['measure', 'reset', 'barrier', 'snapshot'] + device_insts = set(transpile_config.basis_gates).union(basic_insts) + + ms_basis_swap = None + if 'rxx' in transpile_config.basis_gates and \ + not device_insts >= circuit.count_ops().keys(): + ms_basis_swap = transpile_config.basis_gates + transpile_config.basis_gates = list(set(['u3', 'cx']).union( + transpile_config.basis_gates)) + level = transpile_config.optimization_level if level is None: level = 1 @@ -55,6 +69,9 @@ def transpile_circuit(circuit, transpile_config): else: raise TranspilerError("optimization_level can range from 0 to 3.") + if ms_basis_swap is not None: + pass_manager.append(MSBasisDecomposer(ms_basis_swap)) + # Set a callback on the pass manager there is one if getattr(transpile_config, 'callback', None): pass_manager.callback = transpile_config.callback diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 2324996784e4..0663b9ea18b4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -17,6 +17,7 @@ import math import unittest from unittest.mock import patch +from ddt import ddt, data from qiskit import BasicAer from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit @@ -31,8 +32,10 @@ from qiskit.transpiler import PassManager from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, CXDirection +from qiskit.quantum_info import Operator +@ddt class TestTranspile(QiskitTestCase): """Test transpile function.""" @@ -636,3 +639,56 @@ def test_check_circuit_width(self): with self.assertRaises(TranspilerError): transpile(qc, coupling_map=cmap) + + @data(0, 1, 2, 3) + def test_ms_unrolls_to_cx(self, optimization_level): + """Verify a Rx,Ry,Rxx circuit transpile to a U3,CX target.""" + + qc = QuantumCircuit(2) + qc.rx(math.pi/2, 0) + qc.ry(math.pi/4, 1) + qc.rxx(math.pi/4, 0, 1) + + out = transpile(qc, basis_gates=['u3', 'cx'], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_ms_can_target_ms(self, optimization_level): + """Verify a Rx,Ry,Rxx circuit can transpile to an Rx,Ry,Rxx target.""" + + qc = QuantumCircuit(2) + qc.rx(math.pi/2, 0) + qc.ry(math.pi/4, 1) + qc.rxx(math.pi/4, 0, 1) + + out = transpile(qc, basis_gates=['rx', 'ry', 'rxx'], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_cx_can_target_ms(self, optimization_level): + """Verify a U3,CX circuit can transpiler to a Rx,Ry,Rxx target.""" + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.rz(math.pi/4, [0, 1]) + + out = transpile(qc, basis_gates=['rx', 'ry', 'rxx'], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_measure_doesnt_unroll_ms(self, optimization_level): + """Verify a measure doesn't cause an Rx,Ry,Rxx circuit to unroll to U3,CX.""" + + qc = QuantumCircuit(2, 2) + qc.rx(math.pi/2, 0) + qc.ry(math.pi/4, 1) + qc.rxx(math.pi/4, 0, 1) + qc.measure([0, 1], [0, 1]) + + out = transpile(qc, basis_gates=['rx', 'ry', 'rxx'], optimization_level=optimization_level) + + self.assertEqual(qc, out)