Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Transpilace ansatzu LUCJ

Důležité

Koncepty v tomto průvodci jsou dostupné pouze v Python API.

Ansatz lokálního unitárního klastrového Jastrowa (LUCJ) je kompaktní, na hardware efektivní parametrizace korelované elektronové vlnové funkce. Je členem obecnější rodiny unitárního klastrového Jastrowa (UCJ) a má tvar

∣Ψ⟩=(∏k=1LUk eiJk Uk†)∣Φ0⟩,\lvert \Psi \rangle = \left(\prod_{k=1}^{L} \mathcal{U}_k\, e^{i \mathcal{J}_k}\, \mathcal{U}_k^\dagger\right) \lvert \Phi_0 \rangle,

kde ∣Φ0⟩\lvert \Phi_0 \rangle je referenční stav (typicky Hartreeho–Fockův), každé Uk\mathcal{U}_k je orbitální rotace a každé Jk\mathcal{J}_k je diagonální coulombovský operátor

J=12∑ij,στJijστ niσ njτ,\mathcal{J} = \frac12 \sum_{ij,\sigma\tau} \mathbf{J}^{\sigma\tau}_{ij}\, n_{i\sigma}\, n_{j\tau},

kde niσn_{i\sigma} je operátor počtu částic na prostorovém orbitalu ii se spinem σ\sigma.

ffsim sestaví ansatz operátor; tento balíček z něj vytvoří obvod a ten převede na qubity. Tento průvodce se věnuje druhé polovině té věty. Důvodem, proč tuto cestu podstoupit, je samotné převedení: vlastní hradla Qiskit v ffsim jsou specifická pro Jordanovu–Wignerovu transformaci, zatímco FermionicCircuit nepředpokládá žádné kódování fermionů na qubity, takže stejný ansatz lze syntetizovat libovolným kódováním, které vyhovuje cílovému zařízení.

Ansatz operátor tedy přichází hotový a vše níže je to, co se s ním děje poté: na co se hradlo rozkládá, jak se převádí přes Jordanovu–Wignerovu transformaci, jak se přizpůsobuje souvislosti zařízení a co by ještě potřeboval převod jiný než Jordanův–Wignerův. K samotnému sestavení, parametrizaci nebo simulaci ansatzu UCJ viz vlastní průvodce ffsim.

Viz také

Průvodce vztahem k ffsim o tom, jak si oba balíčky dělí práci, a průvodce transpilací o fázích řetězce použitých níže.

1. Získání operátoru LUCJ z ffsim​

Ansatz se inicializuje z amplitud výpočtu coupled-cluster se singlety a dubletami (CCSD). Spusť restrikovaný Hartreeho–Fockův výpočet a poté CCSD pro molekulu vodíku v bázi 6-31g s využitím PySCF pro kvantovou chemii a pak předej amplitudy funkci from_t_amplitudes() z ffsim. Ta provede dvojitou faktorizaci amplitud t2t_2, z níž získá diagonální coulombovské matice a orbitální rotace pro jednotlivé vrstvy, a z amplitud t1t_1 odvodí volitelnou závěrečnou orbitální rotaci. Počet opakování LL je to, co tato faktorizace poskytne; n_reps ho zkrátí, čímž vyměňuje přesnost za mělčí obvod.

>>> import ffsim
>>> import pyscf
>>> import pyscf.cc
>>>
>>> # build the molecule and run Hartree-Fock
>>> mol = pyscf.gto.Mole()
>>> mol.build(
... atom=[["H", (0, 0, 0)], ["H", (0, 0, 0.74)]],
... basis="6-31g",
... symmetry="Dooh",
... verbose=0,
... )
<pyscf.gto.mole.Mole object at ...>
>>> scf = pyscf.scf.RHF(mol).run()
>>>
>>> norb = scf.mo_coeff.shape[1]
>>> nelec = (mol.nelec[0], mol.nelec[1])
>>>
>>> # run CCSD for the t-amplitudes, then factorize them into an ansatz operator
>>> ccsd = pyscf.cc.CCSD(scf).run()
>>> t1, t2 = ccsd.t1, ccsd.t2
>>>
>>> ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(t2, t1=t1, n_reps=2)

Tady přestává být ansatz záležitostí ffsim. ffsim zde nabízí volby, které tento balíček nevidí a nepotřebuje o nich vědět, například variačně optimalizovanou („komprimovanou“) faktorizaci skrytou za optimize=True nebo balení vektoru parametrů, které řídí variační optimalizátor. Všechny produkují operátor stejného druhu a vše níže funguje beze změny na kterémkoli z nich.

2. Převod operátoru na fermionový obvod​

Hradlo UCJ obaluje operátor a vyjadřuje ho jako obvod nad fermionovými módy. Hradlo je čistě unitární a nenese žádnou vlastní referenci, proto na začátek přidej hradlo InitializeModes (sestavené pomocí from_hartree_fock()), které dodá Hartreeho–Fockovu referenci, na niž se ansatz aplikuje.

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import InitializeModes, UCJ
>>>
>>> ansatz = UCJ(ucj_op)
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(InitializeModes.from_hartree_fock(norb, nelec), circuit.modes)
>>> circuit.append(ansatz, circuit.modes)

Rozložení obvodu odhalí jeho anatomii a toto je reprezentace, nad kterou pracuje zbytek průvodce. Hradlo InitializeModes připraví referenční determinant a každá vrstva ansatzu přispívá hradlem OrbitalRotation Uk†\mathcal{U}_k^\dagger, poté eiJke^{i\mathcal{J}_k} (hradlo Evolution diagonálního coulombovského operátoru Jk\mathcal{J}_k), poté Uk\mathcal{U}_k, a na konci následuje závěrečné OrbitalRotation. Orbitální rotace působí zvlášť pro každý spinový sektor, takže každá je umístěna nezávisle na módy alfa 0..norb a módy beta norb..2*norb.

>>> circuit.decompose().draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

The gates that the UCJ ansatz decomposes into.

Každé hradlo v tomto rozkladu je stále fermionové: OrbitalRotation a Evolution jsou definovány na módech a zatím se neobjevil žádný qubit ani Pauliho operátor. Právě to nechává kódování otevřené a v tomto stavu ansatz zůstává, dokud jedno nevybere fáze syntézy.

Poznámka

Každá vrstva končí Uk\mathcal{U}_k a další začíná Uk+1†\mathcal{U}_{k+1}^\dagger, takže sousední hradla OrbitalRotation by bylo možné sloučit do jediné rotace. Transpilační průchod, který toto sloučení provádí, je plánovaným budoucím vývojem.

3. Výběr kódování fermionů na qubity​

Obvod je stále fermionový a právě v tomto bodě si tento balíček ve workflow zasluhuje své místo. FermionicCircuit nepředpokládá nic o tom, jak se módy stanou qubity, takže kódování je volba fáze syntézy, nikoli něco zabudovaného do ansatzu. To je to, co tento balíček přidává oproti vlastním hradlům Qiskit v ffsim, která jsou specifická pro Jordanovu–Wignerovu transformaci.

Pro samotnou Jordanovu–Wignerovu transformaci by ses této oklice nemusel(a) účastnit: ffsim obsahuje UCJOpSpinBalancedJW a jeho sourozence, které samy převedou operátor UCJ na qubitový obvod. Důvodem, proč ansatz vést přes fermionový obvod, je všechno ostatní, co ti kódování může přinést. Lokální kódování například vynaloží další qubity na omezení Pauliho váhy každého členu, čehož využívají průvodci 1D a 2D množinami toku, aby dvouqubitová hloubka jednoho Trotterova kroku nezávisela na velikosti systému.

Pro ansatz UCJ konkrétně musí fáze syntézy pokrýt dva druhy hradel:

  • Diagonální coulombovský vývoj. Evolution je již nezávislé na kódování: MapperFnEvolutionSynthesis přijímá mapovací funkci, takže vrstvy eiJke^{i \mathcal{J}_k} se převedou přes jakékoli kódování, které lze takto vyjádřit. Průvodci množinami toku ukazují, jak ji napsat.

  • Orbitální rotace a příprava Slaterova determinantu. Ani OrbitalRotation, ani PrepareSlaterDeterminant, do něhož se úvodní InitializeModes a rotace sloučí (viz níže), se na qubity jako takové nemapuje. Průchod F2FSynthesis je nejprve rozloží, stále ve fermionovém prostoru, na hradla Evolution na jednom nebo dvou módech (a u přípravy také na InitializeModes reference), takže je zvládne jakékoli kódování, které umí zmapovat Evolution. Při Jordanově–Wignerově transformaci se faktory mezi sousedními módy stanou na qubitové úrovni objekty XXPlusYYGateobjects.

  • Inicializace. Plugin pro InitializeModes není parametrizován kódováním: dokumentuje svůj předpoklad kódování v bázi obsazení s mapováním módů na qubity 1 : 1 a vysílá hradla přímo v této bázi. Jiné kódování pro ni potřebuje vlastní plugin, a žádný zatím není dodáván.

Jak transpilovat obvody UCJ při kódování jiném než Jordanově–Wignerově, je otevřená výzkumná otázka a tento balíček poskytuje rámec, v němž lze takový řetězec vyvinout. Zbytek tohoto průvodce jako příklad sestavuje transpilační řetězec pro Jordanovo–Wignerovo kódování, ekvivalentní tomu, který vytváří generate_preset_jw_pass_manager().

4. Převod přes Jordanovu–Wignerovu transformaci​

Převod probíhá v F2QSynthesis, které funguje podobně jako HighLevelSynthesis v Qiskit: prochází fermionové instrukce a každou předá pluginu registrovanému pro název daného hradla. Jeho atribut F2QSynthesis.methods obsahuje jeden F2QSynthesisPlugin na název hradla, takže sestavení této fáze znamená zvolit plugin pro každé fermionové hradlo, které obvod obsahuje:

>>> from qiskit_fermions.mappers.library import jordan_wigner
>>> from qiskit_fermions.transpiler.passes import F2QSynthesis
>>> from qiskit_fermions.transpiler.passes.synthesis import (
... MapperFnEvolutionSynthesis,
... TrivialOccupationInitializeModesSynthesis,
... )
>>>
>>> synthesis = F2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(jordan_wigner)
>>> synthesis.methods["InitializeModes"] = TrivialOccupationInitializeModesSynthesis()

Přiřazování instancí tímto způsobem je také cesta, jak do řetězce vložit vlastní plugin, aniž bys ho musel(a) nejdřív někde registrovat.

Tady se kódování určí dvěma způsoby popsanými v části 3: explicitně, předáním jordan_wigner() do MapperFnEvolutionSynthesis, a implicitně, tím, že ho už předpokládá druhý plugin.

Tato fáze se pak zasadí do MultiStagePassManager, jehož fáze běží v pevném pořadí:

>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit.transpiler import generate_preset_pass_manager
>>> from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
>>>
>>> from qiskit_fermions.transpiler import (
... FermionicCircuitToDAG,
... FermionicPassManager,
... QuantumDAGToCircuit,
... )
>>> from qiskit_fermions.transpiler.passes import (
... F2FSynthesis,
... HopEvolutionSynthesis,
... MergeOrbitalRotations,
... MergeSlaterDeterminantPreparation,
... TrivialF2QLayout,
... )
>>>
>>> pm = MultiStagePassManager(
... input=FermionicCircuitToDAG(),
... optimization=FermionicPassManager(
... [
... MergeOrbitalRotations(),
... MergeSlaterDeterminantPreparation(),
... F2FSynthesis(
... {"OrbitalRotation": "Givens", "PrepareSlaterDeterminant": "Givens"}
... ),
... ]
... ),
... layout=FermionicPassManager(TrivialF2QLayout()),
... synthesis=synthesis,
... qubit=generate_preset_pass_manager(
... hls_config=HLSConfig(PauliEvolution=[HopEvolutionSynthesis(), "default"])
... ),
... output=QuantumDAGToCircuit(),
... )

Fáze optimization běží před syntézou, na fermionovém obvodu, což ji činí užitečnou: přepisuje hradla, dokud jsou stále fermionová a nezávislá na kódování, takže úspory se přenesou do jakéhokoli kódování, které následuje. MergeOrbitalRotations sloučí každou řadu po sobě jdoucích hradel OrbitalRotation do jednoho, což je pro ansatz UCJ důležité, protože každá vrstva končí Uk\mathcal{U}_k a další začíná Uk+1†\mathcal{U}_{k+1}^\dagger. MergeSlaterDeterminantPreparation pak sloučí úvodní InitializeModes a rotaci, která po něm následuje, do jediného PrepareSlaterDeterminant, které syntéza převede levnějším redukovaným Slaterovým rozkladem místo plné čtvercové rotace. Na pořadí záleží: sloučením rotací nejprve vznikne jediná rotace bezprostředně po inicializaci, kterou Slaterovo sloučení do ní pak pohltí. F2FSynthesis přichází jako poslední, protože rozbíjí jakoukoli zbylou rotaci, a kdyby proběhl dříve, oba slučovací průchody by neměly co sloučit.

Zbývající fáze jsou technické. input a output převádějí mezi obvody a DAG, layout přiřazuje fermionové módy qubitům (TrivialF2QLayout mapuje mód i na qubit i) a qubit je běžný pass manager Qiskit, který převezme řízení, jakmile nezbude nic fermionového.

Spuštění vyžaduje jeden přípravný krok: složené hradlo UCJ je třeba nejprve rozložit na primitivní hradla, aby fáze optimalizace viděla jednotlivé rotace a vývoje.

>>> transpiled = pm.run(circuit.decompose())
>>> print(dict(sorted(transpiled.count_ops().items())))
{'rz': 14, 'rzz': 12, 'x': 2, 'xx_plus_yy': 28}

Bez cílového zařízení se obvod mapuje na 2 * norb qubitů s předpokladem plné propojenosti; orbitální rotace se syntetizují na objekty XXPlusYYGateobjects a diagonální coulombovské vývoje na objekty RZZGateobjects:

>>> transpiled.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

The Jordan-Wigner transpiled LUCJ circuit.

Tento řetězec je přesně to, co sestavuje generate_preset_jw_pass_manager(), takže v praxi sáhni po presetu a fáze sestavuj ručně, když potřebuješ některou z nich změnit:

>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> preset = generate_preset_jw_pass_manager()
>>> preset.run(circuit.decompose()).count_ops() == transpiled.count_ops()
True

5. Přizpůsobení topologii zařízení​

Skutečné zařízení má omezenou souvislost qubitů (coupling map) a ansatz LUCJ je navržen tak, aby jí odpovídal. Interakce se stejným spinem (pairs_aa) tvoří dva lineární řetězce a interakce alfa-beta (pairs_ab) je propojují. Funkce generate_lucj_pass_manager() z ffsim pro tuto strukturu sestaví qubitový řetězec respektující zařízení a vrátí podmnožinu pairs_ab, kterou hardware skutečně dokáže pojmout. Tento řetězec zasaď do fáze qubit presetu a zachovej přitom vlastní syntézu fermionů na qubity z tohoto balíčku:

>>> from ffsim.qiskit import generate_lucj_pass_manager
>>> from qiskit.providers.fake_provider import GenericBackendV2
>>> from qiskit.transpiler import CouplingMap
>>>
>>> # a heavy-hex device coupling map (any BackendV2 works, e.g. a real fake_provider backend)
>>> coupling_map = CouplingMap.from_heavy_hex(5)
>>> backend = GenericBackendV2(
... num_qubits=coupling_map.size(),
... basis_gates=["cp", "xx_plus_yy", "p", "x", "swap"],
... coupling_map=coupling_map,
... )
>>>
>>> # nearest-neighbor same-spin chain; let the pass manager choose the alpha-beta pairs
>>> pairs_aa = [(p, p + 1) for p in range(norb - 1)]
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> hls_config = HLSConfig(PauliEvolution=[HopEvolutionSynthesis(), "default"])
>>> pm.qubit, allowed_pairs_ab = generate_lucj_pass_manager(
... backend,
... norb,
... "heavy-hex",
... (pairs_aa, None),
... optimization_level=3,
... seed_transpiler=0,
... hls_config=hls_config,
... )
>>>
>>> # the alpha-beta interactions the heavy-hex connectivity can implement
>>> print(allowed_pairs_ab)
[(0, 0)]

Právě ta kompozice je smyslem: fáze qubit respektuje zařízení, zatímco fáze syntézy fermionů na qubity před ní zůstává vlastní tomuto balíčku, a tedy zaměnitelná. Nahrazením fáze qubit se zahodí to, co tam preset registruje, tedy HopEvolutionSynthesis, který převádí Givensovy faktory orbitální rotace na objekty XXPlusYYGateobjects. Předej ho dál přes hls_config jako výše, jinak je třeba bázi zařízení dosáhnout bez něj.

Když je nyní pm.qubit nastaveno na řetězec respektující zařízení, spuštění pass manageru rozmístí obvod na qubity backendu a nasměruje ho podle coupling map. U neomezeného ansatzu z kroku 1, jehož diagonální coulombovský operátor stále obsahuje členy alfa-beta, které hardware nedosáhne přímo, musí směrovač vložit mnoho hradel SWAP, aby je překlenul:

>>> naive = pm.run(circuit.decompose())
>>> naive.num_qubits # laid out on the full heavy-hex device register
57
>>> naive_swaps = naive.count_ops()["swap"]
>>> naive_swaps # many SWAPs to bridge the unreachable alpha-beta interactions
33

Omezení ansatzu na interakce implementovatelné na hardwaru

Řešením je vrátit allowed_pairs_ab zpět do sestavení ansatzu prostřednictvím argumentu interaction_pairs funkce from_t_amplitudes(), takže diagonální coulombovský operátor obsahuje jen členy alfa-beta, které coupling map umí implementovat přímo. Ansatz pak odpovídá topologii zařízení a směrovač se ho sotva musí dotknout:

>>> restricted = UCJ(
... ffsim.UCJOpSpinBalanced.from_t_amplitudes(
... t2, t1=t1, n_reps=2, interaction_pairs=(pairs_aa, allowed_pairs_ab)
... )
... )
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(InitializeModes.from_hartree_fock(norb, nelec), circuit.modes)
>>> circuit.append(restricted, circuit.modes)
>>>
>>> transpiled = pm.run(circuit.decompose())
>>> restricted_swaps = transpiled.count_ops()["swap"]
>>> restricted_swaps # far fewer routing SWAPs than the unrestricted ansatz
4

Vykreslení pouze aktivních qubitů (idle_wires=False) ukazuje obvod omezený na dva spinové řetězce a most alfa-beta, vyjádřený v bázových hradlech zařízení. Rozmístění a směrování rozptýlí logické módy po fyzických qubitech zařízení, proto předej wire_order převzatý z konečného rozmístění obvodu (final_index_layout() uvádí fyzický qubit, na kterém každý vstupní qubit skončil, v pořadí vstupních qubitů), aby se vodiče vykreslily zpět v původním pořadí módů:

>>> wire_order = transpiled.layout.final_index_layout(filter_ancillas=False)
>>> transpiled.draw("mpl", idle_wires=False, fold=-1, wire_order=wire_order)
<Figure size ... with 1 Axes>

The hardware-restricted LUCJ circuit routed onto the heavy-hex device coupling map.

Poznámka

Přesné počty hradel a hloubka po rozmístění závisejí na průchodech směrování/optimalizace a zvoleném zařízení, proto je zde neuvádíme. Klíčová je společná tvorba (co-design); vyjádření ansatzu pomocí řetězce pairs_aa mezi nejbližšími sousedy a mostů pairs_ab filtrovaných podle hardwaru udržuje syntetizovaný obvod blízko topologii zařízení a minimalizuje režii směrování (vložená hradla SWAP). Související volbu syntézy (minimize_2q_gate_count), která vyměňuje počet dvouqubitových hradel za směrovanou hloubku, najdeš v FermionicSlaterGivensDecomposition.

Další kroky​