Klasa Operator
Wersje pakietów
Kod na tej stronie został opracowany przy użyciu poniższych wymagań. Zalecamy korzystanie z tych lub nowszych wersji.
qiskit[all]~=2.3.0
Ta strona pokazuje, jak używać klasy Operator. Ogólne omówienie reprezentacji operatorów w Qiskit, w tym klasy Operator i innych, znajdziesz w artykule Przegląd klas operatorów.
# 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
Konwertowanie klas na obiekty Operator
Kilka innych klas w Qiskit można bezpośrednio przekonwertować na obiekt Operator za pomocą metody inicjalizacji operatora. Na przykład:
- Obiekty
Pauli - Obiekty
GateiInstruction - Obiekty
QuantumCircuit
Warto zauważyć, że ostatni punkt oznacza, że możesz używać klasy Operator jako symulatora unitarnego do obliczania końcowej macierzy unitarnej dla Circuit, bez konieczności wywoływania Backend symulatora. Jeśli Circuit zawiera nieobsługiwane operacje, zostanie zgłoszony wyjątek. Nieobsługiwane operacje to: pomiar, reset, operacje warunkowe lub Gate, która nie posiada definicji macierzowej ani rozkładu na Gate z definicjami macierzowymi.
# 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))
Używanie obiektów Operator w Circuit
Unitarne obiekty Operator można bezpośrednio wstawiać do QuantumCircuit za pomocą metody QuantumCircuit.append. Konwertuje to Operator na obiekt UnitaryGate, który jest dodawany do Circuit.
Jeśli operator nie jest unitarny, zostanie zgłoszony wyjątek. Można to sprawdzić funkcją Operator.is_unitary(), która zwraca True, gdy operator jest unitarny, i False w przeciwnym razie.
# 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")
Zauważ, że w powyższym przykładzie operator jest inicjalizowany z obiektu Pauli. Jednak obiekt Pauli można też wstawić bezpośrednio do Circuit, gdzie zostanie przekonwertowany na sekwencję jednoQubitowych bramek 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
Łączenie obiektów Operator
Operatory można łączyć na kilka sposobów.
Iloczyn tensorowy
Dwa operatory i można połączyć w operator iloczynu tensorowego przy użyciu funkcji Operator.tensor. Jeśli oba i są operatorami jednoQubitowymi, to A.tensor(B) = będzie miało podsystemy zaindeksowane tak, że macierz odpowiada podsystemowi 0, a macierz podsystemowi 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))
Rozwinięcie tensorowe
Ściśle powiązaną operacją jest Operator.expand, która działa jak iloczyn tensorowy, ale w odwrotnej kolejności. Dla dwóch operatorów i mamy A.expand(B) = , gdzie podsystemy są zaindeksowane tak, że macierz odpowiada podsystemowi 0, a macierz podsystemowi 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))
Składanie
Możesz również złożyć dwa operatory i , aby zrealizować mnożenie macierzy, korzystając z metody Operator.compose. A.compose(B) zwraca operator z macierzą :
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,))
Możesz też złożyć w odwrotnej kolejności, stosując przed , używając argumentu front metody 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,))
Składanie podsystemów
Zauważ, że poprzednie składanie wymaga, aby całkowity wymiar wyjściowy pierwszego operatora był równy całkowitemu wymiarowi wejściowemu składanego operatora (analogicznie, wymiar wyjściowy musi być równy wymiarowi wejściowemu przy składaniu z front=True).
Możesz też złożyć mniejszy operator z wybranym podzbiorem podsystemów większego operatora, używając argumentu qargs metody compose, zarówno z front=True, jak i bez niego. W takim przypadku odpowiednie wymiary wejściowe i wyjściowe składanych podsystemów muszą być zgodne. Pamiętaj, że mniejszy operator musi zawsze być argumentem metody compose.
Na przykład, aby złożyć bramkę dwu-qubitową z operatorem trzy-qubitowym:
# 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))
Kombinacje liniowe
Operatory można też łączyć za pomocą standardowych operacji liniowych: dodawania, odejmowania i mnożenia skalarnego przez liczby zespolone.
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))
Ważna uwaga: o ile tensor, expand i compose zachowują unitarność operatorów unitarnych, kombinacje liniowe jej nie zachowują — w ogólności suma dwóch operatorów unitarnych daje operator nieunitarny:
op.is_unitary()
False
Niejawna konwersja do operatorów
Zwróć uwagę, że we wszystkich poniższych metodach, jeśli drugi obiekt nie jest jeszcze obiektem Operator, jest on niejawnie konwertowany przez metodę. Oznacza to, że macierze można przekazywać bezpośrednio, bez wcześniejszej jawnej konwersji do Operator. Jeśli konwersja nie jest możliwa, zgłaszany jest wyjątek.
# 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,))
Porównywanie operatorów
Operatory implementują metodę równości, której można użyć do sprawdzenia, czy dwa operatory są w przybliżeniu równe.
Operator(Pauli("X")) == Operator(XGate())
True
Pamiętaj, że porównanie sprawdza, czy każdy element macierzy operatorów jest w przybliżeniu równy; dwa operatory unitarne różniące się globalną fazą nie są uznawane za równe:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
False
Wierność procesu
Możesz też porównywać operatory za pomocą funkcji process_fidelity z modułu Quantum Information. Jest to wielkość teorioiformacyjna określająca, jak bliskie sobie są dwa kanały kwantowe; w przypadku operatorów unitarnych nie zależy od globalnej fazy.
# 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
Pamiętaj, że wierność procesu jest ogólnie poprawną miarą bliskości tylko wtedy, gdy operatory wejściowe są unitarne (lub CP w przypadku kanałów kwantowych); wyjątek jest zgłaszany, jeśli dane wejściowe nie są CP.
Następne kroki
- Zapoznaj się z dokumentacją referencyjną Operator API.