Skip to main content
IBM Quantum Platform

연산자 표현의 설계 원칙

이 가이드에서는 해당 모듈 operators 내의 모든 연산자 표현에 공통적으로 적용되는 일반적인 설계 원칙과 핵심 개념을 설명합니다.


개요

이 모듈에서 제공하는 연산자 표현들은 몇 가지 기본적인 설계 원칙을 공유합니다:

  • 스파스 데이터 구조 : 연산자는 비정체 연산만 인코딩합니다. 내부 데이터 레이아웃은 일반적으로 스파스 행렬 데이터 형식에서 착안한 것으로, 모드는 많지만 유의미한 기여를 하는 모드는 상대적으로 적은 시스템에서 효율적인 저장 및 계산을 가능하게 합니다.
  • 항의 반복 및 재구성 : 내부 스파스 저장 방식과 관계없이 연산자는 일관된 반복 인터페이스를 제공하므로, 사용자는 기본 데이터 구조를 이해하지 않고도 항을 검사, 필터링 및 변환한 다음, 수정된 항을 바탕으로 새로운 연산자를 재구성할 수 있습니다.
  • 모드 기반 인덱싱 : 연산자는 추상 모드 인덱스를 사용하여 페르미온 자유도를 표기함으로써, 물리적 시스템과 연산자 표현 사이의 유연한 매핑을 가능하게 한다.
  • 항의 묶음과 교환 관계 : 연산자는 항을 그룹 인덱스와 연결하는 묶음 정보를 기본적으로 지원합니다. 이를 통해 별도의 데이터 구조를 사용하지 않고도 최적화와 물리적 구조 보존이 가능해집니다. 실제 사용 방법은 그룹화 가이드를 참조하십시오.
  • 산술 및 수학적 연산 : 모든 연산자는 프로토콜을 OperatorTrait 사용하여 일관된 산술 연산 집합(덧셈, 곱셈, 합성 등)과 수학적 함수를 구현함으로써, 서로 다른 연산자 유형 간에 일관된 코드를 사용할 수 있게 합니다.
  • 연산자 항의 순서와 정규형 : 수학적으로 동등한 연산자라도 양자 알고리즘에서는 표현 방식과 동작이 매우 다를 수 있다. 모든 연산자 표현은 표준적이고, 예측 가능하며, 최적화 가능한 연산자 표현을 구현하기 위해 (대수별 교환 관계에 기반한) 다양한 정규형을 지원합니다.

스파스 데이터 구조

모든 연산자는 동일성 연산을 제외한 나머지 연산만 추적하는 스파스 표현을 사용합니다. 각 비정체 연산은 계수 (복소수)와 특정 모드에 대한 일련의 작용으로 구성됩니다.

이러한 접근 방식은 특히 모드는 많지만 실질적인 기여도가 상대적으로 적은 시스템의 경우, 메모리 사용량과 계산 시간을 획기적으로 줄여줍니다. 비정체 연산만을 인코딩함으로써, 연산은 중요한 요소에만 집중하게 되며, 이는 밀집 표현으로는 처리하기 어려웠을 대규모 시스템에 대한 작업을 가능하게 합니다. 또한, 연산자는 자연스럽게 임의의 모드 수에 맞춰 확장됩니다. 모드에 작용하는 연산자는, 영향을 받지 않는 모드가 암묵적으로 항등 연산자로 간주되므로, 훨씬 더 많은 모드를 가진 시스템에서도 변함없이 작동합니다 {0, 1} .

산술 연산 시 동일한 항은 별도로 처리되므로, 필요한 경우 명시적으로 결합해야 합니다.

내장 저장소 형식

내부적으로는 연산자들이 희소 행렬 데이터 형식에서 착안한 배열에 저장됩니다:

  • 계수 배열 : 각 항에 대한 복소수 계수
  • 모드 인덱스 배열 : 각 작용이 영향을 미치는 페르미온 모드
  • 경계 배열 : 모드 배열에서 각 항의 모드가 시작하고 끝나는 위치를 나타내는 인덱스
중요

연산자 유형에 따라 추가 배열이 존재할 수 있습니다. 예를 들어, FermionOperator 인스턴스에는 각 모드 인덱스에 작용하는 페르미온 작용의 유형을 지정하는 부울 값으로 구성된 ‘actions’ 배열 이 포함됩니다. 반면, 이 MajoranaOperator 클래스는 모드 인덱스의 패리티에 해당 정보를 인코딩하고 있으므로 이러한 구분이 필요하지 않습니다. 저장 형식에 대한 자세한 내용은 해당 연산자 유형의 API 문서를 참조하십시오.

다음 예시는 이러한 배열이 어떻게 구성되어 있는지 보여줍니다. 첫 번째는 스파스 배열을 사용한 직접적인 구문입니다:

[x] 파이썬

>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[0, 1],
...     boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[2, 3],
...     boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
  1.000000e0 +0.000000e0j * (+0 -1)
  1.000000e0 +0.000000e0j * (+2 -3)

[] C

#include <qiskit_fermions.h>

// Construct first operator: 1.0 * c_0 a_1
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 1};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

// Construct second operator: 1.0 * c_2 a_3
QkComplex67 coeff2[1] = {{1.0, 0.0}};
uint32_t modes2[2] = {2, 3};
uint32_t boundaries2[2] = {0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(1, 2, coeff2, modes2, boundaries2);

// Add operators
qf_ferm_op_add_assign(op1, op2);

qf_ferm_op_free(op1);
qf_ferm_op_free(op2);

편리한 시공 방법

Python 개발자의 경우, 스파스 저장 방식의 세부 사항을 추상화해 주는 몇 가지 편리한 생성 메서드를 사용할 수 있습니다. 이를 통해 계수, 모드, 경계 배열을 관리하는 데 신경 쓰지 않고도 연산자를 더 쉽게 정의할 수 있습니다:

[x] 파이썬

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)

[] C

// The C API uses direct array construction; convenience methods are not available.
힌트

개별 구현체에 따라 특정 사용 사례에 적합한 추가 생성자 메서드를 지원할 수도 있습니다. 사용 중인 연산자 유형에 대한 API 문서를 확인하여 사용 가능한 모든 생성 옵션을 살펴보세요.


항의 반복 및 재구성

연산자는 내부적인 스파스 표현과 OperatorTrait.iter_terms() 무관하게 일관된 반복 인터페이스를 제공합니다. 따라서 기본이 되는 데이터 구조를 이해할 필요 없이 항목을 확인하거나, 필터링하거나, 변환할 수 있습니다. 그런 다음, 를 사용하여 변환된 항들로부터 새로운 연산자를 재구성할 수 있습니다 OperatorTrait.from_terms().

[x] 파이썬

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
...     (): 0.5,  # constant term (order 0)
...     (cre(0), ann(1)): 1.0,  # two-body term (order 2)
...     (cre(0), cre(1), ann(1), ann(0)): 0.25  # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
...     (term, coeff) for term, coeff in op.iter_terms()
...     if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term

[] C

// WARNING: Term iteration and filtering are not yet available in the C API.

모드 기반 색인 생성

모든 연산자 표현은 해당 항이 작용하는 인덱스를 모드 라고 합니다. 모드란 단순히 시스템 내의 페르미온 자유도를 식별하는 지표입니다. 물리적 자유도(공간 궤도, 스핀 상태 또는 기타 양자수 등)와 모드 지수 간의 대응 관계는 사용자가 직접 설정할 수 있도록 되어 있어, 최대한의 유연성을 보장합니다.

이러한 추상화는 모듈에서도 qiskit_fermions.circuit 나타나는데, 여기서 는 페르미온 모드의 레지스터를 대상으로 연산을 수행한다 FermionicCircuit . 연산자 표현과 회로 표현 모두에서, 모드는 주어진 연산에 어떤 자유도가 관여하는지 명시하는, 일관되고 대수적 표현에 의존하지 않는 방법을 제공합니다.

중요

현재 구현에서는 스핀이 없는 모드를 사용합니다. 이 모듈에서 현재 제공하는 모든 연산자 표현은 모드를 스핀이 없는 페르미온 자유도로 취급합니다. 즉, 시스템에 스핀-업과 스핀-다운 전자와 페르미온이 모두 존재하는 경우, 이를 서로 다른 모드에 명시적으로 매핑해야 합니다(예를 들어, 4개의 공간 궤도에서 스핀-업은 모드 03, 스핀-다운은 모드 47로 매핑하거나, 사용자가 선택한 다른 규칙을 적용할 수 있습니다).

이 설계는 핵심 표현을 단순하고 일반적이며, 동시에 특정 스핀 순서 규칙을 강요하지 않도록 합니다. 와 같은 유틸리티 모듈은 전자 구조 데이터를 불러올 때 이러한 매핑을 자동으로 처리해 주는 편리한 함수(예: FCIDump.from_file())를 제공합니다 qiskit_fermions.operators.library .

힌트

패키지가 발전함에 따라, 데이터 모델 내에서 스핀 자유도를 기본적으로 지원하는 스핀풀 연산자 표현법이 추가될 수도 있습니다. 이 기능들은 기존의 스핀리스 구현 방식과 명확히 구분되며, 모듈 내에서 해당 구현 방식과 공존하게 될 것입니다.


항의 묶음과 교환 관계

계수 및 모드 인덱스와 마찬가지로, 연산자는 선택적으로 각 항을 그룹 인덱스와 연결하는 ‘groups’ 배열을 저장할 수 있습니다. 그룹화를 스파스 데이터 구조의 일부로서 연산자 표현에 직접 통합함으로써, 그룹화 정보는 변환 과정을 거치면서 연산자와 함께 자연스럽게 전달됩니다. 이를 통해 물리적 특성, 대수적 관계, 또는 문제 특유의 대칭성 등 어떤 측면에서든 체계적인 구조 활용이 가능해집니다. 이러한 구조화된 정보는 이후 회로 합성 및 분해와 같은 후속 작업에서 다음과 같은 방법을 활용하여 사용될 수 있습니다 OperatorTrait.split_out_groups().

워크플로우에서 연산자 항목을 그룹화하는 방법에 대한 자세한 안내는 그룹화 가이드를 참조하십시오.

[x] 파이썬

>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
...     (gamma(0, False),): 1.0,
...     (gamma(1, False),): 1.0,
...     (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()

[] C

#include <qiskit_fermions.h>

// Create operator with 3 terms
QkComplex67 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 2, 3};
uint32_t boundaries[4] = {0, 1, 2, 4};
QfMajoranaOperator *op = qf_maj_op_new(3, 4, coeffs, modes, boundaries);

// Assign grouping information
uint32_t groups[3] = {0, 0, 1};
qf_maj_op_set_groups(op, groups, 3);

// Partition operator by groups
QfMajoranaOperator *grouped_ops[2];
qf_maj_op_split_out_groups(op, NULL, 0, grouped_ops);

산술 및 수학적 연산

모든 오퍼레이터는 이 OperatorTrait 프로토콜을 구현하며, 이 프로토콜은 서로 다른 유형의 오퍼레이터에 걸쳐 통일된 연산 집합을 제공합니다. 이를 통해 한 연산자 표현식을 위해 작성된 코드가 다른 연산자 표현식에서도 일관되게 작동하도록 보장합니다. 이 프로토콜이 이 패키지에서 정의된 다른 프로토콜들과 어떤 관련이 있는지 알아보려면 qiskit_fermions.protocols 여기를 참조하십시오.

이 프로토콜에는 산술 연산(덧셈, 곱셈, 합성 등), 구조적 연산(항 반복, 모드 지원 분석, 재표기), 수학적 함수(정상 순서 지정, 단순화, 동치성 검사) 등이 포함되어 있습니다. 사용 가능한 모든 작업에 대한 전체 참조 내용은 문서를 OperatorTrait 참조하십시오.

[x] 파이썬

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
...     (cre(0), ann(1)): 1.0,
...     (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian by verifying H - H† = 0
>>> adjoint = op.adjoint()
>>> difference = op - adjoint
>>> difference = difference.normal_ordered()
>>> difference = difference.simplify(atol=1e-10)
>>> is_hermitian = difference.equiv(FermionOperator.zero(), atol=1e-10)
>>> print(f"Operator is Hermitian: {is_hermitian}")
Operator is Hermitian: True

[] C

#include <qiskit_fermions.h>

// Construct a Hermitian operator: H = +0 -1 + +1 -0
QkComplex67 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 1, 0};
uint32_t boundaries[3] = {0, 2, 4};
QfFermionOperator *op = qf_ferm_op_new(2, 4, coeffs, modes, boundaries);

// Check if Hermitian: compute H - H†, normal-order, and simplify
QfFermionOperator *adjoint = qf_ferm_op_adjoint(op);
QfFermionOperator *difference = qf_ferm_op_sub(op, adjoint);
QfFermionOperator *normal_ordered = qf_ferm_op_normal_ordered(difference);
qf_ferm_op_ichop(normal_ordered, 1e-10);

QfFermionOperator *zero = qf_ferm_op_zero();
bool is_hermitian = qf_ferm_op_equiv(normal_ordered, zero, 1e-10);
printf("Operator is Hermitian: %s\n", is_hermitian ? "true" : "false");

// Clean up
qf_ferm_op_free(op);
qf_ferm_op_free(adjoint);
qf_ferm_op_free(difference);
qf_ferm_op_free(normal_ordered);
qf_ferm_op_free(zero);
중요

이 예제에서는 와 simplify() 모두에서 atol=1e-10 를 사용합니다 equiv(). ( atol 절대 허용오차) 매개변수는 임계값을 지정합니다. 보다 작은 절대값을 가진 계수는 0으로 간주되어 atol 제외됩니다. 이는 연산자를 비교할 때 수치적 안정성을 확보하는 데 필수적입니다. 부동소수점 연산에서는 미세한 반올림 오차가 발생할 수 있는데, 이러한 오차가 없다면 동등한 연산자끼리도 서로 동등한 것으로 인식되지 않을 수 있기 때문입니다.

힌트

프로토콜은 OperatorTrait 공통 인터페이스를 제공하지만, 개별 구현체에서는 프로토콜에 포함되지 않은 추가적인 편의 메서드를 제공할 수도 있습니다. 예를 들어, 일부 연산자는 이 검사를 구현하는 is_hermitian() 메서드를 제공합니다. 사용 가능한 모든 기능을 확인하려면 항상 해당 연산자 유형의 API 문서를 참조하십시오.


연산자 항의 순서와 표준형

양자 연산자 대수에서 근본적인 과제는, 수학적으로 동등한 연산자들이 여러 가지 다른 방식으로 표현될 수 있으며, 각 표현 방식이 양자 알고리즘에 서로 다른 함의를 갖는다는 점이다. 동일한 연산자는 대수적으로 동등한 형태로 표현될 수 있지만(예를 들어, aba^\dagger bba+[a,b]ba^\dagger + [a^\dagger,b] 로 표현될 수 있음), 이러한 표현들은 회로 합성, 단순화 및 수치 알고리즘에서 서로 다른 동작을 초래한다.

연산자 표현은 교환 관계를 이용하여 연산자를 대수 특유의 표준형으로 변환하는 일반적인 순서 연산을 지원한다. 이를 통해 신뢰할 수 있는 비교가 가능해지며(동등한 두 연산자는 동일한 정규 순서 형태를 가짐), 단순화가 드러나며(교환 관계로 인해 항이 소거되거나 결합됨), 정확성과 효율성을 위해 특정 연산자 형태가 필요한 알고리즘을 뒷받침합니다.

중요

서로 다른 연산자 표현 방식들은 각자의 대수에 적합한 서로 다른 교환 관계에 기초하여 정규 순서를 구현할 수 있다. 예를 들어, 페르미온 정규 순서는 반교환 관계( {ci,cj}=δij\{c_i, c_j^\dagger\} = \delta_{ij} )를 사용하는 반면, 마요라나 정규 순서는 다른 대수 규칙( {γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij} )을 사용합니다. 정규 순서가 어떻게 구현되는지 이해하려면 항상 해당 연산자 유형의 문서를 참조하십시오.

[x] 파이썬

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True

[] C

#include <qiskit_fermions.h>
#include <stdbool.h>

// Two different representations of the same operator
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 0};
uint32_t boundaries1[3] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

QkComplex67 coeff2[2] = {{1.0, 0.0}, {-1.0, 0.0}};
uint32_t modes2[2] = {0, 0};
uint32_t boundaries2[3] = {0, 0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(2, 2, coeff2, modes2, boundaries2);

// Direct comparison fails due to different forms
bool equiv_before = qf_ferm_op_equiv(op1, op2, 1e-10);
printf("Equivalent before normal ordering: %s\n", equiv_before ? "true" : "false");

// Normal-order both and compare again
QfFermionOperator *op1_normal = qf_ferm_op_normal_ordered(op1);
QfFermionOperator *op2_normal = qf_ferm_op_normal_ordered(op2);
bool equiv_after = qf_ferm_op_equiv(op1_normal, op2_normal, 1e-10);
printf("Equivalent after normal ordering: %s\n", equiv_after ? "true" : "false");

// Clean up
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(op1_normal);
qf_ferm_op_free(op2_normal);
힌트

이 프로토콜 OperatorTrait.normal_ordered() 메서드는 위치 매개변수와 키워드 매개변수를 의도적으로 명시하지 않음으로써, 구체적인 연산자 구현체가 생성되는 정확한 표준 형식을 제어하는 조정 가능한 매개변수를 정의할 수 있도록 합니다. 이를 통해 연산자별로 최적화를 적용하고, 해당 대수나 사용 사례에 맞춰 변형된 정규형을 구현할 수 있습니다. 사용 중인 연산자 유형에 대한 API 문서를 확인하여 어떤 매개변수를 사용할 수 있는지 확인하십시오.

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