דלג לתוכן הראשי

מחלקת ה-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. לסקירה ברמה גבוהה של ייצוגי אופרטורים ב-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 כסימולטור יוניטרי לחישוב מטריצת היוניטרי הסופית של Circuit קוונטי, מבלי לקרוא ל-Backend סימולטור. אם ה-Circuit מכיל פעולות שאינן נתמכות, תוטל חריגה. פעולות שאינן נתמכות הן: מדידה, איפוס, פעולות מותנות, או Gate שאין לו הגדרת מטריצה או פירוק מבחינת Gates עם הגדרות מטריצה.

# 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 בתוך Circuits

ניתן להכניס Operators יוניטריים ישירות ל-QuantumCircuit באמצעות מתודת QuantumCircuit.append. פעולה זו ממירה את ה-Operator לאובייקט UnitaryGate, המתווסף ל-Circuit.

אם האופרטור אינו יוניטרי, תוטל חריגה. ניתן לבדוק זאת באמצעות הפונקציה 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 ישירות ל-Circuit עצמו, והוא יומר לרצף של Gates של Pauli חד-Qubit:

# 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

ניתן לשלב אופרטורים בכמה שיטות.

מכפלת טנזור

שני אופרטורים AA ו-BB ניתנים לשילוב למכפלת טנזור ABA\otimes B באמצעות הפונקציה Operator.tensor. שים לב שאם גם AA וגם BB הם אופרטורים חד-Qubit, אז 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.

לדוגמה, כדי לרכב Gate דו-Qubit עם אופרטור של שלושה Qubits:

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

צירופים לינאריים

ניתן לשלב אופרטורים גם באמצעות אופרטורים לינאריים סטנדרטיים לחיבור, חיסור, וכפל סקלרי במספרים מרוכבים.

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 תחילה. אם ההמרה אינה אפשרית, תוטל חריגה.

# 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

אופרטורים מממשים מתודת שוויון שניתן להשתמש בה לבדיקה אם שני אופרטורים הם בערך שווים.

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

שים לב שפעולה זו בודקת שכל איבר מטריצה של האופרטורים הוא בערך שווה; שני יוניטריים השונים בפאזה גלובלית אינם נחשבים שווים:

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

נאמנות תהליך

אתה יכול גם להשוות אופרטורים באמצעות הפונקציה 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.

השלבים הבאים

המלצות