מחלקת ה-Operator
Package versions
The code on this page was developed using the following requirements. We recommend using these versions or newer.
qiskit[all]~=2.4.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")
שים לב שבדוגמה לעיל האופרטור מאותחל מאובייקט 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
ניתן לשלב אופרטורים בכמה שיטות.
מכפלת טנזור
שני אופרטורים ו- ניתנים לשילוב למכפלת טנזור באמצעות הפונקציה Operator.tensor. שים לב שאם גם וגם הם אופרטורים חד-Qubit, אז A.tensor(B) = יהיה עם תת-מערכות ממוספרות כך שמטריצה על תת-מערכת 0, ומטריצה על תת-מערכת 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, הפועלת כמו מכפלת טנזור אך בסדר הפוך. לפיכך, עבור שני אופרטורים ו- מתקבל A.expand(B) = שבו תת-המערכות ממוספרות כך שמטריצה על תת-מערכת 0, ומטריצה על תת-מערכת 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))
קומפוזיציה
אתה יכול גם לרכב שני אופרטורים ו- לביצוע כפל מטריצות באמצעות מתודת Operator.compose. A.compose(B) מחזיר את האופרטור עם המטריצה :
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,))
אתה יכול גם לרכב בסדר הפוך על ידי החלת לפני תוך שימוש בארגומנט front של compose: A.compose(B, front=True) = :
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,))
קומפוזיציה על תת-מערכת
שים לב שהרכבה הקודמת דורשת שממד הפלט הכולל של האופרטור הראשון יהיה שווה לממד הקלט הכולל של האופרטור המורכב (ובדומה, ממד הפלט של חייב להיות שווה לממד הקלט של בעת ריכוב עם 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.
השלבים הבאים
- חקור את ה-Operator API.