Skip to main content
IBM Quantum Platform

Transpiler

qiskit.transpiler


Panoramica

Nota

Se si ha già familiarità con i concetti di trasposizione/compilazione dei circuiti, si consiglia di passare al punto precedente:

La transpilazione è il processo di riscrittura di un dato circuito di ingresso per adattarlo alla topologia di uno specifico dispositivo quantistico, e/o per ottimizzare il circuito per l'esecuzione su sistemi quantistici.

La maggior parte dei circuiti deve subire una serie di trasformazioni che li rendono compatibili con un determinato dispositivo di destinazione e li ottimizzano per ridurre gli effetti del rumore sui risultati ottenuti. Riscrivere i circuiti quantistici per adattarli ai vincoli hardware e ottimizzare le prestazioni può essere tutt'altro che banale. Il flusso della logica nella catena degli strumenti di riscrittura non deve necessariamente essere lineare e spesso può avere sottocicli iterativi, rami condizionali e altri comportamenti complessi. Detto questo, il flusso di compilazione standard segue la struttura indicata di seguito:

Il processo di transpilazione prende il circuito di ingresso, applica i passaggi di transpilazione e produce il circuito di uscita.

QuantumCircuitQiskit utilizza la rappresentazione intermedia (IR) di un circuito basata DAGCircuit su grafi in tutto lo stack del transpiler, anziché quella basata su alberi. Una pipeline di transpiler è un PassManager oggetto il cui PassManager.run() metodo accetta un QuantumCircuit e lo converte in un DAGCircuit, quindi sottopone l'IR a una sequenza di passaggi, restituendo infine un QuantumCircuit . Un passaggio può essere un AnalysisPass, che calcola e memorizza le proprietà del circuito nello stato PropertySet, oppure un TransformationPass, che modifica l'IR per raggiungere un obiettivo specifico. Si può pensare a una pipeline come suddivisa in “fasi”, ciascuna delle quali è responsabile di una trasformazione di alto livello.

Qiskit mette a disposizione un generatore predefinito di pipeline di transpilazione che utilizza la funzione generate_preset_pass_manager(). Questo restituisce una pipeline correttamente configurata per la transpilazione completa, a un livello scelto optimization_level (compreso tra 0 e 3, inclusi). A meno che tu non stia cercando qualcosa di altamente specializzato, questo è quasi certamente il punto di partenza che fa al caso tuo. Ecco un esempio di transpilazione:

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

# Any abstract circuit you want:
abstract = QuantumCircuit(2)
abstract.h(0)
abstract.cx(0, 1)

# Any method you like to retrieve the backend you want to run on:
backend = QiskitRuntimeService().backend("some-backend")

# Create the pass manager for the transpilation ...
pm = generate_preset_pass_manager(backend=backend)
# ... and use it (as many times as you like).
physical = pm.run(abstract)

Nei primi esperimenti volti alla tolleranza ai guasti, le funzioni generate_preset_pass_manager() e transpile() attivano una pipeline di transpilazione specializzata quando la base di destinazione è costituita da porte Clifford+T; per ulteriori informazioni, consultare generate_preset_clifford_t_pass_manager() la documentazione. Si consiglia di utilizzare quest'ultimo per una configurazione dettagliata delle pipeline Clifford+T. Ad esempio, la precisione di sintesi dell' RZR_Z non può essere impostata tramite "unitary_synthesis_method" in generate_preset_pass_manager() ma può essere impostata solo a livello globale tramite il "approximation_degree". Tuttavia, generate_preset_clifford_t_pass_manager() mette "rz_synthesis_config" in luce questo aspetto.

Ad esempio:

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import QFTGate
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import get_clifford_gate_names

# Any abstract circuit you want:
abstract = QuantumCircuit(4)
abstract.append(QFTGate(4), [0, 1, 2, 3])

# Use all Clifford+T basis gates
basis_gates = get_clifford_gate_names() + ["t", "tdg"]

# Create and run the pass manager
pm = generate_preset_pass_manager(basis_gates=basis_gates)
transpiled = pm.run(abstract)

Per la maggior parte dei casi d'uso, questo è tutto ciò che serve. Tutta l'infrastruttura del transpiler di Qiskit è comunque altamente estensibile e configurabile. Il resto di questa pagina spiega come sfruttare le capacità di basso livello dello stack del transpiler.


Gestori di pass preimpostati

La funzione generate_preset_pass_manager() crea i “gestori di passaggi predefiniti”. Si tratta in tutti i casi di PassManager, che vengono quindi utilizzati passando un QuantumCircuit al PassManager.run() metodo. Più precisamente, i gestori di passaggi predefiniti sono istanze di StagedPassManager, il che consente una maggiore configurazione delle singole fasi di una transpilazione, compresi gli hook pre- e post-fase.

Un gestore di passaggi preimpostati può avere fino a sei stadi denominati. Questi sono riassunti di seguito, in ordine di esecuzione, con informazioni più approfondite nelle sottosezioni successive.

init

Ottimizzazioni del circuito astratto e riduzione delle operazioni multi-qubit a operazioni a uno o due bit. Per maggiori dettagli, vedere Fase di inizializzazione.

layout

Scegliere una mappatura iniziale di qubit virtuali a qubit fisici, compresa l'espansione del circuito per contenere ancille esplicite. Questa fase a volte è subordinata a routing. Per maggiori dettagli, vedere la fase di layout.

routing

Inserire delle porte nel circuito per garantire che rispetti i vincoli di connettività del Target. I gate inseriti non devono necessariamente corrispondere all’ISA di destinazione, quindi spesso sono semplicemente swap delle istruzioni. Questa fase viene talvolta omessa, quando la layout fase stessa svolge il proprio compito. Per ulteriori dettagli, consultare la sezione "Fase di instradamento".

translation

Convertire tutte le porte logiche del circuito in porte compatibili con l'ISA del Target. Per ulteriori dettagli, consultare la sezione "Fase di traduzione".

optimization

Ottimizzazioni di basso livello, consapevoli dell'hardware. A differenza delle ottimizzazioni astratte dello stadio init , questo stadio agisce su un circuito fisico. Per maggiori dettagli, vedere la fase di ottimizzazione.

scheduling

Inserire Delay per rendere esplicita la temporizzazione wall-clock di un circuito. Ciò può includere anche tecniche di riduzione degli errori online consapevoli dell'hardware, come il disaccoppiamento dinamico, che dipendono dalla conoscenza dei tempi del wall-clock. Per maggiori dettagli, vedere Fase di pianificazione.

Le pipeline del transpiler preimpostate possono essere configurate anche ad alto livello, impostando un'opzione optimization_level. È un numero intero da 0 a 3, incluso, che indica lo sforzo relativo da esercitare nel tentativo di ottimizzare il circuito per l'hardware. Il livello 0 disattiva tutte le ottimizzazioni non necessarie; vengono utilizzate solo le trasformazioni necessarie per rendere il circuito eseguibile. Dall'altra parte, il livello 3 consente una gamma completa di tecniche di ottimizzazione, alcune delle quali possono essere molto costose in termini di tempo di compilazione. Come per i compilatori classici, il livello di ottimizzazione 3 non garantisce sempre i risultati migliori. Qiskit sceglie per default il livello di ottimizzazione 2, come compromesso tra il tempo di compilazione e la quantità di ottimizzazione prevista.

Il livello di ottimizzazione determina quali implementazioni vengono utilizzate per impostazione predefinita in una determinata fase, anche se è possibile sovrascrivere questa impostazione passando argomenti espliciti <stage>_method="<choice>" a generate_preset_pass_manager().

Riproducibilità delle pipeline preimpostate

La compilazione quantistica comporta spesso la risoluzione di problemi noti per avere una complessità non polinomiale e che risultano quindi difficili da ottimizzare a livello globale. In questi casi, gli algoritmi stocastici ed euristici sono spesso più indicati. Ciò comporta tuttavia dei problemi di riproducibilità.

I gestori di passaggi preimpostati includono quasi sempre passaggi stocastici e euristici. Se si vuole garantire la riproducibilità di una compilazione, si può passare un numero intero noto all'argomento seed_transpiler delle funzioni del generatore.

Tutti i plugin integrati in Qiskit devono generare le proprie analisi e modificare il DAGCircuit in modo deterministico qualora la loro randomizzazione (se presente) sia inizializzata con un seme, in modo che la compilazione possa essere ripetuta in un secondo momento. Ci sono però dei limiti:

  • Tutti i passaggi integrati con componenti stocastici devono fornire un modo per alimentare la randomizzazione e, se alimentati, devono rispettare le regole dell'output deterministico.
  • Tutti i passaggi integrati privi di componenti stocastiche devono rispettare le regole dell'uscita deterministica per un input identico. È consentito mantenere una cache per motivi di efficienza, ma, a parità di insieme di input, i risultati del passaggio devono essere gli stessi se il passaggio viene chiamato più volte, a meno che un fattore esterno al controllo del passaggio non modifichi in loco uno dei suoi input (ad esempio, nei gestori di passaggi predefiniti si BasisTranslator utilizza per SessionEquivalenceLibrary impostazione predefinita la libreria di equivalenze, e non è un bug ottenere risultati diversi se vengono aggiunte nuove voci alla libreria di equivalenze). Per “output” si intende qualsiasi elemento che il pass genera per un utilizzo successivo; può trattarsi del valore esplicito return restituito dal pass, ma include anche le proprietà destinate a essere utilizzate in un secondo momento nel PropertySet.
  • L'output di un passaggio deve essere deterministico su una data macchina per una data versione di Qiskit e un ambiente congelato, indipendentemente dal numero di thread disponibili per il passaggio. Molti passaggi incorporati in Qiskit utilizzano la concomitanza dei thread e non è consentito avere un comportamento diverso in base al numero di thread.
  • Non è necessario che l'output di un passaggio per un seme fisso sia uguale se una parte dell'ambiente Python sottostante cambia (ad esempio l'aggiornamento di un pacchetto dipendente) o se le librerie matematiche del sistema cambiano (ad esempio se è disponibile una diversa implementazione di BLAS).
  • Non è necessario che l'output di una passata per un seme fisso sia uguale tra i vari sistemi operativi (anche se in genere è l'implementazione della libreria matematica del sistema la causa principale delle differenze legate al sistema operativo).
  • Non è necessario che l'output di un passaggio per un seme fisso sia lo stesso tra due macchine che hanno a disposizione istruzioni CPU diverse; si prevede che diverse implementazioni di kernel matematici di base possano produrre un comportamento diverso se sono disponibili istruzioni CPU diverse, come ad esempio istruzioni di moltiplicazione-aggiunta fuse che hanno caratteristiche di arrotondamento diverse rispetto a due istruzioni separate di moltiplicazione e aggiunta in virgola mobile.
  • L'output di un passaggio per un seme fisso deve essere lo stesso, indipendentemente dal numero di thread che è consentito utilizzare, a meno che l'utente non scelga specificamente di non adottare questo comportamento. Ad esempio, nei passmanager preimpostati, i metodi di layout e di routing di Sabre devono eseguire lo stesso numero di prove per impostazione predefinita, indipendentemente dal fatto che sia consentito un solo thread o anche più thread rispetto alle prove, anche se questo comportamento può essere esplicitamente sovrascritto impostando la variabile d'ambiente QISKIT_SABRE_ALL_THREADS per scegliere di diventare sensibile al numero di thread.
  • Tutte le regole di cui sopra si applicano anche tra sessioni separate dell'interprete Python, anche quando PYTHONHASHSEED non è stato impostato esplicitamente.

In generale, un utente del DAGCircuit dovrebbe poter dare per scontato che, dopo l’esecuzione di qualsiasi combinazione di passaggi Qiskit integrati (e, se del caso, con valori iniziali predefiniti) con input fissi, l’output esatto di tutti DAGCircuit() i metodi sia deterministico. Ciò include l'ordine dei risultati anche dei metodi che non forniscono alcuna garanzia in merito all'ordine; sebbene non sia possibile fare affidamento sulla semantica e sull'ordine preciso, è invece possibile fare affidamento sul determinismo dello stesso per input fissi.

Gli autori di passi di transpiler dovrebbero consultare Randomness and determinism per una discussione su come rendere deterministico un passo di transpiler.

Scelta delle implementazioni predefinite dello stadio

Qiskit include diverse implementazioni delle fasi sopra descritte e altre possono essere installate come "plugin" separati. Per controllare quale implementazione di uno stadio viene utilizzata, passare il suo nome all'argomento della parola chiave <stage>_method delle due funzioni, come ad esempio translation_method="translator". Per saperne di più sull'implementazione di tali plugin esterni per uno stage, vedere qiskit.transpiler.preset_passmanagers.plugin.

Ad esempio, per generare un gestore di passaggi preimpostati a livello di ottimizzazione 1 che utilizzi esplicitamente il metodo trivial per il layout e il metodo sabre per l'instradamento, occorre procedere come segue:

from qiskit.transpiler import generate_preset_pass_manager
from qiskit.providers.fake_provider import GenericBackendV2

# Whatever backend you like:
backend = GenericBackendV2(num_qubits=5)

pass_manager = generate_preset_pass_manager(
    optimization_level=1,
    backend=backend,
    layout_method="trivial",
    routing_method="sabre",
)
Nota

L'insieme integrato di plugin disponibili per ciascuna fase fa parte dell'API pubblica di Qiskit ed è soggetto a tutte le garanzie di stabilità. Ciò include gli effetti logici di alto livello di tale metodo (ad esempio, routing_method="sabre" utilizzerà sempre un algoritmo derivato da Sabre). La struttura interna esatta del codice PassManager che rappresenta il palcoscenico non è, tuttavia, fissa; l'ordine delle iterazioni potrebbe variare tra le versioni minori, oppure potrebbero essere introdotte nuove iterazioni.

Per qualsiasi stadio che ne abbia uno, il metodo denominato "default" è il più soggetto a modifiche. In genere Qiskit apporta solo modifiche algoritmiche complete al metodo predefinito tra le versioni maggiori, ma potrebbe riequilibrare l'euristica e aggiungere nuovi passaggi ai metodi predefiniti tra le versioni minori.

Poiché l'output di generate_preset_pass_manager() è un StagedPassManager, è anche possibile modificare il gestore dei passaggi dopo la sua creazione per fornire un'implementazione della fase completamente personalizzata. Ad esempio, se si volesse eseguire una fase di pianificazione personalizzata utilizzando il disaccoppiamento dinamico (tramite il PadDynamicalDecoupling pass) e aggiungere anche un'ottimizzazione logica iniziale prima dell'instradamento, si procederebbe in modo simile a quanto segue (partendo dall'esempio precedente):

import numpy as np
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.circuit import library as lib
from qiskit.transpiler import PassManager, generate_preset_pass_manager
from qiskit.transpiler.passes import (
    ALAPScheduleAnalysis,
    InverseCancellation,
    PadDynamicalDecoupling,
)

backend = GenericBackendV2(num_qubits=5)
dd_sequence = [lib.XGate(), lib.XGate()]
scheduling_pm = PassManager(
    [
        ALAPScheduleAnalysis(target=backend.target),
        PadDynamicalDecoupling(target=backend.target, dd_sequence=dd_sequence),
    ]
)
inverse_gate_list = [
    lib.CXGate(),
    lib.HGate(),
    (lib.RXGate(np.pi / 4), lib.RXGate(-np.pi / 4)),
    (lib.PhaseGate(np.pi / 4), lib.PhaseGate(-np.pi / 4)),
    (lib.TGate(), lib.TdgGate()),
]
logical_opt = PassManager([InverseCancellation(inverse_gate_list)])

pass_manager = generate_preset_pass_manager(optimization_level=0)
# Add pre-layout stage to run extra logical optimization
pass_manager.pre_layout = logical_opt
# Set scheduling stage to custom pass manager
pass_manager.scheduling = scheduling_pm

Ora, quando il gestore dei passaggi per lo stage viene eseguito tramite il run() metodo, il scheduling_pm``logical_opt gestore dei passaggi verrà chiamato prima dello layout stage e verrà utilizzato per lo scheduling stage al posto di quello predefinito.

Se state costruendo stadi personalizzati per i gestori di passaggi preimpostati, potreste trovare utili alcune delle funzioni di aiuto di basso livello contenute in qiskit.transpiler.preset_passmanagers utili.

Fase di inizializzazione

Vedi anche

Spiegazione della fase iniziale

Spiegazione di livello superiore per l'utente della fase init nella guida IBM Quantum.

Questa init fase è responsabile delle ottimizzazioni logiche di alto livello sui circuiti astratti e della riduzione delle operazioni su più qubit (3+) a una serie di operazioni su uno o due qubit. Trattandosi della prima esecuzione della fase, il suo input è un circuito completamente astratto. Lo init stage deve essere in grado di gestire gate personalizzati definiti dall'utente e tutti gli oggetti di descrizione astratta dei circuiti di alto livello, quali AnnotatedOperation.

L'uscita dello stadio init è un circuito astratto che contiene solo operazioni a uno o due qubit.

Quando si scrivono i plugin dello stage, il punto di ingresso per init è qiskit.transpiler.init. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoSrotolamento incorporato delle operazioni multi-qubit e ottimizzazioni astratte.

Plugin default integrato

Al livello di ottimizzazione 0, non viene eseguita alcuna ottimizzazione astratta. Il plugin predefinito si limita a “srotolare” le operazioni che coinvolgono più di tre qubit, accedendo ai loro campi gerarchici definition .

Ai livelli di ottimizzazione 1 e superiori, il plugin predefinito esegue anche la semplice cancellazione di porte inverse adiacenti, come ad esempio due porte `cx' retrostanti.

Ai livelli di ottimizzazione 2 e 3, il plugin predefinito consente una gamma molto più ampia di ottimizzazioni astratte. Ciò comprende:

  • “Elisione virtuale delle permutazioni” (vedi ElidePermutations), in cui le operazioni che inducono esplicitamente permutazioni vengono rimosse e sostituite da una rimappatura dei qubit virtuali.
  • Analisi della struttura di commutazione dell'IR per trovare coppie di porte che possono essere annullate.
  • Suddivisione numerica di operazioni a due-qubit che possono essere espresse come una serie di operazioni a un-qubit separabili.
  • Rimozione delle operazioni impercettibili, come le rotazioni di Pauli ad angolo piccolo e le operazioni diagonali immediatamente precedenti le misurazioni.

Fase di layout

Vedi anche

Spiegazione della fase di layout

Spiegazione di livello superiore per l'utente della fase di layout nella guida IBM Quantum.

La fase di layout è responsabile della mappatura iniziale tra i qubit virtuali del circuito di ingresso e i qubit hardware del target. Questo include l'espansione del circuito di ingresso con ancillas esplicite in modo che abbia tanti qubit quanti ne ha l'obiettivo e la riscrittura di tutte le operazioni in termini di qubit hardware. Questo problema può essere chiamato "problema di posizionamento" anche in altri toolkit o nella letteratura.

PropertySetNella fase di layout è necessario impostare le proprietà layout e original_qubit_indices nel file. della pipeline.

Nota

Tutti i plugin integrati per la fase di layout daranno la priorità a un layout esplicito selezionato tramite l'argomento initial_layout di generate_preset_pass_manager() o transpile().

In qualsiasi punto di un circuito, possiamo identificare una mappatura tra i qubit "virtuali" attualmente attivi del circuito di ingresso e i qubit hardware del backend. Un qubit hardware può sempre rappresentare un solo qubit virtuale in un determinato punto, ma la mappatura può variare nel corso del circuito. In linea di principio, alcuni qubit virtuali potrebbero non essere mappati in tutti i punti dell'esecuzione del circuito, se il tempo di vita di uno stato di qubit virtuale può essere ridotto, anche se attualmente le pipeline integrate in Qiskit non utilizzano questa possibilità.

Illustrazione di come i qubit virtuali di un circuito di ingresso possano essere mappati su qubit hardware nella mappa di connettività di un dispositivo backend.

Lo stadio di layout non è responsabile di garantire che la connettività della destinazione sia rispettata lungo tutto il circuito, né che tutte le operazioni siano valide per l'esecuzione diretta sulla destinazione; queste sono responsabilità degli stadi di instradamento e di traduzione, rispettivamente.

La scelta del layout iniziale è uno dei fattori più importanti che influisce sulla qualità del circuito di uscita. La fase di layout è spesso la più dispendiosa dal punto di vista computazionale nelle pipeline predefinite; il plugin predefinito per il layout prova persino diversi algoritmi (descritti in dettaglio in Plugin predefinito incorporato ).

La situazione ideale nella fase di layout consiste nel trovare un layout “perfetto”, in cui tutte le operazioni rispettino i vincoli di connettività del circuito Target , in modo tale da rendere superflua la fase di instradamento. In genere ciò non è possibile per circuiti di input arbitrari, ma quando lo è, il VF2Layout "pass" può essere utilizzato per individuare un layout iniziale valido. Se vengono individuati più layout perfetti, si ricorre a un algoritmo euristico di valutazione basato sui tassi di errore stimati per decidere quale utilizzare.

In tutti i plugin integrati, il generate_preset_pass_manager() passaggio dell'argomento initial_layout comporta l'utilizzo del layout specificato così com'è, saltando la logica di "scelta" individuale. Tutti i plugin integrati gestiscono anche l'incorporamento del circuito a tutta larghezza del dispositivo, compresa l'assegnazione degli ancilla.

Se si scrive il proprio plugin di layout, si può trovare generate_embed_passmanager() utile per automatizzare la fase di "incorporazione" dell'applicazione di layout.

Quando si scrivono i plugin dello stage, il punto di ingresso per layout è qiskit.transpiler.layout. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoAi massimi livelli di ottimizzazione, cerca di trovare un layout perfetto, quindi tenta un passaggio combinato di layout e instradamento basato su Sabre.
densitàTrova il sottografo più denso (in termini di gradi di collegamento dei qubit) del backend da usare come qubit iniziale.
banaleMappa il qubit virtuale 0 al qubit fisico 0, e così via.
sciabolaUtilizza l 'algoritmo di layout Sabre migliorato di Qiskit.

A tutti i livelli di ottimizzazione, il metodo di layout predefinito è default, anche se la struttura di questa fase cambia radicalmente a seconda del livello.

Plugin default integrato

Un'amalgama di diverse tecniche di layout.

Al livello di ottimizzazione 0, viene scelto il layout banale.

A livelli di ottimizzazione superiori a 0, si verifica un processo in due fasi:

  1. Per prima cosa, usa VF2Layout per cercare di trovare una disposizione “perfetta”. Il numero massimo di chiamate al valutatore di isomorfismo aumenta con il livello di ottimizzazione. Nel caso di obiettivi molto grandi e complessi, non è detto che si riesca a trovare una disposizione perfetta, anche se questa esistesse, ma le probabilità aumentano con l’aumentare del livello di ottimizzazione.
  2. Se non è possibile trovare una disposizione perfetta, utilizzare SabreLayout per scegliere una disposizione iniziale, aumentando il numero di tentativi per la disposizione iniziale, di tentativi con la mappa di scambio e di iterazioni avanti-indietro in base al livello di ottimizzazione.

Inoltre, il livello di ottimizzazione 1 prova anche il layout banale prima della versione VF2-based, per una compatibilità storica con il passato.

Plugin dense integrato

Utilizza il DenseLayout pass per scegliere il layout. Questo passaggio individua il sottografo connesso più denso del grafo di connettività completo di destinazione, dove per “più denso” si intende che vengono privilegiati i qubit hardware con il maggior numero di connessioni disponibili. La mappatura dal virtuale all'hardware viene completata assegnando i qubit virtuali di grado più elevato ai qubit hardware di grado più elevato.

Si tratta di un metodo euristico relativamente economico per la scelta di un layout iniziale, ma in genere offre una qualità del risultato di gran lunga inferiore rispetto ai metodi basati su Sabre. Il plugin di layout predefinito utilizza la mappatura iniziale selezionata da DenseLayout come uno dei suoi layout iniziali per inizializzare l'algoritmo Sabre.

Plugin trivial integrato

Utilizza il TrivialLayout pass per scegliere il layout. Si tratta dell'assegnazione più semplice, in cui ogni qubit virtuale viene assegnato al qubit hardware con lo stesso indice; pertanto, il qubit virtuale 0 viene mappato sul qubit hardware 0, e così via.

Questo metodo è molto utile per gli esperimenti di caratterizzazione hardware, in cui il circuito "astratto" in ingresso è già a tutta larghezza sul dispositivo, le sue operazioni corrispondono a operazioni fisiche e il transpiler viene invocato solo per formalizzare la creazione di un circuito fisico QuantumCircuit.

Plugin sabre integrato

Utilizza il SabreLayout per scegliere un layout iniziale, impiegando l’algoritmo di routing Sabre modificato di Qiskit come subroutine per eseguire lo swap-map del circuito candidato sia in avanti che all’indietro.

In sintesi, la componente di layout dell' algoritmo Sabre originale sceglie un layout iniziale in modo arbitrario, poi cerca di "migliorarlo" eseguendo l'instradamento sul circuito, invertendo il circuito ed eseguendo l'instradamento sul circuito invertito con la precedente assegnazione "finale" tra virtuale e hardware come stato iniziale. Il livello di ottimizzazione configurato decide quante iterazioni di questo tira e molla fare e quanti diversi layout iniziali casuali provare.

La differenza principale rispetto allo stadio predefinito con livelli di ottimizzazione diversi da 0 è che questo plugin esegue solo l'algoritmo basato su Sabre. Non cerca di trovare un layout perfetto, né di trovare un layout banale.

Fase di instradamento

Vedi anche

Spiegazione della fase di instradamento

Spiegazione di livello superiore per l'utente della fase di instradamento nella guida IBM Quantum.

La fase di instradamento assicura che il grafo di connettività virtuale del circuito sia compatibile con il grafo di connettività hardware della destinazione. In termini più semplici, la fase di instradamento assicura che tutte le porte a due qubit del circuito siano mappate su qubit hardware che hanno un'operazione a due qubit definita nell'ISA di destinazione. È possibile che questo problema venga indicato come problema di "mappatura" o "swap-mapping" in altri toolkit o nella letteratura.

Gli algoritmi di instradamento fanno questo inserendo le porte di swap nel circuito e modificando la mappatura dei qubit da virtuale a hardware nel corso dell'esecuzione del circuito.

La fase di instradamento non deve garantire che tutti i gate del circuito siano validi per l'ISA di destinazione. SwapGateAd esempio, un plugin di routing può lasciare dei gate letterali swap nel circuito, anche se il circuito Target non ne contiene. Tuttavia, per ogni coppia di qubit hardware a cui viene applicato un gate nel circuito, deve esserci almeno un gate a due qubit definito nel circuito Target stesso.

La fase di instradamento deve impostare le final_layout proprietà e virtual_permutation_layout nel PropertySet qualora sia avvenuto l'instradamento.

Tutte le fasi di routing integrate in Qiskit eseguiranno inoltre il VF2PostLayout passaggio dopo il routing. Ciò potrebbe comportare una ridefinizione della configurazione iniziale, qualora fosse possibile individuare qubit con un errore inferiore. Questo passaggio è molto simile alla VF2Layout classe utilizzata dal plugin di layout predefinito, tranne per VF2PostLayout il fatto che possiamo garantire che esista almeno un sottografo indotto isomorfo della topologia di destinazione che corrisponda alla topologia del circuito.

Nota

I plugin di routing integrati in Qiskit assumono tutti, in generale, che tutte le coppie di qubit con un collegamento a due qubit definito abbiano un insieme universale di porte definite per quei due qubit. L'hardware non deve necessariamente rispettarlo (per esempio, se l'unica porta a due qubit definita è swap, allora non si possono realizzare operazioni di entangling come cx ), ma Qiskit non considera ancora questa possibilità.

Nota

È noto che determinare il numero minimo di scambi da inserire è un problema non polinomiale. Ciò significa che provarci sarebbe troppo costoso, quindi molti degli algoritmi integrati in Qiskit sono stocastici e si potrebbero riscontrare notevoli variazioni tra le diverse compilazioni. Se hai bisogno di riproducibilità, assicurati di impostare l'argomento seed_transpiler su generate_preset_pass_manager() o transpile().

Quando si scrivono i plugin dello stage, il punto di ingresso per routing è qiskit.transpiler.routing. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoUtilizzare un metodo di instradamento predefinito scelto da Qiskit.
sciabolaValore predefinito. Utilizza l 'algoritmo di routing Sabre modificato di Qiskit per scambiare la mappa.
NessunaDisattivare il routing. Solleva un errore se è richiesto il routing.
di baseInserimento avido di swap per instradare una singola operazione alla volta.
lookaheadRicerca Breadth-first con pruning euristico per trovare scambi che rendono le porte eseguibili.

Plugin default integrato

Utilizzare il metodo predefinito scelto da Qiskit per l'instradamento. A partire dalla versione 2.0 di Qiskit, l'algoritmo scelto è lo stesso del plugin Sabre integrato; tuttavia, in pratica, solitamente il plugin predefinito della fase di layout eseguirà l'algoritmo di routing basato su Sabre, mentre la fase di routing verrà utilizzata solo per eseguire VF2PostLayout.

Plugin none integrato

Un plugin fittizio usato per disabilitare completamente il routing. Questo può essere occasionalmente utile per esperimenti di configurazione hardware o in alcuni casi speciali di compilazione parziale.

Plugin basic integrato

Utilizza l'algoritmo di inserimento degli swap avidi di BasisSwap . Concettualmente è molto semplice: per ogni operazione in ordine topologico, si inseriscono gli scambi di percorso più brevi necessari per rendere la connessione eseguibile sul dispositivo.

Il livello di ottimizzazione influisce solo sulla quantità di lavoro che VF2PostLayout la fase svolge per cercare di migliorare la disposizione iniziale dopo il routing.

Questo metodo ha in genere una scarsa qualità di output.

Plugin lookahead integrato

Utilizza LookaheadSwap l'algoritmo per il calcolo del percorso. Si tratta essenzialmente di una ricerca in larghezza volta a generare una rete di scambi, in cui l'albero da esplorare viene ridotto a un numero limitato di scambi candidati a ogni livello di profondità.

Questo algoritmo è simile all'euristica basic del plugin "sabre", ma considera anche i seguenti effetti di ogni scambio a una piccola profondità.

Il livello di ottimizzazione influisce sulla profondità di ricerca, sull'entità del potaggio per ogni livello di profondità e sulla quantità di lavoro svolto per VF2PostLayout ottimizzare a posteriori la disposizione iniziale.

In pratica, il plugin "sabre" funziona diversi ordini di grandezza più velocemente e produce risultati migliori.

Plugin sabre integrato

Utilizza SabreSwap l'algoritmo per il calcolo del percorso. Questo utilizza la versione migliorata di Qiskit dell'algoritmo di routing Sabre originale.

Questo algoritmo di instradamento viene eseguito con un parallelismo a thread per considerare diverse possibilità di instradamento, scegliendo quella che riduce al minimo il numero di scambi inseriti.

Il livello di ottimizzazione influisce sul numero di semi stocastici diversi che vengono provati per l'intero percorso e sulla quantità di lavoro svolto per VF2PostLayout ottimizzare a posteriori il layout iniziale.

Questo è quasi sempre il plugin integrato più performante e quello che Qiskit utilizza di default in tutti i casi in cui è necessario il routing.

Fase di traduzione

Vedi anche

Spiegazione della fase di traduzione

Spiegazione di livello superiore rivolta all'utente della fase di traduzione nella guida IBM Quantum.

La fase di traduzione è responsabile della riscrittura di tutti i gate del circuito in quelli supportati dall'ISA di destinazione. Ad esempio, se viene richiesta un'operazione cx sui qubit hardware 0 e 1, ma l'ISA contiene solo un'operazione cz su tali qubit, lo stadio di traduzione deve trovare un modo per rappresentare la porta cx utilizzando la porta cz e le porte a un qubit disponibili.

La fase di traduzione viene chiamata prima di entrare nella fase di ottimizzazione. I plugin di ottimizzazione (compresi quelli integrati in Qiskit) possono anche utilizzare lo stadio di traduzione come stadio di "correzione" dopo il ciclo di ottimizzazione, se quest'ultimo restituisce un circuito che include porte nonISA. Quest'ultima situazione è abbastanza comune; il ciclo di ottimizzazione può essere interessato solo a minimizzare proprietà come il "numero di porte a due qubit" e lascerà il suo output in termini di porte localmente equivalenti, che lo stadio di traduzione può facilmente riscrivere senza influenzare le proprietà di ottimizzazione dell'obiettivo. Ciò consente di separare più facilmente le preoccupazioni tra le due fasi. Alcuni plugin di ottimizzazione possono essere più severi nei loro risultati e quindi questo follow-up della fase di traduzione potrebbe non essere più necessario.

Quando si scrivono i plugin dello stage, il punto di ingresso per translation è qiskit.transpiler.translation. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoUtilizzare un metodo di traduzione predefinito scelto da Qiskit.
convertitoreTraduzione simbolica delle porte alla base di destinazione utilizzando le equivalenze note.
sintesiRaccogliere ogni serie di porte a uno e due qubit in una rappresentazione matriciale e risintetizzare da lì.

Plugin default integrato

Utilizzare un metodo predefinito scelto da Qiskit per la traduzione. A partire da Qiskit 2.0, è lo stesso del plugin Built-in translator, ma l'algoritmo scelto potrebbe cambiare durante la serie 2.x, sia per tutti i target, sia solo per alcune classi di target.

Plugin synthesis integrato

Raggruppare le sequenze di porte sugli stessi qubit in forma matriciale, quindi risintetizzare utilizzando il UnitarySynthesis pass (con la configurazione unitary_synthesis_method). Questo è, in gran parte, simile al ciclo di ottimizzazione stesso a livelli elevati di ottimizzazione.

La raccolta in matrici è tipicamente più costosa delle traduzioni senza matrice, ma in linea di principio la qualità delle traduzioni può essere migliore. In pratica, ciò richiede un algoritmo di sintesi adattato all'ISA di destinazione, il che rende questo metodo meno generale di altri. Può produrre risultati di qualità superiore quando si punta su ISA semplici che corrispondono alle routine di sintesi già presenti in Qiskit.

Se si utilizza questo metodo, potrebbe non essere necessario il ciclo di ottimizzazione.

Il livello di ottimizzazione non ha alcun effetto su questo plugin.

Plugin translator integrato

Utilizza BasisTranslator l'algoritmo per tradurre simbolicamente le porte nella base di destinazione. A livello generale, il processo parte dall’insieme di porte richieste dal circuito e utilizza le regole di un dato EquivalenceLibrary modello (in genere il SessionEquivalenceLibrary) per arrivare all’ISA.

Questo è il metodo di traduzione predefinito.

Il livello di ottimizzazione non ha alcun effetto su questo plugin.

Fase di ottimizzazione

Vedi anche

Spiegazione della fase di ottimizzazione

Spiegazione di livello superiore per l'utente della fase di ottimizzazione nella guida IBM Quantum.

La fase di ottimizzazione è destinata alle ottimizzazioni di basso livello che tengono conto dell'hardware. A differenza dello stadio di init, l'ingresso a questo stadio è un circuito già compatibile con l'ISA, quindi un plugin di ottimizzazione a basso livello può essere adattato a una particolare ISA.

I requisiti per un plugin di ottimizzazione sono davvero pochi: basta che accetti circuiti supportati dall'ISA e restituisca circuiti supportati dall'ISA. Un plugin di ottimizzazione contiene spesso un ciclo, come ad esempio il DoWhileController, e potrebbe includere la fase di traduzione configurata come pipeline di correzione.

I plugin di ottimizzazione integrati in Qiskit sono generali e si applicano bene alla maggior parte delle ISA del mondo reale per dispositivi non corretti da errori. I plugin integrati sono meno adatti alle ISA che non hanno un gate a singolo qubit parametrizzato in modo continuo.

Quando si scrivono i plugin dello stage, il punto di ingresso per optimization è qiskit.transpiler.optimization. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoUn set predefinito di passaggi di ottimizzazione. Questo varia in modo significativo tra i livelli di ottimizzazione.

Plugin default integrato

Questo varia notevolmente a seconda del livello di ottimizzazione.

Le specifiche di questa pipeline sono soggette a modifiche a seconda delle diverse versioni di Qiskit. Di seguito sono descritti i principi generali.

Al livello di ottimizzazione 0, lo stadio è vuoto.

Al livello di ottimizzazione 1, lo stadio esegue la risintesi matriciale di serie di porte a singolo bit e la cancellazione simbolica inversa molto semplice di porte a due bit, se appaiono consecutivamente. Questa operazione viene eseguita in loop finché le dimensioni e la profondità del circuito non vengono fissate.

Al livello di ottimizzazione 2, oltre alle ottimizzazioni del livello 1, il ciclo contiene l'analisi della commutazione di insiemi di porte per ampliare la gamma di porte che possono essere considerate per la cancellazione. Prima del loop, le corse delle porte a uno e due qubit vengono sottoposte a una singola risintesi basata su una matrice.

Al livello di ottimizzazione 3, la risintesi basata su matrici a due qubit viene eseguita all'interno del ciclo di ottimizzazione. La condizione del loop di ottimizzazione tenta anche più esecuzioni e sceglie il punto minimo in caso di fluttuazione dell'output; questo è necessario perché la risintesi basata su matrice è relativamente instabile in termini di porte concrete.

Il livello di ottimizzazione 3 è in genere molto costoso per i circuiti di grandi dimensioni.

Fase di programmazione

Vedi anche

Programmazione dei circuiti

Una spiegazione a livello di guida dei concetti di schedulazione.

Lo stadio di schedulazione, se richiesto, è responsabile dell'inserimento di istruzioni Delay istruzioni per rendere espliciti i periodi di inattività dei qubit. I plugin possono scegliere opzionalmente di eseguire trasformazioni sensibili al tempo di parete, come l'inserimento di sequenze di disaccoppiamento dinamico.

L'ingresso allo stadio di schedulazione è un circuito compatibile ISA. Anche l'uscita dello stadio di schedulazione deve essere un circuito compatibile con l'ISA, con istruzioni esplicite che soddisfino le informazioni di temporizzazione dell'hardware, se del caso Delay istruzioni esplicite che soddisfano le informazioni di temporizzazione dell'hardware, se opportuno.

PropertySetNella fase di pianificazione si dovrebbe impostare la node_start_time proprietà nella pipeline.

Quando si scrivono i plugin dello stage, il punto di ingresso per scheduling è qiskit.transpiler.scheduling. I plugin integrati sono:

Metodo
Riepilogo
valore predefinitoTentativo di soddisfare i vincoli di allineamento dei tempi senza programmare diversamente.
alapProgrammare il circuito, preferendo che le operazioni avvengano il più tardi possibile.
al più prestoProgrammare il circuito, preferendo che le operazioni avvengano il prima possibile.

Plugin default integrato

Non fare nulla, a meno che il circuito non contenga già istruzioni con tempistiche esplicite. Se nel circuito sono presenti operazioni esplicitamente temporizzate, inserire un padding aggiuntivo per garantire che queste tempistiche soddisfino l'allineamento e altri vincoli hardware.

Plugin alap integrato

Pianificare esplicitamente tutte le operazioni adottando una strategia del tipo “il più tardi possibile”. Questo utilizza l’algoritmo ALAPScheduleAnalysis per decidere dove posizionare i gate.

Plugin asap integrato

Pianificare esplicitamente tutte le operazioni utilizzando una strategia del tipo “il prima possibile”. Questo utilizza l’algoritmo ASAPScheduleAnalysis per decidere dove posizionare i gate.


Gestori di pass personalizzati

Oltre a modificare i gestori di passaggi predefiniti, è anche possibile creare un gestore di passaggi per realizzare una pipeline completamente personalizzata per la trasformazione dei circuiti di ingresso. Per farlo, puoi utilizzare direttamente la StagedPassManager classe. È possibile definire nomi di fase arbitrari e associarvi un'istanza PassManager . Ad esempio, il codice seguente crea un nuovo StagedPassManager che presenta due fasi, init e translation.

from qiskit.transpiler.passes import (
    UnitarySynthesis,
    Collect2qBlocks,
    ConsolidateBlocks,
    UnitarySynthesis,
    Unroll3qOrMore,
)
from qiskit.transpiler import PassManager, StagedPassManager

basis_gates = ["rx", "ry", "rxx"]
init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()])
translate = PassManager(
    [
        Collect2qBlocks(),
        ConsolidateBlocks(basis_gates=basis_gates),
        UnitarySynthesis(basis_gates),
    ]
)

staged_pm = StagedPassManager(
    stages=["init", "translation"], init=init, translation=translate
)

Non c'è alcun limite al numero di fasi che è possibile inserire in un StagedPassManager. Non è necessario che le fasi corrispondano a quelle utilizzate dalle pipeline predefinite di Qiskit.

Le funzioni del generatore Stage possono rivelarsi utili per la creazione di istanze personalizzate StagedPassManager . Generano gestori di pass che forniscono funzionalità comuni utilizzate in molte fasi. Ad esempio, generate_embed_passmanager() genera un PassManager per “incorporare” un iniziale Layout selezionato da un passaggio di layout nel dispositivo di destinazione specificato.


Scrittura di passaggi transpiler personalizzati

Qiskit è progettato per essere esteso con passaggi di transpiler personalizzati e specializzati.

Esistono due tipi di passaggi del transpiler: i passaggi di “analisi” (AnalysisPass), che leggono un circuito e scrivono le proprietà di analisi globali nel PropertySet; e i passaggi di “trasformazione” (TransformationPass), che modificano un DAGCircuit in loco oppure restituiscono un nuovo DAGCircuit. Storicamente, Qiskit ha cercato di separare nettamente questi due tipi. TransformationPassNel Qiskit moderno, tuttavia, è piuttosto comune includere sia l'analisi che le modifiche in un unico file autonomo, piuttosto che cercare di separare ogni cosa. Se il tuo approccio si basa esclusivamente sull'analisi, è comunque opportuno utilizzare AnalysisPass.

Principi generali relativi alla paternità dei passaggi

Se vuoi modificare o creare un nuovo DAGCircuit, devi scrivere un TransformationPass. Se vuoi solo scrivere nel PropertySet e non modificare il DAGCircuit, dovresti scrivere un AnalysisPass. Se vuoi fare entrambe le cose, scrivi un TransformationPass. In entrambi i casi, l'unico metodo obbligatorio è BasePass.run(), che costituisce il cuore del tuo passaggio. Questo dovrebbe accettare un unico argomento (diverso da self), dag: DAGCircuit. AnalysisPassTranspilerPassSe a, dovrebbe restituire un DAGCircuit (che può essere l'input, se modificato in loco), mentre se a, dovrebbe restituire None.

Se il passaggio ha un inizializzatore, è necessario chiamare super().__init__().

In genere, il pass dovrebbe accettare un Target nel proprio inizializzatore, che descrive l'hardware quantistico per cui si sta compilando. Si sconsiglia di accettare vincoli “vaghi” come una mappa di accoppiamento separata e un elenco di porte di base; il Target descrive in modo più corretto l’hardware eterogeneo in generale.

Durante l'esecuzione di una PassManager pipeline, quando viene chiamato il run() metodo del proprio passaggio, è possibile accedere all'attributo self.property_set per ottenere lo stato corrente PropertySet della transpilazione. È necessario leggere e scrivere direttamente su questo elemento. Il pass dovrebbe indicare chiaramente quali attributi, se presenti, dell'insieme di proprietà vengono letti e scritti.

Casualità e determinismo

La compilazione quantistica spesso comporta la risoluzione di problemi che sono intrattabili per l'ottimizzazione globale. In questi casi, gli algoritmi stocastici ed euristici sono spesso più appropriati. Ciò comporta tuttavia problemi di riproducibilità.

Non esiste un requisito formale per cui un passaggio personalizzato debba essere deterministico secondo lo stesso preciso insieme di regole che i passaggi integrati di Qiskit devono seguire. Tuttavia, vi invitiamo caldamente a seguire queste regole nei vostri passaggi; la scienza si basa sulla riproducibilità e il debugging è un incubo quando non si riesce a riprodurre il comportamento osservato in precedenza.

Quando si scrive un passaggio di transpiler, si può fare affidamento sul fatto che i seguenti esempi (rappresentativi e non esaustivi) siano deterministici, anche se la semantica esatta dell'ordinamento potrebbe non essere completamente specificata e i passaggi non dovrebbero fare affidamento su un ordine particolare:

  • I nodi di ordine si trovano in DAGCircuit.op_nodes(). Al contrario, topological_op_nodes() per impostazione predefinita include una chiave di ordinamento che rende il suo ordine del tutto indipendente dall'ordine di rimozione/inserimento dei nodi, risultando quindi completamente deterministico purché venga specificato lo stesso insieme di nodi con lo stesso flusso di dati, anche se è stato costruito in un ordine diverso.
  • I bordi dell'ordine si incontrano in DAGCircuit.edges().
  • L'ordine in cui vengono restituite le esecuzioni e l'ordine esatto in DAGCircuit.collect_2q_runs() cui vengono incontrati i nodi all'interno di un'esecuzione.
  • L'ordine in cui i nodi vengono incontrati nei metodi con ordine degenerato, quali predecessors(), bfs_successors(), e così via.

In generale, il requisito è che vengano apportate tutte le stesse modifiche al circuito, esattamente nello stesso ordine. Ad esempio, se i nodi devono essere aggiunti, ridotti o rimossi, l'ordine di queste modifiche deve essere deterministico e le sostituzioni devono essere specificate in modo deterministico.

Alcuni suggerimenti per garantire ciò includono:

  • Fare molta attenzione quando si itera su contenitori basati su hash. L'iterazione su Python 's set è non deterministica a causa della randomizzazione dei semi di hash. In Rust, l'iterazione sui contenitori basati su hash della libreria standard, compresi gli equivalenti di hashbrown con i loro hashers predefiniti, non è deterministica.

    Nota

    L'iterazione su Python 's dict è deterministica e garantisce un ordine di inserimento se non ci sono state rimozioni, e un ordine arbitrario ma comunque deterministico se ci sono state rimozioni deterministiche.

    In Python, se è necessario creare un oggetto set e poi iterare su di esso, si consideri invece l'utilizzo di un oggetto dict con tutti i valori di None come sostituto. L'utilizzo di un set solo per il test dei membri non è un problema.

    In Rust, utilizzare indexmap e le sue strutture IndexMap e IndexSet come sostituti di HashMap e HashSet, rispettivamente; hanno proprietà di iterazione deterministica simili a quelle di Python 's dict.

  • Se la tua funzione contiene componenti stocastiche, assicurati di accettare un seed valore in ingresso e di rendere il risultato puro se tale valore è fornito come numero intero. In genere ciò significa salvare il seme e creare un nuovo oggetto pRNG a partire da tale seme all'inizio di ogni chiamata a BasePass.run().

  • Se si usa il parallelismo tra thread, bisogna fare attenzione che l'output non dipenda dall'ordine in cui i thread svolgono il loro lavoro o restituiscono i loro risultati parziali. Ad esempio, se si distribuisce il lavoro su un pool di thread e si raccolgono i risultati alla fine, bisogna assicurarsi che l'output sia disposto in un ordine corrispondente all'input. In Python, funzioni come concurrent.futures.ThreadPoolExecutor.map() lo garantiscono. Allo stesso modo, in Rust, gli iteratori paralleli di rayonraccoglieranno il loro output nello stesso ordine dell'input.

    Attenzione: le riduzioni parallele, come "applicare una funzione a ogni elemento di questo iteratore e scegliere quello che minimizza una certa metrica", sono tipicamente molto suscettibili al non-determinismo dei thread, nel caso di degenerazione della metrica. Ad esempio, se due elementi nell'iteratore producono un output non uguale che tuttavia ha la stessa chiave di confronto, quello scelto in un ambiente filettato non è deterministico. Per evitare questo inconveniente, applicare un tie-breaker deterministico per eliminare la degenerazione, ad esempio enumerando gli input e utilizzando il numero di sequenza come chiave di spareggio, in modo che se due elementi hanno lo stesso punteggio, venga scelto in modo affidabile quello corrispondente a un input precedente.


Rappresentazione dei computer quantistici

Per poter compilare un codice QuantumCircuit per un backend specifico, il transpiler necessita di una rappresentazione specializzata di tale backend, che includa i suoi vincoli, il set di istruzioni, le proprietà dei qubit e altro ancora, al fine di poter compilare e ottimizzare in modo efficace. Sebbene la BackendV2 classe definisca un'interfaccia per l'interrogazione e l'interazione con i backend, il suo ambito di applicazione va oltre le semplici esigenze del transpiler, includendo la gestione dell'invio dei lavori e, potenzialmente, l'interfacciamento con servizi remoti. Le informazioni specifiche richieste dal transpiler sono descritte dalla Target classe

Ad esempio, per creare un oggetto semplice Target , è possibile aggiungere in modo iterativo le descrizioni delle istruzioni che esso supporta:

from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate

target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
    UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
    {
        (0,): InstructionProperties(error=.00001, duration=5e-8),
        (1,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RZGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RYGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RXGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    CZGate(),
    {
        (1, 2): InstructionProperties(error=.0001, duration=5e-7),
        (2, 0): InstructionProperties(error=.0001, duration=5e-7)
    }
)
target.add_instruction(
    Measure(),
    {
        (0,): InstructionProperties(error=.001, duration=5e-5),
        (1,): InstructionProperties(error=.002, duration=6e-5),
        (2,): InstructionProperties(error=.2, duration=5e-7)
    }
)
print(target)
Target
Number of qubits: 3
Instructions:
    cx
        (0, 1):
            Duration: 5e-07 sec.
            Error Rate: 0.0001
    u
        (0,):
            Duration: 5e-08 sec.
            Error Rate: 1e-05
        (1,):
            Duration: 6e-08 sec.
            Error Rate: 2e-05
    rz
        (1,):
            Duration: 5e-08 sec.
            Error Rate: 1e-05
        (2,):
            Duration: 6e-08 sec.
            Error Rate: 2e-05
    ry
        (1,):
            Duration: 5e-08 sec.
            Error Rate: 1e-05
        (2,):
            Duration: 6e-08 sec.
            Error Rate: 2e-05
    rx
        (1,):
            Duration: 5e-08 sec.
            Error Rate: 1e-05
        (2,):
            Duration: 6e-08 sec.
            Error Rate: 2e-05
    cz
        (1, 2):
            Duration: 5e-07 sec.
            Error Rate: 0.0001
        (2, 0):
            Duration: 5e-07 sec.
            Error Rate: 0.0001
    measure
        (0,):
            Duration: 5e-05 sec.
            Error Rate: 0.001
        (1,):
            Duration: 6e-05 sec.
            Error Rate: 0.002
        (2,):
            Duration: 5e-07 sec.
            Error Rate: 0.2

Si Target tratta di un backend a 3 qubit che supporta CXGate tra i qubit 0 e 1, UGate sui qubit 0 e 1, RZGate, RXGate, e RYGate sui qubit 1 e 2, CZGate tra i qubit 1 e 2 e tra i qubit 2 e 0, e Measure su tutti i qubit.

Esistono inoltre strutture dati specifiche per rappresentare un sottoinsieme specifico di informazioni provenienti dal Target. Ad esempio, la CouplingMap classe viene utilizzata esclusivamente per rappresentare i vincoli di connettività di un backend sotto forma di grafo orientato. È possibile generare una mappa di accoppiamento a partire da un Target utilizzando il Target.build_coupling_map() metodo. Queste strutture dati sono in genere precedenti alla Target classe, ma vengono ancora utilizzate da alcuni passaggi del transpiler che non funzionano ancora in modo nativo con un'istanza Target di o quando si ha a che fare con backend che non utilizzano l'ultima versione BackendV2 dell'interfaccia.

Ad esempio, se volessimo visualizzare il CouplingMap per l'esempio dei 3 qubit Target sopra riportato:

from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate

target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
    UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
    {
        (0,): InstructionProperties(error=.00001, duration=5e-8),
        (1,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RZGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RYGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RXGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    CZGate(),
    {
        (1, 2): InstructionProperties(error=.0001, duration=5e-7),
        (2, 0): InstructionProperties(error=.0001, duration=5e-7)
    }
)
target.add_instruction(
    Measure(),
    {
        (0,): InstructionProperties(error=.001, duration=5e-5),
        (1,): InstructionProperties(error=.002, duration=6e-5),
        (2,): InstructionProperties(error=.2, duration=5e-7)
    }
)

target.build_coupling_map().draw()

Ciò illustra la connettività globale di, Target che è data dalla combinazione dei qubit supportati per CXGate e CZGate. CouplingMap.build_coupling_map()Per visualizzare la connettività di ogni singolo elemento, è possibile passare il nome dell'operazione a:

from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate

target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
    UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
    {
        (0,): InstructionProperties(error=.00001, duration=5e-8),
        (1,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RZGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RYGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RXGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    CZGate(),
    {
        (1, 2): InstructionProperties(error=.0001, duration=5e-7),
        (2, 0): InstructionProperties(error=.0001, duration=5e-7)
    }
)
target.add_instruction(
    Measure(),
    {
        (0,): InstructionProperties(error=.001, duration=5e-5),
        (1,): InstructionProperties(error=.002, duration=6e-5),
        (2,): InstructionProperties(error=.2, duration=5e-7)
    }
)

target.build_coupling_map('cx').draw()
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate

target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
    UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
    {
        (0,): InstructionProperties(error=.00001, duration=5e-8),
        (1,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RZGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RYGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    RXGate(Parameter('theta')),
    {
        (1,): InstructionProperties(error=.00001, duration=5e-8),
        (2,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
target.add_instruction(
    CZGate(),
    {
        (1, 2): InstructionProperties(error=.0001, duration=5e-7),
        (2, 0): InstructionProperties(error=.0001, duration=5e-7)
    }
)
target.add_instruction(
    Measure(),
    {
        (0,): InstructionProperties(error=.001, duration=5e-5),
        (1,): InstructionProperties(error=.002, duration=6e-5),
        (2,): InstructionProperties(error=.2, duration=5e-7)
    }
)

target.build_coupling_map('cz').draw()

Pianificazione dei circuiti

Vedi anche

Fase di programmazione

Come configurare le fasi di programmazione dei gestori di passaggi preimpostati.

Dopo che il circuito è stato tradotto nella base di destinazione, mappato sul dispositivo e ottimizzato, è possibile applicare una fase di schedulazione per tenere conto, facoltativamente, di tutti i tempi morti del circuito. Ad alto livello, si può pensare che lo scheduling inserisca dei ritardi nel circuito per tenere conto del tempo di inattività dei qubit tra l'esecuzione delle istruzioni. Ad esempio, se si parte da un circuito come:

Schema che illustra il circuito precedentemente descritto.

possiamo quindi chiamare transpile() con scheduling_method :

from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2

backend = GenericBackendV2(5)

ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0,range(1,5))

circ = transpile(ghz, backend, scheduling_method="asap")
circ.draw(output='mpl')
Schema del circuito prodotto dal codice precedente.

Si può notare che il transpiler ha inserito Delay per tenere conto del tempo di inattività di ciascun qubit. Per avere un'idea più precisa della tempistica del circuito, possiamo anche osservarlo con la funzione timeline.draw() :

Uscita dal cassetto dei tempi del circuito.

La pianificazione di un circuito si articola in due fasi: l’analisi e la mappatura dei vincoli, seguite da una fase di riempimento. La prima parte richiede l'esecuzione di un ciclo di analisi di schedulazione, come ALAPSchedulingAnalysis o ASAPSchedulingAnalysis , che analizza il circuito e registra l'ora di inizio di ciascuna istruzione nel circuito utilizzando un algoritmo di schedulazione (“il più tardi possibile” per ALAPSchedulingAnalysis e “il più presto possibile” per ASAPSchedulingAnalysis) nel set di proprietà. Una volta che il circuito dispone di una pianificazione iniziale, è possibile eseguire ulteriori passaggi per tenere conto di eventuali vincoli temporali presenti nel backend di destinazione, come ad esempio i vincoli di allineamento. Ciò avviene in genere tramite il comando ConstrainedReschedule pass, che adegua la pianificazione impostata nel set di proprietà ai vincoli del backend di destinazione. Una volta completate tutte le operazioni di pianificazione e le modifiche/riprogrammazioni, viene eseguito un ciclo di riempimento, come PadDelay o PadDynamicalDecoupling , per inserire le istruzioni nel circuito, completando così la pianificazione.


API Transpiler

Descrizione dell'hardware

Rubrica 1
Rubrica 2
Target( [descrizione, num_qubit, dt,...] )Lo scopo dell'oggetto Target è quello di informare il compilatore di Qiskit sui vincoli di un particolare backend, in modo che il compilatore possa compilare un circuito di input in qualcosa che funzioni e sia ottimizzato per un dispositivo.
InstructionProperties( [durata, errore] )Una rappresentazione delle proprietà di un'implementazione di gate.
WrapAngleRegistry()Registro della funzione Angle Wrapping

Definizione di Pass Manager

Rubrica 1
Rubrica 2
StagedPassManager( [fasi] )Una pipeline di gestione dei passaggi costruita a partire da singole fasi.
PassManager( [passaggi, max_iteration] )Gestore di un insieme di passaggi e della loro programmazione durante la trasposizione.
PassManagerConfig( [initial_layout,...] )Configurazione di Pass Manager.
PassManagerCliffordTConfig( [initial_layout,...] )Configurazione di Pass Manager per la transpilazione Clifford+T.
generate_preset_pass_manager([...])Genera un'impostazione predefinita PassManager
generate_preset_clifford_t_pass_manager([...])StagedPassManagerGenera un preset Clifford+T.
generate_preset_pbc_pass_manager([...])StagedPassManagerGenerare un PBC predefinito.

Layout e topologia

Rubrica 1
Rubrica 2
Layout( [input_dict] )Dettatura a due vie per rappresentare un Layout.
CouplingMap( [elenco degli accoppiamenti, descrizione] )Grafo diretto che specifica l'accoppiamento fisso.
TranspileLayout(initial_layout,...[,...] )Attributi di layout per il circuito di uscita dal transpiler.

Pianificazione

Rubrica 1
Rubrica 2
InstructionDurations( [durata_lezioni, dt] )Classe di aiuto per fornire le durate delle istruzioni per la programmazione.

Pass astratti

Rubrica 1
Rubrica 2
TransformationPass(*args, **kwargs)Un passaggio di trasformazione: cambiare il DAG, non l'insieme di proprietà.
AnalysisPass(*args, **kwargs)Un passaggio di analisi: cambiare il set di proprietà, non il DAG.

Eccezioni

TranspilerError

exception qiskit.transpiler.TranspilerError(*message)

GitHub

Basi: TranspilerAccessError

Eccezioni sollevate durante la trasposizione.

Impostare il messaggio di errore.

TranspilerAccessError

exception qiskit.transpiler.TranspilerAccessError(*message)

GitHub

Basi: PassManagerError

DEPRECATO: Eccezione di errore di accesso nei passaggi del transpiler.

Impostare il messaggio di errore.

CouplingError

exception qiskit.transpiler.CouplingError(*msg)

GitHub

Basi: QiskitError

Classe base per gli errori sollevati dall'oggetto grafico di accoppiamento.

Impostare il messaggio di errore.

LayoutError

exception qiskit.transpiler.LayoutError(*msg)

GitHub

Basi: QiskitError

Errori generati dall'oggetto layout.

Impostare il messaggio di errore.

CircuitTooWideForTarget

exception qiskit.transpiler.CircuitTooWideForTarget(*message)

GitHub

Basi: TranspilerError

Errore sollevato se il circuito è troppo largo per l'obiettivo.

Impostare il messaggio di errore.

InvalidLayoutError

exception qiskit.transpiler.InvalidLayoutError(*message)

GitHub

Basi: TranspilerError

Errore generato quando un layout fornito dall'utente non è valido.

Impostare il messaggio di errore.

Metrica di ottimizzazione

Rubrica 1
Rubrica 2
OptimizationMetric(*valori)Metrica di ottimizzazione considerata durante la transpilazione.
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.