Skip to main content
IBM Quantum Platform

Samplex 입출력


소개

Samplex 이는 samplomatic 라이브러리의 핵심 유형입니다. 샘플렉스(samplex)는 특정 템플릿 회로의 매개변수에 대한 매개변수 확률 분포를 나타내며, 바인딩된 템플릿 회로의 실행을 통해 수집된 데이터를 후처리할 때 사용할 수 있는 다른 배열형 필드들을 포함합니다. 이 인터페이스의 핵심은 이 분포에서 값을 추출하여 배열 모음을 생성하는 메서드입니다 sample() .

이 가이드에서는 sample() 인터페이스 사용 방법, 필요한 입력값을 조회 및 지정하는 방법, 그리고 예상되는 출력값을 확인하는 방법에 대해 설명합니다. 에 전달되거나 반환되는 배열은 강력한 타입을 sample() 갖습니다. 즉, 샘플렉스의 인스턴스의 경우, 샘플링이 수행되기 전에 그 이름, 유형 및 형태가 고정되어 있으며 이를 조회할 수 있습니다. 샘플렉스에 존재하는 입력과 출력은 여러 요인에 따라 달라집니다. 예를 들어, 측정 회전 현상이 발생하는지, 그리고 노이즈 주입이 필요한지 여부 등입니다. 이러한 이유로, 일반적으로 두 개의 샘플렉스 인스턴스가 동일한 입력과 출력을 가질 것으로 예상되지 않습니다.


설정

먼저, 박스형 회로를 구성하고 이를 샘플렉스와 템플릿 쌍으로 만들어 보세요. 이것들은 다음 예제에서 사용될 것입니다. 회로를 수동으로 박스 처리할 때, 이 가이드에서는 alpha, beta, ref1, ref2, mod_ref1, mod_ref2, mod_ref3, 및 라는 이름을 사용한다는 점에 유의하십시오 conclude. 또한, 이 회로에는 네 개의 객체가 Parameter 있습니다. 이 가이드에서는 이러한 요소들이 샘플렉스의 입력 및 출력에서 각각 어떤 역할을 하는지 설명합니다. 회로를 자동으로 박스 처리하는 방법에 대한 정보는 트랜스파일러 가이드를 참조하십시오.

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() 함수를 호출하여 템플릿과 샘플렉스 쌍을 생성합니다. 각 상자가 드레싱을 구현하기 위한 구성 rz-sx-rz-sx-rz 요소들을 포함하는 ‘샌드위치’ 형태의 장벽으로 어떻게 변환되는지 확인하실 수 있습니다.

template, samplex = build(circuit)

template.draw("mpl", fold=100)
../_images/ec8456d0d0a5928c5d056184cbd1842dcf7aefdc7cdba428f62092b27646d601.png

samplex DAG를 그래프로 표시하고 노드 위에 마우스를 올리면 다음과 같은 내용이 표시됩니다

  • 모든 샘플링 노드(별 모양)는 트위링에 필요한 무작위화 생성, 노이즈 모델로부터의 샘플링, 또는 기저 변환 적용을 담당합니다.
  • 모든 수집 노드(나비넥타이 모양)는 측정 플립 또는 매개변수 각도인 출력 슬라이스를 렌더링하는 역할을 담당합니다.
  • 모든 중간 노드(원)는 다양한 종류의 데이터 처리를 담당합니다.
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.

샘플렉스의 outputs() 및 inputs() 뿐만 아니라, 이름, 모양, 유형, 설명과 같은 보다 상세한 정보도 프로그래밍 방식으로 조회할 수 있습니다. 이 두 메서드의 반환 유형은 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() 대한 값이 필요합니다. 이러한 값들은 먼저 입력 인터페이스에 바인딩되어, 필요한 모든 유형 검사(또는 경우에 따라 유형 변환)와 형상 분석을 수행할 수 있게 됩니다. 유형이나 형식에 문제가 있거나, 필수 값이 누락된 경우, 상세한 오류 메시지가 발생합니다.

입력 바인딩

다음 예제에서는 메서드에 sample() 필요한 입력 값이 전달됩니다. 그런 다음 이를 사용하여 샘플렉스에서 세 번의 무작위 추출을 수행합니다. 드레싱에서 필요한 바인딩은 잡음 주입을 위한 파울리-린드블라드 매핑과 기저 변환 배열입니다. 또한, 원래 박스형 회로에 포함된 네 가지 매개변수의 값이 필요하며, 이 경우 해당 값은 로 설정되어 있습니다 np.linspace(0, 1, 4). 샘플렉스는 이러한 매개변수들을 드레싱에 반영하므로, 결과적으로 이는 샘플렉스의 출력 매개변수 값에 대한 수정으로 작용하게 됩니다.

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() 항목을 직접 할당하여 인터페이스에 바인딩할 수 있습니다. 편리성을 제공하는 bind() 중첩된 사전 형식임에도 불구하고, 이는 평면 매핑 객체이며 일반적인 모든 매핑 구문을 지원합니다. 필요한 모든 입력값이 모두 발견되면, 는 참이 됩니다 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

서브클래스를 SamplexOutput 포함한 모든 TensorInterface 객체는 해당 객체에 바인딩된 데이터에 대해 객체를 매핑합니다. 즉, 예를 들어 다음과 같이 템플릿 매개변수 값을 가져올 수 있습니다. 이 경우, 3회의 무작위화가 요청되었고 템플릿 회로에는 48개의 매개변수가 있으므로 (3, 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]

입력을 사전 형식으로 지정합니다

완전히 바인딩된 객체를 제공하는 대신 TensorInterface, 아래 셀에 표시된 것처럼 입력을 표준 사전 형태로 지정할 수 있습니다. 이 경우, 해당 메서드는 sample() 내부적으로 사전 항목을 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)

입력 유형

현재 두 가지 인터페이스 값 유형이 있습니다:

  • 배열 값: 모양과 데이터 유형을 모두 갖습니다. 바인딩된 값은 가능한 경우 해당 데이터 유형으로 강제 변환됩니다. 어떤 치수든 자유 치수가 될 수 있습니다.
  • Pauli Lindblad 매핑: 이는 유형이어야 하며 qiskit.quantum_info.PauliLindbladMap , 규정된 큐비트 수에 작용해야 합니다. 맵에 포함된 용어의 수는 자유 차원입니다.
# 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)

자유 치수

이 TensorInterface 클래스에는 ‘자유 차원’이라는 개념이 있는데, 이는 정수 크기로 명명되며, 이를 제한하는 인터페이스에 데이터가 바인딩될 때까지 그 값이 정해지지 않은 차원들을 말합니다. 데이터를 바인딩할 때, 동일한 이름을 가진 모든 자유 치수는 일관된 크기로 해결되어야 하며, 그렇지 않으면 오류가 발생합니다. 예를 들어, 임의의 outputs() 샘플렉스에서 무작위화에 대응하는 배열 축은 관례에 "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}

또 다른 일반적인 자유 차원 시나리오는 파울리-린드블라드 맵 사양과 그 수정자 간에 항 제약 조건을 공유하는 경우입니다. A는 항들의 순서된 순열이며 qiskit.quantum_info.PauliLindbladMap , 각 항은 부동소수점 비율과 스파스 파울리 표현을 포함한다. 이 build() 함수는 각 InjectNoise 어노테이션에 대해 "pauli_lindblad_maps.<ref>" 해당 유형의 지정자를 추가하며, 어노테이션에 가 포함되어 modifier_ref있는 경우 "local_scales.<modifier_ref>" 지정자도 함께 추가됩니다. 이 두 가지 모두 노이즈 모델에 파울리-린드블라드 항이 몇 개나 포함되어 있는지를 "num_terms_<ref>" 나타내는 자유 차원을 공유하는데, 이는 후자가 전자의 발생률을 기준으로 스케일링을 조정해야 하기 때문이다.

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

큐비트 순서 지정 규칙

함수를 build() 통해 박스형 회로로 구성된 샘플렉스는 배열 값을 입력으로 요구할 수 있으며 sample(), 이때 각 인덱스는 회로 박스 내의 큐비트에 해당합니다. 이 섹션에서는 이러한 축에 대해 적용해야 할 순서 지정 규칙을 설명합니다. 요약하자면: 상자 안에 표시된 회로의 큐비트 순서를 따르되, 해당 상자에 포함된 큐비트로만 제한하여 사용하십시오.

예를 들어, 에 대한 파울리-린드블라드 매핑을 어떻게 지정해야 하는지 알아내고 싶다고 가정해 봅시다 "noise1". 그러면 노이즈 맵에서 큐비트의 순서는 이러한 큐비트를 기준으로 다음과 같습니다:

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

즉, X 노이즈를 이용해 잡음을 발생시키고 <Qubit register=(4, "q0"), index=2> 싶다면, 이것이 의 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)>

기저 변경의 경우에도 마찬가지입니다. 의 큐비트 0, 1, 2에 대해 각각 연산자 X, Y, Z의 +1 고유상태로 회전시키려면 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]

큐비트 순서 지정 규칙은 다음 중 어느 하나에 의해 정의된 순서가 아니라는 점에 유의하십시오:

  • box_instruction.qubits, 여기서 box_instruction 는 qiskit.circuit.CircuitInstruction 연산이 인 이며 qiskit.circuit.BoxOp, 이는 큐비트 목록의 순서가 박스 컨텍스트에 명령어가 추가된 순서에 따라 달라질 수 있기 때문인데, 이는 혼란을 야기할 수 있는 규칙이기 때문이다. 예를 들어, 와 with box(): circuit.x([0, 1]) 는 서로 다른 순서를 나타냅니다 with box(): circuit.x([1, 0]) .
  • box_instruction.operation.body.qubits 왜냐하면 이러한 큐비트들은 외부 회로에 나타나지 않을 수도 있기 때문이다. 명령어 BoxOp 본문(또는 와 같은 기타 제어 흐름 연산 IfElseOp) 내부의 큐비트는 해당 범위 내에서만 일관된 의미를 갖는다고 보장됩니다. 이는 명확하게 정의되지 않은 관례가 될 것이며, 일반적으로 사용하기 어려울 것입니다.

템플릿 회로를 사용한 로컬 테스트

함수 build() 를 사용하여 를 구성하는 Samplex 경우, 출력 매개변수 값은 관련 템플릿 회로의 매개변수와 직접 호환되어야 합니다. 예를 들어, 무작위화 항목 중 하나에 대한 값을 템플릿에 바인딩하여 로컬에서 확인할 수 있습니다.

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

이를 통해 샘플렉스가 예상된 샘플을 출력하고 있는지 확인할 수 있습니다. 예를 들어, 다음 코드는 바인딩된 회로를 qiskit.quantum_info.Operator 객체로 형변환하고, 비트 플립 파울리 연산과 조합하여 세 가지 무작위화 모두 기본 회로와 논리적으로 동등한지 시각적으로 확인합니다.

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

마찬가지로, 다음 코드는 출력 매개변수 값과 템플릿을 샘플러에 전달하여 모든 무작위화 조건에 대해 10,000회의 시뮬레이션을 수행하고, 그 결과로 얻은 기대값(알파 레지스터에 한함)을 기본 회로 시뮬레이션의 기대값과 비교합니다.

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 ]
이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.