Putarkan mod boson
Sebuah ModeRotation mencampurkan mod boson dengan matriks uniter , seperti rangkaian pemecah alur dan penganjak fasa. Ia dipanggil pasif kerana ia memelihara jumlah bilangan boson: ia menggerakkan boson antara mod dan tidak pernah mencipta atau memusnahkannya. (Pemampatan dan sesaran ialah operasi aktif, dan putaran mod tidak dapat menyatakannya.) Ia memetakan pengendali penciptaan sebagai
yang pada ruang Fock penuh ialah uniter banyak-jasad
Ini ialah Gate di sebalik interferometer optik linear: hantar keadaan Fock masuk, putarkan mod, dan baca bagaimana boson diagihkan semula. Seperti setiap Gate BosonicCircuit (lihat Bekerja dengan litar boson), ia tidak membawa pemangkasan dan tiada pengekodan. Kedua-duanya masuk semasa masa transpile, dan panduan ini menunjukkan mengapa pemangkasan memerlukan perhatian khusus bagi sesuatu putaran.
Bina satu putaran
Pembina menerima matriks uniter . Gate bertindak pada mod. Kelas ini tidak menyemak sama ada matriks itu uniter, jadi itu tanggungjawab anda.
>>> import numpy as np
>>> from qiskit_bosons.circuit.library import ModeRotation
>>>
>>> unitary = np.array([[1, -1], [1, 1]]) / np.sqrt(2)
>>> rotation = ModeRotation(unitary)
>>> rotation.num_modes # the gate acts on two modes
2
Putaran biasa mempunyai pembina khusus. ModeRotation.beamsplitter() menerima sudut pencampuran dan fasa pilihan , dan memberikan pemecah alur seimbang 50:50. ModeRotation.phase_shift() menerima satu fasa bagi setiap mod.
>>> beamsplitter = ModeRotation.beamsplitter(np.pi / 4)
>>> np.round(beamsplitter.rotation_unitary.real, 4)
array([[ 0.7071, -0.7071],
[ 0.7071, 0.7071]])
>>> bool(np.allclose(beamsplitter.rotation_unitary, unitary))
True
>>> shift = ModeRotation.phase_shift([0.0, np.pi / 2])
>>> np.round(np.diag(shift.rotation_unitary), 4)
array([1.+0.j, 0.+1.j])
Anda juga boleh bermula daripada penjana anti-Hermitian dan biarkan Gate mengeksponenkannya, yang memberikan putaran . Penjana itu diperoleh semula dengan generator().
>>> generator = np.array([[0, -0.3], [0.3, 0]], dtype=complex)
>>> small = ModeRotation.from_generator(generator)
>>> np.round(small.rotation_unitary.real, 4)
array([[ 0.9553, -0.2955],
[ 0.2955, 0.9553]])
>>> bool(np.allclose(small.generator(), generator))
True
Jambatan ke aljabar pengendali ialah to_operator(), yang mengembalikan penjana Hermitian sebagai BosonOperator, supaya Gate itu ialah . Bagi pemecah alur 50:50, itu ialah sepasang sebutan lompatan dengan pekali khayal yang bertentangan. Pengendali itu Hermitian, iaitu yang diperlukan oleh Gate Evolution, dan ia memelihara bilangan zarah.
>>> operator = beamsplitter.to_operator()
>>> # The stored order of the terms is not specified, so sort them before printing.
>>> sorted((term[0], round(term[1].imag, 4)) for term in operator.iter_terms())
[([(True, 0), (False, 1)], -0.7854), ([(True, 1), (False, 0)], 0.7854)]
>>> operator.is_hermitian()
True
>>> operator.conserves_particle_number()
True
Setiap sebutan membaca (is_creation, mode) dari kiri ke kanan, jadi yang kedua ialah .
Bertindak pada keadaan Fock
Putaran menggerakkan boson antara mod, jadi kesannya paling mudah dilihat pada keadaan Fock. Kesan Hong-Ou-Mandel ialah contoh piawai. Hantar satu boson ke setiap mod input pemecah alur seimbang. Dua laluan yang meninggalkan boson dalam mod output berbeza berinterferens secara pemusnah, jadi keadaan lenyap dan boson sentiasa keluar bersama, dalam atau , masing-masing dengan kebarangkalian satu perdua.
Bina litar pada mod boson dan turunkannya dengan pengurus laluan pratetap. LevelSpec menetapkan pemangkasan dan pengekodan pada ketika ini. Had max_occupation=2 mengekalkan aras 0, 1, dan 2 bagi setiap mod.
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import InitializeModes
>>> from qiskit_bosons.mappers import LevelEncoding, LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> circuit = BosonicCircuit(2)
>>> circuit.append(InitializeModes([1, 1]), circuit.modes)
>>> circuit.append(ModeRotation.beamsplitter(np.pi / 4), circuit.modes)
Saluran paip mengembalikan litar qubit biasa. Baca kebarangkalian hasil daripada keadaan akhir, di sini dengan Statevector Qiskit, yang tepat pada saiz ini. Mana-mana simulator yang mengembalikan amplitud atau kiraan boleh digunakan. Keadaan Fock ialah satu keadaan asas pengiraan, yang indeksnya diberikan oleh corak bit LevelEncoding.level_to_bits(); qubit mod kedua mengikuti qubit mod pertama.
>>> from qiskit.quantum_info import Statevector
>>> from qiskit.transpiler import PassManager
>>>
>>> def fock_probabilities(circuit, max_occupation):
... """Maps each pair of occupations to the probability of that outcome."""
... encoding = LevelEncoding.UNARY_UNPROJECTED
... spec = LevelSpec(max_occupation, encoding)
... pass_manager = generate_preset_occupation_pass_manager(spec)
... pass_manager.qubit = PassManager() # keep the evolutions as the pipeline emitted them
... qubit_circuit = pass_manager.run(circuit)
... state = Statevector(qubit_circuit)
... width = encoding.num_qubits(max_occupation + 1)
... probabilities = {}
... for n0 in range(max_occupation + 1):
... for n1 in range(max_occupation + 1):
... index = encoding.level_to_bits(n0) | (encoding.level_to_bits(n1) << width)
... probability = round(float(abs(state.data[index]) ** 2), 4)
... if probability > 0:
... probabilities[(n0, n1)] = probability
... return probabilities
>>>
>>> fock_probabilities(circuit, max_occupation=2)
{(0, 2): 0.5, (2, 0): 0.5}
Hasil mempunyai kebarangkalian sifar, dan dua hasil berkelompok berkongsi kebarangkalian secara sama rata.
Pilih had
Keputusan sebelumnya memerlukan max_occupation=2. Jalankan litar yang sama dengan had 1. Itu cukup untuk memuatkan keadaan input , tetapi tidak keadaan yang dihasilkan putaran daripadanya, yang mempunyai berat pada dan :
>>> fock_probabilities(circuit, max_occupation=1)
{(1, 1): 1.0}
Litar berjalan tanpa sebarang ralat, dan jawapannya salah: keadaan kekal pada dengan kebarangkalian satu. Putaran mencampurkan sektor penghunian dalam satu mod. Dua boson boleh berakhir dalam mod output yang sama, jadi keadaan tepat mempunyai berat pada penghunian 2 dalam mod yang bermula dengan satu boson. Dengan had 1, aras itu tidak wujud, dan penjana terpangkas tidak dapat menggerakkan keadaan ke sana.
Batas pada penghunian keadaan awal oleh itu tidak mencukupi. Had mesti meliputi jumlah bilangan boson yang boleh sampai ke mana-mana satu mod putaran. Bagi boson keseluruhannya, pilihan selamat ialah had .
Penaakulan yang sama terpakai pada setiap mod secara berasingan, jadi gabungan had berbeza bagi setiap mod (lihat Huraikan pemangkasan dengan LevelSpec) menjadikan putaran mudah dipetakan secara salah. Mod yang boleh menerima boson memerlukan had yang meliputi apa yang boleh diterimanya, bukan sahaja apa yang dipegangnya pada mulanya. Semakan LevelSpec.truncation() membandingkan spesifikasi dengan batas bawah penghunian sesuatu pengendali. Ia tidak dapat menandakan kes di atas. Setiap sebutan penjana pemecah alur memegang satu pengendali penciptaan, jadi batasnya ialah satu dan semakan tidak melaporkan kekurangan pada had 1, walaupun hasilnya salah di situ:
>>> LevelSpec(1, "unary_unprojected").truncation(beamsplitter.to_operator())
[0, 0]
Satu langkah berjaga-jaga lanjut timbul daripada cara Transpiler membina putaran (bahagian seterusnya). Ia menerapkan putaran sebagai jujukan faktor dua-mod, dan dokumentasi pemalam memberi amaran bahawa satu faktor boleh mengisi aras yang tidak pernah dicapai oleh putaran lengkap. Jujukan terpangkas dan evolusi tunggal terpangkas kemudiannya ialah penghampiran berbeza bagi uniter yang sama. Carian ke atas 1,080 putaran tiga-mod rawak, dengan satu boson dalam setiap dua mod input dan tiada penghunian berganda dalam output, tidak menemui kes sebegitu, jadi anggaplah ini sebagai langkah berjaga-jaga dan bukan kegagalan lazim. Penumpukan had seperti anda lakukan untuk mana-mana litar lain, dengan meningkatkannya sehingga hasilnya berhenti berubah.
Bagaimana Transpiler mensintesis putaran
Peringkat optimization bagi saluran paip pratetap memecahkan ModeRotation dalam ruang boson dahulu, dengan pas B2BSynthesis dan kaedah BosonicClementsDecomposition (didaftarkan sebagai ModeRotation.Clements). Itu ialah pilihan pratetap dan bukan keperluan rangka kerja: bahagian kemudian menurunkan Gate yang sama tanpa sebarang penguraian. Penguraian tidak mengeksponenkan penjana penuh dalam satu langkah. Ia mula-mula memfaktorkan uniter kepada paling banyak putaran antara mod bersebelahan ditambah fasa mod tunggal, mengikut jaringan segi empat tepat Clements et al. [1]. Setiap faktor menjadi Gate Evolution atom, yang dipetakan oleh peringkat boson-ke-qubit seperti yang lain.
Pemfaktoran itu sendiri ialah givens_decomposition(), yang dipanggil oleh penguraian tersebut. Pemetaan daripada unitari kepada putaran banyak-jasad ialah homomorfisme kumpulan, jadi hasil darab putaran faktor tepat sama dengan putaran penuh pada ruang yang tidak dipangkas. Oleh itu, mesh tidak menambah sebarang ralat sendiri. Kelebihannya ialah kelokalan. Setiap evolusi yang dihasilkan hanya melibatkan sebutan Pauli bagi dua mod yang dicampurkannya (atau satu mod untuk fasa), bukannya sebutan merentasi semua mod.
Transpile putaran tiga mod rawak dan periksa apa yang dihasilkan oleh saluran paip. Unitari generik memerlukan maksimum tiga putaran, serta tiga fasa, dan setiap faktor ialah PauliEvolutionGate tersendiri. Kesemuanya diisytiharkan pada seluruh register, tetapi pengendali di dalamnya hanya bertindak pada beberapa qubit:
>>> from qiskit.quantum_info import random_unitary
>>>
>>> random = np.asarray(random_unitary(3, seed=7).data)
>>> spec = LevelSpec(2, "unary_unprojected")
>>> rotation_circuit = BosonicCircuit(3)
>>> rotation_circuit.append(InitializeModes([1, 0, 0]), rotation_circuit.modes)
>>> rotation_circuit.append(ModeRotation(random), rotation_circuit.modes)
>>> pass_manager = generate_preset_occupation_pass_manager(spec)
>>> pass_manager.qubit = PassManager()
>>> qubit_circuit = pass_manager.run(rotation_circuit)
>>> dict(qubit_circuit.count_ops())
{'PauliEvolution': 6, 'x': 3}
>>> for instruction in qubit_circuit.data:
... if instruction.operation.name == "PauliEvolution":
... terms = instruction.operation.operator.to_sparse_list()
... print(len(terms), sorted({qubit for _, indices, _ in terms for qubit in indices}))
64 [0, 1, 2, 3, 4, 5]
64 [3, 4, 5, 6, 7, 8]
64 [0, 1, 2, 3, 4, 5]
2 [1, 2]
2 [4, 5]
2 [7, 8]
Dengan tiga aras bagi setiap mod, setiap mod menduduki tiga qubit. Pembahagi alur antara dua mod menyentuh enam qubit bagi mod-mod tersebut, dan fasa menyentuh qubit bagi satu mod.
Faktor-faktor ini tepat hanya apabila setiap evolusi disintesis dengan baik. Sebutan penjana pembahagi alur tidak berkomut, jadi formula hasil darab satu langkah lalai meninggalkan ralat dalam setiap faktor. Hantar formula hasil darab dengan lebih banyak pengulangan kepada pemalam Evolution yang memetakan faktor-faktor tersebut untuk mengurangkannya:
>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit.synthesis import LieTrotter
>>> from qiskit_bosons.circuit.library.synthesis import BosonicClementsDecomposition
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>> from qiskit_bosons.transpiler import (
... BosonicCircuitToDAG,
... BosonicPassManager,
... QuantumDAGToCircuit,
... )
>>> from qiskit_bosons.transpiler.passes import (
... B2BSynthesis,
... B2QSynthesis,
... MapperFnEvolutionSynthesis,
... OccupationB2QLayout,
... OccupationInitializeModesSynthesis,
... )
>>>
>>> def with_product_formula(product_formula):
... synthesis = B2QSynthesis()
... synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(
... lambda operator, num_qubits: occupation_encoding(operator, spec, num_qubits),
... product_formula,
... )
... synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
... return MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... optimization=BosonicPassManager(
... B2BSynthesis({"ModeRotation": BosonicClementsDecomposition()})
... ),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
Satu boson yang memasuki mod 0 keluar pada mod dengan kebarangkalian . Bandingkan litar yang disintesis dengan keputusan tepat itu bagi bilangan pengulangan yang meningkat. Uraikan litar terlebih dahulu supaya formula hasil darab diaplikasikan; tanpa itu, simulator tepat seperti Statevector mengaplikasikan gate sebagai eksponen penuhnya dan menyembunyikan ralat.
>>> encoding = LevelEncoding.UNARY_UNPROJECTED
>>> width = encoding.num_qubits(3)
>>> exact = np.abs(random[:, 0]) ** 2
>>>
>>> def one_boson_probabilities(qubit_circuit):
... """Probability that the single boson sits in mode j, for each j."""
... state = Statevector(qubit_circuit)
... probabilities = []
... for j in range(3):
... occupations = [1 if mode == j else 0 for mode in range(3)]
... index = sum(
... encoding.level_to_bits(n) << (width * mode) for mode, n in enumerate(occupations)
... )
... probabilities.append(abs(state.data[index]) ** 2)
... return np.array(probabilities)
>>>
>>> for repetitions in (1, 4, 16):
... lowered = with_product_formula(LieTrotter(reps=repetitions)).run(rotation_circuit)
... error = np.max(np.abs(one_boson_probabilities(lowered.decompose(reps=2)) - exact))
... print(f"{repetitions:2d} repetitions: error {error:.1e}")
1 repetitions: error 1.2e-03
4 repetitions: error 7.6e-05
16 repetitions: error 4.7e-06
Ralat menurun dengan faktor kira-kira empat bagi setiap peningkatan empat kali ganda dalam pengulangan, seperti yang dijangkakan bagi formula tertib pertama. Kos meningkat secara berkadar, kerana setiap faktor diulang.
Merendahkan putaran tanpa menguraikannya
Penguraian dalam ruang bosonik ialah satu cara untuk merendahkan putaran. (Pustaka ini tidak menyertakan cara lain, tetapi peringkat boson-ke-qubit tidak bergantung padanya.) B2QSynthesis mencari pemalam mengikut nama gate, dan ModeRotation yang dibiarkan utuh sampai kepadanya seperti gate lain. Pemalam untuknya memetakan penjana ModeRotation.to_operator() sekali gus dan menghasilkan satu PauliEvolutionGate pada masa unit. Itu sahaja:
>>> from qiskit.circuit.library import PauliEvolutionGate
>>> from qiskit_bosons.transpiler.passes.utils import map_node_single_register
>>>
>>> class DirectModeRotationSynthesis:
... """Maps the generator of the whole rotation, with no bosonic decomposition."""
...
... def __init__(self, mapper_fn):
... self.mapper_fn = mapper_fn
...
... def run(self, in_node, out_dag, *, b2q_layout):
... modes, qreg = map_node_single_register(in_node, b2q_layout)
... generator = in_node.op.to_operator().relabel_modes(list(modes))
... observable = self.mapper_fn(generator, len(qreg))
... out_dag.apply_operation_back(PauliEvolutionGate(observable, time=1.0), tuple(qreg))
Daftarkannya untuk nama gate dalam pengurus laluan (pass manager) yang tidak menguraikannya:
>>> 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)
>>> synthesis.methods["ModeRotation"] = DirectModeRotationSynthesis(mapper_fn)
>>> direct = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> direct_circuit = direct.run(rotation_circuit)
>>> dict(direct_circuit.count_ops())
{'x': 3, 'PauliEvolution': 1}
>>> terms = direct_circuit.data[-1].operation.operator.to_sparse_list()
>>> len(terms), sorted({qubit for _, indices, _ in terms for qubit in indices})
(198, [0, 1, 2, 3, 4, 5, 6, 7, 8])
Putaran kini merupakan satu evolusi ke atas kesemua sembilan qubit, manakala mesh mempunyai enam, dan tiada satu pun yang merentangi lebih daripada enam. Kedua-duanya bukan penghampiran yang sama. Pemangkasan diaplikasikan sekali pada penjana bagi keseluruhan putaran di sini, dan pada setiap faktor secara berasingan dalam mesh (maka amaran mengenai had di atas). Laluan terus sama dengan occupation_encoding() bagi pengendali pada sebarang had, jadi ia merupakan rujukan untuk membandingkan mesh. Kosnya ialah kelokalan: sebutannya merentangi semua mod, dan formula hasil darab mempunyai lebih banyak sebutan untuk dibahagikan.
Tiada satu pun yang lebih baik secara umum, dan itulah sebab untuk membiarkan pilihan terbuka. Tulis pemalam seperti ini untuk membandingkan penurunan, menggunakan putaran khusus pengekodan, atau menyerahkan gate kepada sesuatu yang memahaminya secara asli.
Langkah seterusnya
-
Transpile litar bosonik menerangkan peringkat yang dijalankan oleh pengurus laluan pratetap, dan cara menggantikan pemalam yang mengendalikan gate.
-
Memetakan perwakilan pengendali merangkumi pemangkasan, pengekodan, dan
LevelSpecyang menetapkan susun atur setiap mod. -
Bekerja dengan litar bosonik menunjukkan cara memasang dan menggabungkan litar bosonik.
-
Dokumentasi API menyenaraikan
qiskit_bosons.circuit.libraryuntuk gate,qiskit_bosons.linalguntuk penguraian, danqiskit_bosons.transpiler.passesuntuk pemalam sintesis.
[1]
W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and I. A. Walmsley, Optimal design for universal multiport interferometers, Optica 3, 1460 (2016), arXiv:1603.08788.