Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

Optimization Solver: Eine Qiskit Function von Q-CTRL Fire Opal

Siehe die API-Referenz

hinweis

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.

Package versions

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese Versionen oder neuere zu verwenden.

qiskit-ibm-runtime~=0.47.0
sympy~=1.14.0

Ü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, wobei Einschränkungen als harte Hamming-Gewicht-1-Constraints statt als Strafterme durchgesetzt werden. 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-Graph-Partitionierungsproblem 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.

Visualisierung des Optimization-Solver-Workflows

So löst du ein allgemeines Problem mit dem Optimization Solver:

  1. Definiere dein Problem als Zielfunktion, einen Graphen oder eine SparsePauliOp-Spinkette.
  2. Verbinde dich mit der Function über den Qiskit Functions Catalog.
  3. Führe das Problem mit dem Solver aus und rufe die Ergebnisse ab.

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 in einen String umgewandelt.

  • Spinkettendarstellung eines bestimmten Problems. Die Spinkette sollte als SparsePauliOp-Objekt dargestellt werden; weitere Details findest du in der Dokumentation.

Unterstützt diese Funktion alle IBM Backends?

Wenn du ein Backend verwenden möchtest, das diese Funktion derzeit nicht unterstützt, wende dich an Q-CTRL, um Support hinzuzufügen.

Benchmarks​

Haftungsausschluss

Die Leistung kann sowohl von der Probleminstanz als auch von den nachfolgenden Verarbeitungsschritten abhängen. In manchen Fällen können klassische und quantengenerierte Stichproben nach vergleichbarer Nachbearbeitung eine ähnliche endgültige Lösungsqualität erreichen. Die Bewertung sollte daher den gesamten Optimierungsworkflow berücksichtigen.

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.

ProblemtypAnzahl der QubitsBeispielGenauigkeitGesamtzeit (s)Runtime-Nutzung (s)Anzahl der Iterationen
Dünn verbundene quadratische Probleme1563-reguläres max-cut100%176429316
Optimierung binärer Probleme höherer Ordnung156Ising-Spinglas-Modell100%146127216
Dicht verbundene quadratische Probleme50Vollständig verbundenes max-cut100%175826812
Beschränktes Problem mit harten Bedingungen50Gewichtete Graphpartitionierung mit 8 % Kantendichte100%107421510

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

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"networkx": "networkx", "numpy": "numpy", "qiskit_ibm_catalog": "qiskit-ibm-catalog", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "sympy": "sympy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Verify that you have access to the function
catalog.list()
[QiskitFunction(qunova/hivqe-chemistry),
QiskitFunction(global-data-quantum/quantum-portfolio-optimizer),
QiskitFunction(algorithmiq/tem),
QiskitFunction(qedma/qesem),
QiskitFunction(multiverse/singularity),
QiskitFunction(ibm/circuit-function),
QiskitFunction(q-ctrl/optimization-solver),
QiskitFunction(colibritd/quick-pde),
QiskitFunction(q-ctrl/performance-management),
QiskitFunction(kipu-quantum/iskay-quantum-optimizer)]
# 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
)

Ausgabe der vorherigen Code-Zelle

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:

# Print the ID so you can use it later, if necessary
print(maxcut_job.job_id)

# Get job status
print(maxcut_job.status())
34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED

3. Das Ergebnis abrufen​

Rufe den optimalen Schnittwert aus dem Ergebnis-Dictionary ab.

hinweis

Die Zuordnung der Variablen zum Bitstring kann sich geändert haben. Das Ausgabe-Dictionary enthält ein 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: 210.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 gängiges quadratisches unbeschränktes binäres Optimierungsproblem. Q-CTRLs Optimization Solver kann auch beschränkte Optimierungsprobleme lösen, indem harte Bedingungen direkt über die Eingabe constraint an den Solver übergeben werden, anstatt sie als Strafterme in der Zielfunktion zu kodieren. Der Solver unterstützt derzeit Hamming-Gewicht-1-Bedingungen: Jede Bedingung legt eine Gruppe von Variablen fest, bei der genau eine Variable gleich 1 und die übrigen gleich 0 sein müssen.

Das folgende Beispiel zeigt, wie eine Kostenfunktion und eine Reihe harter Bedingungen für ein beschränktes Optimierungsproblem, die Graphpartitionierung, konstruiert werden, indem jeder Knoten eines Graphen genau einer von mehreren Gruppen zugewiesen wird, während das Gesamtgewicht der Kanten minimiert wird, deren Endpunkte in derselben Gruppe liegen. 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 Graphpartitionierungsproblem, indem du einen Graphen mit zufällig gewichteten Knoten erzeugst.

import networkx as nx
from sympy import Symbol, 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
graph = nx.erdos_renyi_graph(
node_count, edge_probability, seed=rng_seed, directed=False
)

# add node weights
min_weight = -1.0
max_weight = 1.0
for i in graph.nodes:
weight = (max_weight - min_weight) * _rng.random() + min_weight
graph.add_node(i, weight=weight)

# Optionally, visualize the graph
nx.draw_networkx(graph, nx.kamada_kawai_layout(graph), node_size=200)

Ausgabe der vorherigen Code-Zelle

Ein Standardoptimierungsmodell für die gewichtete Graphpartitionierung lässt sich wie folgt formulieren. Teile die Knoten des Graphen in drei Gruppen g∈{0,1,2}g \in \{0, 1, 2\} auf, und setze ni,g=1n_{i,g} = 1, wenn Knoten ii der Gruppe gg zugewiesen ist, und ni,g=0n_{i,g} = 0 andernfalls. Das Ziel besteht darin, das Gesamtgewicht der Kanten zu minimieren, deren Endpunkte derselben Gruppe zugewiesen sind, wobei das Gewicht einer Kante (i,j)(i,j) die kombinierte Gewichtung ihrer beiden Endpunkte ist, ωi,j=ωi+ωj\omega_{i,j} = \omega_i + \omega_j:

Minimizey=∑(i,j)∈Eωi,j∑gni,g nj,g\textbf{Minimize}\qquad y = \sum_{(i,j)\in E} \omega_{i,j} \sum_{g} n_{i,g}\, n_{j,g}

# Construct the cost function.
group_count = 3
variables = [
Symbol(f"n[{i},{g}]")
for i in range(node_count)
for g in range(group_count)
]
node_group_var = {
(i, g): variables[i * group_count + g]
for i in range(node_count)
for g in range(group_count)
}
cost_function = Poly(0, *variables)

for i, j in graph.edges():
edge_weight = graph.nodes[i]["weight"] + graph.nodes[j]["weight"]
for g in range(group_count):
cost_function += (
edge_weight * node_group_var[(i, g)] * node_group_var[(j, g)]
)

Jeder Knoten muss genau einer der drei Gruppen zugewiesen werden. Dies ist eine Hamming-Gewicht-1-Bedingung: Für jeden Knoten ii muss genau eine der Variablen ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} gleich 1 sein, und die übrigen müssen gleich 0 sein:

ni,0+ni,1+ni,2=1 for all i∈Vn_{i,0} + n_{i,1} + n_{i,2} = 1 \texttt{ for all } i \in V

Anstatt diese Anforderung als Strafterm in der Kostenfunktion zu kodieren, übergib sie direkt als harte Bedingung an den Solver, indem du die Eingabe constraint verwendest.

# Build the hard constraint: exactly one group per node.
constraint_dict = {
str(tuple(f"n[{i},{g}]" for g in range(group_count))): 1
for i in range(node_count)
}
print(f"Problem constraints: {constraint_dict}")
Problem constraints: {"('n[0,0]', 'n[0,1]', 'n[0,2]')": 1, "('n[1,0]', 'n[1,1]', 'n[1,2]')": 1, "('n[2,0]', 'n[2,1]', 'n[2,2]')": 1, "('n[3,0]', 'n[3,1]', 'n[3,2]')": 1, "('n[4,0]', 'n[4,1]', 'n[4,2]')": 1, "('n[5,0]', 'n[5,1]', 'n[5,2]')": 1, "('n[6,0]', 'n[6,1]', 'n[6,2]')": 1, "('n[7,0]', 'n[7,1]', 'n[7,2]')": 1, "('n[8,0]', 'n[8,1]', 'n[8,2]')": 1, "('n[9,0]', 'n[9,1]', 'n[9,2]')": 1, "('n[10,0]', 'n[10,1]', 'n[10,2]')": 1, "('n[11,0]', 'n[11,1]', 'n[11,2]')": 1, "('n[12,0]', 'n[12,1]', 'n[12,2]')": 1, "('n[13,0]', 'n[13,1]', 'n[13,2]')": 1, "('n[14,0]', 'n[14,1]', 'n[14,2]')": 1, "('n[15,0]', 'n[15,1]', 'n[15,2]')": 1, "('n[16,0]', 'n[16,1]', 'n[16,2]')": 1, "('n[17,0]', 'n[17,1]', 'n[17,2]')": 1, "('n[18,0]', 'n[18,1]', 'n[18,2]')": 1, "('n[19,0]', 'n[19,1]', 'n[19,2]')": 1, "('n[20,0]', 'n[20,1]', 'n[20,2]')": 1, "('n[21,0]', 'n[21,1]', 'n[21,2]')": 1, "('n[22,0]', 'n[22,1]', 'n[22,2]')": 1, "('n[23,0]', 'n[23,1]', 'n[23,2]')": 1, "('n[24,0]', 'n[24,1]', 'n[24,2]')": 1, "('n[25,0]', 'n[25,1]', 'n[25,2]')": 1, "('n[26,0]', 'n[26,1]', 'n[26,2]')": 1, "('n[27,0]', 'n[27,1]', 'n[27,2]')": 1, "('n[28,0]', 'n[28,1]', 'n[28,2]')": 1, "('n[29,0]', 'n[29,1]', 'n[29,2]')": 1, "('n[30,0]', 'n[30,1]', 'n[30,2]')": 1, "('n[31,0]', 'n[31,1]', 'n[31,2]')": 1, "('n[32,0]', 'n[32,1]', 'n[32,2]')": 1, "('n[33,0]', 'n[33,1]', 'n[33,2]')": 1, "('n[34,0]', 'n[34,1]', 'n[34,2]')": 1, "('n[35,0]', 'n[35,1]', 'n[35,2]')": 1, "('n[36,0]', 'n[36,1]', 'n[36,2]')": 1, "('n[37,0]', 'n[37,1]', 'n[37,2]')": 1, "('n[38,0]', 'n[38,1]', 'n[38,2]')": 1, "('n[39,0]', 'n[39,1]', 'n[39,2]')": 1, "('n[40,0]', 'n[40,1]', 'n[40,2]')": 1, "('n[41,0]', 'n[41,1]', 'n[41,2]')": 1, "('n[42,0]', 'n[42,1]', 'n[42,2]')": 1, "('n[43,0]', 'n[43,1]', 'n[43,2]')": 1, "('n[44,0]', 'n[44,1]', 'n[44,2]')": 1, "('n[45,0]', 'n[45,1]', 'n[45,2]')": 1, "('n[46,0]', 'n[46,1]', 'n[46,2]')": 1, "('n[47,0]', 'n[47,1]', 'n[47,2]')": 1, "('n[48,0]', 'n[48,1]', 'n[48,2]')": 1, "('n[49,0]', 'n[49,1]', 'n[49,2]')": 1}
Teilweise beschränkte Probleme

Du musst nicht jede Variable zu constraint hinzufügen. Jede Variable, die im Dictionary fehlt, bleibt unbeschränkt, sodass du hart beschränkte Variablengruppen mit freien Variablen im selben Problem kombinieren kannst.

2. Das Problem ausführen​

# Solve the problem
partition_job = solver.run(
problem=srepr(cost_function),
constraint=constraint_dict,
backend_name="ibm_marrakesh", # E.g. "ibm_marrakesh"
)

Überprüfe den Status deiner Qiskit-Function-Arbeitslast oder rufe Ergebnisse wie folgt ab:

# Print the ID so you can use it later, if necessary
print(partition_job.job_id)

# Get job status
print(partition_job.status())
b8085944-f313-444e-be39-ea61b1b47ebd
QUEUED

3. Das Ergebnis abrufen​

Rufe die Lösung ab und analysiere die Ergebnisse. Die Lösungskosten stellen das Gesamtgewicht der Kanten dar, deren Endpunkte in derselben Gruppe gelandet sind, sodass niedrigere Kosten eine bessere Partitionierung des Graphen anzeigen.

partition_result = partition_job.result()
qctrl_cost = partition_result["solution_bitstring_cost"]
solution_bitstring = partition_result["solution_bitstring"]

# Print results
print(f"Total weight of same-group edges: {qctrl_cost}")
print(f"Solution bitstring: {solution_bitstring}")
Total weight of same-group edges: -36.5539
Solution bitstring: 100100100100100001100100100100100100100100100100100001010100010100100100100010001001100100100001100001100001010001001010100100100100100010100100100100

Support erhalten​

Bei Fragen oder Problemen wende dich an Q-CTRL.

Änderungsprotokoll​

  • 2026-08-10: Unterstützung für harte (Hamming-Gewicht-1) Bedingungen über die Eingabe constraint hinzugefügt und das Beispiel für beschränkte Optimierung entsprechend aktualisiert.

  • 2026-02-11: Wir unterstützen jetzt ibm_miami

Nächste Schritte​