Portuguese, Brazilian
Idiomas
English
Japanese
German
Korean
Portuguese, Brazilian
French
Shortcuts

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 the Operator.data property.

  • dims: Para devolver a dimensão total de entrada e saída do operador, podemos usar a propriedade Operator.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 e Instruction

  • 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]:
../../_images/tutorials_circuits_advanced_02_operators_overview_22_0.png

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 SoftwareVersion
QiskitNone
Terra0.14.0
Aer0.6.0
Ignis0.3.0
Aqua0.7.0
IBM Q Provider0.6.1
System information
Python3.7.7 (default, Mar 26 2020, 10:32:53) [Clang 4.0.1 (tags/RELEASE_401/final)]
OSDarwin
CPUs4
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.

[ ]: