Skip to main content
IBM Quantum Platform

Optimization Solver : une fonction Qiskit par Q-CTRL Fire Opal

Consultez la documentation de l'API

Note

Les fonctions Qiskit sont une fonctionnalité expérimentale disponible uniquement pour IBM Quantum® Premium Plan, Flex Plan, et On-Prem (via IBM Quantum Platform API). Elles sont en cours de publication et peuvent être modifiées.

  • Le code présenté sur cette page a été développé en respectant les exigences suivantes. Nous vous recommandons d'utiliser ces versions ou des versions plus récentes.

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

Aperçu

Avec le solveur d'optimisation Fire Opal, vous pouvez résoudre des problèmes d'optimisation à grande échelle sur du matériel quantique sans avoir besoin d'expertise quantique. Il suffit de saisir la définition du problème de haut niveau, et le Solveur s'occupe du reste. L'ensemble du flux de travail est sensible au bruit et tire parti de la gestion des performances de Fire Opal. Le Solveur fournit systématiquement des solutions précises à des problèmes classiques, même à l'échelle d'un appareil complet sur les plus grandes QPU IBM®.

Le solveur est polyvalent et peut être utilisé pour résoudre des problèmes d'optimisation combinatoire définis sous forme de fonctions objectif ou de graphes arbitraires. Il n'est pas nécessaire de faire correspondre les problèmes à la topologie du dispositif. On peut résoudre aussi bien les problèmes sans contraintes que ceux avec contraintes, à condition que ces dernières puissent être formulées sous forme de termes de pénalité. Les exemples présentés dans ce guide montrent comment résoudre un problème d'optimisation à grande échelle, avec ou sans contraintes, en utilisant différents types de données d'entrée pour le solveur. Le premier exemple porte sur un problème de coupe maximale défini sur un graphe régulier de degré 3 comptant 156 nœuds, tandis que le second exemple traite d'un problème de couverture minimale des sommets défini par une fonction de coût et portant sur un graphe de 50 nœuds.

Pour obtenir l'accès au solveur d'optimisation, contactez Q-CTRL.


Description de la fonction

Le Solver optimise et automatise entièrement l'algorithme, de la suppression des erreurs au niveau matériel à la cartographie efficace des problèmes et à l'optimisation classique en boucle fermée. En coulisses, le pipeline du Solveur réduit les erreurs à chaque étape, ce qui permet d'améliorer les performances requises pour une mise à l'échelle significative. Le processus sous-jacent s'inspire de l'algorithme d'optimisation approximative quantique (QAOA), qui est un algorithme hybride quantique-classique. Pour un résumé détaillé du flux de travail complet d'Optimization Solver, veuillez vous référer au manuscrit publié.

Visualisation du flux de travail du solveur d'optimisation

Pour résoudre un problème générique avec le solveur d'optimisation :

  1. Définissez votre problème sous la forme d'une fonction objective, d'un graphique ou d'une chaîne de spin ( SparsePauliOp ).
  2. Connectez-vous à la fonction via le catalogue de fonctions Qiskit.
  3. Exécutez le problème avec le Solveur et récupérez les résultats.

Formats de problèmes acceptés

  • Représentation de l'expression polynomiale d'une fonction objective. Idéalement créé dans Python avec un objet Poly existant SymPy et formaté dans une chaîne de caractères à l'aide de sympy.srepr.
  • Représentation graphique d'un type de problème spécifique. Le graphique doit être créé à l'aide de la bibliothèque networkx dans Python. Il doit ensuite être converti en chaîne de caractères à l'aide de la fonction networkx [nx.readwrite.json_graph.adjacency_data](http://nx.readwrite.json_graph.adjacency_data.).
  • Représentation en chaîne d'un problème spécifique. La chaîne de spin doit être représentée sous la forme d'un objet SparsePauliOp ; voir la documentation pour plus de détails.
Cette fonction prend-elle en charge tous les backends d' IBM?

Si vous souhaitez utiliser un backend qui n'est pas encore pris en charge par cette fonction, contactez Q-CTRL pour qu'il soit ajouté.


Tests de performances

Les résultats d'étalonnage publiés montrent que le Solver résout avec succès des problèmes avec plus de 120 qubits, surpassant même les résultats précédemment publiés sur le recuit quantique et les dispositifs à ions piégés. Les mesures de référence suivantes donnent une indication approximative de la précision et de l'étendue des types de problèmes sur la base de quelques exemples. Les mesures réelles peuvent différer en fonction de diverses caractéristiques du problème, telles que le nombre de termes dans la fonction objective (densité) et leur localisation, le nombre de variables et l'ordre polynomial.

Le "nombre de qubits" indiqué n'est pas une limite absolue, mais représente des seuils approximatifs pour lesquels on peut s'attendre à une précision extrêmement constante des solutions. Des problèmes de plus grande taille ont été résolus avec succès, et les essais au-delà de ces limites sont encouragés.

La connectivité arbitraire des qubits est prise en charge pour tous les types de problèmes.

Type de problème
Nombre de qubits
Exemple
Exactitude
Durée totale (s)
Utilisation du temps d'exécution (s)
Nombre d'itérations
Problèmes quadratiques à connexions éparses1563-régulier max-cut100 %176429316
Optimisation binaire d'ordre supérieur156Modèle de verre de spin d'Ising100 %146127216
Problèmes quadratiques densément connectés50Max-Cut entièrement connecté100 %175826812
Problème contraint avec termes de pénalité50Couverture minimale pondérée des sommets avec une densité d'arêtes de 8%100 %107421510

Premiers pas

Commencez par vous authentifier à l'aide de votre clé API IBM Quantum. Ensuite, sélectionnez la fonction Qiskit comme suit. (Cet extrait de code part du principe que vous avez déjà enregistré votre compte dans votre environnement local.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Verify that you have access to the function
catalog.list()

Output:

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

Exemple : optimisation sans contrainte

Résolvez le problème de la coupe maximale (max-cut). L'exemple suivant illustre les capacités du Solveur sur un problème de coupe maximale dans un graphe non pondéré à 3 arêtes régulières et 156 nœuds, mais vous pouvez également résoudre des problèmes sur des graphes pondérés.

En plus de qiskit-ibm-catalog, vous utiliserez également les paquets suivants pour exécuter cet exemple : networkx et numpy. Vous pouvez installer ces paquets en décommentant la cellule suivante si vous exécutez cet exemple dans un ordinateur portable utilisant le noyau IPython.

# %pip install networkx numpy

1. Définir le problème

Vous pouvez résoudre un problème de coupe maximale en définissant un problème de graphe et en spécifiant problem_type='maxcut'.

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
)

Output:

Output of the previous code cell

Le Solveur accepte une chaîne de caractères comme entrée de la définition du problème.

# Convert graph to string
problem_as_str = nx.readwrite.json_graph.adjacency_data(maxcut_graph)

2. Exécutez le problème

Lors de l'utilisation de la méthode de saisie basée sur un graphique, spécifiez le type de problème.

# Solve the problem
maxcut_job = solver.run(
    problem=problem_as_str,
    problem_type="maxcut",
    backend_name=backend_name,  # E.g. "ibm_fez"
)

Pour vérifier l'état de votre charge de travail Qiskit Function ou obtenir les résultats, procédez comme suit :

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

# Get job status
print(maxcut_job.status())

Output:

34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED

3. Récupérer le résultat

Récupérer la valeur de coupe optimale dans le dictionnaire des résultats.

Note

Le mappage des variables vers la chaîne de bits peut avoir changé. Le dictionnaire de sortie contient un variables_to_bitstring_index_map sous-dictionnaire qui aide à vérifier l'ordre.

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

Output:

Optimal cut value: 210.0

Vous pouvez vérifier l'exactitude du résultat en résolvant le problème de manière classique avec des solveurs open-source tels que PuLP si le graphe n'est pas densément connecté. Les problèmes de haute densité peuvent nécessiter des solveurs classiques avancés pour valider la solution.


Exemple : optimisation sous contraintes

L'exemple précédent de « max-cut » est un problème courant d'optimisation binaire quadratique sans contraintes. Le solveur d'optimisation de Q-CTRL peut être utilisé pour divers types de problèmes, y compris l'optimisation sous contraintes. Vous pouvez résoudre des problèmes de nature quelconque en saisissant la formulation du problème sous forme de polynôme, dans lequel les contraintes sont modélisées sous forme de termes de pénalité.

L'exemple suivant montre comment construire une fonction de coût pour un problème d'optimisation avec contraintes, la couverture minimale des sommets (MVC).

En plus des paquets qiskit-ibm-catalog et qiskit , vous utiliserez également les paquets suivants pour exécuter cet exemple : numpy, networkx, et sympy. Vous pouvez installer ces paquets en décommentant la cellule suivante si vous exécutez cet exemple dans un ordinateur portable utilisant le noyau IPython.

# %pip install numpy networkx sympy

1. Définir le problème

Définir un problème MVC aléatoire en générant un graphe dont les nœuds sont pondérés de manière aléatoire.

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)

Output:

Output of the previous code cell

Un modèle d'optimisation standard pour le MVC pondéré peut être formulé comme suit. Tout d'abord, une pénalité doit être ajoutée dans tous les cas où une arête n'est pas connectée à un sommet du sous-ensemble. Par conséquent, laissez ni=1n_i = 1 si le sommet ii est dans la couverture (c'est-à-dire dans le sous-ensemble) et ni=0n_i = 0 dans le cas contraire. Deuxièmement, l'objectif est de minimiser le nombre total de sommets dans le sous-ensemble, ce qui peut être représenté par la fonction suivante :

Minimizey=iVωini\textbf{Minimize}\qquad y = \sum_{i\in V} \omega_i n_i

# 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

Maintenant, chaque arête du graphique doit inclure au moins un point d'extrémité de la couverture, ce qui peut être exprimé par l'inégalité :

ni+nj1 for all (i,j)En_i + n_j \ge 1 \texttt{ for all } (i,j)\in E

Tous les cas où une arête n'est pas connectée au sommet de couverture doivent être pénalisés. Cela peut être représenté dans la fonction de coût par l'ajout d'une pénalité de la forme P(1ninj+ninj)P(1-n_i-n_j+n_i n_j)PP est une constante de pénalité positive. Ainsi, une alternative non contrainte à l'inégalité contrainte pour la CVM pondérée est :

Minimizey=iVωini+P((i,j)E(1ninj+ninj))\textbf{Minimize}\qquad y = \sum_{i\in V}\omega_i n_i + P(\sum_{(i,j)\in E}(1 - n_i - n_j + n_i n_j))

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

2. Exécutez le problème

# Solve the problem
mvc_job = solver.run(
    problem=srepr(cost_function),
    backend_name=backend_name,  # E.g. "ibm_fez"
)

Pour vérifier l'état de votre charge de travail Qiskit Function ou obtenir les résultats, procédez comme suit :

print(mvc_job.status())

Output:

QUEUED

3. Obtenir le résultat

Récupérer la solution et analyser les résultats. Comme ce problème comporte des nœuds pondérés, la solution n'est pas simplement le nombre minimum de nœuds couverts. Au lieu de cela, le coût de la solution représente la somme des poids des sommets inclus dans la couverture des sommets. Il représente le "coût" ou le "poids" total de la couverture de toutes les arêtes du graphe par les sommets sélectionnés.

mvc_result = mvc_job.result()
qctrl_cost = mvc_result["solution_bitstring_cost"]

# Print results
print(f"Solution cost: {qctrl_cost}")

Output:

Solution cost: 10.248198273708624

Obtenir de l'aide

Pour toute question ou problème, contactez Q-CTRL.


Journal des modifications

  • 11 février 2026 : Nous prenons désormais en charge ibm_miami

Etapes suivantes

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.