Sari la conținutul principal

Feedforward clasic și flux de control

Versiuni de pachete

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm să folosești aceste versiuni sau versiuni mai noi.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Circuitele dinamice sunt acum disponibile pe toate Backend-urile

Noua versiune a circuitelor dinamice este acum disponibilă tuturor utilizatorilor pe toate Backend-urile. Poți rula acum circuite dinamice la scară de utilitate. Consultă anunțul pentru mai multe detalii.

Circuitele dinamice sunt instrumente puternice cu care poți măsura qubiți în mijlocul execuției unui Circuit cuantic și apoi efectua operații de logică clasică în cadrul circuitului, pe baza rezultatelor acelor măsurători la mijlocul circuitului. Acest proces este cunoscut și sub denumirea de feedforward clasic. Deși suntem la început în înțelegerea modului optim de a profita de circuitele dinamice, comunitatea de cercetare cuantică a identificat deja o serie de cazuri de utilizare, precum:

Aceste îmbunătățiri aduse de circuitele dinamice vin însă cu compromisuri. Măsurătorile la mijlocul circuitului și operațiile clasice au în general un timp de execuție mai lung decât porțile cu doi qubiți, iar această creștere a timpului poate anula beneficiile reducerii adâncimii circuitului. Prin urmare, reducerea duratei măsurătorilor la mijlocul circuitului reprezintă un domeniu de îmbunătățire pe care IBM Quantum® îl urmărește în noua versiune a circuitelor dinamice.

Specificația OpenQASM 3 definește o serie de structuri de flux de control, dar Qiskit Runtime acceptă în prezent doar instrucțiunea condițională if. În Qiskit SDK, aceasta corespunde metodei if_test din QuantumCircuit. Această metodă returnează un manager de context și este folosită de obicei într-o instrucțiune with. Acest ghid descrie cum să folosești această instrucțiune condițională.

notă

Exemplele de cod din acest ghid folosesc instrucțiunea standard de măsurare pentru măsurătorile la mijlocul circuitului. Cu toate acestea, se recomandă să folosești instrucțiunea MidCircuitMeasure în schimb, dacă Backend-ul o acceptă. Consultă documentația privind măsurătorile la mijlocul circuitului pentru detalii.

Instrucțiunea if

Instrucțiunea if este folosită pentru a efectua condiționat operații pe baza valorii unui bit sau registru clasic.

În exemplul de mai jos, aplicăm o poartă Hadamard unui qubit și îl măsurăm. Dacă rezultatul este 1, atunci aplicăm o poartă X pe qubit, ceea ce are efectul de a-l readuce la starea 0. Măsurăm apoi qubit-ul din nou. Rezultatul măsurătorii ar trebui să fie 0 cu probabilitate de 100%.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister

qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits

circuit.h(q0)
# Use MidCircuitMeasure() if it's supported by the backend.
# circuit.append(MidCircuitMeasure(), [q0], [c0])
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")

# example output counts: {'0': 1024}

Output of the previous code cell

Instrucțiunii with i se poate atribui o țintă care este ea însăși un manager de context, ce poate fi stocat și utilizat ulterior pentru a crea un bloc else, care se execută ori de câte ori conținutul blocului if nu este executat.

În exemplul de mai jos, inițializăm registre cu doi qubiți și două biți clasici. Aplicăm o poartă Hadamard primului qubit și îl măsurăm. Dacă rezultatul este 1, atunci aplicăm o poartă Hadamard pe al doilea qubit; altfel, aplicăm o poartă X pe al doilea qubit. În final, măsurăm și al doilea qubit.

qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits

circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)

circuit.draw("mpl")

# example output counts: {'01': 260, '11': 272, '10': 492}

Output of the previous code cell

Pe lângă condiționarea pe un singur bit clasic, este posibil și să condiționezi pe valoarea unui registru clasic compus din mai mulți biți.

În exemplul de mai jos, aplicăm porți Hadamard la doi qubiți și îi măsurăm. Dacă rezultatul este 01, adică primul qubit este 1 și al doilea qubit este 0, atunci aplicăm o poartă X unui al treilea qubit. În final, măsurăm al treilea qubit. Remarcă că, pentru claritate, am ales să specificăm starea celui de-al treilea bit clasic, care este 0, în condiția if. În desenul circuitului, condiția este indicată prin cercuri pe biții clasici pe care se condiționează. Un cerc negru indică condiționarea pe 1, în timp ce un cerc alb indică condiționarea pe 0.

qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits

circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.if_test((clbits, 0b001)):
circuit.x(q2)
circuit.measure(q2, c2)

circuit.draw("mpl")

# example output counts: {'101': 269, '011': 260, '000': 252, '010': 243}

Output of the previous code cell

Expresii clasice

Modulul de expresii clasice Qiskit qiskit.circuit.classical conține o reprezentare exploratorie a operațiilor de execuție pe valori clasice în timpul execuției circuitului. Din cauza limitărilor hardware, în prezent sunt acceptate doar condițiile QuantumCircuit.if_test().

Următorul exemplu arată că poți folosi calculul parității pentru a crea o stare GHZ cu n qubiți folosind circuite dinamice. Mai întâi, generează n/2n/2 perechi Bell pe qubiți adiacenți. Apoi, lipește aceste perechi împreună folosind un strat de porți CNOT între perechi. Măsori apoi qubit-ul țintă al tuturor porților CNOT anterioare și resetezi fiecare qubit măsurat la starea 0\vert 0 \rangle. Aplici XX la fiecare poziție nemăsurată pentru care paritatea tuturor biților precedenți este impară. În final, porțile CNOT sunt aplicate qubiților măsurați pentru a restabili entanglement-ul pierdut în măsurătoare.

În calculul parității, primul element al expresiei construite implică ridicarea obiectului Python mr[0] la un nod Value (lift este folosit pentru a transforma obiecte arbitrare în expresii clasice). Acest lucru nu este necesar pentru mr[1] și posibilul registru clasic următor, deoarece acestea sunt intrări pentru expr.bit_xor, iar orice ridicare necesară este efectuată automat în aceste cazuri. Astfel de expresii pot fi construite în bucle și alte constructe.

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr

num_qubits = 8
if num_qubits % 2 or num_qubits < 4:
raise ValueError("num_qubits must be an even integer ≥ 4")
meas_qubits = list(range(2, num_qubits, 2)) # qubits to measure and reset

qr = QuantumRegister(num_qubits, "qr")
mr = ClassicalRegister(len(meas_qubits), "m")
qc = QuantumCircuit(qr, mr)

# Create local Bell pairs
qc.reset(qr)
qc.h(qr[::2])
for ctrl in range(0, num_qubits, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])

# Glue neighboring pairs
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])

# Measure boundary qubits between pairs,reset to 0
for k, q in enumerate(meas_qubits):
qc.measure(qr[q], mr[k])
qc.reset(qr[q])

# Parity-conditioned X corrections
# Each non-measured qubit gets flipped iff the parity (XOR) of all
# preceding measurement bits is 1
for tgt in range(num_qubits):
if tgt in meas_qubits: # skip measured qubits
continue
# all measurement registers whose physical qubit index < tgt
left_bits = [k for k, q in enumerate(meas_qubits) if q < tgt]
if not left_bits: # skip if list empty
continue

# build XOR-parity expression
parity = expr.lift(
mr[left_bits[0]]
) # lift the first bit to Value so it will be treated like a boolean.
for k in left_bits[1:]:
parity = expr.bit_xor(
mr[k], parity
) # calculate parity with all other bits
with qc.if_test(parity): # Add X if parity is 1
qc.x(qr[tgt])

# Re-entangle measured qubits
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
qc.draw(output="mpl", style="iqp", idle_wires=False, fold=-1)

Output of the previous code cell

Găsește Backend-uri care acceptă circuite dinamice

Pentru a găsi toate Backend-urile la care contul tău poate accesa și care acceptă circuite dinamice, rulează cod similar cu cel de mai jos. Acest exemplu presupune că ai salvat credențialele de autentificare. Poți, de asemenea, să specifici explicit credențialele atunci când inițializezi contul tău de serviciu Qiskit Runtime. Aceasta ți-ar permite să vizualizezi Backend-urile disponibile pe o instanță specifică sau un tip de plan, de exemplu.

Note
  • Backend-urile disponibile pentru cont depind de instanța specificată în credențiale.
  • Noua versiune a circuitelor dinamice este acum disponibilă tuturor utilizatorilor pe toate Backend-urile. Consultă anunțul pentru mai multe detalii.
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]

Limitări Qiskit Runtime

Fii conștient de următoarele constrângeri atunci când rulezi circuite dinamice în Qiskit Runtime.

  • Din cauza memoriei fizice limitate de pe electronica de control, există și o limită pentru numărul de instrucțiuni if și dimensiunea operanzilor acestora. Această limită este o funcție a numărului de difuzări și a numărului de biți difuzați într-un job (nu un circuit).

    La procesarea unei condiții if, datele de măsurătoare trebuie transferate la logica de control pentru a efectua acea evaluare. O difuzare este un transfer de date clasice unice, iar biții difuzați reprezintă numărul de biți clasici transferați. Consideră următoarele:

    c0 = ClassicalRegister(3)
    c1 = ClassicalRegister(5)
    ...
    with circuit.if_test((c0, 1)) ...
    with circuit.if_test((c0, 3)) ...
    with circuit.if_test((c1[2], 1)) ...

    În exemplul de cod anterior, primele două obiecte if_test pe c0 sunt considerate o singură difuzare, deoarece conținutul lui c0 nu s-a schimbat și, prin urmare, nu trebuie re-difuzat. if_test pe c1 este o a doua difuzare. Prima difuzează toți cei trei biți din c0, iar a doua difuzează doar un singur bit, rezultând un total de patru biți difuzați.

    În prezent, dacă difuzezi 60 de biți de fiecare dată, atunci job-ul poate avea aproximativ 300 de difuzări. Dacă difuzezi doar un singur bit de fiecare dată, însă, atunci job-ul poate avea 2400 de difuzări.

  • Operandul folosit într-o instrucțiune if_test trebuie să aibă 32 sau mai puțini biți. Astfel, dacă compari un întreg ClassicalRegister, dimensiunea acelui ClassicalRegister trebuie să fie de 32 sau mai puțini biți. Dacă compari doar un singur bit dintr-un ClassicalRegister, însă, acel ClassicalRegister poate fi de orice dimensiune (deoarece operandul este de doar un bit).

    De exemplu, blocul de cod "Nu este valid" nu funcționează deoarece cr are mai mult de 32 de biți. Poți, totuși, să folosești un registru clasic mai lat de 32 de biți dacă testezi doar un singur bit, după cum se arată în blocul de cod "Valid".

       cr = ClassicalRegister(50)
    qr = QuantumRegister(50)
    circuit = QuantumCircuit(qr, cr)
    ...
    circ.measure(qr, cr)
    with circ.if_test((cr, 15)):
    ...
  • Condiționalele imbricate nu sunt permise. De exemplu, următorul bloc de cod nu va funcționa deoarece conține un if_test în interiorul altui if_test:

       c1 = ClassicalRegister(1, "c1")
    c2 = ClassicalRegister(2, "c2")
    ...
    with circ.if_test((c1, 1)):
    with circ.if_test(c2, 1)):
    ...
  • Nu este acceptată utilizarea reset sau a măsurătorilor în interiorul condiționalelor.

  • Operațiile aritmetice nu sunt acceptate.

  • Consultă tabelul de funcționalități OpenQASM 3 pentru a determina ce funcționalități OpenQASM 3 sunt acceptate în Qiskit și Qiskit Runtime.

  • Când OpenQASM 3 (în loc de QuantumCircuit) este folosit ca format de intrare pentru a transmite circuite către primitivele Qiskit Runtime, sunt acceptate doar instrucțiunile care pot fi încărcate în Qiskit. Operațiile clasice, de exemplu, nu sunt acceptate deoarece nu pot fi încărcate în Qiskit. Consultă Importă un program OpenQASM 3 în Qiskit pentru mai multe informații.

  • Instrucțiunile for, while și switch nu sunt acceptate.

Folosește circuite dinamice cu Estimator

Deoarece Estimator nu acceptă circuite dinamice, poți folosi Sampler și îți poți construi propriile circuite de măsurătoare. Alternativ, poți folosi primitiva Executor, care acceptă circuite dinamice.

Pentru a replica comportamentul Estimator, urmează acest proces:

  1. Grupează termenii tuturor observabilelor într-o partiție. Aceasta se poate face folosind API-ul PauliList, de exemplu.
    notă

    Poți folosi atributul primitiv BitArray pentru a calcula valorile de așteptare ale observabilelor furnizate.

  2. Execută un circuit de schimbare a bazei per partiție (orice schimbare de bază trebuie efectuată pentru fiecare partiție). Consultă utilitarul addon pentru baze de măsurătoare measurement_bases module pentru mai multe informații. Începe cu utilitarele.
  3. Adaugă împreună rezultatele pentru fiecare partiție.

Pași următori

Recomandări