Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Optimization Solver: Uma Qiskit Function do Q-CTRL Fire Opal

Consulte a referência da API

nota

As Qiskit Functions são um recurso experimental disponível apenas para usuários dos planos IBM Quantum® Premium, Flex e On-Prem (via IBM Quantum Platform API). Elas estão em status de lançamento de pré-visualização e sujeitas a alterações.

Package versions

O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou mais recentes.

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

Visão geral​

Com o Fire Opal Optimization Solver, você pode resolver problemas de otimização em escala de utilidade em hardware quântico sem precisar de conhecimento especializado em computação quântica. Basta inserir a definição do problema em alto nível e o Solver cuida do resto. Todo o fluxo de trabalho é ciente de ruído e utiliza o Gerenciamento de Desempenho do Fire Opal por baixo dos panos. O Solver entrega consistentemente soluções precisas para problemas classicamente desafiadores, mesmo em escala de dispositivo completo nos maiores QPUs da IBM®.

O Solver é flexível e pode ser usado para resolver problemas de otimização combinatória definidos como funções objetivo ou grafos arbitrários. Os problemas não precisam ser mapeados para a topologia do dispositivo. Tanto problemas sem restrições quanto com restrições são resolúveis, com as restrições aplicadas como restrições de Hamming-weight-1 rigidamente, em vez de termos de penalidade. Os exemplos incluídos neste guia demonstram como resolver um problema de otimização em escala de utilidade sem restrições e um com restrições, usando diferentes tipos de entrada do Solver. O primeiro exemplo envolve um problema max-cut definido em um grafo 3-regular com 156 nós, enquanto o segundo aborda um problema de particionamento de grafo com 50 nós definido por uma função de custo.

Para obter acesso ao Optimization Solver, entre em contato com o Q-CTRL.

Descrição da função​

O Solver otimiza e automatiza completamente todo o algoritmo, desde a supressão de erros no nível de hardware até o mapeamento eficiente de problemas e a otimização clássica em malha fechada. Por baixo dos panos, o pipeline do Solver reduz erros em cada etapa, possibilitando o desempenho aprimorado necessário para escalar de forma significativa. O fluxo de trabalho subjacente é inspirado no Quantum Approximate Optimization Algorithm (QAOA), que é um algoritmo híbrido quântico-clássico. Para um resumo detalhado do fluxo de trabalho completo do Optimization Solver, consulte o manuscrito publicado.

Visualização do fluxo de trabalho do Optimization Solver

Para resolver um problema genérico com o Optimization Solver:

  1. Defina seu problema como uma função objetivo, um grafo ou uma cadeia de spin SparsePauliOp.
  2. Conecte-se à função por meio do Catálogo de Qiskit Functions.
  3. Execute o problema com o Solver e recupere os resultados.

Formatos de problema aceitos​

  • Representação por expressão polinomial de uma função objetivo. Idealmente criada em Python com um objeto SymPy Poly existente e formatada como string usando sympy.srepr.

  • Representação em grafo de um tipo específico de problema. O grafo deve ser criado usando a biblioteca networkx em Python. Em seguida, deve ser convertido para uma string usando a função networkx nx.readwrite.json_graph.adjacency_data.

  • Representação em cadeia de spin de um problema específico. A cadeia de spin deve ser representada como um objeto SparsePauliOp; consulte a documentação para mais detalhes.

Esta função suporta todos os backends da IBM?

Se você quiser usar um backend que esta função não suporta atualmente, entre em contato com o Q-CTRL para adicionar suporte.

Benchmarks​

Aviso

O desempenho pode depender tanto da instância do problema quanto das etapas de processamento subsequentes. Em alguns casos, amostras clássicas e amostras geradas quanticamente podem atingir uma qualidade final de solução semelhante após um pós-processamento equivalente. A avaliação deve, portanto, considerar todo o fluxo de trabalho de otimização.

Resultados de benchmarking publicados mostram que o Solver resolve com sucesso problemas com mais de 120 qubits, superando até mesmo resultados previamente publicados em dispositivos de recozimento quântico e de íons aprisionados. As métricas de benchmark a seguir fornecem uma indicação aproximada da precisão e da escala dos tipos de problema com base em alguns exemplos. As métricas reais podem variar com base em diversas características do problema, como o número de termos na função objetivo (densidade) e sua localidade, o número de variáveis e a ordem polinomial.

O "Número de qubits" indicado não é uma limitação rígida, mas representa limites aproximados em que você pode esperar uma precisão de solução extremamente consistente. Problemas de maior escala já foram resolvidos com sucesso, e testes além desses limites são encorajados.

Conectividade arbitrária de qubits é suportada em todos os tipos de problema.

Tipo de problemaNúmero de qubitsExemploPrecisãoTempo total (s)Uso de runtime (s)Número de iterações
Problemas quadráticos com conexões esparsas156max-cut 3-regular100%176429316
Otimização binária de ordem superior156Modelo Ising spin-glass100%146127216
Problemas quadráticos com conexões densas50max-cut totalmente conectado100%175826812
Problema com restrições de Hamming-weight-1 rigidamente50Particionamento de grafo ponderado com 8% de densidade de arestas100%107421510

Primeiros passos​

Primeiro, autentique-se usando sua chave de API do IBM Quantum. Em seguida, selecione a Qiskit Function da seguinte forma. (Este trecho assume que você já salvou sua conta no seu ambiente local.)

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"networkx": "networkx", "numpy": "numpy", "qiskit_ibm_catalog": "qiskit-ibm-catalog", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "sympy": "sympy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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")

Exemplo: Otimização sem restrições​

Execute o problema de corte máximo (Max-Cut). O exemplo a seguir demonstra as capacidades do Solver em um problema Max-Cut em um grafo 3-regular não ponderado com 156 nós, mas você também pode resolver problemas com grafos ponderados. Além do qiskit-ibm-catalog, você também usará os seguintes pacotes para executar este exemplo: networkx e numpy. Você pode instalar esses pacotes descomentando a célula a seguir se estiver executando este exemplo em um notebook com o kernel IPython.

# %pip install networkx numpy

1. Definir o problema​

Você pode executar um problema Max-Cut definindo um problema em grafo e especificando 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

O Solver aceita uma string como entrada da definição do problema.

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

2. Executar o problema​

Ao usar o método de entrada baseado em grafo, especifique o tipo de problema.

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

Verifique o status da carga de trabalho da sua Qiskit Function ou recupere os resultados da seguinte forma:

# 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. Recuperar o resultado​

Recupere o valor de corte ótimo do dicionário de resultados.

nota

O mapeamento das variáveis para a bitstring pode ter mudado. O dicionário de saída contém um subdicionário variables_to_bitstring_index_map, que ajuda a verificar a ordenação.

# 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

Você pode verificar a precisão do resultado resolvendo o problema classicamente com solvers de código aberto como o PuLP, caso o grafo não seja densamente conectado. Problemas de alta densidade podem exigir solvers clássicos avançados para validar a solução.

Exemplo: Otimização com restrições​

O exemplo anterior de max-cut é um problema comum de otimização binária irrestrita quadrática. O Optimization Solver da Q-CTRL também pode resolver problemas de otimização com restrições ao passar restrições rígidas diretamente para o Solver por meio da entrada constraint, em vez de codificá-las como termos de penalidade na função objetivo. Atualmente, o Solver suporta restrições de peso de Hamming igual a 1: cada restrição especifica um grupo de variáveis em que exatamente uma variável deve ser igual a 1 e o restante deve ser igual a 0.

O exemplo a seguir demonstra como construir uma função de custo e um conjunto de restrições rígidas para um problema de otimização com restrições, o particionamento de grafos, atribuindo cada nó de um grafo a exatamente um de vários grupos, minimizando o peso total das arestas cujas extremidades caem no mesmo grupo. Além dos pacotes qiskit-ibm-catalog e qiskit, você também usará os seguintes pacotes para executar este exemplo: numpy, networkx e sympy. Você pode instalar esses pacotes descomentando a célula a seguir se estiver executando este exemplo em um notebook com o kernel IPython.

# %pip install numpy networkx sympy

1. Definir o problema​

Defina um problema de particionamento de grafos aleatório gerando um grafo com nós ponderados aleatoriamente.

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

Um modelo de otimização padrão para particionamento de grafos ponderados pode ser formulado da seguinte forma. Divida os nós do grafo em três grupos g∈{0,1,2}g \in \{0, 1, 2\}, e seja ni,g=1n_{i,g} = 1 se o nó ii for atribuído ao grupo gg, e ni,g=0n_{i,g} = 0 caso contrário. O objetivo é minimizar o peso total das arestas cujas extremidades são atribuídas ao mesmo grupo, onde o peso de uma aresta (i,j)(i,j) é o peso combinado de suas duas extremidades, ω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)]
)

Todo nó deve ser atribuído a exatamente um dos três grupos. Esta é uma restrição de peso de Hamming igual a 1: para todo nó ii, exatamente um de ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} deve ser igual a 1, e o restante deve ser igual a 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

Em vez de codificar esse requisito como um termo de penalidade na função de custo, passe-o diretamente para o Solver como uma restrição rígida usando a entrada 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}
Problemas parcialmente restritos

Você não precisa adicionar todas as variáveis a constraint. Qualquer variável deixada de fora do dicionário permanece irrestrita, então você pode misturar grupos de variáveis com restrições rígidas com variáveis livres no mesmo problema.

2. Executar o problema​

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

Verifique o status da carga de trabalho da sua Qiskit Function ou recupere os resultados da seguinte forma:

# 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. Obter o resultado​

Recupere a solução e analise os resultados. O custo da solução representa o peso total das arestas cujas extremidades acabaram no mesmo grupo, então um custo menor indica um melhor particionamento do grafo.

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

Obter suporte​

Para qualquer dúvida ou problema, entre em contato com o Q-CTRL.

Registro de alterações​

  • 2026-08-10: Adicionado suporte para restrições rígidas (peso de Hamming 1) por meio da entrada constraint, e atualizado o exemplo de otimização com restrições para usá-las.

  • 2026-02-11: Agora temos suporte para ibm_miami

Próximos passos​