Lumaktaw sa pangunahing nilalaman
Ang proyektong ito ay nasa beta. Nakakita ng bug? Mag-bukas ng GitHub issue. May tanong o ideya? Magtanong sa Discussions — gusto naming marinig ang iyong feedback!
Gaano kaganda ang kalidad ng pagsasalin sa pahinang ito?

Mag-synthesize ng Evolution gate sa fermionic space

Mahalaga

Ang mga konsepto sa gabay na ito ay kasalukuyang available lang sa Python API. Magiging available ang katumbas na functionality sa C API sa isang susunod na release.

Dala ng isang Evolution na gate ang time evolution na e−itHe^{-itH} ng buong operator. Isinusulat muli ng fermion-to-fermion synthesis ang gate na iyon bilang isang FermionicCircuit ng mas maliliit na fermionic gate. Parehong nananatili sa fermionic space ang magkabilang panig ng pagsusulat muli, na siyang nagpapaiba rito sa fermion-to-qubit synthesis na kasunod: nananatili rito ang mga groups ng operator (tingnan ang Group operator terms: use the operator structure), samantalang ang fermion-to-qubit mapping ay nagbabalik ng SparseObservable, na walang konsepto ng grouping.

Dalawang setting ang pumipili ng pamamaraan. Itinatakda ito ng Evolution.synthesis sa isang gate, at itinatakda naman ito ng F2FSynthesis na transpiler pass sa buong circuit. Parehong tumatanggap ng F2FSynthesisPlugin, ang interface na ipinapatupad ng anumang ganitong pagsusulat muli. Ang mga pamamaraang kasalukuyang inihahandog ng package na ito para sa isang evolution ay mga product formula, na siyang ginagamit sa natitirang bahagi ng gabay na ito: FermionicLieTrotter para sa first-order na formula (ang default) at FermionicSuzukiTrotter para sa mas mataas na even order. Pinipili sa parehong paraan ang anumang iba pang nagpapatupad ng interface, at nalalapat nang walang pagbabago sa kanila ang mga seksyon tungkol sa dalawang setting.

Tandaan

Opsyonal ang fermion-to-fermion na hakbang na ito. Maaaring dumiretso ang isang Evolution na gate sa fermion-to-qubit na yugto, na nagma-map at nagsi-synthesize nito nang buo gaano man karami ang laman nitong term. Ang paghahati muna rito ay isang pagpili, na ginagawa dahil mas mura ang bawat nagreresultang gate o dahil inilalantad ng paghahati ang estrukturang magagamit ng susunod na yugto.

Nagko-compose ang dalawang hakbang, kaya ang isang evolution ay maaaring ma-approximate sa alinmang antas, sa pareho, o sa wala. Nag-a-approximate ang isang product formula maliban kung nagko-commute ang mga factor nito; hindi kailangang mag-approximate ang isang synthesis method.

Tandaan

Hindi kailangang mangahulugan ang pagpapalampas sa hakbang na ito ng ganap na pagkawala ng grouping. Kapag binalot ang mapper sa group_wise(), imamapa nito ang operator isang group sa bawat pagkakataon at isusuma ang mga resulta, kaya dumarating nang naka-group ang mga na-map na Pauli term imbes na magkakahalo. Sinisynthesize ng qubit-side na product formula ang mga ito ayon sa pagkakasunod na natanggap nito, kaya ang mga nagko-commute na term na nagkataong magkatabi ay maaaring i-schedule nang sabay.

Sa Hamiltonian sa ibaba, nababawi nito ang buong benepisyo sa two-qubit depth ng paghahati: 18 imbes na 30, na hindi nagbabago ang bilang ng gate. Ang pagkakaiba ay nasa kung saan ipinapahayag ang estruktura. Ang paghahati sa fermionic space ay ginagawang hiwalay na gate ang bawat group, na nakikita at magagamit ng bawat susunod na yugto; ang group_wise ay nag-iiwan ng isang gate at inaayos lang ang pagkakasunod ng mga term sa loob nito, na sapat para sa scheduling ngunit walang iniiwan para pagtrabahuhan ng isang fermionic pass. Gamitin ito kapag kailangang manatiling iisang gate ang evolution, at gumamit ng fermionic product formula sa iba pang kaso.

Bumuo ng grouped na Hamiltonian​

Parehong hinahati ng dalawang product formula ang isang operator sa mga factor, at para sa isang grouped na operator, ang mga group nito ang mga factor na iyon. Kaya ang grouping ang nagtatakda ng parehong gastos at katumpakan ng resulta. Kumuha ng anim-na-site na Fermi-Hubbard chain, na naka-group ayon sa pagko-commute ng mga term nito: ang mga hopping term sa even bond, ang mga hopping term sa odd bond, at ang diagonal na interaksyon.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> def fermi_hubbard_chain(num_sites, tunneling, interaction):
... """A 1D Fermi-Hubbard chain grouped into even bonds, odd bonds, and the interaction."""
... terms, groups = [], []
... for offset in (0, 1):
... for j in range(offset, num_sites - 1, 2):
... terms += [
... ([cre(j), ann(j + 1)], -tunneling),
... ([cre(j + 1), ann(j)], -tunneling),
... ]
... groups += [offset, offset]
... for j in range(num_sites - 1):
... terms.append(([cre(j), ann(j), cre(j + 1), ann(j + 1)], interaction))
... groups.append(2)
... operator = FermionOperator.from_terms(terms)
... operator.groups = groups
... return operator
>>>
>>> num_sites = 6
>>> hamiltonian = fermi_hubbard_chain(num_sites, tunneling=1.0, interaction=2.0)
>>> hamiltonian.num_groups()
3

Labinlimang term sa tatlong group. Ipinapares ng bawat hopping group ang bawat term sa conjugate partner nito, na mahalaga: kailangang Hermitian ang isang factor para maging unitary ang exponential nito, at hindi iyon awtomatikong sumusunod sa pagiging Hermitian ng kabuuan. Sinusuri ng groups_are_hermitian() ang convention na iyon imbes na iasa sa inspeksyon:

>>> from qiskit_fermions.operators.terms.grouping import groups_are_hermitian
>>>
>>> groups_are_hermitian(hamiltonian)
[True, True, True]

Masisira ito kapag pinaghiwalay ang a0†a1a^\dagger_0 a_1 mula sa partner nitong a1†a0a^\dagger_1 a_0, at ganoon din ang epekto ng pag-iwan sa operator na hindi naka-group, dahil hahatiin ito ng parehong formula term por term at hindi Hermitian ang nag-iisang ai†aja^\dagger_i a_j:

>>> ungrouped = fermi_hubbard_chain(num_sites, tunneling=1.0, interaction=2.0)
>>> ungrouped.groups = range(len(list(ungrouped.iter_terms()))) # one group per term
>>> all(groups_are_hermitian(ungrouped))
False

Ang ganitong factor ay isang balidong fermionic operator, at bubuuin ng package na ito ang gate para rito, kaya walang magrereklamo hanggang sa umabot ang na-map na operator sa PauliEvolutionGate, na nangangailangan ng mga real na coefficient at nagtataas ng ValueError: Operator contains complex coefficients, which are not supported. Ang pagsuri sa grouping nang maaga ay ginagawang sagot na makukuha bago pa buuin ang anumang circuit ang huling pagkabigong iyon.

Itakda ang pamamaraan sa isang gate​

Magpasa ng synthesis method sa gate, pagkatapos ay i-decompose ito. Bawat gate na ibinubuga nito ay bumabalik bilang sarili nitong Evolution na gate:

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import (
... FermionicLieTrotter,
... FermionicSuzukiTrotter,
... )
>>>
>>> total_time = 1.0
>>>
>>> def factors(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, hamiltonian, time=total_time, synthesis=synthesis),
... circuit.modes,
... )
... return circuit.decompose().count_ops()["Evolution"]
>>>
>>> factors(FermionicLieTrotter())
3
>>> factors(FermionicSuzukiTrotter(order=2))
5
>>> factors(FermionicSuzukiTrotter(order=2, reps=4))
17
>>> factors(FermionicSuzukiTrotter(order=2, reps=4, merge_adjacent=False))
20

Ang first order ay naglalabas ng isang factor kada group. Ang order two ay naglalabas ng simetrikong palindrome, kaya lumalabas nang dalawang beses ang dalawang panlabas na group na may kalahating oras bawat isa, na nagbibigay ng lima. Ang apat na pag-ulit nito ay magbibigay ng dalawampu, ngunit bawat pag-ulit ay nagtatapos sa parehong group na pinagsisimulan ng susunod. Ang mga magkatabing gate na iyon ay nag-e-evolve sa ilalim ng parehong operator, kaya pinagsasama sila sa isang gate na may pinagsamang oras, na nakakatipid ng isang gate kada tahi. Ipasa ang merge_adjacent=False para panatilihin ang sunud-sunod na pagkakasunod sa aklat. Pareho ang paghahati ng bawat order sa operator; ang nagbabago ay ang pagkakasunod ng mga factor at ang evolution time nila, hindi ang partition.

Tandaan

Minarkahan ang mga factor bilang atomic, kaya ang muling pag-decompose ay iiwan sila sa kinalalagyan nila imbes na hatiin pa. May fixed point ang decomposition, kaya ligtas na tawagin ang decompose() nang paulit-ulit.

Itakda ang pamamaraan para sa buong pipeline​

Ang pagtatakda ng synthesis kada gate ay nangangahulugan ng pagpapasa ng pagpili sa lahat ng bumubuo nito, kasama ang UCC at UCJ, na bumubuo ng sarili nilang mga gate sa loob. Sa halip, ang F2FSynthesis na pass ay naglalapat ng isang pamamaraan sa bawat Evolution sa isang circuit, kaya isang beses lang ginagawa ang pagpili para sa pipeline. Kino-configure ito ayon sa pangalan ng gate, kapareho ng F2QSynthesis, at ang gate na hindi pinangalanan ay iniiwang kung ano ito:

>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import F2FSynthesis
>>>
>>> circuit = FermionicCircuit(num_sites)
>>> circuit.append(Evolution(num_sites, hamiltonian, time=total_time), circuit.modes)
>>>
>>> expanded = FermionicPassManager(
... F2FSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2, "reps": 4})})
... ).run(circuit)
>>> expanded.count_ops()["Evolution"]
17

Ito ang parehong bilang na ibinibigay ng decompose() para sa pamamaraang iyon sa itaas. Inilalapat ng MergeEvolutions na pass ang parehong pagsasama sa mga magkatabing gate na hindi nagmula sa iisang synthesis, tulad ng mga tahi sa pagitan ng sunud-sunod na circuit. Ang pass mismo ang nagsasagawa ng formula, kaya hindi na kailangan ng hiwalay na hakbang ng pagpapalawak, at ang pagpapatakbo nito nang dalawang beses ay walang binabago, dahil atomic ang mga factor na inilalabas nito at kaya wala silang sariling definition.

Tandaan

Ipasa ang apply=False para piliin lang ang pamamaraan at ipaubaya ang pagpapalawak sa iba, tulad ng Decompose ng Qiskit (ang pagpapangalan sa gate bilang Decompose("Evolution") ay naglilimita rito sa mga evolution, at iniiwan ang iba pang fermionic gate sa sarili nilang synthesis plugin).

Kapaki-pakinabang ito para piliin ang pamamaraan para sa buong circuit nang maaga habang ipinagpapaliban ang pagpapalawak hanggang sa puntong gusto ito, halimbawa para siyasatin muna ang circuit:

>>> selected = FermionicPassManager(
... F2FSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2})}, apply=False)
... ).run(circuit)
>>> selected.count_ops()["Evolution"] # selected, not yet expanded
1
>>> selected.decompose().count_ops()["Evolution"] # expanded on demand
5

Gayunpaman, tandaan kung ano ang ginagawa ng hindi pa napapalawak na gate: umaabot ito nang buo sa fermion-to-qubit na yugto, kung saan ito ime-map nang hindi kinukonsulta ang synthesis. Madali itong pagkakamali kapag nagkukumpara ng mga pamamaraan, dahil lahat sila ay gumagawa ng magkakaparehong output at ang paghahambing ay tahimik na walang nasusukat. Sa Hamiltonian sa itaas, ang pag-iwan sa order-two na gate na hindi napapalawak ay nagpapabagsak sa mas malaking circuit sa ibaba hanggang sa gastos ng iisang na-map na evolution, anuman ang napiling pamamaraan.

Sukatin ang kapalit​

Nagkakahalaga ng depth ang mas mataas na order, kaya ang tanong ay hindi kung mas tumpak ito kundi kung mas tumpak ba ito kada gate. Dalawang numero ang kailangan kada pamamaraan: ang error ng state na nabubuo nito, at ang two-qubit na gastos ng circuit na lumikha nito.

Sinusukat ang error gamit ang ffsim, na direktang nagsi-simulate ng isang FermionicCircuit (tingnan ang How this package relates to ffsim). Walang fermion-to-qubit mapping na kasangkot, kaya inihihiwalay ng paghahambing ang fermionic product formula sa anumang maidudulot ng mapping o ng qubit-side synthesis. Gumagana rin ito sa fixed-particle-number sector, na parehong mas maliit kaysa sa buong 2N2^N na espasyo at ang espasyong talagang kinalalagyan ng evolution.

>>> import ffsim
>>> import numpy as np
>>> from scipy.sparse.linalg import expm_multiply
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> norb, nelec = num_sites, (3, 0) # three spinless fermions on six modes
>>> initial = ffsim.hartree_fock_state(norb, nelec)
>>>
>>> hamiltonian_operator = ffsim.linear_operator(hamiltonian, norb=norb, nelec=nelec)
>>> exact = expm_multiply(
... -1j * total_time * hamiltonian_operator,
... initial,
... traceA=-1j * total_time * ffsim.trace(hamiltonian, norb=norb, nelec=nelec),
... )
>>>
>>> def cost_and_error(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, hamiltonian, time=total_time, synthesis=synthesis),
... circuit.modes,
... )
... # the method is set on the gate, so decompose() is what expands it here; a pipeline
... # would reach for F2FSynthesis instead (see the note above)
... circuit = circuit.decompose()
... evolved = ffsim.apply_unitary(initial, circuit, norb=norb, nelec=nelec)
... error = 1 - abs(np.vdot(evolved, exact)) ** 2
... qubit_circuit = generate_preset_jw_pass_manager().run(circuit).decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... return error, qubit_circuit.count_ops()["cx"], qubit_circuit.depth(two_qubit)
>>>
>>> methods = [
... ("order 1, reps 1 ", FermionicLieTrotter()),
... ("order 2, reps 1 ", FermionicSuzukiTrotter(order=2)),
... ("order 1, reps 10", FermionicLieTrotter(reps=10)),
... ("order 2, reps 4 ", FermionicSuzukiTrotter(order=2, reps=4)),
... ]
>>> costs = {label: cost_and_error(synthesis) for label, synthesis in methods}
>>> labels, counts, depths = [], [], []
>>> for label, (error, gates, depth) in costs.items():
... labels.append(label.strip().replace(", ", "\n"))
... counts.append(gates)
... depths.append(depth)
... print(f"{label} error {error:.6f}")
order 1, reps 1 error 0.515997
order 2, reps 1 error 0.180958
order 1, reps 10 error 0.004026
order 2, reps 4 error 0.000421

Sinadyang hindi isama sa talahanayang iyon ang two-qubit na gastos. Nakadepende ito sa qubit-side synthesis na pinipili ng Qiskit, na bumubuti sa pagitan ng mga release, kaya ang mga numerong ipi-print dito ay magiging snapshot lang at hindi katangian ng product formula. Ipinapakita sila sa halip ng figure sa ibaba, at ang pinagbabatayan ng paghahambing ay ina-assert bilang isang relasyon:

Grouped bars of CX count and depth per method, with order two at four repetitions clearly shorter than order one at ten.

Basahin nang magkasama ang huling dalawang pamamaraan, dahil iyon ang paghahambing na mahalaga. Ang order two na may apat na pag-ulit ay humigit-kumulang sampung beses na mas tumpak kaysa sa order one na may sampu, at narating nito iyon na may mas kaunti, hindi mas marami, na entangling gate, gaya ng ipinapakita ng figure. Sa kasong ito, mas mainam na gugulin ang gate budget sa mas mataas na order kaysa sa mas maraming first-order na step.

Hindi pangkalahatan ang resultang iyon. Ang pagtaas ng reps at ang pagtaas ng order ay nagbabawas ng error sa magkaibang bilis para sa parehong uri ng gastos, at nakadepende sa operator at sa oras na ie-evolve kung alin ang mananalo. Ang dalawang hanay sa itaas ang paraan para malaman ito para sa isang partikular na problema.

Tandaan

Nagko-compose ang fermionic formula sa pinili para sa fermion-to-qubit na yugto (tingnan ang MapperFnEvolutionSynthesis.product_formula). Kung pareho silang nag-a-approximate, ang katumpakan ng resulta ay nakadepende sa mas mahina sa dalawa, kaya kaunti lang ang makukuha sa pagtataas ng order dito habang first-order pa rin ang qubit-side na formula.

Limitahan ang pass sa mga gate na makikinabang​

Nakakatulong lang ang mas mataas na order kapag hindi nagko-commute ang mga factor. Kung lahat sila ay nagko-commute, eksakto na ang first-order na formula at purong overhead na ang mga dagdag na factor. Ginagawang kongkreto ito ng isang puro diagonal na operator, dahil nagko-commute ang mga number operator sa isa't isa:

>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> diagonal = FermionOperator.from_terms(
... [([cre(j), ann(j), cre(j + 1), ann(j + 1)], 1.0 + 0.1 * j) for j in range(num_sites - 1)]
... )
>>> diagonal.groups = list(range(num_sites - 1))
>>>
>>> def diagonal_cost(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, diagonal, time=total_time, synthesis=synthesis), circuit.modes
... )
... transpiled = generate_preset_jw_pass_manager().run(circuit.decompose())
... return transpiled.decompose(reps=6).count_ops()["cx"]
>>>
>>> diagonal_cost(FermionicLieTrotter())
10
>>> diagonal_cost(FermionicSuzukiTrotter(order=2))
18

Parehong eksakto ang dalawang circuit, kaya ang pangalawa ay gumagastos ng 80% na mas maraming entangling gate nang walang napapala. Ganito ang hugis ng mga diagonal na Coulomb operator ng isang UCJ, kaya umiiral ang F2FSynthesis.filter: kumukuha ito ng predicate sa DAGOpNode at hindi ginagalaw ang mga tinanggihang gate, kaya maaaring itaas ng isang pass ang order sa mga gate na makikinabang at iwanan ang iba sa first order.

Ipinapakita ng isang circuit na may parehong uri ng evolution kung gaano ang natitipid nito. Tinatanggap lang ng predicate sa ibaba ang isang gate kapag naglilipat ng mga particle sa pagitan ng mga mode ang operator nito, na siyang eksaktong sandali kung kailan hindi nagko-commute ang mga group at may mapapala ang mas mataas na order:

>>> def is_diagonal(operator):
... """Whether every term only counts particles, rather than moving them between modes."""
... return all(
... sorted(mode for created, mode in actions if created)
... == sorted(mode for created, mode in actions if not created)
... for actions, _ in operator.iter_terms()
... )
>>>
>>> def mixed_circuit():
... circuit = FermionicCircuit(num_sites)
... circuit.append(Evolution(num_sites, hamiltonian, time=total_time), circuit.modes)
... circuit.append(Evolution(num_sites, diagonal, time=total_time), circuit.modes)
... return circuit
>>>
>>> def two_qubit_cost(circuit):
... transpiled = generate_preset_jw_pass_manager().run(circuit).decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... return transpiled.count_ops()["cx"], transpiled.depth(two_qubit)
>>>
>>> order_2 = FermionicSuzukiTrotter(order=2)
>>>
>>> everywhere = two_qubit_cost(
... FermionicPassManager(F2FSynthesis({"Evolution": order_2})).run(mixed_circuit())
... )
>>> restricted = two_qubit_cost(
... FermionicPassManager(
... F2FSynthesis(
... {"Evolution": order_2}, filter=lambda node: not is_diagonal(node.op.operator)
... )
... ).run(mixed_circuit())
... )
>>> everywhere[0] - restricted[0] # the gates the filter saved
8
>>> restricted[0] < everywhere[0] and restricted[1] < everywhere[1]
True

Ang mga gate na natitipid ng filter ay eksaktong ang mga ginastos ng order two sa diagonal na evolution, na ayon sa diagonal na paghahambing sa itaas ay may halagang walo (18 kumpara sa 10). Wala silang naibigay doon, dahil eksakto na ang evolution na iyon sa first order, kaya ang pag-alis sa kanila ay walang nawawalang katumpakan.

Tandaan

Ihambing ang dalawang order-two na circuit sa isa't isa, hindi sa hindi pa napapalawak. Ang pag-iwan sa dalawang gate na hindi napapalawak ay nagpapadala sa bawat isa sa fermion-to-qubit na yugto nang buo, na siyang pagkakamaling inilalarawan ng tala tungkol sa apply=False sa itaas: maaari itong mauwi sa parehong bilang ng entangling gate gaya ng na-filter na circuit habang ibang circuit na may ibang depth, kaya ang pagbasa ng benepisyo ng filter mula sa numerong iyon ay walang nasusukat.

Babala

Hindi bine-verify ng alinmang setting na Hermitian ang mga factor, at ang maling naka-group na operator ay maaaring magmukhang mas maganda sa even order kaysa sa first order habang mali pa rin, dahil bahagyang binubura ng symmetrization ang error. Pagsamahin ang mga conjugate partner, gaya ng ginagawa ng fermi_hubbard_chain sa itaas.

Mga susunod na hakbang​