Langkau ke kandungan utama
Projek ini dalam fasa beta. Jumpa pepijat? Buat isu GitHub. Ada soalan atau idea? Tanya dalam Discussions — kami suka dengar maklum balas anda!
Bagaimana kualiti terjemahan pada halaman ini?

Putarkan mod boson

Sebuah ModeRotation mencampurkan mod boson dengan matriks uniter UU, 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

bi†↦∑jUji bj† ,b^\dagger_i \mapsto \sum_j U_{ji} \, b^\dagger_j \, ,

yang pada ruang Fock penuh ialah uniter banyak-jasad

exp⁡(∑ijlog⁡(U)ij bi†bj) .\exp\left(\sum_{ij} \log(U)_{ij} \, b^\dagger_i b_j\right) \, .

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 M×MM \times M. Gate bertindak pada MM 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 θ\theta dan fasa pilihan ϕ\phi, dan θ=π/4\theta = \pi/4 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 KK dan biarkan Gate mengeksponenkannya, yang memberikan putaran exp⁡(K)\exp(K). 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 H=∑ijilog⁡(U)ij bi†bjH = \sum_{ij} \mathrm{i} \log(U)_{ij} \, b^\dagger_i b_j sebagai BosonOperator, supaya Gate itu ialah e−iHe^{-\mathrm{i} H}. 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 iπ4 b1†b0\mathrm{i} \tfrac{\pi}{4} \, b^\dagger_1 b_0.

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 ∣1,1⟩\lvert 1, 1 \rangle lenyap dan boson sentiasa keluar bersama, dalam ∣2,0⟩\lvert 2, 0 \rangle atau ∣0,2⟩\lvert 0, 2 \rangle, 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 ∣n0,n1⟩\lvert n_0, n_1 \rangle 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 ∣1,1⟩\lvert 1, 1 \rangle 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 ∣1,1⟩\lvert 1, 1 \rangle, tetapi tidak keadaan yang dihasilkan putaran daripadanya, yang mempunyai berat pada ∣2,0⟩\lvert 2, 0 \rangle dan ∣0,2⟩\lvert 0, 2 \rangle:

>>> fock_probabilities(circuit, max_occupation=1)
{(1, 1): 1.0}

Litar berjalan tanpa sebarang ralat, dan jawapannya salah: keadaan kekal pada ∣1,1⟩\lvert 1, 1 \rangle 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 NN boson keseluruhannya, pilihan selamat ialah had NN.

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 M(M−1)/2M(M-1)/2 putaran antara mod bersebelahan ditambah MM 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 3×33 \times 3 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 jj dengan kebarangkalian ∣Uj0∣2|U_{j0}|^2. 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​

[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.