Transpile bosonic circuit
Panduan ini menjelaskan cara men-transpile sebuah BosonicCircuit menjadi QuantumCircuit standar. Bosonic circuit tidak membawa pemotongan, encoding, maupun jumlah qubit (lihat Bekerja dengan bosonic circuit), jadi Transpiler adalah tempat pilihan-pilihan itu dibuat. Pemotongan dan encoding dijelaskan oleh sebuah LevelSpec (lihat Mendeskripsikan pemotongan dengan LevelSpec), dan mapper yang mengubah operator menjadi operator qubit dijelaskan di Petakan representasi operator. Hasilnya adalah QuantumCircuit biasa, jadi simulator apa pun yang kompatibel dengan Qiskit bisa menjalankannya.
Panduan ini memakai satu contoh kecil dari awal sampai akhir: dua mode bosonik, sebuah state dengan satu boson di masing-masing mode, dan satu evolusi waktu di bawah Hamiltonian Bose-Hubbard yang suku bond dan on-site-nya berada di kelompok terpisah (lihat Mengelompokkan suku operator: memanfaatkan struktur operator):
>>> 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}
Tahap transpilasi
Seperti dijelaskan di qiskit_bosons.transpiler, preset pass manager di paket ini membagi transpilasi menjadi tahap-tahap berikut:
Input
Mengubah circuit menjadi struktur data directed acyclic graph (DAG), representasi tempat semua pass berikutnya bekerja.
Optimisasi
Menjaga circuit tetap di ruang bosonik. Pass-pass ini memanfaatkan struktur operator bosonik, khususnya kelompok-kelompoknya, selagi belum ada informasi qubit. B2BSynthesis adalah pass yang memecah gate di sini, dengan plugin untuk setiap jenis gate: product formula atau qDRIFT untuk sebuah Evolution, dan mesh Clements untuk sebuah ModeRotation (lihat Mensintesis gate Evolution di ruang bosonik untuk cara sebuah metode dipilih). RelabelModes mengurutkan ulang mode. Preset mendekomposisi rotasi mode pada tahap ini dan membiarkan sisanya apa adanya.
Tata letak
Memetakan setiap register mode bosonik ke register kuantum. Sebuah mode bosonik umumnya menempati beberapa qubit ( qubit pada encoding unary, pada encoding kompak), sehingga register kuantum lebih lebar daripada register mode. OccupationB2QLayout menurunkan lebar register dari sebuah LevelSpec.
Sintesis
Mengubah gate bosonik menjadi operasi qubit, satu plugin per jenis gate. Encoding terjadi pada langkah ini: plugin untuk gate Evolution memetakan operatornya menjadi operator qubit, dan plugin untuk gate InitializeModes mengatur qubit yang meng-encode setiap okupansi. Sebuah barrier diteruskan ke setiap qubit dari setiap register yang disentuhnya.
Qubit
Men-transpile circuit qubit yang dihasilkan dengan metode Qiskit sendiri, misalnya ke basis gate set atau sebuah device. Preset selalu menyertakannya; secara bawaan ia adalah preset pass manager milik Qiskit, yang mensintesis setiap evolusi menjadi gate (lihat Melanjutkan di level qubit).
Output
Mengubah struktur data DAG internal kembali menjadi QuantumCircuit.
Pemotongan dan encoding tidak disimpan di dalam circuit. Keduanya diberikan ke Transpiler, dan layout, persiapan state, serta mapper harus sepakat tentang keduanya. Preset memastikannya dengan memberikan LevelSpec yang sama ke ketiganya.
Contoh praktis
generate_preset_occupation_pass_manager() membangun pipeline dari sebuah spec. Menurunkan contoh di bawah dua encoding hanya mengubah spec-nya. Preset berakhir pada transpilasi level qubit milik Qiskit sendiri, yang akan mensintesis evolusi menjadi gate. Untuk melihat circuit persis seperti yang dihasilkan tahap bosonik, kosongkan dulu tahap itu:
>>> 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}
Kedua encoding masing-masing memakai enam dan empat qubit (tiga level per mode pada encoding unary, dua qubit per mode pada kode Gray), tetapi struktur circuit-nya sama: dua operasi XGate menyiapkan okupansi, dan satu PauliEvolutionGate membawa evolusinya.
Bandingkan dengan alur kerja tradisional
Panduan bosonic circuit menjelaskan alur kerja tradisional, yang memetakan Hamiltonian ke operator qubit sebelum membangun QuantumCircuit. Kedua jalur menghasilkan circuit yang ekuivalen. Bedanya ada pada apa yang harus diulang ketika pemotongan berubah. Bosonic circuit dibangun sekali dan diturunkan di setiap cutoff, sedangkan jalur map-first membutuhkan operator dipetakan lagi dan circuit dibangun ulang:
>>> 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
Jumlah gate pengentang bergantung pada bagaimana Qiskit mensintesis gate evolusi, yang berubah antar rilis, jadi plot-lah:
Jumlahnya tumbuh cepat seiring cutoff, dan itulah alasan menjadikan cutoff sebagai parameter transpilasi dan bukan parameter circuit. Kedua jalur ekuivalen di sini, tetapi hanya jalur bosonik yang juga bisa menyisipkan pengelompokan operator dengan product formula (lihat Mensintesis gate Evolution di ruang bosonik) atau menyerahkan encoding ke spec lain tanpa mengubah satu baris pun dari circuit.
Spec dengan satu cutoff membiarkan jumlah mode terbuka, sehingga satu pass manager melayani circuit dengan lebar berapa pun. Spec dengan satu cutoff per mode menetapkan lebarnya:
>>> 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
Membangun pipeline secara manual
Preset-nya adalah MultiStagePassManager dengan satu entri per stage. Kamu bisa merakit pipeline yang sama dari bagian-bagiannya, yang menjadi titik awal untuk setiap kustomisasi di bawah ini:
>>> 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
Fungsi mapper menerima sebuah operator dan lebar register yang diturunkan oleh layout, lalu mengembalikan sebuah SparseObservable. Karena lebar register diberikan, meneruskan num_qubits ke occupation_encoding() membuat mapper memeriksa apakah spec cocok dengan register, sehingga ketidakcocokan antara layout dan encoding akan gagal dengan pesan yang jelas.
Alur kerja lanjutan
Berikan layout secara eksplisit
CustomB2QLayout menggantikan stage layout dengan pemetaan yang kamu berikan, dari setiap register bosonik pada circuit ke sebuah register kuantum. Ini berguna ketika register harus lebih lebar daripada yang dibutuhkan spec, misalnya untuk menyisakan qubit menganggur untuk keperluan lain:
>>> 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
Layout kustom jadi tanggung jawabmu. Plugin synthesis tetap mengakses qubit lewat spec, jadi register yang tidak cocok dengan lebar di spec akan menghasilkan circuit yang salah atau error jauh di dalam plugin. Layout kustom juga melewati pemeriksaan yang dilakukan OccupationB2QLayout setelah RelabelModes (lihat bagian berikutnya).
Urutkan ulang mode
RelabelModes adalah pass tahap optimisasi yang mengurutkan ulang mode. Permutasinya dibaca sebagai pemetaan dari indeks mode asli ke indeks mode baru, dan dicatat di metadata circuit. Pengurutan ulang mengubah qubit mana yang ditempati tiap mode, sehingga bisa mengurangi depth circuit hasil lowering.
Karena hasilnya dinyatakan dalam urutan mode yang baru, bitstring hasil pengukuran harus dipetakan kembali sebelum ditafsirkan. Dengan beberapa qubit per mode, pemetaan bekerja pada blok qubit, bukan pada bit tunggal. Contoh berikut me-relabel tiga mode, masing-masing menempati dua qubit di bawah kode Gray, dan memulihkan okupansi asli dari counts hasil pengukuran:
>>> # 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'
Bitstring yang dipulihkan adalah apa yang dihasilkan circuit tanpa relabeling, dibaca dalam urutan little-endian Qiskit: mode 0 menyimpan kode Gray level dua (11), mode 1 menyimpan level nol (00), dan mode 2 menyimpan level satu (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
Spec yang memberi mode cutoff berbeda tidak bisa dikombinasikan dengan RelabelModes. Cutoff dikaitkan ke mode berdasarkan posisinya, jadi mengurutkan ulang mode akan menaruh cutoff di mode yang salah. OccupationB2QLayout menolak circuit yang sudah di-relabel dalam kasus itu:
>>> 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
Pilih plugin synthesis
B2QSynthesis tidak punya plugin default; preset mendaftarkan satu per tipe gate. Plugin yang tersedia dikelola oleh B2QSynthesisPluginManager, yang mendaftarkannya berdasarkan nama gate yang mereka tangani:
>>> 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']
Dua metode bawaan adalah Evolution.MapperFn dan InitializeModes.Occupation. Yang pertama mensintesis sebuah evolusi dari fungsi mapper apa pun, dan inilah cara memasang encoding kustom (lihat Petakan representasi operator). Library ini tidak menyertakan plugin untuk gate ModeRotation. Preset menguraikannya di ruang bosonik menjadi gate Evolution terlebih dulu, yang kemudian ditangani metode pertama, tetapi plugin yang didaftarkan untuk ModeRotation bisa menurunkan gate itu secara langsung; lihat Rotasikan mode bosonik dan Turunkan rotasi tanpa mendekomposisinya. Kamu bisa mendaftarkan sebuah metode berdasarkan nama lewat argumen config dari B2QSynthesis, atau menulis objek plugin langsung ke B2QSynthesis.methods, seperti pada contoh-contoh sebelumnya.
Lanjutkan di level qubit
Preset selalu berakhir di stage qubit, yaitu pass manager preset milik Qiskit sendiri. Keyword argument dari generate_preset_occupation_pass_manager() diteruskan ke qiskit.transpiler.generate_preset_pass_manager(), jadi kamu bisa menerjemahkan ke basis gate set atau menargetkan perangkat secara langsung. Sebaiknya gunakan optimization_level=0 agar circuit tetap dekat dengan hasil stage bosonik:
>>> 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']
Ada dua pilihan lain yang umum. Berikan PassManager kosong ke stage qubit kalau kamu ingin menampilkan circuit persis seperti yang disintesis pipeline, seperti pada contoh di atas. Atau rakit pipeline sendiri dan tambahkan pass manager Qiskit apa pun sebagai stage qubit dari MultiStagePassManager. Stage dijalankan sesuai urutan yang diberikan, jadi stage qubit harus berada sebelum output, karena pass Qiskit mengharapkan DAG, bukan circuit jadi:
>>> 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']
Langkah berikutnya
-
Petakan representasi operator untuk encoding dan untuk menulis mapper buatanmu sendiri.
-
Sintesis gate Evolution di ruang bosonik untuk memilih product formula bagi gate evolusi.
-
Rotasikan mode bosonik untuk menurunkan rotasi mode.
-
Simulasikan dinamika Bose-Hubbard dengan pilihan encoding untuk simulasi lengkap yang memeriksa circuit hasil lowering terhadap dinamika eksak.
-
qiskit_bosons.transpileruntuk deskripsi lengkap tentang stage dan pass yang tersedia.