import numpy as np

V = np.array([[3+2j],[4-2j]])
modV = np.real(V.T.conjugate().dot(V)[0,0])**0.5
Vn = V/modV; Vn

v0 = np.array([[1,0]]).T
v1 = np.array([[0,1]]).T
Vn[0,0]*v0 + Vn[1,0]*v1

Vn[0,0].conjugate()*Vn[0,0]

Vn[1,0].conjugate()*Vn[1,0]

import numpy as np
CNOT = np.array([[1,0,0,0],
                 [0,1,0,0],
                 [0,0,0,1],
                 [0,0,1,0]])
CNOT.dot(CNOT)

import numpy as np
H = np.array([[1,1],[1,-1]])/2**0.5
H.dot(H)

import numpy as np
X = np.array([[0,1],
              [1,0]])
X

X.conj().T.dot(X)

Y = np.array([[0,-1j],
              [1j,0]])
Y

Y.conj().T.dot(Y)

Z = np.array([[1,0],
              [0,-1]])
Z

Z.conj().T.dot(Z)

H = (X+Z)/np.sqrt(2); H

H.dot(Z).dot(H)

H.dot(X).dot(H)

-H.dot(Y).dot(H)

import numpy as np
S = np.array([[1,0],
              [0,1j]])
S

S.conj().T.dot(S)

T = np.array([[1,0],
              [0,np.exp(1j*np.pi/4)]])
T

T.conj().T.dot(T)

S = np.array([[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]]); S

v00 = np.array([[1,0,0,0]]).T
S.dot(v00)

v01 = np.array([[0,1,0,0]]).T
S.dot(v01)

v10 = np.array([[0,0,1,0]]).T
S.dot(v10)

v11 = np.array([[0,0,0,1]]).T
S.dot(v11)

C = np.array([[1,0,0,0],
              [0,1,0,0],
              [0,0,0,1],
              [0,0,1,0]]);C

C_ = np.array([[1,0,0,0],
               [0,0,0,1],
               [0,0,1,0],
               [0,1,0,0]]);C_

C.dot(C_).dot(C)

v = v0 + v1; v

n = np.array([[0,0],[0,1]]); n

n.dot(v)

n_ = np.array([[1,0],[0,0]]);n_

I2 = np.identity(2); I2

I2 - n

n_.dot(v)

n.dot(n)

n_.dot(n_)

n.dot(n_)

n_.dot(n)

n+n_

n.dot(X)

X.dot(n_)

import numpy as np
n = np.array([[0,0],[0,1]]); n

n_ = np.array([[1,0],[0,0]]);n_

# ni nj operam em bits distintos. Como cada operador n atua em um vetor de
#  2 dimensões, ni nj é um operador de 4 dimensões. Sendo assim, fica implícito
#  pelos subscritos que ni nj é um produto tensorial.
np.kron(n,n)

# O mesmo vale para n'i n'j
np.kron(n_,n_)

# Para ni n'j
np.kron(n,n_)

# E para n'i nj
np.kron(n_,n)

# Xi Xj são dois operadores 2x2 de inversão, cada um atuando em um bit
#  distinto. Sendo assim, fazemos o produto tensorial entre ambos
#  para obter XiXj
np.kron(X,X)

# No caso da expressão XiXj(ninj' + ni'nj) temos entre parênteses duas
#  matrizes 4x4 (ninj'+ni'nj) e fora dos parênteses uma matriz 4x4 (XiXj)
#  Sendo assim fazemos neste caso o produto matricial normal para
#  calcular esta expressão.
np.kron(X,X).dot(np.kron(n,n_)+np.kron(n_,n))

# E por último somamos com o resultado inicial de ninj + ni'nj'
#  Como pode-se ver, esta expressão gera o operador de SWAP
np.kron(n,n) + np.kron(n_,n_) + np.kron(X,X).dot(np.kron(n,n_)+np.kron(n_,n))

# É importante observar que ninj forma um operador que projeta um vetor de
#  4 dimensões (00, 01, 10, 11) em sua componente 11
np.kron(n,n)

# De maneira similar ni'nj' projeta um vetor de 4 dimensões em sua componente 00
np.kron(n_,n_)

# ni'nj projeta em 01
np.kron(n_,n)

# Assim como ninj' projeta em 10
np.kron(n,n_)

# E por último vemos que ni'nj' + ni'nj + ninj' + ninj = I
nn = np.kron(n,n)
nn_ = np.kron(n,n_)
n_n = np.kron(n_,n)
n_n_ = np.kron(n_,n_)
nn + nn_ + n_n + n_n_



import numpy as np
n = np.array([[0,0],[0,1]]); n

n_ = np.array([[1,0],[0,0]]);n_

# (n x n).(n_ x n_)
np.kron(n,n).dot(np.kron(n_,n_))

# Faz sentido que (ni x nj).(ni_ x nj_) seja o vetor nulo pois:
#   ni_ x nj_ é um operador que recebe um vetor de 4 dimensões e
#   produz como resultado apenas sua componente |00>. Por sua
#   vez ni x nj é um operador que recebe um vetor de 4 dimensões
#   e produz como resultado apenas sua componente |11>. Esta
#   componente foi zerada pelo primeiro operador, logo o resultado
#   será nulo. Isto vai acontecer sempre que a componente não
#   zerada do primeiro operador for diferente da do segundo em
#   outras palavras sempre que ij do primeiro for diferente de ij
#   do segundo.

np.kron(n,n).dot(np.kron(n_,n_))

# Outra forma de entender é transformar (n x n).(n_ x n_) em
#  (n.n_) x (n.n_). Como n é ortogonal a n_, a projeção de n
#  em n_ dará zero também.

np.kron(n.dot(n_),n.dot(n_))

nn.dot(n_n_)

# (n_ x n).(n x n_)
np.kron(n_,n).dot(np.kron(n,n_))

n_n.dot(nn_)

# (n x n_).(n_ x n)
np.kron(n, n_).dot(np.kron(n_,n))

nn_.dot(n_n)

# (n_ x n_).(n x n)
np.kron(n_, n_).dot(np.kron(n, n))

n_n_.dot(nn)