Přeskočit na hlavní obsah

Optimization Solver: Qiskit Function od Q-CTRL Fire Opal

Viz referenci API

poznámka

Qiskit Functions jsou experimentální funkce dostupné pouze uživatelům IBM Quantum® Premium Plan, Flex Plan a On-Prem (prostřednictvím IBM Quantum Platform API) Plan. Jsou ve stavu preview verze a mohou se měnit.

Package versions

Kód na této stránce byl vyvinut s použitím následujících požadavků. Doporučujeme používat tyto verze nebo novější.

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

Přehled​

S Fire Opal Optimization Solver můžeš řešit optimalizační problémy v utility-scale měřítku na kvantovém hardwaru bez potřeby kvantové odbornosti. Jednoduše zadej definici problému na vysoké úrovni a Solver se postará o zbytek. Celý pracovní postup je noise-aware a využívá pod pokličkou Fire Opal Performance Management. Solver konzistentně poskytuje přesná řešení klasicky náročných problémů, a to i v plném rozsahu zařízení na největších IBM® QPU.

Solver je flexibilní a lze jej použít k řešení kombinatorických optimalizačních problémů definovaných jako účelové funkce nebo libovolné grafy. Problémy nemusí být mapovány na topologii zařízení. Řešitelné jsou jak neomezené, tak omezené problémy, přičemž omezení jsou vynucena jako tvrdá Hamiltonova omezení s váhou 1 místo penalizačních členů. Příklady obsažené v tomto průvodci ukazují, jak vyřešit neomezený a omezený optimalizační problém v utility-scale měřítku pomocí různých typů vstupů Solveru. První příklad zahrnuje problém max-cut definovaný na grafu s 156 uzly a 3-regulárním uspořádáním, zatímco druhý příklad řeší problém rozdělení grafu s 50 uzly definovaný pomocí účelové funkce.

Chceš-li získat přístup k Optimization Solver, kontaktuj Q-CTRL.

Popis funkce​

Solver plně optimalizuje a automatizuje celý algoritmus – od potlačování chyb na úrovni hardwaru až po efektivní mapování problému a uzavřenou smyčku klasické optimalizace. V zákulisí pipeline Solveru snižuje chyby v každé fázi, což umožňuje zvýšený výkon potřebný pro smysluplné škálování. Základní pracovní postup je inspirován algoritmem Quantum Approximate Optimization Algorithm (QAOA), který je hybridním kvantově-klasickým algoritmem. Podrobný přehled celého pracovního postupu Optimization Solver najdeš v publikovaném rukopisu.

Vizualizace pracovního postupu Optimization Solver

Jak vyřešit obecný problém pomocí Optimization Solver:

  1. Definuj svůj problém jako účelovou funkci, graf nebo SparsePauliOp spin chain.
  2. Připoj se k funkci prostřednictvím katalogu Qiskit Functions.
  3. Spusť problém pomocí Solveru a načti výsledky.

Přijímané formáty problémů​

  • Polynomiální vyjádření účelové funkce. Ideálně vytvořené v Pythonu s existujícím objektem SymPy Poly a formátované do řetězce pomocí sympy.srepr.

  • Grafová reprezentace konkrétního typu problému. Graf by měl být vytvořen pomocí knihovny networkx v Pythonu. Poté by měl být převeden na řetězec pomocí funkce networkx nx.readwrite.json_graph.adjacency_data.

  • Reprezentace spin chain konkrétního problému. Spin chain by měl být reprezentován jako objekt SparsePauliOp; více podrobností najdeš v dokumentaci.

Podporuje tato funkce všechny IBM Backend?

Pokud chceš použít Backend, který tato funkce aktuálně nepodporuje, kontaktuj Q-CTRL a přidej jeho podporu.

Benchmarky​

Upozornění

Výkon může záviset jak na konkrétní instanci problému, tak na následných krocích zpracování. V některých případech mohou klasické vzorky a kvantově generované vzorky dosáhnout po ekvivalentním postprocessingu podobné kvality výsledného řešení. Hodnocení by proto mělo brát v úvahu celý optimalizační pracovní postup.

Publikované výsledky benchmarkingu ukazují, že Solver úspěšně řeší problémy s více než 120 qubity a dokonce překonává dříve publikované výsledky na kvantových žíhacích a trapped-ion zařízeních. Následující metriky benchmarku poskytují hrubý přehled o přesnosti a škálování typů problémů na základě několika příkladů. Skutečné metriky se mohou lišit v závislosti na různých vlastnostech problému, jako je počet členů v účelové funkci (hustota) a jejich lokalita, počet proměnných a polynomiální řád.

Uvedený „Počet qubitů" není pevným omezením, ale představuje přibližné prahové hodnoty, kde lze očekávat mimořádně konzistentní přesnost řešení. Větší velikosti problémů byly úspěšně vyřešeny a testování za těmito hranicemi je podporováno.

Libovolná konektivita qubitů je podporována napříč všemi typy problémů.

Typ problémuPočet qubitůPříkladPřesnostCelkový čas (s)Využití Runtime (s)Počet iterací
Řídce propojené kvadratické problémy1563-regulární max-cut100%176429316
Binární optimalizace vyššího řádu156Ising spin-glass model100%146127216
Hustě propojené kvadratické problémy50Plně propojený max-cut100%175826812
Omezený problém s tvrdými omezeními50Vážené rozdělení grafu s hustotou hran 8 %100%107421510

Začínáme​

Nejprve se ověř pomocí svého IBM Quantum API klíče. Poté vyber Qiskit Function takto. (Tento úryvek předpokládá, že jsi již uložil/a svůj účet do svého lokálního prostředí.)

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

Příklad: Neomezená optimalizace​

Spusť problém maximálního řezu (Max-Cut). Následující příklad ukazuje schopnosti Solveru na problému Max-Cut s 156uzlovým, 3-regulárním nevážený grafem, ale lze řešit i vážené grafové problémy. Kromě qiskit-ibm-catalog budeš k tomuto příkladu také potřebovat následující balíčky: networkx a numpy. Tyto balíčky můžeš nainstalovat odkomentováním následující buňky, pokud spouštíš tento příklad v notebooku pomocí IPython kernelu.

# %pip install networkx numpy

1. Definuj problém​

Problém Max-Cut můžeš spustit definováním grafového problému a zadáním 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 přijímá řetězec jako vstup definice problému.

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

2. Spusť problém​

Při použití vstupní metody na základě grafu zadej typ problému.

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

Zkontroluj stav své pracovní zátěže Qiskit Function nebo načti výsledky takto:

# 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. Načti výsledek​

Načti optimální hodnotu řezu ze slovníku výsledků.

poznámka

Mapování proměnných na bitstring se mohlo změnit. Výstupní slovník obsahuje podslovník variables_to_bitstring_index_map, který pomáhá ověřit pořadí.

# 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

Přesnost výsledku můžeš ověřit klasickým řešením problému pomocí open-source solverů jako PuLP, pokud graf není hustě propojený. Problémy s vysokou hustotou mohou pro ověření řešení vyžadovat pokročilé klasické solvery.

Příklad: Omezená optimalizace​

Předchozí příklad max-cut je běžný problém kvadratické neomezené binární optimalizace. Optimization Solver od Q-CTRL dokáže řešit i omezené optimalizační problémy tím, že tvrdá omezení předá přímo Solveru prostřednictvím vstupu constraint, místo aby je kódoval jako penalizační termy v účelové funkci. Solver v současnosti podporuje omezení typu Hammingova váha 1: každé omezení určuje skupinu proměnných, kde přesně jedna proměnná musí být rovna 1 a zbytek musí být roven 0.

Následující příklad ukazuje, jak sestavit účelovou funkci a sadu tvrdých omezení pro omezený optimalizační problém, rozdělení grafu, přiřazením každého uzlu v grafu právě do jedné z několika skupin při minimalizaci celkové váhy hran, jejichž koncové body leží ve stejné skupině. Kromě balíčků qiskit-ibm-catalog a qiskit budeš k tomuto příkladu také potřebovat: numpy, networkx a sympy. Tyto balíčky můžeš nainstalovat odkomentováním následující buňky, pokud spouštíš tento příklad v notebooku pomocí IPython kernelu.

# %pip install numpy networkx sympy

1. Definuj problém​

Definuj náhodný problém rozdělení grafu vygenerováním grafu s náhodně váženými uzly.

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

Standardní optimalizační model pro vážené rozdělení grafu lze formulovat následovně. Rozděl uzly grafu do tří skupin g∈{0,1,2}g \in \{0, 1, 2\} a nech ni,g=1n_{i,g} = 1, pokud je uzel ii přiřazen do skupiny gg, a ni,g=0n_{i,g} = 0 jinak. Cílem je minimalizovat celkovou váhu hran, jejichž koncové body jsou přiřazeny do stejné skupiny, kde váha hrany (i,j)(i,j) je kombinovaná váha jejích dvou koncových bodů, ω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)]
)

Každý uzel musí být přiřazen přesně do jedné ze tří skupin. Toto je omezení typu Hammingova váha 1: pro každý uzel ii musí být přesně jedna z hodnot ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} rovna 1 a zbytek musí být roven 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

Místo toho, aby byl tento požadavek kódován jako penalizační term v účelové funkci, předej ho Solveru přímo jako tvrdé omezení pomocí vstupu 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}
Částečně omezené problémy

Nemusíš přidat každou proměnnou do constraint. Jakákoli proměnná, která ve slovníku chybí, zůstává neomezená, takže můžeš v rámci stejného problému kombinovat skupiny proměnných s tvrdým omezením a volné proměnné.

2. Spusť problém​

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

Zkontroluj stav své pracovní zátěže Qiskit Function nebo načti výsledky takto:

# 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. Získej výsledek​

Získej řešení a analyzuj výsledky. Cena řešení představuje celkovou váhu hran, jejichž koncové body skončily ve stejné skupině, takže nižší cena znamená lepší rozdělení grafu.

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

Získej podporu​

Máš-li jakékoli dotazy nebo problémy, kontaktuj Q-CTRL.

Changelog​

  • 2026-08-10: Přidána podpora tvrdých omezení (Hammingova váha 1) prostřednictvím vstupu constraint a aktualizován příklad omezené optimalizace tak, aby je používal.

  • 2026-02-11: Nyní je podporováno ibm_miami

Další kroky​