Skip to main content
IBM Quantum Platform

Solucionador de otimização: uma função Qiskit da Q-CTRL Fire Opal

Consulte a referência da API

Note

As funções do Qiskit são um recurso experimental disponível apenas para usuários dos planos IBM Quantum® Premium Plan, Flex Plan e On-Prem (via IBM Quantum Platform API). Eles estão no status de versão prévia e estão sujeitos a alterações.

  • O código desta página foi desenvolvido com base nos seguintes requisitos. Recomendamos o uso dessas versões ou versões 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 serviços públicos em hardware quântico sem precisar de conhecimento quântico. Basta inserir a definição do problema de alto nível, e o Solver se encarrega do resto. Todo o fluxo de trabalho é sensível a ruídos e aproveita o Fire Opal's Performance Management. O Solver fornece de forma consistente soluções precisas para problemas classicamente desafiadores, mesmo em escala de dispositivo completo nas maiores QPUs IBM®.

O Solver é flexível e pode ser utilizado 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 os problemas sem restrições quanto aqueles com restrições são solucionáveis, sendo as restrições aplicadas como restrições rígidas Hamming-weight-1, 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 com restrições, utilizando diferentes tipos de entrada do Solver. O primeiro exemplo envolve um problema de corte máximo definido em um grafo regular de grau 3 com 156 vértices, enquanto o segundo exemplo aborda um problema de partição de grafo com 50 vértices definido por uma função de custo.

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


Descrição da função

O Solver otimiza e automatiza totalmente o algoritmo inteiro, desde a supressão de erros no nível do hardware até o mapeamento eficiente do problema e a otimização clássica de loop fechado. Nos bastidores, o pipeline do Solver reduz os erros em todos os estágios, possibilitando o desempenho aprimorado necessário para um dimensionamento significativo. O fluxo de trabalho subjacente é inspirado no Algoritmo de Otimização Aproximada Quântica (QAOA), que é um algoritmo híbrido quântico-clássico. Para obter 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 objetiva, um gráfico ou uma cadeia de spin SparsePauliOp .
  2. Conecte-se à função por meio do Qiskit Functions Catalog.
  3. Execute o problema com o Solver e recupere os resultados.

Formatos de problemas aceitos

  • Representação de expressão polinomial de uma função objetiva. Idealmente criado em Python com um objeto SymPy Poly existente e formatado em uma string usando sympy.srepr.
  • Representação gráfica de um tipo específico de problema. O gráfico deve ser criado usando a biblioteca networkx, disponível em Python. Em seguida, deve ser convertido em uma string usando a função do networkx nx.readwrite.json_graph.adjacency_data.
  • Representação da cadeia de spin de um problema específico. A cadeia de spin deve ser representada como um objeto SparsePauliOp ; consulte a documentação para obter mais detalhes.
Esta função é compatível com todos os back-ends do ` IBM `?

Se você quiser usar um backend que esta função ainda não suporta, entre em contato com a Q-CTRL para solicitar a adição desse suporte.


Referências

Renúncia de responsabilidade

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 quânticas podem atingir uma qualidade final de solução semelhante após um pós-processamento equivalente. A avaliação deve, portanto, levar em conta todo o fluxo de trabalho de otimização.

Os resultados de benchmarking publicados mostram que o Solver resolve com sucesso problemas com mais de 120 qubits, superando até mesmo os resultados publicados anteriormente sobre recozimento quântico e dispositivos de íons presos. As métricas de referência a seguir fornecem uma indicação aproximada da precisão e do dimensionamento dos tipos de problemas com base em alguns exemplos. As métricas reais podem diferir com base em vários recursos do problema, como o número de termos na função objetiva (densidade) e sua localidade, número de variáveis e 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 tamanhos maiores foram resolvidos com sucesso, e os testes além desses limites são incentivados.

A conectividade arbitrária de qubit é suportada em todos os tipos de problemas.

Tipo de problema
Número de qubits
Exemplo
Precisão
Tempo Total (s)
Uso do tempo de execução (s)
Número de iterações
Problemas quadráticos com conexões esparsas1563-corte máximo regular100%176429316
Otimização binária de ordem superior156Modelo de vidro de spin de Ising100%146127216
Problemas quadráticos densamente conectados50Corte máximo com conexão total100%175826812
Problema com restrições rígidas50Particionamento de grafos ponderados com densidade de arestas de 8%100%107421522

Introdução

Primeiro, autentique-se usando sua chave de API IBM Quantum. Em seguida, selecione a função do Qiskit da seguinte maneira. (Este trecho pressupõe que você já tenha salvo sua conta no seu ambiente local.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Verify that you have access to the function
catalog.list()

Output:

[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

Resolva o problema do corte máximo (max-cut). O exemplo a seguir demonstra as capacidades do Solver em um problema de corte máximo em um grafo não ponderado de 156 nós e 3 arcos regulares, mas também é possível resolver problemas em 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 usando o kernel IPython.

# %pip install networkx numpy

1. Defina o problema

Você pode resolver um problema de corte máximo definindo um problema de grafos 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:

Output of the previous code cell

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

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

2. Execute o problema

Ao usar o método de entrada baseado em gráficos, especifique o tipo de problema.

# 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 sua carga de trabalho do Qiskit Function ou obtenha os resultados da seguinte maneira:

# Print the ID so you can use it later, if necessary
print(maxcut_job.job_id)

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

Output:

34b53970-d95a-4e24-8763-fc6f3d112843
QUEUED

3. Recuperar o resultado

Recupere o valor de corte ideal do dicionário de resultados.

Note

O mapeamento das variáveis para a cadeia de bits pode ter mudado. O dicionário de saída contém um variables_to_bitstring_index_map subdicionário, que ajuda a verificar a ordem.

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

Output:

Optimal cut value: 210.0

Você pode verificar a precisão do resultado resolvendo o problema de forma clássica com solucionadores de código aberto, como PuLP se o gráfico não for densamente conectado. Problemas de alta densidade podem exigir solucionadores clássicos avançados para validar a solução.


Exemplo: Otimização restrita

O exemplo anterior de max-cut é um problema comum de otimização binária quadrática sem restrições. O Solucionador de Otimização do Q-CTRL também pode resolver problemas de otimização com restrições, passando as restrições rígidas diretamente para o Solucionador por meio da entrada constraint , em vez de codificá-las como termos de penalidade na função-objetivo. Atualmente, o Solver suporta restrições do tipo “ Hamming-weight-1 ”: cada restrição especifica um grupo de variáveis em que exatamente uma variável deve ser igual a 1 e as demais devem ser iguais 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, a partição de grafos, atribuindo cada nó de um grafo a exatamente um dos vários grupos, ao mesmo tempo em que se minimiza o peso total das arestas cujos vértices terminais pertencem ao 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 usando o kernel IPython.

# %pip install numpy networkx sympy

1. Defina o problema

Defina um problema de particionamento de grafo 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:

Output of the previous code cell

Um modelo padrão de otimização para a partição de grafos ponderados pode ser formulado da seguinte maneira. Divida os nós do grafo em três grupos g∈{0,1,2}g \in \{0, 1, 2\} e defina que 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 estão atribuídas ao mesmo grupo, sendo que 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)]
        )

Cada nó deve ser atribuído a exatamente um dos três grupos. Esta é uma restrição do tipo “ Hamming-weight-1 ”: para cada nó ii, exatamente um dos elementos ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} deve ser igual a 1, e os demais devem ser iguais 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 essa restrição como um termo de penalidade na função de custo, passe-a diretamente ao 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}")

Output:

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 ao constraint. Qualquer variável que não seja incluída no dicionário permanece sem restrição; portanto, é possível combinar grupos de variáveis com restrições rígidas e variáveis livres no mesmo problema.

2. Execute 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 sua carga de trabalho do Qiskit Function ou obtenha os resultados da seguinte maneira:

# Print the ID so you can use it later, if necessary
print(partition_job.job_id)

# Get job status
print(partition_job.status())

Output:

b8085944-f313-444e-be39-ea61b1b47ebd
QUEUED

3. Obtenha o resultado

Recupere a solução e analise os resultados. O custo da solução representa o peso total das arestas cujos vértices finais acabaram no mesmo grupo; portanto, 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}")

Output:

Total weight of same-group edges: -36.5539
Solution bitstring: 100100100100100001100100100100100100100100100100100001010100010100100100100010001001100100100001100001100001010001001010100100100100100010100100100100

Obtenha suporte

Em caso de dúvidas ou problemas, entre em contato com a Q-CTRL.


Log de mudanças

  • 10/08/2026: Adicionou-se suporte para restrições rígidas (peso de Hamming 1) por meio do campo de entrada constraint e atualizou-se o exemplo de otimização com restrições para utilizá-las.
  • 11/02/2026: Agora oferecemos suporte para ibm_miami

Próximas etapas

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.