주 콘텐츠로 건너뛰기

Operator 클래스

패키지 버전

이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 이 버전 이상을 사용하는 것을 권장합니다.

qiskit[all]~=2.3.0

이 페이지에서는 Operator 클래스를 사용하는 방법을 설명합니다. Operator 클래스 및 기타 클래스를 포함한 Qiskit의 연산자 표현에 대한 개략적인 개요는 연산자 클래스 개요를 참조하세요.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import CXGate, RXGate, XGate
from qiskit.quantum_info import Operator, Pauli, process_fidelity

클래스를 Operator로 변환하기

Qiskit의 여러 다른 클래스는 연산자 초기화 메서드를 사용하여 Operator 객체로 직접 변환할 수 있습니다. 예를 들면 다음과 같습니다:

  • Pauli 객체
  • GateInstruction 객체
  • QuantumCircuit 객체

마지막 항목의 의미는, 시뮬레이터 Backend를 호출하지 않고도 Operator 클래스를 유니터리 시뮬레이터로 사용하여 양자 Circuit의 최종 유니터리 행렬을 계산할 수 있다는 것입니다. Circuit에 지원되지 않는 연산이 포함되어 있으면 예외가 발생합니다. 지원되지 않는 연산은 측정(measure), 리셋(reset), 조건부 연산, 또는 행렬 정의나 행렬 정의가 있는 Gate로의 분해가 없는 Gate입니다.

# Create an Operator from a Pauli object

pauliXX = Pauli("XX")
Operator(pauliXX)
Operator([[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))
# Create an Operator for a Gate object
Operator(CXGate())
Operator([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
Operator([[0.70710678+0.j        , 0.        -0.70710678j],
[0. -0.70710678j, 0.70710678+0.j ]],
input_dims=(2,), output_dims=(2,))
# Create an operator from a QuantumCircuit object
circ = QuantumCircuit(10)
circ.h(0)
for j in range(1, 10):
circ.cx(j - 1, j)

# Convert circuit to an operator by implicit unitary simulation
Operator(circ)
Operator([[ 0.70710678+0.j,  0.70710678+0.j,  0.        +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0.70710678+0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
...,
[ 0. +0.j, 0. +0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0.70710678+0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j]],
input_dims=(2, 2, 2, 2, 2, 2, 2, 2, 2, 2), output_dims=(2, 2, 2, 2, 2, 2, 2, 2, 2, 2))

Circuit에서 Operator 사용하기

유니터리 OperatorsQuantumCircuit.append 메서드를 사용하여 QuantumCircuit에 직접 삽입할 수 있습니다. 이렇게 하면 OperatorUnitaryGate 객체로 변환되어 Circuit에 추가됩니다.

연산자가 유니터리가 아닌 경우 예외가 발생합니다. 이는 Operator.is_unitary() 함수를 사용하여 확인할 수 있으며, 연산자가 유니터리이면 True를, 그렇지 않으면 False를 반환합니다.

# Create an operator
XX = Operator(Pauli("XX"))

# Add to a circuit
circ = QuantumCircuit(2, 2)
circ.append(XX, [0, 1])
circ.measure([0, 1], [0, 1])
circ.draw("mpl")

Output of the previous code cell

위 예제에서 연산자는 Pauli 객체로부터 초기화됩니다. 그러나 Pauli 객체를 Circuit에 직접 삽입할 수도 있으며, 이 경우 단일 Qubit Pauli Gate의 시퀀스로 변환됩니다:

# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli("XX"), [0, 1])
circ2.measure([0, 1], [0, 1])
circ2.draw()
┌────────────┐┌─┐   
q_0: ┤0 ├┤M├───
│ Pauli(XX) │└╥┘┌─┐
q_1: ┤1 ├─╫─┤M├
└────────────┘ ║ └╥┘
c: 2/═══════════════╩══╩═
0 1

Operator 결합하기

Operator는 여러 방법으로 결합할 수 있습니다.

텐서곱

두 연산자 AABBOperator.tensor 함수를 사용하여 텐서곱 연산자 ABA\otimes B로 결합할 수 있습니다. AABB가 모두 단일 Qubit 연산자인 경우, A.tensor(B) = ABA\otimes B는 서브시스템 0에 행렬 BB, 서브시스템 1에 행렬 AA로 인덱싱됩니다.

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.tensor(B)
Operator([[ 0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j],
[ 0.+0.j, -0.+0.j, 0.+0.j, -1.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, -0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

텐서 전개

밀접하게 관련된 연산으로 Operator.expand가 있으며, 이는 텐서곱과 유사하지만 순서가 반대입니다. 따라서 두 연산자 AABB에 대해 A.expand(B) = BAB\otimes A이며, 서브시스템 0에 행렬 AA, 서브시스템 1에 행렬 BB로 인덱싱됩니다.

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.expand(B)
Operator([[ 0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, -0.+0.j, -1.+0.j],
[ 0.+0.j, 0.+0.j, -1.+0.j, -0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

합성

Operator.compose 메서드를 사용하여 두 연산자 AABB를 합성하여 행렬 곱셈을 구현할 수 있습니다. A.compose(B)는 행렬 B.AB.A를 갖는 연산자를 반환합니다:

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.compose(B)
Operator([[ 0.+0.j,  1.+0.j],
[-1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

composefront 키워드 인수를 사용하여 AA 앞에 BB를 적용하는 역순으로 합성할 수도 있습니다: A.compose(B, front=True) = A.BA.B:

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.compose(B, front=True)
Operator([[ 0.+0.j, -1.+0.j],
[ 1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

서브시스템 합성

이전의 합성 방법은 첫 번째 연산자 AA의 전체 출력 차원이 합성할 연산자 BB의 전체 입력 차원과 같아야 합니다 (마찬가지로, front=True로 합성할 때 BB의 출력 차원은 AA의 입력 차원과 같아야 합니다).

composeqargs 키워드 인수를 사용하여, front=True 유무에 상관없이 더 큰 연산자의 서브시스템 선택과 더 작은 연산자를 합성할 수 있습니다. 이 경우, 합성되는 서브시스템의 관련 입력 및 출력 차원이 일치해야 합니다. 더 작은 연산자는 항상 compose 메서드의 인수여야 합니다.

예를 들어, 2-Qubit Gate를 3-Qubit 연산자와 합성하려면:

# Compose XZ with a 3-qubit identity operator
op = Operator(np.eye(2**3))
XZ = Operator(Pauli("XZ"))
op.compose(XZ, qargs=[0, 2])
Operator([[ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
-1.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j]],
input_dims=(2, 2, 2), output_dims=(2, 2, 2))
# Compose YX in front of the previous operator
op = Operator(np.eye(2**3))
YX = Operator(Pauli("YX"))
op.compose(YX, qargs=[0, 2], front=True)
Operator([[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j],
[0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2, 2), output_dims=(2, 2, 2))

선형 결합

Operator는 덧셈, 뺄셈, 복소수에 의한 스칼라 곱 등 표준 선형 연산자를 사용하여 결합할 수도 있습니다.

XX = Operator(Pauli("XX"))
YY = Operator(Pauli("YY"))
ZZ = Operator(Pauli("ZZ"))

op = 0.5 * (XX + YY - 3 * ZZ)
op
Operator([[-1.5+0.j,  0. +0.j,  0. +0.j,  0. +0.j],
[ 0. +0.j, 1.5+0.j, 1. +0.j, 0. +0.j],
[ 0. +0.j, 1. +0.j, 1.5+0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0. +0.j, -1.5+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

중요한 점은, tensor, expand, compose는 유니터리 연산자의 유니터리성을 보존하지만, 선형 결합은 그렇지 않다는 것입니다. 따라서 두 유니터리 연산자를 더하면 일반적으로 비유니터리 연산자가 됩니다:

op.is_unitary()
False

Operator로의 암묵적 변환

다음의 모든 메서드에서, 두 번째 객체가 이미 Operator 객체가 아닌 경우, 메서드에 의해 암묵적으로 Operator로 변환됩니다. 즉, 행렬을 명시적으로 Operator로 변환하지 않고 직접 전달할 수 있습니다. 변환이 불가능한 경우 예외가 발생합니다.

# Compose with a matrix passed as a list
Operator(np.eye(2)).compose([[0, 1], [1, 0]])
Operator([[0.+0.j, 1.+0.j],
[1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

Operator 비교하기

Operator는 두 연산자가 근사적으로 같은지 확인하는 데 사용할 수 있는 등호 메서드를 구현합니다.

Operator(Pauli("X")) == Operator(XGate())
True

이것은 연산자의 각 행렬 원소가 근사적으로 같은지를 확인합니다. 전역 위상만 다른 두 유니터리는 같다고 간주되지 않습니다:

Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
False

프로세스 충실도

양자 정보 모듈의 process_fidelity 함수를 사용하여 연산자를 비교할 수도 있습니다. 이는 두 양자 채널이 얼마나 유사한지를 나타내는 정보 이론적 양으로, 유니터리 연산자의 경우 전역 위상에 의존하지 않습니다.

# Two operators which differ only by phase
op_a = Operator(XGate())
op_b = np.exp(1j * 0.5) * Operator(XGate())

# Compute process fidelity
F = process_fidelity(op_a, op_b)
print("Process fidelity =", F)
Process fidelity = 1.0

프로세스 충실도는 일반적으로 입력 연산자가 유니터리(또는 양자 채널의 경우 CP)인 경우에만 유효한 근접도 측도이며, 입력이 CP가 아닌 경우 예외가 발생합니다.

다음 단계

권장 사항