Código fonte de qiskit.transpiler.passes.layout.noise_adaptive_layout

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# 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.

"""Choose a noise-adaptive Layout based on current calibration data for the backend."""

import math

import rustworkx as rx

from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.target import target_to_backend_properties, Target


[documentos]class NoiseAdaptiveLayout(AnalysisPass): """Choose a noise-adaptive Layout based on current calibration data for the backend. This pass associates a physical qubit (int) to each virtual qubit of the circuit (Qubit), using calibration data. The pass implements the qubit mapping method from: Noise-Adaptive Compiler Mappings for Noisy Intermediate-Scale Quantum Computers Prakash Murali, Jonathan M. Baker, Ali Javadi-Abhari, Frederic T. Chong, Margaret R. Martonosi ASPLOS 2019 (arXiv:1901.11054). Methods: Ordering of edges: Map qubits edge-by-edge in the order of decreasing frequency of occurrence in the program dag. Initialization: If an edge exists with both endpoints unmapped, pick the best available hardware cx to execute this edge. Iterative step: When an edge exists with one endpoint unmapped, map that endpoint to a location which allows maximum reliability for CNOTs with previously mapped qubits. In the end if there are unmapped qubits (which don't participate in any CNOT), map them to any available hardware qubit. Notes: even though a `layout` is not strictly a property of the DAG, in the transpiler architecture it is best passed around between passes by being set in `property_set`. """ def __init__(self, backend_prop): """NoiseAdaptiveLayout initializer. Args: backend_prop (Union[BackendProperties, Target]): backend properties object Raises: TranspilerError: if invalid options """ super().__init__() if isinstance(backend_prop, Target): self.target = backend_prop self.backend_prop = target_to_backend_properties(self.target) else: self.target = None self.backend_prop = backend_prop self.swap_graph = rx.PyDiGraph() self.cx_reliability = {} self.readout_reliability = {} self.available_hw_qubits = [] self.gate_list = [] self.gate_reliability = {} self.swap_reliabs = {} self.prog_graph = rx.PyGraph() self.prog_neighbors = {} self.qarg_to_id = {} self.pending_program_edges = [] self.prog2hw = {} def _initialize_backend_prop(self): """Extract readout and CNOT errors and compute swap costs.""" backend_prop = self.backend_prop edge_list = [] for ginfo in backend_prop.gates: if ginfo.gate == "cx": for item in ginfo.parameters: if item.name == "gate_error": g_reliab = 1.0 - item.value break g_reliab = 1.0 swap_reliab = pow(g_reliab, 3) # convert swap reliability to edge weight # for the Floyd-Warshall shortest weighted paths algorithm swap_cost = -math.log(swap_reliab) if swap_reliab != 0 else math.inf edge_list.append((ginfo.qubits[0], ginfo.qubits[1], swap_cost)) edge_list.append((ginfo.qubits[1], ginfo.qubits[0], swap_cost)) self.cx_reliability[(ginfo.qubits[0], ginfo.qubits[1])] = g_reliab self.gate_list.append((ginfo.qubits[0], ginfo.qubits[1])) self.swap_graph.extend_from_weighted_edge_list(edge_list) idx = 0 for q in backend_prop.qubits: for nduv in q: if nduv.name == "readout_error": self.readout_reliability[idx] = 1.0 - nduv.value self.available_hw_qubits.append(idx) idx += 1 for edge in self.cx_reliability: self.gate_reliability[edge] = ( self.cx_reliability[edge] * self.readout_reliability[edge[0]] * self.readout_reliability[edge[1]] ) swap_reliabs_ro = rx.digraph_floyd_warshall_numpy(self.swap_graph, lambda weight: weight) for i in range(swap_reliabs_ro.shape[0]): self.swap_reliabs[i] = {} for j in range(swap_reliabs_ro.shape[1]): if (i, j) in self.cx_reliability: self.swap_reliabs[i][j] = self.cx_reliability[(i, j)] elif (j, i) in self.cx_reliability: self.swap_reliabs[i][j] = self.cx_reliability[(j, i)] else: best_reliab = 0.0 for n in self.swap_graph.neighbors(j): if (n, j) in self.cx_reliability: reliab = math.exp(-swap_reliabs_ro[i][n]) * self.cx_reliability[(n, j)] else: reliab = math.exp(-swap_reliabs_ro[i][n]) * self.cx_reliability[(j, n)] if reliab > best_reliab: best_reliab = reliab self.swap_reliabs[i][j] = best_reliab def _qarg_to_id(self, qubit): """Convert qarg with name and value to an integer id.""" return self.qarg_to_id[qubit] def _create_program_graph(self, dag): """Program graph has virtual qubits as nodes. Two nodes have an edge if the corresponding virtual qubits participate in a 2-qubit gate. The edge is weighted by the number of CNOTs between the pair. """ idx = 0 for q in dag.qubits: self.qarg_to_id[q] = idx idx += 1 edge_list = [] for gate in dag.two_qubit_ops(): qid1 = self._qarg_to_id(gate.qargs[0]) qid2 = self._qarg_to_id(gate.qargs[1]) min_q = min(qid1, qid2) max_q = max(qid1, qid2) edge_weight = 1 if self.prog_graph.has_edge(min_q, max_q): edge_weight = self.prog_graph[min_q][max_q]["weight"] + 1 edge_list.append((min_q, max_q, edge_weight)) self.prog_graph.extend_from_weighted_edge_list(edge_list) return idx def _select_next_edge(self): """Select the next edge. If there is an edge with one endpoint mapped, return it. Else return in the first edge """ for edge in self.pending_program_edges: q1_mapped = edge[0] in self.prog2hw q2_mapped = edge[1] in self.prog2hw assert not (q1_mapped and q2_mapped) if q1_mapped or q2_mapped: return edge return self.pending_program_edges[0] def _select_best_remaining_cx(self): """Select best remaining CNOT in the hardware for the next program edge.""" candidates = [] for gate in self.gate_list: chk1 = gate[0] in self.available_hw_qubits chk2 = gate[1] in self.available_hw_qubits if chk1 and chk2: candidates.append(gate) best_reliab = 0 best_item = None for item in candidates: if self.gate_reliability[item] > best_reliab: best_reliab = self.gate_reliability[item] best_item = item return best_item def _select_best_remaining_qubit(self, prog_qubit): """Select the best remaining hardware qubit for the next program qubit.""" reliab_store = {} if prog_qubit not in self.prog_neighbors: self.prog_neighbors[prog_qubit] = self.prog_graph.neighbors(prog_qubit) for hw_qubit in self.available_hw_qubits: reliab = 1 for n in self.prog_neighbors[prog_qubit]: if n in self.prog2hw: reliab *= self.swap_reliabs[self.prog2hw[n]][hw_qubit] reliab *= self.readout_reliability[hw_qubit] reliab_store[hw_qubit] = reliab max_reliab = 0 best_hw_qubit = None for hw_qubit in reliab_store: if reliab_store[hw_qubit] > max_reliab: max_reliab = reliab_store[hw_qubit] best_hw_qubit = hw_qubit return best_hw_qubit
[documentos] def run(self, dag): """Run the NoiseAdaptiveLayout pass on `dag`.""" self.swap_graph = rx.PyDiGraph() self.cx_reliability = {} self.readout_reliability = {} self.available_hw_qubits = [] self.gate_list = [] self.gate_reliability = {} self.swap_reliabs = {} self.prog_graph = rx.PyGraph() self.prog_neighbors = {} self.qarg_to_id = {} self.pending_program_edges = [] self.prog2hw = {} self._initialize_backend_prop() num_qubits = self._create_program_graph(dag) if num_qubits > len(self.swap_graph): raise TranspilerError("Number of qubits greater than device.") # sort by weight, then edge name for determinism (since networkx on python 3.5 returns # different order of edges) self.pending_program_edges = sorted( self.prog_graph.weighted_edge_list(), key=lambda x: [x[2], -x[0], -x[1]], reverse=True ) while self.pending_program_edges: edge = self._select_next_edge() q1_mapped = edge[0] in self.prog2hw q2_mapped = edge[1] in self.prog2hw if (not q1_mapped) and (not q2_mapped): best_hw_edge = self._select_best_remaining_cx() if best_hw_edge is None: raise TranspilerError( "CNOT({}, {}) could not be placed " "in selected device.".format(edge[0], edge[1]) ) self.prog2hw[edge[0]] = best_hw_edge[0] self.prog2hw[edge[1]] = best_hw_edge[1] self.available_hw_qubits.remove(best_hw_edge[0]) self.available_hw_qubits.remove(best_hw_edge[1]) elif not q1_mapped: best_hw_qubit = self._select_best_remaining_qubit(edge[0]) if best_hw_qubit is None: raise TranspilerError( "CNOT({}, {}) could not be placed in selected device. " "No qubit near qr[{}] available".format(edge[0], edge[1], edge[0]) ) self.prog2hw[edge[0]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) else: best_hw_qubit = self._select_best_remaining_qubit(edge[1]) if best_hw_qubit is None: raise TranspilerError( "CNOT({}, {}) could not be placed in selected device. " "No qubit near qr[{}] available".format(edge[0], edge[1], edge[1]) ) self.prog2hw[edge[1]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) new_edges = [ x for x in self.pending_program_edges if not (x[0] in self.prog2hw and x[1] in self.prog2hw) ] self.pending_program_edges = new_edges for qid in self.qarg_to_id.values(): if qid not in self.prog2hw: self.prog2hw[qid] = self.available_hw_qubits[0] self.available_hw_qubits.remove(self.prog2hw[qid]) layout = Layout() for q in dag.qubits: pid = self._qarg_to_id(q) hwid = self.prog2hw[pid] layout[q] = hwid for qreg in dag.qregs.values(): layout.add_register(qreg) self.property_set["layout"] = layout