Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Lavorare con i circuiti bosonici

Un BosonicCircuit esprime un calcolo direttamente sui modi bosonici. Non porta con sé alcun troncamento, alcuna codifica e alcun numero di qubit: questi entrano in gioco più tardi, durante il transpile, ed è così che un solo circuito può essere tradotto con diverse codifiche e diversi troncamenti senza essere ricostruito.

Espone deliberatamente un'API ridotta rispetto al QuantumCircuit di Qiskit, in modo che i gate sui qubit non possano essere applicati a un modo bosonico, il che non sarebbe un'operazione ben definita. Questa guida tratta le operazioni che sono esposte per assemblare circuiti più grandi a partire da circuiti più piccoli.

Perché usare i circuiti bosonici​

Considera il flusso di lavoro tradizionale per portare l'evoluzione temporale di una Hamiltoniana bosonica su un'architettura basata su qubit:

  1. Definire la Hamiltoniana bosonica, per esempio H=−∑ijbi†bjH = -\sum_{ij} b^\dagger_i b_j.

  2. Scegliere un troncamento e una codifica, e con essi mappare la Hamiltoniana su un operatore di qubit.

  3. Implementare l'evoluzione temporale nello spazio dei qubit, tramite trotterizzazione o un altro metodo.

Il secondo passo arriva presto, e prende due decisioni costose da ritirare. Il troncamento fissa quanti livelli mantiene ogni modo, e la codifica fissa quanti qubit ciò costa, quindi entrambi sono fissati nell'operatore di qubit prima che sia stato fatto qualsiasi altra cosa con il circuito. Per guardare la stessa fisica con un altro cutoff o un'altra codifica, devi mappare di nuovo la Hamiltoniana e costruire di nuovo il circuito. Inoltre, ciò che il mapping scarta è la struttura bosonica: quali termini appartengono insieme, e il fatto che i termini di una coppia di hopping sono l'uno l'aggiunto dell'altro.

Un BosonicCircuit sposta quelle decisioni alla fine:

  1. Definire la Hamiltoniana bosonica.

  2. Esprimere l'evoluzione temporale direttamente sui modi bosonici, con gate bosonici.

  3. Scegliere il troncamento e la codifica come parte del transpile.

Il circuito descrive quale calcolo eseguire, e il transpile decide come metterlo sui qubit. Lo stesso circuito viene poi tradotto a ogni cutoff e con ogni codifica, e il raggruppamento dell'operatore è ancora lì per i pass che possono usarlo. Le sezioni successive mostrano come si assembla un circuito di questo tipo; Eseguire il transpile di circuiti bosonici lo mostra mentre viene tradotto.

Indicizzazione generica dei modi​

Il modulo operators e il circuito condividono una convenzione per i modi. Un modo è solo un indice che etichetta un grado di libertà bosonico, e non ha alcun significato proprio. Puoi quindi far evolvere qualsiasi operatore che implementi il protocollo OperatorTrait, qualunque cosa rappresenti, e il circuito non ha mai bisogno di sapere cosa significhi il modo 2. Lo stesso vale per la dimensione di un modo: un circuito non ha un cutoff di occupazione, quindi nulla qui dice quanti livelli abbia un modo.

Aggiungere gate​

I gate vengono aggiunti sui modi. Sono accettate solo istanze di BosonicGate:

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)

Qualsiasi altra cosa viene rifiutata, e l'errore nomina il tipo non valido:

>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>

Comporre circuiti​

compose() colloca le istruzioni di un circuito su un altro. Sono inserite in linea, non incapsulate in un singolo gate, quindi il risultato è un circuito ordinario che puoi continuare a decomporre e a sottoporre a transpile.

>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A composed BosonicCircuit.

Per impostazione predefinita viene restituito un nuovo circuito e entrambi gli operandi restano intatti. Passa inplace=True per modificare invece il primo, e front=True per mettere l'operando prima delle istruzioni esistenti:

>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True

Un circuito più stretto può essere collocato su un sottoinsieme scelto dei modi:

>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A narrow-composed BosonicCircuit.

Come operando è accettato solo un altro BosonicCircuit, perché il compose di Qiskit inserirebbe in linea i gate sui qubit e aggirerebbe il controllo che append impone.

Ripetere un circuito​

repeat() ripete un circuito da un capo all'altro. Le ripetizioni sono appiattite: il risultato contiene le istruzioni reps volte in sequenza invece di un gate che incapsula il circuito. Pertanto i conteggi delle operazioni scalano con reps, e decompose agisce sul corpo ripetuto invece di rimuovere una ripetizione.

>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit.

Questo è il modo a livello di circuito di trotterizzare: evolvi per un breve tempo e ripeti. Vedi Sintetizzare un gate Evolution nello spazio bosonico per l'alternativa di scegliere una formula di prodotto sul gate stesso, e perché le due sono cose diverse che si trovano a condividere la parola «ripetizioni».

Separare i passi con le barriere​

Una barriera contrassegna un punto di sincronizzazione attraverso il quale i pass di ottimizzazione non fondono i gate. barrier() ne colloca una su tutti i modi, o sui modi che indichi:

>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A BosonicCircuit with barriers.

repeat(..., insert_barriers=True) ne inserisce una tra ripetizioni consecutive, e nessuna dopo l'ultima:

>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit with barriers.

Barriere e codifica​

Una codifica richiede più qubit per modo, quindi il registro dei modi e il registro dei qubit differiscono in dimensione, e un layout mette in relazione registri interi. Un modo non ha quindi qubit propri a cui restringere una barriera. Durante la sintesi da bosone a qubit una barriera viene mappata su ogni qubit di ciascun registro che toccava, il che è corretto con qualsiasi layout:

>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A QuantumCircuit showing the expansion of barrier to cover the entire qubit register.

Leggere l'output di un circuito bosonico​

Un BosonicCircuit non ha measure né measure_all. Cosa significhi un bit misurato dipende dalla codifica. In una codifica unaria l'occupazione di un modo è distribuita su più qubit e va letta da uno schema di bit, mentre in una compatta è un numero binario, quindi una misura che nomina un modo dovrebbe conoscere la codifica, cosa che un circuito bosonico deliberatamente non fa. Misura invece il QuantumCircuit sottoposto a transpile, e decodifica le stringhe di bit con LevelEncoding.level_to_bits() e con gli offset del LevelSpec con cui hai eseguito la traduzione. Simulare la dinamica di Bose-Hubbard con una scelta di codifica lo fa per un modello completo.

>>> hasattr(BosonicCircuit, "measure_all")
False

Prossimi passi​