Mit bosonischen Circuits arbeiten
Ein BosonicCircuit drückt eine Berechnung direkt auf bosonischen Moden aus. Er enthält weder Trunkierung noch Kodierung noch eine Qubit-Anzahl: Diese kommen erst später, während der Transpilation, und genau so lässt sich ein Circuit unter mehreren Kodierungen und mehreren Trunkierungen absenken, ohne neu gebaut zu werden.
Er stellt bewusst eine reduzierte API im Vergleich zu Qiskits QuantumCircuit bereit, damit sich keine Qubit-Gates auf eine bosonische Mode anwenden lassen, was keine wohldefinierte Operation wäre. Diese Anleitung behandelt die Operationen, die für das Zusammensetzen größerer Circuits aus kleineren bereitstehen.
Warum bosonische Circuits verwenden
Betrachte den traditionellen Workflow, um die Zeitentwicklung eines bosonischen Hamiltonians auf eine Qubit-basierte Architektur zu bringen:
-
Definiere den bosonischen Hamiltonian, zum Beispiel .
-
Wähle eine Trunkierung und eine Kodierung und bilde den Hamiltonian damit auf einen Qubit-Operator ab.
-
Setze die Zeitentwicklung im Qubit-Raum um, per Trotterisierung oder mit einer anderen Methode.
Der zweite Schritt kommt früh, und er trifft zwei Entscheidungen, die sich nur teuer zurücknehmen lassen. Die Trunkierung legt fest, wie viele Niveaus jede Mode behält, und die Kodierung legt fest, wie viele Qubits das kostet, sodass beides im Qubit-Operator fixiert ist, bevor mit dem Circuit irgendetwas anderes geschehen ist. Um dieselbe Physik bei einem anderen Cutoff oder mit einer anderen Kodierung zu betrachten, bildest du den Hamiltonian erneut ab und baust den Circuit erneut. Was die Abbildung außerdem verwirft, ist die bosonische Struktur: welche Terme zusammengehören und dass die Terme eines Hopping-Paars jeweils die Adjungierten voneinander sind.
Ein BosonicCircuit verschiebt diese Entscheidungen ans Ende:
-
Definiere den bosonischen Hamiltonian.
-
Drücke die Zeitentwicklung direkt auf bosonischen Moden aus, mit bosonischen Gates.
-
Wähle Trunkierung und Kodierung als Teil der Transpilation.
Der Circuit beschreibt, was berechnet werden soll, und die Transpilation entscheidet, wie es auf Qubits gebracht wird. Derselbe Circuit wird dann bei jedem Cutoff und unter jeder Kodierung abgesenkt, und die Gruppierung des Operators ist für die Passes, die sie nutzen können, weiterhin vorhanden. Die nächsten Abschnitte zeigen, wie ein solcher Circuit zusammengesetzt wird; Bosonische Circuits transpilieren zeigt, wie er abgesenkt wird.
Generische Mode-Indizierung
Das Modul operators und der Circuit teilen sich eine Konvention für Moden. Eine Mode ist nur ein Index, der einen bosonischen Freiheitsgrad benennt, und sie hat keine eigene Bedeutung. Du kannst daher jeden Operator entwickeln, der das Protokoll OperatorTrait implementiert, was immer er darstellt, und der Circuit muss nie wissen, wofür Mode 2 steht. Dasselbe gilt für die Größe einer Mode: Ein Circuit hat keinen Besetzungs-Cutoff, also sagt hier nichts, wie viele Niveaus eine Mode hat.
Gates anhängen
Gates werden an Moden angehängt. Nur BosonicGate-Instanzen werden akzeptiert:
>>> 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)
Alles andere wird abgelehnt, und der Fehler nennt den beanstandeten Typ:
>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>
Circuits komponieren
compose() setzt die Anweisungen eines Circuits auf einen anderen. Sie werden inline eingefügt, nicht in ein einzelnes Gate verpackt, sodass das Ergebnis ein gewöhnlicher Circuit ist, den du weiter zerlegen und transpilieren kannst.
>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Standardmäßig wird ein neuer Circuit zurückgegeben, und beide Operanden bleiben unverändert. Übergib inplace=True, um stattdessen den ersten zu verändern, und front=True, um den Operanden vor die vorhandenen Anweisungen zu setzen:
>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True
Ein schmalerer Circuit kann auf eine gewählte Teilmenge der Moden gesetzt werden:
>>> 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>
Nur ein weiterer BosonicCircuit wird als Operand akzeptiert, weil Qiskits eigenes compose Qubit-Gates inline einfügen und die Prüfung umgehen würde, die append durchsetzt.
Einen Circuit wiederholen
repeat() wiederholt einen Circuit Ende an Ende. Die Wiederholungen werden geglättet: Das Ergebnis enthält die Anweisungen reps-mal hintereinander, statt eines Gates, das den Circuit umschließt. Daher skalieren die Operationszahlen mit reps, und decompose wirkt auf den wiederholten Rumpf, statt eine Wiederholung aufzulösen.
>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Das ist der Weg auf Circuit-Ebene zum Trotterisieren: für kurze Zeit entwickeln und wiederholen. Siehe Ein Evolution-Gate im bosonischen Raum synthetisieren für die Alternative, eine Produktformel am Gate selbst zu wählen, und warum die beiden verschiedene Dinge sind, die sich zufällig das Wort „Wiederholungen“ teilen.
Schritte durch Barrieren trennen
Eine Barriere markiert einen Synchronisationspunkt, über den Optimierungs-Passes keine Gates verschmelzen. barrier() setzt eine auf alle Moden oder auf die Moden, die du benennst:
>>> 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>
repeat(..., insert_barriers=True) setzt eine zwischen aufeinanderfolgende Wiederholungen und keine nach der letzten:
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Barrieren und die Kodierung
Eine Kodierung braucht mehrere Qubits pro Mode, sodass das Register der Moden und das Register der Qubits unterschiedlich groß sind und ein Layout ganze Register aufeinander bezieht. Eine Mode hat daher keine eigenen Qubits, auf die sich eine Barriere eingrenzen ließe. Bei der Boson-zu-Qubit-Synthese wird eine Barriere auf jedes Qubit jedes Registers abgebildet, das sie berührt hat, was bei jedem Layout korrekt ist:
>>> 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>
Einen bosonischen Circuit auslesen
Ein BosonicCircuit hat kein measure oder measure_all. Was ein gemessenes Bit bedeutet, hängt von der Kodierung ab. Bei einer unären Kodierung ist die Besetzung einer Mode über mehrere Qubits verteilt und muss aus einem Bitmuster gelesen werden, und bei einer kompakten ist sie eine Binärzahl, sodass eine Messung, die eine Mode benennt, die Kodierung kennen müsste, was ein bosonischer Circuit bewusst nicht tut. Miss stattdessen den transpilierten QuantumCircuit und dekodiere die Bitstrings mit LevelEncoding.level_to_bits() und den Offsets des LevelSpec, mit dem du abgesenkt hast. Bose-Hubbard-Dynamik mit wählbarer Kodierung simulieren macht das für ein vollständiges Modell.
>>> hasattr(BosonicCircuit, "measure_all")
False
Nächste Schritte
-
Bosonische Circuits transpilieren erklärt Stufe für Stufe, wie ein bosonischer Circuit zu einem Qubit-Circuit wird.
-
Bosonische Moden rotieren behandelt das Gate, das Moden mischt und die Bosonenzahl erhält.
-
Ein Evolution-Gate im bosonischen Raum synthetisieren zeigt, wie du eine Evolution im bosonischen Raum trotterisierst.
-
Designprinzipien von Operatordarstellungen erklärt die Operatoren, welche die Gates tragen.