Skip to main content
IBM Quantum Platform

최적화 솔버: Q-CTRL Fire Opal의 Qiskit 함수

API 참조 보기

Note

키스킷 기능은 IBM 퀀텀 프리미엄 요금제, 플렉스 요금제 및 온프레미스( IBM 퀀텀 플랫폼 API를 통해) 요금제 사용자에게만 제공되는 실험적 기능입니다. 프리뷰 릴리스 상태이며 변경될 수 있습니다.

  • 이 페이지의 코드는 다음 요구 사항을 바탕으로 개발되었습니다. 이 버전 이상을 사용하시기를 권장합니다.

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

개요

파이어 오팔 최적화 솔버를 사용하면 양자 전문 지식이 없어도 양자 하드웨어에서 유틸리티 규모의 최적화 문제를 해결할 수 있습니다. 높은 수준의 문제 정의만 입력하면 나머지는 솔버가 알아서 처리합니다. 전체 워크플로는 노이즈를 인식하며 내부적으로 Fire Opal의 성능 관리 기능을 활용합니다. 솔버는 최대 규모의 최대 장치 규모( IBM® )에서도 고전적으로 까다로운 문제에 대한 정확한 솔루션을 일관되게 제공합니다.

이 솔버는 유연성이 뛰어나며, 목적 함수나 임의의 그래프로 정의된 조합 최적화 문제를 해결하는 데 사용할 수 있습니다. 문제를 반드시 장치 토폴로지에 매핑할 필요는 없습니다. 제약이 없는 문제와 제약이 있는 문제 모두 풀 수 있으며, 제약 조건은 페널티 항이 아닌 하드 Hamming-weight-1 제약 조건으로 적용됩니다. 이 가이드에 포함된 예제들은 다양한 솔버 입력 유형을 사용하여 제약 조건이 없는 최적화 문제와 제약 조건이 있는 대규모 최적화 문제를 해결하는 방법을 보여줍니다. 첫 번째 예제는 156개의 정점을 가진 3-정규 그래프에서 정의된 최대 절단 문제를 다루는 반면, 두 번째 예제는 비용 함수에 의해 정의된 50개의 정점을 가진 그래프 분할 문제를 다룬다.

최적화 솔버에 액세스하려면 Q-CTRL에 문의하세요.


기능 설명

솔버는 하드웨어 수준에서의 오류 억제부터 효율적인 문제 매핑 및 폐쇄 루프 클래식 최적화에 이르기까지 전체 알고리즘을 완전히 최적화하고 자동화합니다. 솔버의 파이프라인은 모든 단계에서 오류를 줄여 의미 있는 확장에 필요한 향상된 성능을 제공합니다. 기본 워크플로우는 양자-클래식 하이브리드 알고리즘인 양자 근사 최적화 알고리즘(QAOA)에서 영감을 받았습니다. 전체 최적화 솔버 워크플로우에 대한 자세한 요약은 게시된 원고를 참조하세요.

최적화 솔버 워크플로우의 시각화

최적화 솔버로 일반적인 문제를 해결하려면:

  1. 문제를 목적 함수, 그래프 또는 SparsePauliOp 스핀 체인으로 정의합니다.
  2. 키스킷 함수 카탈로그를 통해 함수에 연결합니다.
  3. 솔버로 문제를 실행하고 결과를 검색합니다.

허용되는 문제 형식

  • 목적함수의 다항식 표현. Python 에서 기존 SymPy 폴리 개체를 사용하여 생성하고 sympy.srepr.
  • 특정 문제 유형을 그래프로 나타낸 것. 그래프는 Python 에 있는 networkx 라이브러리를 사용하여 작성해야 합니다. 그런 다음 networkx 함수를 사용하여 이를 문자열로 변환해야 합니다 nx.readwrite.json_graph.adjacency_data.
  • 특정 문제에 대한 스핀 체인 표현. 스핀 체인은 SparsePauliOp 객체로 표시되어야 합니다. 자세한 내용은 문서를 참조하세요.
이 기능은 모든 IBM 백엔드를 지원합니까?

이 함수가 현재 지원하지 않는 백엔드를 사용하고자 하는 경우, Q-CTRL에 문의하여 지원 기능을 추가해 주시기 바랍니다.


벤치마크

면책사항

성능은 문제 인스턴스와 후속 처리 단계 모두에 따라 달라질 수 있습니다. 경우에 따라서는, 고전적 샘플과 양자 방식으로 생성된 샘플이 동등한 후처리를 거친 후 유사한 최종 솔루션 품질을 달성할 수도 있다. 따라서 평가 시에는 최적화 워크플로우 전체를 고려해야 합니다.

공개된 벤치마킹 결과에 따르면 솔버는 120큐비트 이상의 문제를 성공적으로 해결했으며, 심지어 양자 어닐링 및 갇힌 이온 장치에 대해 이전에 발표된 결과보다 더 뛰어난 성능을 보였습니다. 다음 벤치마크 메트릭은 몇 가지 예를 바탕으로 문제 유형의 정확도와 확장성을 대략적으로 보여줍니다. 실제 지표는 목적 함수의 항 수(밀도)와 그 위치, 변수 수, 다항식 순서 등 다양한 문제 특징에 따라 달라질 수 있습니다.

표시된 '큐비트 수'는 엄격한 제한이 아니라 매우 일관된 솔루션 정확도를 기대할 수 있는 대략적인 임계값을 나타냅니다. 더 큰 규모의 문제도 성공적으로 해결되었으며, 이러한 한계를 넘어서는 테스트가 권장됩니다.

모든 문제 유형에서 임의 큐비트 연결이 지원됩니다.

문제점 유형
큐비트 수
정확도
총시간(초)
런타임 사용량(초)
반복 수
드물게 연결된 이차 문제1563-정규 최대 절단100%로1764293그림 16
고차 바이너리 최적화156이싱 스핀 글래스 모델100%로1461272그림 16
밀접하게 연결된 이차 문제50완전 연결 최대 절단100%로175826812
엄격한 제약 조건이 있는 제약 문제508%의 간선 밀도를 가진 가중 그래프 분할100%로10742151,000만

시작하기

먼저, IBM Quantum API 키 를 사용하여 인증하십시오. 그런 다음 다음과 같이 Qiskit 함수를 선택합니다. (이 코드 조각은 이미 계정을 로컬 환경에 저장해 두었다고 가정합니다.)

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

예시: 제약 없는 최적화

최대 절단 (max-cut) 문제를 실행하십시오. 다음 예제는 156개의 노드를 가진 3-정규 무가중 그래프의 최대 절단 문제에 대해 솔버의 기능을 보여줍니다. 하지만 가중 그래프 문제도 해결할 수 있습니다.

qiskit-ibm-catalog 외에도 다음 패키지를 사용하여 이 예제를 실행합니다: networkxnumpy. IPython 커널을 사용하는 노트북에서 이 예제를 실행하는 경우 다음 셀의 주석 처리를 해제하여 이러한 패키지를 설치할 수 있습니다.

# %pip install networkx numpy

1. 문제 정의

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

솔버는 문제 정의 입력으로 문자열을 받습니다.

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

2. 문제를 실행하다

그래프 기반 입력 방법을 사용하는 경우 문제 유형을 지정합니다.

# Solve the problem
maxcut_job = solver.run(
    problem=problem_as_str,
    problem_type="maxcut",
    backend_name=backend_name,  # E.g. "ibm_fez"
)

다음과 같이 Qiskit 함수 워크로드의 상태를 확인하거나 결과를 반환하세요:

# 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. 결과를 가져옵니다

결과 사전에서 최적의 컷 값을 검색합니다.

Note

변수들의 비트열 매핑이 변경되었을 수 있습니다. 출력 사전에는 순서를 확인하는 데 도움이 되는 하위 variables_to_bitstring_index_map 사전이 포함되어 있습니다.

# 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

다음과 같은 오픈 소스 솔버를 사용하여 고전적으로 문제를 해결하여 결과의 정확성을 확인할 수 있습니다 PuLP 와 같은 오픈 소스 솔버를 사용하여 고전적으로 문제를 풀면 정확성을 확인할 수 있습니다. 고밀도 문제는 솔루션을 검증하기 위해 고급 클래식 솔버가 필요할 수 있습니다.


예시: 제약 조건 최적화

앞서 살펴본 맥스컷 예제는 일반적인 2차 제약 조건이 없는 이진 최적화 문제입니다. Q-CTRL의 최적화 솔버는 제약 조건을 목적 함수 내의 페널티 항으로 인코딩하는 대신, constraint 입력을 통해 솔버에 직접 전달함으로써 제약 조건이 있는 최적화 문제도 해결할 수 있습니다. 현재 솔버는 ‘ Hamming-weight-1 ’ 제약 조건을 지원합니다. 각 제약 조건은 변수 집합을 지정하며, 이 집합 내에서는 정확히 하나의 변수만 1이 되어야 하고 나머지는 모두 0이어야 합니다.

다음 예제는 그래프 분할이라는 제약 최적화 문제에 대해, 그래프의 각 정점을 여러 그룹 중 정확히 하나에 할당하면서, 같은 그룹에 속하는 양 끝점이 있는 가장자리의 총 가중치를 최소화함으로써, 비용 함수와 일련의 강제약 조건을 구성하는 방법을 보여줍니다.

qiskit-ibm-catalogqiskit 패키지 외에도 다음 패키지를 사용하여 이 예제를 실행합니다: numpy, networkx, 및 sympy. IPython 커널을 사용하는 노트북에서 이 예제를 실행하는 경우 다음 셀의 주석 처리를 해제하여 이러한 패키지를 설치할 수 있습니다.

# %pip install numpy networkx sympy

1. 문제 정의

노드에 무작위 가중치를 부여하여 그래프를 생성함으로써, 무작위 그래프 분할 문제를 정의하라.

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

가중 그래프 분할에 대한 표준 최적화 모델은 다음과 같이 정식화할 수 있다. 그래프의 노드들을 세 그룹 g{0,1,2}g \in \{0, 1, 2\} 으로 나누고, 노드 ii 이 그룹 gg 에 할당된 경우 ni,g=1n_{i,g} = 1 로 하고, 그렇지 않은 경우 ni,g=0n_{i,g} = 0 로 한다. 목표는 양 끝점이 동일한 그룹에 할당된 간선의 총 가중치를 최소화하는 것이며, 여기서 간선 (i,j)(i,j) 의 가중치는 두 끝점 ωi,j=ωi+ωj\omega_{i,j} = \omega_i + \omega_j 의 가중치를 합한 값이다:

Minimizey=(i,j)Eωi,jgni,gnj,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)]
        )

모든 노드는 세 그룹 중 정확히 하나에 속해야 합니다. 이는 ‘ Hamming-weight-1 ’ 제약 조건입니다. 모든 노드 ii 에 대해, ni,0,ni,1,ni,2n_{i,0}, n_{i,1}, n_{i,2} 중 정확히 하나만 1이어야 하며, 나머지는 모두 0이어야 합니다:

ni,0+ni,1+ni,2=1 for all iVn_{i,0} + n_{i,1} + n_{i,2} = 1 \texttt{ for all } i \in V

이 요구 사항을 비용 함수에 벌칙 항으로 반영하기보다는, 입력을 constraint 사용하여 이를 고정 제약 조건으로 Solver에 직접 전달하십시오.

# 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}
부분적으로 제약 조건이 주어진 문제

모든 변수를 constraint.에 추가할 필요는 없습니다. 사전에 포함되지 않은 변수는 제약 조건이 적용되지 않으므로, 동일한 문제 내에서 엄격한 제약 조건이 적용된 변수 그룹과 제약 조건이 없는 변수를 함께 사용할 수 있습니다.

2. 문제를 실행하다

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

다음과 같이 Qiskit 함수 워크로드의 상태를 확인하거나 결과를 반환하세요:

# 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. 결과를 얻다

해법을 확인하고 결과를 분석하십시오. 해의 비용은 양 끝점이 동일한 그룹에 속하게 된 변들의 총 무게를 나타내므로, 비용이 낮을수록 그래프의 분할이 더 우수함을 의미합니다.

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

지원 받기

질문이나 문제가 있는 경우 Q-CTRL로 문의하세요.


변경 로그

  • 2026-08-10: input constraint 을 통해 하드 제약 조건(해밍 가중치 1)에 대한 지원을 추가했으며, 이를 사용하도록 제약 최적화 예제를 업데이트했습니다.
  • 2026-02-11: 이제 다음을 지원합니다. ibm_miami

다음 단계

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