Práce s bosonovými obvody
BosonicCircuit vyjadřuje výpočet přímo na bosonových módech. Nenese žádné omezení (truncation), žádné kódování a žádný počet qubitů: ty vstupují později, během transpilace, díky čemuž lze jeden obvod převést pod několik kódování a několik omezení bez jeho přestavby.
Záměrně zpřístupňuje redukované API oproti Qiskit QuantumCircuit, aby na bosonový mód nešla aplikovat qubitová hradla, což by nebyla dobře definovaná operace. Tento návod pokrývá operace, které zpřístupněné jsou, pro skládání větších obvodů z menších.
Proč používat bosonové obvody
Uvažuj tradiční postup pro převedení časového vývoje bosonového hamiltoniánu na architekturu založenou na qubitech:
-
Definuj bosonový hamiltonián, například .
-
Zvol omezení (truncation) a kódování a s nimi zmapuj hamiltonián na qubitový operátor.
-
Implementuj časový vývoj v qubitovém prostoru, trotterizací nebo jinou metodou.
Druhý krok přichází brzy a činí dvě rozhodnutí, která je drahé vzít zpět. Omezení určuje, kolik úrovní si každý mód ponechá, a kódování určuje, kolik qubitů to stojí, takže obojí je pevně dáno v qubitovém operátoru dříve, než se s obvodem udělá cokoli dalšího. Chceš-li se na stejnou fyziku podívat při jiném cutoffu nebo jiném kódování, zmapuješ hamiltonián znovu a obvod postavíš znovu. Mapování navíc zahazuje bosonovou strukturu: které členy patří k sobě a že členy hopping páru jsou navzájem svými adjungovanými.
BosonicCircuit přesouvá tato rozhodnutí na konec:
-
Definuj bosonový hamiltonián.
-
Vyjádři časový vývoj přímo na bosonových módech pomocí bosonových hradel.
-
Zvol omezení a kódování jako součást transpilace.
Obvod popisuje, jaký výpočet provést, a transpilace rozhoduje, jak ho umístit na qubity. Stejný obvod se pak převádí při každém cutoffu a pod každým kódováním a seskupení operátoru je stále k dispozici pro průchody, které ho umějí využít. Následující části ukazují, jak se takový obvod sestavuje; Transpilace bosonových obvodů ukazuje jeho převod.
Obecné indexování módů
Modul operators a obvod sdílejí jednu konvenci pro módy. Mód je jen index, který označuje bosonový stupeň volnosti, a sám o sobě nenese žádný význam. Můžeš proto vyvíjet libovolný operátor, který implementuje protokol OperatorTrait, ať představuje cokoli, a obvod nikdy nemusí vědět, co mód 2 znamená. Totéž platí pro velikost módu: obvod nemá žádný okupační cutoff, takže nic zde neříká, kolik úrovní mód má.
Přidávání hradel
Hradla se přidávají na módy. Přijímají se pouze instance 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)
Cokoli jiného je odmítnuto a chyba pojmenuje nevhodný typ:
>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>
Skládání obvodů
compose() umístí instrukce jednoho obvodu na jiný. Jsou vloženy přímo (inlined), nikoli zabaleny do jediného hradla, takže výsledkem je běžný obvod, který můžeš dál rozkládat a transpilovat.
>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Ve výchozím nastavení se vrací nový obvod a oba operandy zůstanou beze změny. Předej inplace=True, aby se místo toho upravil první, a front=True, aby se operand umístil před stávající instrukce:
>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True
Užší obvod lze umístit na zvolenou podmnožinu módů:
>>> 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>
Jako operand se přijímá pouze jiný BosonicCircuit, protože vlastní compose v Qiskit by vložil qubitová hradla a obešel kontrolu, kterou vynucuje append.
Opakování obvodu
repeat() opakuje obvod od konce ke konci. Opakování jsou zploštěna: výsledek obsahuje instrukce reps-krát za sebou, nikoli jedno hradlo obalující obvod. Počty operací proto rostou s reps a decompose působí na opakované tělo místo odstranění jednoho opakování.
>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Toto je způsob trotterizace na úrovni obvodu: vyvíjej po krátkou dobu a opakuj. Alternativu volby součinového vzorce přímo na hradle a důvod, proč jsou tyto dvě věci odlišné, i když sdílejí slovo „opakování“, najdeš v Syntéza hradla Evolution v bosonovém prostoru.
Oddělování kroků bariérami
Bariéra (barrier) označuje synchronizační bod, přes který optimalizační průchody neslučují hradla. barrier() ji umístí na všechny módy nebo na módy, které pojmenuješ:
>>> 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) vloží jednu mezi po sobě jdoucí opakování a žádnou za poslední:
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Bariéry a kódování
Kódování potřebuje několik qubitů na mód, takže registr módů a registr qubitů se liší velikostí a layout se vztahuje k celým registrům. Mód proto nemá žádné vlastní qubity, na které by šla bariéra zúžit. Během syntézy boson-na-qubit se bariéra mapuje na každý qubit každého registru, kterého se dotýkala, což je správné při jakémkoli layoutu:
>>> 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>
Vyčtení bosonového obvodu
BosonicCircuit nemá measure ani measure_all. Co znamená změřený bit, závisí na kódování. U unárního kódování je obsazení módu rozprostřeno na několik qubitů a musí se číst ze vzoru bitů, u kompaktního je to binární číslo, takže měření pojmenovávající mód by muselo znát kódování, což bosonový obvod záměrně nezná. Místo toho měř transpilovaný QuantumCircuit a bitové řetězce dekóduj pomocí LevelEncoding.level_to_bits() a offsetů LevelSpec, se kterým jsi převáděl. Simulace Bose-Hubbardovy dynamiky s volbou kódování to dělá pro úplný model.
>>> hasattr(BosonicCircuit, "measure_all")
False
Další kroky
-
Transpilace bosonových obvodů vysvětluje, jak se bosonový obvod krok za krokem stává qubitovým obvodem.
-
Rotace bosonových módů pokrývá hradlo, které míchá módy a zachovává počet bosonů.
-
Syntéza hradla Evolution v bosonovém prostoru ukazuje, jak trotterizovat vývoj v bosonovém prostoru.
-
Principy návrhu reprezentací operátorů vysvětluje operátory, které hradla nesou.