Constrained Quantum Optimizer – Eine Qiskit Function von Aqarios
Siehe die API-Referenz
Qiskit Functions sind ein experimentelles Feature, das nur Nutzern des IBM Quantum® Premium Plan, Flex Plan und On-Prem Plan (über die IBM Quantum Platform API) zur Verfügung steht. Sie befinden sich im Preview-Release-Status und können sich ändern.
Überblick
Mit dem Constrained Quantum Optimizer von Aqarios kannst du binäre Optimierungsprobleme mit Nebenbedingungen auf IBM®-Quantenhardware lösen, ohne manuelle Neuformulierung und Circuit-Mapping. Gib das Problem einfach als LP-Datei, MPS-Datei oder als Luna Model an. Die Funktion übernimmt automatisch die Neuformulierung, die Circuit-Synthese, die Transpilation und die Hardwareausführung. Der Solver nutzt iteratives Warm-Starting [1] des FlexQAOA-Algorithmus, einer constraint-nativen Erweiterung des Quantum Approximate Optimization Algorithm (QAOA).
Der Optimierer zielt auf binäre Probleme mit Constraints ab. Insbesondere können One-Hot-Constraints und Set-Packing-Constraints nativ mithilfe von XY-Mixern durchgesetzt werden. Andere Constraint-Typen werden automatisch als Penalty-Terme umformuliert.
Die Funktion löst nur diskrete Probleme. Jede Entscheidungsvariable muss binär sein oder ganzzahlig mit expliziter unterer und oberer Schranke, damit sie in Qubits kodiert werden kann. Kontinuierliche (reellwertige) Variablen werden nicht unterstützt: Ein Modell, das sie enthält, wird mit dem Fehlercode 4710 abgelehnt.
Beschreibung
Die Performance kann sowohl von der Probleminstanz als auch von den nachfolgenden Verarbeitungsschritten abhängen. In manchen Fällen können klassische Samples und quantengenerierte Samples nach äquivalenter Nachbearbeitung eine ähnliche finale Lösungsqualität erreichen. Die Bewertung sollte daher den gesamten Optimierungsworkflow berücksichtigen.
Der Constrained Quantum Optimizer implementiert den Iterative Warm-Start XY-Mixer QAOA (IWS-QAOA) [1]. Der Algorithmus führt Fixed-Angle-QAOA-Circuits auf Quantenhardware aus und aktualisiert die anfänglichen Warm-Start-Wahrscheinlichkeiten jedes Circuits anhand der Messergebnisse der vorherigen Iteration. Es ist kein variationelles Parametertraining erforderlich; stattdessen wird eine feste lineare Ramp-Parametrisierung gewählt.
Durch die Verwendung von XY-Mixern zur Durchsetzung von One-Hot-Constraints kannst du die Lösungsqualität für Probleme mit Nebenbedingungen drastisch verbessern, da der Suchraum praktisch auf einen kleineren zulässigen Teilraum reduziert wird — und keine zusätzliche Komplexität durch Penalty-Terme in die Optimierungslandschaft eingeführt wird.
Workflow
Der Workflow der Funktion wird im Folgenden beschrieben:
-
Preprocessing: Vor der Circuit-Synthese analysiert der Optimierer die Constraint-Struktur des Eingabeproblems:
-
Graph-Preprocessing: Sofern es sich anbietet, wird das Problem als Graph analysiert, wobei Kanten die Beziehung zwischen Variablen bestimmen. Diese Graphanalyse ermöglicht zwei Preprocessing-Verbesserungen des Problems. Erstens können einzelne Constraints durch Clique-Merging zu einem großen Constraint zusammengefasst werden, sodass sie durch einen einzigen XY-Mixer dargestellt werden. Zweitens können Variablen mit nur einem einzigen Nachbarn deterministisch fixiert werden, was die Problemgröße und -komplexität reduziert.
-
Constraint-Auflösung: Der Preprocessor behandelt jeden Constraint separat: Handelt es sich um einen One-Hot-Constraint, kann er durch XY-Mixer durchgesetzt werden. Handelt es sich um einen Set-Packing-Constraint, wird er zunächst in einen One-Hot-Constraint umgewandelt. Gleichheits-Constraints werden durch quadratische Penalties behandelt und Ungleichheits-Constraints durch unausgewogene Penalisierung.
-
Penalty-Ableitung: Constraints, die nicht als One-Hot-Gleichheits-Constraints ausgedrückt werden können, werden in Penalty-Terme umgewandelt. Der Optimierer leitet die Penalty-Stärke automatisch aus der Problemstruktur ab.
-
-
Iteratives Warm-Starting: Der Optimierer führt mehrere parallele Warm-Start-Ketten aus. Jede Kette verwaltet eine Wahrscheinlichkeitsverteilung über Variablenbelegungen, die als Startwahrscheinlichkeit des QAOA-Circuits (Warm-Start-Zustand) verwendet wird. Bei jeder Iteration sampelt eine Kette aus dem Quanten-Circuit, wendet klassische Nachbearbeitung an und aktualisiert ihre Verteilung basierend auf dem Auftreten von Samples. Über die Iterationen hinweg fokussiert dies die Suche auf hochwertige Lösungen ohne eine variationelle Optimierungsschleife.
Die Anzahl der Ketten (num_parallel), das gesamte Shot-Budget pro Kette (total_shots) und die Anzahl der QAOA-Wiederholungen (reps) sind die wichtigsten Stellschrauben, um Lösungsqualität gegen Laufzeit abzuwägen.
Benchmarks
Die folgenden Benchmarks zeigen, dass die Constrained Quantum Optimizer Qiskit Function in der Lage ist, hochwertige, zulässige Lösungen für Optimierungsprobleme mit Nebenbedingungen mit deutlich über 100 binären Variablen zu finden. Die Genauigkeit ist das Verhältnis des gefundenen Zielfunktionswerts zum bekannten Optimum. Die Laufzeit ist die reale Zeit (Wall-Clock-Zeit) vom Funktionsaufruf bis zur Ergebnislieferung. Die QPU-Nutzung ist die abrechenbare Quantum-Compute-Zeit.
Es wurden zwei Probleme untersucht: das Maximum Independent Set (MIS)-Problem und der Max--Cut, eine Erweiterung des bekannten MaxCut-Problems auf mehr als zwei Kategorien. Max--Cut ist auch das zum Graph-Coloring-Entscheidungsproblem äquivalente Optimierungsproblem (wenn alle Kanten geschnitten werden, ist der Graph -färbbar).
| Problem | Variablen | Constraints | Qubits | Genauigkeit | Zulässigkeit | Gesamtlaufzeit (s) | QPU-Nutzung (s) | Gesamt-Shots |
|---|---|---|---|---|---|---|---|---|
| MIS es60fst02 | 186 | 280 | 124 | 100.0% | True | 940.2 | 147.0 | 25.0k |
| MIS sloane_2dc_128 | 128 | 5173 | 74 | 100.0% | True | 347.6 | 106.0 | 12.5k |
| Max-3-Cut Karate Club | 99 | 33 | 99 | 98.7% | True | 279.5 | 166.0 | 25.0k |
| Max-4-Cut Karate Club | 132 | 33 | 132 | 100.0% | True | 258.3 | 200.0 | 25.0k |
Benchmark-Hinweise
-
Die obigen Ergebnisse wurden auf dem
ibm_marrakeshHeron r2-Prozessor mit Standardoptionen und aktiviertemuse_sessionerzielt. Für die Instanzsloane_2dc_128wurdetotal_shots=12500verwendet. -
Die reduzierte Qubit-Anzahl für MIS-Instanzen (74 und 124 gegenüber 128 und 186 Variablen) spiegelt die Variablenfixierung im Preprocessing wider.
-
Die Probleminstanzen für MIS stammen aus der Quantum Optimization Benchmarking Library. - Die Instanz für das Max--Cut-Problem ist der Zachary Karate Club-Graph, der eine chromatische Zahl von 5 hat.
Erste Schritte
Dieser Leitfaden zeigt die grundlegende Verwendung des Constrained Quantum Optimizer, indem er zeigt, wie du die Funktion lädst und einfache Probleme ausführst. Für ein ausführlicheres Tutorial siehe Finde das Maximum Independent Set mit dem Aqarios Constrained Quantum Optimizer.
Authentifiziere dich mit deinem IBM Quantum API-Schlüssel und deiner Instance CRN und lade dann die Funktion aus dem Katalog.
Der folgende Code setzt voraus, dass du deine Anmeldedaten gespeichert hast. Folge zum Einrichten den Anweisungen unter Speichere deine IBM Cloud-Kontoanmeldedaten.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"luna_model": "luna-model", "networkx": "networkx", "qiskit_addon_opt_mapper": "qiskit-addon-opt-mapper", "qiskit_ibm_catalog": "qiskit-ibm-catalog"}
_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(aqarios/constrained-quantum-optimizer)]
# Load the function
optimizer = catalog.load("aqarios/constrained-quantum-optimizer")
# Check the list of backends you have access to
catalog.backends()
[<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_boston')>,
<IBMBackend('ibm_phoenix')>,
<IBMBackend('ibm_fez')>,
<IBMBackend('ibm_miami')>,
<IBMBackend('ibm_marrakesh')>,
<IBMBackend('ibm_kingston')>]
# Select the backend you want to use
backend = catalog.backend("ibm_phoenix")
Beispiel 1: Ein einfaches Problem mit Nebenbedingungen
Das erste Beispiel zeigt, wie du die Funktion mit LP-Dateien verwendest, einer gängigen Optimierungsproblem-Spezifikation aus dem Operations Research. Du wirst das folgende beliebige Optimierungsproblem mit fünf binären Variablen unter zwei Constraints formulieren:
1. Formuliere das Problem
Gib den mathematischen Ausdruck direkt im folgenden LP-String-Format an:
lp_str = """\\Problem name: Example
Minimize
obj: [ 2 x_1 x_2 + 4 x_2 x_3 - 2 x_3 x_4 - 4 x_4 x_5 - 4 x_1 x_3 - 8 x_2 x_5 ] / 2
Subject To
c1: x_1 + x_2 + x_3 <= 1
c2: x_3 - x_4 + x_5 = 0
Binaries
x_1 x_2 x_3 x_4 x_5
End
"""
Beachte, dass [ ... ] / 2 eine erforderliche Notation für quadratische Zielfunktionen aus dem LP-Standard ist.
2. Führe den Optimierer aus
Löse das Problem mit dem Optimierer.
# Set up low-resource options for the simple problem
options = {"total_shots": 2000, "num_parallel": 5, "use_session": True}
# Dispatch the job
job_1 = optimizer.run(
problem=lp_str, backend_name=backend.name, options=options
)
print(f"Job ID: {job_1.job_id}")
Job ID: 2e06a3a8-7c29-4c76-94d7-194293363f4a
# Monitor the job status
job_1.status()
'RUNNING: WAITING_FOR_QPU'
3. Rufe das Ergebnis ab
Das Ergebnis enthält die Lösung, die die Variablennamen den zugehörigen Zuweisungen zuordnet.
result = job_1.result()
result
{'solutions': [{'x_1': 0, 'x_2': 1, 'x_3': 0, 'x_4': 1, 'x_5': 1}],
'solution_bitstrings': ['01011'],
'raw_energy': -6.0,
'objective_value': -6.0,
'feasible': True,
'metadata': {'resource_usage': {'RUNNING: MAPPING': {'CPU': 5.405},
'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU': 9.902},
'RUNNING: WAITING_FOR_QPU': {'CPU': 25.456},
'RUNNING: EXECUTING_QPU': {'CPU': 25.202},
'RUNNING: POST_PROCESSING': {'CPU': 6.481}},
'circuit_metrics': {'before_transpilation': {'num_qubits': 6,
'gate_count': 42,
'depth': 15,
'two_qubit_gate_count': 12,
'two_qubit_gate_depth': 7,
'operations': {'rz': 12,
'p': 8,
'ry': 7,
'rzz': 4,
'cx': 3,
'xx_plus_yy': 3,
'global_phase': 2,
'cry': 2,
'x': 1}},
'after_transpilation': {'num_qubits': 6,
'gate_count': 254,
'depth': 100,
'two_qubit_gate_count': 48,
'two_qubit_gate_depth': 33,
'operations': {'rz': 78, 'sx': 122, 'cz': 48, 'measure': 6, 'x': 0}}},
'solver_info': {'variable_mapping': {'x_1': 0,
'x_2': 1,
'x_3': 2,
'x_4': 3,
'x_5': 4}}}}
Das Feld solutions im zurückgegebenen Dictionary enthält alle gefundenen entarteten besten Lösungen als Dictionaries, die jeden Variablennamen auf seine Zuweisung abbilden. Dieselben Lösungen werden auch als Bitstrings in solution_bitstrings zurückgegeben; metadata.solver_info.variable_mapping sagt dir, welche Zeichenposition eines Bitstrings zu welcher Variable gehört. Neben den Lösungsdaten werden der objective_value, ein feasible-Flag und zusätzliche metadata bereitgestellt.
Beispiel 2: Maximum Independent Set
Das Maximum Independent Set (MIS)-Problem fragt nach der größten Teilmenge von Knoten in einem Graphen, sodass keine zwei Knoten in der Teilmenge eine Kante teilen. Dieses Beispiel löst MIS auf einem regulären Graphen mit 12 Knoten. Weitere Informationen zum Problem und ein größeres Beispiel findest du im Tutorial.
Das Problem wird mit qiskit-addon-opt-mapper erstellt. Installiere bei Bedarf die erforderlichen Pakete:
# %pip install networkx qiskit-addon-opt-mapper
Schritt 1: Definiere das Problem
Erstelle einen zufälligen 3-regulären Graphen und erzeuge das MIS-Optimierungsproblem. Jeder Graphknoten wird zu einer binären Variable, und jede Kante führt einen Constraint ein, der sicherstellt, dass höchstens ein Endpunkt ausgewählt wird.
import networkx as nx
from qiskit_addon_opt_mapper.applications import IndependentSet
from qiskit_addon_opt_mapper.translators import to_docplex_mp
# 12-node random 3-regular graph
G = nx.random_regular_graph(3, 12, seed=7)
# Build MIS problem and export as LP string
mis = IndependentSet(G)
mis_problem = mis.to_optimization_problem()
lp_str = to_docplex_mp(mis_problem).export_as_lp_string()
print(lp_str)
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: Independent set
Maximize
obj: x_0 + x_1 + x_2 + x_3 + x_4 + x_5 + x_6 + x_7 + x_8 + x_9 + x_10 + x_11
Subject To
c0: x_0 + x_4 <= 1
c1: x_0 + x_9 <= 1
c2: x_0 + x_8 <= 1
c3: x_1 + x_2 <= 1
c4: x_1 + x_7 <= 1
c5: x_1 + x_3 <= 1
c6: x_2 + x_4 <= 1
c7: x_2 + x_11 <= 1
c8: x_3 + x_10 <= 1
c9: x_3 + x_11 <= 1
c10: x_4 + x_5 <= 1
c11: x_5 + x_10 <= 1
c12: x_5 + x_6 <= 1
c13: x_6 + x_8 <= 1
c14: x_6 + x_7 <= 1
c15: x_7 + x_9 <= 1
c16: x_8 + x_11 <= 1
c17: x_9 + x_10 <= 1
Bounds
0 <= x_0 <= 1
0 <= x_1 <= 1
0 <= x_2 <= 1
0 <= x_3 <= 1
0 <= x_4 <= 1
0 <= x_5 <= 1
0 <= x_6 <= 1
0 <= x_7 <= 1
0 <= x_8 <= 1
0 <= x_9 <= 1
0 <= x_10 <= 1
0 <= x_11 <= 1
Binaries
x_0 x_1 x_2 x_3 x_4 x_5 x_6 x_7 x_8 x_9 x_10 x_11
End
Schritt 2: Führe den Optimierer aus
Löse das Problem mit dem Optimierer.
options = {"total_shots": 2000, "use_session": True}
# Dispatch the job
job_2 = optimizer.run(
problem=lp_str, backend_name=backend.name, options=options
)
print(f"Job ID: {job_2.job_id}")
Job ID: f041431c-b42e-4627-afe5-522fea61faf6
# Monitor the job status
job_2.status()
'QUEUED'
Schritt 3: Rufe Ergebnisse ab und interpretiere sie
Abschließend rufen wir die Lösung ab und überprüfen sie auf Korrektheit.
result = job_2.result()
print(f"Feasible: {result['feasible']}")
print(f"Independent set size: {int(result['objective_value'])}")
# Extract the selected nodes
solution = result["solutions"][0]
independent_set = [
int(k.split("_")[1]) for k, v in solution.items() if v == 1
]
print(f"Selected nodes: {sorted(independent_set)}")
# Verify no two selected nodes share an edge
conflicts = [
(u, v)
for u, v in G.edges()
if u in independent_set and v in independent_set
]
print(f"Edge conflicts: {conflicts}")
Feasible: True
Independent set size: 5
Selected nodes: [1, 4, 6, 9, 11]
Edge conflicts: []
Beispiel 3: Graph-3-Färbung (Max-3-Cut)
Das Max--Cut-Problem partitioniert die Knoten eines Graphen in Gruppen, um die Anzahl der Kanten zu maximieren, deren Endpunkte zu unterschiedlichen Gruppen gehören. Für ist dies gleichbedeutend mit dem Finden einer 3-Färbung, die die Anzahl der farbübergreifenden Kanten maximiert.
Formal wird jeder Knoten mithilfe binärer Variablen für Farbe einer von Gruppen zugewiesen. Der One-Hot-Constraint für jeden Knoten erzwingt eine eindeutige Zuweisung. Die Zielfunktion maximiert gruppenübergreifende Kanten:
Die One-Hot-Constraints sind ideal für XY-Mixer, die der Optimierer nativ verarbeitet.
Dieses Beispiel verwendet den Zachary Karate Club-Graphen (34 Knoten, 78 Kanten) und erstellt das Modell mit dem Luna Model. Installiere bei Bedarf die erforderlichen Pakete:
# %pip install networkx matplotlib luna-model
Schritt 1: Definiere das Problem
Erzeuge zunächst die binären Variablen mit dem Paket luna-model. Definiere anschließend die Zielfunktion und füge die Constraints zum Modell hinzu.
import networkx as nx
from luna_model import Model, quicksum
k = 3
G = nx.karate_club_graph()
# Instantiate the Model object
model = Model(name="Max-3-Cut")
# Add binary variables
x = model.add_variables("x", shape=(G.number_of_nodes(), k))
# Add the objective by using the numpy dot abstraction
model.objective = quicksum(x[i].dot(x[j]) for i, j in G.edges)
# Bulk-add the one-hot constraints
model.add_constraints(x.sum(axis=1) == 1)
# Encode the model to send via API
model_enc = model.encode_b64()
Schritt 2: Führe den Optimierer aus
Löse das Problem mit dem Optimierer.
options = {"use_session": True}
# Dispatch the job
job_3 = optimizer.run(
problem=model_enc, backend_name=backend.name, options=options
)
print(f"Job ID: {job_3.job_id}")
Job ID: df911d83-9df7-44a1-bfad-5d66e08676c3
# Monitor the job status
job_3.status()
'RUNNING: WAITING_FOR_QPU'
Schritt 3: Rufe Ergebnisse ab und visualisiere sie
Rufe die Ergebnisse ab und visualisiere sie.
result = job_3.result()
solution = result["solutions"][0]
# Map each node to its assigned color group
node_group = {}
for i in G.nodes:
for c in range(k):
if solution[f"x{i},{c}"] == 1:
node_group[i] = c
print(f"Feasible: {result['feasible']}")
print(f"Violations: {int(result['objective_value'])}")
# Visualize the partition
colors = ["tab:blue", "tab:orange", "tab:green"]
node_colors = [colors[node_group[i]] for i in G.nodes()]
nx.draw(G, nx.kamada_kawai_layout(G), node_color=node_colors, node_size=200)
Feasible: True
Violations: 4

Änderungsprotokoll
[0.4.0] - 2026-09-22
Breaking Changes
- Der Eingabeparameter
modelwurde inproblemumbenannt. - Das Ausgabefeld
obj_valuewurde inobjective_valueumbenannt. - Die Option
postprocessingwurde durchpostprocessing_levelersetzt, die statt eines Strings eine ganze Zahl im Bereich[0, 3]erwartet. Die bisherigen Werte werden wie folgt abgebildet:"off"->0,"weak"->1,"medium"->2und"strong"->3; der Standardwert bleibt unverändert bei3. metadata.circuit_metricswurde umstrukturiert. Es meldet nunnum_qubits,gate_count,depth,two_qubit_gate_count,two_qubit_gate_depthundoperationsgetrennt fürbefore_transpilationundafter_transpilation, statt eines einzelnen flachen Dictionaries gemittelter Gate-Anzahlen. Das Feld istNone, wenn das Problem während des Preprocessings gelöst wurde und kein Circuit ausgeführt wurde (Fehlercode4712).
Features
- Die Option
seed_transpilerwurde hinzugefügt, um Circuit-Synthese und Routing über mehrere Läufe hinweg reproduzierbar zu machen. - Die Option
job_tagswurde hinzugefügt, um jedem Qiskit-Runtime-Job, den die Function einreicht, eigene Tags anzuhängen. - Das Ausgabefeld
solution_bitstringswurde hinzugefügt, das dieselben Lösungen wiesolutionsin Bitstring-Form zurückgibt. metadata.solver_info.variable_mappingwurde hinzugefügt, das jeden Variablennamen auf seine Zeichenposition in den Einträgen vonsolution_bitstringsabbildet.
Dokumentation
- Es wurde dokumentiert, dass kontinuierliche (reellwertige) Variablen nicht unterstützt werden und dass ganzzahlige Variablen explizite Schranken benötigen. Solche Modelle schlagen mit dem Fehlercode
4710fehl.
Support erhalten
Bei Fragen oder Problemen wende dich mit deiner Job-ID an support@aqarios.com.
Nächste Schritte
- Fordere Zugriff auf den Constrained Quantum Optimizer von Aqarios an.
- Besuche die API-Referenz für die vollständige Dokumentation der Parameter und Ausgaben.
- Probiere das Tutorial Finde das Maximum Independent Set mit dem Aqarios Constrained Quantum Optimizer aus, um tief in eine reale QOBLIB-Benchmark-Instanz einzutauchen.
- Lies die Publikation Constrained Quantum Optimization via Iterative Warm-Start XY-Mixers.
Referenzen
- Bucher et al. (2026), Constrained Quantum Optimization via Iterative Warm-Start XY-Mixers 10.1088/1367-2630/ae8ea2