ข้ามไปยังเนื้อหาหลัก
โครงการนี้อยู่ในช่วงเบต้า พบข้อผิดพลาด? สร้าง GitHub Issue มีคำถามหรือไอเดีย? ถามใน Discussions — ยินดีรับฟังความคิดเห็น!
คุณภาพการแปลในหน้านี้เป็นอย่างไร?

ทรานสไพล์ Circuit โบซอน

คู่มือนี้อธิบายวิธีทรานสไพล์ BosonicCircuit ให้เป็น QuantumCircuit มาตรฐาน Circuit โบซอนไม่มีการตัดทอน ไม่มีการเข้ารหัส และไม่มีจำนวนคิวบิต (ดู ทำงานกับ Circuit โบซอน) ดังนั้น Transpiler คือที่ที่ทางเลือกเหล่านั้นถูกกำหนด การตัดทอนและการเข้ารหัสอธิบายด้วย LevelSpec (ดู อธิบายการตัดทอนด้วย LevelSpec) และมาปเปอร์ที่เปลี่ยนตัวดำเนินการเป็นตัวดำเนินการคิวบิตอธิบายไว้ใน แมปการแทนตัวดำเนินการ ผลลัพธ์คือ QuantumCircuit ธรรมดา ตัวจำลองที่เข้ากันได้กับ Qiskit ใด ๆ จึงรันมันได้

คู่มือใช้ตัวอย่างเล็ก ๆ ตัวอย่างเดียวตลอด: สองโหมดโบซอน สถานะที่มีโบซอนโหมดละหนึ่งตัว และการวิวัฒน์ตามเวลาครั้งเดียวภายใต้แฮมิลโทเนียน Bose-Hubbard ที่พจน์พันธะและพจน์บนไซต์อยู่ในกลุ่มแยกกัน (ดู จัดกลุ่มพจน์ตัวดำเนินการ: ใช้โครงสร้างของตัวดำเนินการ):

>>> 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}

ขั้นตอนการทรานสไพล์​

ดังที่อธิบายใน qiskit_bosons.transpiler พาสแมเนเจอร์พรีเซ็ตในแพ็กเกจนี้แบ่งการทรานสไพล์เป็นขั้นตอนต่อไปนี้:

อินพุต​

แปลง Circuit เป็นโครงสร้างข้อมูลกราฟมีทิศทางไม่มีวงจร (DAG) ซึ่งเป็นการแทนที่ทุกพาสหลังจากนี้ทำงานกับมัน

การเพิ่มประสิทธิภาพ​

คง Circuit ไว้ในปริภูมิโบซอน พาสเหล่านี้ใช้โครงสร้างของตัวดำเนินการโบซอน โดยเฉพาะกลุ่มของมัน ขณะที่ยังไม่มีข้อมูลคิวบิต B2BSynthesis คือพาสที่แยกเกตลงตรงนี้ โดยมีปลั๊กอินสำหรับเกตแต่ละชนิด: สูตรผลคูณหรือ qDRIFT สำหรับ Evolution และเมช Clements สำหรับ ModeRotation (ดู สังเคราะห์เกต Evolution ในปริภูมิโบซอน สำหรับวิธีเลือกเมธอด) RelabelModes จัดลำดับโหมดใหม่ พรีเซ็ตแยกส่วนประกอบการหมุนโหมดในขั้นตอนนี้ และปล่อยอย่างอื่นไว้ตามเดิม

เลย์เอาต์​

แมปรีจิสเตอร์โหมดโบซอนแต่ละชุดไปยังรีจิสเตอร์ควอนตัม โดยทั่วไปโหมดโบซอนใช้หลายคิวบิต (dd ตัวภายใต้การเข้ารหัสแบบยูนารี ⌈log⁡2d⌉\lceil \log_2 d \rceil ภายใต้การเข้ารหัสแบบกะทัดรัด) รีจิสเตอร์ควอนตัมจึงกว้างกว่ารีจิสเตอร์โหมด OccupationB2QLayout หาความกว้างของรีจิสเตอร์จาก LevelSpec

การสังเคราะห์​

แปลงเกตโบซอนเป็นการดำเนินการคิวบิต หนึ่งปลั๊กอินต่อชนิดเกต การเข้ารหัสเกิดขึ้นในขั้นตอนนี้: ปลั๊กอินสำหรับเกต Evolution แมปตัวดำเนินการของมันเป็นตัวดำเนินการคิวบิต และปลั๊กอินสำหรับเกต InitializeModes ตั้งค่าคิวบิตที่เข้ารหัสการครอบครองแต่ละอัน แบริเออร์ถูกนำไปใช้กับทุกคิวบิตของแต่ละรีจิสเตอร์ที่มันแตะ

Qubit​

ทรานสไพล์ Circuit คิวบิตที่ได้ด้วยเมธอดของ Qiskit เอง เช่นไปยังเซตเกตฐานหรืออุปกรณ์ พรีเซ็ตมีขั้นตอนนี้เสมอ ตามค่าเริ่มต้นคือพาสแมเนเจอร์พรีเซ็ตของ Qiskit ซึ่งสังเคราะห์ทุก evolution เป็นเกต (ดู ดำเนินการต่อที่ระดับคิวบิต)

เอาต์พุต​

แปลงโครงสร้างข้อมูล DAG ภายในกลับเป็น QuantumCircuit

การตัดทอนและการเข้ารหัสไม่ได้ถูกเก็บใน Circuit พวกมันถูกส่งให้ Transpiler และเลย์เอาต์ การเตรียมสถานะ และมาปเปอร์ต้องตรงกันทั้งหมด พรีเซ็ตรับรองเรื่องนี้โดยส่ง LevelSpec ตัวเดียวกันให้ทั้งสาม

ตัวอย่างการใช้งานจริง​

generate_preset_occupation_pass_manager() สร้างไปป์ไลน์จากสเปก การลดระดับตัวอย่างภายใต้สองการเข้ารหัสเปลี่ยนเพียงสเปก พรีเซ็ตจบด้วยการทรานสไพล์ระดับคิวบิตของ Qiskit เอง ซึ่งจะสังเคราะห์ evolution เป็นเกต หากต้องการเห็น Circuit ตามที่ขั้นตอนโบซอนปล่อยออกมาพอดี ให้ทำให้ขั้นตอนนั้นว่างก่อน:

>>> 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}

สองการเข้ารหัสใช้หกและสี่คิวบิตตามลำดับ (สามระดับต่อโหมดภายใต้การเข้ารหัสแบบยูนารี สองคิวบิตต่อโหมดภายใต้รหัส Gray) แต่โครงสร้างของ Circuit เหมือนกัน: การดำเนินการ XGate สองตัวเตรียมการครอบครอง และ PauliEvolutionGate หนึ่งตัวนำการวิวัฒน์

เปรียบเทียบกับเวิร์กโฟลว์แบบดั้งเดิม​

คู่มือ Circuit โบซอน อธิบายเวิร์กโฟลว์แบบดั้งเดิม ซึ่งแมปแฮมิลโทเนียนเป็นตัวดำเนินการคิวบิต ก่อน สร้าง QuantumCircuit ทั้งสองเส้นทางให้ Circuit ที่เทียบเท่ากัน ต่างกันที่สิ่งที่ต้องทำใหม่เมื่อการตัดทอนเปลี่ยน Circuit โบซอนถูกสร้างครั้งเดียวและลดระดับที่ทุกค่าตัด ขณะที่เส้นทางแมปก่อนต้องแมปตัวดำเนินการใหม่และสร้าง 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

จำนวนเกตพันกันขึ้นกับวิธีที่ Qiskit สังเคราะห์เกต evolution ซึ่งเปลี่ยนไปตามรุ่น จึงควรพล็อตมัน:

CX count of the lowered circuit against the number of levels d, growing quickly with d.

จำนวนเพิ่มขึ้นอย่างรวดเร็วตามค่าตัด ซึ่งเป็นเหตุผลที่ควรให้ค่าตัดเป็นพารามิเตอร์ของการทรานสไพล์ ไม่ใช่ของ Circuit ทั้งสองเส้นทางเทียบเท่ากันที่นี่ แต่มีเพียงเส้นทางโบซอนที่สลับการจัดกลุ่มของตัวดำเนินการกับสูตรผลคูณได้ (ดู สังเคราะห์เกต Evolution ในปริภูมิโบซอน) หรือส่งการเข้ารหัสให้สเปกอื่นโดยไม่เปลี่ยนสักบรรทัดของ Circuit

สเปกที่มีค่าตัดเดียวปล่อยจำนวนโหมดไว้เปิด ดังนั้นพาสแมเนเจอร์ตัวเดียวรองรับ Circuit ที่มีความกว้างใดก็ได้ สเปกที่มีหนึ่งค่าตัดต่อหนึ่งโหมดกำหนดความกว้างไว้:

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

สร้างไปป์ไลน์ด้วยมือ​

preset คือ MultiStagePassManager ที่มีหนึ่งรายการต่อหนึ่งสเตจ คุณสามารถประกอบ pipeline เดียวกันนี้จากส่วนประกอบของมันได้ ซึ่งเป็นจุดเริ่มต้นของการปรับแต่งทุกอย่างด้านล่างนี้:

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

ฟังก์ชัน mapper รับ operator และความกว้างของ register ที่ layout คำนวณได้ แล้วคืนค่าเป็น SparseObservable เนื่องจากมันได้รับความกว้างมาด้วย การส่ง num_qubits ต่อไปยัง occupation_encoding() จะทำให้ mapper ตรวจสอบว่า spec พอดีกับ register หรือไม่ ดังนั้นถ้า layout กับการเข้ารหัสไม่ตรงกัน ก็จะล้มเหลวพร้อมข้อความที่ชัดเจน

เวิร์กโฟลว์ขั้นสูง​

กำหนด layout แบบระบุเอง​

CustomB2QLayout จะแทนที่สเตจ layout ด้วยการแมปที่คุณกำหนด จาก bosonic register แต่ละตัวของ circuit ไปยัง quantum register มีประโยชน์เมื่อ register ต้องกว้างกว่าที่ spec ต้องการ เช่น เพื่อเหลือ qubit ว่างไว้ใช้งานอย่างอื่น:

>>> 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 แบบกำหนดเองเป็นความรับผิดชอบของคุณ ปลั๊กอิน synthesis ยังคงอ้างอิง qubit ผ่าน spec ดังนั้น register ที่ไม่ตรงกับความกว้างของ spec จะให้ circuit ที่ผิดหรือเกิดข้อผิดพลาดลึกภายในปลั๊กอิน นอกจากนี้ layout แบบกำหนดเองยังข้ามการตรวจสอบที่ OccupationB2QLayout ทำหลังจาก RelabelModes (ดูหัวข้อถัดไป)

จัดลำดับ mode ใหม่​

RelabelModes เป็น pass ในสเตจ optimization ที่จัดลำดับ mode ใหม่ การเรียงสับเปลี่ยนถูกอ่านเป็นการแมปจากดัชนี mode เดิมไปยังดัชนี mode ใหม่ และถูกบันทึกไว้ใน metadata ของ circuit การจัดลำดับใหม่เปลี่ยนว่าแต่ละ mode ใช้ qubit ตัวใด จึงอาจลดความลึกของ circuit ที่ถูกลดระดับลงได้

เนื่องจากผลลัพธ์แสดงในลำดับ mode ใหม่ จึงต้องแมป bitstring ที่วัดได้กลับก่อนตีความ เมื่อแต่ละ mode ใช้หลาย qubit การแมปจะทำงานกับบล็อกของ qubit ไม่ใช่บิตเดี่ยว ตัวอย่างต่อไปนี้ relabel สาม mode ซึ่งแต่ละ mode ใช้สอง qubit ภายใต้ Gray code และกู้คืนการครอบครองเดิมจาก counts ที่วัดได้:

>>> # 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 ที่กู้คืนมาคือสิ่งที่ circuit สร้างขึ้นเมื่อไม่มีการ relabel อ่านตามลำดับ little-endian ของ Qiskit: mode 0 เก็บ Gray code ของระดับสอง (11), mode 1 เก็บระดับศูนย์ (00) และ mode 2 เก็บระดับหนึ่ง (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 ที่กำหนด cutoff ต่างกัน ให้แต่ละ mode ไม่สามารถใช้ร่วมกับ RelabelModes ได้ cutoff ผูกกับ mode ตามตำแหน่ง ดังนั้นการจัดลำดับ mode ใหม่จะทำให้ cutoff ไปอยู่กับ mode ที่ผิด OccupationB2QLayout จะปฏิเสธ circuit ที่ถูก relabel ในกรณีนั้น:

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

เลือกปลั๊กอิน synthesis​

B2QSynthesis ไม่มีปลั๊กอินเริ่มต้น preset จะลงทะเบียนหนึ่งตัวต่อชนิดของ gate ปลั๊กอินที่ใช้ได้ถูกจัดการโดย B2QSynthesisPluginManager ซึ่งแสดงรายการตามชื่อของ gate ที่ปลั๊กอินทำงานด้วย:

>>> 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']

เมธอดในตัวสองแบบคือ Evolution.MapperFn และ InitializeModes.Occupation แบบแรกสังเคราะห์ evolution จากฟังก์ชัน mapper ใดก็ได้ ซึ่งเป็นวิธีเสียบการเข้ารหัสแบบกำหนดเองเข้ามา (ดู Map operator representations) ไลบรารีไม่มีปลั๊กอินสำหรับ gate ModeRotation preset จะแยกมันใน bosonic space เป็น gate Evolution ก่อน แล้วเมธอดแรกจึงจัดการต่อ แต่ปลั๊กอินที่ลงทะเบียนไว้สำหรับ ModeRotation สามารถลดระดับ gate นั้นโดยตรงแทนได้ ดู Rotate bosonic modes และ Lower a rotation without decomposing it คุณสามารถลงทะเบียนเมธอดตามชื่อผ่านอาร์กิวเมนต์ config ของ B2QSynthesis หรือเขียนออบเจกต์ปลั๊กอินลงใน B2QSynthesis.methods โดยตรง เหมือนที่ตัวอย่างก่อนหน้าทำ

ดำเนินการต่อในระดับ qubit​

preset จะจบด้วยสเตจ qubit เสมอ ซึ่งก็คือ preset pass manager ของ Qiskit เอง อาร์กิวเมนต์แบบคีย์เวิร์ดของ generate_preset_occupation_pass_manager() จะถูกส่งต่อไปยัง qiskit.transpiler.generate_preset_pass_manager() ดังนั้นคุณจึงแปลงเป็นชุด gate พื้นฐานหรือกำหนดเป้าหมายเป็นอุปกรณ์ได้โดยตรง แนะนำให้ใช้ optimization_level=0 เพื่อให้ circuit ใกล้เคียงกับที่สเตจ bosonic สร้างไว้:

>>> 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']

มีอีกสองตัวเลือกที่พบบ่อย กำหนด PassManager ที่ว่างเปล่าให้สเตจ qubit เมื่อต้องการแสดง circuit ตามที่ pipeline สังเคราะห์ไว้พอดี อย่างที่ตัวอย่างข้างต้นทำ หรือประกอบ pipeline เองแล้วเพิ่ม pass manager ของ Qiskit ใดก็ได้เป็นสเตจ qubit ของ MultiStagePassManager สเตจต่าง ๆ ทำงานตามลำดับที่กำหนด ดังนั้นสเตจ qubit ต้องมาก่อน output เพราะ pass ของ Qiskit คาดหวัง DAG ไม่ใช่ 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']

ขั้นตอนถัดไป​