Optimization Solver: Eine Qiskit Function von Q-CTRL Fire Opal
Qiskit Functions sind ein experimentelles Feature, das ausschließlich Nutzerinnen und Nutzern des IBM Quantum® Premium Plan, Flex Plan und On-Prem (über IBM Quantum Platform API) Plan zur Verfügung steht. Sie befinden sich im Preview-Release-Status und können sich noch ändern.
Überblick
Mit dem Fire Opal Optimization Solver kannst du Optimierungsprobleme im Utility-Scale auf Quantenhardware lösen, ohne Quantenexpertise zu benötigen. Gib einfach die übergeordnete Problemdefinition ein, und der Solver übernimmt den Rest. Der gesamte Workflow ist rauschbewusst und nutzt intern Fire Opals Performance Management. Der Solver liefert zuverlässig genaue Lösungen für klassisch anspruchsvolle Probleme, auch im vollen Geräteumfang auf den größten IBM® QPUs.
Der Solver ist flexibel und kann eingesetzt werden, um kombinatorische Optimierungsprobleme zu lösen, die als Zielfunktionen oder beliebige Graphen definiert sind. Probleme müssen nicht auf die Gerätetopologie abgebildet werden. Sowohl unbeschränkte als auch beschränkte Probleme sind lösbar, sofern Einschränkungen als Strafterme formuliert werden können. Die in diesem Leitfaden enthaltenen Beispiele zeigen, wie man ein unbeschränktes und ein beschränktes Optimierungsproblem im Utility-Scale mit verschiedenen Solver-Eingabetypen löst. Das erste Beispiel umfasst ein Max-Cut-Problem, das auf einem 156-Knoten-3-regulären Graphen definiert ist, während das zweite Beispiel ein 50-Knoten-Minimum-Vertex-Cover-Problem angeht, das durch eine Kostenfunktion definiert ist.
Um Zugang zum Optimization Solver zu erhalten, kontaktiere Q-CTRL.
Beschreibung der Function
Der Solver optimiert und automatisiert den gesamten Algorithmus vollständig – von der Fehlerunterdrückung auf Hardwareebene bis hin zu effizientem Problem-Mapping und geschlossener klassischer Optimierung. Im Hintergrund reduziert die Pipeline des Solvers Fehler auf jeder Stufe und ermöglicht so die verbesserte Leistung, die für eine bedeutungsvolle Skalierung erforderlich ist. Der zugrundeliegende Workflow ist vom Quantum Approximate Optimization Algorithm (QAOA) inspiriert, einem hybriden quanten-klassischen Algorithmus. Eine detaillierte Zusammenfassung des vollständigen Optimization-Solver-Workflows findest du im veröffentlichten Manuskript.
So löst du ein allgemeines Problem mit dem Optimization Solver:
- Definiere dein Problem als Zielfunktion, einen Graphen oder eine
SparsePauliOp-Spinkette. - Verbinde dich mit der Function über den Qiskit Functions Catalog.
- Führe das Problem mit dem Solver aus und rufe die Ergebnisse ab.
Eingaben und Ausgaben
Eingaben
| Name | Typ | Beschreibung | Erforderlich | Standard | Beispiel |
|---|---|---|---|---|---|
| problem | str oder SparsePauliOp | Eine der unter „Akzeptierte Problemformate" aufgeführten Darstellungen | Ja | k. A. | Poly(2.0*n[0]*n[1] + 2.0*n[0]*n[2] - 3.13520241298341*n[0] + 2.0*n[1]*n[2] - 3.14469748506794*n[1] - 3.18897660121566*n[2] + 6.0, n[0], n[1], n[2], domain='RR') |
| problem_type | str | Name der Problemklasse; wird nur für Graph- und Spinketten-Problemdefinitionen verwendet, die auf „maxcut" oder „spin_chain" beschränkt sind; für beliebige Zielfunktions-Problemdefinitionen nicht erforderlich | Nein | None | "maxcut" |
| backend_name | str | Name des zu verwendenden Backends | Nein | Am wenigsten ausgelastetes Backend, auf das deine Instanz Zugriff hat | "ibm_fez" |
| options | dict | Eingabeoptionen, einschließlich: (Optional) session_id: str; standardmäßig wird eine neue Session erstellt | Nein | None | {"session_id": "cw2q70c79ws0008z6g4g"} |
Akzeptierte Problemformate:
- Polynomielle Ausdrucksdarstellung einer Zielfunktion. Idealerweise in Python mit einem vorhandenen SymPy-Poly-Objekt erstellt und mit sympy.srepr in einen String formatiert.
- Graphdarstellung eines bestimmten Problemtyps. Der Graph sollte mit der networkx-Bibliothek in Python erstellt werden. Er wird dann durch Verwendung der networkx-Funktion
[nx.readwrite.json_graph.adjacency_data](http://nx.readwrite.json_graph.adjacency_data.)in einen String umgewandelt. - Spinkettendarstellung eines bestimmten Problems. Die Spinkette sollte als
SparsePauliOp-Objekt dargestellt werden; weitere Details findest du in der Dokumentation.
Unterstützte Backends: Führe den folgenden Code aus, um die Liste der aktuell unterstützten Backends anzuzeigen. Wenn dein Gerät nicht aufgeführt ist, wende dich an Q-CTRL, um Support hinzuzufügen.
# Added by doQumentation — required packages for this notebook
!pip install -q networkx numpy qiskit-ibm-catalog qiskit-ibm-runtime sympy
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
service.backends()
[<IBMBackend('ibm_fez')>,
<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_kingston')>,
<IBMBackend('ibm_torino')>,
<IBMBackend('ibm_marrakesh')>]
Optionen:
| Name | Typ | Beschreibung | Standard |
|---|---|---|---|
| session_id | str | Eine vorhandene Qiskit-Runtime-Session-ID | "cw4r3je6f0t010870y3g" |
| job_tags | list[str] | Eine Liste von Job-Tags | [] |
Ausgaben
| Name | Typ | Beschreibung | Beispiel |
|---|---|---|---|
| result | dict[str, Any] | Lösung und Metadaten, aufgeführt unter „Inhalt des Ergebnis-Dictionarys" | {'solution_bitstring_cost': 3.0, 'final_bitstring_distribution': {'000001': 100, '000011': 2}, 'iteration_count': 3, 'solution_bitstring': '000001', 'variables_to_bitstring_index_map': {n[1]': 5, 'n[2]': 4, 'n[3]': 3, 'n[4]': 2, 'n[5]': 1}, 'best_parameters': [0.19628831763697097, -1.047052334523102], 'warnings': []} |
Inhalt des Ergebnis-Dictionarys:
| Name | Typ | Beschreibung |
|---|---|---|
| solution_bitstring_cost | float | Die beste minimale Kosten über alle Iterationen hinweg |
| final_bitstring_distribution | CountsDict | Das Bitstring-Counts-Dictionary, das den minimalen Kosten zugeordnet ist |
| solution_bitstring | str | Bitstring mit den besten Kosten in der finalen Verteilung |
| iteration_count | int | Die Gesamtzahl der vom Solver durchgeführten QAOA-Iterationen |
| variables_to_bitstring_index_map | float | Die Zuordnung der Variablen zum entsprechenden Bit im Bitstring |
| best_parameters | list[float] | Die optimierten Beta- und Gamma-Parameter über alle Iterationen hinweg |
| warnings | list[str] | Die beim Kompilieren oder Ausführen von QAOA erzeugten Warnungen (Standard: None) |
Benchmarks
Veröffentlichte Benchmarking-Ergebnisse zeigen, dass der Solver Probleme mit über 120 Qubits erfolgreich löst und dabei sogar zuvor veröffentlichte Ergebnisse auf Quanten-Annealing- und Trapped-Ion-Geräten übertrifft. Die folgenden Benchmark-Metriken geben einen groben Hinweis auf die Genauigkeit und Skalierung von Problemtypen anhand einiger Beispiele. Die tatsächlichen Metriken können je nach verschiedenen Problemeigenschaften variieren, wie z. B. der Anzahl der Terme in der Zielfunktion (Dichte) und deren Lokalität, der Anzahl der Variablen und der polynomiellen Ordnung.
Die angegebene „Anzahl der Qubits" ist keine absolute Begrenzung, sondern stellt ungefähre Schwellenwerte dar, bei denen du eine äußerst konsistente Lösungsgenauigkeit erwarten kannst. Größere Problemgrößen wurden erfolgreich gelöst, und Tests jenseits dieser Grenzen sind ausdrücklich erwünscht.
Beliebige Qubit-Konnektivität wird für alle Problemtypen unterstützt.
| Problemtyp | Anzahl der Qubits | Beispiel | Genauigkeit | Gesamtzeit (s) | Runtime-Nutzung (s) | Anzahl der Iterationen |
|---|---|---|---|---|---|---|
| Dünn verbundene quadratische Probleme | 156 | 3-reguläres Max-Cut | 100% | 1764 | 293 | 16 |
| Optimierung binärer Probleme höherer Ordnung | 156 | Ising-Spinglas-Modell | 100% | 1461 | 272 | 16 |
| Dicht verbundene quadratische Probleme | 50 | Vollständig verbundenes Max-Cut | 100% | 1758 | 268 | 12 |
| Beschränktes Problem mit Straftermen | 50 | Gewichtetes Minimum Vertex Cover mit 8 % Kantendichte | 100% | 1074 | 215 | 10 |
Erste Schritte
Authentifiziere dich zunächst mit deinem IBM Quantum API-Schlüssel. Wähle dann die Qiskit Function wie folgt aus. (Dieser Codeausschnitt setzt voraus, dass du dein Konto bereits in deiner lokalen Umgebung gespeichert hast.)
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Access Function
solver = catalog.load("q-ctrl/optimization-solver")
Beispiel: Unbeschränkte Optimierung
Führe das Maximum-Cut-Problem (Max-Cut) aus. Das folgende Beispiel demonstriert die Fähigkeiten des Solvers an einem 156-Knoten-3-regulären ungewichteten Graphen-Max-Cut-Problem, du kannst aber auch gewichtete Graphenprobleme lösen.
Zusätzlich zu qiskit-ibm-catalog verwendest du in diesem Beispiel auch die folgenden Pakete: networkx und numpy. Du kannst diese Pakete installieren, indem du die folgende Zelle auskommentierst, wenn du dieses Beispiel in einem Notebook mit dem IPython-Kernel ausführst.
# %pip install networkx numpy
1. Das Problem definieren
Du kannst ein Max-Cut-Problem ausführen, indem du ein Graphenproblem definierst und problem_type='maxcut' angibst.
import networkx as nx
import numpy as np
# Generate a random graph with 156 nodes
maxcut_graph = nx.random_regular_graph(d=3, n=156, seed=8)
# Optionally, visualize the graph
nx.draw_networkx(
maxcut_graph, nx.kamada_kawai_layout(maxcut_graph), node_size=100
)

Der Solver akzeptiert einen String als Problemdefinitions-Eingabe.
# Convert graph to string
problem_as_str = nx.readwrite.json_graph.adjacency_data(maxcut_graph)
2. Das Problem ausführen
Wenn du die graphenbasierte Eingabemethode verwendest, gib den Problemtyp an.
# This cell is hidden from users
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
backend_name = service.least_busy(n_qubits=156).name
# Solve the problem
maxcut_job = solver.run(
problem=problem_as_str,
problem_type="maxcut",
backend_name=backend_name, # E.g. "ibm_fez"
)
Überprüfe den Status deiner Qiskit-Function-Arbeitslast oder rufe Ergebnisse wie folgt ab:
# Get job status
print(maxcut_job.status())
QUEUED
3. Das Ergebnis abrufen
Rufe den optimalen Schnittwert aus dem Ergebnis-Dictionary ab.
variables_to_bitstring_index_map-Unter-Dictionary, das hilft, die Reihenfolge zu überprüfen.# Poll for results
maxcut_result = maxcut_job.result()
# Take the absolute value of the solution since the cost function is minimized
qctrl_maxcut = abs(maxcut_result["solution_bitstring_cost"])
# Print the optimal cut value found by the Optimization Solver
print(f"Optimal cut value: {qctrl_maxcut}")
Optimal cut value: 209.0
Du kannst die Genauigkeit des Ergebnisses überprüfen, indem du das Problem klassisch mit Open-Source-Solvern wie PuLP löst, sofern der Graph nicht dicht verbunden ist. Bei Problemen mit hoher Dichte sind möglicherweise fortgeschrittene klassische Solver erforderlich, um die Lösung zu validieren.
Beispiel: Beschränkte Optimierung
Das vorherige Max-Cut-Beispiel ist ein klassisches quadratisches unbeschränktes binäres Optimierungsproblem. Q-CTRLs Optimization Solver kann für verschiedene Problemtypen eingesetzt werden, einschließlich beschränkter Optimierung. Du kannst beliebige Problemtypen lösen, indem du die Problemdefinition als Polynom eingibst, bei dem Einschränkungen als Strafterme modelliert werden.
Das folgende Beispiel zeigt, wie eine Kostenfunktion für ein beschränktes Optimierungsproblem, das Minimum Vertex Cover (MVC), konstruiert wird.
Zusätzlich zu den Paketen qiskit-ibm-catalog und qiskit verwendest du in diesem Beispiel auch die folgenden Pakete: numpy, networkx und sympy. Du kannst diese Pakete installieren, indem du die folgende Zelle auskommentierst, wenn du dieses Beispiel in einem Notebook mit dem IPython-Kernel ausführst.
# %pip install numpy networkx sympy
1. Das Problem definieren
Definiere ein zufälliges MVC-Problem, indem du einen Graphen mit zufällig gewichteten Knoten erzeugst.
import networkx as nx
from sympy import symbols, Poly, srepr
# To change the weights, change the seed to any integer.
rng_seed = 18
_rng = np.random.default_rng(rng_seed)
node_count = 50
edge_probability = 0.08
mvc_graph = nx.erdos_renyi_graph(
node_count, edge_probability, seed=rng_seed, directed=False
)
# add node weights
for i in mvc_graph.nodes:
mvc_graph.add_node(i, weight=_rng.random())
# Optionally, visualize the graph
nx.draw_networkx(mvc_graph, nx.kamada_kawai_layout(mvc_graph), node_size=200)

Ein Standardoptimierungsmodell für gewichtetes MVC kann wie folgt formuliert werden. Zunächst muss eine Strafe für jeden Fall hinzugefügt werden, in dem eine Kante nicht mit einem Knoten in der Teilmenge verbunden ist. Sei daher , wenn Knoten zur Überdeckung gehört (d. h. in der Teilmenge ist), und andernfalls. Das Ziel ist es, die Gesamtzahl der Knoten in der Teilmenge zu minimieren, was durch die folgende Funktion dargestellt werden kann:
# Construct the cost function.
variables = symbols([f"n[{i}]" for i in range(node_count)])
cost_function = Poly(0, variables)
for i in mvc_graph.nodes():
weight = mvc_graph.nodes[i].get("weight", 0)
cost_function += variables[i] * weight
Jetzt sollte jede Kante im Graphen mindestens einen Endpunkt der Überdeckung enthalten, was als Ungleichung ausgedrückt werden kann:
Jeder Fall, in dem eine Kante nicht mit dem Knoten der Überdeckung verbunden ist, muss bestraft werden. Dies kann in der Kostenfunktion durch Hinzufügen einer Strafe der Form dargestellt werden, wobei eine positive Strafkonstante ist. Damit ist eine unbeschränkte Alternative zur beschränkten Ungleichung für gewichtetes MVC:
# Add penalty term.
penalty_constant = 2
for i, j in mvc_graph.edges():
cost_function += penalty_constant * (
1 - variables[i] - variables[j] + variables[i] * variables[j]
)