Přeskočit na hlavní obsah

Optimization Solver: Qiskit Function od Q-CTRL Fire Opal

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.

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 za předpokladu, že omezení lze formulovat jako penalizační členy. 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 Minimum Vertex Cover 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.

Vstupy a výstupy

Vstupy

NázevTypPopisPovinnéVýchozíPříklad
problemstr nebo SparsePauliOpJedno z reprezentací uvedených v části „Přijímané formáty problémů"AnoN/APoly(2.0*n[0]*n[1] + 2.0*n[0]*n[2] - 3.13520241298341*n[0] + 2.0*n[1]*n[2] - 3.14469748506794*n[1] - 3.18897660121566*n[2] + 6.0, n[0], n[1], n[2], domain='RR')
problem_typestrNázev třídy problému; používá se pouze pro definice problémů na základě grafu a spin chain, které jsou omezeny na „maxcut" nebo „spin_chain"; není vyžadováno pro definice problémů s libovolnou účelovou funkcíNeNone"maxcut"
backend_namestrNázev Backend, který se má použítNeNejméně vytížený Backend, ke kterému má tvá instance přístup"ibm_fez"
optionsdictVstupní možnosti, včetně následujících: (Volitelné) session_id: str; výchozí chování vytvoří novou SessionNeNone{"session_id": "cw2q70c79ws0008z6g4g"}

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 a poté převeden na řetězec pomocí funkce networkx [nx.readwrite.json_graph.adjacency_data](http://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.

Podporované Backend: Spusť následující kód, abys viděl seznam Backend, které jsou aktuálně podporovány. Pokud tvé zařízení není uvedeno, kontaktuj Q-CTRL a přidej jeho podporu.

# Added by doQumentation — required packages for this notebook
!pip install -q networkx numpy qiskit-ibm-catalog qiskit-ibm-runtime sympy
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()

service.backends()
[<IBMBackend('ibm_fez')>,
<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_kingston')>,
<IBMBackend('ibm_torino')>,
<IBMBackend('ibm_marrakesh')>]

Možnosti:

NázevTypPopisVýchozí
session_idstrID existující Qiskit Runtime Session"cw4r3je6f0t010870y3g"
job_tagslist[str]Seznam tagů úloh[]

Výstupy

NázevTypPopisPříklad
resultdict[str, Any]Řešení a metadata uvedená v části „Obsah slovníku výsledků"{'solution_bitstring_cost': 3.0, 'final_bitstring_distribution': {'000001': 100, '000011': 2}, 'iteration_count': 3, 'solution_bitstring': '000001', 'variables_to_bitstring_index_map': {n[1]': 5, 'n[2]': 4, 'n[3]': 3, 'n[4]': 2, 'n[5]': 1}, 'best_parameters': [0.19628831763697097, -1.047052334523102], 'warnings': []}

Obsah slovníku výsledků:

NázevTypPopis
solution_bitstring_costfloatNejlepší minimální cena napříč všemi iteracemi
final_bitstring_distributionCountsDictSlovník počtů bitstring přidružený k minimální ceně
solution_bitstringstrBitstring s nejlepší cenou ve finálním rozložení
iteration_countintCelkový počet QAOA iterací provedených Solverem
variables_to_bitstring_index_mapfloatMapování proměnných na odpovídající bit v bitstringu
best_parameterslist[float]Optimalizované parametry beta a gamma napříč všemi iteracemi
warningslist[str]Varování vzniklá při kompilaci nebo spuštění QAOA (výchozí hodnota je None)

Benchmarky

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 penalizačními členy50Vážené Minimum Vertex Cover 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 svůj účet do svého lokálního prostředí.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

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

Výstup předchozí buňky kódu

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:

# Get job status
print(maxcut_job.status())
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: 209.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ý kvadratický neomezený binární optimalizační problém. Optimization Solver od Q-CTRL lze použít pro různé typy problémů, včetně omezené optimalizace. Libovolné typy problémů můžeš řešit zadáním definice problému reprezentované jako polynom, kde jsou omezení modelována jako penalizační členy.

Následující příklad ukazuje, jak sestavit účelovou funkci pro omezený optimalizační problém – minimum vertex cover (MVC). 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 MVC vygenerováním grafu s náhodně váženými uzly.

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)

Výstup předchozí buňky kódu

Standardní optimalizační model pro vážené MVC lze formulovat takto. Nejprve musí být přidána penalizace pro každý případ, kdy hrana není spojena s vrcholem v podmnožině. Proto nechť ni=1n_i = 1, pokud je vrchol ii v pokrytí (tj. v podmnožině), a ni=0n_i = 0 v opačném případě. Zadruhé, cílem je minimalizovat celkový počet vrcholů v podmnožině, což lze vyjádřit následující funkcí:

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

Nyní každá hrana v grafu musí obsahovat alespoň jeden koncový bod z pokrytí, což lze vyjádřit nerovností:

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

Každý případ, kdy hrana není spojena s vrcholem pokrytí, musí být penalizován. To lze vyjádřit v účelové funkci přidáním penalizačního členu ve tvaru P(1ninj+ninj)P(1-n_i-n_j+n_i n_j), kde PP je kladná penalizační konstanta. Neomezená alternativa k omezené nerovnosti pro vážené MVC je tedy:

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. Spusť problém

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

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

print(mvc_job.status())

3. Získej výsledek

Načti řešení a analyzuj výsledky. Protože tento problém má vážené uzly, řešení není jednoduše minimální počet pokrytých uzlů. Místo toho cena řešení představuje součet vah vrcholů, které jsou zahrnuty ve vertex cover. Reprezentuje celkové „náklady" nebo „váhu" pokrytí všech hran v grafu pomocí vybraných vrcholů.

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

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

Získej podporu

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

Další kroky