Sari la conținutul principal

Intrări și ieșiri ale primitivelor

Versiuni de pachete

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.

qiskit[all]~=2.4.0

Această pagină oferă o prezentare generală a intrărilor și ieșirilor primitivelor Qiskit. Cu aceste primitive poți folosi o structură de date cunoscută sub numele de Primitive Unified Bloc (PUB) pentru a defini în mod eficient sarcini de lucru vectorizate. Aceste PUBuri sunt unitatea fundamentală de lucru pentru execuția sarcinilor de lucru. Ele sunt utilizate ca intrări pentru metoda run() a primitivelor Sampler și Estimator, care execută sarcina de lucru definită ca un job. Apoi, după ce job-ul s-a finalizat, rezultatele sunt returnate într-un format care depinde de PUBurile utilizate și de orice opțiuni specificate.

Prezentare generală a PUBurilor

Când apelezi metoda run() a unei primitive, principalul argument necesar este o list cu unul sau mai multe tupluri — câte unul pentru fiecare circuit executat de primitivă. Fiecare dintre aceste tupluri este considerat un PUB, iar elementele obligatorii ale fiecărui tuplu din listă depind de primitiva utilizată. Datele furnizate acestor tupluri pot fi, de asemenea, organizate într-o varietate de forme pentru a oferi flexibilitate în cadrul unei sarcini de lucru prin broadcasting — regulile acestuia fiind descrise într-o secțiune ulterioară.

Estimator PUB

Pentru primitiva Estimator, formatul PUB ar trebui să conțină cel mult patru valori:

  • Un singur QuantumCircuit, care poate conține unul sau mai multe obiecte Parameter
  • O listă cu unul sau mai multe observabile, care specifică valorile de așteptare de estimat, organizate într-un tablou (de exemplu, un singur observabil reprezentat ca un tablou 0-d, o listă de observabile ca un tablou 1-d și așa mai departe). Datele pot fi în oricare dintre formatele ObservablesArrayLike, cum ar fi Pauli, SparsePauliOp, PauliList sau str.
    notă

    Dacă ai două observabile care comută în PUBuri diferite, dar cu același Circuit, acestea nu vor fi estimate folosind aceeași măsurătoare. Fiecare PUB reprezintă o bază diferită pentru măsurătoare și, prin urmare, sunt necesare măsurători separate pentru fiecare PUB. Pentru a te asigura că observabilele care comută sunt estimate folosind aceeași măsurătoare, acestea trebuie grupate în același PUB.

  • O colecție de valori ale parametrilor pentru a lega Circuit-ul. Aceasta poate fi specificată ca un singur obiect de tip tablou în care ultimul indice este peste obiectele Parameter ale Circuit-ului, sau omisă (sau echivalent, setată la None) dacă Circuit-ul nu are obiecte Parameter.
  • (Opțional) o precizie țintă pentru valorile de așteptare de estimat

Sampler PUB

Pentru primitiva Sampler, formatul tuplului PUB conține cel mult trei valori:

  • Un singur QuantumCircuit, care poate conține unul sau mai multe obiecte Parameter Notă: Aceste circuit-uri trebuie să includă și instrucțiuni de măsurare pentru fiecare dintre qubit-urile care urmează să fie eșantionate.
  • O colecție de valori ale parametrilor pentru a lega Circuit-ul în raport cu θk\theta_k (necesară doar dacă sunt utilizate obiecte Parameter care trebuie legate la momentul execuției)
  • (Opțional) un număr de shots cu care să se măsoare Circuit-ul

Următorul cod demonstrează un exemplu de set de intrări vectorizate pentru primitiva Estimator.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit.circuit import (
Parameter,
QuantumCircuit,
ClassicalRegister,
QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray
from qiskit.primitives import StatevectorEstimator

import numpy as np

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)

# Transpile the circuit without providing a backend
pm = generate_preset_pass_manager(optimization_level=1)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout

# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
[
np.linspace(-np.pi, np.pi, 10),
np.linspace(-4 * np.pi, 4 * np.pi, 10),
]
).T

# Define three observables. The inner length-1 lists cause this array of
# observables to have shape (3, 1), rather than shape (3,) if they were
# omitted.
observables = [
[SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
[SparsePauliOp("XX")],
[SparsePauliOp("IY")],
]
# Apply the same layout as the transpiled circuit.
observables = [
[observable.apply_layout(layout) for observable in observable_set]
for observable_set in observables
]

# Estimate the expectation value for all 300 combinations of observables
# and parameter values, where the pub result will have shape (3, 100).
#
# This shape is due to our array of parameter bindings having shape
# (100, 2), combined with our array of observables having shape (3, 1).
estimator = StatevectorEstimator()
estimator_pub = (transpiled_circuit, observables, params)

# Run the transpiled circuit
# using the set of parameters and observables.

job = estimator.run([estimator_pub])
result = job.result()

Reguli de broadcasting

PUB-urile agregează elemente din mai multe array-uri (observabile și valori de parametri) urmând aceleași reguli de broadcasting ca NumPy. Această secțiune rezumă pe scurt acele reguli. Pentru o explicație detaliată, consultă documentația NumPy privind regulile de broadcasting.

Reguli:

  • Array-urile de intrare nu trebuie să aibă același număr de dimensiuni.
    • Array-ul rezultat va avea același număr de dimensiuni ca array-ul de intrare cu cele mai multe dimensiuni.
    • Mărimea fiecărei dimensiuni este cea mai mare mărime a dimensiunii corespunzătoare.
    • Dimensiunile lipsă sunt considerate a avea mărimea unu.
  • Comparațiile de formă încep cu dimensiunea cea mai din dreapta și continuă spre stânga.
  • Două dimensiuni sunt compatibile dacă mărimile lor sunt egale sau dacă una dintre ele este 1.

Exemple de perechi de array-uri care fac broadcasting:

A1 (1d array): 1
A2 (2d array): 3 x 5
Result (2d array): 3 x 5

A1 (3d array): 11 x 2 x 7
A2 (3d array): 11 x 1 x 7
Result (3d array): 11 x 2 x 7

Exemple de perechi de array-uri care nu fac broadcasting:

A1 (1d array): 5
A2 (1d array): 3

A1 (2d array): 2 x 1
# The following would work if the middle dimension were 2,
# instead of 5.
A2 (3d array): 6 x 5 x 4

Estimator returnează câte o estimare a valorii de așteptare pentru fiecare element al formei rezultate în urma broadcasting-ului.

Iată câteva exemple de tipare comune exprimate în termeni de broadcasting de array-uri. Reprezentarea lor vizuală corespunzătoare este prezentată în figura care urmează:

Seturile de valori de parametri sunt reprezentate prin array-uri n x m, iar array-urile de observabile sunt reprezentate prin unul sau mai multe array-uri cu o singură coloană. Pentru fiecare exemplu din codul anterior, seturile de valori de parametri sunt combinate cu array-ul lor de observabile pentru a crea estimările valorilor de așteptare rezultante.

  • Exemplul 1: (broadcasting al unui singur observabil) are un set de valori de parametri care este un array 5x1 și un array de observabile 1x1. Singurul element din array-ul de observabile este combinat cu fiecare element din setul de valori de parametri pentru a crea un singur array 5x1 în care fiecare element este o combinație a elementului original din setul de valori de parametri cu elementul din array-ul de observabile.

  • Exemplul 2: (zip) are un set de valori de parametri 5x1 și un array de observabile 5x1. Rezultatul este un array 5x1 în care fiecare element este o combinație a celui de-al n-lea element din setul de valori de parametri cu cel de-al n-lea element din array-ul de observabile.

  • Exemplul 3: (outer/produs) are un set de valori de parametri 1x6 și un array de observabile 4x1. Combinarea lor duce la un array 4x6 creat prin combinarea fiecărui element din setul de valori de parametri cu fiecare element din array-ul de observabile, astfel încât fiecare valoare de parametru devine o întreagă coloană în rezultat.

  • Exemplul 4: (generalizare standard nd) are un array de set de valori de parametri 3x6 și două array-uri de observabile 3x1. Acestea se combină pentru a crea două array-uri de ieșire 3x6 într-un mod similar cu exemplul anterior.

Această imagine ilustrează mai multe reprezentări vizuale ale broadcasting-ului de array-uri

# Broadcast single observable
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = SparsePauliOp("ZZZ") # shape ()
# >> pub result has shape (5,)

# Zip
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = [
SparsePauliOp(pauli) for pauli in ["III", "XXX", "YYY", "ZZZ", "XYZ"]
] # shape (5,)
# >> pub result has shape (5,)

# Outer/Product
parameter_values = np.random.uniform(size=(1, 6)) # shape (1, 6)
observables = [
[SparsePauliOp(pauli)] for pauli in ["III", "XXX", "YYY", "ZZZ"]
] # shape (4, 1)
# >> pub result has shape (4, 6)

# Standard nd generalization
parameter_values = np.random.uniform(size=(3, 6)) # shape (3, 6)
observables = [
[
[SparsePauliOp(["XII"])],
[SparsePauliOp(["IXI"])],
[SparsePauliOp(["IIX"])],
],
[
[SparsePauliOp(["ZII"])],
[SparsePauliOp(["IZI"])],
[SparsePauliOp(["IIZ"])],
],
] # shape (2, 3, 1)
# >> pub result has shape (2, 3, 6)
SparsePauliOp

Fiecare SparsePauliOp contează ca un singur element în acest context, indiferent de numărul de Pauli conținuți în SparsePauliOp. Astfel, în scopul acestor reguli de broadcasting, toate elementele de mai jos au aceeași formă:

a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()

Următoarele liste de operatori, deși echivalente în ceea ce privește informațiile conținute, au forme diferite:

list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"])
# list1 has shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")]
# list2 has shape (3, )

Prezentare generală a rezultatelor primitivelor

Odată ce unul sau mai multe PUB-uri sunt trimise la un QPU pentru execuție și un job se finalizează cu succes, datele sunt returnate sub forma unui obiect container PrimitiveResult. PrimitiveResult conține o listă iterabilă de obiecte PubResult care conțin rezultatele execuției pentru fiecare PUB. De exemplu, un job trimis cu 20 de PUB-uri va returna un obiect PrimitiveResult care conține o listă cu 20 de PubResult-uri, câte unul corespunzător fiecărui PUB.

Fiecare dintre aceste obiecte PubResult posedă atât un atribut data, cât și un atribut opțional metadata. Atributul data este un DataBin personalizat, care conține estimările valorilor de așteptare în cazul Estimator-ului, sau eșantioane ale ieșirii Circuit-ului în cazul Sampler-ului.

Atributul data poate include și alte informații specifice implementării, cum ar fi abaterile standard. Atributul metadata poate conține informații suplimentare specifice implementării despre execuția PUB-ului asociat.

Urmează o schiță vizuală a structurii de date PrimitiveResult:

└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object,
| | ## which includes data such as the following:
│ ├── evs
│ │ └── List of estimated expectation values in the shape
| | specified by the first pub
│ └── stds
│ └── List of calculated standard deviations in the
| same shape as above
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object,
| | ## which includes data such as the following:
| ├── evs
| │ └── List of estimated expectation values in the shape
| | specified by the second pub
| └── stds
| └── List of calculated standard deviations in the
| same shape as above
├── ...
├── ...
└── ...
notă

Cele de mai sus sunt un exemplu de date care ar putea fi returnate. Datele efectiv returnate depind de implementare.

Estimator output

Așa cum s-a menționat anterior, datele returnate în PubResult pentru primitiva Estimator depind de implementare. De exemplu, pot conține un tablou de valori de așteptare (PubResult.data.evs) și abaterile standard asociate (PubResult.data.stds).

Fragmentul de cod de mai jos descrie formatul PrimitiveResult (și al PubResult-ului asociat) pentru job-ul creat mai sus.

print(
f"The result of the submitted job had {len(result)} PUB and "
f"has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:"
f"\n {result[0].data}\n"
)
print(f"And this DataBin has attributes: {result[0].data.keys()}")
print(
"Recall that this shape is due to our array of parameter binding sets "
"having shape (100, 2) -- where 2 is the number of parameters in the circuit -- "
"combined with our array of observables having shape (3, 1)."
)

print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10)), metadata={'target_precision': 0.0, 'circuit_metadata': {}})], metadata={'version': 2})

The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10))

And this DataBin has attributes: dict_keys(['evs', 'stds'])
Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the
number of parameters in the circuit -- combined with our array of observables having shape (3, 1).

The expectation values measured from this PUB are:
[[ 3.06161700e-16 4.52395120e-01 4.36594428e-01 2.16506351e-01
6.33718361e-01 -6.33718361e-01 -2.16506351e-01 -4.36594428e-01
-4.52395120e-01 -3.06161700e-16]
[ 1.22464680e-16 6.42787610e-01 9.84807753e-01 8.66025404e-01
3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
-6.42787610e-01 -1.22464680e-16]
[ 4.89858720e-16 2.62002630e-01 -1.11618897e-01 -4.33012702e-01
9.25416578e-01 -9.25416578e-01 4.33012702e-01 1.11618897e-01
-2.62002630e-01 -4.89858720e-16]]

Sampler output

Când un job Sampler se finalizează cu succes, obiectul PrimitiveResult returnat conține o listă de obiecte SamplerPubResult, câte unul per PUB. Compartimentele de date ale acestor obiecte SamplerPubResult sunt obiecte de tip dicționar care conțin câte un BitArray per ClassicalRegister din Circuit.

Clasa BitArray este un container pentru date de shot ordonate. Mai în detaliu, stochează bitstring-urile eșantionate ca octeți într-un tablou bidimensional. Axa cea mai din stânga a acestui tablou parcurge shot-urile ordonate, în timp ce axa cea mai din dreapta parcurge octeții.

Ca prim exemplu, să analizăm următorul Circuit cu zece qubiți:

from qiskit.primitives import StatevectorSampler

# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure_all()

# transpile the circuit
transpiled_circuit = pm.run(circuit)

sampler = StatevectorSampler()

# run the Sampler job and retrieve the results

job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")

# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
Databin: DataBin(meas=BitArray(<shape=(), num_shots=1024, num_bits=10>))

BitArray: BitArray(<shape=(), num_shots=1024, num_bits=10>)

The shape of register `meas` is (1024, 2).

The bytes in register `alpha`, shot by shot:
[[ 0 0]
[ 3 255]
[ 0 0]
...
[ 3 255]
[ 3 255]
[ 3 255]]

Uneori poate fi convenabil să convertești din formatul de octeți al BitArray în bitstring-uri. Metoda get_count returnează un dicționar care mapează bitstring-urile la numărul de apariții ale acestora.

# optionally convert the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'0000000000': 492, '1111111111': 532}

Când un Circuit conține mai mult de un registru clasic, rezultatele sunt stocate în obiecte BitArray diferite. Exemplul următor modifică fragmentul anterior prin împărțirea registrului clasic în două registre distincte:

# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results

job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
BitArray for register 'alpha': BitArray(<shape=(), num_shots=1024, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=1024, num_bits=9>)

Utilizarea obiectelor BitArray pentru post-procesare performantă

Deoarece tablourile oferă în general o performanță mai bună față de dicționare, este recomandat să efectuezi orice post-procesare direct pe obiectele BitArray, nu pe dicționare de numărători. Clasa BitArray oferă o serie de metode pentru a efectua unele operații comune de post-procesare:

print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")

print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")

# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")

# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")

# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)

# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")

# concatenate the bitstrings in `alpha` and `beta` to "merge" the results
# of the two registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
The shape of register `alpha` is (1024, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]

The shape of register `beta` is (1024, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 1 255]
[ 1 255]
...
[ 0 0]
[ 0 0]
[ 1 255]]

The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (1024, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[7]
[7]
...
[0]
[0]
[7]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: -0.017578125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: -0.017578125

The shape of the merged results is (1024, 2).
The bytes of the merged results:
[[ 3 255]
[ 3 255]
[ 3 255]
...
[ 0 0]
[ 0 0]
[ 3 255]]

Metadatele rezultatelor

Pe lângă rezultatele execuției, obiectele PrimitiveResult și PubResult conțin un atribut opțional de metadate referitor la job-ul care a fost trimis. Metadatele returnate (dacă există) sunt specifice implementării.

# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")

print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")
The metadata of the PrimitiveResult is:
'version' : 2,

The metadata of the PubResult result is:
'shots' : 1024,
'circuit_metadata' : {},

Pași următori

Recomandări