Optimalizace přípravy Slaterova determinantu
Koncepty v tomto průvodci jsou v současnosti dostupné pouze v Python API. Ekvivalentní funkcionalita bude v C API zpřístupněna v budoucím vydání.
Hradlo InitializeModes deklaruje referenční obsazení módů; hradlo OrbitalRotation rotuje jednočásticovou bázi. Umístěna těsně za sebou, obě hradla připraví Slaterův determinant; módy začínají v referenčním determinantu a pak se rotují do cílových orbitalů. Hradlo PrepareSlaterDeterminant toto složení zachycuje a protože nese referenční obsazení, může jeho transpilace použít obdélníkovou givens_decomposition_slater(), která realizuje pouze obsazené orbitaly, místo úplného čtvercového rozkladu, který vyžaduje samotné OrbitalRotation. To je striktně levnější syntéza (viz přínos na konci tohoto průvodce).
Transpiler pass MergeSlaterDeterminantPreparation ve FermionicCircuit rozpozná vzor InitializeModes následovaný OrbitalRotation a přepíše ho na hradla PrepareSlaterDeterminant, takže pozdější fáze syntézy automaticky využije redukovaný rozklad. Při simulaci je přepis na stavu bez účinku: PrepareSlaterDeterminant je validate-then-rotate (ověří referenční obsazení a poté aplikuje rotaci, stejně jako dvě samostatná hradla), takže sloučení odemkne levnější syntézu, aniž by změnilo připravený stav.
V celém tomto průvodci je každý obvod nakreslen před průchodem passu a po něm, vedle sebe: vstup vlevo a výsledek běhu passu na něm vpravo. Dělají to dvě pomocné funkce. merge spustí pass na kopii obvodu (přes jeho FermionicDAGCircuit) a převede výsledek zpět na vykreslitelný FermionicCircuit a draw_merge vykreslí obě poloviny do jediného obrázku před/po:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>> from qiskit_fermions.transpiler import FermionicCircuitToDAG
>>> from qiskit_fermions.transpiler.converters import FermionicDAGToCircuit
>>> from qiskit_fermions.transpiler.passes import MergeSlaterDeterminantPreparation
>>>
>>> def merge(circuit):
... dag = FermionicCircuitToDAG().run(circuit)
... merged = MergeSlaterDeterminantPreparation().run(dag)
... return FermionicDAGToCircuit().run(merged)
>>>
>>> def natural_size(circuit):
... # the figure size Qiskit picks for a circuit on its own (discard the probe figure)
... probe = circuit.draw("mpl")
... size = probe.get_size_inches()
... plt.close(probe)
... return size
>>>
>>> def draw_merge(circuit):
... merged = merge(circuit)
... # let Qiskit size each half naturally, then lay them out side by side
... (bw, bh), (aw, ah) = natural_size(circuit), natural_size(merged)
... fig, (before, after) = plt.subplots(1, 2, figsize=(bw + aw, max(bh, ah)))
... circuit.draw("mpl", ax=before)
... merged.draw("mpl", ax=after)
... before.set_title("before")
... after.set_title("after")
... return fig
Co se sloučí
Pass rozpoznává tři vzory, všechny vycházející z konvence módů po blocích podle spinu, kterou používají InitializeModes a OrbitalRotation. Pro systém s norb prostorovými orbitaly jsou módy 0..norb sektorem spinu alfa a norb..2*norb sektorem spinu beta. Příprava Slaterova determinantu se provádí pro každý spinový sektor zvlášť, protože každý sektor má svůj vlastní tvar (elektrony, orbitaly).
Celý registr (nebo bez spinu)
Nejjednodušší vzor je použití InitializeModes, po němž bezprostředně následuje OrbitalRotation na stejných módech. To je případ bez spinu (jeden determinant přes všechny módy) a také spinový obvod, jehož rotace zasahuje oba sektory najednou. Obě hradla se spojí do jediného PrepareSlaterDeterminant:
>>> circuit = FermionicCircuit(4)
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(4)), circuit.modes)
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Podle spinového sektoru
Je to stejný tvar, ale omezený na jednu spinovou polovinu. Zde se dva nezávislé páry inicializace a rotace, jeden na sektor, každý samostatně spojí do PrepareSlaterDeterminant:
>>> circuit = FermionicCircuit(6) # norb = 3: alpha modes 0..3, beta modes 3..6
>>> circuit.append(InitializeModes([1, 1, 0]), circuit.modes[:3])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> circuit.append(InitializeModes([1, 0, 0]), circuit.modes[3:])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[3:])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Globální inicializace, rotace podle spinu
Jde o jediné InitializeModes přes celý registr (2 * norb), po němž následují dvě akce OrbitalRotation, jedna na každé souvislé spinové polovině. Pass rozdělí globální obsazení na hranici sektorů a vydá dvě hradla PrepareSlaterDeterminant. Obě rotace se mohou objevit v libovolném pořadí.
Tento tvar vzniká v pracovním postupu lokálního unitárního klastrového Jastrowa (LUCJ) (viz průvodce LUCJ); uživatel umístí InitializeModes (typicky Hartreeho-Fockovu referenci pomocí from_hartree_fock()) na začátek obvodu a připojí ansatz UCJ, jehož první dvě orbitalové rotace podle spinu po rozložení ansatzu bezprostředně navazují na inicializaci.
>>> circuit = FermionicCircuit(6) # norb = 3, nelec = (2, 1)
>>> circuit.append(InitializeModes.from_hartree_fock(3, (2, 1)), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[3:])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Tento vzor se uplatní i tehdy, když je hradlem bezprostředně za inicializací rotována jen jedna spinová polovina, což je tvar vznikající, když na začátku obvodu náhodou stojí orbitalová rotace jen jednoho spinového sektoru. Rotovaná polovina se spolu se svou rotací stane PrepareSlaterDeterminant a druhá polovina se připraví s identitní rotací. Identitní příprava se syntetizuje na referenční hradla X, která by pro tuto polovinu stejně vydalo InitializeModes, takže doplnění nestojí žádná další hradla a přitom odemkne redukovanou Slaterovu syntézu na rotované polovině:
>>> circuit = FermionicCircuit(6)
>>> circuit.append(InitializeModes.from_hartree_fock(3, (2, 1)), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Co zůstane nedotčeno
Pass je záměrně konzervativní. Spojuje pouze tehdy, když se obě hradla skutečně skládají do přípravy Slaterova determinantu, a vše ostatní zkopíruje beze změny. „Bezprostředně následuje“ se chápe nad grafem toku dat obvodu. OrbitalRotation se spojí jen tehdy, když je InitializeModes jeho jediným předchůdcem napříč všemi jeho módy. Na obrázcích níže jsou stavy před a po shodné: nic se nespojilo.
Operace mezi nimi
Pokud se mezi inicializací a rotací dotkne módů jakékoli hradlo, už se neskládají do holé přípravy, takže se nic nesloučí:
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> circuit = FermionicCircuit(4)
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> number_op = FermionOperator.from_dict({(cre(0), ann(0)): 1.0})
>>> circuit.append(Evolution(4, number_op, time=0.5), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(4)), circuit.modes)
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Částečná rotace, která není spinovou polovinou
Rotace pokrývající přesně jednu souvislou spinovou polovinu inicializace celého registru se spojí (viz předchozí část). Rotace na jakékoli jiné částečné množině módů (méně módů než inicializace a ne jedna z jejích dvou spinových polovin, například podrozsah přesahující hranici sektorů) však není přípravou po sektorech. Jeho sloučení by potichu zahodilo roli inicializace na módech, které vynechává, a proto pass nechá obě hradla na místě:
>>> circuit = FermionicCircuit(4) # norb = 2: the only spin halves are modes [0, 1] and [2, 3]
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
Proč na tom záleží: přínos syntézy
Sloučení se vyplatí, protože PrepareSlaterDeterminant se syntetizuje na mnohem méně hradel než OrbitalRotation, které pohlcuje. Determinant s elektrony nad orbitaly potřebuje nejvýše dvouqubitových rotací a žádná diagonální fázová hradla, oproti rotacím plus fázím úplné čtvercové orbitalové rotace.
Předvolený pass manager pro Jordanovu-Wignerovu transformaci spouští MergeSlaterDeterminantPreparation ve své optimalizační fázi, takže k této redukci dochází automaticky. Transpiluj přípravu dvou elektronů v šesti orbitalech až na QuantumCircuit a spočítej jeho hradla:
Předvolba spouští MergeOrbitalRotations bezprostředně před MergeSlaterDeterminantPreparation. To je důležité, když za inicializací následuje řada po sobě jdoucích hradel OrbitalRotation. Dřívější pass nejprve sloučí řadu do jediné rotace, kterou pak MergeSlaterDeterminantPreparation smrští s inicializací do jediného PrepareSlaterDeterminant. Bez tohoto pořadí by se pohltila jen první rotace řady a zbytek by se syntetizoval úplným čtvercovým rozkladem.
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> # a reproducible random 6 x 6 orbital rotation
>>> rng = np.random.default_rng(1)
>>> z = rng.standard_normal((6, 6)) + 1j * rng.standard_normal((6, 6))
>>> q, r = np.linalg.qr(z)
>>> rotation = q * (np.diagonal(r) / np.abs(np.diagonal(r)))
>>>
>>> circuit = FermionicCircuit(6)
>>> circuit.append(InitializeModes([1, 1, 0, 0, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(rotation), circuit.modes)
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> prepared = pm.run(circuit)
>>> print(dict(sorted(prepared.count_ops().items())))
{'x': 2, 'xx_plus_yy': 8}
>>> prepared.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Porovnej to se syntézou téže orbitalové rotace samotné. Bez předcházející inicializace není co sloučit, takže předvolba sáhne po úplném čtvercovém rozkladu, který je hlubší a nese diagonální fázová hradla:
>>> reference = FermionicCircuit(6)
>>> reference.append(OrbitalRotation(rotation), reference.modes)
>>> reference_prepared = pm.run(reference)
>>> print(dict(sorted(reference_prepared.count_ops().items())))
{'rz': 6, 'xx_plus_yy': 15}
>>> reference_prepared.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

Redukovaný rozklad vypouští všech šest fázových hradel a téměř o polovinu snižuje počet dvouqubitových rotací. Připravený determinant je správný až na globální fázi (fyzikálně pro přípravu stavu nepodstatnou), kterou by fázová hradla opravila.
Dva způsoby, jak hradlo rozložit
PrepareSlaterDeterminant lze rozložit dvěma různými způsoby a nejsou zaměnitelné:
decompose()
Rozloží hradlo na jeho definici: InitializeModes reference, za nímž následuje úplné OrbitalRotation. To je ekvivalentní hradlu jako operátoru, pro libovolný vstupní stav.
PrepareSlaterDeterminant.synthesis, aplikovaná pomocí F2FSynthesis
Nahradí hradlo redukovaným rozkladem z tohoto průvodce. To je ekvivalentní hradlu pouze jako příprava stavu: vytvoří stejný determinant, až na globální fázi, když módy začínají ve vakuu. Je levnější právě proto, že to předpokládá.
Syntézu použij, když hradlo připravuje stav na vakuu, k čemuž hradlo slouží a co za tebe dělá transpiler. decompose() použij, když hradlo musí nadále působit jako operátor, například proto, že před ním na stejných módech stojí jiná hradla, nebo když chceš prostá hradla prozkoumat či simulovat. Pass F2FSynthesis nemůže vědět, o který případ jde, takže je na obvodu, který mu předáš, aby determinant připravil z vakua.
Simulace obvodu pomocí ffsim nepotřebuje ani jedno: každé hradlo aplikuje celý svůj unitární operátor, takže obvod obsahující PrepareSlaterDeterminant se simuluje tak, jak je, a zůstává na rychlé cestě pro okolní hradla OrbitalRotation. Rozložení pouze Slaterova hradla pomocí circuit.decompose("PrepareSlaterDeterminant") také ponechává tyto rotace celé.
PrepareSlaterDeterminant, InitializeModes, OrbitalRotation, MergeOrbitalRotations, F2FSynthesis, FermionicSlaterGivensDecomposition a průvodce LUCJ pro pracovní postup, který vytváří vzor globální inicializace.
