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

อินพุตและเอาต์พุตของ Samplex

บทนำ​

Samplex เป็นชนิดหลักของไลบรารี samplomatic โดย samplex แทนการแจกแจงความน่าจะเป็นแบบพารามิเตอร์เหนือพารามิเตอร์ของ template circuit บางตัว พร้อมกับฟิลด์ที่เป็นอาร์เรย์อื่น ๆ ที่ใช้เมื่อประมวลผลข้อมูลที่เก็บได้จากการรัน template circuit ที่ผูกค่าแล้ว อินเทอร์เฟซหลักของมันคือเมธอด sample() ที่สุ่มจากการแจกแจงนี้เพื่อสร้างกลุ่มของอาร์เรย์

คู่มือนี้อธิบายวิธีใช้อินเทอร์เฟซ sample() วิธีสอบถามและระบุอินพุตที่จำเป็น และวิธีตรวจสอบเอาต์พุตที่คาดหวัง อาร์เรย์ที่ส่งให้และส่งคืนจาก sample() มี type ที่เข้มงวด กล่าวคือ สำหรับอินสแตนซ์ของ samplex ชื่อ ชนิด และรูปร่างของอาร์เรย์เหล่านั้นถูกกำหนดไว้และสอบถามได้ก่อนที่จะสุ่มตัวอย่าง อินพุตและเอาต์พุตที่มีอยู่ใน samplex ขึ้นอยู่กับหลายปัจจัย เช่น มีการทำ measurement twirling หรือไม่ และจำเป็นต้องมี noise injection หรือไม่ ด้วยเหตุนี้ โดยทั่วไปแล้วอินสแตนซ์ samplex สองตัวจึงไม่จำเป็นต้องมีอินพุตและเอาต์พุตเหมือนกัน

การตั้งค่า​

ก่อนอื่น ให้สร้าง circuit ที่ใส่ box แล้ว และ build มันเป็นคู่ samplex กับ template ซึ่งจะใช้ในตัวอย่างถัดไป โปรดทราบว่าเมื่อใส่ box ให้ circuit ด้วยตนเอง คู่มือนี้ใช้ชื่อ alpha, beta, ref1, ref2, mod_ref1, mod_ref2, mod_ref3 และ conclude นอกจากนี้ circuit ยังมีออบเจกต์ Parameter สี่ตัว คู่มือนี้แสดงให้เห็นว่าแต่ละตัวมีบทบาทอย่างไรในอินพุตและเอาต์พุตของ samplex ดูคู่มือ transpiler สำหรับข้อมูลเกี่ยวกับการใส่ box ให้ circuit โดยอัตโนมัติ

import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit import ClassicalRegister, Parameter, QuantumCircuit, QuantumRegister
from qiskit.quantum_info import Operator, Pauli, PauliLindbladMap

from samplomatic import ChangeBasis, InjectNoise, Twirl, build

# our circuit has two classical registers named alpha and beta
circuit = QuantumCircuit(
QuantumRegister(4), alpha := ClassicalRegister(3, "alpha"), beta := ClassicalRegister(1, "beta")
)

# the first box is only twirled
with circuit.box([Twirl()]):
for idx in range(4):
circuit.rx(Parameter(f"a{idx}"), idx)
circuit.cz(0, 1)
circuit.cz(1, 2)

# the second box is twirled, and has noise injected
with circuit.box([Twirl(), InjectNoise(ref="ref1", modifier_ref="mod_ref1")]):
circuit.h(1)
circuit.cz(1, 2)

# the third box is twirled, and has different noise injected
with circuit.box([Twirl(), InjectNoise(ref="ref2", modifier_ref="mod_ref2")]):
circuit.rx(0.1, range(4))
circuit.cz(0, 1)
circuit.cz(1, 2)

# the fourth box is the same as the second, but with a different modifer ref
with circuit.box([Twirl(), InjectNoise(ref="ref1", modifier_ref="mod_ref3")]):
circuit.h(1)
circuit.cz(1, 2)

circuit.barrier()

# the final two boxes twirl, and add a basis change in one case
with circuit.box([Twirl(), ChangeBasis(ref="conclude")]):
circuit.measure(range(3), alpha)

with circuit.box([Twirl()]):
circuit.measure([3], beta)

circuit.draw("mpl")

../_images/88f6576d7e2fd76bfb52d5133c6bc07e908a28941c6538f5251ac508cfb202f7.png

ถัดไป เรียก build() กับ circuit ที่ใส่ box แล้วเพื่อสร้างคู่ template และ samplex คุณจะเห็นว่าแต่ละ box ถูกเปลี่ยนเป็น “แซนด์วิช” ของ barrier ที่มีชิ้นส่วน rz-sx-rz-sx-rz เพื่อทำ dressing

template, samplex = build(circuit)

template.draw("mpl", fold=100)

../_images/ec8456d0d0a5928c5d056184cbd1842dcf7aefdc7cdba428f62092b27646d601.png

พลอต samplex DAG แล้วเลื่อนเมาส์ไปบน node เพื่อดูสิ่งต่อไปนี้

  • Node สำหรับการสุ่มตัวอย่างทั้งหมด (ดาว) มีหน้าที่สร้าง randomization สำหรับ twirling สุ่มตัวอย่างจาก noise model หรือ inject การเปลี่ยน basis

  • Node สำหรับการรวบรวมทั้งหมด (โบว์ไท) มีหน้าที่สร้างเอาต์พุตเป็น slice ซึ่งคือ measurement flip หรือมุมของพารามิเตอร์

  • Node ตัวกลางทั้งหมด (วงกลม) มีหน้าที่จัดการข้อมูลในรูปแบบต่าง ๆ

samplex.draw()

สอบถามอินพุตที่จำเป็นและเอาต์พุตที่คาดหวัง​

วิธีที่ง่ายที่สุดในการดูอินพุตที่จำเป็นและเอาต์พุตที่คาดหวังคือการพิมพ์ออบเจกต์ Samplex รายการอาร์เรย์จะถูกจัดรูปแบบเป็น '{name}' <{type}[{shape}...]> และอินพุตที่จำเป็นจะอยู่ก่อนอินพุตที่ไม่บังคับ

print(samplex)
Samplex(<58 nodes>)
Inputs:
- 'basis_changes.conclude' <uint8[3]>: Basis changing gates, in the symplectic ordering
I=0, Z=1, X=2, and Y=3.
- 'parameter_values' <float64[4]>: Input parameter values to use during sampling.
- 'pauli_lindblad_maps.ref1' <PauliLindbladMap>: A PauliLindblad map acting on 2 qubits,
with 'num_terms_ref1' terms.
- 'pauli_lindblad_maps.ref2' <PauliLindbladMap>: A PauliLindblad map acting on 4 qubits,
with 'num_terms_ref2' terms.

- 'local_scales.mod_ref1' <float64['num_terms_ref1']>: (Optional) An array of factors by
which to scale individual rates of a Pauli Lindblad map, where the order should match the
corresponding list of terms.
- 'local_scales.mod_ref2' <float64['num_terms_ref2']>: (Optional) An array of factors by
which to scale individual rates of a Pauli Lindblad map, where the order should match the
corresponding list of terms.
- 'local_scales.mod_ref3' <float64['num_terms_ref1']>: (Optional) An array of factors by
which to scale individual rates of a Pauli Lindblad map, where the order should match the
corresponding list of terms.
- 'noise_scales.mod_ref1' <float64[]>: (Optional) A scalar factor by which to scale rates
of a Pauli Lindblad map.
- 'noise_scales.mod_ref2' <float64[]>: (Optional) A scalar factor by which to scale rates
of a Pauli Lindblad map.
- 'noise_scales.mod_ref3' <float64[]>: (Optional) A scalar factor by which to scale rates
of a Pauli Lindblad map.
Outputs:
* 'measurement_flips.alpha' <bool['num_randomizations', 1, 3]>: Bit-flip corrections
for measurement twirling.
* 'measurement_flips.beta' <bool['num_randomizations', 1, 1]>: Bit-flip corrections for
measurement twirling.
* 'parameter_values' <float32['num_randomizations', 48]>: Parameter values valid for an
associated template circuit.
* 'pauli_signs' <bool['num_randomizations', 3]>: Signs from sampled Pauli Lindblad
maps, where boolean values represent the parity of the number of non-trivial factors in the
sampled error that arise from negative rates. In other words, in order to implement basic
PEC, the sign used to correct expectation values should be ``(-1)**bool_value``. The order
matches the iteration order of boxes in the original circuit with noise injection
annotations.

inputs() และ outputs() ของ samplex รวมถึงข้อมูลที่ละเอียดกว่า เช่น ชื่อ รูปร่าง ชนิด และคำอธิบาย ก็สอบถามผ่านโปรแกรมได้เช่นกัน ชนิดที่ส่งคืนของทั้งสองเมธอดคือ TensorInterface ตัวอย่างเช่น โค้ดต่อไปนี้แสดงรายการข้อกำหนดทั้งหมดของอินเทอร์เฟซอินพุต

samplex.inputs().specs
[TensorSpecification('basis_changes.conclude', (3,), dtype('uint8'), 'Basis changing gates, in the symplectic ordering I=0, Z=1, X=2, and Y=3.'),
TensorSpecification('local_scales.mod_ref1', ('num_terms_ref1',), dtype('float64'), 'An array of factors by which to scale individual rates of a Pauli Lindblad map, where the order should match the corresponding list of terms.', optional=True),
TensorSpecification('local_scales.mod_ref2', ('num_terms_ref2',), dtype('float64'), 'An array of factors by which to scale individual rates of a Pauli Lindblad map, where the order should match the corresponding list of terms.', optional=True),
TensorSpecification('local_scales.mod_ref3', ('num_terms_ref1',), dtype('float64'), 'An array of factors by which to scale individual rates of a Pauli Lindblad map, where the order should match the corresponding list of terms.', optional=True),
TensorSpecification('noise_scales.mod_ref1', (), dtype('float64'), 'A scalar factor by which to scale rates of a Pauli Lindblad map.', optional=True),
TensorSpecification('noise_scales.mod_ref2', (), dtype('float64'), 'A scalar factor by which to scale rates of a Pauli Lindblad map.', optional=True),
TensorSpecification('noise_scales.mod_ref3', (), dtype('float64'), 'A scalar factor by which to scale rates of a Pauli Lindblad map.', optional=True),
TensorSpecification('parameter_values', (4,), dtype('float64'), 'Input parameter values to use during sampling.'),
PauliLindbladMapSpecification('pauli_lindblad_maps.ref1', num_qubits=2, num_terms=num_terms_ref1),
PauliLindbladMapSpecification('pauli_lindblad_maps.ref2', num_qubits=4, num_terms=num_terms_ref2)]

ตัวอย่างถัดไปพิมพ์ข้อมูลบางส่วนเกี่ยวกับตัวระบุเอาต์พุตที่ชื่อมีสตริง "flips"

for spec in samplex.outputs().get_specs("flips"):
print(spec.name, spec.shape, spec.dtype)
measurement_flips.alpha ('num_randomizations', 1, 3) bool
measurement_flips.beta ('num_randomizations', 1, 1) bool

ผูกค่าอินพุตและสุ่มตัวอย่าง​

หากต้องการ sample() samplex ต้องมีค่าอย่างน้อยสำหรับ inputs() ที่ถูกระบุว่าจำเป็น ค่าเหล่านี้จะถูกผูกกับอินเทอร์เฟซอินพุตก่อน เพื่อให้มันตรวจสอบ type ที่จำเป็นทั้งหมด (หรือแปลง type ในบางกรณี) และวิเคราะห์รูปร่างได้ หาก type หรือรูปร่างไม่ถูกต้อง หรือขาดค่าที่จำเป็น จะเกิดข้อผิดพลาดแบบละเอียด

ผูกอินพุต​

ในตัวอย่างต่อไปนี้ เมธอด sample() ได้รับค่าอินพุตที่จำเป็น จากนั้นจึงใช้สุ่ม randomization สามชุดจาก samplex การผูกค่าที่จำเป็นจาก dressing คือ Pauli Lindblad map สำหรับ noise injection และอาร์เรย์เปลี่ยน basis นอกจากนี้ยังต้องมีค่าของพารามิเตอร์ทั้งสี่ตัวใน circuit ที่ใส่ box ดั้งเดิม ซึ่งในกรณีนี้ตั้งเป็น np.linspace(0, 1, 4) samplex จะรวมพารามิเตอร์เหล่านี้เข้ากับ dressing ดังนั้นมันจึงกลายเป็นการปรับเปลี่ยนค่าพารามิเตอร์เอาต์พุตของ samplex โดยปริยาย

inputs = samplex.inputs().bind(
# bind() concatenates nested dicts with a '.' so that we can
# set 'pauli_lindblad_maps.noise0/1' as follows. note that the
# names 'ref1' and 'ref2' derive from the names in the InjectNoise
# annotation.
pauli_lindblad_maps={
"ref1": PauliLindbladMap.identity(2),
"ref2": PauliLindbladMap.identity(4),
},
# likewise, we can set all basis changes as follows, where
# the name 'conclude' comes from our basis change annotation
basis_changes={"conclude": [0, 1, 2]},
# we must provide values for all parameters in the original circuit
parameter_values=np.linspace(0, 1, 4),
)

outputs = samplex.sample(inputs, num_randomizations=3)
print(outputs)
SamplexOutput(<
- Dimension constraints: num_randomizations=3

* 'measurement_flips.alpha' <bool['num_randomizations', 1, 3]>: Bit-flip corrections for
measurement twirling.
* 'measurement_flips.beta' <bool['num_randomizations', 1, 1]>: Bit-flip corrections for
measurement twirling.
* 'parameter_values' <float32['num_randomizations', 48]>: Parameter values valid for an
associated template circuit.
* 'pauli_signs' <bool['num_randomizations', 3]>: Signs from sampled Pauli Lindblad maps,
where boolean values represent the parity of the number of non-trivial factors in the sampled
error that arise from negative rates. In other words, in order to implement basic PEC, the
sign used to correct expectation values should be ``(-1)**bool_value``. The order matches the
iteration order of boxes in the original circuit with noise injection annotations.
>)

สามารถผูกอินพุตกับอินเทอร์เฟซได้ผ่านการเรียก bind() หลายครั้ง หรือโดยการกำหนดค่าให้ item โดยตรง แม้ bind() จะรองรับรูปแบบ dictionary ซ้อนกันเพื่อความสะดวก แต่ตัวอินเทอร์เฟซเป็นออบเจกต์ mapping แบบแบน และรองรับไวยากรณ์ mapping ตามปกติทั้งหมด เมื่อพบอินพุตที่จำเป็นครบทุกตัวแล้ว fully_bound จะเป็นจริง

inputs = (
samplex.inputs()
.bind(
pauli_lindblad_maps={
"ref1": PauliLindbladMap.identity(2),
"ref2": PauliLindbladMap.identity(4),
}
)
.bind(parameter_values=np.linspace(0, 1, 4))
)

# once the final requirement is bound, fully_bound becomes True
print("Fully bound?", inputs.fully_bound)
inputs["basis_changes.conclude"] = [0, 2, 3]
print("Fully bound?", inputs.fully_bound)

# setting optional values does not affect whether the interface is fully bound
inputs["noise_scales.mod_ref1"] = 3
print("Fully bound?", inputs.fully_bound)
Fully bound? False
Fully bound? True
Fully bound? True

ออบเจกต์ TensorInterface ทั้งหมด รวมถึงซับคลาส SamplexOutput ทำหน้าที่เป็น mapping กับข้อมูลที่ถูกผูกไว้ ซึ่งหมายความว่าคุณสามารถดึงค่าพารามิเตอร์ของ template ได้ ดังตัวอย่างต่อไปนี้ ในกรณีนี้มีรูปร่าง (3, 48) เพราะขอ randomization สามชุด และ template circuit มีพารามิเตอร์ 48 ตัว

print("(num_randomizations, num_template_params) =", outputs["parameter_values"].shape)
print(outputs["parameter_values"][0, :])
(num_randomizations, num_template_params) = (3, 48)
[ 0.0000000e+00 0.0000000e+00 0.0000000e+00 1.5707964e+00
3.3333334e-01 1.5707964e+00 -1.5707964e+00 2.4749260e+00
-1.5707964e+00 1.5707964e+00 1.0000000e+00 1.5707964e+00
0.0000000e+00 1.5707964e+00 0.0000000e+00 3.1415927e+00
0.0000000e+00 0.0000000e+00 1.5707964e+00 3.0415926e+00
-1.5707964e+00 -1.5707964e+00 3.0415926e+00 -1.5707964e+00
-1.5707964e+00 1.0000000e-01 1.5707964e+00 1.5707964e+00
1.0000000e-01 1.5707964e+00 3.1415927e+00 1.5707964e+00
0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 3.1415927e+00 0.0000000e+00 3.1415927e+00
0.0000000e+00 0.0000000e+00 3.1415927e+00 1.5707964e+00
0.0000000e+00 4.4408921e-16 3.1415927e+00 0.0000000e+00]

ระบุอินพุตเป็น dictionary​

แทนที่จะส่ง TensorInterface ที่ผูกค่าครบแล้ว คุณสามารถระบุอินพุตเป็น dictionary มาตรฐานได้ ดังที่แสดงในเซลล์ด้านล่าง ในกรณีนี้ เมธอด sample() จะผูกรายการใน dictionary เข้ากับอินเทอร์เฟซอินพุตของ samplex ภายใน เพื่อตรวจสอบว่ามีอินพุตที่จำเป็นครบถ้วนและเข้ากันได้

inputs = {
"pauli_lindblad_maps": {
"ref1": PauliLindbladMap.identity(2),
"ref2": PauliLindbladMap.identity(4),
},
"basis_changes": {"conclude": [0, 1, 2]},
"parameter_values": np.linspace(0, 1, 4),
}
outputs = samplex.sample(inputs, num_randomizations=3)

ชนิดของอินพุต​

ปัจจุบันมีชนิดค่าของอินเทอร์เฟซสองแบบ

  • แบบอาร์เรย์: มีรูปร่างและชนิดข้อมูล ค่าที่ผูกจะถูกแปลงเป็นชนิดข้อมูลนั้นหากเป็นไปได้ มิติใดก็ได้สามารถเป็น free dimension

  • Pauli Lindblad map: ต้องเป็นชนิด qiskit.quantum_info.PauliLindbladMap และต้องกระทำกับจำนวน qubit ที่กำหนด จำนวน term ใน map เป็น free dimension

# up to precision loss, these are equivalent via automatic type coersion of array inputs
samplex.inputs()["parameter_values"] = [1, 2, 8, 9]
samplex.inputs()["parameter_values"] = np.array([1, 2, 8, 9], dtype=np.float16)

Free dimension​

คลาส TensorInterface มีแนวคิดของ free dimension ซึ่งเป็นขนาดจำนวนเต็มที่มีชื่อ และค่าของมันยังไม่แน่นอนจนกว่าจะมีข้อมูลบางอย่างถูกผูกกับอินเทอร์เฟซเพื่อกำหนดค่านั้น free dimension ทั้งหมดที่ชื่อเดียวกันต้องได้ขนาดที่สอดคล้องกันเมื่อผูกข้อมูล มิฉะนั้นจะเกิดข้อผิดพลาด ตัวอย่างเช่น ใน outputs() ของ samplex ใด ๆ แกนอาร์เรย์ที่ตรงกับ randomization เป็น free parameter ที่มีชื่อ "num_randomizations" ตามธรรมเนียม เมื่อเรียก sample() มันจะผูกค่ากับเอาต์พุต

print("All free dimensions:", outputs.free_dimensions)
print("Current constraints:", outputs.bound_dimensions)
All free dimensions: {'num_randomizations'}
Current constraints: {'num_randomizations': 3}

อีกสถานการณ์ free dimension ที่พบบ่อยคือการใช้ข้อจำกัดของ term ร่วมกันระหว่างข้อกำหนดของ Pauli lindblad map กับตัวปรับแต่งของมัน qiskit.quantum_info.PauliLindbladMap คือลำดับของ term ที่เรียงกัน โดยแต่ละ term มีอัตราแบบ floating point และการแทน Pauli แบบ sparse ฟังก์ชัน build() จะเพิ่มตัวระบุ "pauli_lindblad_maps.<ref>" สำหรับชนิดนี้ในแต่ละ annotation InjectNoise และหากมี modifier_ref ก็จะเพิ่มตัวระบุ "local_scales.<modifier_ref>" ด้วย ทั้งสองใช้ free dimension "num_terms_<ref>" ร่วมกัน ซึ่งระบุว่ามี Pauli Lindblad term กี่ตัวใน noise model เพราะตัวหลังต้อง zip กับอัตราของตัวแรกเพื่อปรับสเกล

# both the PauliLindbladMap and the local scales imply 2 terms, so that the free dimension
# 'num_terms_noise2' is satisfied
inputs = samplex.inputs().bind(
pauli_lindblad_maps={"ref2": PauliLindbladMap.from_list([("XXYZ", 0.1), ("IIXX", 0.2)])},
local_scales={"mod_ref2": [1, 2]},
)
print("All free dimensions:", inputs.free_dimensions)
print("Current constraints:", inputs.bound_dimensions)
All free dimensions: {'num_terms_ref1', 'num_terms_ref2'}
Current constraints: {'num_terms_ref2': 2}

ข้อตกลงเรื่องลำดับของ qubit​

Samplex ที่สร้างจาก circuit ที่ใส่ box แล้วด้วยฟังก์ชัน build() อาจต้องการอินพุตแบบอาร์เรย์ให้กับ sample() ซึ่งมีหนึ่งดัชนีที่ตรงกับ qubit ใน box ของ circuit ส่วนนี้อธิบายข้อตกลงเรื่องลำดับที่ควรใช้กับแกนเหล่านั้น โดยสรุป: ใช้ลำดับ qubit ของ circuit ที่ใส่ box โดยจำกัดเฉพาะ qubit ของ box นั้น

ตัวอย่างเช่น สมมติว่าคุณต้องการทราบวิธีระบุ Pauli Lindblad map สำหรับ "noise1" ลำดับของ qubit ใน noise map จะอิงตาม qubit เหล่านี้

box_of_interest = circuit[1]
qubit_ordering_convention = [qubit for qubit in circuit.qubits if qubit in box_of_interest.qubits]
qubit_ordering_convention
[<Qubit register=(4, "q0"), index=1>, <Qubit register=(4, "q0"), index=2>]

กล่าวคือ หากคุณต้องการให้ <Qubit register=(4, "q0"), index=2> มี noise แบบ X คุณควรกำหนด Pauli Lindblad map แบบนี้ เพราะมันอยู่ที่ดัชนี 1 ของ qubit_ordering_convention

samplex.inputs().bind(
pauli_lindblad_maps={
"ref1": (noise1 := PauliLindbladMap.from_sparse_list([("X", [1], 0.12)], num_qubits=2))
}
)
noise1
<PauliLindbladMap with 1 term on 2 qubits: (0.12)L(X_1)>

เช่นเดียวกันสำหรับการเปลี่ยน basis หากคุณต้องการหมุนไปยัง eigenstate +1 ของตัวดำเนินการ X, Y และ Z สำหรับ qubit 0, 1 และ 2 ของ circuit ตามลำดับ คุณควรใช้อาร์เรย์ต่อไปนี้ โดยจำไว้ว่าไลบรารีนี้ (และ Qiskit) ใช้ข้อตกลง I=0I=0, Z=1Z=1, X=2X=2 และ Y=3Y=3

samplex.inputs().bind(basis_changes={"conclude": (basis_change := [2, 3, 1])})
basis_change
[2, 3, 1]

โปรดทราบว่าข้อตกลงเรื่องลำดับของ qubit ไม่ใช่ ลำดับที่กำหนดโดยสิ่งใดสิ่งหนึ่งต่อไปนี้

  • box_instruction.qubits โดยที่ box_instruction คือ qiskit.circuit.CircuitInstruction ที่มีการดำเนินการเป็น qiskit.circuit.BoxOp เพราะลำดับของรายการ qubit นี้อาจขึ้นอยู่กับลำดับที่คำสั่งถูกเพิ่มเข้าไปในบริบทของ box ซึ่งจะเป็นข้อตกลงที่ทำให้สับสน ตัวอย่างเช่น with box(): circuit.x([0, 1]) และ with box(): circuit.x([1, 0]) จะให้ลำดับที่ต่างกัน

  • box_instruction.operation.body.qubits เพราะ qubit เหล่านี้อาจไม่ปรากฏใน circuit ชั้นนอกด้วยซ้ำ Qubit ภายในตัว body จริงของคำสั่ง BoxOp (หรือการดำเนินการ control flow อื่นใด เช่น IfElseOp) รับประกันความหมายที่สอดคล้องกันเฉพาะภายในขอบเขตนั้นเท่านั้น ข้อตกลงนี้จะนิยามไม่ชัดเจนและใช้งานยากโดยทั่วไป

การทดสอบในเครื่องด้วย template circuit​

หาก Samplex ถูกสร้างโดยใช้ฟังก์ชัน build() ค่าพารามิเตอร์เอาต์พุตควรเข้ากันได้โดยตรงกับพารามิเตอร์ของ template circuit ที่เกี่ยวข้อง ตัวอย่างเช่น คุณสามารถผูกค่าของ randomization ชุดหนึ่งกับ template เพื่อตรวจสอบในเครื่องได้

inputs = samplex.inputs().bind(
pauli_lindblad_maps={
"ref1": PauliLindbladMap.identity(2),
"ref2": PauliLindbladMap.identity(4),
},
basis_changes={"conclude": [0, 0, 0]},
parameter_values=np.linspace(0, 1, 4),
)

outputs = samplex.sample(inputs, num_randomizations=3)

bound_template = template.assign_parameters(outputs["parameter_values"][2])
bound_template.draw("mpl", fold=1000)

../_images/1d82c46209f8648a2df572cfef8ca256f47952566d34386761ab942aa6e50afd.png

วิธีนี้ช่วยให้คุณตรวจสอบได้ว่า samplex ให้ตัวอย่างเอาต์พุตตามที่คาดหวัง ตัวอย่างเช่น โค้ดต่อไปนี้แปลง circuit ที่ผูกค่าแล้วเป็นออบเจกต์ qiskit.quantum_info.Operator และ compose กับ bitflip Pauli เพื่อตรวจสอบด้วยสายตาว่า randomization ทั้งสามชุดเทียบเท่าเชิงตรรกะกับ circuit ฐาน

from samplomatic.utils import unbox

# Operator requires that we unpack all of the boxes first
unboxed = unbox(circuit).assign_parameters(inputs["parameter_values"])
# We want to plot unitary matrix amplitudes, so we need to remove the non-unitary measurements
unboxed.remove_final_measurements()
unboxed_unitary = Operator(unboxed)

# Plot magnitudes of the unitary matrix represenation of the circuit
plt.subplot(1, 4, 1)
plt.imshow(np.abs(unboxed_unitary))
plt.title("Base Circuit")

# For each randomization we sampled, plot the unitary matrix representation
for idx in range(3):
bound_template = template.assign_parameters(outputs["parameter_values"][idx])
bound_template.remove_final_measurements()

# The example does measurement twirling by compiling random bitflip gates before the
# measurements, so we need to undo these at the operator level for each randomization.
# We do this by casting them to Paulis and composing with the bound circuit's unitary
alpha_flips = Pauli(([0, 0, 0], outputs["measurement_flips.alpha"][idx, 0]))
beta_flips = Pauli(([0], outputs["measurement_flips.beta"][idx, 0]))
flips = beta_flips ^ alpha_flips
bound_unitary = Operator(bound_template) & flips

# Plot magnitudes of the unitary matrix represenation of the circuit
plt.subplot(1, 4, idx + 2)
plt.imshow(np.abs(bound_unitary))
plt.title(f"Randomization {idx}")

plt.tight_layout()

../_images/af1aa74f8a0e8abed673d4cd43ff8b810c44297d2f3a42fcfb49ade811e51c58.png

ในทำนองเดียวกัน โค้ดต่อไปนี้ส่งค่าพารามิเตอร์เอาต์พุตและ template ให้ sampler เพื่อจำลอง 10,000 shot ของ randomization ทั้งหมด และเปรียบเทียบค่าคาดหมายที่ได้ (เฉพาะ register alpha) กับค่าคาดหมายจากการจำลอง circuit ฐาน

from qiskit.primitives import StatevectorEstimator as Estimator
from qiskit.primitives import StatevectorSampler as Sampler
from qiskit.primitives.containers import BitArray

# simulate some expectation values direction from the base circuit
estimator_job = Estimator().run([(unboxed, ["IZII", "IIZI", "IIIZ"])])
evs = estimator_job.result()[0].data["evs"]

# get the sampler data from each randomization, and do bitflip correction
sampler_job = Sampler().run([(template, outputs["parameter_values"])], shots=10_000)
alpha_data = sampler_job.result()[0].data["alpha"]
alpha_data ^= BitArray.from_bool_array(outputs["measurement_flips.alpha"], "little")

# compare the results
print(" EVs from base circuit:", evs)
print("EVs from randomizations:", alpha_data.expectation_values(["ZII", "IZI", "IIZ"]))
EVs from base circuit: [0.7819611 0.93553883 0.99500417]
EVs from randomizations: [0.789 0.9366 0.995 ]