주 콘텐츠로 건너뛰기
이 프로젝트는 베타 단계입니다. 버그를 발견하셨나요? GitHub 이슈 만들기. 질문이나 아이디어가 있으신가요? Discussions에서 질문하기 — 피드백을 기다립니다!
이 페이지의 번역 품질은 어떻습니까?

연산자 표현의 설계 원칙

이 가이드는 operators 모듈의 모든 연산자 표현에 공통된 설계 원칙과 핵심 개념을 설명합니다.

개요​

이 모듈이 제공하는 연산자 표현은 몇 가지 기본 설계 원칙을 공유합니다.

  • 희소 데이터 구조: 연산자는 항등이 아닌 연산만 인코딩합니다. 내부 데이터 레이아웃은 일반적으로 희소 행렬 데이터 형식에서 착안한 것으로, 모드가 많지만 유의미한 기여는 상대적으로 적은 시스템에서 효율적인 저장과 계산을 가능하게 합니다.

  • 항 순회와 재구성: 내부 희소 저장 방식과 무관하게 연산자는 일관된 순회 인터페이스를 제공하므로, 기반 데이터 구조를 이해하지 않고도 항을 검사, 필터링, 변환한 다음 수정된 항으로 새 연산자를 재구성할 수 있습니다.

  • 모드 기반 인덱싱: 연산자는 추상적인 모드 인덱스로 페르미온 자유도를 표시하므로, 물리 시스템에서 연산자 표현으로의 유연한 매핑이 가능합니다.

  • 항 그룹화와 교환 관계: 연산자는 항을 그룹 인덱스와 연결하는 그룹화 정보를 기본적으로 지원합니다. 이를 통해 별도의 데이터 구조 없이도 최적화와 물리적 구조 보존이 가능합니다. 실제 사용법은 그룹화 가이드를 참조하세요.

  • 산술 및 수학 연산: 모든 연산자는 OperatorTrait 프로토콜을 사용하여 일관된 산술 연산(덧셈, 곱셈, 합성 등)과 수학 함수 집합을 구현하므로, 서로 다른 연산자 유형에서도 통일된 코드를 작성할 수 있습니다.

  • 연산자 항 순서와 정규형: 수학적으로 동등한 연산자라도 양자 알고리즘에서 표현과 동작이 매우 다를 수 있습니다. 모든 연산자 표현은 (대수별 교환 관계에 기반한) 다양한 정규형을 지원하여 표준적이고 예측 가능하며 최적화 가능한 연산자 표현을 얻을 수 있게 합니다.

희소 데이터 구조​

모든 연산자는 항등이 아닌 연산만 추적하는 희소 표현을 사용합니다. 각 항등이 아닌 연산은 계수(복소수)와 특정 모드에 대한 행동의 시퀀스로 구성됩니다.

이 접근 방식은 메모리 사용량과 계산 시간을 크게 줄여 주며, 특히 모드는 많지만 유의미한 기여는 상대적으로 적은 시스템에서 효과적입니다. 항등이 아닌 연산만 인코딩하므로 연산이 중요한 부분에만 집중하여, 밀집 표현으로는 불가능한 대규모 시스템도 다룰 수 있습니다. 또한 연산자는 어떤 모드 수로도 자연스럽게 확장됩니다. 영향받지 않는 모드는 암묵적으로 항등이므로, 모드 {0, 1}에 작용하는 연산자는 모드가 훨씬 많은 시스템에서도 그대로 동작합니다.

동일한 항은 산술 연산 중에도 별도로 유지되며, 필요하면 명시적으로 결합해야 합니다.

내부 저장 형식​

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

  • 계수 배열: 각 항의 복소 계수

  • 모드 인덱스 배열: 각 행동이 작용하는 페르미온 모드

  • 경계 배열: 모드 배열에서 각 항의 모드가 시작하고 끝나는 위치를 표시하는 인덱스

중요

연산자 유형에 따라 추가 배열이 있을 수 있습니다. 예를 들어 FermionOperator 인스턴스에는 해당 모드 인덱스에 작용하는 페르미온 행동의 유형을 지정하는 불리언 값의 행동 배열이 포함됩니다. 반면 MajoranaOperator 클래스는 모드 인덱스의 패리티에 그 정보를 인코딩하므로 이러한 구분이 필요하지 않습니다. 전체 저장 형식은 해당 연산자 유형의 API 문서를 참조하세요.

다음 예제는 이 배열들이 어떻게 구성되는지 보여줍니다. 먼저 희소 배열을 사용한 직접 구성입니다.

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

편리한 생성 메서드​

Python 개발자를 위해 희소 저장 방식의 세부 사항을 추상화한 여러 편리한 생성 메서드가 제공됩니다. 이를 사용하면 계수, 모드, 경계 배열을 직접 관리하지 않고도 연산자를 더 쉽게 만들 수 있습니다.

>>> 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)
힌트

개별 연산자 구현은 해당 용도에 맞는 추가 생성 메서드를 지원할 수 있습니다. 사용 가능한 모든 생성 옵션은 사용 중인 연산자 유형의 API 문서에서 확인하세요.

항 순회와 재구성​

연산자는 내부의 희소 표현과 관계없이 OperatorTrait.iter_terms()를 통해 일관된 순회 인터페이스를 제공합니다. 따라서 내부 데이터 구조를 이해하지 않고도 항을 살펴보거나 필터링하거나 변환할 수 있습니다. 그런 다음 OperatorTrait.from_terms()를 사용해 변환된 항으로 새 연산자를 재구성할 수 있습니다.

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

모드 기반 인덱싱​

모든 연산자 표현은 항이 작용하는 인덱스를 모드라고 부릅니다. 모드는 시스템의 페르미온 자유도를 식별하는 단순한 인덱스입니다. 물리적 자유도(공간 오비탈, 스핀 상태 또는 기타 양자수 등)를 모드 인덱스에 대응시키는 방식은 사용자에게 맡겨지며, 이를 통해 최대한의 유연성을 확보할 수 있습니다.

이러한 추상화는 qiskit_fermions.circuit 모듈에도 나타나며, 여기서 FermionicCircuit은 페르미온 모드 레지스터에서 동작합니다. 연산자 표현과 회로 표현 모두에서 모드는 특정 연산에 어떤 자유도가 참여하는지를 대수와 무관하게 일관된 방식으로 지정합니다.

중요

현재 구현은 스핀이 없는 모드를 사용합니다: 이 모듈이 현재 제공하는 모든 연산자 표현은 모드를 스핀이 없는 페르미온 자유도로 취급합니다. 따라서 시스템에 스핀 업과 스핀 다운 전자 또는 페르미온이 모두 있다면, 이를 서로 다른 모드에 명시적으로 대응시켜야 합니다(예: 공간 오비탈 4개의 스핀 업은 모드 0-3, 스핀 다운은 모드 4-7로 두거나, 원하는 다른 규칙을 사용).

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

힌트

패키지가 발전하면서 데이터 모델에서 스핀 자유도를 기본적으로 지원하는 스핀 있는 연산자 표현이 추가될 수 있습니다. 이러한 표현은 현재의 스핀 없는 구현과 명확히 구분되며, 모듈 안에서 함께 존재하게 됩니다.

항 그룹화와 교환 관계​

계수 및 모드 인덱스와 마찬가지로, 연산자는 각 항을 그룹 인덱스와 연결하는 그룹 배열을 선택적으로 저장할 수 있습니다. 그룹화를 희소 데이터 구조의 일부로 연산자 표현에 직접 통합하면, 그룹화 정보가 변환 과정에서도 자연스럽게 연산자와 함께 유지됩니다. 이를 통해 물리적 성질, 대수적 관계 또는 문제별 대칭성에서 비롯된 구조를 체계적으로 활용할 수 있습니다. 이렇게 구조화된 정보는 OperatorTrait.split_out_groups() 같은 메서드를 사용해 회로 합성 및 분해와 같은 후속 연산에 활용할 수 있습니다.

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

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

산술 및 수학 연산​

모든 연산자는 OperatorTrait 프로토콜을 구현하며, 이 프로토콜은 서로 다른 연산자 유형 전반에 걸쳐 통일된 연산 집합을 제공합니다. 따라서 한 연산자 표현을 위해 작성한 코드가 다른 표현에서도 동일하게 동작합니다. 이 프로토콜이 이 패키지가 정의하는 다른 프로토콜과 어떤 관계인지는 qiskit_fermions.protocols를 참조하세요.

이 프로토콜에는 산술 연산(덧셈, 곱셈, 합성 등), 구조 연산(항 순회, 모드 지원 분석, 레이블 재지정), 수학 함수(정규 순서화, 단순화, 동치 검사) 등이 포함됩니다. 사용 가능한 모든 연산의 전체 참조는 OperatorTrait 문서를 참조하세요.

>>> 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
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
참고

is_hermitian()는 H−H†=0H - H^\dagger = 0인지를 확인하기 위해, 그 차이를 정규형으로 줄인 뒤 zero()와 비교합니다. 다른 프로토콜 연산으로 풀어서 쓰면 다음과 같은 작업을 수행합니다.

>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True

이 검사의 강도는 연산자 유형에 따라 달라지는데, 해당 유형의 정규형이 인접한 생성자를 얼마나 완전하게 축약하느냐에 달려 있기 때문입니다. True 결과는 항상 신뢰할 수 있지만, 일부 유형에서는 False 결과가 보수적인 판정일 수 있습니다. 자세한 내용은 is_hermitian()를 참조하세요.

중요

이 예제는 atol=1e-10을 사용합니다. atol(절대 허용 오차) 매개변수는 임계값을 지정합니다. 크기가 atol보다 작은 계수는 0으로 취급되어 버려집니다. 부동소수점 연산에서는 작은 반올림 오차가 생겨 동등한 연산자가 동등하다고 인식되지 못할 수 있으므로, 연산자를 비교할 때 수치 안정성을 위해 이 설정이 필수적입니다.

힌트

OperatorTrait 프로토콜은 공통 인터페이스를 제공하지만, 개별 연산자 구현은 프로토콜에 포함되지 않은 추가 편의 메서드를 제공할 수 있습니다. 예를 들어 FermionOperator와 MajoranaOperator는 가장 긴 항의 길이를 알려 주는 max_rank() 메서드를 제공합니다. 사용 가능한 모든 기능을 확인하려면 항상 사용 중인 연산자 유형의 API 문서를 참조하세요.

연산자 항 순서와 정규형​

양자 연산자 대수의 근본적인 난제 중 하나는 수학적으로 동등한 연산자를 여러 다른 방식으로 표현할 수 있고, 각 표현이 양자 알고리즘에 서로 다른 영향을 준다는 점입니다. 같은 연산자를 대수적으로 동등한 형태로 쓸 수 있지만(예: a†ba^\dagger b는 ba†+[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})을 사용합니다. 정규 순서화가 어떻게 구현되어 있는지 이해하려면 항상 사용 중인 연산자 유형의 문서를 참조하세요.

>>> 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
힌트

OperatorTrait.normal_ordered() 프로토콜 메서드는 위치 인수와 키워드 인수를 의도적으로 지정하지 않은 채로 두며, 구체적인 연산자 구현이 생성되는 정준형을 제어하는 조정 가능한 매개변수를 정의할 수 있게 합니다. 이를 통해 각 대수나 사용 사례에 맞춘 연산자별 최적화와 정규형 변형이 가능합니다. 사용할 수 있는 매개변수는 사용 중인 연산자 유형의 API 문서에서 확인하세요.