Zum Hauptinhalt springen

Verzögerte Zeitauflösung mit stretch

Die OpenQASM 3-Sprachspezifikation enthält einen stretch-Typ, mit dem du relative anstelle absoluter Zeitangaben für Operationen festlegen kannst. Die Unterstützung für stretch als Dauer für Delay-Instruktionen wurde in Qiskit v2.0.0 hinzugefügt. Der konkrete Wert einer stretch-Dauer wird zur Kompilierzeit aufgelöst, nachdem die genaue Dauer der kalibrierten Gates bekannt ist. Der Compiler versucht, die stretch-Dauer zu minimieren, wobei er Zeitbeschränkungen auf einem oder mehreren Qubits berücksichtigt. So kannst du Gate-Designs ausdrücken, wie zum Beispiel das gleichmäßige Verteilen von Gates (etwa zur Implementierung einer Echo-Entkopplungssequenz höherer Ordnung), das linksbündige Ausrichten einer Gate-Sequenz oder das Anwenden eines Gates für die Dauer eines Teilschaltkreises – ohne die genaue Zeitplanung zu kennen.

Beispiele

Dynamische Entkopplung

Ein häufiger Anwendungsfall von stretch ist die Anwendung dynamischer Entkopplung auf ein untätiges Qubit, während ein anderes Qubit bedingte Operationen durchführt.

Zum Beispiel können wir stretch verwenden, um eine XX-Sequenz zur dynamischen Entkopplung auf Qubit 1 anzuwenden, für die Dauer des bedingten Blocks auf Qubit 0, wie im folgenden Diagramm dargestellt:

Abbildung des folgenden Schaltkreises

Der entsprechende Schaltkreis würde wie folgt aussehen. Beachte, dass ein Paar von Barrieren benötigt wird, um die Grenzen dieser relativen Zeitplanung zu definieren.

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

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

# Add barriers to define the boundaries
circuit.barrier()
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q0)
with else_:
circuit.x(q0)

# Apply an XX DD sequence with stretch on qubit 1
s = circuit.add_stretch("s")
circuit.delay(s, q1)
circuit.x(q1)
circuit.delay(expr.mul(s, 2), q1)
circuit.x(q1)
circuit.delay(s, q1)
circuit.barrier()

Ausrichtung der Zeitplanung

Dieses Beispiel verwendet stretch, um sicherzustellen, dass eine Sequenz von Gates zwischen zwei Barrieren linksbündig ausgerichtet ist, unabhängig von ihrer tatsächlichen Dauer:

from qiskit import QuantumCircuit
from numpy import pi

qc = QuantumCircuit(5)
qc.barrier()
qc.cx(0, 1)
qc.u(pi/4, 0, pi/2, 2)
qc.cx(3, 4)

a = qc.add_stretch("a")
b = qc.add_stretch("b")
c = qc.add_stretch("c")

# Use the stretches as Delay duration.
qc.delay(a, [0, 1])
qc.delay(b, 2)
qc.delay(c, [3, 4])
qc.barrier()
hinweis

Wenn du stretch mit Qiskit Runtime verwendest, wird jeder Rest, der aus einer stretch-Auflösung entsteht, zur ersten Verzögerung hinzugefügt, die den stretch verwendet.

Beispiel:

a = circuit.add_stretch("a")
circuit.barrier(q0, q1)
circuit.delay(100, q0)
circuit.delay(a, q1) # resolve to 26
circuit.x(q1) # duration: 8
circuit.delay(a, q1) # resolve to 25
circuit.x(q1) # duration: 8
circuit.delay(a, q1) # resolve to 25
circuit.x(q1) # duration: 8
circuit.barrier(q0, q1)

Der obige Code löst sich zu einem Wert von 25 mit einem Rest von 1 auf. Die erste Verzögerung [a] erhält den Rest hinzugefügt.

Stretch-Auflösungsgleichung: a+8+a+8+a+8=100=3a+24a + 8 + a + 8 + a + 8 = 100 = 3*a + 24

Stretch-Werte in Qiskit Runtime anzeigen

Der tatsächliche Wert einer stretch-Dauer wird zur Kompilierzeit aufgelöst, nachdem der Schaltkreis geplant wurde. Wenn du einen Sampler-Job in Qiskit Runtime ausführst, kannst du die aufgelösten stretch-Werte in den Job-Ergebnis-Metadaten einsehen. Die Unterstützung für stretch in Qiskit Runtime ist derzeit experimentell, daher musst du zunächst eine experimentelle Option aktivieren, um den Abruf zu ermöglichen, und dann wie folgt direkt auf die Daten aus den Metadaten zugreifen:

# Enable stretch value retrieval.
sampler.options.experimental = {
"execution": {
"stretch_values": True,
"scheduler_timing": True,
},
}

# Access the stretch values from the metadata.
job_result = job.result()
circuit_stretch_values = job_result[0].metadata["compilation"]["stretch_values"]

# Visualize the timing.
# Use the sliders at the bottom, the controls at the top, and the legend on the side
# of the output to customize the view.
draw_circuit_schedule_timing(ob.result()[0].metadata['compilation']['scheduler_timing']['timing'])
hinweis

Obwohl die gesamte Schaltkreiszeit in den „compilation"-Metadaten zurückgegeben wird, ist dies NICHT die für die Abrechnung verwendete Zeit (Quantenzeit).

Metadaten-Ausgabe verstehen

Die stretch_values-Metadaten geben die folgenden Informationen zurück:

  • Name: Der Name des angewendeten stretch.
  • Value: Der angestrebte Zielwert.
  • Remainder: Der Rest aus der Auflösung des stretch, der zur ersten Verzögerung hinzugefügt wird, die den stretch verwendet.
  • Expanded values: Wertesets, die den stretch-Start und seine Dauer angeben.

Beispiel

# Define the circuit
circuit = QuantumCircuit(4)
foo = circuit.add_stretch("foo")
bar = circuit.add_stretch("bar")
circuit.barrier()
circuit.cz(0, 1)
circuit.cz(0, 1)
circuit.cz(0, 1)
circuit.cz(0, 1)

circuit.delay(foo, 2)
circuit.x(2)
# 3*foo
circuit.delay(expr.mul(3, foo), 2)
circuit.x(2)
# 2*foo
circuit.delay(expr.mul(2, foo), 2)

circuit.delay(bar, 3)
circuit.x(3)
circuit.delay(bar, 3)

circuit.measure_all()

Metadaten-Ausgabe

 [{'name': 'bar',
'value': 29,
'remainder': 1,
'expanded_values': [[1365, 30], [1404, 29]]},
{'name': 'foo',
'value': 8,
'remainder': 2,
'expanded_values': [[1365, 10], [1384, 24], [1417, 16]]}
]

Die zurückgegebenen Dauerwerte hängen vom Zielwert und dem berechneten Rest ab. Zum Beispiel sind dies die für foo zurückgegebenen Dauern:

  • foo value + remainder (8+2 = 10)
  • foo value * 3 (8 x 3 = 24)
  • foo value * 2 (8 x 2 = 16)

Du kannst eine Visualisierung verwenden, um die Zeitplanung besser zu verstehen und zu überprüfen.

draw_circuit_schedule_timing(job.result()[0].metadata['compilation']['scheduler_timing']['timing'])

Im folgenden Bild, basierend auf der Beispielausgabe, entspricht foo den Stretches auf Qubit 2. Die erste stretch-Verzögerung, die foo verwendet, beginnt am Ende des init_play (1365). Die stretch-Dauer beträgt 10, sodass diese Verzögerung endet, wenn das x-Gate beginnt (1365+10=1375). Die zweite und dritte stretch-Verzögerung lassen sich entsprechend interpretieren.

Die Ausgabe des Befehls draw_circuit_schedule_timing wird angezeigt.

Verwende die Schieberegler unten, die Steuerungen oben (fahre mit der Maus über dein Ausgabebild, um diese einzublenden) und die Legende seitlich der Ausgabe, um die Ansicht anzupassen. Fahre mit der Maus über das Bild, um genaue Daten einzusehen.

Weitere Details findest du im Thema Schaltkreis-Zeitplanung visualisieren.

Einschränkungen in Qiskit Runtime

Die Unterstützung für stretch in Qiskit Runtime ist derzeit experimentell und hat folgende Einschränkungen:

  • Höchstens eine stretch-Variable pro Qubit-Menge zwischen Barrieren (implizit und explizit). Eine Qubit-Menge besteht aus einem oder mehreren Qubits; diese Mengen müssen sich gegenseitig ausschließen.

    a = circuit.add_stretch("a")
    b = circuit.add_stretch("b")
    circuit.delay(a, (q0, q1))
    circuit.delay(b, q0) # Invalid because 2 stretches are applied on q0
  • Der von einer Menge von Barrieren umgebene Bereich wird als Barrierenregion bezeichnet. Eine stretch-Variable darf nicht in mehreren Barrierenregionen verwendet werden.

    # Stretch a is used in two barrier regions
    a = circuit.add_stretch("a")
    circuit.barrier((q0, q1))
    circuit.delay(a, q0)
    circuit.barrier((q0, q1))
    circuit.delay(a, q0)
    circuit.barrier((q0, q1))

    Illustration der vorherigen Codeausgabe

  • Stretch-Ausdrücke sind auf die Form X*stretch + Y beschränkt, wobei X und Y Gleitkomma- oder Ganzzahlkonstanten sind.

    a = circuit.add_stretch("a")
    b = circuit.add_stretch("b")
    c = circuit.add_stretch("c")

    # (a / b) * c is not supported
    circuit.delay(expr.mul(expr.div(a, b), c), q1)
  • Stretch-Ausdrücke dürfen nur eine einzige stretch-Variable enthalten.

    a = circuit.add_stretch("a")
    b = circuit.add_stretch("b")
    circuit.delay(expr.add(a, b), 0)
  • Stretch-Ausdrücke dürfen nicht zu negativen Verzögerungswerten aufgelöst werden. Der aktuelle Löser leitet keine Nicht-Negativitätsbeschränkungen ab.

    from qiskit.circuit import Duration

    circuit.barrier((q0, q1))
    circuit.delay(20, q1)
    # The length of this barrier region is 20dt, meaning the
    # equation for solving stretch 'a' is a + 40dt = 20dt, giving a = -20dt.
    circuit.delay(expr.add(a, Duration.dt(40)), q0)
    circuit.barrier((q0, q1))