Merotasi mode bosonik
Sebuah ModeRotation mencampur mode bosonik dengan matriks uniter , seperti jaringan beamsplitter dan phase shifter. Disebut pasif karena ia mempertahankan jumlah total boson: ia memindahkan boson antar mode dan tidak pernah menciptakan atau menghancurkannya. (Squeezing dan displacement adalah operasi aktif, dan rotasi mode tidak bisa mengungkapkannya.) Ia memetakan operator creation sebagai
yang pada seluruh ruang Fock adalah uniter many-body
Ini adalah gate di balik interferometer optik linear: kirim state Fock masuk, rotasikan mode-modenya, lalu baca bagaimana boson terdistribusi ulang. Seperti setiap gate dari BosonicCircuit (lihat Bekerja dengan Circuit bosonik), ia tidak membawa pemotongan maupun encoding. Keduanya masuk saat waktu transpile, dan panduan ini menunjukkan kenapa pemotongan butuh perhatian khusus untuk sebuah rotasi.
Bangun sebuah rotasi
Konstruktor menerima matriks uniter . Gate bekerja pada mode. Kelas ini tidak mengecek apakah matriksnya uniter, jadi itu tanggung jawabmu.
>>> 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
Rotasi umum punya konstruktor khusus. ModeRotation.beamsplitter() menerima sudut pencampuran dan fase opsional , dan memberi beamsplitter 50:50 yang seimbang. ModeRotation.phase_shift() menerima satu fase per mode.
>>> 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])
Kamu juga bisa mulai dari generator anti-Hermitian dan membiarkan gate meng-eksponensialkannya, yang memberi rotasi . Generatornya didapat kembali 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
Jembatan ke aljabar operator adalah to_operator(), yang mengembalikan generator Hermitian sebagai BosonOperator, sehingga gate-nya adalah . Untuk beamsplitter 50:50, itu berupa sepasang suku hopping dengan koefisien imajiner berlawanan. Operatornya Hermitian, yang memang dibutuhkan gate Evolution, dan ia mempertahankan jumlah partikel.
>>> 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 suku dibaca sebagai (is_creation, mode) dari kiri ke kanan, jadi yang kedua adalah .
Bekerja pada state Fock
Sebuah rotasi memindahkan boson antar mode, jadi efeknya paling mudah dilihat pada state Fock. Efek Hong-Ou-Mandel adalah contoh standarnya. Kirim satu boson ke tiap mode input dari beamsplitter seimbang. Dua jalur yang meninggalkan boson di mode output berbeda berinterferensi destruktif, sehingga state lenyap dan boson selalu keluar bersama, di atau , masing-masing dengan probabilitas setengah.
Bangun Circuit pada mode bosonik dan turunkan dengan preset pass manager. LevelSpec menetapkan pemotongan dan encoding pada titik ini. Cutoff max_occupation=2 mempertahankan level 0, 1, dan 2 dari setiap mode.
>>> 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)
Pipeline mengembalikan Circuit qubit biasa. Baca probabilitas hasil dari state akhir, di sini dengan Statevector milik Qiskit, yang eksak pada ukuran ini. Simulator apa pun yang mengembalikan amplitudo atau counts bisa dipakai. State Fock adalah satu computational basis state, yang indeksnya diberikan oleh pola bit dari LevelEncoding.level_to_bits(); qubit mode kedua mengikuti qubit mode 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 punya probabilitas nol, dan dua hasil bunched berbagi probabilitas secara merata.
Pilih cutoff
Hasil sebelumnya membutuhkan max_occupation=2. Jalankan Circuit yang sama dengan cutoff 1. Itu cukup untuk menampung state input , tapi tidak untuk state yang dihasilkan rotasi darinya, yang punya bobot pada dan :
>>> fock_probabilities(circuit, max_occupation=1)
{(1, 1): 1.0}
Circuit berjalan tanpa error apa pun, dan jawabannya salah: state tetap di dengan probabilitas satu. Rotasi mencampur sektor okupansi di dalam satu mode. Dua boson bisa berakhir di mode output yang sama, jadi state eksak punya bobot pada okupansi 2 di mode yang awalnya berisi satu boson. Dengan cutoff 1 level itu tidak ada, dan generator terpotong tidak bisa memindahkan state ke sana.
Batas pada okupansi state awal karena itu tidak cukup. Cutoff harus mencakup jumlah total boson yang bisa sampai ke satu mode mana pun dari rotasi. Untuk total boson, pilihan aman adalah cutoff .
Penalaran yang sama berlaku untuk tiap mode secara terpisah, jadi campuran cutoff berbeda per mode (lihat Deskripsikan pemotongan dengan LevelSpec) membuat rotasi mudah dipetakan secara keliru. Mode yang bisa menerima boson butuh cutoff yang mencakup apa yang bisa diterimanya, bukan hanya apa yang dipegangnya di awal. Pengecekan LevelSpec.truncation() membandingkan sebuah spec dengan batas bawah okupansi dari sebuah operator. Ia tidak bisa menandai kasus di atas. Setiap suku generator beamsplitter memegang satu operator creation, jadi batasnya satu dan pengecekan tidak melaporkan kekurangan pada cutoff 1, padahal hasilnya di sana salah:
>>> LevelSpec(1, "unary_unprojected").truncation(beamsplitter.to_operator())
[0, 0]
Peringatan lebih lanjut muncul dari cara Transpiler membangun rotasi (bagian berikutnya). Ia menerapkan rotasi sebagai urutan faktor dua-mode, dan dokumentasi plugin memperingatkan bahwa satu faktor bisa mengisi level yang tidak pernah dicapai rotasi lengkapnya. Urutan terpotong dan evolusi tunggal terpotong itu kemudian menjadi aproksimasi berbeda dari uniter yang sama. Pencarian atas 1.080 rotasi tiga-mode acak, dengan satu boson di masing-masing dari dua mode input dan tanpa okupansi ganda di output, tidak menemukan kasus seperti itu, jadi anggap ini sebagai tindakan pencegahan, bukan kegagalan yang umum. Konvergenkan cutoff seperti untuk Circuit lain, dengan menaikkannya sampai hasilnya berhenti berubah.
Cara Transpiler mensintesis sebuah rotasi
Tahap optimization dari preset pipeline menguraikan ModeRotation di ruang bosonik terlebih dahulu, dengan pass B2BSynthesis dan metode BosonicClementsDecomposition (terdaftar sebagai ModeRotation.Clements). Itu adalah pilihan preset, bukan persyaratan framework: bagian berikutnya menurunkan gate yang sama tanpa dekomposisi apa pun. Dekomposisi ini tidak meng-eksponensialkan generator penuh dalam satu langkah. Ia pertama memfaktorkan uniter menjadi paling banyak rotasi antara mode yang bersebelahan ditambah fase single-mode, mengikuti mesh persegi panjang dari Clements dkk. [1]. Setiap faktor menjadi gate Evolution atomik, yang dipetakan tahap boson-ke-qubit seperti yang lain.
Faktorisasi itu sendiri adalah givens_decomposition(), yang dipanggil oleh dekomposisi tersebut. Pemetaan dari sebuah unitary ke rotasi many-body adalah homomorfisme grup, jadi hasil kali rotasi-rotasi faktor persis sama dengan rotasi penuh pada ruang yang tidak dipotong. Dengan begitu, mesh ini tidak menambah galat apa pun. Keuntungannya ada pada lokalitas. Setiap evolusi yang dihasilkan hanya melibatkan suku Pauli dari dua mode yang dicampurnya (atau dari satu mode untuk sebuah fase), bukan suku-suku dari semua mode.
Transpile rotasi acak tiga mode lalu periksa apa yang dihasilkan pipeline. Unitary yang generik membutuhkan tiga rotasi maksimum, ditambah tiga fase, dan setiap faktor adalah PauliEvolutionGate tersendiri. Semuanya dideklarasikan pada seluruh register, tetapi operator di dalamnya hanya bekerja pada beberapa qubit saja:
>>> 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 level per mode, setiap mode menempati tiga qubit. Sebuah beamsplitter di antara dua mode menyentuh enam qubit dari kedua mode itu, dan sebuah fase menyentuh qubit dari satu mode.
Faktor-faktor ini baru eksak kalau setiap evolusi disintesis dengan baik. Suku-suku dari generator beamsplitter tidak saling komut, jadi product formula satu langkah bawaan menyisakan galat di setiap faktor. Berikan product formula dengan repetisi lebih banyak ke plugin Evolution yang memetakan faktor-faktor tersebut untuk mengurangi galat itu:
>>> 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 masuk ke mode 0 keluar di mode dengan probabilitas . Bandingkan circuit hasil sintesis dengan hasil eksak itu untuk jumlah repetisi yang meningkat. Dekomposisi circuit-nya dulu supaya product formula diterapkan; tanpa itu, simulator eksak seperti Statevector menerapkan gate sebagai eksponensial penuhnya dan menyembunyikan galatnya.
>>> 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
Galat turun sekitar empat kali lipat setiap kali repetisi naik empat kali lipat, sesuai harapan untuk formula orde pertama. Biayanya naik secara proporsional, karena setiap faktor diulang.
Menurunkan rotasi tanpa mendekomposisinya
Dekomposisi di ruang bosonik adalah salah satu cara untuk menurunkan rotasi. (Library ini tidak menyertakan cara lain, tetapi tahap boson-ke-qubit tidak bergantung padanya.) B2QSynthesis mencari plugin berdasarkan nama gate, dan ModeRotation yang dibiarkan utuh sampai ke sana seperti gate lainnya. Plugin untuknya memetakan generator dari ModeRotation.to_operator() sekaligus dan menghasilkan satu PauliEvolutionGate pada waktu satuan. Hanya itu saja:
>>> 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))
Daftarkan plugin itu untuk nama gate di pass manager yang tidak mendekomposisinya:
>>> 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])
Sekarang rotasinya adalah satu evolusi atas kesembilan qubit, sedangkan mesh punya enam, yang tidak satu pun mencakup lebih dari enam. Keduanya bukan pendekatan yang sama. Pemotongan di sini diterapkan sekali pada generator seluruh rotasi, dan pada setiap faktor secara terpisah di mesh (karena itu ada peringatan tentang cutoff di atas). Jalur langsung sama dengan occupation_encoding() dari operator pada cutoff berapa pun, jadi ini adalah acuan untuk membandingkan sebuah mesh. Harganya adalah lokalitas: suku-sukunya mencakup semua mode, dan product formula punya lebih banyak suku untuk dibagi.
Tidak ada yang lebih baik secara umum, dan itulah alasan untuk membiarkan pilihannya terbuka. Tulis plugin seperti ini untuk membandingkan cara penurunan, untuk memakai rotasi khusus encoding, atau untuk menyerahkan gate ke sesuatu yang memahaminya secara native.
Langkah berikutnya
-
Transpile bosonic circuit menjelaskan tahap-tahap yang dijalankan preset pass manager, dan cara mengganti plugin yang menangani sebuah gate.
-
Petakan representasi operator membahas pemotongan, encoding, dan
LevelSpecyang menetapkan tata letak setiap mode. -
Bekerja dengan bosonic circuit menunjukkan cara menyusun dan menggabungkan bosonic circuit.
-
Dokumentasi API mencantumkan
qiskit_bosons.circuit.libraryuntuk gate,qiskit_bosons.linalguntuk dekomposisi, danqiskit_bosons.transpiler.passesuntuk plugin 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.