Skip to main content
IBM Quantum Platform

Modello di circuito quantistico

qiskit.circuit

L'elemento fondamentale dell'informatica quantistica è il circuito quantistico. Si tratta di una routine di calcolo che può essere eseguita, un colpo alla volta, su un'unità di elaborazione quantistica (QPU). Un circuito agisce su una quantità predefinita di dati quantistici (in Qiskit supportiamo direttamente solo i qubit) con operazioni unitarie (gate), misure e reset. Inoltre, un circuito quantistico può contenere operazioni su dati classici, compresi calcoli in tempo reale e costrutti di flusso di controllo, che vengono eseguiti dai controllori della QPU.

I circuiti hanno un basso livello di astrazione quando si costruiscono programmi quantistici. Sono il costrutto utilizzato per costruire livelli di astrazione più elevati, come le primitive della computazione quantistica, che accumulano i dati di molti cicli di esecuzione di circuiti quantistici, insieme a tecniche avanzate di attenuazione degli errori e ottimizzazioni delle misure, in dati classici e statistiche di errore ben tipizzati.

In Qiskit, i circuiti possono essere definiti in uno dei due regimi :

  • un circuito astratto, definito in termini di qubit virtuali e operazioni arbitrarie di alto livello, come algoritmi incapsulati e porte definite dall'utente.
  • un circuito fisico, definito in termini di qubit hardware di un particolare backend e contenente solo le operazioni supportate nativamente da questo backend. Questo concetto può essere definito anche come circuito ISA.

La conversione da un circuito astratto a un circuito fisico si effettua utilizzando il pacchetto di transpilation di Qiskit, il cui punto di accesso di primo livello è transpile(). Se si definisce un circuito in cui si intende che gli indici dei qubit si riferiscano a qubit fisici, si può usare il metodo QuantumCircuit.ensure_physical() per riscrivere i metadati del circuito in modo che Qiskit lo riconosca come circuito fisico, anche se, a differenza della transpilazione, questo non impone il rispetto dei vincoli delle porte di base e dell'accoppiamento hardware.

In Qiskit, un circuito quantistico è rappresentato dalla classe QuantumCircuit classe. Di seguito è riportato un esempio di circuito quantistico che realizza uno stato Greenberger-Horne-Zeilinger (GHZ) a tre qubit, definito come:

∣ψ⟩=(∣000⟩+∣111⟩)/2|\psi\rangle = \left( |000\rangle + |111\rangle \right) / \sqrt{2}
from qiskit import QuantumCircuit

# Create a circuit with a register of three qubits
circ = QuantumCircuit(3)
# H gate on qubit 0, putting this qubit in a superposition of |0> + |1>.
circ.h(0)
# A CX (CNOT) gate on control qubit 0 and target qubit 1 generating a Bell state.
circ.cx(0, 1)
# CX (CNOT) gate on control qubit 0 and target qubit 2 resulting in a GHZ state.
circ.cx(0, 2)
# Draw the circuit
circ.draw('mpl')
Schema del circuito prodotto dal codice precedente.

Concetti e definizioni relativi ai circuiti

Esiste una terminologia molto specialistica sui circuiti quantistici. Gran parte di queste informazioni sono comuni nella letteratura sul calcolo quantistico, mentre alcune sono più specifiche dei pacchetti software quantistici e una piccola parte è specifica di Qiskit. Si tratta di un elenco alfabetico di alcuni concetti importanti come riferimento rapido, ma non approfondisce i concetti fondamentali. Considerate l'utilizzo della piattaforma IBM Quantum Learning se volete partire dall'inizio.

circuito astratto

Un circuito definito in termini di operazioni matematiche astratte e di qubit virtuali. Questo è in genere il modo in cui si pensa agli algoritmi quantistici: un circuito astratto può essere costituito da operazioni unitarie completamente arbitrarie, misurazioni e potenzialmente calcoli classici in tempo reale, senza restrizioni su quali qubit possono interagire tra loro.

Per trasformare un circuito astratto in un circuito fisico si utilizza il pacchetto di transpilazione di Qiskit.

ancilla qubit

Un qubit aggiuntivo che viene utilizzato per aiutare a implementare operazioni su altri qubit, ma il cui stato finale non è importante per il programma.

circuito

Una routine di calcolo che definisce una singola esecuzione da eseguire su una QPU. Può trattarsi di un circuito astratto o di un circuito fisico.

clbit

Abbreviazione specifica di Qiskit che indica un singolo bit classico di dati.

attacco di colata

Un' operazione unitaria su uno o più qubit.

qubit hardware

La rappresentazione di un singolo qubit su una particolare QPU. Un qubit hardware ha un sistema quantomeccanico fisico a supporto, come i circuiti superconduttori; a differenza di un qubit virtuale, ha particolari vincoli di accoppiamento e solo determinati gate possono essere applicati a determinati gruppi di qubit hardware.

Qiskit non distingue i qubit logici dai singoli qubit fisici quando parla di qubit hardware. Una QPU può implementare i suoi qubit hardware come qubit logici, dove ogni qubit hardware comprende molti qubit fisici che sono controllati e corretti dagli errori in modo opaco per Qiskit dall'elettronica di controllo. Più probabilmente, per le applicazioni a breve termine, una QPU esporrà direttamente i suoi qubit fisici come qubit hardware su cui Qiskit potrà ragionare.

Sia i qubit fisici che quelli logici avranno vincoli di accoppiamento tra loro, consentiranno solo determinate operazioni quantistiche su di essi e avranno problemi di programmazione tra loro. Qiskit astrae questi aspetti nel concetto di qubit hardware. Agli inizi della correzione degli errori quantistici, alcuni backend possono consentire l'accesso alle loro risorse di qubit sia come qubit logici di alto livello sia come qubit fisici di basso livello attraverso Qiskit.

architettura del set di istruzioni (ISA)

Il modello astratto di quali operazioni sono disponibili su quali set di qubit hardware su una particolare QPU. Ad esempio, una QPU può consentire le operazioni X\sqrt X e RZR_Z su tutti i singoli qubit hardware e le operazioni CXCX su alcune coppie di qubit hardware.

qubit logico

Una collezione di diversi qubit fisici controllati insieme da una QPU (dal punto di vista dell'utente) per applicare la correzione degli errori quantistici in tempo reale. Un qubit logico è un tipo di qubit hardware per Qiskit.

misurazione

L'atto di estrarre un bit classico di un dato da un singolo stato di qubit. Si tratta di un'operazione irreversibile, che di solito distrugge l'entanglement e la coerenza di fase tra il qubit bersaglio e il resto del sistema.

circuito fisico

Un circuito definito in termini di qubit hardware e solo delle operazioni quantistiche disponibili nell'ISA di una particolare QPU. I circuiti fisici sono legati a una particolare architettura QPU e non funzioneranno su altre architetture incompatibili. Si può anche sentire parlare di circuito ISA.

In genere si ottiene un circuito fisico utilizzando le routine di transpilazione di Qiskit su un circuito astratto costruito.

qubit fisico

Un sistema quantistico controllabile a due livelli. Si tratta letteralmente di un qubit "fisico", come un transmon o lo stato elettronico di uno ione intrappolato. Una QPU può esporre questo qubit direttamente come suo qubit hardware, oppure combinare diversi qubit fisici in un qubit logico.

unità di elaborazione quantistica (QPU)

Analogamente a una CPU nell'informatica classica o a una GPU nell'elaborazione grafica, una QPU è l'hardware che esegue operazioni quantistiche su dati quantistici. Ci si può sempre aspettare che una QPU che utilizza il modello circuitale di calcolo sia in grado di eseguire un certo insieme di porte e di operazioni di misura. A seconda della particolare tecnologia, possono anche essere in grado di eseguire alcuni calcoli classici in tempo reale, come il flusso di controllo classico e i calcoli bitwise su dati classici.

Qubit

L'unità di base dell'informazione quantistica.

calcolo classico in tempo reale

Qualsiasi calcolo classico che può avvenire all'interno dell'esecuzione di un singolo colpo di un circuito, dove i risultati del calcolo classico possono influenzare l'esecuzione successiva del circuito. La quantità di calcolo classico in tempo reale disponibile con determinate QPU varia in modo significativo in base a molti fattori, come l'elettronica di controllo e la tecnologia dei qubit in uso. Per ulteriori informazioni, consultare la documentazione del fornitore dell'hardware.

operazione unitaria

Un'operazione reversibile su uno stato quantistico. Tutte le porte quantistiche sono operazioni unitarie (per definizione).

qubit virtuale

Un qubit astratto e matematico utilizzato per costruire un circuito astratto. I qubit virtuali sono il modo in cui si pensa tipicamente agli algoritmi quantistici ad alto livello; assumiamo che tutte le porte quantistiche siano valide su tutti i qubit virtuali e che tutti i qubit virtuali siano sempre connessi a ogni altro qubit virtuale.

Quando si esegue la mappatura su hardware, i qubit virtuali devono essere assegnati ai qubit hardware. Questa mappatura non deve essere necessariamente uno a uno. In genere, un qubit virtuale deve essere scambiato da un qubit hardware a un altro nel corso dell'esecuzione di un circuito per soddisfare i vincoli di accoppiamento della QPU sottostante. Non è strettamente necessario che tutti i qubit virtuali utilizzati in un circuito siano mappati su un qubit fisico in un determinato punto del circuito fisico; potrebbe accadere che un qubit virtuale venga misurato (facendo collassare il suo stato) e poi non venga più utilizzato, per cui un nuovo qubit virtuale potrebbe prendere il suo posto. Valutare queste condizioni per mappare un circuito virtuale in un circuito fisico è il compito del pacchetto di transpilazione di Qiskit.


Panoramica API dell' qiskit.circuit

Tutti gli oggetti qui presenti sono descritti in modo più dettagliato e contestualizzato nelle sezioni successive. Questa sezione fornisce una panoramica degli elementi dell'API qui documentati.

La classe principale è QuantumCircuitche ha una propria pagina di documentazione, che include una sezione approfondita sulla costruzione dei circuiti. I dati quantistici e i più semplici dati classici sono rappresentati da "bit" e "registri":

All’interno di un circuito, ogni operazione completa CircuitInstruction è costituita da un Operation (che può essere un Instruction, un Gate, o qualche altra sottoclasse) e dagli operandi qubit e clbit. Le classi base principali sono:

La sottoclasse concreta più comune dell'interfaccia astratta Operation minima è la Instruction. Sebbene Operation possa includere oggetti matematici astratti, un Instruction è qualcosa che, in teoria, potrebbe essere eseguito direttamente sull’hardware. A sua volta, questa classe è sottoclassificata da Gate e ControlledGate che aggiungono ulteriori caratteristiche di unitarità e semantica controllata:

  • Instruction, che rappresenta un'istruzione basata su hardware
  • Gate, che rappresenta un'istruzione hardware unitaria
  • ControlledGate, che rappresenta un gate con struttura di controllo.

Qiskit include un'ampia libreria di porte e circuiti standard, documentata in qiskit.circuit.library. Molti di questi sono dichiarati come singleton di Python oggetti. I macchinari per questo sono descritti in dettaglio in qiskit.circuit.singleton, le cui classi principali sono ciascuna una forma singleton della gerarchia standard delle porte di istruzione:

Alcune istruzioni sono particolarmente speciali in quanto influenzano il flusso di controllo o di dati del circuito. Quelli di primo livello sono:

  • Barrierper contrassegnare le parti del circuito che devono essere ottimizzate in modo indipendente

  • Delay, per inserire un periodo di attesa in tempo reale

  • Measure, per misurare un Qubit in un Clbit

  • Reset, per resettare irreversibilmente un qubit allo stato ∣0⟩\lvert0\rangle

  • Store, per scrivere un'espressione classica in tempo reale in una posizione di memorizzazione

  • ControlFlowOp, che presenta alcune sottoclassi specifiche:

    • BreakLoopOp, per uscire dal ciclo di contenimento più vicino
    • ContinueLoopOp, per passare immediatamente all'iterazione successiva del ciclo contenente
    • BoxOp, un semplice insieme di istruzioni
    • ForLoopOp, per eseguire un ciclo su un intervallo fisso di valori
    • IfElseOp, per accedere in modo condizionale a uno dei due sottocircuiti
    • SwitchCaseOp, per accedere in modo condizionale a uno dei numerosi sottocircuiti
    • WhileLoopOp, per ripetere un sottocircuito fino a quando una condizione non risulta falsa.

Alcune istruzioni possono essere “annotate” con metadati, che in genere sono destinati a essere utilizzati da un ciclo di compilazione, sia a livello locale che in una successiva elaborazione da parte del backend. Al momento ciò è limitato a BoxOp. Queste annotazioni sono rappresentate da sottoclassi personalizzate di Annotation, e l'infrastruttura di supporto viene approfondita in qiskit.circuit.annotation.

I circuiti possono includere espressioni classiche che vengono valutate in tempo reale, mentre la QPU esegue un singolo colpo del circuito. Questi sono principalmente documentati nella documentazione del modulo di qiskit.circuit.classical. Si potrebbe essere particolarmente interessati alle classi di base (che non sono esposte dalla cartella qiskit.circuit root):

  • Var, una posizione di memorizzazione classica tipizzata in un circuito
  • Expr, un'espressione valutata in tempo reale
  • Type, il tipo classico di un'espressione.

Oltre alla valutazione delle espressioni in tempo reale, che è limitata dalle rappresentazioni hardware classiche dei dati, Qiskit ha il concetto di parametrizzazione "compile-time", che viene eseguita in algebra simbolica astratta. Questi sono tipicamente utilizzati per rappresentare gli angoli delle porte in algoritmi di alto livello che potrebbero voler eseguire le derivate numeriche, ma sono una parte di Qiskit più vecchia della valutazione in tempo reale, quindi sono ancora utilizzati in alcuni punti per eseguire la parametrizzazione generale. Le principali classi correlate sono:

Il modulo qiskit.circuit espone anche alcune classi di calcolo che lavorano con i circuiti per aiutare i flussi di compilazione. Queste release includono:

Esistono anche utility per la generazione di circuiti casuali:

Infine, il modulo del circuito ha la sua classe di eccezione, per indicare quando le cose vanno male in modo specifico per il circuito:


Rappresentazione dei circuiti in Qiskit

La classe principale per la rappresentazione dei circuiti è QuantumCircuit. Può trattarsi di un circuito astratto o di un circuito fisico. Molte altre informazioni sulla classe QuantumCircuit e sulla moltitudine di metodi disponibili nella documentazione della classe.

Internamente, un QuantumCircuit contiene i qubit, i bit classici, i parametri di compilazione, le variabili in tempo reale e altre informazioni di tracciamento relative ai dati su cui agisce e alle modalità di parametrizzazione. Contiene quindi una sequenza di CircuitInstructions, che a loro volta contengono la specifica operazione (gate, misurazione, ecc.) e i relativi operandi (i qubit e i bit classici).

Bit e registri

I Qubit e i bit classici sono rappresentati da un tipo di base condiviso, che è solo un "tag" Bit condiviso, che è inteso solo come un "tag di tipo"; le classi non hanno alcun comportamento, se non quello di essere oggetti immutabili:

Bit

class qiskit.circuit.Bit

GitHub

Basi: object

Implementare un bit generico.

Nota

Questa classe non può essere istanziata direttamente. Il suo unico scopo è quello di consentire il controllo generico dei tipi per Clbit e Qubit.

Qubit

class qiskit.circuit.Qubit(register=None, index=None)

GitHub

Basi: Bit

Un qubit, che può essere confrontato tra diversi circuiti.

Clbit

class qiskit.circuit.Clbit(register=None, index=None)

GitHub

Basi: Bit

Un clbit, che può essere confrontato tra circuiti diversi.

Qubit e clbit sono istanziati dagli utenti senza argomenti, come ad esempio da Qubit(). I bit si confrontano tra loro se sono lo stesso oggetto Python o se sono stati entrambi creati da un registro con lo stesso nome e la stessa dimensione e si riferiscono allo stesso indice all'interno di quel registro. Esiste anche un tag di tipo speciale per i qubit "ancilla", ma è poco utilizzato allo stato attuale di Qiskit:

AncillaQubit

class qiskit.circuit.AncillaQubit

GitHub

Basi: Qubit

Un qubit usato come ancilla.

Una collezione di bit dello stesso tipo può essere incapsulata in un registro del tipo corrispondente. La funzionalità di base si trova in una classe base che non viene istanziata direttamente:

Registrati

class qiskit.circuit.Register

GitHub

Basi: object

Implementare un registro generico.

Nota

Questa classe non può essere istanziata direttamente. Il suo unico scopo è quello di consentire il controllo generico dei tipi per ClassicalRegister e QuantumRegister.

Ciascuno dei sottotipi di bit definiti ha un registro associato, che ha le stesse firme dei costruttori, metodi e proprietà della classe base:

QuantumRegister

class qiskit.circuit.QuantumRegister(size=None, name=None, bits=None)

GitHub

Basi: Register

Implementare un registro.

ClassicalRegister

class qiskit.circuit.ClassicalRegister(size=None, name=None, bits=None)

GitHub

Basi: Register

Implementare un registro.

AncillaRegister

class qiskit.circuit.AncillaRegister(size=None, name=None, bits=None)

GitHub

Basi: QuantumRegister

Implementare un registro ancilla.

Un modo comune per istanziare più bit in una volta sola è quello di creare un registro, ad esempio con QuantumRegister("my_qreg", 5). Questo ha il vantaggio di poter dare un nome alla collezione di bit, che apparirà durante le visualizzazioni dei circuiti (QuantumCircuit.draw()) e nelle esportazioni verso i linguaggi di interscambio (vedere qasm2 e qasm3). È anche possibile passare un nome e un elenco di bit precostituiti, ma in questo modo si crea un "registro di aliasing", che è molto poco supportato dall'hardware.

I circuiti tengono traccia dei registri, ma i registri stessi non apportano quasi nessuna differenza di comportamento ai circuiti. L'unica eccezione è che ClassicalRegisters può essere implicitamente lanciato a numeri interi senza segno per essere utilizzato nei confronti condizionali delle operazioni di flusso di controllo.

I registri e i bit classici erano il modo originale di rappresentare i dati classici in Qiskit e rimangono il più supportato attualmente. A lungo termine, il modello dei dati si sta muovendo verso una rappresentazione più completa e fortemente tipizzata di una serie di dati classici (vedere Calcolo classico in tempo reale ), ma è ancora molto frequente l'uso di bit classici nell'attuale Qiskit.

Contesti didattici

Il tipo scalare della QuantumCircuit.data sequenza è l'oggetto “contesto di istruzione”, CircuitInstruction. Si tratta essenzialmente di una semplice classe di dati che contiene una rappresentazione di ciò che deve essere fatto (il suo operation) e dei dati su cui agisce (il qubits e clbitsil ).

Rubrica 1
Rubrica 2
CircuitInstruction(operazione[, qubit, clbit] )Una singola istruzione in un programma QuantumCircuit, composta da operation e da vari operandi.

Dal punto di vista della programmazione, questa classe è effettivamente implementata in Rust e rappresenta un handle creato per accedere ai dati interni all'ambito di Rust. Le modifiche apportate alle istanze di questa classe non si rifletteranno nel circuito. In generale, non è possibile modificare direttamente i contesti di istruzione già presenti nel circuito; l'interfaccia QuantumCircuit è progettata per memorizzare e costruire circuiti, mentre il transpiler, con i suoi passaggi e la sua rappresentazione intermedia DAGCircuit , è lo strumento da utilizzare per modificare i circuiti.

I QuantumCircuit metodi che aggiungono istruzioni ai circuiti (come append(), e tutti i metodi ausiliari relativi alle porte standard) restituiscono un InstructionSet, che rappresenta un riferimento a più CircuitInstruction contemporaneamente.

Rubrica 1
Rubrica 2
InstructionSet(*[, richiedente_risorsa] )Raccolta di istruzioni e loro contesti.

Questa funzione InstructionSet è ormai poco utilizzata in Qiskit. Fornisce un insieme molto limitato di metodi per eseguire modifiche post-aggiunta alle istruzioni (che verranno propagate al circuito), ma il loro utilizzo è ormai sconsigliato ed è preferibile ricorrere alle alternative indicate in tali metodi.

Operazioni, istruzioni e porte

All'interno di un CircuitInstruction, l'interfaccia minima che ogni operazione deve soddisfare è Operation. Si tratta di una visione di altissimo livello, utilizzabile solo per circuiti astratti. Lo scopo principale di considerare le operazioni come Operation è quello di consentire l’aggiunta diretta di oggetti matematici arbitrari (come quantum_info.Operator) ai circuiti astratti.

Rubrica 1
Rubrica 2
Operation()Interfaccia operativa quantistica.

InstructionLa maggior parte delle operazioni, comprese tutte quelle sui circuiti fisici, sono casi particolari di quella definita in modo più concreto. Si tratta di qualsiasi istruzione che una QPU potrebbe essere in grado di eseguire in modo nativo, come ad esempio Measure. Instruction non deve necessariamente essere unitaria (proprio come Measure non lo è); un’istruzione è specificamente unitaria se è un Gate.

Rubrica 1
Rubrica 2
Instruction(nome, num_qubit, num_clbit, parametri)Istruzione quantistica generica.

Instructions possono essere pressoché arbitrari, a condizione che agiscano solo su Qubits e Clbits e siano parametrizzati dal loro params; non dovrebbero tentare di “chiudere” i registri del circuito esterno, né utilizzare parametri nascosti al loro interno. InstructionGli s possono essere collegati ad altri circuiti per fornire una scomposizione utilizzando il loro Instruction.definition attributo, che offre una scomposizione locale e una tantum. Ciò può avvenire utilizzando qualsiasi insieme di operazioni ti risulti più comodo, purché le definizioni di tutte le porte in esso contenute presentino un ordine topologico; in altre parole, non è possibile utilizzare una porta in una definizione se la sua stessa definizione dipende dalla porta padre. Se il Instruction dovesse essere considerato del tutto opaco agli ottimizzatori, il suo definition potrebbe essere None. Per ulteriori dettagli, consulta la sezione "Creazione di istruzioni personalizzate".

Il params di un'istruzione può tecnicamente essere arbitrario, ma in generale è opportuno cercare di attenersi, ove possibile, a parametrizzazioni in termini di numeri reali. Qiskit stesso infrange questa regola in molti punti, e nei Instruction.params campi si possono trovare tipi di ogni genere, ma questi rappresentano una fastidiosa fonte di bug perché spesso implicano la necessità di casi speciali che tengano conto del tipo. Se l'istruzione è parametrizzata in termini di angoli, sarà possibile utilizzare in modo affidabile la parametrizzazione in fase di compilazione al suo interno, e si integrerà bene con QuantumCircuit.assign_parameters().

Sebbene Instruction non sia necessariamente unitario, la sua sottoclasse Gate implica l'unitarità e aggiunge to_matrix() i metodi e control() a tutti i metodi ereditati da Instruction.

Rubrica 1
Rubrica 2
Gate(nome, num_qubit, parametri[, etichetta] )Cancello unitario.

Gate eredita tutti i metodi relativi a Instruction e tutte le stesse considerazioni riguardo al suo params e definition al campo, tranne ovviamente per il fatto che Gates non può agire su alcuna risorsa classica.

definitionGate Le istanze possono (e dovrebbero) avere una base, ma è anche possibile specificare diverse decomposizioni in basi diverse utilizzando un EquivalenceLibrary.

GatePer quanto riguarda le sottoclassi, anche Qiskit dispone di una classe speciale ControlledGate . Questa classe costituisce la base di molti gate della libreria standard che vengono controllati (come ad esempio CXGate), ed è proprio lì che è più probabile che la si incontri:

Rubrica 1
Rubrica 2
ControlledGate(nome, num_qubit, parametri[,...] )Cancello unitario controllato.

Ciascuno tra Instruction, Gate e ControlledGate ha un tipo singleton corrispondente, costruito utilizzando il meccanismo descritto in qiskit.circuit.singleton. La documentazione relativa ai moduli contiene informazioni complete, insieme alle descrizioni di SingletonInstruction, SingletonGate e SingletonControlledGate. Dal punto di vista dell'utente, poco cambia a seconda che la classe base sia o meno un singleton; l'intenzione rimane sempre quella di chiamare to_mutable() per primo se si ha bisogno di ottenere una copia di proprietà di un'istruzione che possa essere modificata in tutta sicurezza (non si può dare per scontato che un'istruzione arbitraria sia modificabile) e, sebbene l'ispezione diretta type di sia sconsigliata, se dovesse essere necessaria, il modo affidabile per individuare il tipo "base" di un'istruzione potenzialmente singleton è quello di utilizzare base_class.

ControlledGate utilizza gli stessi meccanismi delle porte sottoclasse per definire una sintesi fissa e pigra per se stessa. Ovviamente questo approccio non tiene conto dell'hardware ed è più difficile da integrare nelle routine di sintesi del compilatore, ma funziona meglio come soluzione concreta Instruction che potrebbe potenzialmente essere eseguita in modo nativo sull'hardware. base_opNei casi in cui la sintesi e l’ottimizzazione astratta rivestono maggiore importanza, Qiskit offre una classe componibile denominata AnnotatedOperation, che tiene traccia dei “modificatori di gate” (di cui ControlModifier è uno) da applicare al.

Rubrica 1
Rubrica 2
AnnotatedOperation(operazione di base, modificatori)Operazione annotata.

I modificatori disponibili per AnnotatedOperation sono:

InverseModifier

class qiskit.circuit.InverseModifier

GitHub

Basi: Modifier

Modificatore inverso: specifica che l'operazione è invertita.

ControlModifier

class qiskit.circuit.ControlModifier(num_ctrl_qubits=0, ctrl_state=None)

GitHub

Basi: Modifier

Modificatore di controllo: specifica che l'operazione è controllata da num_ctrl_qubits e ha lo stato di controllo ctrl_state.

Parametri

  • num_ctrl_qubits (int)
  • ctrl_state (int |str | None)

PowerModifier

class qiskit.circuit.PowerModifier(power)

GitHub

Basi: Modifier

Modificatore di potenza: specifica che l'operazione viene elevata alla potenza power.

Parametri

power (float)

Per informazioni su come creare porte e istruzioni personalizzate, compreso il modo di costruire oggetti unici e porte parametriche riutilizzabili tramite sottoclassi, vedere Creazione di istruzioni personalizzate di seguito. La libreria di circuiti Qiskit in qiskit.circuit.library contiene molti circuiti e porte predefiniti da utilizzare.

Istruzioni speciali integrate

Qiskit contiene alcune Instruction classi che, per certi versi, sono “speciali”. In genere, questi elementi richiedono un trattamento speciale nel codice del circuito, nel transpiler o nei modelli hardware. Si tratta, in generale, di istruzioni che probabilmente conosci già.

Le misure in Qiskit sono di una singola Qubit in un singolo Clbit. Questi sono i due a cui viene applicata l'istruzione. Le misure sono nella base computazionale.

Misura

class qiskit.circuit.Measure(label=None)

GitHub

Basi: SingletonInstruction

La misura quantistica nella base computazionale.

Parametri

label - etichetta di stringa opzionale per questa istruzione.

In relazione alle misure, esiste un'operazione Reset che non produce dati classici ma istruisce l'hardware a riportare il qubit allo stato ∣0⟩\lvert0\rangle. Si presume che questo avvenga in modo incoerente e che faccia collassare qualsiasi entanglement.

Reimposta

class qiskit.circuit.Reset(label=None)

GitHub

Basi: SingletonInstruction

Ripristino incoerente di un qubit allo stato ∣0⟩\lvert0\rangle.

Parametri

label - stringa opzionale dell'etichetta di questa istruzione.

È possibile impostare l'hardware in modo che applichi un periodo di inattività in tempo reale a un determinato qubit. Un circuito programmato (vedi qiskit.transpiler) includerà esplicitamente tutti i tempi di inattività dei qubit in termini di questo Delay. BoxOp può anche avere una durata esplicita associata, nel relativo BoxOp.duration campo.

Ritardo

class qiskit.circuit.Delay(duration, unit=None)

GitHub

Basi: Instruction

Non fa nulla e si limita a ritardare/attendere/idillare per una durata specificata.

Parametri

  • durata - la durata della durata. Se si tratta di un'espressione Exprdeve essere un'espressione costante di tipo Duration e il parametro unit deve essere omesso (o DEVE essere "expr" se è specificato).
  • unit - l'unità della durata, se duration è un valore numerico. Deve essere "dt", un'unità di misura dei secondi con prefisso SI, o "expr".

Aumenti

CircuitError - È stata specificata un'espressione duration con un tipo risolto che non è basato sui tempi, oppure unit è stato specificato in modo improprio.

Le durate dei ritardi possono essere specificate sia con tempi concreti e costanti, sia con "espressioni di durata" a risoluzione ritardata, costruite a partire da oggetti expr.Stretch oggetti. Per ulteriori informazioni, vedere Pianificazione a risoluzione ritardata.

L'istruzione Barrier può coprire un numero arbitrario di qubit e clbit ed è un no-op nell'hardware. Durante la transpilazione e l'ottimizzazione, tuttavia, blocca qualsiasi ottimizzazione dall'"attraversamento" della barriera; cioè, in:

from qiskit.circuit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()
qc.x(0)

è vietato all'ottimizzatore annullare le due istruzioni XX.

Barriera

class qiskit.circuit.Barrier(num_qubits, label=None)

GitHub

Basi: Instruction

Una direttiva per la compilazione dei circuiti per separare i pezzi di un circuito in modo che le ottimizzazioni o le riscritture siano limitate ad agire solo tra le barriere.

Questo apparirà anche nelle visualizzazioni come indicatore visivo.

Parametri

  • num_qubits (int) – il numero di qubit per la barriera.
  • label (str | None) – l'etichetta opzionale di questa barriera.

L'istruzione Store è un'istruzione particolarmente speciale, in quanto consente di scrivere il risultato di un' espressione di calcolo classico in tempo reale (an expr.Expr) in una variabile classica locale (a expr.Var). Non ci vuole *né l'uno né l'altro *Qubit né Clbit operandi, ma ha un esplicito lvalue E rvalue.

Ad esempio, per determinare la parità di una stringa di bit cr e memorizzarla in un altro registro creg, l'istruzione Store può essere utilizzata nel modo seguente:

parity = expr.lift(cr[0])
for i in range(1,n):
    parity = expr.bit_xor(cr[i], parity)
qc.store(creg[0], parity)

Negozio

class qiskit.circuit.Store(lvalue, rvalue)

GitHub

Basi: Instruction

Memorizzazione manuale di un valore classico in una posizione di memoria classica.

Si tratta di una primitiva di basso livello della gestione delle espressioni classiche (simile a come Measure è una primitiva per la misurazione quantistica) e non può essere sottoclassificata.

Parametri

  • lvalue (expr.Expr) – la posizione di memoria in cui viene memorizzata.
  • rvalue (expr.Expr) – il risultato dell'espressione che viene memorizzato.
lvalue

Ottenere il valore l Expr in cui viene memorizzato il nodo.

rvalue

Ottenere il valore r Expr che viene scritto nel valore l.

Calcolo classico in tempo reale

Vedi anche

qiskit.circuit.classical

Documentazione a livello di modulo sul funzionamento dei sistemi di variabili, espressioni e tipi, sugli oggetti utilizzati per rappresentarli e sulle operazioni classiche disponibili.

Lavorare con dati classici digitati in tempo reale

I QuantumCircuit metodi per lavorare con queste variabili nel contesto di un singolo circuito.

Qiskit dispone di un supporto rudimentale di basso livello per la rappresentazione di calcoli classici in tempo reale, che avvengono durante l'esecuzione della QPU e influenzano i risultati. Il supporto hardware per questi concetti è ancora relativamente agli inizi, quindi è necessario lavorare a stretto contatto con la documentazione del fornitore di hardware per ottenere il massimo da qualsiasi calcolo classico in tempo reale.

Questi calcoli in tempo reale sono rappresentati dal sistema di espressioni e tipi in qiskit.circuit.classical. Ad alto livello, tutte le espressioni in tempo reale sono rappresentate da un Expr che fa parte di una rappresentazione ad "albero" dell'espressione, a cui è associato un ben definito Type a ogni livello. Per maggiori dettagli sulle rappresentazioni interne di queste classi, si veda la documentazione a livello di modulo.

Il risultato di un'operazione in tempo reale Expr può essere utilizzato direttamente in determinati contesti. Attualmente ciò è limitato alle condizioni di IfElseOp e WhileLoopOp, e all'obiettivo di SwitchCaseOp. Il risultato può anche essere memorizzato in una posizione di archiviazione classica tipizzata, utilizzando l'istruzione Store (o il suo QuantumCircuit.store() costruttore), supportata da un expr.Var nodo.

Un circuito può contenere locazioni di memorizzazione classiche manuali, rappresentate internamente dal Var del nodo Expr albero. Queste hanno un tipo classico collegato (come qualsiasi altra espressione). Questi possono essere dichiarati e inizializzati in ogni esecuzione del circuito (add_var()), oppure essere ingressi del circuito (add_input()).

Parametrizzazione in fase di compilazione

Diverse istanze parametriche Instruction in Qiskit possono essere parametrizzate in modi pensati per essere risolti in fase di compilazione. Queste sono caratterizzate dall'uso delle Parameter classi e ParameterExpression .

Rubrica 1
Rubrica 2
Parameter(nome[, uuid] )Un parametro simbolico in tempo di compilazione.
ParameterExpression(nome_map, espr)Un'espressione di parametro.

La differenza principale rispetto alle expr.Var variabili utilizzate nel calcolo classico in tempo reale consiste nel fatto che ParameterExpression è una rappresentazione simbolica di un'espressione matematica. La semantica dell'espressione è quella della matematica ordinaria sui numeri reali continui (e, in casi limitati, sui numeri complessi). Al contrario, Var è un identificatore di una variabile memorizzata su un computer classico, come un valore in virgola mobile o un intero a larghezza fissa, che sono sempre discreti.

In altre parole, ci si può aspettare ParameterExpression di effettuare semplificazioni simboliche valide in matematica, come ad esempio semplificare l' (x+y−x)/y→1(x + y - x) / y \to 1. Una semplificazione di questo tipo non è valida nell'aritmetica in virgola mobile e expr.Expr non verrà eseguita.

La parte "compile-time" di questi parametri significa che in genere si desidera "assegnare" i valori ai parametri prima di inviare il circuito in esecuzione. Questi parametri possono essere utilizzati ovunque ci si aspetti un angolo matematico (come i parametri di un gate di rotazione), con l'avvertenza che l'hardware di solito richiede che siano assegnati a un valore tipicamente corretto prima dell'esecuzione. È possibile svolgere questo compito utilizzando QuantumCircuit.assign_parameters().

Potresti voler utilizzare molti parametri correlati tra loro. ParameterVectorPer semplificare le cose (ed evitare di dover inventare tanti nomi), puoi utilizzare il costruttore di convenienza. ParameterVectorElementGli elementi del vettore sono tutti esempi validi Parameter di una sottoclasse speciale.

Rubrica 1
Rubrica 2
ParameterVector(nome[, lunghezza, uuid] )Un contenitore che racchiude molti oggetti correlati Parameter .
ParameterVectorElement(vettore, indice[, uuid] )Un elemento di un ParameterVector.

Controllo del flusso nei circuiti

All'interno di QuantumCircuit, il flusso di controllo classico è rappresentato da specifici Instructions, che sono sottoclassi di ControlFlowOp.

Rubrica 1
Rubrica 2
ControlFlowOp(nome, num_qubit, num_clbit,...)Classe astratta per incapsulare tutte le operazioni del flusso di controllo.

Per comodità, è disponibile un’istanza frozenset contenente gli Instruction.name attributi di ciascuna delle operazioni di flusso di controllo.

qiskit.circuit.CONTROL_FLOW_OP_NAMES

Insieme dei nomi delle istruzioni delle operazioni di controllo note di Qiskit.

La funzione get_control_flow_name_mapping() permette di accedere alle classi di operazioni del flusso di controllo associate a ciascun nome.

get_control_flow_name_mapping

qiskit.circuit.get_control_flow_name_mapping()

GitHub

Restituisce un dizionario che mappa i nomi delle operazioni del flusso di controllo con le classi corrispondenti"

Esempi

from qiskit.circuit import get_control_flow_name_mapping

ctrl_flow_name_map = get_control_flow_name_mapping()
if_else_object = ctrl_flow_name_map["if_else"]

print(if_else_object)
<class 'qiskit.circuit.controlflow.if_else.IfElseOp'>

Queste operazioni di controllo del flusso (IfElseOp, WhileLoopOp, SwitchCaseOp, ForLoopOp, e BoxOp) hanno tutte uno stato specifico che definisce le condizioni e le strategie di ramificazione, ma contengono tutti i diversi blocchi di sottocircuiti a cui è possibile accedere tramite la loro blocks proprietà.

Rubrica 1
Rubrica 2
IfElseOp(condizione, corpo_vero[, corpo_falso,...] )Un'operazione di circuito che esegue un programma (true_body) se una condizione fornita (condition) è vera e, facoltativamente, valuta un altro programma (false_body) altrimenti.
WhileLoopOp(condizione, corpo[, etichetta] )Un'operazione di circuito che esegue ripetutamente un sottocircuito (body) finché una condizione (condition) non viene valutata come falsa.
SwitchCaseOp(target, casi, *[, etichetta] )Un'operazione di circuito che esegue un particolare blocco di circuito in base alla corrispondenza di un dato target con un elenco ordinato di values.
ForLoopOp(insieme_di_indici, parametro_di_ciclo, corpo[,...] )Un'operazione di circuito che esegue ripetutamente un sottocircuito (body) parametrizzato da un parametro loop_parameter attraverso l'insieme dei valori interi forniti in indexset.
BoxOp(corpo[, durata, unità, etichetta, annotazioni] )Una "scatola" di operazioni su un circuito che vengono trattate atomicamente in un contesto più ampio.

Il SwitchCaseOp riconosce inoltre un valore speciale:

qiskit.circuit.CASE_DEFAULT

Valore predefinito: <default case>

Un oggetto speciale che rappresenta il caso "predefinito" di un'istruzione switch. Se si usa questo come target di un caso, deve essere l'ultimo caso e corrisponderà a tutto ciò che non è già stato corrisposto. Quando si utilizza l'interfaccia del costruttore di QuantumCircuit.switch()si può accedere anche all'attributo DEFAULT dell'oggetto case-builder vincolato.

Oltre alle operazioni di flusso di controllo della struttura a blocchi, esistono anche due istruzioni speciali che influenzano il flusso di controllo all'interno dei cicli. Questi corrispondono agli usi tipici delle istruzioni break e continue nei linguaggi di programmazione classici.

Rubrica 1
Rubrica 2
BreakLoopOp(num_qubit, num_clbit[, etichetta] )Un'operazione di circuito che, quando viene incontrata, salta alla fine del ciclo più vicino.
ContinueLoopOp(num_qubit, num_clbit[, etichetta] )Un'operazione di circuito che, quando viene incontrata, passa all'iterazione successiva del ciclo più vicino.
Nota

Le rappresentazioni delle classi sono documentate qui, ma si noti che la costruzione manuale di queste classi è un'operazione di basso livello che non ci aspettiamo che gli utenti debbano fare di frequente.

Gli utenti dovrebbero leggere Aggiunta del flusso di controllo ai circuiti per i flussi di lavoro raccomandati per la creazione di circuiti abilitati al flusso di controllo.

Poiché ControlFlowOp anche Instruction le sottoclassi sono sottoclassi, ciò significa che il modo in cui sono memorizzate nelle CircuitInstruction istanze comporta la loro “applicazione” a una sequenza di qubit e clbit nei rispettivi qubits attributi e clbits . Ciò può causare sottili problemi di coerenza dei dati: gli Qubit oggetti e Clbit utilizzati all’interno dei blocchi dei sottocircuiti delle operazioni di flusso di controllo non saranno necessariamente identici agli oggetti corrispondenti nel CircuitInstruction. Qualsiasi codice che utilizzi operazioni di flusso di controllo in Qiskit deve tenerne conto; all’interno di un sottocircuito, è necessario trattare subcircuit.qubits[i] come se fosse effettivamente outer_instruction.qubits[i], e così via. Per creare una semplice tabella di riferimento, basta procedere come segue:

cf_instruction: CircuitInstruction = ...
cf_operation: ControlFlowOp = cf_instruction.operation
for block in blocks:
    # Mappings of "inner" qubits/clbits to the outer ones.
    qubit_map = dict(zip(block.qubits, cf_instruction.qubits))
    clbit_map = dict(zip(block.clbits, cf_instruction.clbits))

    # ... do something with `block` ...

Ricordate che dovrete propagare queste informazioni se ricorrete a sottoblocchi di operazioni di flusso di controllo.

Tutti i blocchi di sottocircuiti in un ControlFlowOp devono contenere lo stesso numero di Qubits e Clbits, riferiti agli stessi bit esterni nello stesso ordine, in modo che il zip ciclo riportato nel blocco di codice sopra funzioni. Gli oggetti del circuito Bit interno non devono necessariamente essere letteralmente gli stessi oggetti. {x: x}Quando si utilizza l'interfaccia del generatore di flusso di controllo (che, non lo si sottolineerà mai abbastanza, è vivamente consigliata agli utenti), i generatori faranno in modo che gli oggetti bit interni siano identici a quelli esterni; il qubit_map nel blocco di codice sopra riportato sarà sempre una mappatura, ma se si utilizzano i blocchi, è bene essere preparati all'eventualità che la mappatura sia richiesta.

Qualsiasi ClassicalRegisters utilizzato in un sottocircuito di flusso di controllo deve essere presente anche in tutti i blocchi che lo contengono ( ovvero in tutte le operazioni di flusso di controllo che lo contengono e nel circuito più esterno), e tutti i blocchi dello stesso ControlFlowOp circuito devono contenere gli stessi registri. Anche in questo caso, l'interfaccia del builder provvederà affinché ciò avvenga (oppure genererà un errore immediato se non è in grado di farlo).

Quando si utilizza la costruzione a basso livello, i blocchi interni devono chiudere manualmente le variabili di calcolo QuantumCircuit devono chiudere manualmente tutte le variabili di calcolo classico in tempo reale di ambito esterno che utilizzano. Questo è segnalato dalla presenza di questi nell'iteratore iter_captured_vars() per quel blocco. Le librerie che costruiscono manualmente questi blocchi dovranno tenere traccia di queste catture durante la costruzione dei blocchi di circuiti a flusso di controllo e aggiungerle al blocco usando il comando add_capture() (o l'argomento del costruttore captures ), ma il codice utente userà in genere l'interfaccia del costruttore di circuiti di controllo-flusso, che gestisce automaticamente questa operazione.

Per maggiori informazioni su come realizzare circuiti con flusso di controllo, consultare la documentazione sulla costruzione del flusso di controllo.

Annotazioni locali alle istruzioni

Vedi anche

qiskit.circuit.annotation

La discussione a livello di modulo del framework di annotazione, compreso il modo in cui definire annotazioni personalizzate e come il sistema interagisce con il compilatore e con la serializzazione in altri formati.

Alcune istruzioni del circuito possono essere “annotate” con annotazioni locali all’istruzione. A partire dalla versione 2.1.0 di Qiskit, questa funzionalità è limitata a BoxOp. Tutte le annotazioni sono sottoclassi di un unico oggetto che definisce l'interfaccia di base, ma in genere rappresentano analisi e comandi interamente personalizzati.

Rubrica 1
Rubrica 2
Annotation(*args, **kwargs)Un'annotazione arbitraria per le istruzioni.

Indagine sulle relazioni di commutazione

Se due operazioni in un circuito si scambiano, possiamo scambiare l'ordine in cui vengono applicate. Questo può consentire ottimizzazioni e semplificazioni, ad esempio, se permette di unire o annullare le porte:

     ┌─────────┐     ┌─────────┐               ┌─────────┐
q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■──     q_0: ┤ Rz(1.7) ├
     └─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐  =       └──┬───┬──┘
q_1: ───────────┤ X ├───┤ X ├───┤ X ├     q_1: ───┤ X ├───
                └───┘   └───┘   └───┘             └───┘

L'esecuzione di queste ottimizzazioni fa parte del transpiler, ma gli strumenti per analizzare le commutazioni sono disponibili nel CommutationChecker.

Rubrica 1
Rubrica 2
CommutationChecker([...])Verificare le commutazioni di due operazioni.

Pianificazione a risoluzione ritardata

In genere, l'output del compilatore di Qiskit non può essere eseguito direttamente su una QPU. In primo luogo, è probabile che passi attraverso un compilatore a livello di impulsi specifico del fornitore, che converte le porte logiche e le misurazioni in segnali destinati all’elettronica di controllo della QPU. Sebbene Qiskit Target sia in grado di rappresentare alcuni dei vincoli temporali a cui saranno soggetti questi impulsi, in genere tale rappresentazione non è del tutto completa. Ciò è particolarmente vero quando sono coinvolti circuiti dinamici (operazioni feed-forward); i ritardi indotti dai componenti classici dipendono spesso da dettagli di basso livello relativi alla post-ottimizzazione dei compilatori backend e non possono essere conosciuti da Qiskit.

In queste situazioni, l'utente può comunque esercitare un controllo sulla programmazione relativa degli impulsi, ad esempio per il disaccoppiamento dinamico, utilizzando durate di "allungamento". Questi sono costruiti da QuantumCircuit.add_stretch()e interagiscono con il sistema di espressione classico descritto in qiskit.circuit.classical, anche se non sono mutabili in tempo reale.

Ad esempio, possiamo aggiungere tratti e scatole per creare un sistema in cui due sequenze di disaccoppiamento dinamico separate vengono applicate allo stesso qubit, mentre una coppia di altri qubit subisce un ritardo di durata sconosciuta. Le due sequenze sono vincolate alla stessa lunghezza, anche se internamente gli impulsi DD concreti hanno lunghezze diverse.

from qiskit import QuantumCircuit
from qiskit.circuit.classical import expr

qc = QuantumCircuit(3, 3)
# This sets up three duration "degrees of freedom" that will
# be resolved later by a backend compiler.
a = qc.add_stretch("a")
b = qc.add_stretch("b")
c = qc.add_stretch("c")

# This set of operations involves feed-forward operations that
# Qiskit cannot know the length of.
with qc.box():
    qc.h(1)
    qc.cx(1, 2)
    qc.measure([1, 2], [1, 2])
    with qc.if_test(expr.equal(qc.clbits[1], qc.clbits[2])):
        qc.h(1)

# While that stuff is happening to qubits (1, 2), we want
# qubit 0 to do two different DD sequences.  The two DD
# sequences are fixed to be the same length as each other,
# even though they're both internally stretchy.
with qc.box(duration=a):
    # Textbook NMRish XX DD.
    qc.delay(b, 0)
    qc.x(0)
    qc.delay(expr.mul(2, b), 0)
    qc.x(0)
    qc.delay(b, 0)
with qc.box(duration=a):
    # XY4-like DD.
    for _ in range(2):
        qc.delay(c, 0)
        qc.y(0)
        qc.delay(expr.mul(2, c), 0)
        qc.x(0)
        qc.delay(c, 0)

Creazione di istruzioni personalizzate

Se desideri creare semplici istruzioni o porte una tantum da aggiungere a un circuito, e i blocchi vengono utilizzati solo a scopo di visualizzazione o raggruppamento, il modo più semplice per creare un’istruzione o una porta personalizzata è semplicemente definirne la struttura come un QuantumCircuit, e poi utilizzare il suo to_instruction() metodo o to_gate() a seconda dei casi. I risultati possono essere riportati direttamente sul QuantumCircuit.append() circuito più grande. Questi metodi creeranno istanze di base Instruction o Gate istanze il cui definition attributo corrisponde al circuito fornito, il che significa che sarà automaticamente accessibile al transpiler e alle altre funzioni di Qiskit che tentano di scomporre i circuiti.

Si noti che le istruzioni e le porte indipendenti devono agire solo sui qubit e sui clbit; le istruzioni che devono usare un flusso di controllo complesso devono essere inserite nella QuantumCircuit usando compose().

Creazione di sottoclassi di istruzioni

InstructionLe classi base, Gate e ControlledGate sono tutte progettate per consentire la creazione di sottoclassi in modo sicuro e dispongono di punti di aggancio che le sottoclassi possono implementare. Se il tuo gate personalizzato è privo di parametri e senza stato, potresti anche volerlo derivare dalla corrispondente classe singleton in qiskit.circuit.singleton, ad esempio SingletonGate. Si consiglia di consultare la documentazione disponibile all'indirizzo qiskit.circuit.singleton per ulteriori metodi e punti di aggancio relativi al meccanismo singleton.

Le sottoclassi dovrebbero in genere definire un costruttore predefinito che chiami il costruttore :class`super` con gli argomenti corretti per la propria istruzione. Instruction.paramsÈ consentito includere stati aggiuntivi nella classe, ma le sottoclassi si integreranno in modo più affidabile con il resto del framework Qiskit se si fa affidamento esclusivamente sui propri parametri, che sono esclusivamente angoli dei gate.

Le sottoclassi di Instruction (o una delle sue sottoclassi) dovrebbero implementare il metodo privato Instruction._define() , che popola in modo pigro la cache nascosta _definition che fa da supporto al metodo pubblico definition .

_define

Instruction._define()

GitHub

Compila il campo "cached _definition " di questo Instruction.

Le sottoclassi dovrebbero implementare questo metodo per consentire la creazione differita del proprio attributo pubblico definition . Una sottoclasse può utilizzare la propria params al momento della chiamata. Il metodo dovrebbe assegnare _definition un QuantumCircuit valore a e non restituire alcun valore.

Nelle sottoclassi di ControlledGate, il _define() metodo dovrebbe implementare la decomposizione solo per lo stato di controllo "tutti a uno". Il ControlledGate.definition sistema provvederà a modificarlo in base allo stato effettivo del controllo.

Se la sottoclasse utilizza il macchinario dei singleton, si tenga presente che il metodo _define() sarà richiamato immediatamente dopo l'esecuzione del corpo della classe, per produrre l'oggetto definizione per l'oggetto singleton canonico. Ciò significa che la definizione deve utilizzare solo porte già definite; se si sta scrivendo una libreria con molte porte singleton, si dovranno ordinare i file e le importazioni per garantire che ciò sia possibile.

Probabilmente anche le sottoclassi di Gate vorranno sovrascrivere il metodo di istanza del protocollo array di Numpy, __array__. Viene utilizzato da Gate.to_matrix(), e presenta la seguente firma:

__array__

object.__array__(dtype=None, copy=None)

Restituisce un array Numpy che rappresenta il gate. In questo modo è possibile utilizzare il campo della params porta e si può presumere che si tratti di valori numerici (ammesso che la sottoclasse lo preveda) e non di parametri definiti in fase di compilazione.

Per una maggiore efficienza, l'array restituito dovrebbe essere predefinito con un dtype di complex.

Se la tua sottoclasse personalizzata dispone di rappresentazioni naturali delle sue forme controllate o inverse, potresti voler sovrascrivere anche i inverse() metodi e control() .

A titolo di esempio, ecco come definire un gate personalizzato di tipo “ RxzR_{xz} ”; ovvero, una rotazione di un singolo angolo attorno all’asse XZXZ. RZXGateIn sostanza, se i qubit fossero disposti al contrario, scriveremo la nostra definizione in base a questa configurazione. Siamo parametrici, quindi non possiamo essere un singleton, ma siamo unitari, quindi dovremmo essere un Gate:

import math
import numpy as np
from qiskit.circuit import Gate, QuantumCircuit

class RXZGate(Gate):
    def __init__(self, theta):
        # Initialize with our name, number of qubits and parameters.
        super().__init__("rxz", 2, [theta])

    def _define(self):
        # Our base definition is an RZXGate, applied "backwards".
        defn = QuantumCircuit(2)
        defn.rzx(1, 0)
        self._definition = defn

    def inverse(self, annotated = False):
        # We have an efficient representation of our inverse,
        # so we'll override this method.
        return RXZGate(-self.params[0])

    def power(self, exponent: float):
        # Also we have an efficient representation of power.
        return RXZGate(exponent * self.params[0])

    def __array__(self, dtype=None, copy=None):
        if copy is False:
            raise ValueError("unable to avoid copy while creating an array as requested")
        cos = math.cos(0.5 * self.params[0])
        isin = 1j * math.sin(0.5 * self.params[0])
        return np.array([
            [cos, -isin, 0, 0],
            [-isin, cos, 0, 0],
            [0, 0, cos, isin],
            [0, 0, isin, cos],
        ], dtype=dtype)

In questo esempio abbiamo definito una base in termini di RZXGate, ma per consentire decomposizioni più veloci in una serie di basi, potremmo voler aggiungere alcune ulteriori equivalenze a SessionEquivalenceLibrary. Si noti che la BasisTranslator ricerca di traduzioni prenderà in considerazione tutte le possibili equivalenze a tutte le possibili profondità; pertanto, fornendo un’equivalenza in termini di (ad esempio) XGate , si renderanno automaticamente disponibili anche le scomposizioni in termini di RXGate .

Aggiungiamo un'equivalenza in termini di HH, CXCX e RzR_z per un parametro simbolico arbitrario:

from qiskit.circuit import SessionEquivalenceLibrary, Parameter

theta = Parameter("theta")

equiv = QuantumCircuit(2)
equiv.h(0)
equiv.cx(1, 0)
equiv.rz(theta, 0)
equiv.cx(1, 0)
equiv.h(0)

SessionEquivalenceLibrary.add_equivalence(RZXGate(theta), equiv)

Da quel momento in poi, per tutta la durata della sessione dell’interprete Python, i traduttori come BasisTranslator troveranno la nostra nuova definizione nei risultati di ricerca.


Lavorare con oggetti a livello di circuito

Conversione di circuiti astratti in circuiti fisici

Un'astratta QuantumCircuit non può essere eseguito in modo affidabile sull'hardware. Potreste essere in grado di utilizzare alcuni dei simulatori di alto livello collegati a Simulazione di circuiti per produrre risultati rapidi per circuiti su piccola scala, ma per eseguire circuiti su scala di utilità, dovrete utilizzare hardware reale, il che comporta la compilazione di un circuito fisico.

La funzione di alto livello che permette di farlo è transpile(); accetta in parametro un circuito astratto e un hardware backend o target, e restituisce un circuito fisico. Per avere maggiore accesso e controllo sulle fasi dei passaggi che verranno eseguiti, usa generate_preset_pass_manager() per creare prima un StagedPassManager , che potrai poi modificare.

L'intero meccanismo di transpilazione e compilazione è descritto in dettaglio nella documentazione del modulo qiskit.transpiler e i dettagli su tutti i passaggi integrati in Qiskit sono disponibili in qiskit.transpiler.passes.

Simulazione di circuiti

Anche se non fa parte dell'interfaccia qiskit.circuit una delle esigenze più comuni è quella di ottenere rapidamente i risultati della simulazione di QuantumCircuit oggetti. Questa sezione fornisce un rapido punto di partenza per trovare le informazioni pertinenti in altri punti della documentazione.

Per i circuiti unitari, è possibile simulare gli effetti sullo stato dell' ∣0⋯0⟩\lvert0\dotsm0\rangle e passando quest'ultimo QuantumCircuit direttamente al costruttore Statevector predefinito. Allo stesso modo, è possibile ottenere una matrice unitaria che rappresenta il circuito come operatore passandolo al Operator costruttore predefinito. Se si dispone di un circuito fisico, si potrebbe preferire passarlo al Operator.from_circuit() metodo per applicare le trasformazioni dal QuantumCircuit.layout in modo da rimapparlo allo spazio dei qubit “astratto”.

Per un'esperienza di simulazione più simile a un backend, esistono implementazioni supportate da un simulatore di tutte le interfacce hardware di Qiskit. In particolare, potreste essere interessati a:

Definizione delle relazioni di equivalenza

Un compito comune nella mappatura di circuiti astratti su hardware fisico e nell'ottimizzazione del risultato consiste nell'individuare relazioni di equivalenza che mappino una porta logica su un diverso insieme di base. Qiskit memorizza queste informazioni in una classe di database denominata EquivalenceLibrary.

Rubrica 1
Rubrica 2
EquivalenceLibrary( [base] )Una libreria che fornisce una mappatura unidirezionale di Gates alle loro implementazioni equivalenti come QuantumCircuits.

Qiskit viene fornito con un ampio insieme di relazioni di equivalenza predefinite per tutte le porte standard. Questa libreria di base si chiama StandardEquivalenceLibrarye deve essere trattata come immutabile.

qiskit.circuit.StandardEquivalenceLibrary

Un EquivalenceLibrary che memorizza tutte le relazioni tra i gate standard integrati in Qiskit. SessionEquivalenceLibrarybaseNon dovresti modificarlo direttamente, ma piuttosto crearne uno tuo EquivalenceLibrary utilizzando questo come modello, oppure modificare lo stato globale.

Qiskit definisce inoltre un oggetto di stato globale condiviso, SessionEquivalenceLibrary, che rappresenta le equivalenze predefinite utilizzate in vari punti di Qiskit, in particolare nel passaggio del BasisTranslator transpiler. Sentiti libero di aggiungere le tue equivalenze utilizzando il relativo add_equivalence() metodo: queste verranno automaticamente rilevate dalle istanze predefinite di BasisTranslator.

qiskit.circuit.SessionEquivalenceLibrary

L'istanza predefinita di EquivalenceLibrary, che verrà utilizzata dalla maggior parte degli oggetti Qiskit se non viene specificata manualmente alcuna libreria. Se lo desideri, puoi aggiungere delle equivalenze a questo utilizzando add_equivalence(). Eredita tutte le regole integrate di StandardEquivalenceLibrary.

Applicare il principio di Pauli a un circuito

Nell'esecuzione dei circuiti quantistici esistono due tipi principali di rumore. Il primo è il rumore stocastico, o incoerente, dovuto principalmente all'interazione indesiderata tra il processore quantistico e l'ambiente esterno in cui risiede. Il secondo è noto come errore di coerenza, e questi errori si verificano a causa di un controllo imperfetto di un sistema quantistico. Può trattarsi di termini indesiderati nell'hamiltoniana di un sistema, cioè di un'evoluzione unitaria errata, o di errori dovuti a un controllo temporale errato del sistema quantistico, che comprende elementi come forme di impulso errate per le porte.

Il Pauli twirling è una tecnica di soppressione degli errori quantistici che utilizza la randomizzazione per trasformare gli errori coerenti in errori stocastici, combinando insieme i risultati di molti circuiti casuali, ma logicamente equivalenti. Qiskit fornisce una funzione per applicare il Pauli twirling a un dato circuito per porte standard a due qubit. Per maggiori dettagli è possibile consultare la documentazione della funzione di seguito riportata:

pauli_twirl_2q_gates

qiskit.circuit.pauli_twirl_2q_gates(circuit, twirling_gate=None, seed=None, num_twirls=None, target=None)

GitHub

Creare copie di un circuito dato con il Twirling di Pauli applicato intorno a due porte di qubit specificate.

Se si esegue questa funzione con l'intento di eseguire un circuito su hardware, questo potrebbe non essere il modo più efficiente per eseguire il twirling. Soprattutto se il fornitore di hardware ha implementato l'interfaccia di primitives con SamplerV2 e EstimatorV2 , molto probabilmente questo non è il modo migliore per applicare il twirling al vostro circuito e dovrete fare riferimento all'implementazione di SamplerV2 e/o EstimatorV2 per il fornitore di hardware specificato.

Se si intende eseguire questa funzione dopo transpile() o PassManager.run() , è possibile utilizzare l'argomento opzionale target in modo che le porte di Pauli a 1 qubit inserite vengano sintetizzate in modo da essere compatibili con il valore specificato Target , garantendo così che i circuiti di uscita rimangano compatibili.

Parametri

  • circuit (QuantumCircuit) – Il circuito da girare
  • twirling_gate (None | str |Gate |list[str] | list[Gate]) – Il gate da ruotare è impostato di default su "None", il che significa che vengono ruotati tutti i gate predefiniti: CXGate, CZGate, ECRGate, e iSwapGate. Se specificato, può trattarsi di un singolo gate oppure di un elenco di gate, sotto forma sia di oggetto gate che del relativo nome come stringa. Al momento sono supportati solo i nomi “cx”, “cz”, “ecr” e “iswap”. Se viene fornito un oggetto gate al di fuori dei gate predefiniti, esso deve avere una matrice definita nel proprio to_matrix metodo affinché il gate possa essere eventualmente ruotato. Se non è possibile calcolare una configurazione di rotazione valida, quel gate specifico verrà ignorato senza alcun avviso e non verrà sottoposto a rotazione.
  • seed (int | None) – Un seme intero per il generatore di numeri casuali utilizzato internamente da questa funzione. Se specificato, deve essere compreso tra 0 e 18.446.744.073.709.551.615.
  • num_twirls (int | None) – Il numero di circuiti di rotazione da costruire. L'impostazione predefinita è None e restituisce un singolo circuito. Se si tratta di un numero intero, verrà restituito un elenco di circuiti con circuiti num.
  • target (Target | None) – Se specificata, Target un'istanza da utilizzare per eseguire la decomposizione di un singolo qubit nell'ambito del "Pauli twirling", al fine di ottimizzare e mappare i gate di Pauli aggiunti al circuito sull'obiettivo specificato.

Risultati

Una copia del circuito dato con il twirling Pauli applicato a ogni istanza della porta di twirling specificata.

Tipo di restituzione

QuantumCircuit | Elenco [ QuantumCircuit ]


Eccezioni

Quasi tutte le funzioni e i metodi del circuito sollevano un errore CircuitError quando incontrano un errore particolare nell'uso di Qiskit (al contrario dei normali problemi di digitazione o indicizzazione, che di solito sollevano il corrispondente errore standard di Python ).

CircuitError

exception qiskit.circuit.CircuitError(*message)

GitHub

Basi: QiskitError

Classe base per gli errori generati durante l'elaborazione di un circuito.

Impostare il messaggio di errore.


Convenzioni dei circuiti

Quando si costruiscono circuiti a partire da oggetti astratti e matrici più concrete, esistono diverse convenzioni possibili per quanto riguarda l'etichettatura dei bit, l'ordinamento dei bit e il modo in cui il prodotto tensoriale astratto viene realizzato nell'algebra delle matrici concrete.

Le convenzioni di Qiskit sono:

  • nelle rappresentazioni a stringa di bit, i bit sono etichettati con il bit più a destra della stringa chiamato 00 e il bit più a sinistra della stringa di bit nn chiamato n−1n - 1.
  • quando si usano gli interi come indici di bit-specificatori nelle funzioni di costruzione dei circuiti, l'intero viene trattato come un indice in QuantumCircuit.qubits (o clbits).
  • quando si disegnano i circuiti, si mettono in cima i bit con l'indice più basso.
  • nelle rappresentazioni dei vettori di stato, realizziamo il prodotto tensoriale astratto come prodotto di Kronecker e ordiniamo gli argomenti in modo tale che l'ampiezza dello stato di base computazionale ∣x⟩\lvert x\rangle, dove xx è la stringa di bit interpretata come un intero, si trovi nella posizione statevector[x].
  • quando si controlla un gate, il qubit o i qubit di controllo vengono inseriti per primi nell'elenco degli argomenti, ad esempio nella chiamata qc.cx(0, 1), il qubit 0 sarà il controllo e il qubit 1 sarà il target. Analogamente, nella chiamata manuale qc.append(CXGate(), [0, 1]), il qubit 0 sarà il controllo e il qubit 1 sarà il target.

Illustriamo queste convenzioni con alcuni esempi.

Etichettatura dei bit

Prendete il circuito:

from qiskit import QuantumCircuit

qc = QuantumCircuit(5, 5)
qc.x(0)
qc.x(1)
qc.x(4)
qc.measure(range(5), range(5))

In questo modo si capovolgono gli stati dei qubit 0, 1 e 4 da ∣0⟩\lvert0\rangle a ∣1⟩\lvert1\rangle, quindi si misurano tutti i qubit nn nel corrispondente clbit nn utilizzando la base computazionale ( ZZ ). Se simulata senza rumore, la stringa di bit in uscita da questo circuito sarà ogni volta 1001110011; i qubit 0, 1 e 4 sono invertiti e i valori "uno" nella stringa di bit si trovano nelle cifre zerotesta, prima e quarta da destra.

In Qiskit, scriveremmo lo stato del qubit immediatamente prima della misura in ket-notazione stenografica come ∣10011⟩\lvert10011\rangle. Si noti che l'etichetta ket corrisponde alla stringa di bit classica e ha il valore numerico binario 19.

Se disegniamo questo circuito, vedremo che Qiskit colloca lo zeroth qubit nella parte superiore del disegno del circuito:

qc.draw("mpl")
Schema del circuito prodotto dal codice precedente.

Rappresentazioni matriciali

I vettori di stato sono definiti secondo la convenzione per cui, per un sistema a due livelli, la relazione tra rappresentazione astratta e rappresentazione matriciale è tale che

α∣0⟩+β∣1⟩↔(αβ)\alpha\lvert0\rangle + \beta\lvert1\rangle \leftrightarrow \begin{pmatrix} \alpha \\ \beta \end{pmatrix}

dove α\alpha e β\beta sono numeri complessi. Memorizziamo il vettore di stati come 1D Numpy ndarray con i dati sv = [alpha, beta], cioè sv[0] == alpha e sv[1] == beta; si noti che gli indici nel vettore degli stati corrispondono alle etichette dei ket.

Costruiamo il prodotto tensoriale di due stati di qubit in algebra matriciale usando il prodotto di Kronecker, con il qubit 0 a destra e il qubit 1 a sinistra, in modo tale che lo stato base ZZ ∣x⟩\lvert x\rangle (dove xx è l'interpretazione intera della stringa di bit) abbia il suo termine non nullo nel vettore di stato sv a sv[x]:

import numpy
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

state_0 = [1, 0]  # defined representation of |0>
state_1 = [0, 1]  # defined representation of |1>

# Circuit that creates basis state |10011>, where
# binary 10011 has the decimal value 19.
qc = QuantumCircuit(5)
qc.x(0)
qc.x(1)
qc.x(4)
qiskit_sv = Statevector(qc)

# List index 'n' corresponds to qubit 'n'.
individual_states = [
    state_1,
    state_1,
    state_0,
    state_0,
    state_1,
]
# Start from a scalar.
manual_sv = [1]
for qubit_state in individual_states:
    # Each new qubit goes "on the left".
    manual_sv = numpy.kron(qubit_state, manual_sv)

# Now `qiskit_sv` and `manual_sv` are the same, and:
assert manual_sv[19] == 1
assert qiskit_sv[19] == 1

Ciò si riflette nella rappresentazione matriciale degli operatori e si integra con le convenzioni relative all'ordine dei bit per gli operatori controllati. Ad esempio, la forma matriciale di CXGate è:

import numpy
from qiskit.circuit.library import CXGate

numpy.array(CXGate())
array⁡(CX)=(1000000100100100)\operatorname{array}(CX) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix}

Questo potrebbe essere diverso da altre rappresentazioni matriciali che avete visto per CXCX, ma ricordate che la scelta della rappresentazione matriciale è convenzionale, e questa forma corrisponde alle convenzioni di Qiskit per cui i qubit di controllo vengono prima e il prodotto tensoriale è rappresentato in modo tale che ci sia una corrispondenza tra l'indice dell'"ampiezza uno" e il valore della bitstring di uno stato.

Nel caso in cui un gate abbia più controlli, come ad esempio nel caso di CCXGate, l’argomento ctrl_state viene interpretato come il valore della stringa di bit dei qubit di controllo, utilizzando le stesse convenzioni di numerazione a partire da zero. CCXGateAd esempio, dato che il valore ctrl_state predefinito è la stringa di bit composta interamente da uno, possiamo notare che la forma matriciale di CCXGate con ctrl_state = 1 è la stessa che si otterrebbe se prendessimo lo stato di controllo composto interamente da uno, ma capovolgessimo il valore del qubit di controllo con indice più alto all’ingresso e all’uscita del gate:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator

# Build the natural representation of `CCX` with the
# control qubits being `[0, 1]`, relative to the
# bitstring state "01", such that qubit 0 must be in |1>
# and qubit 1 must be in |0>.  The target qubit is 2.
ccx_natural = QuantumCircuit(3)
ccx_natural.ccx(0, 1, 2, ctrl_state=1)

# Build the same circuit in terms of the all-ones CCX.
# Note that we flip _qubit 1_, because that's the one
# that differs from the all-ones state.
ccx_relative = QuantumCircuit(3)
ccx_relative.x(1)
ccx_relative.ccx(0, 1, 2)
ccx_relative.x(1)

assert Operator(ccx_relative) == Operator(ccx_natural)

In entrambi questi casi, la forma matriciale di CCXGate in ctrl_state = 1 è:

array⁡(CCX(ctrl_state=1))=(1000000000000100001000000001000000001000010000000000001000000001)\operatorname{array}\bigl(CCX(\text{ctrl\_state}=1)\bigr) = \begin{pmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \end{pmatrix}
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.