Práce s fermionickými obvody
Koncepty v tomto průvodci jsou aktuálně dostupné pouze v Python API. Ekvivalentní funkcionalita bude v C API zpřístupněna v budoucím vydání.
Tento průvodce vysvětluje, jak použít FermionicCircuit k implementaci kvantových algoritmů přímo ve fermionickém prostoru, místo aby se nejdříve mapovalo na qubity.
Proč používat fermionické obvody
Uvažuj tradiční postup implementace časového vývoje fermionického hamiltoniánu na architektuře založené na qubitech:
-
Definuj fermionický hamiltonián (například ).
-
Namapuj ho na qubitový operátor (například pomocí Jordanova-Wignerova mapování).
-
Implementuj časový vývoj v qubitovém prostoru (pomocí trotterizace nebo jiných metod).
Tento přístup má zásadní omezení: brzké mapování na qubity zahazuje znalosti o fermionické struktuře problému. Standardní optimalizační průchody nemohou využít fermionické symetrie ani povolená přeuspořádání členů, což vede k suboptimálním obvodům s zbytečnou hloubkou a počtem hradel.
FermionicCircuit umožňuje lepší postup:
-
Definuj fermionický hamiltonián (například ).
-
Implementuj časový vývoj přímo ve fermionickém prostoru pomocí fermionických hradel.
-
Namapuj na qubitový prostor jako součást procesu transpilace.
Tento přístup omezení řeší tím, že znalosti o problému zůstávají ve fermionickém prostoru, kde optimalizační průchody mohou využít fermionickou strukturu a komutační relace ke snížení hloubky obvodu. Fermionický obvod popisuje, jaký výpočet provést, zatímco transpilace řeší, jak ho namapovat na qubity, což zajišťuje čistší oddělení zodpovědností.
Obecné indexování módů
Modul operators i FermionicCircuit používají obecné indexování založené na módech, které nepředpokládá nic o povaze módů. Mód je pouze abstraktní index označující fermionický stupeň volnosti, bez vlastní sémantiky. Toto návrhové rozhodnutí odpovídá modulu operátorů a zajišťuje maximální flexibilitu. Například můžeš implementovat časový vývoj pro libovolný operátor implementující protokol OperatorTrait, bez ohledu na jeho matematickou reprezentaci.
Sestavení fermionického obvodu
Příklad níže implementuje časový vývoj popsaný v předchozí části. Sestaví FermionicCircuit zadáním počtu fermionických módů a poté přidá fermionická hradla z qiskit_fermions.circuit.library, která časový vývoj implementují.
Příklad také ukazuje, jak do operátoru zahrnout znalosti o dané oblasti pomocí seskupování členů operátoru. Přiřazením indexů skupin členům hamiltoniánu zachováš strukturní informace, které mohou optimalizační průchody využít v celém transpilačním zásobníku.
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Create a circuit with 4 fermionic modes
>>> circuit = FermionicCircuit(4)
>>>
>>> # Define a simple fermionic Hamiltonian
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(2)], 0.5),
... ([cre(2), ann(0)], 0.5),
... ([cre(1), ann(3)], 0.5),
... ([cre(3), ann(1)], 0.5),
... ])
>>> # Add some problem structure by grouping our Hamiltonian terms
>>> hamiltonian.groups = [0, 0, 1, 1]
>>>
>>> # Add an evolution gate to implement exp(-i * t * H)
>>> evolution = Evolution(4, hamiltonian, time=1.0)
>>> circuit.append(evolution, circuit.register)
Všimni si, že seskupení členů operátoru se zachovává i při jednoduchých operacích, jako je dekompozice. To ukazuje, jak strukturní informace procházejí zásobníkem obvodu. Úplnou transpilaci na qubity najdeš v průvodci Transpilace fermionických obvodů.
Oddělení hradel pomocí bariéry
Optimalizační průchody slučují sousední hradla, kdykoli to jde. Například MergeOrbitalRotations spojí řadu po sobě jdoucích hradel OrbitalRotation do jednoho a MergeSlaterDeterminantPreparation zapracuje InitializeModes do rotace, která po něm následuje.
Použij FermionicCircuit.barrier(), když tomu chceš zabránit. Bariéra nemá žádný unitární účinek, takže nemění stav, který tvůj obvod připravuje; pouze označuje místo, přes které průchody hradla nesloučí. Zavolej ji bez argumentů, aby zahrnula všechny módy, nebo jí předej módy, na které ji chceš omezit:
>>> from qiskit_fermions.circuit.library import OrbitalRotation
>>> import numpy as np
>>>
>>> rotations = FermionicCircuit(4)
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
>>> rotations.barrier()
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
Bariéry přežijí transpilaci z fermionů na qubity, takže omezují i následnou optimalizaci na úrovni qubitů. Tuto fázi popisuje průvodce Transpilace fermionických obvodů.
Opakování obvodu
Mnoho algoritmů aplikuje stejný blok hradel mnohokrát. Typickým případem je časový vývoj: Trotterův krok se sestaví jednou a poté se opakuje, dokud se nedosáhne cílového času. Použij repeat() k sestavení opakovaného obvodu a compose() k jeho připojení k předponě s přípravou stavu.
Příklad níže sestavuje Trotterův krok druhého řádu pro Fermiho-Hubbardův model. Krok je symetrický: rozdělí člen přeskoku na dvě hradla OrbitalRotation s poloviční dobou trvání, která obklopují interakci na místě.
>>> import scipy.linalg
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>>
>>> norb, reps, time = 2, 3, 1.0
>>> num_modes = 2 * norb
>>> dt = time / reps
>>>
>>> hopping = np.zeros((norb, norb))
>>> hopping[0, 1] = hopping[1, 0] = -1.0
>>> interaction = FermionOperator.from_dict(
... {(cre(p), ann(p), cre(p + norb), ann(p + norb)): 2.0 for p in range(norb)}
... )
>>>
>>> def rotation(duration):
... propagator = scipy.linalg.expm(-1j * duration * hopping)
... # the same rotation acts on the alpha and beta halves of the register
... return scipy.linalg.block_diag(propagator, propagator)
>>>
>>> trotter_step = FermionicCircuit(num_modes)
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
>>> trotter_step.append(
... Evolution(num_modes, interaction, dt, atomic=True), trotter_step.modes
... )
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
Opakováním kroku a jeho připojením k referenčnímu stavu Hartreeho-Focka získáš celý vývoj:
>>> evolution = FermionicCircuit(num_modes)
>>> evolution.append(InitializeModes([True, False, True, False]), evolution.modes)
>>> evolution.compose(trotter_step.repeat(reps), inplace=True)
>>> evolution.count_ops()["OrbitalRotation"]
6
Opakování se rozbalí do vráceného obvodu, místo aby byla zabalena do jediného hradla, takže count_ops() napočítá dvě rotace na krok. Právě toto rozbalení činí redundanci na hranicích kroků viditelnou pro transpilátor: koncová rotace s poloviční dobou trvání jednoho kroku a úvodní rotace dalšího kroku spolu sousedí, takže je MergeOrbitalRotations spojí do jediné rotace.
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4
Výsledek stojí jednu rotaci na krok plus jednu, což je přesně to, co získáš ručním sloučením těchto polovičních kroků. Zapsání kroku v jeho přirozené symetrické podobě a jeho opakování vytvoří stejný obvod, takže sloučení se přenechá optimalizační fázi.
Když potřebuješ opakování udržet oddělená, předej insert_barriers=True, čímž mezi ně vložíš barrier(). Optimalizační průchody hradla přes bariéru nesloučí, takže každá rotace zůstane zachována:
>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6
Argument reps třídy FermionicSuzukiTrotter je odlišný koncept. Rozdělí jedno hradlo Evolution na více kratších činitelů, zatímco repeat() duplikuje celé obvody. Opakování obvodu, který obsahuje jediné Evolution, pouze znásobí jeho celkový čas vývoje.
Měření fermionických módů
measure_all() přečte každý mód do vlastního klasického bitu, přičemž páruje mód i s bitem i. Použij measure() s vlastním ClassicalRegister, pokud chceš přečíst jen některé z nich.
>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4
Předání inplace=False vrátí změřenou kopii a původní obvod ponechá beze změny, na čemž záleží, protože měření není unitární: obvod, který ho obsahuje, už nelze aplikovat na stavový vektor, takže si nech nezměřený obvod, pokud ho chceš také simulovat.
Měření zde, a ne na transpilovaném QuantumCircuit, zajišťuje, že vzorkované bitové řetězce zůstanou snadno čitelné. Fermionické měření pojmenovává mód, který čte, takže každá fáze, která módy přesouvá (RelabelModes na fermionické úrovni, rozložení a směrování na úrovni qubitů), přenáší měření s sebou, a bitový řetězec zůstává indexován podle módu, na který ses ptal.
Na rozdíl od measure_all() se před měření nevkládá žádná barrier(). Pokud ji tam chceš, zavolej barrier() nejdříve sám.
Zda změřený bit znamená to, co očekáváš, závisí na kódování fermionů na qubity, které se volí během transpilace, nikoli zde. Při kódování v okupační bázi, jako je Jordanovo-Wignerovo, je každý mód nesen jedním qubitem a bit je okupace tohoto módu; viz FermionicMeasure.
Transpilace fermionických obvodů
Aby bylo možné implementovat kvantový algoritmus reprezentovaný tvým FermionicCircuit, musí být transpilován na QuantumCircuit. Jak na to, se dozvíš v průvodci Transpilace fermionických obvodů.
Další kroky
-
Přečti si o dostupných fermionických hradlech v dokumentaci
qiskit_fermions.circuit.library. -
Prozkoumej vysvětlující průvodce operátory, abys pochopil, jak sestavit fermionické hamiltoniány, které můžeš použít s fermionickými obvody.
-
Projdi si úvodního průvodce SQDRIFT s praktickým příkladem od začátku do konce.
-
Přečti si průvodce vztahem k ffsim, abys pochopil, jak lze
FermionicCircuitsimulovat přímo a jak tyto obecné indexy módů nabývají spinové sémantiky až v okamžiku simulace.

