Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Constrained Quantum Optimizer - Une fonction Qiskit par Aqarios

Consulte la référence API

remarque

Les Qiskit Functions sont une fonctionnalité expérimentale disponible uniquement pour les utilisateurs des plans Premium, Flex et On-Prem (via l'API IBM Quantum Platform) d'IBM Quantum®. Elles sont en statut de version préliminaire et sont susceptibles d'être modifiées.

Aperçu​

Avec le Constrained Quantum Optimizer d'Aqarios, tu peux résoudre des problèmes d'optimisation binaire contrainte sur du matériel quantique IBM® sans reformulation manuelle ni mapping de circuit. Il te suffit de fournir un problème sous forme de fichier LP, de fichier MPS ou de Luna Model. La fonction gère automatiquement la reformulation, la synthèse de circuit, la transpilation et l'exécution matérielle. Le solveur utilise le démarrage à chaud itératif [1] de l'algorithme FlexQAOA, une extension de l'algorithme d'optimisation approximative quantique (QAOA) qui gère nativement les contraintes.

L'optimiseur cible les problèmes binaires contraints. En particulier, les contraintes one-hot et les contraintes de set-packing peuvent être appliquées nativement à l'aide de mixeurs XY. Les autres types de contraintes sont automatiquement reformulés sous forme de termes de pénalité.

Types de variables pris en charge

La fonction résout uniquement des problèmes discrets. Chaque variable de décision doit être binaire, ou entière avec une borne inférieure et une borne supérieure explicites, afin de pouvoir être encodée dans des qubits. Les variables continues (à valeurs réelles) ne sont pas prises en charge : un modèle qui en contient est rejeté avec le code d'erreur 4710.

Description​

Avertissement

La performance peut dépendre à la fois de l'instance du problème et des étapes de traitement ultérieures. Dans certains cas, les échantillons classiques et les échantillons générés par voie quantique peuvent atteindre une qualité de solution finale similaire après un post-traitement équivalent. L'évaluation doit donc prendre en compte le workflow d'optimisation complet.

Le Constrained Quantum Optimizer met en œuvre le QAOA à mixeur XY et démarrage à chaud itératif (IWS-QAOA) [1]. L'algorithme exécute des circuits QAOA à angle fixe sur du matériel quantique et met à jour les probabilités de démarrage à chaud initiales de chaque circuit à partir des résultats de mesure de l'itération précédente. Aucun entraînement de paramètre variationnel n'est requis ; à la place, une paramétrisation en rampe linéaire fixe est choisie.

En utilisant des mixeurs XY pour appliquer les contraintes one-hot, tu peux considérablement améliorer la qualité des solutions pour les problèmes contraints, car l'espace de recherche est pratiquement réduit à un sous-espace réalisable plus petit — et aucune complexité supplémentaire, due aux termes de pénalité, n'est introduite dans le paysage d'optimisation.

Flux de travail​

Le flux de travail de la fonction est décrit ci-dessous :

  1. Prétraitement : avant la synthèse du circuit, l'optimiseur analyse la structure de contraintes du problème d'entrée :

    • Prétraitement du graphe : Lorsque cela s'applique au problème, celui-ci est analysé comme un graphe, où les arêtes déterminent la relation entre les variables. Cette analyse de graphe permet deux améliorations de prétraitement du problème. Premièrement, des contraintes individuelles peuvent être agrégées en une seule grande contrainte par fusion de cliques, de sorte qu'elles soient représentées par un seul mixeur XY. Deuxièmement, les variables n'ayant qu'un seul voisin peuvent être fixées de manière déterministe, ce qui réduit la taille et la complexité du problème.

    • Résolution des contraintes : le préprocesseur traite chaque contrainte séparément : si la contrainte est une contrainte one-hot, elle peut être appliquée via des mixeurs XY. S'il s'agit d'une contrainte de set-packing, elle sera d'abord transformée en contrainte one-hot. Les contraintes d'égalité sont traitées par des pénalités quadratiques, et les contraintes d'inégalité par pénalisation déséquilibrée.

    • Dérivation des pénalités : les contraintes qui ne peuvent pas être exprimées comme des contraintes d'égalité one-hot sont converties en termes de pénalité. L'optimiseur déduit automatiquement l'intensité de la pénalité à partir de la structure du problème.

  2. Démarrage à chaud itératif : l'optimiseur exécute plusieurs chaînes de démarrage à chaud parallèles. Chaque chaîne détient une distribution de probabilité sur les affectations de variables, utilisée comme probabilité de départ du circuit QAOA (état de démarrage à chaud). À chaque itération, une chaîne échantillonne à partir du circuit quantique, applique un post-traitement classique et met à jour sa distribution en fonction de l'occurrence des échantillons. Au fil des itérations, cela concentre la recherche sur des solutions de haute qualité sans boucle d'optimisation variationnelle.

Le nombre de chaînes (num_parallel), le budget total de shots par chaîne (total_shots) et le nombre de répétitions QAOA (reps) sont les principaux leviers pour arbitrer entre qualité de la solution et temps d'exécution.

Workflow diagram of the Constrained Quantum Optimizer

Benchmarks​

Les benchmarks ci-dessous montrent que la fonction Qiskit Constrained Quantum Optimizer est capable de trouver des solutions réalisables de haute qualité pour des problèmes d'optimisation contrainte comportant bien plus de 100 variables binaires. La précision (Accuracy) est le rapport entre la valeur de l'objectif trouvée et l'optimum connu. Le temps d'exécution (Runtime) est le temps réel écoulé entre l'invocation de la fonction et la livraison du résultat. L'utilisation QPU est le temps de calcul quantique facturable.

Deux problèmes ont été examinés : le problème du Maximum Independent Set (MIS) et le Max-kk-Cut, qui est une extension du célèbre problème MaxCut à plus de deux catégories. Le Max-kk-Cut est également l'équivalent, en tant que problème d'optimisation, du problème de décision de coloration de graphe (lorsque toutes les arêtes sont coupées, le graphe est kk-colorable).

ProblèmeVariablesContraintesQubitsPrécisionFaisabilitéTemps d'exécution total (s)Utilisation QPU (s)Total de shots
MIS es60fst02186280124100.0%True940.2147.025.0k
MIS sloane_2dc_128128517374100.0%True347.6106.012.5k
Max-3-Cut Karate Club99339998.7%True279.5166.025.0k
Max-4-Cut Karate Club13233132100.0%True258.3200.025.0k

Notes sur les benchmarks​

  • Les résultats ci-dessus ont été obtenus sur le processeur Heron r2 ibm_marrakesh avec les options par défaut et use_session activé. Pour l'instance sloane_2dc_128, total_shots=12500 a été utilisé.

  • Le nombre réduit de qubits pour les instances MIS (74 et 124 contre 128 et 186 variables) reflète la fixation de variables lors du prétraitement.

  • Les instances de problèmes pour MIS proviennent de la Quantum Optimization Benchmarking Library. - L'instance pour le problème Max-kk-Cut est le graphe du Zachary Karate Club, qui a un nombre chromatique de 5.

Prise en main​

Ce guide illustre l'utilisation de base du Constrained Quantum Optimizer en montrant comment charger la fonction et exécuter des problèmes simples. Pour un tutoriel plus approfondi, consulte Trouver le Maximum Independent Set avec le Constrained Quantum Optimizer d'Aqarios.

Authentifie-toi avec ta clé API IBM Quantum et le CRN de ton instance, puis charge la fonction depuis le catalogue.

remarque

Le code suivant suppose que tu as enregistré tes identifiants. Pour les configurer, suis les instructions dans Save your IBM Cloud account credentials.

# 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")

Exemple 1 : un problème contraint simple​

Le premier exemple montre comment utiliser la fonction avec des fichiers LP, une spécification courante de problème d'optimisation utilisée en recherche opérationnelle. Tu vas formuler le problème d'optimisation arbitraire suivant, comportant cinq variables binaires xi∈{0,1}x_i \in \{0,1\} soumises à deux contraintes :

min⁡x1x2+2x2x3−x3x4−2x4x5−2x1x3−4x2x5s.t.x1+x2+x3≤1x3−x4+x5=0 \min x_1 x_2 + 2 x_2 x_3 - x_3 x_4 - 2 x_4 x_5 - 2 x_1 x_3 - 4 x_2 x_5 \\ \text{s.t.}\quad x_1 + x_2 + x_3 \leq 1 \\ \quad x_3 - x_4 + x_5 = 0

1. Formuler le problème​

Exprime directement l'expression mathématique au format de chaîne LP suivant :

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
"""

Remarque que [ ... ] / 2 est une notation requise pour les objectifs quadratiques selon la norme LP.

2. Exécuter l'optimiseur​

Résous le problème avec l'optimiseur.

# 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. Récupérer le résultat​

La solution, qui associe les noms de variables aux affectations correspondantes, est fournie par le résultat.

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}}}}

Le champ solutions du dictionnaire retourné contient toutes les meilleures solutions dégénérées trouvées, sous forme de dictionnaires qui associent chaque nom de variable à son affectation. Les mêmes solutions sont également retournées sous forme de chaînes de bits dans solution_bitstrings ; metadata.solver_info.variable_mapping t'indique quelle position de caractère d'une chaîne de bits correspond à quelle variable. En plus des données de solution, l'objective_value, un indicateur feasible et des metadata supplémentaires sont fournis.

Exemple 2 : Maximum Independent Set​

Le problème du Maximum Independent Set (MIS) consiste à trouver le plus grand sous-ensemble de sommets d'un graphe tel qu'aucun couple de sommets du sous-ensemble ne partage une arête. Cet exemple résout le MIS sur un graphe régulier à 12 nœuds. Pour plus d'informations sur le problème et un exemple plus vaste, consulte le tutoriel.

Le problème est construit avec qiskit-addon-opt-mapper. Installe les paquets requis si nécessaire :

# %pip install networkx qiskit-addon-opt-mapper

Étape 1 : définir le problème​

Construis un graphe 3-régulier aléatoire et crée le problème d'optimisation MIS. Chaque nœud du graphe devient une variable binaire, et chaque arête introduit une contrainte garantissant qu'au plus une extrémité est sélectionnée.

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

Étape 2 : exécuter l'optimiseur​

Résous le problème avec l'optimiseur.

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'

Étape 3 : récupérer et interpréter les résultats​

Enfin, nous récupérons la solution et vérifions son exactitude.

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: []

Exemple 3 : coloration de graphe à 3 couleurs (Max-3-Cut)​

Le problème Max-kk-Cut partitionne les sommets d'un graphe en kk groupes afin de maximiser le nombre d'arêtes dont les extrémités appartiennent à des groupes différents. Pour k=3k = 3, cela équivaut à trouver une coloration à 3 couleurs qui maximise les arêtes entre couleurs différentes.

Formellement, chaque nœud i∈Vi \in V est affecté à l'un des kk groupes à l'aide de variables binaires xi,cx_{i,c} pour la couleur c∈{1,…,k}c \in \{1, \ldots, k\}. La contrainte one-hot ∑cxi,c=1\sum_c x_{i,c} = 1 pour chaque nœud impose une affectation unique. L'objectif maximise les arêtes entre groupes différents :

min⁡∑(u,v)∈E∑cxu,c xv,cs.t.∑cxi,c=1∀i∈V\min \sum_{(u,v) \in E} \sum_c x_{u,c} \, x_{v,c}\\ \text{s.t.} \sum_{c} x_{i,c} = 1 \quad \forall i \in V

Les contraintes one-hot sont idéales pour les mixeurs XY, que l'optimiseur gère nativement.

Cet exemple utilise le graphe du Zachary Karate Club (34 nœuds, 78 arêtes) et construit le modèle avec le Luna Model. Installe les paquets requis si nécessaire :

# %pip install networkx matplotlib luna-model

Étape 1 : définir le problème​

Génère d'abord les variables binaires avec le paquet luna-model. Ensuite, définis l'objectif et ajoute les contraintes au modèle.

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

Étape 2 : exécuter l'optimiseur​

Résous le problème avec l'optimiseur.

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'

Étape 3 : récupérer et visualiser les résultats​

Récupère et visualise les résultats.

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

Output of the previous code cell

Journal des modifications​

[0.4.0] - 2026-09-22​

Changements incompatibles

  • Le paramètre d'entrée model a été renommé en problem.
  • Le champ de sortie obj_value a été renommé en objective_value.
  • L'option postprocessing a été remplacée par postprocessing_level, qui prend un entier dans l'intervalle [0, 3] au lieu d'une chaîne de caractères. Les anciennes valeurs correspondent à "off" -> 0, "weak" -> 1, "medium" -> 2 et "strong" -> 3, et la valeur par défaut reste 3.
  • metadata.circuit_metrics a été restructuré. Il indique désormais num_qubits, gate_count, depth, two_qubit_gate_count, two_qubit_gate_depth et operations séparément pour before_transpilation et after_transpilation, au lieu d'un unique dictionnaire plat de nombres de portes moyennés. Le champ vaut None lorsque le problème a été résolu pendant le prétraitement et qu'aucun circuit n'a été exécuté (code d'erreur 4712).

Fonctionnalités

  • Ajout de l'option seed_transpiler pour rendre la synthèse et le routage des circuits reproductibles d'une exécution à l'autre.
  • Ajout de l'option job_tags pour associer tes propres étiquettes à chaque job Qiskit Runtime soumis par la fonction.
  • Ajout du champ de sortie solution_bitstrings, qui retourne les mêmes solutions que solutions sous forme de chaînes de bits.
  • Ajout de metadata.solver_info.variable_mapping, qui associe chaque nom de variable à sa position de caractère dans les entrées de solution_bitstrings.

Documentation

  • Il est désormais précisé que les variables continues (à valeurs réelles) ne sont pas prises en charge et que les variables entières nécessitent des bornes explicites. Ces modèles échouent avec le code d'erreur 4710.

Obtenir de l'aide​

Pour toute question ou tout problème, contacte support@aqarios.com en indiquant ton ID de job.

Prochaines étapes​

Recommandations

Références​

  1. Bucher et al. (2026), Constrained Quantum Optimization via Iterative Warm-Start XY-Mixers 10.1088/1367-2630/ae8ea2