Portuguese
Idiomas
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

Nota

Esta página foi gerada a partir do tutorials/circuits_advanced/02_operators_overview.ipynb.

Operadores

[1]:
import numpy as np

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import 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:

[2]:
XX = Operator([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]])
XX
[2]:
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: Para acessar o array Numpy subjacente, nós podemos utilizar a propriedade Operator.data.

  • 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.

[3]:
XX.data
[3]:
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]])
[4]:
input_dim, output_dim = XX.dim
input_dim, output_dim
[4]:
(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:

[5]:
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\):

[6]:
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:

[7]:
# 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,)
[8]:
# 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:

[9]:
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.

[10]:
# Create an Operator from a Pauli object

pauliXX = Pauli('XX')
Operator(pauliXX)
[10]:
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))
[11]:
# Create an Operator for a Gate object
Operator(CXGate())
[11]:
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))
[12]:
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
[12]:
Operator([[0.70710678+0.j        , 0.        -0.70710678j],
          [0.        -0.70710678j, 0.70710678+0.j        ]],
         input_dims=(2,), output_dims=(2,))
[13]:
# 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)
[13]:
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.

[14]:
# 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')
[14]:
../../_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:

[15]:
backend = BasicAer.get_backend('qasm_simulator')
circ = transpile(circ, backend, basis_gates=['u1','u2','u3','cx'])
job = backend.run(circ)
job.result().get_counts(0)
[15]:
{'11': 1024}
[16]:
# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli('XX'), [0, 1])
circ2.measure([0,1], [0,1])
circ2.draw()
[16]:
     ┌────────────┐┌─┐
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.

[17]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.tensor(B)
[17]:
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 :math:\) no subsistema 1.

[18]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.expand(B)
[18]:
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\):

[19]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.compose(B)
[19]:
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\):

[20]:
A = Operator(Pauli('X'))
B = Operator(Pauli('Z'))
A.compose(B, front=True)
[20]:
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:

[21]:
# Compose XZ with an 3-qubit identity operator
op = Operator(np.eye(2 ** 3))
XZ = Operator(Pauli('XZ'))
op.compose(XZ, qargs=[0, 2])
[21]:
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))
[22]:
# Compose YX in front of the previous operator
op = Operator(np.eye(2 ** 3))
YX = Operator(Pauli('YX'))
op.compose(XZ, qargs=[0, 2], front=True)
[22]:
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.

[23]:
XX = Operator(Pauli('XX'))
YY = Operator(Pauli('YY'))
ZZ = Operator(Pauli('ZZ'))

op = 0.5 * (XX + YY - 3 * ZZ)
op
[23]:
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:

[24]:
op.is_unitary()
[24]:
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.

[25]:
# Compose with a matrix passed as a list
Operator(np.eye(2)).compose([[0, 1], [1, 0]])
[25]:
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.

[26]:
Operator(Pauli('X')) == Operator(XGate())
[26]:
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:

[27]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[27]:
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.

[28]:
# 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.

[29]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.2
qiskit-aer0.10.4
qiskit-ignis0.7.1
qiskit-ibmq-provider0.19.1
qiskit0.36.2
System information
Python version3.9.9
Python compilerGCC 11.1.0
Python buildmain, Dec 29 2021 22:19:36
OSLinux
CPUs32
Memory (Gb)125.64821243286133
Wed Jun 22 15:52:11 2022 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

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.

[ ]: