Pag-transpile ng mga bosonic circuit
Ipinaliliwanag ng gabay na ito kung paano i-transpile ang isang BosonicCircuit sa isang karaniwang QuantumCircuit. Ang isang bosonic circuit ay walang truncation, walang encoding, at walang bilang ng qubit (tingnan ang Work with bosonic circuits), kaya ang transpiler ang lugar kung saan ginagawa ang mga pagpiling iyon. Ang truncation at encoding ay inilalarawan ng isang LevelSpec (tingnan ang Describe the truncation with a LevelSpec), at ang mapper na nagpapalit ng mga operator sa qubit operator ay inilalarawan sa Map operator representations. Ang resulta ay isang ordinaryong QuantumCircuit, kaya anumang simulator na compatible sa Qiskit ay kayang magpatakbo nito.
Gumagamit ang gabay ng isang maliit na halimbawa sa kabuuan: dalawang bosonic mode, isang state na may isang boson sa bawat isa, at iisang time evolution sa ilalim ng Bose-Hubbard Hamiltonian na ang bond at on-site na mga term ay nasa magkahiwalay na grupo (tingnan ang Group operator terms: use the operator structure):
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> terms = {(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0}
>>> terms.update({(cre(mode), cre(mode), ann(mode), ann(mode)): 0.5 for mode in range(2)})
>>> hamiltonian = BosonOperator.from_dict(terms)
>>> # the bond is group 0 and the on-site interaction is group 1; derive the index from the term,
>>> # because the stored order of a dictionary is not specified
>>> hamiltonian.groups = [
... 0 if len({mode for _, mode in term}) == 2 else 1 for term, _ in hamiltonian.iter_terms()
... ]
>>>
>>> circuit = BosonicCircuit(2)
>>> circuit.append(InitializeModes([1, 1]), circuit.modes)
>>> circuit.append(Evolution(2, hamiltonian, time=0.1), circuit.modes)
>>> dict(circuit.count_ops())
{'InitializeModes': 1, 'Evolution': 1}
Mga stage ng transpilation
Gaya ng ipinaliwanag sa qiskit_bosons.transpiler, hinahati ng mga preset pass manager sa package na ito ang transpilation sa mga sumusunod na stage:
Pasok
Kino-convert ang circuit sa isang directed acyclic graph (DAG) na data structure, ang representasyong pinagtatrabahuhan ng bawat susunod na pass.
Pag-optimize
Pinananatili ang circuit sa bosonic space. Ginagamit ng mga pass na ito ang istruktura ng mga bosonic operator, partikular ang mga grupo nila, habang wala pang impormasyon tungkol sa qubit. Ang B2BSynthesis ang pass na naghahati-hati ng mga gate rito, na may plugin para sa bawat uri ng gate: product formula o qDRIFT para sa isang Evolution, at ang Clements mesh para sa isang ModeRotation (tingnan ang Synthesize an Evolution gate in bosonic space kung paano pinipili ang isang method). Ang RelabelModes ay nagre-reorder ng mga mode. Dine-decompose ng preset ang mga mode rotation sa stage na ito at iniiwan ang lahat ng iba pa gaya ng dati.
Pagkakaayos
Nagma-map ng bawat bosonic mode register sa isang quantum register. Karaniwang gumagamit ang isang bosonic mode ng ilang qubit ( sa ilalim ng unary encoding, sa ilalim ng compact), kaya mas malapad ang quantum register kaysa sa mode register. Hinuhugot ng OccupationB2QLayout ang lapad ng register mula sa isang LevelSpec.
Sintesis
Kino-convert ang mga bosonic gate sa mga qubit operation, isang plugin bawat uri ng gate. Nangyayari ang encoding sa hakbang na ito: ang plugin para sa gate na Evolution ay nagma-map ng operator nito sa isang qubit operator, at ang plugin para sa gate na InitializeModes ay nagse-set ng mga qubit na nag-e-encode ng bawat occupation. Ang isang barrier ay dinadala sa bawat qubit ng bawat register na hinawakan nito.
Qubit
Tina-transpile ang nagresultang qubit circuit gamit ang sariling mga method ng Qiskit, halimbawa sa isang basis gate set o isang device. Palaging kasama ito sa preset; bilang default ito ang preset pass manager ng Qiskit, na nagsi-synthesize ng bawat evolution sa mga gate (tingnan ang Continue on the qubit level).
Resulta
Kino-convert pabalik ang internal na DAG data structure sa isang QuantumCircuit.
Hindi iniimbak sa circuit ang truncation at ang encoding. Ibinibigay ang mga ito sa transpiler, at dapat magkasundo ang layout, ang state preparation, at ang mapper tungkol sa mga ito. Tinitiyak ito ng preset sa pamamagitan ng pagbibigay ng parehong LevelSpec sa lahat ng tatlo.
Isang praktikal na halimbawa
Binubuo ng generate_preset_occupation_pass_manager() ang pipeline mula sa isang spec. Ang pagbaba ng halimbawa sa ilalim ng dalawang encoding ay spec lang ang binabago. Nagtatapos ang preset sa sariling qubit-level na transpilation ng Qiskit, na magsi-synthesize ng evolution sa mga gate. Para makita ang circuit nang eksakto kung paano ito inilalabas ng mga bosonic stage, alisan muna ng laman ang stage na iyon:
>>> from qiskit.transpiler import PassManager
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> for encoding in ("unary_unprojected", "gray"):
... pass_manager = generate_preset_occupation_pass_manager(LevelSpec(2, encoding))
... pass_manager.qubit = PassManager()
... lowered = pass_manager.run(circuit)
... print(f"{encoding:18s} qubits={lowered.num_qubits} ops={dict(lowered.count_ops())}")
unary_unprojected qubits=6 ops={'x': 2, 'PauliEvolution': 1}
gray qubits=4 ops={'x': 2, 'PauliEvolution': 1}
Ang dalawang encoding ay gumagamit ng anim at apat na qubit ayon sa pagkakasunod (tatlong level bawat mode sa ilalim ng unary encoding, dalawang qubit bawat mode sa ilalim ng Gray code), pero pareho ang istruktura ng circuit: dalawang operasyong XGate ang naghahanda ng mga occupation, at isang PauliEvolutionGate ang may dala ng evolution.
Ihambing sa tradisyonal na workflow
Inilalarawan ng gabay na bosonic circuits ang tradisyonal na workflow, na nagma-map ng Hamiltonian sa isang qubit operator bago nito buuin ang QuantumCircuit. Gumagawa ang dalawang ruta ng magkaparehong circuit. Nagkakaiba sila sa kung ano ang kailangang ulitin kapag nagbago ang truncation. Ang bosonic circuit ay binubuo nang isang beses at ibinababa sa bawat cutoff, samantalang ang map-first na ruta ay nangangailangang mamapa muli ang operator at muling buuin ang circuit:
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit.library import PauliEvolutionGate
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>>
>>> basis = ["rz", "sx", "x", "cx"]
>>> cx_counts = []
>>> for max_occupation in (1, 2, 3):
... spec = LevelSpec(max_occupation, "gray")
... # the bosonic circuit is reused as it is
... lowered = generate_preset_occupation_pass_manager(
... spec, optimization_level=0, basis_gates=basis
... ).run(circuit)
... # the map-first route maps the operator and builds a new circuit for every cutoff
... qubit_hamiltonian = occupation_encoding(hamiltonian, spec)
... by_hand = QuantumCircuit(qubit_hamiltonian.num_qubits)
... _ = by_hand.append(PauliEvolutionGate(qubit_hamiltonian, time=0.1), by_hand.qubits)
... cx_counts.append(lowered.count_ops().get("cx", 0))
... print(
... f"d={max_occupation + 1}: {lowered.num_qubits} qubits; map-first: "
... f"{len(qubit_hamiltonian)} Pauli terms to place by hand"
... )
d=2: 2 qubits; map-first: 2 Pauli terms to place by hand
d=3: 4 qubits; map-first: 10 Pauli terms to place by hand
d=4: 4 qubits; map-first: 22 Pauli terms to place by hand
Nakadepende ang bilang ng entangling gate sa kung paano sini-synthesize ng Qiskit ang evolution gate, na nagbabago sa pagitan ng mga release, kaya i-plot ito:
Mabilis na lumalaki ang bilang kasabay ng cutoff, kaya dapat panatilihing parameter ng transpilation ang cutoff at hindi ng circuit. Magkatumbas ang dalawang ruta rito, pero ang bosonic lang ang kayang magsingit ng grouping ng operator sa product formula (tingnan ang Synthesize an Evolution gate in bosonic space) o magpasa ng encoding sa ibang spec nang hindi binabago ang isang linya ng circuit.
Ang isang spec na may iisang cutoff ay iniiwang bukas ang bilang ng mga mode, kaya isang pass manager ang nagsisilbi sa mga circuit na may anumang lapad. Ang isang spec na may isang cutoff bawat mode ay nagtatakda ng lapad:
>>> wide = BosonicCircuit(3)
>>> wide.append(InitializeModes([1, 0, 1]), wide.modes)
>>> shared = generate_preset_occupation_pass_manager(LevelSpec(2, "gray"))
>>> shared.run(wide).num_qubits
6
>>> per_mode = generate_preset_occupation_pass_manager(LevelSpec([2, 1], "gray"))
>>> per_mode.run(circuit).num_qubits
3
>>> try:
... per_mode.run(wide)
... except ValueError as error:
... print(error)
the spec covers 2 modes, but 3 were expected
Buuin ang pipeline nang mano-mano
Ang preset ay isang MultiStagePassManager na may isang entry bawat stage. Maaari mong buuin ang parehong pipeline mula sa mga bahagi nito, na siyang panimulang punto ng bawat customization sa ibaba:
>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>> from qiskit_bosons.transpiler import BosonicCircuitToDAG, QuantumDAGToCircuit
>>> from qiskit_bosons.transpiler.passes import (
... B2QSynthesis,
... MapperFnEvolutionSynthesis,
... OccupationB2QLayout,
... OccupationInitializeModesSynthesis,
... )
>>>
>>> spec = LevelSpec(2, "gray")
>>>
>>> def mapper_fn(operator, num_qubits):
... return occupation_encoding(operator, spec, num_qubits)
>>>
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(mapper_fn)
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>>
>>> by_hand = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> preset = generate_preset_occupation_pass_manager(spec)
>>> preset.qubit = PassManager()
>>> by_hand.run(circuit) == preset.run(circuit)
True
Ang mapper function ay tumatanggap ng isang operator at ang lapad ng register na nakuha ng layout, at nagbabalik ng isang SparseObservable. Dahil ibinibigay sa kanya ang lapad, kapag ipinasa mo ang num_qubits sa occupation_encoding(), sinusuri ng mapper kung kasya ang spec sa register, kaya ang hindi pagtugma ng layout at ng encoding ay magpapakita ng malinaw na mensahe ng error.
Mga advanced na workflow
Magbigay ng tahasang layout
Pinapalitan ng CustomB2QLayout ang layout stage ng isang mapping na ikaw ang nagbibigay, mula sa bawat bosonic register ng circuit papunta sa isang quantum register. Kapaki-pakinabang ito kapag kailangang mas malapad ang register kaysa sa kailangan ng spec, halimbawa para mag-iwan ng mga idle na qubit para sa ibang gamit:
>>> from qiskit.circuit import QuantumRegister
>>> from qiskit_bosons.transpiler.passes import CustomB2QLayout
>>>
>>> (mode_register,) = BosonicCircuitToDAG().run(circuit).qregs.values()
>>> custom = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=CustomB2QLayout({mode_register: QuantumRegister(6, "q")}),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> custom.run(circuit).num_qubits
6
Ikaw ang may pananagutan sa custom na layout. Ina-address pa rin ng mga synthesis plugin ang mga qubit sa pamamagitan ng spec, kaya ang register na hindi tugma sa mga lapad ng spec ay magbibigay ng maling circuit o error sa kailaliman ng isang plugin. Nilalaktawan din ng custom na layout ang pagsusuring ginagawa ng OccupationB2QLayout pagkatapos ng RelabelModes (tingnan ang susunod na seksyon).
Ayusin muli ang mga mode
Ang RelabelModes ay isang pass sa optimization stage na nag-aayos muli ng mga mode. Binabasa ang permutation bilang mapping mula sa orihinal patungo sa bagong mode index, at nire-record ito sa metadata ng circuit. Binabago ng pag-aayos muli kung aling mga qubit ang inookupahan ng bawat mode, kaya maaari nitong bawasan ang depth ng na-lower na circuit.
Dahil nakasulat ang resulta sa bagong ayos ng mga mode, kailangang i-map pabalik ang mga nasukat na bitstring bago bigyang-kahulugan. Kapag maraming qubit bawat mode, ang mapping ay umaaksyon sa mga block ng qubit at hindi sa mga indibidwal na bit. Nire-relabel ng sumusunod na halimbawa ang tatlong mode, na bawat isa ay may dalawang qubit sa ilalim ng Gray code, at binabawi ang orihinal na occupation mula sa mga nasukat na count:
>>> # BasicSimulator is a convenient pure-Python choice; any simulator works
>>> from qiskit.providers.basic_provider import BasicSimulator
>>> from qiskit_bosons.transpiler.passes import RelabelModes
>>>
>>> state = BosonicCircuit(3)
>>> state.append(InitializeModes([2, 0, 1]), state.modes)
>>> spec = LevelSpec(2, "gray") # three levels, two qubits per mode
>>> width = spec.resolve(3).widths[0]
>>> permutation = [2, 0, 1]
>>>
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>> relabeled = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... optimization=RelabelModes(permutation),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... ).run(state)
>>> relabeled.metadata
{'permutation': [2, 0, 1]}
>>>
>>> def restore(bitstring, permutation, width):
... """Map a bitstring in the new mode order back to the original mode order."""
... qubits = bitstring[::-1] # character q is now the value of qubit q
... blocks = [qubits[mode * width : (mode + 1) * width] for mode in range(len(permutation))]
... return "".join(blocks[new] for new in permutation)[::-1]
>>>
>>> relabeled.measure_all()
>>> (measured,) = BasicSimulator().run(relabeled, shots=1).result().get_counts()
>>> measured
'110100'
>>> restore(measured, permutation, width)
'010011'
Ang naibalik na bitstring ang ginagawa ng circuit nang walang relabeling, na binasa sa little-endian na ayos ng Qiskit: hawak ng mode 0 ang Gray code ng level dalawa (11), hawak ng mode 1 ang level zero (00), at hawak ng mode 2 ang level isa (01).
>>> plain = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... ).run(state)
>>> plain.measure_all()
>>> (reference,) = BasicSimulator().run(plain, shots=1).result().get_counts()
>>> restore(measured, permutation, width) == reference
True
Ang spec na nagbibigay sa mga mode ng magkakaibang cutoff ay hindi maaaring pagsamahin sa RelabelModes. Nakakabit ang mga cutoff sa mga mode ayon sa posisyon nila, kaya ang pag-aayos muli ng mga mode ay maglalagay ng cutoff sa maling mode. Tinatanggihan ng OccupationB2QLayout ang na-relabel na circuit sa ganitong kaso:
>>> mixed = LevelSpec([2, 1, 2], "gray")
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(mixed)
>>> refused = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... optimization=RelabelModes(permutation),
... layout=OccupationB2QLayout(mixed),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> try:
... refused.run(state)
... except ValueError as error:
... print(str(error).split(",")[0])
the circuit has been relabeled by RelabelModes
Piliin ang mga synthesis plugin
Walang default na plugin ang B2QSynthesis; nagrerehistro ang preset ng isa bawat uri ng gate. Ang mga available na plugin ay pinamamahalaan ng B2QSynthesisPluginManager, na naglilista sa kanila ayon sa pangalan ng gate na kanilang inaaksyunan:
>>> from qiskit_bosons.transpiler.passes import B2QSynthesisPluginManager
>>> manager = B2QSynthesisPluginManager()
>>> for gate in manager.op_names():
... print(gate, manager.method_names(gate))
Evolution ['MapperFn']
InitializeModes ['Occupation']
Ang dalawang builtin na method ay ang Evolution.MapperFn at InitializeModes.Occupation. Ang una ay nagsi-synthesize ng evolution mula sa kahit anong mapper function, na paraan kung paano isinasaksak ang custom na encoding (tingnan ang Map operator representations). Walang plugin na kasama ang library para sa isang ModeRotation gate. Una itong hinahati ng preset sa bosonic space tungo sa mga Evolution gate, na hinahawakan naman ng unang method, pero ang plugin na nakarehistro para sa ModeRotation ay maaaring direktang mag-lower ng gate; tingnan ang Rotate bosonic modes at Lower a rotation without decomposing it. Maaari kang magrehistro ng method ayon sa pangalan sa pamamagitan ng argumentong config ng B2QSynthesis, o magsulat ng plugin object direkta sa B2QSynthesis.methods, tulad ng ginagawa ng mga naunang halimbawa.
Magpatuloy sa antas ng qubit
Laging nagtatapos ang preset sa isang qubit stage, na siyang sariling preset pass manager ng Qiskit. Ang mga keyword argument ng generate_preset_occupation_pass_manager() ay ipinapasa sa qiskit.transpiler.generate_preset_pass_manager(), kaya maaari kang mag-translate sa isang basis gate set o direktang mag-target ng device. Mas mainam ang optimization_level=0 para manatiling malapit ang circuit sa nabuo ng mga bosonic stage:
>>> from qiskit.transpiler import generate_preset_pass_manager
>>>
>>> spec = LevelSpec(2, "gray")
>>> translated = generate_preset_occupation_pass_manager(
... spec, optimization_level=0, basis_gates=["rz", "sx", "x", "cx"]
... ).run(circuit)
>>> sorted(translated.count_ops())
['cx', 'rz', 'sx', 'x']
Dalawa pang pagpipilian ang karaniwan. Magtalaga ng walang laman na PassManager sa qubit stage kapag gusto mong ipakita ang circuit nang eksakto kung paano ito na-synthesize ng pipeline, tulad ng ginagawa ng mga halimbawa sa itaas. O buuin mo mismo ang pipeline at magdagdag ng kahit anong Qiskit pass manager bilang qubit stage ng MultiStagePassManager. Tumatakbo ang mga stage ayon sa pagkakasunod na ibinigay, kaya ang qubit stage ay dapat mauna sa output, dahil inaasahan ng mga pass ng Qiskit ang isang DAG at hindi isang tapos na circuit:
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(
... lambda operator, num_qubits: occupation_encoding(operator, spec, num_qubits)
... )
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>> staged = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... qubit=generate_preset_pass_manager(
... optimization_level=0, basis_gates=["rz", "sx", "x", "cx"]
... ),
... output=QuantumDAGToCircuit(),
... )
>>> sorted(staged.run(circuit).count_ops())
['cx', 'rz', 'sx', 'x']
Mga susunod na hakbang
-
Map operator representations para sa mga encoding at sa pagsulat ng sarili mong mapper.
-
Synthesize an Evolution gate in bosonic space para sa pagpili ng product formula para sa mga evolution gate.
-
Rotate bosonic modes para sa pag-lower ng mga mode rotation.
-
Simulate Bose-Hubbard dynamics with a choice of encoding para sa kumpletong simulation na sumusuri sa na-lower na circuit laban sa eksaktong dynamics.
-
qiskit_bosons.transpilerpara sa buong paglalarawan ng mga stage at ng mga available na pass.