Przejdź do głównej treści

Optimization Solver: Funkcja Qiskit od Q-CTRL Fire Opal

uwaga

Funkcje Qiskit to funkcja eksperymentalna dostępna wyłącznie dla użytkowników planów IBM Quantum® Premium Plan, Flex Plan oraz On-Prem (przez IBM Quantum Platform API). Są one w stanie podglądu (preview) i mogą ulec zmianie.

Przegląd

Dzięki Fire Opal Optimization Solver możesz rozwiązywać problemy optymalizacyjne w skali użytkowej na sprzęcie kwantowym bez konieczności posiadania wiedzy z zakresu informatyki kwantowej. Po prostu podaj definicję problemu na wysokim poziomie, a Solver zajmie się resztą. Cały przepływ pracy jest świadomy szumów i pod spodem korzysta z Fire Opal Performance Management. Solver konsekwentnie dostarcza dokładnych rozwiązań klasycznie trudnych problemów, nawet w pełnej skali urządzenia na największych QPU IBM®.

Solver jest elastyczny i można go używać do rozwiązywania kombinatorycznych problemów optymalizacyjnych zdefiniowanych jako funkcje celu lub dowolne grafy. Problemy nie muszą być mapowane na topologię urządzenia. Można rozwiązywać zarówno problemy nieograniczone, jak i ograniczone, pod warunkiem że ograniczenia można sformułować jako człony kary. Przykłady zawarte w tym przewodniku pokazują, jak rozwiązać nieograniczony i ograniczony problem optymalizacyjny w skali użytkowej przy użyciu różnych typów danych wejściowych Solvera. Pierwszy przykład dotyczy problemu Max-Cut zdefiniowanego na grafie 3-regularnym o 156 węzłach, podczas gdy drugi przykład dotyczy problemu Minimalnego Pokrycia Wierzchołkowego (Minimum Vertex Cover) na 50 węzłach zdefiniowanego przez funkcję kosztu.

Aby uzyskać dostęp do Optimization Solver, skontaktuj się z Q-CTRL.

Opis funkcji

Solver w pełni optymalizuje i automatyzuje cały algorytm – od tłumienia błędów na poziomie sprzętu po wydajne mapowanie problemu i zamkniętą pętlę optymalizacji klasycznej. Za kulisami potok Solvera redukuje błędy na każdym etapie, umożliwiając zwiększoną wydajność niezbędną do sensownego skalowania. Bazowy przepływ pracy jest inspirowany Quantum Approximate Optimization Algorithm (QAOA), który jest hybrydowym algorytmem kwantowo-klasycznym. Szczegółowe podsumowanie pełnego przepływu pracy Optimization Solver znajdziesz w opublikowanym artykule.

Wizualizacja przepływu pracy Optimization Solver

Aby rozwiązać ogólny problem za pomocą Optimization Solver:

  1. Zdefiniuj swój problem jako funkcję celu, graf lub łańcuch spinowy SparsePauliOp.
  2. Połącz się z funkcją przez katalog Qiskit Functions Catalog.
  3. Uruchom problem za pomocą Solvera i pobierz wyniki.

Dane wejściowe i wyjściowe

Dane wejściowe

NazwaTypOpisWymaganeDomyślnePrzykład
problemstr lub SparsePauliOpJedna z reprezentacji wymienionych w sekcji „Akceptowane formaty problemu"TakbrakPoly(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_typestrNazwa klasy problemu; używana tylko dla definicji problemu opartych na grafie i łańcuchu spinowym, które są ograniczone do „maxcut" lub „spin_chain"; niewymagane dla definicji problemów z dowolną funkcją celuNieNone"maxcut"
backend_namestrNazwa Backend do użyciaNieNajmniej zajęty Backend, do którego masz dostęp"ibm_fez"
optionsdictOpcje wejściowe, w tym: (Opcjonalne) session_id: str; domyślne zachowanie tworzy nową SessionNieNone{"session_id": "cw2q70c79ws0008z6g4g"}

Akceptowane formaty problemu:

  • Reprezentacja wyrażenia wielomianowego funkcji celu. Najlepiej tworzona w Pythonie za pomocą istniejącego obiektu SymPy Poly i formatowana do postaci ciągu znaków przy użyciu sympy.srepr.
  • Reprezentacja grafowa określonego typu problemu. Graf powinien być tworzony przy użyciu biblioteki networkx w Pythonie, a następnie konwertowany do ciągu znaków za pomocą funkcji networkx [nx.readwrite.json_graph.adjacency_data](http://nx.readwrite.json_graph.adjacency_data.).
  • Reprezentacja łańcucha spinowego określonego problemu. Łańcuch spinowy powinien być reprezentowany jako obiekt SparsePauliOp; więcej szczegółów znajdziesz w dokumentacji.

Obsługiwane Backend-y: Uruchom następujący kod, aby zobaczyć listę Backend-ów, które są aktualnie obsługiwane. Jeśli twojego urządzenia nie ma na liście, skontaktuj się z Q-CTRL, aby dodać obsługę.

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

Opcje:

NazwaTypOpisDomyślne
session_idstrIdentyfikator istniejącej Session Qiskit Runtime"cw4r3je6f0t010870y3g"
job_tagslist[str]Lista tagów zadania[]

Dane wyjściowe

NazwaTypOpisPrzykład
resultdict[str, Any]Rozwiązanie i metadane wymienione w sekcji „Zawartość słownika wyników"{'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': []}

Zawartość słownika wyników:

NazwaTypOpis
solution_bitstring_costfloatNajlepszy minimalny koszt we wszystkich iteracjach
final_bitstring_distributionCountsDictSłownik liczby bitstring-ów powiązany z minimalnym kosztem
solution_bitstringstrBitstring z najlepszym kosztem w końcowym rozkładzie
iteration_countintCałkowita liczba iteracji QAOA wykonanych przez Solver
variables_to_bitstring_index_mapfloatMapowanie zmiennych na odpowiadający bit w bitstring-u
best_parameterslist[float]Zoptymalizowane parametry beta i gamma we wszystkich iteracjach
warningslist[str]Ostrzeżenia wygenerowane podczas kompilacji lub uruchamiania QAOA (domyślnie None)

Benchmarki

Opublikowane wyniki testów porównawczych pokazują, że Solver skutecznie rozwiązuje problemy z ponad 120 Qubitami, a nawet przewyższa wcześniej opublikowane wyniki na urządzeniach do wyżarzania kwantowego i pułapkowania jonów. Poniższe metryki benchmarkowe dają przybliżone wskazanie dokładności i skalowalności typów problemów na podstawie kilku przykładów. Rzeczywiste metryki mogą się różnić w zależności od różnych cech problemu, takich jak liczba składników w funkcji celu (gęstość) i ich lokalność, liczba zmiennych oraz rząd wielomianu.

„Liczba Qubitów" podana w tabeli nie jest twardym ograniczeniem, lecz przybliżonym progiem, poniżej którego możesz oczekiwać bardzo spójnej dokładności rozwiązania. Większe rozmiary problemów zostały z powodzeniem rozwiązane i zachęcamy do testowania powyżej tych limitów.

Dowolna łączność Qubitów jest obsługiwana dla wszystkich typów problemów.

Typ problemuLiczba QubitówPrzykładDokładnośćCałkowity czas (s)Użycie środowiska uruchomieniowego (s)Liczba iteracji
Rzadko połączone problemy kwadratowe156Max-Cut 3-regularny100%176429316
Optymalizacja binarna wyższego rzędu156Model szkła spinowego Isinga100%146127216
Gęsto połączone problemy kwadratowe50Max-Cut w pełni połączony100%175826812
Problem ograniczony z członami kary50Ważone Minimalne Pokrycie Wierzchołkowe z gęstością krawędzi 8%100%107421510

Pierwsze kroki

Najpierw uwierzytelnij się przy użyciu swojego klucza API IBM Quantum. Następnie wybierz Funkcję Qiskit w sposób podany poniżej. (Ten fragment zakłada, że masz już zapisane konto w lokalnym środowisku.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Access Function
solver = catalog.load("q-ctrl/optimization-solver")

Przykład: Optymalizacja nieograniczona

Uruchom problem maksymalnego cięcia (Max-Cut). Poniższy przykład demonstruje możliwości Solvera na problemie Max-Cut na nieważonym grafie 3-regularnym o 156 węzłach, ale możesz też rozwiązywać problemy na grafach ważonych. Oprócz qiskit-ibm-catalog będziesz również używać następujących pakietów do uruchomienia tego przykładu: networkx i numpy. Możesz zainstalować te pakiety, odkomentowując poniższą komórkę, jeśli uruchamiasz ten przykład w notatniku z jądrem IPython.

# %pip install networkx numpy

1. Zdefiniuj problem

Możesz uruchomić problem Max-Cut, definiując problem grafowy i określając 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
)

Wynik poprzedniej komórki kodu

Solver przyjmuje ciąg znaków jako dane wejściowe definicji problemu.

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

2. Uruchom problem

Gdy używasz metody wejściowej opartej na grafie, określ typ problemu.

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

Sprawdź status obciążenia swojej Funkcji Qiskit lub pobierz wyniki w następujący sposób:

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

3. Pobierz wynik

Pobierz optymalną wartość cięcia ze słownika wyników.

uwaga
Mapowanie zmiennych do bitstring-u mogło ulec zmianie. Słownik wyjściowy zawiera pod-słownik variables_to_bitstring_index_map, który pomaga zweryfikować kolejność.
# 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

Możesz zweryfikować dokładność wyniku, rozwiązując problem klasycznie za pomocą solverów open-source, takich jak PuLP, jeśli graf nie jest gęsto połączony. W przypadku problemów o dużej gęstości do walidacji rozwiązania mogą być wymagane zaawansowane solwery klasyczne.

Przykład: Optymalizacja ograniczona

Poprzedni przykład Max-Cut to popularny problem kwadratowej binarnej optymalizacji bez ograniczeń. Optimization Solver Q-CTRL można stosować do różnych typów problemów, w tym optymalizacji ograniczonej. Możesz rozwiązywać dowolne typy problemów, podając definicję problemu reprezentowaną jako wielomian, w którym ograniczenia są modelowane jako człony kary.

Poniższy przykład pokazuje, jak skonstruować funkcję kosztu dla ograniczonego problemu optymalizacyjnego, minimalnego pokrycia wierzchołkowego (MVC). Oprócz pakietów qiskit-ibm-catalog i qiskit będziesz również używać następujących pakietów do uruchomienia tego przykładu: numpy, networkx i sympy. Możesz zainstalować te pakiety, odkomentowując poniższą komórkę, jeśli uruchamiasz ten przykład w notatniku z jądrem IPython.

# %pip install numpy networkx sympy

1. Zdefiniuj problem

Zdefiniuj losowy problem MVC, generując graf z losowo ważonymi węzłami.

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)

Wynik poprzedniej komórki kodu

Standardowy model optymalizacyjny dla ważonego MVC można sformułować w następujący sposób. Po pierwsze, należy dodać karę za każdy przypadek, gdy krawędź nie jest połączona z wierzchołkiem w podzbiorze. Dlatego niech ni=1n_i = 1, jeśli wierzchołek ii należy do pokrycia (tj. do podzbioru) i ni=0n_i = 0 w przeciwnym razie. Po drugie, celem jest minimalizacja łącznej liczby wierzchołków w podzbiorze, co można wyrazić następującą funkcją:

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

Teraz każda krawędź w grafie powinna zawierać co najmniej jeden punkt końcowy z pokrycia, co można wyrazić jako nierówność:

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

Każdy przypadek, gdy krawędź nie jest połączona z wierzchołkiem pokrycia, musi być ukarany. Można to przedstawić w funkcji kosztu, dodając karę postaci P(1ninj+ninj)P(1-n_i-n_j+n_i n_j), gdzie PP jest dodatnią stałą kary. Zatem nieograniczona alternatywa dla ograniczonej nierówności dla ważonego MVC to:

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. Uruchom problem

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

Sprawdź status obciążenia swojej Funkcji Qiskit lub pobierz wyniki w następujący sposób:

print(mvc_job.status())

3. Pobierz wynik

Pobierz rozwiązanie i przeanalizuj wyniki. Ponieważ ten problem ma ważone węzły, rozwiązanie to nie jest po prostu minimalna liczba pokrytych węzłów. Zamiast tego koszt rozwiązania reprezentuje sumę wag wierzchołków należących do pokrycia wierzchołkowego. Reprezentuje on łączny „koszt" lub „wagę" pokrycia wszystkich krawędzi w grafie przy użyciu wybranych wierzchołków.

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

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

Uzyskaj wsparcie

W razie pytań lub problemów skontaktuj się z Q-CTRL.

Następne kroki