Sari la conținutul principal

Optimization Solver: O Funcție Qiskit de Q-CTRL Fire Opal

Consultă referința API

notă

Funcțiile Qiskit sunt o funcționalitate experimentală disponibilă doar utilizatorilor IBM Quantum® cu abonament Premium Plan, Flex Plan și On-Prem (prin IBM Quantum Platform API) Plan. Acestea se află în stadiu de previzualizare și pot fi modificate.

Package versions

Codul din această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.

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

Prezentare generală​

Cu Fire Opal Optimization Solver, poți rezolva probleme de optimizare la scară utilă pe hardware cuantic fără a necesita expertiză în domeniul cuantic. Pur și simplu introduci definiția problemei la nivel înalt, iar Solver-ul se ocupă de restul. Întregul flux de lucru este conștient de zgomot și utilizează Fire Opal's Performance Management în fundal. Solver-ul oferă în mod constant soluții precise pentru probleme dificil de rezolvat clasic, chiar și la scara completă a dispozitivului, pe cele mai mari QPU-uri IBM®.

Solver-ul este flexibil și poate fi utilizat pentru a rezolva probleme de optimizare combinatorie definite ca funcții obiectiv sau grafuri arbitrare. Problemele nu trebuie să fie mapate la topologia dispozitivului. Atât problemele neconstrânse, cât și cele constrânse pot fi rezolvate, cu restricțiile aplicate ca constrângeri hard cu greutate Hamming-1, în loc de termeni de penalizare. Exemplele incluse în acest ghid demonstrează cum să rezolvi o problemă de optimizare neconstrânsă și una constrânsă la scară utilă, folosind diferite tipuri de intrări ale Solver-ului. Primul exemplu implică o problemă max-cut definită pe un graf 3-regulat cu 156 de noduri, în timp ce al doilea exemplu abordează o problemă de partiționare a grafului cu 50 de noduri, definită printr-o funcție cost.

Pentru a obține acces la Optimization Solver, contactează Q-CTRL.

Descrierea funcției​

Solver-ul optimizează și automatizează complet întregul algoritm, de la suprimarea erorilor la nivel hardware până la maparea eficientă a problemei și optimizarea clasică în buclă închisă. În fundal, pipeline-ul Solver-ului reduce erorile la fiecare etapă, permițând performanța îmbunătățită necesară pentru o scalare semnificativă. Fluxul de lucru de bază este inspirat de Quantum Approximate Optimization Algorithm (QAOA), care este un algoritm hibrid cuantic-clasic. Pentru un rezumat detaliat al fluxului complet de lucru al Optimization Solver, consultă manuscrisul publicat.

Vizualizarea fluxului de lucru al Optimization Solver

Pentru a rezolva o problemă generică cu Optimization Solver:

  1. Definește problema ta ca o funcție obiectiv, un graf sau un lanț de spin SparsePauliOp.
  2. Conectează-te la funcție prin Catalogul de Funcții Qiskit.
  3. Rulează problema cu Solver-ul și recuperează rezultatele.

Formate de problemă acceptate​

  • Reprezentarea expresiei polinomiale a unei funcții obiectiv. Ideal creată în Python cu un obiect SymPy Poly existent și formatată într-un șir folosind sympy.srepr.

  • Reprezentarea grafului unui tip specific de problemă. Graful trebuie creat folosind biblioteca networkx în Python. Apoi trebuie convertit într-un șir folosind funcția networkx nx.readwrite.json_graph.adjacency_data.

  • Reprezentarea lanțului de spin a unei probleme specifice. Lanțul de spin trebuie reprezentat ca obiect SparsePauliOp; consultă documentația pentru mai multe detalii.

Această funcție suportă toate Backend-urile IBM?

Dacă vrei să folosești un Backend pe care această funcție nu îl suportă în prezent, contactează Q-CTRL pentru a adăuga suport.

Benchmark-uri​

Declinare de responsabilitate

Performanța poate depinde atât de instanța problemei, cât și de pașii de procesare ulteriori. În unele cazuri, eșantioanele clasice și cele generate cuantic ar putea atinge o calitate finală similară a soluției după o post-procesare echivalentă. Prin urmare, evaluarea ar trebui să ia în considerare întregul flux de lucru de optimizare.

Rezultatele de benchmark publicate arată că Solver-ul rezolvă cu succes probleme cu peste 120 de Qubiți, depășind chiar rezultatele publicate anterior pe dispozitivele de tip quantum annealing și trapped-ion. Următoarele metrici de benchmark oferă o indicație aproximativă a acurateței și scalabilității tipurilor de probleme, bazate pe câteva exemple. Metricile reale pot diferi în funcție de diverse caracteristici ale problemei, cum ar fi numărul de termeni din funcția obiectiv (densitate) și localitatea lor, numărul de variabile și ordinul polinomului.

„Numărul de Qubiți" indicat nu este o limitare strictă, ci reprezintă praguri aproximative unde poți anticipa o acuratețe a soluției extrem de consistentă. Dimensiunile mai mari ale problemelor au fost rezolvate cu succes, iar testarea dincolo de aceste limite este încurajată.

Conectivitatea arbitrară a Qubiților este suportată pentru toate tipurile de probleme.

Tip de problemăNumăr de QubițiExempluAcuratețeTimp total (s)Utilizare Runtime (s)Număr de iterații
Probleme pătratice cu conectivitate redusă156max-cut 3-regulat100%176429316
Optimizare binară de ordin superior156Model Ising spin-glass100%146127216
Probleme pătratice cu conectivitate densă50max-cut complet conectat100%175826812
Problemă constrânsă cu constrângeri hard50Partiționare ponderată a grafului cu densitate de 8% a muchiilor100%107421510

Începe​

Mai întâi, autentifică-te folosind cheia API IBM Quantum. Apoi, selectează Funcția Qiskit după cum urmează. (Acest fragment de cod presupune că ai salvat deja contul în mediul tău local.)

# Added by doQumentation — required packages for this notebook
!pip install -q networkx numpy qiskit-ibm-catalog qiskit-ibm-runtime sympy
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")

Exemplu: Optimizare neconstrânsă​

Rulează problema tăieturii maxime (Max-Cut). Următorul exemplu demonstrează capacitățile Solver-ului pe o problemă Max-Cut pe un graf 3-regulat neponderat cu 156 de noduri, dar poți rezolva și probleme pe grafuri ponderate. Pe lângă qiskit-ibm-catalog, vei folosi și următoarele pachete pentru a rula acest exemplu: networkx și numpy. Poți instala aceste pachete decomentând celula de mai jos dacă rulezi acest exemplu într-un notebook folosind kernelul IPython.

# %pip install networkx numpy

1. Definește problema​

Poți rula o problemă Max-Cut definind o problemă pe bază de graf și specificând 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 of the previous code cell

Solver-ul acceptă un șir ca intrare pentru definiția problemei.

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

2. Rulează problema​

Când folosești metoda de intrare bazată pe graf, specifică tipul problemei.

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

Verifică starea sarcinii din Funcția Qiskit sau recuperează rezultatele după cum urmează:

# 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. Recuperează rezultatul​

Recuperează valoarea tăieturii optime din dicționarul de rezultate.

notă

Maparea variabilelor la șirul de biți poate fi modificată. Dicționarul de ieșire conține un sub-dicționar variables_to_bitstring_index_map, care ajută la verificarea ordinii.

# 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

Poți verifica acuratețea rezultatului rezolvând problema clasic cu solvere open-source precum PuLP, dacă graful nu este dens conectat. Problemele cu densitate ridicată pot necesita solvere clasice avansate pentru a valida soluția.

Exemplu: Optimizare constrânsă​

Exemplul anterior de max-cut este o problemă comună de optimizare binară fără constrângeri cuadratice. Optimization Solver de la Q-CTRL poate rezolva, de asemenea, probleme de optimizare constrânse trecând constrângeri stricte direct către Solver prin intrarea constraint, în loc să le codifice ca termeni de penalizare în funcția obiectiv. Solver-ul suportă în prezent constrângeri de tip Hamming-weight-1: fiecare constrângere specifică un grup de variabile în care exact o variabilă trebuie să fie egală cu 1 și restul trebuie să fie egale cu 0.

Următorul exemplu demonstrează cum să construiești o funcție cost și un set de constrângeri stricte pentru o problemă de optimizare constrânsă, partiționarea graf, atribuind fiecare nod dintr-un graf exact unuia dintre mai multe grupuri, minimizând în același timp greutatea totală a muchiilor ale căror capete se află în același grup. Pe lângă pachetele qiskit-ibm-catalog și qiskit, vei folosi și următoarele pachete pentru a rula acest exemplu: numpy, networkx și sympy. Poți instala aceste pachete decomentând celula de mai jos dacă rulezi acest exemplu într-un notebook folosind kernelul IPython.

# %pip install numpy networkx sympy

1. Definește problema​

Definește o problemă aleatoare de partiționare a grafului generând un graf cu noduri ponderate aleator.

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)

Output of the previous code cell

Un model standard de optimizare pentru partiționarea unui graf ponderat poate fi formulat astfel. Împarte nodurile graf­ului în trei grupuri g∈{0,1,2}g \in \{0, 1, 2\} și fie ni,g=1n_{i,g} = 1 dacă nodul ii este atribuit grupului gg, și ni,g=0n_{i,g} = 0 altfel. Scopul este să minimizezi greutatea totală a muchiilor ale căror capete sunt atribuite aceluiași grup, unde greutatea unei muchii (i,j)(i,j) este greutatea combinată a celor două capete ale sale, ω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)]
)

Fiecare nod trebuie atribuit exact unuia dintre cele trei grupuri. Aceasta este o constrângere de tip Hamming-weight-1: pentru fiecare nod ii, exact unul dintre ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} trebuie să fie egal cu 1, iar restul trebuie să fie egale cu 0:

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

În loc să codifici această cerință ca un termen de penalizare în funcția cost, trece-o direct la Solver ca o constrângere strictă folosind intrarea constraint.

# 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}
Probleme parțial constrânse

Nu trebuie să adaugi fiecare variabilă la constraint. Orice variabilă lăsată în afara dicționarului rămâne neconstrânsă, astfel încât poți combina grupuri de variabile strict constrânse cu variabile libere în aceeași problemă.

2. Rulează problema​

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

Verifică starea sarcinii din Funcția Qiskit sau recuperează rezultatele după cum urmează:

# 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. Obține rezultatul​

Preia soluția și analizează rezultatele. Costul soluției reprezintă greutatea totală a muchiilor ale căror capete au ajuns în același grup, deci un cost mai mic indică o partiționare mai bună a grafului.

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

Obține suport​

Pentru orice întrebări sau probleme, contactează Q-CTRL.

Jurnal de modificări​

  • 2026-08-10: A fost adăugat suport pentru constrângeri stricte (Hamming weight 1) prin intrarea constraint, iar exemplul de optimizare constrânsă a fost actualizat pentru a le folosi.

  • 2026-02-11: Acum avem suport pentru ibm_miami

Pași următori​