Korean
언어
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

참고

이 페이지는 tutorials/circuits_advanced/02_operators_overview.ipynb 로부터 생성되었다.

연산자

[1]:
import numpy as np

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import BasicAer
from qiskit.compiler import transpile
from qiskit.quantum_info.operators import Operator, Pauli
from qiskit.quantum_info import process_fidelity

from qiskit.extensions import RXGate, XGate, CXGate

연산자 클래스

Qiskit에서 Operator 클래스는 양자 시스템에 작용하는 행렬 연산자를 나타내는데 사용된다. 이 클래스는 텐서곱을 이용한 다중 결합 연산자를 만들거나 연산자를 작성하는데 필요한 다양한 매서드를 포함한다.

연산자 만들기

연산자 객체를 만드는 가장 쉬운 방법은 리스트나 Numpy 배열로 주어진 행렬을 연산자로 초기화 하는 것이다. 예를 들면 두 큐비트 Pauli-XX 연산자를 작성하고자 한다면 다음과 같이 할 수 있다.

[2]:
XX = Operator([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]])
XX
[2]:
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))

연산자의 특성

연산자 객체는 그 기반이 되는 행렬과 서브시스템들의 입력 및 출력 차원을 포함한다.

  • data: 기반이 되는 Numpy 배열에 접근하고자 할 때 Operator.data 속성을 사용할 수 있다.

  • dims: 연산자의 전체 입력과 출력의 차원을 반환하기 위해 Operator.dim 속성을 사용할 수 있다. 주의: 출력은 기반 행렬의 모양과 반대인 튜플 (input_dim, output_dim) 로 반환한다.

[3]:
XX.data
[3]:
array([[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]])
[4]:
input_dim, output_dim = XX.dim
input_dim, output_dim
[4]:
(4, 4)

입력과 출력의 차원

연산자 클래스는 서브시스템의 차원을 추적하기 때문에 다중결합 연산자를 작성하는데 사용할 수 있다. 이 값은 input_dimsoutput_dims 함수를 이용하여 접근할 수 있다.

연산자의 행이 \(2^M\) 개이고 열이 \(2^M\) 개이면 입력과 출력 차원이 자동으로 M 큐비트와 N 큐비트으로 가정된다.

[5]:
op = Operator(np.random.rand(2 ** 1, 2 ** 2))
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (2, 2)
Output dimensions: (2,)

입력된 행렬을 서브시스템으로 나눌 수 없는 경우 단일 연산자로 저장된다. 예를 들어 입력된 행렬이 \(6\times6\) matrix:이면

[6]:
op = Operator(np.random.rand(6, 6))
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (6,)
Output dimensions: (6,)

입력과 출력 차원은 새 연산자를 초기화 해 줄 때 수동으로 명시해 줄 수 있다.

[7]:
# Force input dimension to be (4,) rather than (2, 2)
op = Operator(np.random.rand(2 ** 1, 2 ** 2), input_dims=[4])
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (4,)
Output dimensions: (2,)
[8]:
# Specify system is a qubit and qutrit
op = Operator(np.random.rand(6, 6),
              input_dims=[2, 3], output_dims=[2, 3])
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (2, 3)
Output dimensions: (2, 3)

또한 input_dimsoutput_dims 함수를 사용하여 일부 서브시스템들의 입력 혹은 출력 차원만을 추출할 수 있다.

[9]:
print('Dimension of input system 0:', op.input_dims([0]))
print('Dimension of input system 1:', op.input_dims([1]))
Dimension of input system 0: (2,)
Dimension of input system 1: (3,)

클래스를 연산자로 변환하기

Qiskit에서 사용되는 다른 클래스들은 연산자 초기화 매서드를 사용하여 Operator 객체로 바로 변환 가능하다. 예를 들어

  • Pauli 객체

  • GateInstruction 객체

  • QuantumCircuit 객체

마지막 포인트는 시뮬레이터 백엔드를 호출하지 않고 양자 회로에 대한 최종 유니타리 행렬을 계산하기 위해 Operator 클래스를 유니타리 시뮬레이터로 사용할 수 있다는 것을 의미한다. 회로에 지원되지 않는 연산이 포함되어 있으면 예외가 발생합니다. 지원되지 않는 연산들은: 측정, 재설정, 조건부 연산 또는 〈게이트를 행렬로 정의한다고 할 때, 행렬적 정의 또는 분해가 존재하지 않는 게이트들〉 이다.

[10]:
# Create an Operator from a Pauli object

pauliXX = Pauli('XX')
Operator(pauliXX)
[10]:
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))
[11]:
# Create an Operator for a Gate object
Operator(CXGate())
[11]:
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))
[12]:
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
[12]:
Operator([[0.70710678+0.j        , 0.        -0.70710678j],
          [0.        -0.70710678j, 0.70710678+0.j        ]],
         input_dims=(2,), output_dims=(2,))
[13]:
# 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)
[13]:
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))

회로에서 연산자 사용하기

유니터리 Operators 는 매서드 QuantumCircuit.append 를 이용하여 QuantumCircuit 에 바로 넣을 수 있다. 이는 Operator 를 회로에 추가되는 UnitaryGate 객체로 바꿔준다.

만약 연산자가 유니터리가 아니면 예외 처리 된다.유니터리인지는 함수 Operator.is_unitary() 를 사용하면 확인할 수 있다. 만약 연산자가 유니터리이면 True 를 그렇지 않으면 False 를 반환한다.

[14]:
# 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')
[14]:
../../_images/tutorials_circuits_advanced_02_operators_overview_22_0.png

위의 예제에서는 연산자를 Pauli 오브젝트에서 초기화한다. 그러나, Pauli 객체는 또한 회로 자체에 직접 삽입될 수 있고, 단일 큐비트(single-qubit) 파울리 게이트들의 시퀀스로 변환될 것이다.

[15]:
backend = BasicAer.get_backend('qasm_simulator')
circ = transpile(circ, backend, basis_gates=['u1','u2','u3','cx'])
job = backend.run(circ)
job.result().get_counts(0)
[15]:
{'11': 1024}
[16]:
# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli('XX'), [0, 1])
circ2.measure([0,1], [0,1])
circ2.draw()
[16]:
     ┌────────────┐┌─┐
q_0: ┤0           ├┤M├───
     │  Pauli(XX) │└╥┘┌─┐
q_1: ┤1           ├─╫─┤M├
     └────────────┘ ║ └╥┘
c: 2/═══════════════╩══╩═
                    0  1 

연산자 결합하기

연산자들은 다양한 방법으로 결합될 수 있다.

텐서 곱

두 연산자 \(A\)\(B\) 가 모두 단일 큐비트 연산자라면, A.tensor(B) = \(A\otimes B\) 를 하면 \(B\) 는 서브 시스템 0에, \(A\) 는 서브 시스템 1에 해당한다.

[17]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.tensor(B)
[17]:
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 이다. 따라서, 두 연산자 \(A\) = \(B\otimes A\) 이고, 이는 행렬 \(A\) 가 서브 시스템 1에 해당한다.

[18]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.expand(B)
[18]:
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))

합성

우리는 두 연산자 \(A\)\(B\) 를 또한 합성할 수 있다. 행렬 곱을 하려면 매서드 Operator.compose 가 사용된다. A.compose(B) 를 하게 되면 행렬 \(B.A\) 에 해당하는 연산자를 얻는다.

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

(composefront kwarg를 사용하면) \(A\) 앞에 \(B\) 를 적용함으로써 역순으로 합성을 할 수 있다. A.compose(B, front=True) = \(A.B\):

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

서브시스템 구성

이전 compose에서는 첫 번째 연산자 \(A\) 의 전체 출력 차원이 compose될 연산자 \(B\) 의 입력 차원과 일치해야 함을 주목하자. (마찬가지로, front=True 로 compose할 때는 \(B\) 의 출력 차원은 \(A\) 의 입력 차원과 같아야 한다).

또한 front=True 유무에 관계없이 compose 의 키워드 매개변수 qargs 를 사용하여 더 큰 연산자에서 하위 시스템을 선택하여 더 작은 연산자를 구성할 수 있다. 이 경우 구성중인 하위 시스템의 관련된 입력과 출력 차원이 일치해야 한다. 더 작은 연산자는 항상 compose 메서드의 인수여야 한다.

예를 들어, 3 큐비트 연산자를 사용하여 2 큐비트 게이트를 작성하려면:

[21]:
# Compose XZ with an 3-qubit identity operator
op = Operator(np.eye(2 ** 3))
XZ = Operator(Pauli('XZ'))
op.compose(XZ, qargs=[0, 2])
[21]:
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))
[22]:
# Compose YX in front of the previous operator
op = Operator(np.eye(2 ** 3))
YX = Operator(Pauli('YX'))
op.compose(XZ, qargs=[0, 2], front=True)
[22]:
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))

선형 결합

연산자는 또한 복소수의 가감산 및 스칼라 곱셈에 대한 표준 선형 연산자를 사용하여 결합될 수 있습니다.

[23]:
XX = Operator(Pauli('XX'))
YY = Operator(Pauli('YY'))
ZZ = Operator(Pauli('ZZ'))

op = 0.5 * (XX + YY - 3 * ZZ)
op
[23]:
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 는 유니터리가 되지만 선형결합은 그렇지 않다는 것이다. 때문에 두 유니터리 연산자의 합은 일반적으로 유니터리 연산자가 아니다.

[24]:
op.is_unitary()
[24]:
False

암묵적으로 연산자로 변환됨

다음과 같이 했을 때 두번째 객체가 Operator 객체가 아니라고 하더라도 매서드에서 자동으로 연산자로 바꿔준다. 따라서 행렬을 Operator 로 먼저 변환하지 않고 바로 넣어도 오류가 발생하지 않을 수 있다. 변환이 자동으로 되지 않는 경우 예외 처리 된다.

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

연산자의 비교

연산자는 두 연산자가 거의 동일한지 확인하는데 사용할 수 있는 등식 매서드를 제공한다.

[26]:
Operator(Pauli('X')) == Operator(XGate())
[26]:
True

이는 연산자의 각 행렬 성분들이 대략적으로 동일 한지를 확인한다. 전역 위상이 다른 두 유니터리는 다르게 간주한다.

[27]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[27]:
False

프로세스 정확도(Fidelity)

또한, Quantum Information 모듈의 process_fidelity 기능을 사용하여 연산자를 비교할 수도 있다. 이는 두 개의 양자 채널이 얼마나 유사한지를 나타내는 정보이론적 양이며 유니터리 연산자의 경우 글로벌 위상에는 의존하지 않는다.

[28]:
# 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가 아니면 예외 처리 된다.

[29]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.2
qiskit-aer0.10.4
qiskit-ignis0.7.1
qiskit-ibmq-provider0.19.1
qiskit0.36.2
System information
Python version3.9.9
Python compilerGCC 11.1.0
Python buildmain, Dec 29 2021 22:19:36
OSLinux
CPUs32
Memory (Gb)125.64821243286133
Wed Jun 22 15:52:11 2022 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: