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

참고

이 페이지는 tutorials/operators/01_operator_flow.ipynb 에서 생성되었다.

연산자 플로우 (Operator Flow)

소개

Qiskit은 상태와 연산자와 그들의 합, 텐서 곱, 그리고 그들의 합성을 나타내는 클래스들을 제공한다. 이런 대수적 구조들은 연산자들을 나타내는 표현을 빌드할 수 있도록 해 준다.

Pauli 연산자(Pauli operators)로부터 빌드되는 표현을 소개한다. 후속 절에서는 연산자와 상태, 그들이 어떻게 표현되는지, 그리고 그들로 무엇을 할 수 있는지 더 상세하게 알아본다. 마지막 절에서는 해밀토니안을 따라 변화(time-evolution)하는 상태를 구성하고, 관측가능량(observable)의 기댓값을 계산할 것이다.

Pauli 연산자, 합계, 합성 및 텐서 곱

가장 중요한 기반 연산자는 Pauli 연산자이다. Pauli 연산자는 다음과 같이 표현된다.

[1]:
from qiskit.opflow import I, X, Y, Z
print(I, X, Y, Z)
I X Y Z

이 연산자들은 계수 또한 가져갈 수 있다.

[2]:
print(1.5 * I)
print(2.5 * X)
1.5 * I
2.5 * X

이 계수들은 연산자들을 합계의 항들로 사용할 수 있도록 해준다.

[3]:
print(X + 2.0 * Y)
1.0 * X
+ 2.0 * Y

텐서 곱은 이와 같이 캐럿(caret)으로 표시된다.

[4]:
print(X^Y^Z)
XYZ

합성은 @ 기호로 표시된다.

[5]:
print(X @ Y @ Z)
iI

앞의 두 예에서, Pauli 연산자들의 텐서 곱과 합성은 즉각 동등한 (다수의 큐비트일 가능성이 있는) Pauli 연산자로 줄여 표현되었다. 텐서 곱과 합성이 포함된 더 복잡한 객체의 경우, 결과는 평가 전의 연산들(unevaluated operations)을 나타내는 객체가 된다. 즉, 대수적 표현으로 나타난다.

예를 들어, 두 합의 합성은 다음을 주고

[6]:
print((X + Y) @ (Y + Z))
1j * Z
+ -1j * Y
+ 1.0 * I
+ 1j * X

두 합의 텐서 곱은 다음을 준다

[7]:
print((X + Y) ^ (Y + Z))
1.0 * XY
+ 1.0 * XZ
+ 1.0 * YY
+ 1.0 * YZ

Let’s take a closer look at the types introduced above. First the Pauli operators.

[8]:
(I, X)
[8]:
(PauliOp(Pauli('I'), coeff=1.0), PauliOp(Pauli('X'), coeff=1.0))

각 Pauli 연산자는 PauliOp 인스턴스로써 qiskit.quantum_info.Pauli 인스턴스를 래핑하고, 계수 coeff 를 갖는다. 일반적으로 PauliOp 는 Pauli 연산자의 가중 텐서 곱을 나타낸다.

[9]:
2.0 * X^Y^Z
[9]:
PauliOp(Pauli('XYZ'), coeff=2.0)

Pauli 연산자를 불리언 값 쌍으로 인코딩하려면 qiskit.quantum_info.Pauli 문서를 참조하라.

All of the objects representing operators, whether as 《primitive》s such as PauliOp, or algebraic expressions carry a coefficient

[10]:
print(1.1 * ((1.2 * X)^(Y + (1.3 * Z))))
1.2 * (
  1.1 * XY
  + 1.4300000000000002 * XZ
)

In the following we take a broader and deeper look at Qiskit’s operators, states, and the building blocks of quantum algorithms.

파트 I: 상태 함수 및 측정

양자 상태는 StateFn 클래스의 하위 클래스로 표현될 수 있다. 양자 상태를 표현하는 4개의 하위 클래스는 다음과 같다: DictStateFndict 를 기반으로 한 계산 기저의 희소 표현이다. VectorStateFn 은 NumPy array를 기반으로 한 계산 기저의 밀집 표현이다. CircuitStateFn 은 회로를 기반으로, 계산 기저가 전부 0인 상태를 회로에 수행시켜 얻은 상태를 나타낸다. OperatorStateFn 은 밀도 행렬로 표현한 섞인 상태를 나타낸다. (나중에 확인하겠지만, OperatorStateFn 은 관측량을 나타낼 때도 사용된다.)

몇 개의 StateFn 인스턴스가 편의를 위해 제공된다. 예를 들어, Zero, One, Plus, Minus 이다.

[11]:
from qiskit.opflow import (StateFn, Zero, One, Plus, Minus, H,
                           DictStateFn, VectorStateFn, CircuitStateFn, OperatorStateFn)

ZeroOne 은 각각 양자 상태 \(|0\rangle\)\(|1\rangle\) 을 의미한다. 이들을 DictStateFn 을 통해 표현된다.

[12]:
print(Zero, One)
DictStateFn({'0': 1}) DictStateFn({'1': 1})

\((|0\rangle + |1\rangle)/\sqrt{2}\)\((|0\rangle - |1\rangle)/\sqrt{2}\) 상태를 표현하는 PlusMinus 가 회로를 통해 표현되었다. HPlus 를 나타내는 동의어이다.

[13]:
print(Plus, Minus)
CircuitStateFn(
   ┌───┐
q: ┤ H ├
   └───┘
) CircuitStateFn(
   ┌───┐┌───┐
q: ┤ X ├┤ H ├
   └───┘└───┘
)

양자 상태들의 인덱싱은 eval 메서드로 수행할 수 있다. 아래의 코드는 01 의 기저 상태의 계수를 반환하는 예제이다. (아래에서 eval 메서드가 다른 계산에 이용되는 것도 확인할 것이다.)

[14]:
print(Zero.eval('0'))
print(Zero.eval('1'))
print(One.eval('1'))
print(Plus.eval('0'))
print(Minus.eval('1'))
1.0
0.0
1.0
(0.7071067811865475+0j)
(-0.7071067811865475+8.7e-17j)

양자 상태의 쌍대 벡터 (dual vector), 즉 주어진 ket 에 해당하는 braadjoint 메서드를 통해 얻어진다. StateFnis_measurement 라는 플래그를 가지고 있는데, 이것은 해당 객체가 ket이면 False 이고 bra 이면 True 상태가 된다.

여기서 \(\langle 1 |\) 을 구성한다.

[15]:
One.adjoint()
[15]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)

편의상, 이와 같이 틸데(tilde)를 이용하여 쌍대 벡터를 얻을 수 있다.

[16]:
~One
[16]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)

대수적 연산자들과 술어

StateFn들 간에 다양한 대수적 연산과 술어가 지원된다:

  • + - 덧셈

  • - - 뺄셈, 부정 (-1을 스칼라 곱)

  • * - 스칼라 곱셈

  • / - 스칼라 나눗셈

  • @ - 합성

  • ^ - 텐서 곱 또는 텐서 지수 (n 제곱 텐서)

  • ** - 합성 지수 (n번 합성)

  • == - 상등

  • ~ - 수반, State Function 과 Measurement 사이에서 변동

이 연산자들은 Python rules for operator precedence 을 따르며, 수학적으로 예상되는 결과가 아닐 수 있음에 주의하라. 예를 들어, I^X + X^I 는 정확히 I ^ (X + X) ^ I == 2 * (I^X^I) 에 해당하며 Python 은 ^ 전에 + 를 먼저 연산한다. 이런 경우에는 메서드(.tensor(), 등)나 괄호를 사용할 수 있다.

StateFn들은 계수를 지니고 있다. 이는 다중 상태들을 스칼라로 곱을 하여 합계를 만들 수 있게 해준다.

여기서는 \((2 + 3i)|0\rangle\) 을 구성한다.

[17]:
(2.0 + 3.0j) * Zero
[17]:
DictStateFn({'0': 1}, coeff=(2+3j), is_measurement=False)

여기에서는 두 개의 DictStateFn 을 더하는 것이 동일한 유형의 객체를 반환하는 것을 볼 수 있다: \(|0\rangle + |1\rangle\)

[18]:
print(Zero + One)
DictStateFn({'0': 1.0, '1': 1.0})

상태를 수동으로 정규화해야 하는 것에 유의하라. 예를 들어, \((|0\rangle + |1\rangle)/\sqrt{2}\) 를 구성하고자 아래와 같이 작성할 수 있다

[19]:
import math

v_zero_one = (Zero + One) / math.sqrt(2)
print(v_zero_one)
DictStateFn({'0': 1.0, '1': 1.0}) * 0.7071067811865475

다른 경우에는 결과가 합계의 심볼릭 표현으로 쓰여진다. 예를 들어, \(|+\rangle + |-\rangle\) 의 표현은 다음과 같다.

[20]:
print(Plus + Minus)
SummedOp([
  CircuitStateFn(
     ┌───┐
  q: ┤ H ├
     └───┘
  ),
  CircuitStateFn(
     ┌───┐┌───┐
  q: ┤ X ├┤ H ├
     └───┘└───┘
  )
])

합성 연산자는 내적을 수행할 때 사용될 수 있으며, 내적 값이 계산되지 않은 상태로 초기화된다. \(\langle 1 | 1 \rangle\) 의 표현은 다음과 같다.

[21]:
print(~One @ One)
ComposedOp([
  DictMeasurement({'1': 1}),
  DictStateFn({'1': 1})
])

is_measurement 플래그는 (브라) 상태 ~OneDictMeasurement 에 출력되도록 했다.

심볼릭 표현은 eval 메서드로 값을 구할 수 있다.

[22]:
(~One @ One).eval()
[22]:
1.0
[23]:
(~v_zero_one @ v_zero_one).eval()
[23]:
0.9999999999999998

여기 \(\langle - | 1 \rangle = \langle (\langle 0| - \langle 1|)/\sqrt{2} | 1\rangle\) 이 있다.

[24]:
(~Minus @ One).eval()
[24]:
(-0.7071067811865475-8.7e-17j)

합성 연산자 @compose 메서드를 호출하는 것과 동등하다.

[25]:
print((~One).compose(One))
ComposedOp([
  DictMeasurement({'1': 1}),
  DictStateFn({'1': 1})
])

내적은 ComposedOp 를 통해 구성하지 않고도 eval 메서드를 통해 바로 계산하는 것도 가능하다.

[26]:
(~One).eval(One)
[26]:
1.0

심볼릭 텐서 곱은 다음과 같이 만들어질 수 있다. 여기서는 \(|0\rangle \otimes |+\rangle\) 의 경우이다.

[27]:
print(Zero^Plus)
TensoredOp([
  DictStateFn({'0': 1}),
  CircuitStateFn(
     ┌───┐
  q: ┤ H ├
     └───┘
  )
])

이는 간단한 (합성되지 않은) CircuitStateFn 로 표현될 수 있다.

[28]:
print((Zero^Plus).to_circuit_op())
CircuitStateFn(
     ┌───┐
q_0: ┤ H ├
     └───┘
q_1: ─────

)

텐서 파워는 탈자 기호 ^ 를 사용하여 다음과 같이 구성된다. 여기 \(600 (|11111\rangle + |00000\rangle)\)\(|10\rangle^{\otimes 3}\) 이 있다.

[29]:
print(600 * ((One^5) + (Zero^5)))
print((One^Zero)^3)
DictStateFn({'11111': 1.0, '00000': 1.0}) * 600.0
DictStateFn({'101010': 1})

to_matrix_opVectorStateFn 으로 변환된다.

[30]:
print(((Plus^Minus)^2).to_matrix_op())
print(((Plus^One)^2).to_circuit_op())
print(((Plus^One)^2).to_matrix_op().sample())
VectorStateFn(Statevector([ 0.25-6.1e-17j, -0.25+6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j, -0.25+6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j, -0.25+6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j, -0.25+6.1e-17j,
              0.25-6.1e-17j],
            dims=(2, 2, 2, 2)))
CircuitStateFn(
     ┌───┐
q_0: ┤ X ├
     ├───┤
q_1: ┤ H ├
     ├───┤
q_2: ┤ X ├
     ├───┤
q_3: ┤ H ├
     └───┘
)
{'1101': 0.2734375, '0111': 0.2509765625, '1111': 0.24609375, '0101': 0.2294921875}

StateFn을 생성하는 것은 쉽다. StateFn 은 팩토리로도 제공되며, 어떤 원시 자료형이라도 생성자에서 받을 수 있으며, 정확한 StateFn 하위 클래스를 반환할 수 있다. 현재 다음과 같은 원시 자료형을 생성자로 전달할 수 있으며, 이는 그들이 생성하는 StateFn 하위 클래스들과 나란히 나열되어 있다.

  • str (일부 기저 비트 문자열과 동일) -> DictStateFn

  • dict -> DictStateFn

  • Qiskit Result object -> DictStateFn

  • list -> VectorStateFn

  • np.ndarray -> VectorStateFn

  • Statevector -> VectorStateFn

  • QuantumCircuit -> CircuitStateFn

  • Instruction -> CircuitStateFn

  • OperatorBase -> OperatorStateFn

[31]:
print(StateFn({'0':1}))
print(StateFn({'0':1}) == Zero)

print(StateFn([0,1,1,0]))

from qiskit.circuit.library import RealAmplitudes
print(StateFn(RealAmplitudes(2)))
DictStateFn({'0': 1})
True
VectorStateFn(Statevector([0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j],
            dims=(2, 2)))
CircuitStateFn(
     ┌──────────────────────────────────────────────────────────┐
q_0: ┤0                                                         ├
     │  RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │
q_1: ┤1                                                         ├
     └──────────────────────────────────────────────────────────┘
)

파트 II: PrimitiveOp

기본 연산자들은 PrimitiveOp 의 하위 클래스이다. StateFn 과 마찬가지로 PrimitiveOp 는 주어진 원시 자료형에 올바른 자료형을 생성하기 위한 팩토리이기도 하다. 지금은 다음의 원시 자료형들이 생성자를 통해 PrimitiveOp 의 하위 클래스로서 만들어 질 수 있다.

  • Terra’s Pauli -> PauliOp

  • Instruction -> CircuitOp

  • QuantumCircuit -> CircuitOp

  • 2d List -> MatrixOp

  • np.ndarray -> MatrixOp

  • spmatrix -> MatrixOp

  • Terra’s quantum_info.Operator -> MatrixOp

[32]:
from qiskit.opflow import X, Y, Z, I, CX, T, H, S, PrimitiveOp

행렬 원소

eval 메서드는 연산자의 열을 반환한다. 예를 들어서 파울리 \(X\) 연산자는 PauliOp 로 표현되어 있다. 열을 요청하는 것은 객체의 희소 표현 DictStateFn 을 반환한다.

[33]:
X
[33]:
PauliOp(Pauli('X'), coeff=1.0)
[34]:
print(X.eval('0'))
DictStateFn({'1': (1+0j)})

연산자에 인덱싱을 하는 것, 즉 행렬의 원소를 얻는 것은 eval 메서드를 두 번 호출하는 것으로 수행할 수 있다.

행렬 \(X = \left(\begin{matrix} 0 & 1 \\ 1 & 0 \end{matrix} \right)\) 이 주어지면, 행렬의 원소 \(\left\{X \right\}_{0,1}\) 는 다음과 같이 구할 수 있다.

[35]:
X.eval('0').eval('1')
[35]:
(1+0j)

여기에 회로로 표현된 두 큐비트 연산자 CX (제어 X) 를 사용한 예시가 있다.

[36]:
print(CX)
print(CX.to_matrix().real) # The imaginary part vanishes.

q_0: ──■──
     ┌─┴─┐
q_1: ┤ X ├
     └───┘
[[1. 0. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]]
[37]:
CX.eval('01')  # 01 is the one in decimal. We get the first column.
[37]:
VectorStateFn(Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
            dims=(2, 2)), coeff=1.0, is_measurement=False)
[38]:
CX.eval('01').eval('11')  # This returns element with (zero-based) index (1, 3)
[38]:
(1+0j)

상태 벡터에 연산자 적용하기

상태 벡터에 연산자를 가하는 것은 compose 메서드를 통해 할 수 있다 (@ 연산자도 가능하다). \(X | 1 \rangle = |0\rangle\) 라는 표현은 다음과 같다.

[39]:
print(X @ One)
ComposedOp([
  X,
  DictStateFn({'1': 1})
])

더 간단한 표현으로, \(|0\rangle\) 표현은 eval 메서드로 얻을 수 있다.

[40]:
(X @ One).eval()
[40]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)

eval 을 직접적으로 사용함으로써 중간 단계인 ComposedOp 을 건너뛸 수 있다.

[41]:
X.eval(One)
[41]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)

연산자의 합성 및 텐서곱은 @^ 에 영향을 받는다. 여기 몇가지 예시들이 있다.

[42]:
print(((~One^2) @ (CX.eval('01'))).eval())

print(((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2)
print((((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2) @ (Minus^5))
print(((H^I^I)@(X^I^I)@Zero))
(1+0j)
          ┌───┐          ┌───┐
q_0: ──■──┤ H ├───────■──┤ H ├─────
     ┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐
q_1: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_2: ──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├
     ┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_3: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_4: ─────┤ X ├┤ H ├─────┤ X ├┤ H ├
          └───┘└───┘     └───┘└───┘
CircuitStateFn(
     ┌───┐┌───┐     ┌───┐          ┌───┐
q_0: ┤ X ├┤ H ├──■──┤ H ├───────■──┤ H ├─────
     ├───┤├───┤┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐
q_1: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_2: ┤ X ├┤ H ├──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├
     ├───┤├───┤┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_3: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_4: ┤ X ├┤ H ├─────┤ X ├┤ H ├─────┤ X ├┤ H ├
     └───┘└───┘     └───┘└───┘     └───┘└───┘
)
CircuitStateFn(
     ┌─────────────┐
q_0: ┤0            ├─────
     │             │
q_1: ┤1 Pauli(XII) ├─────
     │             │┌───┐
q_2: ┤2            ├┤ H ├
     └─────────────┘└───┘
)
[43]:
print(~One @ Minus)
ComposedOp([
  DictMeasurement({'1': 1}),
  CircuitStateFn(
     ┌───┐┌───┐
  q: ┤ X ├┤ H ├
     └───┘└───┘
  )
])

파트 III: ListOp 및 서브클래스

ListOp

ListOp 는 효과적으로 벡터화된 연산자들의 컨테이너로써 연산자와 상태들의 리스트에 가해진다.

[44]:
from qiskit.opflow import ListOp

print((~ListOp([One, Zero]) @ ListOp([One, Zero])))
ComposedOp([
  ListOp([
    DictMeasurement({'1': 1}),
    DictMeasurement({'0': 1})
  ]),
  ListOp([
    DictStateFn({'1': 1}),
    DictStateFn({'0': 1})
  ])
])

예를 들어, 위의 합성은 단순화 메서드 reduce 를 사용하여 리스트 (ListOp) 위에 분배된다.

[45]:
print((~ListOp([One, Zero]) @ ListOp([One, Zero])).reduce())
ListOp([
  ListOp([
    ComposedOp([
      DictMeasurement({'1': 1}),
      DictStateFn({'1': 1})
    ]),
    ComposedOp([
      DictMeasurement({'1': 1}),
      DictStateFn({'0': 1})
    ])
  ]),
  ListOp([
    ComposedOp([
      DictMeasurement({'0': 1}),
      DictStateFn({'1': 1})
    ]),
    ComposedOp([
      DictMeasurement({'0': 1}),
      DictStateFn({'0': 1})
    ])
  ])
])

ListOp의 종류: SummedOp, ComposedOp, TensoredOp

ListOp, introduced above, is useful for vectorizing operations. But, it also serves as the superclass for list-like composite classes. If you’ve already played around with the above, you’ll notice that you can easily perform operations between OperatorBases which we may not know how to perform efficiently in general (or simply haven’t implemented an efficient procedure for yet), such as addition between CircuitOps. In those cases, you may receive a ListOp result (or subclass thereof) from your operation representing the lazy execution of the operation. For example, if you attempt to add together a DictStateFn and a CircuitStateFn, you’ll receive a SummedOp representing the sum of the two. This composite State function still has a working eval (but may need to perform a non-scalable computation under the hood, such as converting both to vectors).

이렇게 합성된 OperatorBase들은 더 복잡하고 풍부한 계산을 할 때 PrimitiveOpStateFn 빌딩 블록에서 벗어날 수 있게 한다.

모든 ListOp 는 다음과 같은 네 가지 속성을 갖고 있다:

  • oplist - 항과 인자 등을 나타내는 OperatorBase 의 리스트.

  • combo_fn - 복소수들의 리스트를 받아 oplist 의 항목의 결과를 어떻게 결합시킬지를 정의하는 출력값을 내는 함수. 브로드캐스팅을 단순하게 하고자 NumPy의 행렬을 받도록 정의되어 있다.

  • coeff - 원시 값을 곱하는 계수. coeff 는 정수, 소수, 복소수, 또는 나중에 my_op.bind_parameters 를 사용하여 바인드되는 (테라의 qiskit.circuit 에 있는) 자유로운 Parameter 객체일 수도 있다는 점을 유의하라.

  • abelian - oplist 의 연산자가 상호 통근하는 것으로 알려져 있는지 표시한다(일반적으로 AbelianGrouper 변환기에 의해 변환된 후에 설정된다).

ListOp 는 일반적인 시퀀스 오버로드를 지원하므로, oplist 에 있는 OperatorBase 들에 접근할 때 my_op[4] 와 같은 인덱싱을 사용할 수 있다.

OperatorStateFn

위에서 OperatorStateFn 은 밀도 연산자를 나타낸다고 했다. 그러나 is_measurement 플래그가 True 일 경우, OperatorStateFn 은 관측가능량을 나타낸다. 이 관측가능량의 기댓값은 ComposedOp 를 통해 만들 수 있다. 또는 eval 을 직접 이용할 수도 있다. is_measurement 플래그(특성)는 adjoint 매서드로 설정된다는 것을 기억하라.

여기서는 Pauli \(Z\) 연산자에 해당하는 관측가능량을 구성한다. 프린트를 할 때는 OperatorMeasurement 로 불리는 것을 주의하라.

[46]:
print(StateFn(Z).adjoint())
StateFn(Z).adjoint()
OperatorMeasurement(Z)
[46]:
OperatorStateFn(PauliOp(Pauli('Z'), coeff=1.0), coeff=1.0, is_measurement=True)

이번에는 \(\langle 0 | Z | 0 \rangle\), \(\langle 1 | Z | 1 \rangle\), 그리고 \(\langle + | Z | + \rangle\) 를 계산할 것이다. 이 때 \(|+\rangle = (|0\rangle + |1\rangle)/\sqrt{2}\) 이다.

[47]:
print(StateFn(Z).adjoint().eval(Zero))
print(StateFn(Z).adjoint().eval(One))
print(StateFn(Z).adjoint().eval(Plus))
(1+0j)
(-1+0j)
0j

파트 IV: 변환기 (Converters)

Converters are classes that manipulate operators and states and perform building blocks of algorithms. Examples include changing the basis of operators and Trotterization. Converters traverse an expression and perform a particular manipulation or replacement, defined by the converter’s convert() method, of the Operators within. Typically, if a converter encounters an OperatorBase in the recursion which is irrelevant to its conversion purpose, that OperatorBase is left unchanged.

[48]:
import numpy as np
from qiskit.opflow import I, X, Y, Z, H, CX, Zero, ListOp, PauliExpectation, PauliTrotterEvolution, CircuitSampler, MatrixEvolution, Suzuki
from qiskit.circuit import Parameter
from qiskit import Aer

시간 변화, exp_i(), 그리고 EvolvedOp

Every PrimitiveOp and ListOp has an .exp_i() function such that H.exp_i() corresponds to \(e^{-iH}\). In practice, only a few of these Operators have an efficiently computable exponentiation (such as MatrixOp and the PauliOps with only one non-identity single-qubit Pauli), so we need to return a placeholder, or symbolic representation, (similar to how SummedOp is a placeholder when we can’t perform addition). This placeholder is called EvolvedOp, and it holds the OperatorBase to be exponentiated in its .primitive property.

Qiskit operators fully support parameterization, so we can use a Parameter for our evolution time here. Notice that there’s no 《evolution time》 argument in any function. The Operator flow exponentiates whatever operator we tell it to, and if we choose to multiply the operator by an evolution time, \(e^{-iHt}\), that will be reflected in our exponentiation parameters.

Pauli 연산자의 가중합

멀티 큐비트 Pauli 연산자들의 선형 결합으로 표현되는 해밀토니안은 이와 같이 구성될 수 있다.

[49]:
two_qubit_H2 =  (-1.0523732 * I^I) + \
                (0.39793742 * I^Z) + \
                (-0.3979374 * Z^I) + \
                (-0.0112801 * Z^Z) + \
                (0.18093119 * X^X)

two_qubit_H2PauliOp들의 항을 갖는 SummedOp 로 표현된다.

[50]:
print(two_qubit_H2)
-1.0523732 * II
+ 0.39793742 * IZ
- 0.3979374 * ZI
- 0.0112801 * ZZ
+ 0.18093119 * XX

다음에는 해밀토니안에 Parameter 를 곱할 것이다. 이 ParameterSummedOpcoeff 속성에 저장되어 있다. exp_i() 를 결과에 호출하면 그것을 지수 표현인 EvolvedOp 에 감싼다.

[51]:
evo_time = Parameter('θ')
evolution_op = (evo_time*two_qubit_H2).exp_i()
print(evolution_op) # Note, EvolvedOps print as exponentiations
print(repr(evolution_op))
e^(-i*1.0*θ * (
  -1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX
))
EvolvedOp(PauliSumOp(SparsePauliOp(['II', 'IZ', 'ZI', 'ZZ', 'XX'],
              coeffs=[-1.0523732 +0.j,  0.39793742+0.j, -0.3979374 +0.j, -0.0112801 +0.j,
  0.18093119+0.j]), coeff=1.0*θ), coeff=1.0)

two_qubit_H2 를 관측가능량으로 나타내는 h2_measurement 를 구성할 것이다.

[52]:
h2_measurement = StateFn(two_qubit_H2).adjoint()
print(h2_measurement)
OperatorMeasurement(-1.0523732 * II
+ 0.39793742 * IZ
- 0.3979374 * ZI
- 0.0112801 * ZZ
+ 0.18093119 * XX)

\(\text{CX} (H\otimes I) |00\rangle\) 를 통해 Bell 상태 \(|\Phi_+\rangle\) 를 구축한다.

[53]:
bell = CX @ (I ^ H) @ Zero
print(bell)
CircuitStateFn(
     ┌───┐
q_0: ┤ H ├──■──
     └───┘┌─┴─┐
q_1: ─────┤ X ├
          └───┘
)

여기서는 \(H e^{-iHt} |\Phi_+\rangle\) 표현이 있다.

[54]:
evo_and_meas = h2_measurement @ evolution_op @ bell
print(evo_and_meas)
ComposedOp([
  OperatorMeasurement(-1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX),
  e^(-i*1.0*θ * (
    -1.0523732 * II
    + 0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ
    + 0.18093119 * XX
  )),
  CircuitStateFn(
       ┌───┐
  q_0: ┤ H ├──■──
       └───┘┌─┴─┐
  q_1: ─────┤ X ├
            └───┘
  )
])

대체로 \(e^{-iHt}\) 에 트로터리제이션을 적용하는 PauliTrotterEvolutionconvert 메서드로 얻을 수 있다. 여기서는 PauliTrotterEvolution 을 사용했지만, 같은 지수화를 수행하는 MatrixEvolution 과 같은 다양한 방법들도 있다.

[55]:
trotterized_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=1)).convert(evo_and_meas)
# We can also set trotter_mode='suzuki' or leave it empty to default to first order Trotterization.
print(trotterized_op)
ComposedOp([
  OperatorMeasurement(-1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX),
  CircuitStateFn(
  global phase: 1.0523732*θ
       ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
  q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
       └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
  q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
            └───┘└───┘                              └───┘     »
  «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
  «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
  «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
  «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
  «                              └──────────────────┘└──────────────────┘     »
  «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐
  «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├
  «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤
  «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├
  «                              └───┘                              └───┘
  )
])

trotterized_opParameter 를 포함하고 있다. bind_parameters 메서드는 표현을 오가며 dict 로 지정된 매개변수 이름과 해당하는 값을 바인딩 한다. 여기서는 파라미터가 1개 뿐인 경우이다.

[56]:
bound = trotterized_op.bind_parameters({evo_time: .5})

bound is a ComposedOp. The second factor is the circuit. Let’s draw it to verify that the binding has taken place.

[57]:
bound[1].to_circuit().draw()
[57]:
global phase: 0.52619
     ┌───┐     ┌───┐┌───┐┌─────────────────┐┌───┐┌───┐┌───┐┌─────────────────┐»
q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.090465595) ├┤ X ├┤ H ├┤ X ├┤ Rz(-0.00564005) ├»
     └───┘┌─┴─┐├───┤└─┬─┘└─────────────────┘└─┬─┘├───┤└─┬─┘└─────────────────┘»
q_1: ─────┤ X ├┤ H ├──■───────────────────────■──┤ H ├──■─────────────────────»
          └───┘└───┘                             └───┘                        »
«     ┌───┐┌────────────────┐┌────────────────┐┌───┐┌─────────────────┐┌───┐»
«q_0: ┤ X ├┤ Rz(0.19896871) ├┤ Rz(0.19896871) ├┤ X ├┤ Rz(-0.00564005) ├┤ X ├»
«     └─┬─┘├────────────────┤├────────────────┤└─┬─┘└─────────────────┘└─┬─┘»
«q_1: ──■──┤ Rz(-0.1989687) ├┤ Rz(-0.1989687) ├──■───────────────────────■──»
«          └────────────────┘└────────────────┘                             »
«     ┌───┐┌───┐┌─────────────────┐┌───┐┌───┐
«q_0: ┤ H ├┤ X ├┤ Rz(0.090465595) ├┤ X ├┤ H ├
«     ├───┤└─┬─┘└─────────────────┘└─┬─┘├───┤
«q_1: ┤ H ├──■───────────────────────■──┤ H ├
«     └───┘                             └───┘

기댓값

Expectation들은 관측가능량들의 기댓값을 계산할 수 있는 변환기이다. 이들은 연산자 트리를 오가며 OperatorStateFn들(관측가능량)을 양자 혹은 고전 하드웨어에서의 계산에 적합하도록 동등한 명령어로 대체한다. 예를 들어서, 어떤 상태 함수에 대한 Pauli 연산자들의 합으로 표현된 연산자 o 의 기댓값을 측정하고 싶다면, 양자 하드웨어에서는 오직 대각 성분 측정에만 접근할 수 있으므로, 관측가능량 ~StateFn(o) 을 생성하고 PauliExpectation 을 사용하여 그것을 대각 성분 측정으로 변환하고 회전 전 회로를 상태에 추가할 수 있다.

다른 흥미로운 ExpectationAerPauliExpectation 으로, 관측가능량을 Aer 이 고성능으로 처리할 수 있는 특별한 기댓값 스냅샷을 가진 CircuitStateFn 으로 변환한다.

[58]:
# Note that XX was the only non-diagonal measurement in our H2 Observable
print(PauliExpectation(group_paulis=False).convert(h2_measurement))
SummedOp([
  ComposedOp([
    OperatorMeasurement(-1.0523732 * II),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(-0.3979374 * ZI),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(-0.0112801 * ZZ),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ),
         ┌───┐
    q_0: ┤ H ├
         ├───┤
    q_1: ┤ H ├
         └───┘
  ])
])

기본값 키워드 group_paulis=TrueAbelianGrouper 를 사용하여 SummedOp 를 큐비트 단위로 상호 교환 가능한 Pauli 연산자의 모음으로 변환한다. 각각의 모음이 동일한 회로 실행을 공유하므로, 이를 통해 전반적인 회로 실행을 줄일 수 있다.

[59]:
print(PauliExpectation().convert(h2_measurement))
SummedOp([
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ
    - 1.0523732 * II),
         ┌───┐
    q_0: ┤ H ├
         ├───┤
    q_1: ┤ H ├
         └───┘
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ),
    II
  ])
])

변환기는 재귀적으로 동작하는데, 그 말은, 표현을 오가면서 오직 동작 가능한 경우에만 적용한다는 말이다. 그러므로 전체 시간 변화(full evolution)와 식 측정을 바로 변환할 수 있다. 변환된 h2_measurement 는 시간 변화 CircuitStateFn 으로 동등하게 구성될 수 있다. 이는 전체 표현에 변환을 적용하여 진행된다.

[60]:
diagonalized_meas_op = PauliExpectation().convert(trotterized_op)
print(diagonalized_meas_op)
SummedOp([
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ
    - 1.0523732 * II),
    CircuitStateFn(
    global phase: 1.0523732*θ
         ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
    q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
         └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
    q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
              └───┘└───┘                              └───┘     »
    «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
    «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
    «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
    «                              └──────────────────┘└──────────────────┘     »
    «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ H ├
    «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤├───┤
    «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├┤ H ├
    «                              └───┘                              └───┘└───┘
    )
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ),
    CircuitStateFn(
    global phase: 1.0523732*θ
         ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
    q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
         └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
    q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
              └───┘└───┘                              └───┘     »
    «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
    «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
    «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
    «                              └──────────────────┘└──────────────────┘     »
    «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├
    «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤
    «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├
    «                              └───┘                              └───┘
    )
  ])
])

이제 여러 매개변수를 ListOp 로 바인드 하고, 이어서 전체 표현을 eval 로 구한다. 바인딩이 먼저 되었다면 eval 을 먼저 사용할 수도 있지만, 별로 효과적이진 않다. 여기서는 내부 시뮬레이션을 통해 evalCircuitStateFn들을 VectorStateFn들로 변환한다.

[61]:
evo_time_points = list(range(8))
h2_trotter_expectations = diagonalized_meas_op.bind_parameters({evo_time: evo_time_points})

여기 다양한 매개변수에 해당하는 기댓값들 \(\langle \Phi_+| e^{iHt} H e^{-iHt} |\Phi_+\rangle\) 이 있다.

[62]:
h2_trotter_expectations.eval()
[62]:
array([-0.88272211+0.0e+00j, -0.88272211+0.0e+00j, -0.88272211+0.0e+00j,
       -0.88272211+0.0e+00j, -0.88272211+0.0e+00j, -0.88272211+0.0e+00j,
       -0.88272211+5.6e-17j, -0.88272211+0.0e+00j])

CircuitSamplerCircuitStateFn들을 실행하기

CircuitSampler 는 연산자를 오가며, 양자 백엔드를 사용하는 DictStateFn 또는 VectorStateFn 으로 CircuitStateFn 을 결과 상태 함수의 근사치로 변환한다. CircuitStateFn 의 값을 근사하려면, 1) 상태 함수를 모든 위상 정보를 파기하는 탈분극 채널을 통해 보내야 하며, 2) 샘플 주파수들을 샘플링의 (보른 규칙을 따라 상태 함수의 제곱 을 샘플링하는 것과 동등한) 원래 확률이 아닌 주파수의 제곱근 으로 대체해야 한다.

[63]:
sampler = CircuitSampler(backend=Aer.get_backend('aer_simulator'))
# sampler.quantum_instance.run_config.shots = 1000
sampled_trotter_exp_op = sampler.convert(h2_trotter_expectations)
sampled_trotter_energies = sampled_trotter_exp_op.eval()
print('Sampled Trotterized energies:\n {}'.format(np.real(sampled_trotter_energies)))
Sampled Trotterized energies:
 [-0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211
 -0.88272211 -0.88272211]

회로들이 회로 샘플링 확률들의 제곱근 을 갖는 사전형(dicts)으로 대체된다는 것에 유의하라. 변환 전후의 이 하위 표현식을 참고하라.

[64]:
print('Before:\n')
print(h2_trotter_expectations.reduce()[0][0])
print('\nAfter:\n')
print(sampled_trotter_exp_op[0][0])
Before:

ComposedOp([
  OperatorMeasurement(0.18093119 * ZZ
  - 1.0523732 * II),
  CircuitStateFn(
       ┌───┐     ┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───────┐»
  q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ Rz(0) ├»
       └───┘┌─┴─┐├───┤└─┬─┘└───────┘└─┬─┘├───┤└─┬─┘└───────┘└─┬─┘├───────┤»
  q_1: ─────┤ X ├┤ H ├──■─────────────■──┤ H ├──■─────────────■──┤ Rz(0) ├»
            └───┘└───┘                   └───┘                   └───────┘»
  «     ┌───────┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐
  «q_0: ┤ Rz(0) ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ H ├
  «     ├───────┤└─┬─┘└───────┘└─┬─┘├───┤└─┬─┘└───────┘└─┬─┘├───┤├───┤
  «q_1: ┤ Rz(0) ├──■─────────────■──┤ H ├──■─────────────■──┤ H ├┤ H ├
  «     └───────┘                   └───┘                   └───┘└───┘
  )
])

After:

ComposedOp([
  OperatorMeasurement(0.18093119 * ZZ
  - 1.0523732 * II),
  DictStateFn({'00': 0.7167090588237321, '11': 0.6973723001381686})
])
[65]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.19.0.dev0+fe3eb3f
qiskit-aer0.10.0.dev0+b78f265
qiskit-ignis0.7.0.dev0+0eb9dcc
qiskit-ibmq-provider0.17.0.dev0+15d2dfe
System information
Python version3.9.5
Python compilerClang 10.0.0
Python builddefault, May 18 2021 12:31:01
OSDarwin
CPUs4
Memory (Gb)32.0
Fri Oct 29 16:10:45 2021 BST

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

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.