Skip to main content
IBM Quantum Platform

양자 근사 최적화 알고리즘

사용 예상 시간: Heron r3 프로세서에서 22분(참고: 이는 예상치일 뿐입니다. 런타임은 다를 수 있습니다.)


학습 성과

  • 고전적 조합 최적화 문제(최대 절단 문제)를 양자 해밀토니안으로 매핑하는 방법
  • IBM Quantum 컴퓨트 서비스 세션을 사용하여 양자 근사 최적화 알고리즘(QAOA)을 구현하고 실행하는 방법
  • 소규모 시뮬레이터 예제에서 유틸리티급 하드웨어 실행으로 QAOA 워크플로를 확장하는 방법

전제조건


배경

양자 근사 최적화 알고리즘(QAOA) 은 조합 최적화 문제를 해결하기 위한 양자-고전 하이브리드 반복법이다. 이 튜토리얼에서는 QAOA를 사용하여 최대 절단(max-cut) 문제를 해결할 것입니다. 이 문제는 클러스터링, 네트워크 과학, 통계 물리학 등에 응용되는 NP-완전 최적화 문제입니다. 가장자리로 연결된 노드들로 구성된 그래프가 주어졌을 때, 분할을 가로지르는 가장자리의 개수가 최대가 되도록 노드들을 두 집합으로 나누는 것이 목표이다.

최대 컷 문제의 예시

고전 최적화에서 양자 회로에 이르기까지

맥스컷은 고전적인 이진 최적화 문제로 표현될 수 있다. 각 노드에는 자신이 속한 집합을 나타내는 이진 변수 xi{0,1}x_i \in \{0, 1\} 가 할당됩니다. 목표는 양 끝점이 서로 다른 집합에 속하는 변의 개수를 최대화하는 것입니다:

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

이는 minxxTQx\min_x\, x^T Q x 형태의 2차 무제약 이진 최적화(QUBO) 문제와 동등합니다. 표준 변수 대입( xi(1Zi)/2x_i \to (1 - Z_i)/2 )을 통해, 이 QUBO는 기저 상태가 최적 해를 포함하는 비용 해밀토니안 으로 다시 표현될 수 있습니다. 일반적으로 이 해밀토니안은 2차 항과 1차 항을 모두 포함합니다:

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

여기서 다루는 가중치 없는 최대 절단 문제(unweighted max-cut problem)의 경우, 각 변에 대해 선형 계수들은 0이 되며( bi=0b_i = 0 ), Qij=1Q_{ij} = 1 이므로, 아래 코드에서 구현하게 될 더 간단한 형태인 HC=(i,j)EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j 가 남게 됩니다. 위에서 언급한 더 일반적인 형태는 이 워크플로를 가중 그래프나 기타 QUBO로 표현 가능한 문제로 적용할 때 필요한 것입니다.

QAOA의 작동 원리

QAOA는 초기 중첩 상태 Hn0H^{\otimes n}|0\rangle비용 연산자 eiγkHCe^{-i\gamma_k H_C}혼합 연산자 eiβkHme^{-i\beta_k H_m} 를 번갈아 가며 적용하여 후보 해를 도출합니다. 각도 γk\gamma_kβk\beta_k 는 고전적 피드백 루프를 통해 최적화되며, 양자 컴퓨터는 비용 함수를 평가하고, 고전적 최적화기는 수렴할 때까지 매개변수를 업데이트합니다. 이 반복 루프는 양자 컴퓨팅 세션 내에서 실행되며, 이를 통해 반복 과정 전반에 걸쳐 양자 장치가 예약된 상태를 유지함으로써 지연 시간을 줄입니다.

QAOA 계층이 있는 회로도

QUBO에서 해밀턴 연산자로의 완전한 유도 과정을 포함한 QAOA 이론에 대한 보다 심층적인 내용은 QAOA 강의 모듈 을 참고하십시오.

이 튜토리얼에서는 먼저 5개 노드로 구성된 작은 그래프에서 최대 절단(max-cut) 문제를 해결한 다음, 실제 하드웨어 환경에서 동일한 워크플로를 100개 노드로 구성된 대규모 유틸리티급 문제로 확장해 보겠습니다. 요금제 이용 관련 참고 사항: 이 튜토리얼에서는 ‘Quantum Compute’ 세션을 사용하며, 이 세션은 프리미엄 요금제에서만 이용할 수 있습니다. Open Plan 요금제를 이용 중이라면, 이 튜토리얼을 설명된 대로 실행할 수 없습니다. 대신, Session작업 모드로 변경해야 합니다(즉, 최적화 루프를 로 감싸지 않고 각 반복을 독립적인 작업으로 제출해야 합니다 Session(...)). 워크플로는 여전히 실행되지만, 각 반복마다 예약된 장치를 재사용하는 대신 전체 큐 지연 시간이 발생합니다. 자세한 내용은 이용 가능한 요금제 개요를 참조하십시오.


요구사항

이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • Qiskit SDK v2.0 또는 그 이후 버전, 시각화 기능 지원
  • Qiskit Runtime v0.22 또는 이후 (pip install qiskit-ibm-runtime)

또한, IBM Quantum® Platform 에 있는 인스턴스에 접속할 수 있어야 합니다.


설정

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

간단한 예시

이 섹션에서는 5노드로 구성된 소규모 최대 절단(max-cut) 인스턴스를 예로 들어 QAOA 워크플로의 각 단계를 단계별로 설명합니다. “소규모”라는 표현이 붙었지만, 이 예제는 여전히 실제 IBM Quantum 하드웨어에서 실행됩니다. 코드는 127개 이상의 큐비트를 가진 백엔드를 선택하여 그곳에서 회로를 실행합니다.

n=5n=5 노드로 그래프를 만들어 문제를 초기화합니다.

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

1단계: 고전적 입력을 양자 문제에 매핑하기

고전 그래프를 양자 회로와 연산자로 매핑한다. 배경’에서 설명한 바와 같이, 가중치 없는 최대 절단(unweighted max-cut)의 경우 비용 해밀토니안은 HC=(i,j)EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j 로 환원되며, QAOA는 매개변수화된 안잭 회로를 사용하여 HCH_C 의 후보 기저 상태를 준비합니다.

비용 해밀토니안 구축

그래프의 변을 파울리 ZiZjZ_iZ_j 항으로 변환하여 HCH_C 를 구성한다(도출 과정은 ‘배경’ 참조).

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

QAOA 근사 회로 구축

QAOAAnsatz 사용하여 비용 해밀토니안으로부터 매개변수화된 QAOA 회로를 구성합니다. 여기서는 (두 개의 QAOA 층, 네 가지 매개변수: β0,β1,γ0,γ1\beta_0, \beta_1, \gamma_0, \gamma_1 )를 사용합니다 reps=2 .

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

2단계: 양자 하드웨어 실행을 위한 문제 최적화

추상 회로를 하드웨어 전용 명령어로 변환한다. 이 단계에서는 큐비트 매핑, 게이트 분해, 라우팅 및 오류 억제를 처리합니다. 자세한 내용은 트랜스파일링 서를 참조하십시오.

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

3단계: Qiskit primitives 명령어로 실행합니다

QAOA 최적화 루프는 양자 컴퓨팅 세션 내에서 실행되어, 반복 과정 전반에 걸쳐 장치가 예약된 상태를 유지합니다. 추정기는 각 단계에서 HC\langle H_C \rangle 를 평가하며, 고전적 최적화기(COBYLA)는 수렴할 때까지 매개변수를 업데이트합니다.

단일 작업, 배치 및 세션 런타임 모드의 동작을 보여주는 그림입니다.

초기 매개변수를 정의하고 최적화 루프를 실행합니다:

# 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

옵티마이저는 비용을 절감하고 회로의 더 나은 파라미터를 찾을 수 있었습니다.

점차 완만하게 감소하다가 평탄해지는 곡선은 수렴의 특징이다. 시끄럽고 비단조적인 곡선은 대개 상류 단계에서 해결해야 할 문제가 있음을 나타냅니다. 일반적인 원인으로는 평가당 샘플 수가 너무 적거나(추정량의 분산이 큼), 초기 매개변수가 부적절하거나, 하드웨어 노이즈의 영향이 큰 회로 등이 있습니다. COBYLA는 미분 연산이 필요 없으며 중간 정도의 노이즈에 대해 상당히 견고하지만, 노이즈가 단계당 실제 비용 개선 효과를 압도하게 되면, 이 알고리즘의 선형 근사 모델은 더 이상 실제 하강과 무작위 변동을 구분하지 못하게 되어 최적화 과정이 방황하게 됩니다.

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

최적화된 매개변수를 할당하고 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}

4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환

표본 분포에서 가장 가능성이 높은 비트열을 추출한다. 이는 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

최적 절단 시각화

최적의 비트열을 바탕으로, 이 절단 부분을 원본 그래프에서 시각화할 수 있습니다.

# 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

이제 절단면의 값을 계산해 봅시다:

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

그래프가 이 정도로 작다면 진정한 최적 해를 무차별 대입법으로 쉽게 구할 수 있으므로, QAOA 결과를 정확한 정답과 비교하여 결과를 다시 확인할 수 있습니다.

# 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

대규모 하드웨어 예시

IBM Quantum Platform 에서는 100 큐비트 이상의 용량을 갖춘 다양한 장치를 이용할 수 있습니다. 100개의 노드를 가진 가중 그래프에서 최대 절단 문제를 풀기 위해 하나를 선택하십시오. 이는 “대규모” 문제입니다. 이 워크플로는 위와 동일한 단계를 따르지만, 훨씬 더 큰 그래프에 적용됩니다.

대규모 유틸리티 수준의 종단간 워크플로

100노드 그래프에 적용한 4단계의 전체 과정이 아래에 나와 있습니다. 구성은 소규모 예제와 동일합니다: 맵핑, 트랜스파일, 실행, 후처리 — 다만 문제 규모가 더 크며, 이해를 돕기 위해 아래의 네 개의 셀로 나누어 설명합니다.

# 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

1단계 : 그래프, 비용 해밀토니안 및 가정을 수립한다.

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

2단계 : 선택한 하드웨어 백엔드에 맞게 트랜스파일합니다.

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

3단계 : 세션 내에서 QAOA 최적화 루프를 실행한 다음, 샘플링을 수행합니다.

# 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

4단계 : 샘플링된 분포를 후처리하여 최적의 절단점을 추출합니다.

# 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

최적화 루프에서 최소화하려는 비용이 수렴했는지 확인하고, 결과를 시각화합니다.

# 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

다음 단계

이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:

권장사항
이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.