참고
이 페이지는 tutorials/circuits_advanced/02_operators_overview.ipynb 로부터 생성되었다.
IBM 퀀텀 랩 에서 대화식으로 실행하시오.
연산자¶
[7]:
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute, 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 연산자를 작성하고자 한다면 다음과 같이 할 수 있다.
[8]:
XX = Operator([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]])
XX
[8]:
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)`` 을 반환한다. 이는 기반 행렬의 모양과 반대이다.
[9]:
XX.data
[9]:
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]])
[10]:
input_dim, output_dim = XX.dim
input_dim, output_dim
[10]:
(4, 4)
입력과 출력의 차원¶
연산자 클래스는 서브시스템의 차원을 추적하기 때문에 다중결합 연산자를 작성하는데 사용할 수 있다. 이 값은 input_dims
와``output_dims`` 함수를 이용하여 접근할 수 있다.
연산자의 행이 \(2^M\) 개이고 열이 \(2^M\) 개이면 입력과 출력 차원이 자동으로 M 큐빗과 N 큐빗으로 가정된다.
[11]:
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:이면
[12]:
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,)
입력과 출력 차원은 새 연산자를 초기화 해 줄 때 수동으로 명시해 줄 수 있다.
[13]:
# 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,)
[14]:
# 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_dims`` 와 output_dims
함수를 사용하여 일부 서브시스템들의 입력 혹은 출력 차원만을 추출할 수 있다.
[15]:
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
객체Gate
와Instruction
객체QuantumCircuits
객체
마지막 포인트는 시뮬레이터 백엔드를 호출하지 않고 양자 회로에 대한 최종 유니타리 행렬을 계산하기 위해 Operator
클래스를 유니타리 시뮬레이터로 사용할 수 있다는 것을 의미한다. 회로에 지원되지 않는 연산이 포함되어 있으면 예외가 발생합니다. 지원되지 않는 연산들은: 측정, 재설정, 조건부 연산 또는 ‘게이트를 행렬로 정의한다고 할 때, 행렬적 정의 또는 분해가 존재하지 않는 게이트들’ 입니다.
[16]:
# Create an Operator from a Pauli object
pauliXX = Pauli(label='XX')
Operator(pauliXX)
[16]:
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))
[19]:
# Create an Operator for a Gate object
Operator(CXGate())
[19]:
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))
[20]:
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
[20]:
Operator([[0.70710678+0.j , 0. -0.70710678j],
[0. -0.70710678j, 0.70710678+0.j ]],
input_dims=(2,), output_dims=(2,))
[21]:
# 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)
[21]:
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
를 반환한다.
[22]:
# Create an operator
XX = Operator(Pauli(label='XX'))
# Add to a circuit
circ = QuantumCircuit(2, 2)
circ.append(XX, [0, 1])
circ.measure([0,1], [0,1])
circ.draw('mpl')
[22]:

위의 예제에서는 연산자를 Pauli
오브젝트에서 초기화한다. 그러나, Pauli
객체는 또한 회로 자체에 직접 삽입될 수 있고, 단일 큐비트(single-qubit) 파울리 게이트들의 시퀀스로 변환될 것이다.
[23]:
backend = BasicAer.get_backend('qasm_simulator')
job = execute(circ, backend, basis_gates=['u1','u2','u3','cx'])
job.result().get_counts(0)
[23]:
{'11': 1024}
[24]:
# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli(label='XX'), [0, 1])
circ2.measure([0,1], [0,1])
circ2.draw()
[24]:
┌───────────┐┌─┐ q_0: ┤0 ├┤M├─── │ Pauli:XX │└╥┘┌─┐ q_1: ┤1 ├─╫─┤M├ └───────────┘ ║ └╥┘ c: 2/══════════════╩══╩═ 0 1
연산자 결합하기¶
연산자들은 다양한 방법으로 결합될 수 있다.
텐서 곱¶
두 연산자 \(A\) 와 \(B\) 는 함수 Operator.tensor
를 사용하여 텐서곱 연산자 \(A\otimes B\) 로 결합할 수 있다. 만약 A와 B가 단일 큐빗 연산자라면 A.tensor(B)
= \(A\otimes B\) 를 하면 \(B\) 는 서브시스템 0에 \(A\) 는 서브시스템 1에 해당한다.
[25]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.tensor(B)
[25]:
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\) 에 대해 A.expand(B)
= \(B\otimes A\) 이다. 여기서 행렬 A는 서브시스템 0에 \(B\) 는 서브시스템 1에 해당한다.
[26]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.expand(B)
[26]:
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\) 에 해당하는 연산자를 얻는다.
[27]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.compose(B)
[27]:
Operator([[ 0.+0.j, 1.+0.j],
[-1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))
(compose
의 front
kwarg를 사용하면) \(A\) 앞에 \(B\) 를 적용함으로써 역순으로 합성을 할 수 있다. A.compose(B, front=True)
= \(A.B\):
[28]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.compose(B, front=True)
[28]:
Operator([[ 0.+0.j, -1.+0.j],
[ 1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))
서브시스템 구성¶
이전 compose에서는 첫 번째 연산자 :math:’A 의 전체 출력 차원이 compose될 연산자 \(B\) 의 입력 차원과 일치해야 함을 주목하자. (마찬가지로, front=True
로 compose할 때는 \(B\) 의 출력 차원은 \(A\) 의 입력 차원과 같아야 한다).
또한 front=True
를 사용하거나 사용하지 않고 compose
의 qargs
kwarg를 사용하여 더 큰 연산자에서 하위 시스템을 선택하여 작은 연산자를 구성할 수 있다. 이 경우 구성 중인 하위 시스템의 관련 입력 및 출력의 차원이 일치해야 한다. 작은 연산자는 항상 ``compose`` 메서드의 인수여야 함에 주의할 것.
예를 들어, 3 큐비트 연산자를 사용하여 2 큐비트 게이트를 작성하려면:
[29]:
# Compose XZ with an 3-qubit identity operator
op = Operator(np.eye(2 ** 3))
XZ = Operator(Pauli(label='XZ'))
op.compose(XZ, qargs=[0, 2])
[29]:
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))
[30]:
# Compose YX in front of the previous operator
op = Operator(np.eye(2 ** 3))
YX = Operator(Pauli(label='YX'))
op.compose(XZ, qargs=[0, 2], front=True)
[30]:
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))
선형 결합¶
연산자는 또한 복소수의 가감산 및 스칼라 곱셈에 대한 표준 선형 연산자를 사용하여 결합될 수 있습니다.
[31]:
XX = Operator(Pauli(label='XX'))
YY = Operator(Pauli(label='YY'))
ZZ = Operator(Pauli(label='ZZ'))
op = 0.5 * (XX + YY - 3 * ZZ)
op
[31]:
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
는 유니터리가 되지만 선형결합은 그렇지 않다는 것이다. 때문에 두 유니터리 연산자의 합은 일반적으로 유니터리 연산자가 아니다.
[32]:
op.is_unitary()
[32]:
False
암묵적으로 연산자로 변환됨¶
다음과 같이 했을 때 두번째 객체가 Operator
객체가 아니라고 하더라도 매서드에서 자동으로 연산자로 바꿔준다. 따라서 행렬을 Operator
로 먼저 변환하지 않고 바로 넣어도 오류가 발생하지 않을 수 있다. 변환이 자동으로 되지 않는 경우 예외 처리 된다.
[33]:
# Compose with a matrix passed as a list
Operator(np.eye(2)).compose([[0, 1], [1, 0]])
[33]:
Operator([[0.+0.j, 1.+0.j],
[1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))
연산자의 비교¶
연산자는 두 연산자가 거의 동일한지 확인하는데 사용할 수 있는 등식 매서드를 제공한다.
[34]:
Operator(Pauli(label='X')) == Operator(XGate())
[34]:
True
이는 연산자의 각 행렬 성분들이 대략적으로 동일 한지를 확인한다. 전역 위상이 다른 두 유니터리는 다르게 간주한다.
[35]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[35]:
False
프로세스 정확도(Fidelity)¶
또한, Quantum Information 모듈의 process_fidelity
기능을 사용하여 연산자를 비교할 수도 있다. 이는 두 개의 양자 채널이 얼마나 유사한지를 나타내는 정보이론적 양이며 유니터리 연산자의 경우 글로벌 위상에는 의존하지 않는다.
[36]:
# 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가 아니면 예외 처리 된다.
[37]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
Qiskit | None |
Terra | 0.14.0 |
Aer | 0.6.0 |
Ignis | 0.3.0 |
Aqua | 0.7.0 |
IBM Q Provider | 0.6.1 |
System information | |
Python | 3.7.7 (default, Mar 26 2020, 10:32:53) [Clang 4.0.1 (tags/RELEASE_401/final)] |
OS | Darwin |
CPUs | 4 |
Memory (Gb) | 16.0 |
Wed Apr 29 12:36:22 2020 EDT |
This code is a part of Qiskit
© Copyright IBM 2017, 2020.
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.
[ ]: