Skip to content

Commit

Permalink
Support for backends with defective qubits and gates (#4110)
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 <ajavadia@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>

* 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

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 13, 2020
1 parent e03cb1b commit 41cf87c
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 15 deletions.
12 changes: 12 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1460,6 +1460,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
164 changes: 149 additions & 15 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 @@ -201,17 +203,20 @@ def callback_func(**kwargs):
config = user_config.get_config()
optimization_level = config.get('transpile_optimization_level', 1)

faulty_qubits_map = _create_faulty_qubits_map(backend)

# Get transpile_args to configure the circuit transpilation job(s)
transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map,
backend_properties, initial_layout,
layout_method, routing_method, translation_method,
seed_transpiler, optimization_level,
callback, output_name)
callback, output_name, faulty_qubits_map)

_check_circuits_coupling_map(circuits, transpile_args, backend)

# Transpile circuits in parallel
circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_args)))
circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_args)),
task_args=(faulty_qubits_map, backend))

if len(circuits) == 1:
end_time = time()
Expand Down Expand Up @@ -256,18 +261,23 @@ def _log_transpile_time(start_time, end_time):
LOG.info(log_msg)


def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> QuantumCircuit:
def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict],
faulty_qubits_map: [None, Dict], backend) -> QuantumCircuit:
"""Select a PassManager and run a single circuit through it.
Args:
circuit_config_tuple (tuple):
circuit (QuantumCircuit): circuit to transpile
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}
faulty_qubits_map (dict or None): Only used when backends report faulty qubits/gates. It
is a map from working qubit in the backend to dumnmy qubits that are consecutive
and connected.
backend (BaseBackend or None): The backend with remap the circuit if it has faulty
qubits/gates.
Returns:
The transpiled circuit
Raises:
Expand Down Expand Up @@ -308,15 +318,73 @@ 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 faulty_qubits_map:
return _remap_circuit_faulty_backend(result, backend, faulty_qubits_map)

return result


def _remap_circuit_faulty_backend(circuit, backend, faulty_qubits_map):
faulty_qubits = backend.properties().faulty_qubits() if backend.properties() 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(backend.configuration().n_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,
basis_gates, coupling_map, backend_properties,
initial_layout, layout_method, routing_method, translation_method,
seed_transpiler, optimization_level,
callback, output_name) -> List[Dict]:
callback, output_name, faulty_qubits_map) -> 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
what types of inputs are allowed.
Expand All @@ -336,9 +404,10 @@ def _parse_transpile_args(circuits, backend,
num_circuits = len(circuits)

basis_gates = _parse_basis_gates(basis_gates, backend, circuits)
coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits)
backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits)
initial_layout = _parse_initial_layout(initial_layout, circuits)
coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits, faulty_qubits_map)
backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits,
faulty_qubits_map)
initial_layout = _parse_initial_layout(initial_layout, circuits, faulty_qubits_map)
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)
Expand Down Expand Up @@ -368,6 +437,37 @@ def _parse_transpile_args(circuits, backend,
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 @@ -381,13 +481,19 @@ def _parse_basis_gates(basis_gates, backend, circuits):
return basis_gates


def _parse_coupling_map(coupling_map, backend, num_circuits):
def _parse_coupling_map(coupling_map, backend, num_circuits, faulty_map):
# try getting coupling_map from user, else backend
if coupling_map is None:
if getattr(backend, 'configuration', None):
configuration = backend.configuration()
if hasattr(configuration, 'coupling_map') and configuration.coupling_map:
coupling_map = CouplingMap(configuration.coupling_map)
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 @@ -401,17 +507,40 @@ def _parse_coupling_map(coupling_map, backend, num_circuits):
return coupling_map


def _parse_backend_properties(backend_properties, backend, num_circuits):
def _parse_backend_properties(backend_properties, backend, num_circuits, faulty_qubits_map):
# try getting backend_properties from user, else backend
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)
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_initial_layout(initial_layout, circuits):
def _parse_initial_layout(initial_layout, circuits, faulty_qubits_map):
# 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}
def _layout_from_raw(initial_layout, circuit):
Expand All @@ -436,8 +565,13 @@ 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)

if faulty_qubits_map:
initial_layout = [_remap_layout_faulty_backend(i, faulty_qubits_map) for i in
initial_layout]
return initial_layout


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 @@ -314,6 +314,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 @@ -416,6 +453,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
Loading

0 comments on commit 41cf87c

Please sign in to comment.