注釈
当ページは tutorials/circuits_advanced/02_operators_overview.ipynb から生成されました。
IBM Quantum lab でインタラクティブに実行します。
オペレーター¶
[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
Operator クラス¶
Operator
クラスは、量子系に作用する行列演算子を表すために Qiskit で利用されています。小さな演算子のテンソル積を用いた合成演算子の構築や、演算子の合成といったいくつかのメソッドがあります。
Operator の作成¶
Operator オブジェクト作成のもっとも簡単な方法は、Numpy 配列のリストとして行列を指定し、それで初期化することです。例えば、2量子ビット 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))
Operator プロパティ¶
Operator オブジェクトは配下の行列とサブシステムのインプット、アウトプット次元を保持します。
data
: To access the underlying Numpy array, we may use theOperator.data
property.dims
: operator の総インプット、アウトプット次元を返却。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)
インプットアウトプット次元¶
Operator クラスはまたサブシステムの次元も追跡し、operator を合成するときに使われます。これらは、input_dims
と output_dims
関数で取得できます。
\(2^N\) 掛ける \(2^M\) operatorの場合、インプットとアウトプットの次元は、 M-qubit と N-qubit と自動的に想定されます。
[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,)
入力の行列が量子ビットサブシステムに分割できない場合には、単一量子ビット operator として保持されます。例えば、 \(6\times6\) 行列の場合:
[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,)
インプットとアウトプット次元は新しい operator の初期化時に手動で指定することも出来ます。
[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 の初期化方法を利用して、Operator
オブジェクトに変換することができます。例えば:
Pauli
オブジェクトGate
とInstruction
オブジェクトQuantumCircuits
オブジェクト
最後の点は、``Operator``クラスにより、量子回路の最終ユニタリ行列の計算のためのユニタリシミュレーターとして使うことができ、シミュレーターのバックエンドを呼び出す必要がないことに着目してください。回路にサポートされない演算子が含まれている場合には、例外が発生します。サポートされていない演算子は: measure、reset、条件付き演算子、行列定義のないゲート、もしくは 行列定義があるゲートだがその分解(decomposition) です。
[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))
回路にオペレーターを利用する¶
ユニタリな Operator
は QuantumCircuit.append
メソッドを利用して QuantumCircuit
に直接追加出来ます。これにより、Operator` は UnitaryGate
オブジェクトに変換され、回路に追加されます。
operator がユニタリでない場合には、例外が発生します。Operator.is_unitary()
関数でチェックができ、operator がユニタリの場合は 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
オブジェクトから operator を初期化していることに注目してください。しかしながら、Pauli
オブジェクトは回路にそれ自体で直接追加可能であり、この場合単一量子ビットの パウリゲート列として変換されます。
[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
オペレーターを結合する¶
Operators may be combined using several methods.
テンソル積¶
2つの operator \(A\) と \(B\) は、Operator.tensor
関数を利用して、テンソル積 operator \(A\otimes B\) に結合出来ます。A と B の両者が単一量子ビット operator の場合、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
があり、テンソル積のように振る舞いますが、順番が逆になります。従って、 2つの operator \(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))
コンポジション¶
2つの operator \(A\) の掛け算を Operator.compose
メソッドを利用して合成出来ます。A.compose(B)
の結果として、行列 \(B.A\) となる operator が返却されます:
[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,))
サブシステム合成¶
前の合成は最初の operator \(A\) の総アウトプット次元が、合成される operator \(B\) の総インプット次元と同じであるが必要なことに注意してください(同様に、\(B\) のインプット次元は、front=True
で合成される場合、等しくなる必要があります)。
大きな operator から compose
の qargs
kwarg を指定して front=True
設定があるかないかに関わらず、サブシステムを選択し小さな operator を合成できます。この場合、合成される関係するサブシステムのインプット、アウトプット次元は一致する必要があります。注: 小さな operator は ``compose`` メソッドの引数である必要があります。
例えば、3量子ビット Operator から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
はユニタリ operator のユニタリー性を保ちますが、一方、線型結合はユニタリー性を保たず、2つのユニタリー operator の足し算は、一般的に非ユニタリー operator になります:
[32]:
op.is_unitary()
[32]:
False
オペレーターへの暗黙的変換¶
以下の全てのメソッドでは2番目のオブジェクトがまだ 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,))
オペレーターの比較¶
オペレーター には二つの operator が近似的に一致しているかをチェックする equality メソッドがあります。
[34]:
Operator(Pauli(label='X')) == Operator(XGate())
[34]:
True
ここではオペレーターの行列要素が近似的に一致しているかをチェックしますが、グローバル位相が異なる2つのユニタリは同じとは見なされないことに注意してください。
[35]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[35]:
False
プロセスフィデリティー¶
Quantum Information モジュールの process_fidelity
関数を使って operator を比較することも出来ます。これは2つの量子チャネルが情報理論的な量としてどれだけ近いかを示し、ユニタリオペレーターの場合にはグローバル位相に依存しません。
[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.
[ ]: