Skip to main content
IBM Quantum Platform

Ingressi e uscite Samplex


Introduzione

Samplex è il tipo principale della libreria Samplomatic. Un samplex rappresenta una distribuzione di probabilità parametrica sui parametri di un determinato circuito modello, con altri campi a valori vettoriali da utilizzare nella post-elaborazione dei dati raccolti dall'esecuzione del circuito modello associato. La sua interfaccia principale è il metodo sample() che attinge da questa distribuzione per generare una raccolta di array.

Questa guida illustra come utilizzare l'interfaccia sample() , come effettuare query e specificare gli input richiesti, nonché come verificare i risultati attesi. Gli array forniti a e restituiti da sample() sono fortemente tipizzati. In altre parole, nel caso di un samplex, i nomi, i tipi e le forme sono fissi e consultabili prima che venga effettuato il campionamento. Gli ingressi e le uscite presenti in un samplex dipendono da molti fattori. Ad esempio, se è presente il “twirling” della misurazione e se è necessaria l’iniezione di rumore. Per questo motivo, in generale, non ci si aspetta che due istanze di Samplex abbiano gli stessi input e output.


Configura

Per prima cosa, crea un circuito incorniciato e inseriscilo in una coppia composta da un samplex e un modello. Questi saranno utilizzati negli esempi che seguono. Si noti che, quando si definisce manualmente il circuito, questa guida utilizza i nomi alpha, beta, ref1, ref2, mod_ref1, mod_ref2, mod_ref3, e conclude. Inoltre, il circuito presenta quattro oggetti Parameter . Questa guida illustra il ruolo che ciascuno di essi svolge negli ingressi e nelle uscite del Samplex. Per informazioni sul raggruppamento automatico dei circuiti, consultare la guida al transpiler.

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

Successivamente, richiamare il build() circuito racchiuso nel riquadro per creare una coppia composta da modello e samplex. Vedrete come ciascuna delle scatole venga trasformata in un “sandwich” di barriera che include rz-sx-rz-sx-rz frammenti per realizzare la medicazione.

template, samplex = build(circuit)

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

Visualizza il DAG di Samplex e passa il mouse sui nodi per vedere quanto segue:

  • Tutti i nodi di campionamento (stelle) hanno il compito di generare randomizzazioni per il twirling, effettuare campionamenti da modelli di rumore o introdurre modifiche alla base.
  • Tutti i nodi di raccolta (a farfalla) sono responsabili del rendering delle sezioni di output, che corrispondono a inversioni di misura o angoli dei parametri.
  • Tutti i nodi intermedi (cerchi) sono responsabili di vari tipi di elaborazione dei dati.
samplex.draw()

Verificare gli input richiesti e gli output previsti

Il modo più semplice per visualizzare gli input richiesti e gli output previsti è stampare l'oggetto Samplex . Gli elementi dell'array sono formattati come '{name}' <{type}[{shape}...]>, e gli input obbligatori precedono quelli facoltativi.

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.

È inoltre possibile interrogare a livello di programmazione i inputs() e outputs() del samplex, nonché informazioni più dettagliate quali i loro nomi, le forme, i tipi e le descrizioni. Il tipo di ritorno di entrambi questi metodi è un TensorInterface. Ad esempio, il codice seguente elenca tutte le specifiche dell'interfaccia di input:

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

L'esempio seguente visualizza alcune informazioni relative agli specificatori di output i cui nomi contengono la stringa "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

Associa i valori di input e genera un campione

Per sample(), il samplex richiede i valori almeno per quei inputs() campi contrassegnati come obbligatori. Questi valori vengono innanzitutto associati a un'interfaccia di input, in modo che questa possa eseguire tutti i controlli di tipo necessari (o, in alcuni casi, la coercizione di tipo) e l'analisi della struttura. Se si riscontra un errore nei tipi o nelle forme, oppure se manca un valore obbligatorio, viene generato un messaggio di errore dettagliato.

Associare l'input

Nell'esempio seguente, al sample() metodo vengono passati i valori di input richiesti. Viene quindi utilizzato per estrarre tre randomizzazioni dal samplex. I legami richiesti dalle rappresentazioni sono le mappe di Pauli-Lindblad per l’iniezione di rumore e la matrice di cambio di base. Inoltre, sono richiesti i valori dei quattro parametri del circuito originale racchiuso nel riquadro, che in questo caso è impostato su np.linspace(0, 1, 4). Il samplex integra questi parametri nelle elaborazioni, per cui essi diventano di fatto delle modifiche ai valori dei parametri di output del 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.
>)

I valori di input possono essere associati all'interfaccia tramite più chiamate a oppure bind() tramite l'assegnazione diretta di elementi. Nonostante il formato a dizionario annidato, che offre bind() una maggiore praticità, si tratta di un oggetto di mappatura "piatto" che supporta tutta la consueta sintassi di mappatura. Una volta individuati tutti gli input richiesti, diventa fully_bound vero.

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

Tutti gli TensorInterface oggetti, compresa la sottoclasse SamplexOutput , associano gli oggetti della mappa ai dati che sono stati collegati ad essi. Ciò significa che è possibile ottenere, ad esempio, i valori dei parametri del modello come segue. In questo caso, ha una forma specifica poiché (3, 48) sono state richieste tre randomizzazioni e il circuito modello ha 48 parametri.

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]

Specificare l'input come dizionario

Invece di fornire un oggetto completamente associato TensorInterface, è possibile specificare gli input come un dizionario standard, come mostrato nella cella sottostante. In questo caso, il metodo sample() associa internamente le voci del dizionario all'interfaccia di input del samplex per verificare che tutti gli input richiesti siano presenti e compatibili.

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)

Tipi di input

Attualmente esistono due tipi di valori di interfaccia:

  • A valori array: hanno una forma e un tipo di dati. I valori associati vengono convertiti nel tipo di dati corrispondente, se possibile. Qualsiasi dimensione può essere una dimensione libera.
  • Mappature di Pauli-Lindblad: devono essere di tipo qiskit.quantum_info.PauliLindbladMap e devono agire sul numero prescritto di qubit. Il numero di termini presenti nella mappa è una dimensione libera.
# 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)

Dimensioni libere

La classe TensorInterface prevede il concetto di "dimensioni libere ", ovvero dimensioni di tipo intero i cui valori rimangono indeterminati finché non vengono associati dei dati all'interfaccia che li vincola. Tutte le dimensioni libere con lo stesso nome devono corrispondere a una misura coerente al momento del binding dei dati; in caso contrario, verrà generato un errore. Ad esempio, nel di outputs() qualsiasi samplex, l'asse dell'array corrispondente alle randomizzazioni è un parametro libero che, "num_randomizations" per convenzione, prende il nome di. Quando sample() viene chiamata, associa i valori alle uscite

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

Un altro scenario comune in cui le dimensioni sono libere è la condivisione dei vincoli sui termini tra le specifiche delle mappe di Pauli-Lindblad e i relativi modificatori. A qiskit.quantum_info.PauliLindbladMap è una sequenza ordinata di termini, in cui ciascun termine contiene un tasso in virgola mobile e una rappresentazione di Pauli sparsa. La funzione build() aggiunge uno specificatore "pauli_lindblad_maps.<ref>" per questo tipo per ogni InjectNoise annotazione e, se questa contiene un modifier_ref, viene aggiunto anche uno "local_scales.<modifier_ref>" specificatore. Entrambi condividono la dimensione libera che "num_terms_<ref>" specifica quanti termini di Pauli-Lindblad sono presenti nel modello di rumore, poiché quest’ultimo deve essere correlato ai tassi del primo per poterli scalare.

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

Convenzione di ordinamento dei qubit

I Samplex costruiti a partire da circuiti "boxed" tramite la funzione build() possono richiedere input con valori vettoriali sample(), in cui un indice corrisponde ai qubit presenti in un "box" del circuito. In questa sezione viene illustrata la convenzione di numerazione da utilizzare per tali assi. In sintesi: utilizzare l'ordine dei qubit del circuito racchiuso nel riquadro, limitandosi ai qubit contenuti nel riquadro stesso.

Ad esempio, supponiamo che tu voglia capire come specificare una mappa di Pauli-Lindblad per "noise1". Pertanto, l'ordine dei qubit nella mappa del rumore è definito in relazione a questi 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>]

Cioè, se vuoi <Qubit register=(4, "q0"), index=2> generare rumore con il rumore X , allora dovresti definire una mappa di Pauli-Lindblad come questa, perché si trova all’indice di 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)>

Lo stesso vale per le modifiche alla base. Se si desidera ruotare negli stati propri +1 degli operatori X, Y e Z rispettivamente per i qubit 0, 1 e 2 di circuit, è necessario utilizzare il seguente array, tenendo presente che in questa libreria (e in Qiskit) viene utilizzata la convenzione I=0I=0, Z=1Z=1, X=2X=2 e Y=3Y=3.

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

Si noti che la convenzione di ordinamento dei qubit non corrisponde all'ordine definito da nessuna delle seguenti opzioni:

  • box_instruction.qubits, dove box_instruction è un la cui qiskit.circuit.CircuitInstruction operazione è un qiskit.circuit.BoxOp, poiché l'ordine di questa lista di qubit potrebbe dipendere dall'ordine in cui le istruzioni sono state aggiunte al contesto della casella, il che costituirebbe una convenzione fonte di confusione. Ad esempio, e with box(): circuit.x([0, 1]) with box(): circuit.x([1, 0]) darrebbero luogo a ordinamenti diversi.
  • box_instruction.operation.body.qubits perché questi qubit potrebbero anche non comparire nel circuito esterno. Si garantisce che i qubit all’interno del corpo effettivo delle istruzioni BoxOp (o di qualsiasi altra operazione di flusso di controllo come IfElseOp) abbiano un significato coerente solo all’interno di tale ambito. Si tratterebbe di una convenzione poco chiara e, in generale, difficile da utilizzare.

Test locale con il circuito modello

Se un Samplex viene costruito utilizzando la funzione build() , i valori dei parametri di uscita dovrebbero essere direttamente compatibili con i parametri del circuito modello associato. Ad esempio, è possibile associare i valori di una delle randomizzazioni al modello per poterli esaminare localmente.

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

In questo modo è possibile verificare che il samplex stia generando i campioni previsti. Ad esempio, il codice seguente converte il circuito legato in un oggetto qiskit.quantum_info.Operator e lo combina con i Pauli bitflip per verificare visivamente che tutte e tre le randomizzazioni siano logicamente equivalenti al circuito di base.

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

Allo stesso modo, il codice seguente passa i valori dei parametri di output e il modello a un campionatore per simulare 10.000 campioni di tutte le randomizzazioni e confronta i valori attesi risultanti (solo del registro alfa) con i valori attesi di una simulazione del circuito di base.

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 ]
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.