Skip to content

Commit

Permalink
Revert "Support for backends with defective qubits and gates (Qiskit#…
Browse files Browse the repository at this point in the history
…4782)"

This PR was merged in as the last commit in the release as an admittedly
less than ideal implementation but a first pass of a feature we deemed
as necessary to include in the release. However, it has not been
throghoughly tested enough and is blocking the release of the
metapackage [1] because it breaks transpiling in the notebooks.
This PR has been problematic and was already reverted once for a similar
reason. This reverts commit a67440a to
unblock the metapackage release we should sit down and thing about how
to fit this feature into the compiler better before we attemp a third
time.

[1] https://travis-ci.com/github/Qiskit/qiskit/jobs/369663144#L732
  • Loading branch information
mtreinish committed Aug 7, 2020
1 parent 2cbccd5 commit 5bc9f04
Show file tree
Hide file tree
Showing 9 changed files with 7 additions and 660 deletions.
12 changes: 0 additions & 12 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1576,18 +1576,6 @@ 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: 7 additions & 162 deletions qiskit/compiler/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
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 @@ -30,8 +29,7 @@
from qiskit.circuit.quantumregister import Qubit
from qiskit import user_config
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.passes import ApplyLayout
from qiskit.converters import isinstanceint, isinstancelist, dag_to_circuit, circuit_to_dag
from qiskit.converters import isinstanceint, isinstancelist
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 @@ -266,6 +264,7 @@ 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 @@ -278,10 +277,6 @@ 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 @@ -313,68 +308,8 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua
if ms_basis_swap is not None:
pass_manager.append(MSBasisDecomposer(ms_basis_swap))

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
return pass_manager.run(circuit, callback=transpile_config['callback'],
output_name=transpile_config['output_name'])


def _parse_transpile_args(circuits, backend,
Expand All @@ -401,10 +336,8 @@ 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 @@ -418,7 +351,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, backend_num_qubits, faulty_qubits_map):
output_name, callback):
transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0],
coupling_map=args[1],
backend_properties=args[2],
Expand All @@ -429,45 +362,12 @@ def _parse_transpile_args(circuits, backend,
seed_transpiler=args[7]),
'optimization_level': args[8],
'output_name': args[9],
'callback': args[10],
'backend_num_qubits': args[11],
'faulty_qubits_map': args[12]}
'callback': args[10]}
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 @@ -487,14 +387,7 @@ 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:
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 = 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 @@ -513,46 +406,11 @@ 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 @@ -578,10 +436,8 @@ 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 @@ -627,17 +483,6 @@ 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: 0 additions & 52 deletions qiskit/providers/models/backendproperties.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,43 +308,6 @@ 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 @@ -447,21 +410,6 @@ 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: 0 additions & 6 deletions qiskit/test/mock/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,6 @@ 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: 0 additions & 4 deletions qiskit/transpiler/coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,6 @@ 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 5bc9f04

Please sign in to comment.