Skip to main content
IBM Quantum Platform

Algoritmo de otimização aproximada quantum

Estimativa de uso: 22 minutos em um processador Heron r3 (OBSERVAÇÃO: esta é apenas uma estimativa. Seu tempo de execução pode variar)


Resultados do aprendizado

Ao concluir este tutorial, você deverá compreender as seguintes informações:

  • Como mapear um problema clássico de otimização combinatória (corte máximo) para um hamiltoniano quântico
  • Como implementar e executar o Algoritmo de Otimização Aproximada Quântica (QAOA) usando sessões d Qiskit Runtime
  • Como escalar um fluxo de trabalho QAOA, partindo de um exemplo em um pequeno simulador, para a execução em hardware em escala comercial

Pré-requisitos

Recomenda-se que você se familiarize com estes tópicos:


Segundo plano

O Algoritmo de Otimização Aproximada Quântica (QAOA) é um método iterativo híbrido quântico-clássico para a resolução de problemas de otimização combinatória. Neste tutorial, você utilizará o QAOA para resolver o problema do corte máximo (max-cut) — um problema de otimização NP-difícil com aplicações em agrupamento, ciência das redes e física estatística. Dado um grafo composto por nós conectados por arestas, o objetivo é dividir os nós em dois conjuntos de forma a maximizar o número de arestas que atravessam a divisão.

Ilustração de um problema de corte máximo

Da otimização clássica aos circuitos quânticos

O Max-cut pode ser formulado como um problema clássico de otimização binária. A cada nó é atribuída uma variável binária xi{0,1}x_i \in \{0, 1\} que indica a qual conjunto ele pertence. O objetivo é maximizar o número de arestas cujos vértices iniciais e finais pertençam a conjuntos diferentes:

maxx{0,1}n(i,j)xi+xj2xixj.\max_{x \in \{0,1\}^n} \sum_{(i,j)} x_i + x_j - 2x_ix_j.

Isso equivale a um problema de otimização binária quadrática sem restrições (QUBO) da forma minxxTQx\min_x\, x^T Q x. Por meio de uma substituição de variáveis padrão ( xi(1Zi)/2x_i \to (1 - Z_i)/2 ), o QUBO pode ser reescrito como um hamiltoniano de custo cujo estado fundamental codifica a solução ótima. Em geral, esse hamiltoniano contém termos quadráticos e lineares:

HC=ijQijZiZj+ibiZi.H_C = \sum_{ij} Q_{ij} \, Z_i Z_j + \sum_i b_i \, Z_i.

Para o problema do corte máximo não ponderado aqui considerado, os coeficientes lineares desaparecem ( bi=0b_i = 0 ) e Qij=1Q_{ij} = 1 para cada aresta, resultando na forma mais simples HC=(i,j)EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j, que você irá implementar no código abaixo. A forma mais geral apresentada acima é o que você precisaria para adaptar esse fluxo de trabalho a grafos ponderados ou a outros problemas expressáveis em QUBO.

Como funciona o QAOA

O QAOA prepara soluções candidatas aplicando camadas alternadas de dois operadores a um estado de superposição inicial Hn0H^{\otimes n}|0\rangle : o operador de custo eiγkHCe^{-i\gamma_k H_C} e um operador misturador eiβkHme^{-i\beta_k H_m}. Os ângulos γk\gamma_k e βk\beta_k são otimizados em um ciclo de retroalimentação clássico; o computador quântico avalia a função de custo, e um otimizador clássico atualiza os parâmetros até a convergência. Este ciclo iterativo é executado dentro de uma sessão de " Qiskit Runtime ", que mantém o dispositivo quântico reservado ao longo das iterações para reduzir a latência.

Diagrama de circuito com camadas QAOA

Para uma abordagem mais aprofundada da teoria QAOA, incluindo a derivação completa do QUBO para o hamiltoniano, consulte o módulo do curso sobre QAOA.

Neste tutorial, você resolverá primeiro o problema do corte máximo em um pequeno grafo de cinco nós e, em seguida, aplicará o mesmo fluxo de trabalho a um problema em escala real com 100 nós, utilizando hardware real. Observação sobre o plano de acesso: este tutorial utiliza sessões do tipo “ Qiskit Runtime ”, que estão disponíveis apenas no Plano Premium. Se você estiver no plano Open Plan, não poderá executar este tutorial tal como está escrito; em vez disso, precisará alternar Session para o modo de tarefa (ou seja, enviar cada iteração como uma tarefa independente, em vez de envolver o ciclo de otimização com Session(...)). O fluxo de trabalho continua em execução, mas cada iteração sofre toda a latência da fila, em vez de reutilizar um dispositivo reservado. Consulte a Visão geral dos planos disponíveis para obter mais informações.


Requisitos

Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:

  • Qiskit SDK v2.0 ou posterior, com suporte à visualização
  • Qiskit Runtime v0.22 ou mais tarde (pip install qiskit-ibm-runtime)

Além disso, você precisará ter acesso a uma instância em IBM Quantum® Platform.


Instalação

import matplotlib.pyplot as plt
import rustworkx as rx
from rustworkx.visualization import mpl_draw as draw_graph
import numpy as np
from scipy.optimize import minimize
from collections import defaultdict
from typing import Sequence


from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import QAOAAnsatz
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler

Exemplo em pequena escala

Esta seção orienta o usuário em cada etapa do fluxo de trabalho do QAOA em uma pequena instância de max-cut com cinco nós. Apesar de ser considerado de “pequena escala”, este exemplo ainda é executado em hardware real do IBM Quantum — o código seleciona um backend com 127 ou mais qubits e executa o circuito nesse ambiente.

Inicialize seu problema criando um gráfico com n=5n=5 nós.

n_small = 5

graph = rx.PyGraph()
graph.add_nodes_from(np.arange(0, n_small, 1))
edge_list = [
    (0, 1, 1.0),
    (0, 2, 1.0),
    (0, 4, 1.0),
    (1, 2, 1.0),
    (2, 3, 1.0),
    (3, 4, 1.0),
]
graph.add_edges_from(edge_list)
draw_graph(graph, node_size=600, with_labels=True)

Output:

Output of the previous code cell

Passo 1: Mapear entradas clássicas para um problema quântico

Mapeie o gráfico clássico para circuitos e operadores quânticos. Conforme descrito na Introdução, para o problema do corte máximo não ponderado, o hamiltoniano de custo reduz-se a HC=(i,j)EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j, e o QAOA utiliza um circuito de ansatz parametrizado para preparar estados fundamentais candidatos de HCH_C.

Construir o hamiltoniano de custo

Converta as arestas do grafo em termos de Pauli- ZiZjZ_iZ_j o para construir um grafo de HCH_C (consulte a seção “Contexto” para a derivação).

def build_max_cut_paulis(
    graph: rx.PyGraph,
) -> list[tuple[str, list[int], float]]:
    """Convert graph edges to a list of ZZ Pauli terms.

    The returned list is in the sparse format expected by
    ``SparsePauliOp.from_sparse_list``: each element is
    ``(pauli_string, qubit_indices, coefficient)``.
    """
    pauli_list = []
    for edge in list(graph.edge_list()):
        weight = graph.get_edge_data(edge[0], edge[1])
        pauli_list.append(("ZZ", [edge[0], edge[1]], weight))
    return pauli_list


max_cut_paulis = build_max_cut_paulis(graph)
cost_hamiltonian = SparsePauliOp.from_sparse_list(max_cut_paulis, n_small)
print("Cost Function Hamiltonian:", cost_hamiltonian)

Output:

Cost Function Hamiltonian: SparsePauliOp(['IIIZZ', 'IIZIZ', 'ZIIIZ', 'IIZZI', 'IZZII', 'ZZIII'],
              coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Construir o circuito do modelo QAOA

Use QAOAAnsatz para construir o circuito QAOA parametrizado a partir do hamiltoniano de custo. Aqui utilizamos reps=2 (duas camadas QAOA, quatro parâmetros: β0,β1,γ0,γ1\beta_0, \beta_1, \gamma_0, \gamma_1 ).

circuit = QAOAAnsatz(cost_operator=cost_hamiltonian, reps=2)
circuit.measure_all()

circuit.draw("mpl")

Output:

Output of the previous code cell
circuit.parameters

Output:

ParameterView([ParameterVectorElement(β[0]), ParameterVectorElement(β[1]), ParameterVectorElement(γ[0]), ParameterVectorElement(γ[1])])

Etapa 2: Otimizar o problema para execução em hardware quântico

Converter o circuito abstrato em instruções nativas do hardware. Esta etapa lida com o mapeamento de qubits, a decomposição de portas, o roteamento e a supressão de erros. Consulte a documentação sobre transpilagem para obter mais informações.

service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)
print(backend)

# Create pass manager for transpilation. Level 3 is the most aggressive
# preset: slower to transpile, but produces shorter circuits that are
# more robust to hardware noise.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)

candidate_circuit = pm.run(circuit)
candidate_circuit.draw("mpl", fold=False, idle_wires=False)

Output:

<IBMBackend('ibm_pittsburgh')>
Output of the previous code cell

Passo 3: Execute usando Qiskit primitives

O ciclo de otimização do QAOA é executado dentro de uma sessão do Qiskit Runtime para manter o dispositivo reservado ao longo das iterações. Um estimador avalia um HC\langle H_C \rangle e em cada etapa, e um otimizador clássico (COBYLA) atualiza os parâmetros até a convergência.

Ilustração mostrando o comportamento dos modos de tempo de execução Single job, Batch e Session.

Defina os parâmetros iniciais e execute o ciclo de otimização:

# QAOA doesn't prescribe principled default angles — any bounded choice
# works as a warm start for problems this small. beta and gamma are
# periodic (beta in [0, pi] and gamma in [0, 2*pi] modulo the underlying
# Pauli-rotation periods), and pi/2 and pi are just midpoints of those
# ranges. For harder problems you would typically warm start from known
# good angles or transfer parameters from smaller instances.
initial_gamma = np.pi
initial_beta = np.pi / 2
init_params = [initial_beta, initial_beta, initial_gamma, initial_gamma]
def cost_func_estimator(params, ansatz, hamiltonian, estimator):
    # transform the observable defined on virtual qubits to
    # an observable defined on all physical qubits
    isa_hamiltonian = hamiltonian.apply_layout(ansatz.layout)

    pub = (ansatz, isa_hamiltonian, params)
    job = estimator.run([pub])

    results = job.result()[0]
    cost = results.data.evs

    objective_func_vals.append(cost)

    return cost
objective_func_vals = []  # Global variable
with Session(backend=backend) as session:
    # If using qiskit-ibm-runtime<0.24.0, change `mode=` to `session=`
    estimator = Estimator(mode=session)
    estimator.options.default_shots = 1000

    # Set simple error suppression/mitigation options
    estimator.options.dynamical_decoupling.enable = True
    estimator.options.dynamical_decoupling.sequence_type = "XY4"
    estimator.options.twirling.enable_gates = True
    estimator.options.twirling.num_randomizations = "auto"
    estimator.options.environment.job_tags = ["TUT_QAOA"]

    result = minimize(
        cost_func_estimator,
        init_params,
        args=(candidate_circuit, cost_hamiltonian, estimator),
        method="COBYLA",
        tol=1e-2,
    )
    print(result)

Output:

 message: Return from COBYLA because the trust region radius reaches its lower bound.
 success: True
  status: 0
     fun: -2.0402211719947774
       x: [ 3.041e+00  1.212e+00  2.081e+00  4.471e+00]
    nfev: 36
   maxcv: 0.0

O otimizador foi capaz de reduzir o custo e encontrar parâmetros melhores para o circuito.

Uma curva que diminui suavemente e se estabiliza é o sinal característico da convergência. Uma curva irregular e não monotônica geralmente indica que algo na fase anterior requer atenção; causas comuns são um número insuficiente de iterações por avaliação (alta variância do estimador), parâmetros iniciais inadequados ou um circuito cuja profundidade é dominada pelo ruído do hardware. O COBYLA não utiliza derivadas e é bastante robusto contra ruídos moderados, mas quando o ruído supera as melhorias reais de custo por passo, seu modelo de aproximação linear não consegue mais distinguir o deslocamento real da oscilação aleatória, e o otimizador se desvia do caminho.

plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()

Output:

Output of the previous code cell

Atribua os parâmetros otimizados e gere amostras da distribuição final usando a primitiva Sampler.

optimized_circuit = candidate_circuit.assign_parameters(result.x)
optimized_circuit.draw("mpl", fold=False, idle_wires=False)

Output:

Output of the previous code cell
# If using qiskit-ibm-runtime<0.24.0, change `mode=` to `backend=`
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10000

# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.twirling.enable_gates = True
sampler.options.twirling.num_randomizations = "auto"

sampler.options.environment.job_tags = ["TUT_QAOA"]

pub = (optimized_circuit,)
job = sampler.run([pub], shots=int(1e4))
counts_int = job.result()[0].data.meas.get_int_counts()
counts_bin = job.result()[0].data.meas.get_counts()
shots = sum(counts_int.values())
final_distribution_int = {key: val / shots for key, val in counts_int.items()}
final_distribution_bin = {key: val / shots for key, val in counts_bin.items()}
print(final_distribution_int)

Output:

{18: 0.039, 5: 0.0665, 20: 0.0973, 29: 0.0063, 9: 0.0899, 13: 0.0379, 2: 0.0047, 1: 0.0153, 11: 0.0932, 14: 0.0327, 12: 0.0314, 25: 0.0193, 21: 0.0398, 6: 0.0224, 4: 0.0197, 10: 0.0387, 3: 0.0181, 26: 0.07, 17: 0.0327, 19: 0.0332, 22: 0.0914, 24: 0.007, 0: 0.0033, 8: 0.0066, 30: 0.0158, 28: 0.0169, 27: 0.0222, 16: 0.0073, 7: 0.0057, 23: 0.0062, 15: 0.0054, 31: 0.0041}

Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado

Extraia a sequência de bits mais provável da distribuição amostrada. Este é o melhor corte identificado pela QAOA.

# auxiliary functions to sample most likely bitstring
def to_bitstring(integer, num_bits):
    result = np.binary_repr(integer, width=num_bits)
    return [int(digit) for digit in result]


keys = list(final_distribution_int.keys())
values = list(final_distribution_int.values())
most_likely = keys[np.argmax(np.abs(values))]
most_likely_bitstring = to_bitstring(most_likely, len(graph))
most_likely_bitstring.reverse()

print("Result bitstring:", most_likely_bitstring)

Output:

Result bitstring: [0, 0, 1, 0, 1]
plt.rcParams.update({"font.size": 10})
final_bits = final_distribution_bin
values = np.abs(list(final_bits.values()))
top_4_values = sorted(values, reverse=True)[:4]
positions = []
for value in top_4_values:
    positions.append(np.where(values == value)[0])
fig = plt.figure(figsize=(11, 6))
ax = fig.add_subplot(1, 1, 1)
plt.xticks(rotation=45)
plt.title("Result Distribution")
plt.xlabel("Bitstrings (reversed)")
plt.ylabel("Probability")
ax.bar(list(final_bits.keys()), list(final_bits.values()), color="tab:grey")
for p in positions:
    ax.get_children()[int(p[0])].set_color("tab:purple")
plt.show()

Output:

Output of the previous code cell

Visualize o melhor corte

A partir da sequência de bits ideal, é possível visualizar esse corte no gráfico original.

# auxiliary function to plot graphs
def plot_result(G, x):
    colors = ["tab:grey" if i == 0 else "tab:purple" for i in x]
    pos, _default_axes = rx.spring_layout(G), plt.axes(frameon=True)
    rx.visualization.mpl_draw(
        G, node_color=colors, node_size=100, alpha=0.8, pos=pos
    )


plot_result(graph, most_likely_bitstring)

Output:

Output of the previous code cell

Agora, calcule o valor do corte:

def evaluate_sample(x: Sequence[int], graph: rx.PyGraph) -> float:
    assert len(x) == len(
        list(graph.nodes())
    ), "The length of x must coincide with the number of nodes in the graph."
    return sum(
        x[u] * (1 - x[v]) + x[v] * (1 - x[u])
        for u, v in list(graph.edge_list())
    )


cut_value = evaluate_sample(most_likely_bitstring, graph)
print("The value of the cut is:", cut_value)

Output:

The value of the cut is: 5

Para um gráfico tão pequeno, é fácil encontrar o verdadeiro ótimo por força bruta; portanto, você pode verificar os resultados comparando o resultado do QAOA com a resposta exata.

# Classical baseline: enumerate all 2**n_small bitstrings and take the best cut.
def brute_force_max_cut(graph: rx.PyGraph) -> tuple[int, list[int]]:
    n = len(list(graph.nodes()))
    best_cut = -1
    best_x: list[int] = []
    for i in range(2**n):
        x = [(i >> k) & 1 for k in range(n)]
        cut = evaluate_sample(x, graph)
        if cut > best_cut:
            best_cut = int(cut)
            best_x = x
    return best_cut, best_x


classical_best, classical_x = brute_force_max_cut(graph)
print(f"Classical optimum (brute force): {classical_best}")
print(f"QAOA cut value:                  {cut_value}")

Output:

Classical optimum (brute force): 5
QAOA cut value:                  5

Exemplo de hardware em grande escala

Você tem acesso a diversos dispositivos com mais de 100 qubits em IBM Quantum Platform. Escolha um para resolver o problema do corte máximo em um grafo ponderado de 100 nós. Este é um problema de "escala industrial". O fluxo de trabalho segue as mesmas etapas descritas acima, aplicadas a um gráfico muito maior.

Fluxo de trabalho completo em escala de serviços públicos

As quatro etapas são apresentadas a seguir, aplicadas ao gráfico de 100 nós. A estrutura é a mesma do passo a passo em pequena escala: mapear, transpilar, executar, pós-processar — mas com um problema maior e dividido entre as quatro células abaixo para maior clareza.

# Precomputed parity lookup table: _PARITY[b] = +1 if popcount(b) is even, else -1.
# We use this to vectorize expectation-value evaluation across all Pauli terms.
_PARITY = np.array(
    [-1 if bin(i).count("1") % 2 else 1 for i in range(256)],
    dtype=np.complex128,
)


def evaluate_sparse_pauli(state: int, observable: SparsePauliOp) -> complex:
    """Expectation value of a SparsePauliOp on a single computational-basis state.

    For a Z-only observable (which QAOA cost Hamiltonians are, after the
    QUBO-to-Hamiltonian mapping), the eigenvalue of each Pauli term on a
    computational-basis state is simply (-1)**popcount(z_mask AND state),
    i.e., the parity of the bitwise-AND of the term's Z-support and the
    measured bitstring.

    This routine packs the Z-support of every Pauli term into bytes, ANDs
    them against the measured state in a single vectorized op, and looks up
    the parity in _PARITY. For a 100-qubit / ~hundreds-of-terms Hamiltonian
    over 10_000 samples, this is dramatically faster than calling
    SparsePauliOp.expectation_value per sample.
    """
    packed_uint8 = np.packbits(observable.paulis.z, axis=1, bitorder="little")
    state_bytes = np.frombuffer(
        state.to_bytes(packed_uint8.shape[1], "little"), dtype=np.uint8
    )
    reduced = np.bitwise_xor.reduce(packed_uint8 & state_bytes, axis=1)
    return np.sum(observable.coeffs * _PARITY[reduced])


def best_solution(samples, hamiltonian):
    """Return the sampled bitstring (as int) with the lowest Hamiltonian cost."""
    min_cost = float("inf")
    min_sol = None
    for bit_str in samples.keys():
        candidate_sol = int(bit_str)
        fval = evaluate_sparse_pauli(candidate_sol, hamiltonian).real
        if fval <= min_cost:
            min_cost = fval
            min_sol = candidate_sol
    return min_sol


def _plot_cdf(objective_values: dict, ax, color):
    x_vals = sorted(objective_values.keys(), reverse=True)
    y_vals = np.cumsum([objective_values[x] for x in x_vals])
    ax.plot(x_vals, y_vals, color=color)


def plot_cdf(dist, ax, title):
    _plot_cdf(dist, ax, "C1")
    ax.vlines(min(list(dist.keys())), 0, 1, "C1", linestyle="--")
    ax.set_title(title)
    ax.set_xlabel("Objective function value")
    ax.set_ylabel("Cumulative distribution function")
    ax.grid(alpha=0.3)


def samples_to_objective_values(samples, hamiltonian):
    """Convert the samples to values of the objective function."""
    objective_values = defaultdict(float)
    for bit_str, prob in samples.items():
        candidate_sol = int(bit_str)
        fval = evaluate_sparse_pauli(candidate_sol, hamiltonian).real
        objective_values[fval] += prob
    return objective_values

Passo 1 : Construa o grafo, o hamiltoniano de custo e a hipótese inicial.

# Step 1: build the 100-node graph, cost Hamiltonian, and QAOA ansatz.
n_large = 100
graph_100 = rx.PyGraph()
graph_100.add_nodes_from(np.arange(0, n_large, 1))
elist = []
for edge in backend.coupling_map:
    if edge[0] < n_large and edge[1] < n_large:
        elist.append((edge[0], edge[1], 1.0))
graph_100.add_edges_from(elist)

max_cut_paulis_100 = build_max_cut_paulis(graph_100)
cost_hamiltonian_100 = SparsePauliOp.from_sparse_list(
    max_cut_paulis_100, n_large
)

circuit_100 = QAOAAnsatz(cost_operator=cost_hamiltonian_100, reps=1)
circuit_100.measure_all()

Passo 2 : Transpilar para o backend de hardware selecionado.

# Step 2: transpile for hardware.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit_100 = pm.run(circuit_100)

Etapa 3 : Execute o ciclo de otimização do QAOA dentro de uma sessão e, em seguida, faça a amostragem.

# Step 3: run the QAOA optimization loop on the device, then sample the
# final distribution with the optimized parameters.
initial_gamma = np.pi
initial_beta = np.pi / 2
init_params = [initial_beta, initial_gamma]

objective_func_vals = []  # Global variable
with Session(backend=backend) as session:
    estimator = Estimator(mode=session)
    estimator.options.default_shots = 1000

    # Set simple error suppression/mitigation options
    estimator.options.dynamical_decoupling.enable = True
    estimator.options.dynamical_decoupling.sequence_type = "XY4"
    estimator.options.twirling.enable_gates = True
    estimator.options.twirling.num_randomizations = "auto"
    estimator.options.environment.job_tags = ["TUT_QAOA"]

    result = minimize(
        cost_func_estimator,
        init_params,
        args=(candidate_circuit_100, cost_hamiltonian_100, estimator),
        method="COBYLA",
    )
    print(result)

# Assign optimal parameters and sample the final distribution.
optimized_circuit_100 = candidate_circuit_100.assign_parameters(result.x)

sampler = Sampler(mode=backend)
sampler.options.default_shots = 10000

# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.twirling.enable_gates = True
sampler.options.twirling.num_randomizations = "auto"

# Add a unique tag to the job execution
sampler.options.environment.job_tags = ["TUT_QAOA"]

pub = (optimized_circuit_100,)
job = sampler.run([pub], shots=int(1e4))

counts_int = job.result()[0].data.meas.get_int_counts()
shots = sum(counts_int.values())
final_distribution_100_int = {
    key: val / shots for key, val in counts_int.items()
}

Output:

 message: Return from COBYLA because the trust region radius reaches its lower bound.
 success: True
  status: 0
     fun: -17.172689238986344
       x: [ 2.574e+00  4.166e+00]
    nfev: 28
   maxcv: 0.0

Passo 4 : Realizar o pós-processamento da distribuição amostrada para extrair o melhor corte.

# Step 4: find the best-cost sample and evaluate its cut value.
best_sol_100 = best_solution(final_distribution_100_int, cost_hamiltonian_100)
best_sol_bitstring_100 = to_bitstring(int(best_sol_100), len(graph_100))
best_sol_bitstring_100.reverse()

print("Result bitstring:", best_sol_bitstring_100)

cut_value_100 = evaluate_sample(best_sol_bitstring_100, graph_100)
print("The value of the cut is:", cut_value_100)

Output:

Result bitstring: [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0]
The value of the cut is: 156

Verifique se o custo minimizado no ciclo de otimização convergiu e visualize os resultados.

# Plot convergence
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()

# Visualize the cut
plot_result(graph_100, best_sol_bitstring_100)

# Plot cumulative distribution function
result_dist = samples_to_objective_values(
    final_distribution_100_int, cost_hamiltonian_100
)
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
plot_cdf(result_dist, ax, backend.name)

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

Próximas etapas

Se você achou este trabalho interessante, talvez se interesse pelo seguinte material:

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