Nota
Esta página foi gerada a partir do tutorials/circuits_advanced/02_operators_overview.ipynb.
Execute interativamente no IBM Quantum lab.
Operadores¶
[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
Classe do operador¶
A classe Operator
é usada no Qiskit para representar operadores matriciais que atuam em um sistema quântico. Possui vários métodos para construir operadores compostos utilizando produtos tensoriais de operadores menores e para compor operadores.
Criando Operadores¶
A maneira mais fácil de criar um objeto operador é inicializá-lo com uma matriz dada como uma lista ou como um arranjo Numpy. Por exemplo, para criar um operador Pauli-XX de dois qubits:
[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))
Propriedades do operador¶
O objeto operador armazena a matriz subjacente e a dimensão de entrada e de saída dos subsistemas.
data
: To access the underlying Numpy array, we may use theOperator.data
property.dims
: Para devolver a dimensão total de entrada e saída do operador, podemos usar a propriedadeOperator.dim
. Nota: a saída é retornada como um tuple ``(input_dim, output_dim)``, que é o reverso da forma da matriz subjacente.
[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)
Dimensões de Entrada e Saída¶
A classe de operador também acompanha as dimensões dos subsistemas, que podem ser utilizadas para a composição conjunta dos operadores. Estes podem ser acessados utilizando as funções input_dims
e output_dims
.
Para os operadores \(2^N\) por \(2^M\), a dimensão de entrada e saída será assumida automaticamente como M-qubit e 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,)
Se a matriz de entrada não for divisível em subsistemas de qubit, então ela será armazenada como um operador de único qubit. Por exemplo, se tivermos uma matriz \(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,)
A dimensão de entrada e saída também pode ser especificada manualmente ao inicializar um novo operador:
[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)
Nós também podemos extrair apenas as dimensões de entrada ou saída de um subconjunto de subsistemas usando as funções input_dims
e 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,)
Convertendo classes para Operadores¶
Várias outras classes no Qiskit podem ser diretamente convertidas para um objeto Operator
utilizando o método de inicialização do operador. Por exemplo:
Objetos
Pauli
Objetos de
Gate
eInstruction
Objetos
QuantumCircuits
Note que o último ponto significa que podemos usar a classe Operator
como um simulador unitário para calcular a matriz unitária final para um circuito quântico, sem ter que chamar um simulador de backend. Se o circuito contiver quaisquer operações não suportadas, uma exceção será levantada. Operações não suportadas são: medida, redefinir, operações condicionais, ou um gate que não tenha uma definição de matriz ou decomposição em termos de gate com definição de matriz.
[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))
Usando Operadores em circuitos¶
Operators
unitários podem ser diretamente inseridos em um QuantumCircuit
usando o método QuantumCircuito.append
. Isso converte o Operator
em um objeto UnitaryGate
, que é adicionado ao circuito.
Se o operador não for unitário, uma exceção será gerada. Isto pode ser verificado usando a função Operator.is _unitary()
, que retornará True
se o operador for unitário e False
, caso contrário.
[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]:

Note que no exemplo acima nós inicializamos o operador de um objeto Pauli
. No entanto, o objeto Pauli
também pode ser diretamente inserido no próprio circuito e será convertido em uma sequência de gates Pauli de um único qubit:
[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
Combinando Operadores¶
Os operadores podem ser combinados usando vários métodos.
Produto do Tensor¶
Dois operadores \(A\) e \(B\) podem ser combinados em um operador de produto de tensor \(A\otimes B\) usando a função Operator.tensor
. Note que se A e B são operadores de um único qubit, então A.tensor(B)
= \(A\otimes B\) terão os subsistemas indexados como matriz B no subsistema 0, e a matriz \(A\) no subsistema 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))
Expansão de Tensor¶
Uma operação próxima é a Operator.expand
, que atua como um produto de tensor, mas na ordem inversa. Portanto, para os dois operadores \(A\) e \(B\) temos A.expand(B)
= \(B\otimes A\) onde os subsistemas indexaram como matriz A no subsistema 0, e matriz \(B\) no subsistema 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))
Composição¶
Também podemos compor dois operadores \(A\) e \(B\) para implementar a multiplicação da matriz usando o método Operator.compose
. Temos que o A.compose(B)
retorna o operador com matriz \(B.A\):
[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,))
Também podemos compor na ordem inversa aplicando \(B\) na frente de \(A\) usando o front
kwarg de compose
: A. ompose(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,))
Composição do Subsistema¶
Note que a composição anterior requer que a dimensão de saída total do primeiro operador \(A\) seja igual à dimensão de entrada total do operador composto \(B\) (e da mesma forma, a dimensão de saída de \(B\) deve ser igual à dimensão de entrada de \(A\) ao compor com front=True
).
Também podemos compor um operador menor com uma seleção de subsistemas em um operador maior usando o qargs
kwarg de compose
, com ou sem front=True
. Neste caso, as dimensões relevantes de entrada e saída dos subsistemas que estão a ser compostos devem corresponder. Observe que o operador menor deve sempre ser o argumento do método ``compose``.
Por exemplo, para escrever um gate de dois qubit com um Operador de três qubit:
[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))
Combinações lineares¶
Os operadores também podem ser combinados utilizando operadores lineares padrão para adição, subtração e multiplicação escalar por números complexos.
[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))
Um ponto importante é que, enquanto tensor
, expand
e compose
irão preservar a unidade de operadores unitários, combinações lineares não terão o mesmo efeito; portanto, adicionar dois operadores unitários irá, em geral, resultar num operador não unitário:
[32]:
op.is_unitary()
[32]:
False
Conversão Implícita para Operadores¶
Note que para todos os seguintes métodos, se o segundo objeto não é já um objeto Operator
, ela será implicitamente convertida em um pelo método. Isto significa que matrizes podem ser passadas diretamente sem serem explicitamente convertidas para um Operator
primeiro. Se a conversão não for possível, uma exceção será gerada.
[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,))
Comparação de Operadores¶
Os operadores implementam um método de igualdade que pode ser utilizado para verificar se dois operadores são aproximadamente iguais.
[34]:
Operator(Pauli(label='X')) == Operator(XGate())
[34]:
True
Note que isso verifica que cada elemento matrix dos operadores é aproximadamente igual; duas unidades que diferem por uma fase global não serão consideradas iguais:
[35]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[35]:
False
Fidelidade de Processo¶
Podemos também comparar operadores usando a função process_fidelity
a partir do módulo Informações Quânticas. Esta é uma informação sobre a quantidade teórica de como dois canais quânticos são próximos um do outro, e no caso dos operadores unitários, isso não depende da fase global.
[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
Observe que a fidelidade do processo, geralmente, é apenas uma medida válida de proximidade se os operadores de entrada são unitários (ou CP no caso dos canais quânticos), e uma exceção será levantada se as entradas não forem 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.
[ ]: