Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for backends with defective qubits and gates #4110

Merged
merged 73 commits into from
Jul 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e4035a6
initial commit
Mar 27, 2020
f14f154
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Mar 30, 2020
1b4b5f5
faulty qubit insertion
Mar 30, 2020
102cb57
operational False
Mar 30, 2020
ad0261a
pruned coupling map
Mar 30, 2020
b13e6fb
no gate operation parameter for now
Mar 31, 2020
0accd6d
faulty gate
Mar 31, 2020
c7422d1
fake_ourense_faulty_cx13
Mar 31, 2020
5d8fa92
testing of the fake backends
Mar 31, 2020
f9f2cc4
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 3, 2020
d8ea453
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 4, 2020
1c5c683
a test
Apr 4, 2020
a9b5fce
disconnected coupling map
Apr 4, 2020
2b5d3b0
add fault register at the end
Apr 4, 2020
702adc9
transform the circuit into the faulty backend
Apr 5, 2020
6103f0a
simulators do not have properties
Apr 5, 2020
250bc42
assertIdleQ1
Apr 5, 2020
644196b
assertEqualCount
Apr 5, 2020
26cd85d
remove faulty qubtis from properties
Apr 5, 2020
4c37fea
backend_property for faulty qubits
Apr 5, 2020
d906cdd
adapt Layout2qDistance to disconnected coupling map
Apr 5, 2020
444a435
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 6, 2020
a38c101
remove _create_qreg
Apr 7, 2020
108c7cd
assuming coupling map
Apr 8, 2020
a38a923
layout with disconnected qubits
Apr 8, 2020
92f5f94
test for faulty gate
Apr 8, 2020
775aa5e
support for faulty gate
Apr 8, 2020
c83e48b
remove gates when they are faulty from the backend properties
Apr 8, 2020
7fd8f3f
test
Apr 8, 2020
86cf9b6
unused import
Apr 8, 2020
25985e3
merge
Apr 8, 2020
9c4904f
new fault backend
Apr 8, 2020
b4bcb3b
test
Apr 9, 2020
232e1b8
take the largest connected component
Apr 9, 2020
05f1ae7
lint
Apr 9, 2020
04469e0
adjust test
Apr 9, 2020
54b5aab
lint
Apr 9, 2020
c398c6c
lint1
Apr 9, 2020
aaee4b5
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 9, 2020
488e717
lint2
Apr 9, 2020
b0234f3
lint3
Apr 9, 2020
f2cb84e
lint4
Apr 9, 2020
018145b
lint5
Apr 9, 2020
991975b
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 13, 2020
26895fd
_parse_initial_layout
Apr 13, 2020
dd2c757
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Apr 18, 2020
cee7c36
initial layot support
Apr 18, 2020
b0a4d07
TestFaultyCX13
Apr 18, 2020
6985728
more testing
Apr 18, 2020
d85dcde
cm -> context
Apr 18, 2020
1c4e380
Merge branch 'master' into defective_qubits_and_gates_connected_cm
Jun 1, 2020
e9f3524
Merge branch 'master' into defective_qubits_and_gates_connected_cm
Jun 11, 2020
6e92865
Update qiskit/providers/models/backendproperties.py
Jun 18, 2020
477015c
Apply suggestions from code review
Jun 18, 2020
323fc54
Merge branch 'master' into defective_qubits_and_gates_connected_cm
Jun 18, 2020
13b92f3
mv qiskit/test/mock/backends/ourense/fake_ourense_faulty* to test/pyt…
Jun 18, 2020
24ea395
cleaning up
Jun 18, 2020
a9dbcee
lint
Jun 18, 2020
ade0a3c
Merge branch 'master' into defective_qubits_and_gates_connected_cm
Jun 19, 2020
239af26
Merge branch 'master' of github.com:Qiskit/qiskit-terra into defectiv…
Jul 1, 2020
db52ca4
moving faulty_qubits and faulty_gates to properties
Jul 1, 2020
d24ca1a
another fix
Jul 1, 2020
339136c
lint
Jul 1, 2020
5cb5d97
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 7, 2020
fa15de7
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 7, 2020
c6fd128
Merge branch 'master' into defective_qubits_and_gates_connected_cm
ajavadia Jul 7, 2020
63ab997
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 7, 2020
9b5422f
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 7, 2020
a8e7b5e
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 8, 2020
4a7c46f
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 8, 2020
6f6a9fe
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 8, 2020
8925508
Merge branch 'master' into defective_qubits_and_gates_connected_cm
mergify[bot] Jul 8, 2020
16d7c96
Merge branch 'master' into defective_qubits_and_gates_connected_cm
Jul 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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