Skip to content

Commit

Permalink
add ECR gate and approximation_degree option (#5609)
Browse files Browse the repository at this point in the history
* add approximate decomposition

* add echo cross-resonance gate (ECR)

* add FakeJohannesburgECR

* modify fake_johannesburg_ecr basis_gates

* fix circ.ecr() method

* update direction fixing passes to work on ecr as well

* level 3: optimize after direction fixup

* add synthesis_fidelity to UnitarySynthesis and ensure ConsolidateBlocks emits unitaries

* fix cx->ecr translation global phase

* keep level3 structure as before

* lint

* releasenote

* rename passes from cx to gate

* fix deprecation warning

* ensure we stay in basis after gate direction

* lint

* circular import

* minor fixes

* synthesis_fidelity -> approximation_degree

* approximation degree in UnitarySynthesis

* style

* add tests

* lint

* Update test/python/compiler/test_transpiler.py

Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>
Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Mar 30, 2021
1 parent a568893 commit 346ffa8
Show file tree
Hide file tree
Showing 26 changed files with 609 additions and 221 deletions.
1 change: 1 addition & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
RZGate
RZZGate
RZXGate
ECRGate
SGate
SdgGate
SwapGate
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/standard_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
RZGate
RZZGate
RZXGate
ECRGate
SGate
SdgGate
SwapGate
Expand Down Expand Up @@ -80,6 +81,7 @@
from .rz import RZGate, CRZGate
from .rzz import RZZGate
from .rzx import RZXGate
from .ecr import ECRGate
from .s import SGate, SdgGate
from .swap import SwapGate, CSwapGate
from .iswap import iSwapGate
Expand Down
107 changes: 107 additions & 0 deletions qiskit/circuit/library/standard_gates/ecr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# 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.

"""Two-qubit ZX-rotation gate."""

import numpy as np

from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from .rzx import RZXGate
from .x import XGate


class ECRGate(Gate):
r"""An echoed RZX(pi/2) gate implemented using RZX(pi/4) and RZX(-pi/4).
This gate is maximally entangling and is equivalent to a CNOT up to
single-qubit pre-rotations. The echoing procedure mitigates some
unwanted terms (terms other than ZX) to cancel in an experiment.
**Circuit Symbol:**
.. parsed-literal::
┌─────────┐ ┌────────────┐┌────────┐┌─────────────┐
q_0: ┤0 ├ q_0: ┤0 ├┤ RX(pi) ├┤0 ├
│ ECR │ = │ RZX(pi/4) │└────────┘│ RZX(-pi/4) │
q_1: ┤1 ├ q_1: ┤1 ├──────────┤1 ├
└─────────┘ └────────────┘ └─────────────┘
**Matrix Representation:**
.. math::
ECR\ q_0, q_1 = \frac{1}{\sqrt{2}}
\begin{pmatrix}
0 & 1 & 0 & i \\
1 & 0 & -i & 0 \\
0 & i & 0 & 1 \\
-i & 0 & 1 & 0
\end{pmatrix}
.. note::
In Qiskit's convention, higher qubit indices are more significant
(little endian convention). In the above example we apply the gate
on (q_0, q_1) which results in the :math:`X \otimes Z` tensor order.
Instead, if we apply it on (q_1, q_0), the matrix will
be :math:`Z \otimes X`:
.. parsed-literal::
┌─────────┐
q_0: ┤1 ├
│ ECR │
q_1: ┤0 ├
└─────────┘
.. math::
ECR\ q_0, q_1 = \frac{1}{\sqrt{2}}
\begin{pmatrix}
0 & 0 & 1 & i \\
0 & 0 & i & 1 \\
1 & -i & 0 & 0 \\
-i & 1 & 0 & 0
\end{pmatrix}
"""

def __init__(self):
"""Create new ECR gate."""
super().__init__('ecr', 2, [])

def _define(self):
"""
gate ecr a, b { rzx(pi/4) a, b; x a; rzx(-pi/4) a, b;}
"""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit
q = QuantumRegister(2, 'q')
qc = QuantumCircuit(q, name=self.name)
rules = [
(RZXGate(np.pi/4), [q[0], q[1]], []),
(XGate(), [q[0]], []),
(RZXGate(-np.pi/4), [q[0], q[1]], [])
]
for instr, qargs, cargs in rules:
qc._append(instr, qargs, cargs)

self.definition = qc

def to_matrix(self):
"""Return a numpy.array for the ECR gate."""
return 1/np.sqrt(2) * \
np.array([[0, 1, 0, 1.j],
[1, 0, -1.j, 0],
[0, 1.j, 0, 1],
[-1.j, 0, 1, 0]], dtype=complex)
38 changes: 38 additions & 0 deletions qiskit/circuit/library/standard_gates/equivalence_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
YGate,
CYGate,
RYYGate,
ECRGate,
ZGate,
CZGate,
)
Expand Down Expand Up @@ -303,6 +304,33 @@
def_rzz.append(inst, qargs, cargs)
_sel.add_equivalence(RZZGate(theta), def_rzz)

# 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)

# ECRGate

q = QuantumRegister(2, 'q')
def_ecr = QuantumCircuit(q)
for inst, qargs, cargs in [
(RZXGate(pi/4), [q[0], q[1]], []),
(XGate(), [q[0]], []),
(RZXGate(-pi/4), [q[0], q[1]], [])
]:
def_ecr.append(inst, qargs, cargs)
_sel.add_equivalence(ECRGate(), def_ecr)

# SGate

q = QuantumRegister(1, 'q')
Expand Down Expand Up @@ -616,6 +644,16 @@
cx_to_iswap.append(inst, qargs, cargs)
_sel.add_equivalence(CXGate(), cx_to_iswap)

q = QuantumRegister(2, 'q')
cx_to_ecr = QuantumCircuit(q, global_phase=-pi/4)
for inst, qargs, cargs in [
(RZGate(-pi/2), [q[0]], []),
(RYGate(pi), [q[0]], []),
(RXGate(pi/2), [q[1]], []),
(ECRGate(), [q[0], q[1]], [])
]:
cx_to_ecr.append(inst, qargs, cargs)
_sel.add_equivalence(CXGate(), cx_to_ecr)

# CCXGate

Expand Down
5 changes: 5 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,11 @@ def rzz(self, theta, qubit1, qubit2):
from .library.standard_gates.rzz import RZZGate
return self.append(RZZGate(theta), [qubit1, qubit2], [])

def ecr(self, qubit1, qubit2):
"""Apply :class:`~qiskit.circuit.library.ECRGate`."""
from .library.standard_gates.ecr import ECRGate
return self.append(ECRGate(), [qubit1, qubit2], [])

def s(self, qubit): # pylint: disable=invalid-name
"""Apply :class:`~qiskit.circuit.library.SGate`."""
from .library.standard_gates.s import SGate
Expand Down
35 changes: 24 additions & 11 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
scheduling_method: Optional[str] = None,
instruction_durations: Optional[InstructionDurationsType] = None,
dt: Optional[float] = None,
approximation_degree: Optional[float] = None,
seed_transpiler: Optional[int] = None,
optimization_level: Optional[int] = None,
pass_manager: Optional[PassManager] = None,
Expand Down Expand Up @@ -143,6 +144,8 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
If the time unit is 'dt', the duration must be an integer.
dt: Backend sample time (resolution) in seconds.
If ``None`` (default), ``backend.configuration().dt`` is used.
approximation_degree (float): heuristic dial used for circuit approximation
(1.0=no approximation, 0.0=maximal approximation)
seed_transpiler: Sets random seed for the stochastic parts of the transpiler
optimization_level: How much optimization to perform on the circuits.
Higher levels generate more optimized circuits,
Expand Down Expand Up @@ -211,6 +214,7 @@ def callback_func(**kwargs):
initial_layout=initial_layout, layout_method=layout_method,
routing_method=routing_method,
translation_method=translation_method,
approximation_degree=approximation_degree,
backend=backend)

warnings.warn("The parameter pass_manager in transpile is being deprecated. "
Expand All @@ -233,8 +237,8 @@ def callback_func(**kwargs):
backend_properties, initial_layout,
layout_method, routing_method, translation_method,
scheduling_method, instruction_durations, dt,
seed_transpiler, optimization_level,
callback, output_name)
approximation_degree, seed_transpiler,
optimization_level, callback, output_name)

_check_circuits_coupling_map(circuits, transpile_args, backend)

Expand Down Expand Up @@ -390,7 +394,7 @@ def _parse_transpile_args(circuits, backend,
basis_gates, coupling_map, backend_properties,
initial_layout, layout_method, routing_method, translation_method,
scheduling_method, instruction_durations, dt,
seed_transpiler, optimization_level,
approximation_degree, seed_transpiler, optimization_level,
callback, output_name) -> List[Dict]:
"""Resolve the various types of args allowed to the transpile() function through
duck typing, overriding args, etc. Refer to the transpile() docstring for details on
Expand Down Expand Up @@ -422,11 +426,11 @@ def _parse_transpile_args(circuits, backend,
layout_method = _parse_layout_method(layout_method, num_circuits)
routing_method = _parse_routing_method(routing_method, num_circuits)
translation_method = _parse_translation_method(translation_method, num_circuits)
approximation_degree = _parse_approximation_degree(approximation_degree, num_circuits)
seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits)
optimization_level = _parse_optimization_level(optimization_level, num_circuits)
output_name = _parse_output_name(output_name, circuits)
callback = _parse_callback(callback, num_circuits)

durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits)
scheduling_method = _parse_scheduling_method(scheduling_method, circuits)
if scheduling_method and not durations:
Expand All @@ -436,7 +440,7 @@ def _parse_transpile_args(circuits, backend,
list_transpile_args = []
for args in zip(basis_gates, coupling_map, backend_properties, initial_layout,
layout_method, routing_method, translation_method, scheduling_method,
durations, seed_transpiler, optimization_level,
durations, approximation_degree, seed_transpiler, optimization_level,
output_name, callback, backend_num_qubits, faulty_qubits_map):
transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0],
coupling_map=args[1],
Expand All @@ -447,12 +451,13 @@ def _parse_transpile_args(circuits, backend,
translation_method=args[6],
scheduling_method=args[7],
instruction_durations=args[8],
seed_transpiler=args[9]),
'optimization_level': args[10],
'output_name': args[11],
'callback': args[12],
'backend_num_qubits': args[13],
'faulty_qubits_map': args[14]}
approximation_degree=args[9],
seed_transpiler=args[10]),
'optimization_level': args[11],
'output_name': args[12],
'callback': args[13],
'backend_num_qubits': args[14],
'faulty_qubits_map': args[15]}
list_transpile_args.append(transpile_args)

return list_transpile_args
Expand Down Expand Up @@ -676,6 +681,14 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits):
return durations


def _parse_approximation_degree(approximation_degree, num_circuits):
if not isinstance(approximation_degree, list):
approximation_degree = [approximation_degree] * num_circuits
if not all(0.0 <= d <= 1.0 for d in approximation_degree if d):
raise TranspilerError("Approximation degree must be in [0.0, 1.0]")
return approximation_degree


def _parse_seed_transpiler(seed_transpiler, num_circuits):
if not isinstance(seed_transpiler, list):
seed_transpiler = [seed_transpiler] * num_circuits
Expand Down
8 changes: 5 additions & 3 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
CheckMap
CheckCXDirection
CXDirection
GateDirection
MergeAdjacentBarriers
BarrierBeforeFinalMeasurements
RemoveFinalMeasurements
Expand Down Expand Up @@ -183,8 +183,10 @@

# additional utility passes
from .utils import CheckMap
from .utils import CheckCXDirection
from .utils import CXDirection
from .utils import CheckCXDirection # Deprecated
from .utils import CXDirection # Deprecated
from .utils import CheckGateDirection
from .utils import GateDirection
from .utils import BarrierBeforeFinalMeasurements
from .utils import RemoveFinalMeasurements
from .utils import MergeAdjacentBarriers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def run(self, dag):
and not set(subcirc.count_ops()).issubset(self.basis_gates))
):
new_dag.apply_operation_back(
UnitaryGate(unitary),
unitary,
sorted(block_qargs, key=lambda x: block_index_map[x]))
else:
for nd in block:
Expand Down
20 changes: 16 additions & 4 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from qiskit.converters import circuit_to_dag
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit.dagcircuit import DAGCircuit
from qiskit.circuit.library.standard_gates import iSwapGate, CXGate, CZGate, RXXGate
from qiskit.extensions.quantum_initializer import isometry
from qiskit.quantum_info.synthesis import one_qubit_decompose
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitBasisDecomposer
from qiskit.circuit.library.standard_gates import (iSwapGate, CXGate, CZGate,
RXXGate, ECRGate)


def _choose_kak_gate(basis_gates):
Expand All @@ -32,6 +33,7 @@ def _choose_kak_gate(basis_gates):
'cz': CZGate(),
'iswap': iSwapGate(),
'rxx': RXXGate(pi / 2),
'ecr': ECRGate()
}

kak_gate = None
Expand All @@ -56,14 +58,23 @@ def _choose_euler_basis(basis_gates):
class UnitarySynthesis(TransformationPass):
"""Synthesize gates according to their basis gates."""

def __init__(self, basis_gates: List[str]):
"""SynthesizeUnitaries initializer.
def __init__(self,
basis_gates: List[str],
approximation_degree: float = 1):
"""
Synthesize unitaries over some basis gates.
This pass can approximate 2-qubit unitaries given some approximation
closeness measure (expressed as approximation_degree). Other unitaries
are synthesized exactly.
Args:
basis_gates: List of gate names to target.
approximation_degree: closeness of approximation (0: lowest, 1: highest).
"""
super().__init__()
self._basis_gates = basis_gates
self._approximation_degree = approximation_degree

def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the UnitarySynthesis pass on `dag`.
Expand Down Expand Up @@ -93,7 +104,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
elif len(node.qargs) == 2:
if decomposer2q is None:
continue
synth_dag = circuit_to_dag(decomposer2q(node.op.to_matrix()))
synth_dag = circuit_to_dag(decomposer2q(node.op.to_matrix(),
basis_fidelity=self._approximation_degree))
else:
synth_dag = circuit_to_dag(
isometry.Isometry(node.op.to_matrix(), 0, 0).definition)
Expand Down
Loading

0 comments on commit 346ffa8

Please sign in to comment.