انتقل إلى المحتوى الرئيسي

كلاس Operator

Package versions

The code on this page was developed using the following requirements. We recommend using these versions or newer.

qiskit[all]~=2.3.0

تُوضّح هذه الصفحة كيفية استخدام كلاس Operator. للاطّلاع على نظرة عامة عن تمثيلات العوامل (operators) في Qiskit، بما فيها كلاس Operator وغيره، راجع نظرة عامة على كلاسات العوامل.

# 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

تحويل الكلاسات إلى Operators

يمكن تحويل عدة كلاسات أخرى في Qiskit مباشرةً إلى كائن Operator باستخدام طريقة تهيئة العامل. على سبيل المثال:

  • كائنات Pauli
  • كائنات Gate وInstruction
  • كائنات QuantumCircuit

لاحظ أن النقطة الأخيرة تعني أنه يمكنك استخدام كلاس Operator كمحاكٍ وحيدي (unitary simulator) لحساب المصفوفة الوحيدية النهائية لدائرة كمّية، دون الحاجة إلى استدعاء خلفية محاكٍ. إذا احتوت الدائرة على أي عمليات غير مدعومة، يُرفع استثناء. العمليات غير المدعومة هي: القياس (measure)، وإعادة الضبط (reset)، والعمليات الشرطية، أو أي بوابة لا تملك تعريف مصفوفة أو تحليلاً من حيث بوابات ذات تعريفات مصفوفة.

# 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))

استخدام Operators في الدوائر

يمكن إدراج Operators الوحيدية مباشرةً في QuantumCircuit باستخدام طريقة QuantumCircuit.append. يُحوّل هذا الكائنَ Operator إلى كائن UnitaryGate، الذي يُضاف بعدها إلى الدائرة.

إذا لم يكن العامل وحيدياً، يُرفع استثناء. يمكن التحقق من ذلك باستخدام الدالة 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 يمكن إدراجه مباشرةً في الدائرة ذاتها أيضاً، وسيُحوَّل عندئذٍ إلى سلسلة من بوابات Pauli أحادية الكيوبت:

# 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

دمج Operators

يمكن دمج Operators بعدة طرق.

حاصل الضرب التنسوري

يمكن دمج عاملَين AA وBB في عامل حاصل ضرب تنسوري ABA\otimes B باستخدام الدالة Operator.tensor. لاحظ أنه إذا كان كلٌّ من AA وBB عاملَين أحاديَي الكيوبت، فإن A.tensor(B) = ABA\otimes B ستكون أنظمتها الفرعية مفهرسةً بحيث تكون المصفوفة BB على النظام الفرعي 0، والمصفوفة AA على النظام الفرعي 1.

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، التي تعمل كحاصل ضرب تنسوري لكن بترتيب عكسي. لذلك، بالنسبة لعاملَين AA وBB، فإن A.expand(B) = BAB\otimes A، حيث تُفهرَس الأنظمة الفرعية بحيث تكون المصفوفة AA على النظام الفرعي 0، والمصفوفة BB على النظام الفرعي 1.

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))

التركيب

يمكنك أيضاً تركيب عاملَين AA وBB لتنفيذ ضرب المصفوفات باستخدام طريقة Operator.compose. يُعيد 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,))

يمكنك أيضاً التركيب بالترتيب العكسي بتطبيق BB أمام AA باستخدام المعامل front في compose: 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 (وبالمثل، يجب أن يكون البُعد الخرجي لـBB مساوياً للبُعد الدخلي لـAA عند التركيب باستخدام front=True).

يمكنك أيضاً تركيب عامل أصغر مع مجموعة من الأنظمة الفرعية لعامل أكبر باستخدام المعامل qargs في compose، سواء مع front=True أو بدونه. في هذه الحالة، يجب أن تتطابق أبعاد الدخل والخرج ذات الصلة للأنظمة الفرعية المُركَّبة. لاحظ أن العامل الأصغر يجب أن يكون دائماً هو وسيط طريقة compose.

على سبيل المثال، لتركيب بوابة ثنائية الكيوبت مع عامل ثلاثي الكيوبت:

# 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))

التركيبات الخطية

يمكن أيضاً دمج Operators باستخدام عمليات خطية قياسية للجمع والطرح والضرب الكمّي بأعداد مركّبة.

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

التحويل الضمني إلى Operators

لاحظ أنه في جميع الطرق التالية، إذا لم يكن الكائن الثاني كائن 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,))

مقارنة Operators

تُنفّذ Operators طريقة مساواة يمكن استخدامها للتحقق من أن عاملَين متساويان تقريباً.

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

لاحظ أن هذا يتحقق من أن كل عنصر من عناصر مصفوفتَي العاملَين متساوٍ تقريباً؛ فالعاملان الوحيديان اللذان يختلفان بمرحلة عالمية لا يُعتبران متساويَين:

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

درجة التقارب بين العمليات

يمكنك أيضاً مقارنة Operators باستخدام الدالة process_fidelity من وحدة Quantum Information. هذه كمية نظرية معلوماتية تقيس مدى تقارب قناتَين كمّيتَين، وفي حالة العوامل الوحيدية لا تعتمد على المرحلة العالمية.

# 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.

الخطوات التالية

توصيات