Skip to content

Commit

Permalink
Support for backends with defective qubits and gates (#4782)
Browse files Browse the repository at this point in the history
* initial commit

* faulty qubit insertion

* operational False

* pruned coupling map

* no gate operation parameter for now

* faulty gate

* fake_ourense_faulty_cx13

* testing of the fake backends

* a test

* disconnected coupling map

* add fault register at the end

* transform the circuit into the faulty backend

* simulators do not have properties

* assertIdleQ1

* assertEqualCount

* remove faulty qubtis from properties

* backend_property for faulty qubits

* adapt Layout2qDistance to disconnected coupling map

* remove _create_qreg

* assuming coupling map

* layout with disconnected qubits

* test for faulty gate

* support for faulty gate

* remove gates when they are faulty from the backend properties

* test

* unused import

* new fault backend

* test

* take the largest connected component

* lint

* adjust test

* lint

* lint1

* lint2

* lint3

* lint4

* lint5

* _parse_initial_layout

* initial layot support

* TestFaultyCX13

* more testing

* cm -> context

* Update qiskit/providers/models/backendproperties.py

Co-authored-by: Ali Javadi-Abhari <[email protected]>

* Apply suggestions from code review

Co-authored-by: Ali Javadi-Abhari <[email protected]>

* mv qiskit/test/mock/backends/ourense/fake_ourense_faulty* to test/python/providers/faulty_backends.py

* cleaning up

* lint

* moving faulty_qubits and faulty_gates to properties

* another fix

* lint

* defective qubtis per circuit

* docstring

* linting

* test added

* docstring

* reno

* Update releasenotes/notes/defective_qubits-d826ccbd049603b9.yaml

Co-authored-by: Ali Javadi-Abhari <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
4 people authored Aug 6, 2020
1 parent 444a3be commit a67440a
Show file tree
Hide file tree
Showing 10 changed files with 669 additions and 7 deletions.
12 changes: 12 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,18 @@ def _create_creg(self, length, name):
new_creg = ClassicalRegister(length, name)
return new_creg

def _create_qreg(self, length, name):
""" Creates a qreg, checking if QuantumRegister with same name exists
"""
if name in [qreg.name for qreg in self.qregs]:
save_prefix = QuantumRegister.prefix
QuantumRegister.prefix = name
new_qreg = QuantumRegister(length)
QuantumRegister.prefix = save_prefix
else:
new_qreg = QuantumRegister(length, name)
return new_qreg

def measure_active(self, inplace=True):
"""Adds measurement to all non-idle qubits. Creates a new ClassicalRegister with
a size equal to the number of non-idle qubits being measured.
Expand Down
169 changes: 162 additions & 7 deletions qiskit/compiler/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.providers import BaseBackend
from qiskit.providers.models import BackendProperties
from qiskit.providers.models.backendproperties import Gate
from qiskit.transpiler import Layout, CouplingMap, PropertySet, PassManager
from qiskit.transpiler.basepasses import BasePass
from qiskit.dagcircuit import DAGCircuit
Expand All @@ -29,7 +30,8 @@
from qiskit.circuit.quantumregister import Qubit
from qiskit import user_config
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.converters import isinstanceint, isinstancelist
from qiskit.transpiler.passes import ApplyLayout
from qiskit.converters import isinstanceint, isinstancelist, dag_to_circuit, circuit_to_dag
from qiskit.transpiler.passes.basis.ms_basis_decomposer import MSBasisDecomposer
from qiskit.transpiler.preset_passmanagers import (level_0_pass_manager,
level_1_pass_manager,
Expand Down Expand Up @@ -264,7 +266,6 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua
transpile_config (dict): configuration dictating how to transpile. The
dictionary has the following format:
{'optimization_level': int,
'pass_manager': PassManager,
'output_name': string,
'callback': callable,
'pass_manager_config': PassManagerConfig}
Expand All @@ -277,6 +278,10 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua

pass_manager_config = transpile_config['pass_manager_config']

if transpile_config['faulty_qubits_map']:
pass_manager_config.initial_layout = _remap_layout_faulty_backend(
pass_manager_config.initial_layout, transpile_config['faulty_qubits_map'])

ms_basis_swap = None
if (pass_manager_config.translation_method == 'unroller'
and pass_manager_config.basis_gates is not None):
Expand Down Expand Up @@ -308,8 +313,68 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua
if ms_basis_swap is not None:
pass_manager.append(MSBasisDecomposer(ms_basis_swap))

return pass_manager.run(circuit, callback=transpile_config['callback'],
output_name=transpile_config['output_name'])
result = pass_manager.run(circuit, callback=transpile_config['callback'],
output_name=transpile_config['output_name'])

if transpile_config['faulty_qubits_map']:
return _remap_circuit_faulty_backend(result, transpile_config['backend_num_qubits'],
pass_manager_config.backend_properties,
transpile_config['faulty_qubits_map'])

return result


def _remap_circuit_faulty_backend(circuit, num_qubits, backend_prop, faulty_qubits_map):
faulty_qubits = backend_prop.faulty_qubits() if backend_prop else []
disconnected_qubits = {k for k, v in faulty_qubits_map.items()
if v is None}.difference(faulty_qubits)
faulty_qubits_map_reverse = {v: k for k, v in faulty_qubits_map.items()}
if faulty_qubits:
faulty_qreg = circuit._create_qreg(len(faulty_qubits), 'faulty')
else:
faulty_qreg = []
if disconnected_qubits:
disconnected_qreg = circuit._create_qreg(len(disconnected_qubits), 'disconnected')
else:
disconnected_qreg = []

new_layout = Layout()
faulty_qubit = 0
disconnected_qubit = 0

for real_qubit in range(num_qubits):
if faulty_qubits_map[real_qubit] is not None:
new_layout[real_qubit] = circuit._layout[faulty_qubits_map[real_qubit]]
else:
if real_qubit in faulty_qubits:
new_layout[real_qubit] = faulty_qreg[faulty_qubit]
faulty_qubit += 1
else:
new_layout[real_qubit] = disconnected_qreg[disconnected_qubit]
disconnected_qubit += 1
physical_layout_dict = {}
for qubit in circuit.qubits:
physical_layout_dict[qubit] = faulty_qubits_map_reverse[qubit.index]
for qubit in faulty_qreg[:] + disconnected_qreg[:]:
physical_layout_dict[qubit] = new_layout[qubit]
dag_circuit = circuit_to_dag(circuit)
apply_layout_pass = ApplyLayout()
apply_layout_pass.property_set['layout'] = Layout(physical_layout_dict)
circuit = dag_to_circuit(apply_layout_pass.run(dag_circuit))
circuit._layout = new_layout
return circuit


def _remap_layout_faulty_backend(layout, faulty_qubits_map):
if layout is None:
return layout
new_layout = Layout()
for virtual, physical in layout.get_virtual_bits().items():
if faulty_qubits_map[physical] is None:
raise TranspilerError("The initial_layout parameter refers to faulty"
" or disconnected qubits")
new_layout[virtual] = faulty_qubits_map[physical]
return new_layout


def _parse_transpile_args(circuits, backend,
Expand All @@ -336,8 +401,10 @@ def _parse_transpile_args(circuits, backend,
num_circuits = len(circuits)

basis_gates = _parse_basis_gates(basis_gates, backend, circuits)
faulty_qubits_map = _parse_faulty_qubits_map(backend, num_circuits)
coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits)
backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits)
backend_num_qubits = _parse_backend_num_qubits(backend, num_circuits)
initial_layout = _parse_initial_layout(initial_layout, circuits)
layout_method = _parse_layout_method(layout_method, num_circuits)
routing_method = _parse_routing_method(routing_method, num_circuits)
Expand All @@ -351,7 +418,7 @@ def _parse_transpile_args(circuits, backend,
for args in zip(basis_gates, coupling_map, backend_properties,
initial_layout, layout_method, routing_method, translation_method,
seed_transpiler, optimization_level,
output_name, callback):
output_name, callback, backend_num_qubits, faulty_qubits_map):
transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0],
coupling_map=args[1],
backend_properties=args[2],
Expand All @@ -362,12 +429,45 @@ def _parse_transpile_args(circuits, backend,
seed_transpiler=args[7]),
'optimization_level': args[8],
'output_name': args[9],
'callback': args[10]}
'callback': args[10],
'backend_num_qubits': args[11],
'faulty_qubits_map': args[12]}
list_transpile_args.append(transpile_args)

return list_transpile_args


def _create_faulty_qubits_map(backend):
"""If the backend has faulty qubits, those should be excluded. A faulty_qubit_map is a map
from working qubit in the backend to dumnmy qubits that are consecutive and connected."""
faulty_qubits_map = None
if backend is not None:
if backend.properties():
faulty_qubits = backend.properties().faulty_qubits()
faulty_edges = [gates.qubits for gates in backend.properties().faulty_gates()]
else:
faulty_qubits = []
faulty_edges = []

if faulty_qubits or faulty_edges:
faulty_qubits_map = {}
configuration = backend.configuration()
full_coupling_map = configuration.coupling_map
functional_cm_list = [edge for edge in full_coupling_map
if (set(edge).isdisjoint(faulty_qubits) and
edge not in faulty_edges)]

connected_working_qubits = CouplingMap(functional_cm_list).largest_connected_component()
dummy_qubit_counter = 0
for qubit in range(configuration.n_qubits):
if qubit in connected_working_qubits:
faulty_qubits_map[qubit] = dummy_qubit_counter
dummy_qubit_counter += 1
else:
faulty_qubits_map[qubit] = None
return faulty_qubits_map


def _parse_basis_gates(basis_gates, backend, circuits):
# try getting basis_gates from user, else backend
if basis_gates is None:
Expand All @@ -387,7 +487,14 @@ def _parse_coupling_map(coupling_map, backend, num_circuits):
if getattr(backend, 'configuration', None):
configuration = backend.configuration()
if hasattr(configuration, 'coupling_map') and configuration.coupling_map:
coupling_map = CouplingMap(configuration.coupling_map)
faulty_map = _create_faulty_qubits_map(backend)
if faulty_map:
coupling_map = CouplingMap()
for qubit1, qubit2 in configuration.coupling_map:
if faulty_map[qubit1] is not None and faulty_map[qubit2] is not None:
coupling_map.add_edge(faulty_map[qubit1], faulty_map[qubit2])
else:
coupling_map = CouplingMap(configuration.coupling_map)

# coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
if coupling_map is None or isinstance(coupling_map, CouplingMap):
Expand All @@ -406,11 +513,46 @@ def _parse_backend_properties(backend_properties, backend, num_circuits):
if backend_properties is None:
if getattr(backend, 'properties', None):
backend_properties = backend.properties()
if backend_properties and \
(backend_properties.faulty_qubits() or backend_properties.faulty_gates()):
faulty_qubits = sorted(backend_properties.faulty_qubits(), reverse=True)
faulty_edges = [gates.qubits for gates in backend_properties.faulty_gates()]
# remove faulty qubits in backend_properties.qubits
for faulty_qubit in faulty_qubits:
del backend_properties.qubits[faulty_qubit]

gates = []
for gate in backend_properties.gates:
# remove gates using faulty edges or with faulty qubits (and remap the
# gates in terms of faulty_qubits_map)
faulty_qubits_map = _create_faulty_qubits_map(backend)
if any([faulty_qubits_map[qubits] is not None for qubits in gate.qubits]) or \
gate.qubits in faulty_edges:
continue
gate_dict = gate.to_dict()
replacement_gate = Gate.from_dict(gate_dict)
gate_dict['qubits'] = [faulty_qubits_map[qubit] for qubit in gate.qubits]
args = '_'.join([str(qubit) for qubit in gate_dict['qubits']])
gate_dict['name'] = "%s%s" % (gate_dict['gate'], args)
gates.append(replacement_gate)

backend_properties.gates = gates
if not isinstance(backend_properties, list):
backend_properties = [backend_properties] * num_circuits
return backend_properties


def _parse_backend_num_qubits(backend, num_circuits):
if backend is None:
return [None] * num_circuits
if not isinstance(backend, list):
return [backend.configuration().n_qubits] * num_circuits
backend_num_qubits = []
for a_backend in backend:
backend_num_qubits.append(a_backend.configuration().n_qubits)
return backend_num_qubits


def _parse_initial_layout(initial_layout, circuits):
# initial_layout could be None, or a list of ints, e.g. [0, 5, 14]
# or a list of tuples/None e.g. [qr[0], None, qr[1]] or a dict e.g. {qr[0]: 0}
Expand All @@ -436,8 +578,10 @@ def _layout_from_raw(initial_layout, circuit):
else:
# even if one layout, but multiple circuits, the layout needs to be adapted for each
initial_layout = [_layout_from_raw(initial_layout, circ) for circ in circuits]

if not isinstance(initial_layout, list):
initial_layout = [initial_layout] * len(circuits)

return initial_layout


Expand Down Expand Up @@ -483,6 +627,17 @@ def _parse_callback(callback, num_circuits):
return callback


def _parse_faulty_qubits_map(backend, num_circuits):
if backend is None:
return [None] * num_circuits
if not isinstance(backend, list):
return [_create_faulty_qubits_map(backend)] * num_circuits
faulty_qubits_map = []
for a_backend in backend:
faulty_qubits_map.append(_create_faulty_qubits_map(a_backend))
return faulty_qubits_map


def _parse_output_name(output_name, circuits):
# naming and returning circuits
# output_name could be either a string or a list
Expand Down
52 changes: 52 additions & 0 deletions qiskit/providers/models/backendproperties.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,43 @@ def gate_property(self,
raise BackendPropertyError("Could not find the desired property for {g}".format(g=gate))
return result

def faulty_qubits(self):
"""Return a list of faulty qubits.
"""
faulty = []
for qubit in self._qubits:
if not self.is_qubit_operational(qubit):
faulty.append(qubit)
return faulty

def faulty_gates(self):
"""Return a list of faulty gates.
"""
faulty = []
for gate in self.gates:
if not self.is_gate_operational(gate.gate, gate.qubits):
faulty.append(gate)
return faulty

def is_gate_operational(self,
gate: str,
qubits: Union[int, Iterable[int]] = None) -> bool:
"""
Return the operational status of the given gate.
Args:
gate: Name of the gate.
qubits: The qubit to find the operational status for.
Returns:
bool: Operational status of the given gate. True if the gate is operational,
False otherwise.
"""
properties = self.gate_property(gate, qubits)
if 'operational' in properties:
return bool(properties['operational'][0])
return True # if property operational not existent, then True.

def gate_error(self, gate: str, qubits: Union[int, Iterable[int]]) -> float:
"""
Return gate error estimates from backend properties.
Expand Down Expand Up @@ -410,6 +447,21 @@ def readout_error(self, qubit: int) -> float:
"""
return self.qubit_property(qubit, 'readout_error')[0] # Throw away datetime at index 1

def is_qubit_operational(self, qubit: int) -> bool:
"""
Return the operational status of the given qubit.
Args:
qubit: Qubit for which to return operational status of.
Returns:
Operational status of the given qubit.
"""
properties = self.qubit_property(qubit)
if 'operational' in properties:
return bool(properties['operational'][0])
return True # if property operational not existent, then True.

def _apply_prefix(self, value: float, unit: str) -> float:
"""
Given a SI unit prefix and value, apply the prefix to convert to
Expand Down
6 changes: 6 additions & 0 deletions qiskit/test/mock/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ def properties(self):
"name": "readout_error",
"unit": "",
"value": 0.0
},
{
"date": "2000-01-01 00:00:00Z",
"name": "operational",
"unit": "",
"value": 1
}
] for _ in range(len(unique_qubits))
],
Expand Down
4 changes: 4 additions & 0 deletions qiskit/transpiler/coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ def from_grid(cls, num_rows, num_columns, bidirectional=True):
cmap.add_edge(node, right)
return cmap

def largest_connected_component(self):
"""Return a set of qubits in the largest connected component."""
return max(nx.strongly_connected_components(self.graph), key=len)

def __str__(self):
"""Return a string representation of the coupling graph."""
string = ""
Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/defective_qubits-d826ccbd049603b9.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
Support was added to the :func:`qiskit.compiler.transpile` function for handling backends that have qubits or
gates marked as defective in their :class:`~qiskit.providers.models.BackendProperties`. If a gate or qubit
parameter in the :class:`~qiskit.providers.models.BackendProperties` has an ``operational`` field and it
is set to ``False``, then the :func:`~qiskit.compiler.transpile` function will consider the maximum connected
subgraph of operational qubits as a coupling map and handle the process to avoid using non-operational
qubits/gates.
Loading

0 comments on commit a67440a

Please sign in to comment.