<jupyter_start><jupyter_text>Quantum Machine Learning: Uma Introdução  Quantum Computing for All! (A Poetic Introduction) Dear coleagues, it's been a while since I started reading about Quantum Computing. In the mean time, I have also developed some courses on what is called now Machine Learning. Ten, fifteen years ago Machine Learning was called Statistics. Then, the CSVs files started becoming huge, with millions of rows and hundreds, thousands of columns. Statistics became Big Data. Some statistics rules for selecting which column (variable) to use, like p-value became difficult to be explained to customers, specially executives, which need something that can be quickly learned, easily remembered and  effortlessly applied on a daily basis. P-value also started giving strange answers when used in these colossal tables (after all, if 5% of your data is a 100.000 rows with 100 columns, who knows what will appear in this subset of your data???). It was at this moment that somebody "remembered" that p-values had been developed originally with way much more simpler tables, tables in which you were forced to use all the rows and columns in the development of your model. In this context p-values were a way of estimating the other data that was "out there". But with tables made of millions of rows you could just separate 500.000 of them to be the "world out there", use the 4.500.000 to develop (train) your model and afterwards see ("test") what would happen in the real world when your model was put to work. Statistics that had became Big Data was now renamed also to Machine Learning.The algorithms were still being used to model simple phenomena. Phenomena that could be described with straight lines. Science, the scientific revolution started 500 years ago when a group of geniuses tried to apply simple rules to simple phenomena. It worked for quite a while (astonishing well), but the rule was always: simple phenomena that can be described preferably with straight lines, at most with a parabola. The world is incredibly complex, but nonetheless our minds are capable of dealing with all kinds of phenomena, be they describable by straight lines or not. And then somebody tried to apply a method of finding non linear patterns in the data called neural networks. Statistics, now mingled with computer science became Deep Learning.But all this has a cost. The cost of dealing with millions of rows, thousands of columns, to train, validade and test our models. And this cost is paid in the most precious commoditty that a human being can spend: TIME! Our computers are becoming overwhelmed by this combination of Big Data, Machine Learning and Deep Learning that has accomplished a lot of things but needs an ever increasing ammount of data to be processed.It was then that another technology appeared. A technology that is still in its infancy, but is promising us with a future when computers will be able to digest an unthinkable ammount of data in a matter of seconds, process it and tell us where the results are. This technology is called Quantum Computing.What is Quantum Computing and how it can be used in the Machine Learning way of finding the utmost complex patterns in mountains of data is the purpose of a field now called Quantum Machine Learning.This field blends together two of my mostly cherished areas of study: Computing and Physics (and of course, the possibility of making some money along the line, after all, bills doesn't stop arriving each month for us to have them paid...). And this group of Jupyter Notebooks is my attempt to make this area of study better know by as many people as possible. Quantum Machine Learning. This is the Holly Grail of my knowledge dreams. Understand this dream, make it available to others, is the objective of this and other Jupyter Notebooks that I will try writing in the near future.In this journey, Python will be our driver, and Qiskit our compass. The destiny: translating to the world of Quantum Computing the Machine Learning algorithms that have made us devise a future with Intelligent Machines at our disposal.Hope you enjoy the ride. It will be bumpy, sometimes frustating, but if we keep our focus and always remember why we are here, I promise you that there will be a pot of gold at the end of the rainbow waiting for us. Happy Journey! And Let Quantum Machine Learning BE!  Python setup Three libraries will be heavily used in this course: `pandas`, `scikit-learn` and `qiskit`. It is also interesting to import support libraries `numpy` and `matplotlib` object plotting library `pyplot`. The qiskit library and visualization tools need to be installed from `pip` each time the jupyter notebook is runned in Google Colab. The option `-q` installs qiskit silently.<jupyter_code>!pip install qiskit -q
!pip install qiskit[visualization] -q

import numpy as np
np.set_printoptions(precision=3, suppress=True)
from matplotlib import pyplot as plt
%matplotlib inline

import pandas as pd
import sklearn as sk
import qiskit as qk<jupyter_output><empty_output><jupyter_text>Python (very) Short Review In the cell below there's a very short summary of what is needed to be remembered from Python: `numpy` arrays and basic `matplotlib` plots.<jupyter_code>import numpy as np
from matplotlib import pyplot as plt
x = np.linspace(0, 2*np.pi, 1000)
y1 = np.sin(x)
y2 = np.cos(x)

fig, ax = plt.subplots(nrows=1, ncols=2)
ax[0].scatter(x=x, y=y1)
ax[0].scatter(x=x, y=y2)
ax[0].set_title("Gráfico 1")
ax[1].plot(x,y1)
ax[1].plot(x,y2)
ax[1].set_title("Gráfico 2")
plt.tight_layout();<jupyter_output><empty_output><jupyter_text>Álgebra Linear com Numpy e Sympy Para trabalhar com Data Science é essencial uma boa noção de Álgebra Linear incluindo o uso de números complexos. A seguir são resolvidos exercícios envolvendo o uso de matrizes e números complexos, tanto de forma puramente numérica (pelo uso da `numpy`) quanto de forma simbólica (pelo uso da `sympy`). Os exercícios foram retirados de **QCE**: Quantum Computing Explained, <NAME> e **CSE**: Computer Science and Engineering, <NAME>.  Produto Matricial **Ex.:** Calcule o produto matricial $X.T$ e $T.X$ das matrizes X e T<jupyter_code>import sympy as sp
X = sp.Matrix([[0,1],[1,0]])
T = sp.Matrix([[1,1],[1,-1]])
X*T, T*X<jupyter_output><empty_output><jupyter_text>Matriz Simétrica **Ex.:** Uma matriz é considerada simétrica quando ela for igual a sua transposta. Prove que a matriz $$ K_4 = \begin{bmatrix} 2 & -1 & 0 & 0 \\           -1 & 2 & -1 & 0 \\            0 & -1 & 2 & -1 \\            0 & 0 & -1 & 2 \end{bmatrix}$$ é uma matriz simétrica.<jupyter_code>import numpy as np
K4 = np.array([[2,-1,0,0],[-1,2,-1,0],[0,-1,2,-1],[0,0,-1,2]])
K4
(K4 == K4.T).all()
import sympy as sp
from IPython.display import Math
K4 = sp.Matrix([[2,-1,0,0],[-1,2,-1,0],[0,-1,2,-1],[0,0,-1,2]])
Math("K_4 = " + sp.latex(K4) + " , " + "{K_4}^T = " + sp.latex(K4.transpose()))
K4 == K4.transpose()<jupyter_output><empty_output><jupyter_text>Matriz Inversa **Ex.:** Calcule a matriz inversa da matriz T3 (abaixo)<jupyter_code>import numpy as np
T3 = np.array([[3,2,2],[2,2,1],[1,1,1]])
T3
np.linalg.inv(T3)
import sympy as sp
T3sp = sp.Matrix(T3)
Math("T_3 = " + sp.latex(T3sp) + " , \\ \\ " + \
     "{T_3}^{-1} = " + sp.latex(T3sp.inv()))<jupyter_output><empty_output><jupyter_text>Matrizes Especiais K1, K2, K3, K4 (CSE) **Ex.:** Prove que as matrizes K apresentadas abaixo são simétricas para $K_1$, $K_2$, $K_3$ e $K_4$<jupyter_code>import numpy as np
K1 = np.array([[2]])
K2 = np.array([[2,-1],[-1,2]])
K3 = np.array([[2,-1,0],[-1,2,-1],[0,-1,2]])
K4 = np.array([[2,-1,0,0],[-1,2,-1,0],[0,-1,2,-1],[0,0,-1,2]])
K4
K1 - K1.T
K2 - K2.T
K3 - K3.T
K4 - K4.T
T3_inv = np.array([[3,2,2],[2,2,1],[1,1,1]])
T3_inv
np.linalg.inv(T3_inv)<jupyter_output><empty_output><jupyter_text>Espaços Vetoriais **Ex.:** De um exemplo onde a combinação linear de três vetores não nulos do $R^4$ produza o vetor nulo. Confirme o resultado criando com estes três vetores uma matriz A e multiplicando a mesma por um vetor x apropriado para gerar o vetor nulo. Indique também as dimensões de A, x e do vetor nulo.<jupyter_code>from IPython.display import Math
import numpy as np
import sympy as sp
x1 = np.array([1,1,1,1]).reshape(-1,1)
x2 = np.array([2,3,4,5]).reshape(-1,1)
x3 = np.array([3,4,5,6]).reshape(-1,1)
A = np.concatenate([x1,x2,x3],axis=1)
x = np.array([1,1,-1]).reshape(-1,1)
R = A.dot(x)
print('A =', A.shape, ', x =', x.shape, ', R =', R.shape) 
expr = sp.latex('A =') + sp.latex(sp.Matrix(A)) + sp.latex(",") + \
       sp.latex('x =') + sp.latex(sp.Matrix(x)) + sp.latex(",") + \
       sp.latex('R =') + sp.latex(sp.Matrix(R))
Math(expr)<jupyter_output>A = (4, 3) , x = (3, 1) , R = (4, 1)<jupyter_text>**Ex.:** Suponha uma matriz com vetores no $R^{4}$ onde existam duas colunas em que cada uma seja combinação linear de outras duas colunas. Com base nesta afirmação encontre duas soluções distintas x e y para Az=0 (isto é vetores x e y tais que Ax=0 e Ay=0)<jupyter_code>x1 = np.array([1, 2, 3, 4]).reshape(-1,1)
x2 = np.array([2, 3, 5, 1]).reshape(-1,1)
x3 = np.array([3, 5, 8, 5]).reshape(-1,1)
x4 = np.array([1, 1, 2,-3]).reshape(-1,1)

A = np.concatenate([x1,x2,x3,x4], axis=1)
x = np.array([1,1,-1,0]).reshape(-1,1)
Ax = A.dot(x)

y = np.array([1,-1,0,1]).reshape(-1,1)
Ay = A.dot(y)

expr = "A =" + sp.latex(sp.Matrix(A)) + "," + \
       "x =" + sp.latex(sp.Matrix(x)) + "," + "A.x =" + sp.latex(sp.Matrix(Ax)) + "," + \
       "y =" + sp.latex(sp.Matrix(y)) + "," + "A.y =" + sp.latex(sp.Matrix(Ay))
mview(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Suponha uma matriz de uns (3x3). Encontre dois vetores independentes que sejam solução de Az=0. Existe um 3o vetor independente? Não! O espaço das colunas de A tem dimensão 1. A dimensão de A é 3. Logo o espaço nulo de A tem dimensão 2. Tendo dimensão 2 temos apenas dois vetores independentes que podem ser solução de A.z = 0.<jupyter_code>import numpy as np
A = np.ones([3,3]).astype('int32')
x = np.array([1,-1,0]).reshape(-1,1)
Ax = A.dot(x)
y = np.array([1,0,-1]).reshape(-1,1)
Ay = A.dot(y)

expr = "A =" + sp.latex(sp.Matrix(A)) + " ," + \
       "x =" + sp.latex(sp.Matrix(x)) + " ," + " A.x =" + sp.latex(sp.Matrix(Ax)) + \
       "y =" + sp.latex(sp.Matrix(y)) + " ," + " A.y =" + sp.latex(sp.Matrix(Ay))
mview(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Os vetores $v = (1,1,0)$ e $w = (0,1,1)$ formam um plano no $R^3$. Encontre um vetor z perpendicular a este plano. Solução: se este vetor for chamado de $z$ teremos $(c\vec{v}+d\vec{w}).z=0$. Logo $c = -z_1$, $d = -z_3$ e $c+d=-z_2$. Logo o vetor z perpendicular ao plano formado por $v$ e $w$ terá o formato $z_1 + z_3 = z_2$. Um exemplo seria $z = (1,2,1)$<jupyter_code>import numpy as np
T4 = np.array([[4,3,2,1],[3,3,2,1],[2,2,2,1],[1,1,1,1]]).astype('int32')
T4_inv = np.linalg.inv(T4).astype('int32')
expr = sp.latex('T_4 =') + sp.latex(sp.Matrix(T4)) + " , " + sp.latex("{T_4}^{-1} =") + sp.latex(sp.Matrix(T4_inv)) 
mview(expr)<jupyter_output><empty_output><jupyter_text>Potências de $i$ **Ex.:** Calcule $\sqrt{-1}$, $i^3$ e $i^{15}$<jupyter_code>import numpy as np
np.sqrt(-1+0j), (1j)**3, (1j)**15
from IPython.display import Math
import sympy as sp
Math("\sqrt{-1} = " + sp.latex(sp.sqrt(-1)) + " , \\ \\ " + 
     "i^{3} = " + sp.latex(sp.I**3) + " , \\ \\ " + 
     "i^{15} = " + sp.latex(sp.I**15))<jupyter_output><empty_output><jupyter_text>Forma retangular e polar de um número complexo **Ex.:** Dado $c=3+4j$ apresente a parte real, imaginária, o conjugado, módulo e ângulo do mesmo.<jupyter_code>import numpy as np
c = 3+4j
c, c.real, c.imag, c.conjugate(), np.abs(c), np.angle(c)
import sympy as sp
c = sp.symbols("c")
c = 3 + 4*sp.I
display(Math("c = " + sp.latex(c)))
display(Math(sp.latex("Parte Real de ") + "c = " + sp.latex(sp.re(c))))
display(Math(sp.latex("Parte Imaginária de ") + "c = " + sp.latex(sp.im(c))))
display(Math("c^* = " + sp.latex(sp.conjugate(c))))
display(Math("|c| = " + sp.latex(sp.Abs(c))))
display(Math("\\theta = " + sp.latex(sp.arg(c))))<jupyter_output><empty_output><jupyter_text>Raízes de um número complexo **Ex.:** Encontre as raizes cúbicas de $c=(1+j)$ As raízes cúbicas de $-1+j$ podem ser expressas como a solução da equação:<jupyter_code>display(Math("x^3 = 1 + j"))
display(Math("x^3 - (1+j) = 0"))
display(Math("x^3 + 0.x^2 + 0.x - (1+j) = 0"))<jupyter_output><empty_output><jupyter_text>Esta equação pode ser resolvida via `numpy` através dos comandos abaixo:<jupyter_code>import numpy as np
coef = [1,0,0,-(1+1j)]
np.roots(coef)<jupyter_output><empty_output><jupyter_text>E através da `sympy` via os comandos a seguir:<jupyter_code>import sympy as sp
x = sp.symbols("x")
sols = sp.solve(x**3 - (1+sp.I),x)
sols = list(map(sp.simplify, sols))
Math("x = " + sp.latex(sols))<jupyter_output><empty_output><jupyter_text>Autovalores, autovetores e traço de uma matriz quadrada **Ex.:** Calcule os autovalores, autovetores e o traço (soma dos autovalores) da matriz $$X = \begin{bmatrix}0 & 1 \\1 & 0 \end{bmatrix}$$<jupyter_code>import numpy as np
Xnum = np.array([[0,1],[1,0]])
Xnum
import sympy as sp
Xsym = sp.symbols("X")
Xsym = sp.Matrix([[0,1],[1,0]])
Math(sp.latex(Xsym))<jupyter_output><empty_output><jupyter_text>Neste caso os autovalores são todos reais<jupyter_code>np.linalg.eig(Xnum)[0]<jupyter_output><empty_output><jupyter_text>A `sympy` apresenta os autovalores no fomato : [autovalor, multiplicadade do mesmo]<jupyter_code>Xsym.eigenvals()<jupyter_output><empty_output><jupyter_text>Observe que a `numpy` apresenta os autovetores normalizados (com módulo igual a 1)<jupyter_code>np.linalg.eig(Xnum)[1]<jupyter_output><empty_output><jupyter_text>A `sympy` apresenta os mesmos de forma mais elaborada. Primeiro o autovalor, depois a multiplicidade do mesmo e por último o autovetor associado, mas na forma mais simples possível, sem ser normalizado.<jupyter_code>Math(sp.latex(Xsym.eigenvects()))<jupyter_output><empty_output><jupyter_text>No caso do traço, ambas as bibliotecas tem um método similar.<jupyter_code>Xnum.trace(), Xsym.trace()<jupyter_output><empty_output><jupyter_text>Sistemas de Equações Lineares com coeficientes complexos **Ex.:** Dadas as matrizes $A = \begin{bmatrix} 2 & i \\ 3 & 5i \end{bmatrix}$ e $B = \begin{bmatrix} a & b \\ c & d \end{bmatrix}$ resolva para $a$ e $b$ o sistema de equações formado pela expressão $A.B - B.A = 0$<jupyter_code>a,b,c,d = sp.symbols(["a","b","c","d"])
A = sp.Matrix([[2,sp.I],[3,5*sp.I]])
B = sp.Matrix([[a,b],[c,d]])
instr = "A = {0}, B = {1}".format(sp.latex(A), sp.latex(B))
Math(instr)
R = A*B - B*A
Z = sp.Matrix.zeros(2,2)
Equacoes = [R[0,0], R[0,1], R[1,0], R[1,1]]
res = sp.linsolve(Equacoes, a,b,c,d)
instr = "R = A.B - B.A = {0} = {1}".format(sp.latex(R), sp.latex(Z))
Math(instr)
instr = "a = {0},\\\ \ \\\ b = {1}".format(sp.latex(res.args[0][0]), 
                                     sp.latex(res.args[0][1]))
Math(instr)<jupyter_output><empty_output><jupyter_text>Propriedades do módulo de dois números complexos **Ex.:** Prove que, dados dois números complexos $c_1$ and $c_2$, $|c_1||c_2|=|c_1c_2|$. $$c_1 = a + bj , |c_1| = \sqrt{a^2+b^2}$$$$ $$$$c_2 = c + dj , |c_2| = \sqrt{c^2+d^2}$$$$ $$$$|c_1|*|c_2| = \sqrt{{a^2c^2}+{b^2c^2}+{a^2d^2}+{b^2d^2}}$$$$ $$$$|c_1|*|c_2| = \sqrt{{a^2c^2}-{2abcd}+{b^2d^2}+{b^2c^2}+{2abcd}+{a^2d^2}}$$$$ $$$$|c_1|*|c_2| = \sqrt{(ac-bd)^2+(bc+ad)^2}$$$$ $$$$|c_1*c_2| = \sqrt{(ac-bd)^2+(bc+ad)^2} $$$$Q.E.D.$$<jupyter_code>import sympy as sp
x, y, z, w = sp.symbols("x y z w", real = True)
c1, c2 = sp.symbols("c1 c2", complex = True)
c1 = x + y*sp.I
c2 = z + w*sp.I
sp.simplify( sp.Abs(c1*c2) - sp.Abs(c1)*sp.Abs(c2) ) == 0<jupyter_output><empty_output><jupyter_text>**Ex.:** Prove que |c1 + c2| <= |c1| + |c2| A expressão dentro da raiz contém o termo $(wy)^2$. Isto torna a raiz pelo menos igual a $-wy$. Ela contém também $(xz)^2$. Isto torna a raiz pelo menos também igual a $-xz$. Sobram ainda os termos cruzados $(wx)^2$ e $(zy)^2$ dentro da raiz o que torna ela maior que $-(wy)-(xz)$, logo a expressão é maior do que $0$ o que mostra que $|c1|+|c2| >= |c1+c2|$<jupyter_code>expr1 = sp.Abs(c1+c2)**2
expr2 = (sp.Abs(c1)+sp.Abs(c2))**2
Math(sp.latex(sp.simplify(expr2-expr1)/2))<jupyter_output><empty_output><jupyter_text>Produto Tensorial **Ex.:** Calcule o produto tensorial $a*c$ das matrizes a e c<jupyter_code>a = sp.Matrix([[1],[2]])
c = sp.Matrix([[3],[4]])
from sympy.physics.quantum.tensorproduct import TensorProduct
TensorProduct(a,c)<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule o produto tensorial da matriz $a$ com a adjunta da matriz $c$<jupyter_code>import sympy as sp
a = sp.Matrix([[1],[2]])
c = sp.Matrix([[3],[4]])
from sympy.physics.quantum.tensorproduct import TensorProduct
display(TensorProduct(a,c))
print()
display(TensorProduct(a.adjoint(),c.adjoint()))<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule o produto tensorial $X*Y$ das matrizes X e Y<jupyter_code>import sympy as sp
from sympy.physics.quantum.tensorproduct import TensorProduct
X = sp.Matrix([[0,1],[1,0]])
Y = sp.Matrix([[0,-sp.I],[sp.I,0]])
TensorProduct(X,Y)<jupyter_output><empty_output><jupyter_text>Matrizes Unitárias **Ex.:** Uma matriz quadrada composta de números complexos é unitária se sua transposta conjugada ${U}^{*}$ é também sua inversa. Prove que a matriz a seguir é unitária<jupyter_code>import numpy as np
U = sp.Matrix([[1/sp.sqrt(2), 1/sp.sqrt(2),0],
              [-sp.I/sp.sqrt(2),sp.I/sp.sqrt(2),0],
              [0,0,sp.I]])
Ud = U.conjugate().transpose()
expr1 = sp.latex(U)
expr2 = sp.latex(Ud)
expr3 = sp.latex(U*Ud)
expr = expr1 + expr2 + expr3
Math(expr)<jupyter_output><empty_output><jupyter_text>Hadamard Product de Duas Matrizes (Elemento a Elemento) **Ex.:** Calcule o produto elemento a elemento da matriz Theta<jupyter_code>theta = sp.symbols("theta")
U = sp.Matrix([[sp.cos(theta), -sp.sin(theta), 0],
               [sp.sin(theta),  sp.cos(theta), 0],
               [            0,              0, 1]])
Math(U)
Math(U.multiply_elementwise(sp.conjugate(U)))<jupyter_output><empty_output><jupyter_text>Machine Learning Machine learning is the process of tagging data in a data frame. The first thing that needs to be choosen is a target variable. Depending on whether you have examples of your target variable or not, the task can fall in one of two main categories: 1.   **Supervised Learning**: when you have past examples of your input and output data. Depending on whether your output variable is categorical (discrete) or continuous, the supervised learning task can be called:      1. **Classification**: when your output variable is discrete (categorical). Logistic regression is the main example of classification technique.      2. **Regression**: when your output variable is continuous. Linear regression is the main example.2.   **Unsupervised Learning**: when you don't have past examples of your output variable or not even know what it could be. In this case you want the machine to group your data so you can try finding some pattern in it. Depending on wether you want to group the rows or the columns of you data frame, this task can be called:       1. **Clustering**: when you want the machine to tag (group) the rows of your data frame. This tagging can become in the future your output variable to be used in a supervised learning classification problem.      2. **PCA**: when you want the machine to tag (group) the columns of your data frame. In this case you want a new column that will replace some of the original ones through a simple formula. The process of solving a problem using Machine Learning usually involves five steps: 1.   **Problem Definition**: decide which variable will be predicted. This will be called your output variable. 2.   **Data Preparation**: decide which variables you need to try predicting your output variable. These variables will be called input variables. In this step you will also find examples of your variables and have them cleaned and ready to be used.3.   **Model Development**: Based on your output variable type availability you will decide if you're going to develop a classification, regression, clustering or PCA model.4.   **Results Analysis**: When you link the math results from trainning and testing the model developed in stage 3 with your business objectives and present your results for both a technical and a business oriented audience.5.   **Model Deployment**: When your model goes live. Here usually you deal with problems of interfacing your model with real people (managerial issues) or with other systems in your client (a task usually called data engineering).  Quantum Computing The idea behind quantum computing is based in three concepts:1.   **Superposition of states**: the Qubit is neither in the $|0 \rangle$ or $|1 \rangle$ state but in a mixed state called superposition of states. Bare in mind that the notion that it is at the same time in the $|0 \rangle$ and $|1 \rangle$ state is wrong. The system is in a state that is formed from the basis states $|0 \rangle$ and $|1 \rangle$. 2.   **Interference**: is the possibility of making states interact with one another (or with themselves) in such a way that the desired pattern (the one that holds the answer for our problem) is reinforced while the other patterns are cancelled out.3.   **Entanglement**: an extremely strong correlation between states that cannot happen in classical physics. Because of it, two particles when they interact with one another remain entangled ("amarradas" in portuguese) after that. This entangled state means that their states are grouped in such a way that they cannot be represented individually.  Os conceitos apresentados acima são aprofundados a seguir: **Notação de Dirac**: através dela podemos representar a sobreposição de $n$ estados e amplitudes em um sistema $\psi$ conforme segue: $\psi = c_0|x_0 \rangle + c_1 | x_1 \rangle + ... + \ c_{n-1}|x_{n-1} \rangle$ **Colapso da Função de Onda**: só podemos saber "o que se passa" com uma partícula subatômica (um fóton de luz por exemplo) se tentarmos medir o seu estado. Neste momento a partícula irá nos "responder" com qualquer dos possíveis estados $x_i$ acima. Neste momento a partícula "interrompe" sua sobreposição e nos mostra uma fotografia momentânea de si mesma. Damos o nome a este fenômeno de colapso da função de onda associada à partícula.  **Amplitudes $c_0$ ... $c_{n-1}$** e **probabilidades de medição dos estados $x_0$ ... $x_{n-1}$** de uma **partícula**: Cada estado passível de ser medido $x_0$ ... $x_{n-1}$ tem uma amplitude $c_i$ associada ao mesmo. Esta amplitude pode ser um número complexo qualquer, com qualquer valor (negativo, nulo ou positivo) tanto na sua parte real quanto na parte imaginária.A probabilidade de obtermos como resposta a uma medição do estado de uma partícula o valor $x_i$ está associada com o produto $c_i.c_i^{*}$ associado a amplitude $c_i$ do estado $x_i$.Uma vez que o valor numérico $c_i$ pode ser um número complexo qualquer, devemos lembrar que $c_i^{*}$ é o seu complexo conjugado (o número obtido trocando a parte imaginária do mesmo de sinal). Por exemplo se $c_i = 3 + 4i$, seu complexo conjugado $c_i^{*}$ será igual a $c_i^{*} = 3 - 4i$.Porém observe que o produto $c_i.c_i^{*}$ sempre será um número real. Se supusermos que a soma de todos os produtos $c_i.c_i^{*}$ para $i$ indo de $0$ a $n-1$ seja igual a $1$, podemos dizer que cada produto $c_i.c_i^{*}$ representará a probabilidade de obtermos o estado $x_i$ associado em uma medida qualquer. Toda a "estranheza" da Mecânica Quântica decorre da possibilidade destas $n$ amplitudes $c_i$ serem constituidas de qualquer número possível. Podendo misturar valores positivos com negativos podemos obter como resultado para uma combinação de $c_i$s distintos até mesmo o valor 0. Isto significa que o estado $x_i$ associado à amplitude $c_i$ terá probabilidade 0% de ser medido, o que significa que não vamos medi-lo. Dizemos portanto que aquele resultado "desapareceu" de nossas medidas, em outras palavras, naquela situação a partícula "sumiu". Dado que tal soma com resultado nulo só pode aparecer decorrente da interação de outros estados, dizemos que ocorreu um fenômeno de **interferência** da partícula. Porém somos levados a concluir que, dados os estados da partícula interagirem entre sí (enquanto a mesma está em sobreposição), a partícula está interindo **consigo própria**.Do ponto de vista filosófico, isto pode gerar (alias tem gerado a mais de cem anos) uma enorme confusão, com grandes nomes da ciência tentando dar voltas de pensamento e executando verdadeiros malabarismos mentais para encontrar formas de explicar tais fenômenos. De nossa parte, o que nos interessa é que a sobreposição e suas consequências podem ser facilmente analisadas através da maquinaria dos números complexos e da álgebra linear. Seguiremos aqui a linha *filosófica* muito bem descrita por uma frase pronunciada pelo físico <NAME> a um de seus orientandos quando o mesmo começou a gerar as famosas perguntas sem resposta que todo professor de Mecânica Quântica já se acostumou em responder com um sorriso enigmático. A frase simplesmente foi (em inglês no original): *Shut Up and Calculate*. Pelo estilo da mesma, ela foi atribuida ao físico ganhador do Nobel, <NAME>. Ele não pronunciou a mesma, mas parafraseando as palavras de um grande humorista brasileiro <NAME>, criador do inesquecível prefeito de <NAME>: "Se não disse, deveria ter dito". A partir deste momento, nossas partículas elementares passarão a se constituir de entes abstratos chamados de **Qubits**. Tais elementos terão a capacidade de entrar em sobreposição de seus estados fundamentais *x_i* e serão retirados dela quando forem medidos, momento no qual nos apresentarão como resultado um dos $x_i$ possíveis. A probabilidade de medirmos o resultado $x_i$ será dada pelo produto $c_i.c_i^{*}$. Estaremos interessados apenas com as situações nas quais a soma de todos os produtos $c_i.c_i^*$ dará 1 (100%).    **NISQ: Noisy Intermediate Scale Quantum Computing**: Quantum computing today is based in a technique called **NISQ**. This technology uses both classical and quantum computers. Classical computers handle the machine learning problem the same way they usually do. The task of "crunching" the numbers (get the parameters for the machine learning model and having it tested) is handled by the quantum counterpart. The algorithms that handle the problem this way are called **Variational Quantum-Classical Algorithms**  Álgebra Linear: Exercícios Avançados  Estados Quânticos **Ex.(QCE 0.3):** Apresente a representação da sobreposição das amplitudes e dos estados de um sistema $\psi$ com $n$ estados possíveis?<jupyter_code>from sympy import latex
from IPython.display import Math
expr = "\psi ="
expr = expr + "c_0|x_0\\rangle" + " + " + "c_1|x_1\\rangle"
expr = expr + " \ + \ " + "..." + " \ + \ " + "c_{n-1}|x_{n-1}\\rangle"
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule a probabilidade dos sistemas abaixo, ao serem medidos, indicarem o estado $|1 \rangle$<jupyter_code>import numpy as np
p_e = lambda vetor,x : (np.linalg.norm(vetor[x])/np.linalg.norm(vetor))**2

psi = [1/np.sqrt(3), np.sqrt(2/3)]
phi = [1j/2, np.sqrt(3)/2]
chi = [(1+1j)/np.sqrt(3), -1j/np.sqrt(3)]

[float(x.__format__('.3f')) for x in [p_e(psi,1), p_e(phi,1), p_e(chi,1)]]<jupyter_output><empty_output><jupyter_text>**Ex.:** Qual a probabilidade do sistema Phi estar no $3^o$ estado?<jupyter_code>import numpy as np
import sympy as sp
Phi = sp.Matrix([-3 - sp.I, -2*sp.I, sp.I, 2])
num = (Phi[2] * Phi[2].conjugate()) 
den = sp.sqrt(sum(sp.matrix_multiply_elementwise(Phi,Phi.conjugate())))
P = sp.simplify((num/den)**2)
P<jupyter_output><empty_output><jupyter_text>**Ex.:** Dadas as amplitudes $c_{up} = 3-4j$ e $c_{down} = 7 + 2j$ aplicadas à uma base ortonormal, calcule as probabilidades associadas a cada uma das amplitudes.<jupyter_code>import numpy as np
c_up = 3-4j
c_down = 7+2j
ket = np.array([c_up,c_down]).reshape(-1,1)
p_up = np.linalg.norm(c_up)**2/np.linalg.norm(ket)**2
p_down = np.linalg.norm(c_down)**2/np.linalg.norm(ket)**2
[float(x) for x in [p_up.__format__('.3f'), p_down.__format__('.3f')]]<jupyter_output><empty_output><jupyter_text>**Ex.:** Apresente a representação $ket$ do vetor $\psi$ com valores $2-i, 2i, 1-i, 1, -2i, 2$<jupyter_code>import numpy as np
import sympy as sp
psi = sp.Matrix([2 - sp.I, 2*sp.I, 1 - sp.I, 1, -2*sp.I, 2])
expr = "| \\psi \\rangle =" + sp.latex(psi)
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Apresente a representação $bra$ do vetor $\psi$ com valores $2-i, 2i, 1-i, 1, -2i, 2$<jupyter_code>psiT = sp.conjugate(sp.Matrix.transpose(psi))
expr = "\\langle \\psi | =" + sp.latex(psiT)
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Apresente a representação $ket$ do vetor $\psi$ com valores $1/2, -i/2, \sqrt{2}/2$, a representação $bra$ associada e o produto da representação $bra$ com a matriz $A$ (vide abaixo) e com a representação $ket$.<jupyter_code>psi_ket = sp.Matrix([[sp.Rational(1,2)],
                      [-sp.I/2],
                      [1/sp.sqrt(2)]])
psi_ket
psi_bra = psi_ket.adjoint()
psi_bra
A = sp.Matrix([[0,1/sp.sqrt(2),0],
                [1,1/sp.sqrt(2),0],
                [0,0,0]])
psi_bra, A, psi_ket, (psi_bra * A * psi_ket).expand(complex=True)<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule o módulo do vetor $\psi$<jupyter_code>expr = sp.simplify(psiT.multiply(psi))
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Prove que os vetores $|\psi_1 \rangle = [1 + i, i]^T$ e $\psi_2 \rangle = [2 + 4i, 3i − 1]^T$ diferem pelo fator $3+i$<jupyter_code>psi1 = np.array([1+1j,1j]).reshape(2,1)
psi2 = np.array([2+4j,3j-1]).reshape(2,1)
psi2/psi1<jupyter_output><empty_output><jupyter_text>**Ex.:** Os vetores $| \psi_1 \rangle = [1 + i, 2 − i]^T$ e $| \psi_2 \rangle = [2 + 2i, 1 − 2i]^T$ podem representar o mesmo estado? RESP: Não, pois suas componentes diferem por valores distintos.<jupyter_code>psi1 = np.array([1+1j,2-1j]).reshape(-1,1)
psi2 = np.array([2+2j,1-2j]).reshape(-1,1)
psi2/psi1<jupyter_output><empty_output><jupyter_text>**Ex.:** Qual é o comprimento do vetor $| \psi \rangle = [2 − 3i,1 + 2i]^T$?<jupyter_code>psi1 = np.array([2-3j,1+2j]).reshape(-1,1)
print(np.sqrt(psi1.conjugate().T.dot(psi1)[0,0]).real)
np.linalg.norm(psi1)<jupyter_output><empty_output><jupyter_text>**Ex.:** Normalize o ket $| \psi \rangle = [3 − i, 2 + 6i,7 − 8i,6.3 + 4.9i, 13i,0,21.1]^T$<jupyter_code>psi1 = np.array([3-1j, 2+6j, 7-8j, 6.3+4.9j, 13j, 0, 21.1]).reshape(-1,1)
psi1_norm = (psi1 / np.linalg.norm(psi1))
psi1_norm<jupyter_output><empty_output><jupyter_text>**Ex.:** Verifique que os dois estados: $x_1 = [\frac{\sqrt 2}{2},\frac{\sqrt 2}{2}]^T$ e $x_2 = [\frac{\sqrt 2}{2},-\frac{\sqrt 2}{2}]^T$ tem módulo igual a 1 e apresente o vetor que representa a sobreposição destes estados. O vetor é: $| \psi \rangle = c_1 | x_1 \rangle + c_2 | x_2 \rangle$<jupyter_code>import numpy as np
x1 = np.array([np.sqrt(2)/2,np.sqrt(2)/2]).reshape(-1,1)
x2 = np.array([np.sqrt(2)/2,-np.sqrt(2)/2]).reshape(-1,1)
print(np.linalg.norm(x1), np.linalg.norm(x2))<jupyter_output><empty_output><jupyter_text>**Ex.:** Apresente a matrix hermitiana de $\Omega$ apresentada a seguir<jupyter_code>omega = np.array([[-1,-1j],
                  [1j, 1]])
Math(sp.Matrix(omega))
Math(sp.Matrix(np.matrix(omega).getH()))<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule o vetor resultante da atuação do operador $\Omega$ em $\psi$<jupyter_code>psi = np.array([-1+0j,-1-1j])
omega = np.array([[-1,-1j],
                  [1j, 1]])
expr = "\Omega =" + sp.latex(sp.Matrix(omega))
expr = expr + sp.latex('\psi =') + sp.latex(sp.Matrix(psi))
expr = expr + sp.latex('\Omega . \psi =') + sp.latex(sp.Matrix(omega.dot(psi)))
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Calcule as operações a seguir<jupyter_code>X = sp.Matrix([[0,1],[1,0]])
psi_bra = sp.Matrix([[1/sp.sqrt(3),sp.sqrt(sp.Rational(2,3))]]) # sp.Rational(2,3) = 2/3
psi_ket = psi_bra.T
psi_bra * X * psi_ket
exp_X = psi_bra * X * psi_ket
exp_X2 = psi_bra * X * X * psi_ket # X squared expectation
exp_X_2 = exp_X ** 2 # X expectation squared
desv_X = sp.sqrt(exp_X2[0] - exp_X_2[0]) # X standard deviation
exp_X2, exp_X_2, desv_X<jupyter_output><empty_output><jupyter_text>**Ex.:** Using the dynamics given in Equation (3.4), determine what the state of the system would be if you start with the state $[5, 5, 0, 2, 0, 15]^T$. The Dinamics is given by matrix M below:<jupyter_code>import sympy as sp
M = sp.Matrix([[0,0,0,0,0,0],
               [0,0,0,0,0,0],
               [0,1,0,0,0,1],
               [0,0,0,1,0,0],
               [0,0,1,0,0,0],
               [1,0,0,0,1,0]])
X = (sp.Matrix([5,5,0,2,0,15]))
Math("M =" + sp.latex(M) + "X =" + sp.latex(X) + "M.X =" + sp.latex(M*X))<jupyter_output><empty_output><jupyter_text>**Ex.:** For the matrix M given in Equation (3.4), calculate $M^6$. If all the marbles start at vertex 2, where will all the marbles end up after 6 time steps?<jupyter_code>Mn = lambda M,n : ["M"+str(n)+" =", M**n]
n = 6
expr1 = "M^{} =".format(n) + sp.latex(M**n)
expr2 = "X =" + sp.latex(X)
expr3 = "M^{}.X =".format(n) + sp.latex((M**n)*X)
expr = expr1 + expr2 + expr3
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Consider the following graph representing city streets. Singleheaded arrows (−→) correspond to one-way streets and double-headed arrows (←→) correspond to two-way streets. Imagine that it takes one time click to traverse an arrow. You may assume that everyone must move at every time click. If every corner starts with exactly one person, where will everyone be after one time click? After two time clicks? After four time clicks?<jupyter_code>M = sp.Matrix([[0,1,0,0,0,0,0,0,0],
               [1,0,0,0,0,0,0,0,0],
               [0,0,0,0,0,0,0,0,0],
               [0,0,0,1,0,0,0,0,0],
               [0,0,0,0,0,0,0,0,0],
               [0,0,1,0,0,0,0,0,0],
               [0,0,0,0,0,0,0,0,0],
               [0,0,0,0,0,0,1,0,0],
               [0,0,0,0,0,1,0,1,1]])
X = sp.Matrix([1,1,1,1,1,1,1,1,1])
expr1 = M*X
expr2 = (M**2)*X
expr4 = (M**4)*X
expr = "X_1 =" + sp.latex(expr1) + "X_2 =" + \
       sp.latex(expr2) + "X_4 =" + sp.latex(expr4)
Math(expr)<jupyter_output><empty_output><jupyter_text>**Ex.:** Suponha a matriz de transição de estados M apresentada abaixo. Calcule o produto elemento a elemento da matriz M com sua transposta conjugada.<jupyter_code>import sympy as sp
M = sp.Matrix([[           0,                   0,0,0,0,0,0,0],
               [1/sp.sqrt(2),                   0,0,0,0,0,0,0],
               [1/sp.sqrt(2),                   0,0,0,0,0,0,0],
               [           0,(-1+sp.I)/sp.sqrt(6),                   0,1,0,0,0,0],
               [           0,(-1-sp.I)/sp.sqrt(6),                   0,0,1,0,0,0],
               [           0,(+1-sp.I)/sp.sqrt(6),(-1+sp.I)/sp.sqrt(6),0,0,1,0,0],
               [           0,                   0,(-1-sp.I)/sp.sqrt(6),0,0,0,1,0],
               [           0,                   0,(+1-sp.I)/sp.sqrt(6),0,0,0,0,1]]) 

X1 = sp.Matrix([1,0,0,0,0,0,0,0])
expr = "M =" + sp.latex(M) + "X_1 =" + sp.latex(X1)
Math(expr)
P1 = sp.simplify(sp.matrix_multiply_elementwise(M,M.conjugate()))
expr = "P_1 =" + sp.latex(P1)
Math(expr)
M2 = sp.simplify(M*M)
expr = "M_2 =" + sp.latex(M2)
Math(expr)<jupyter_output><empty_output><jupyter_text>Interferência e Sobreposição If the weights are real numbers, the probabilities associated with them can only increase when they are added. In contrast, if the weights are complex numbers, the probabilities are associated with their modulus squared. Therefore if you add two weights that are complex numbers, the probability can either increase, add up to 0 or decrease.When two complex numbers are added and cancel each other (they add up to 0) this is referred to as **interference**. Let the state of the system be given by $X = [c_0, c_1,..., c_{n−1}], T ∈ C_n$. It is incorrect to say that theprobability of the photon’s being in position $k$ is $|c_k|$Rather, to be in state $X$ means that the particle is in some sense in all positions simultaneously. The photon passes through the top slit and the bottom slit simultaneously, and when it exits both slits, it can cancel itself out. A photon is not in a single position, rather it is in many positions, a **superposition**. Matrix below shows the probability of starting in column $j$ a photon will end up in row $i$ after 2 time periods (or 2 ticks of clock).For instance, if a photon is in state $0$ (first column), after two time periods it will end up in state $3$ (forth row) with probability $\frac{1}{6}$.Now look at the element $P_2[5,0]$. It is equal to $0$. This means that the probability of a photon being in state $0$ in instant $0$ and in state $5$ in instant $2$ is $0$, so there will be no photon at all there.This is caused by **interference**.The difficult thing to understand (to accept would be a better term) is that it is wrong to say that there's a probability for the photon to be in a state. In reality the photon is itself in all states, and each state has a definite probability associated with it.Probability of what? Of us, when trying to measure where the photon "is" will find it in that state at that time.BUT, when we're not looking (i.e.trying to measure it), photon is in a **superposition** of states. In other words: it is simultaneously in all states at the same time!<jupyter_code>P2 = sp.simplify(sp.matrix_multiply_elementwise(M2,M2.conjugate()))
expr = "P_2 =" + sp.latex(P2)
Math(expr)<jupyter_output><empty_output><jupyter_text>A classical computer can be in one state at a time (0 or 1). A quantum computer uses the superposition phenomenon to be in all classical states at the same time. In other words: a quantum computer is the ultimate idea in parallel processing. Both matter and light manifest a particle-like and a wave-like behavior Assembling Systems: Calcule o produto tensorial de M com N. O que ele representa em termos de circuitos quânticos?<jupyter_code>import sympy as sp
from sympy.physics.quantum import TensorProduct
M = sp.Matrix([[0,               sp.Rational(1,6),sp.Rational(5,6)],
               [sp.Rational(1,3),sp.Rational(1,2),sp.Rational(1,6)],
               [sp.Rational(2,3),sp.Rational(1,3),               0]])

N = sp.Matrix([[sp.Rational(1,3),sp.Rational(2,3)],
               [sp.Rational(2,3),sp.Rational(1,3)]])
MxN = TensorProduct(M,N)
expr1 = sp.latex('M = ') + sp.latex(M)
expr2 = sp.latex('N = ') + sp.latex(N)
expr3 = sp.latex('M x N = ') + sp.latex(MxN)
expr = expr1 + expr2 + expr3
Math(expr)<jupyter_output><empty_output><jupyter_text>The Bancalvo Dataset<jupyter_code>site = "https://github.com/"
diretorio = "gustavomirapalheta/classes_datasets/blob/master/"
arquivo = "bancalvo.xlsx?raw=true"
link = site + diretorio + arquivo
train = pd.read_excel(link)

lista = list(train['UF'].unique())
lista.remove('SP')
train['novaUF'] = train['UF'].replace(lista, 'NOSP')
train['lRENDA'] = np.log10(train['RENDA'])
train = train.drop(['IDENTIDADE','UF','RENDA'], axis=1)
train = pd.get_dummies(train, drop_first=True)
X = train.drop('STATUS_inad', axis=1).copy()
y = train['STATUS_inad'].copy()

from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, random_state = 53, test_size = 0.20)
Xtrain.shape, Xtest.shape, ytrain.shape, ytest.shape<jupyter_output><empty_output><jupyter_text>Meu primeiro programa com Qubits **Introdução** O objetivo desta seção é demonstrar a execução de um pequeno programa baseado em Qubits. O programa será projetado na forma de um circuito com portas lógicas quânticas. Utilizaremos a linguagem Qiskit da IBM, a partir de um programa Python, criado em um Jupyter Notebook no ambiente Google Colab  **Criação de Um Circuito com Qubits** Nosso circuito será composto de dois Qubits, os quais serão inicializados no estado $|{0}\rangle$. O primeiro Qubit passará por um Hadamart gate (colocando-o em sobreposição dos estados $|{0}\rangle$ e $|{1}\rangle$. Em seguida é aplicado um CNOT gate ao conjunto o que os colocará em entrelaçamento e sobreposição, nos estados $|{00}\rangle$ e $|{11}\rangle$. Por último medimos o estado de ambos os Qubits. A forma gráfica do circuito é apresentada no último comando da célula abaixo. Observe que o código aqui apresentado apenas cria o circuito sem no entando executa-lo.<jupyter_code># Criamos os Qubits
q = qk.QuantumRegister(2)

# Criamos os Bits
c = qk.ClassicalRegister(2)

# Criamos o circuito (com Qubits e Bits)
circuit = qk.QuantumCircuit(q, c)

# Resetamos o primeiro Qubit para o estado |0>
circuit.reset(q[0])

# Resetamos o segundo Qubit para o estado |0>
circuit.reset(q[1])

# Aplicamos um Hadamart Gate no Qubit[0]
circuit.h(q[0])

# e um CNOT Gate na saida do Hadamart (Qubit[0], Qubit[1])
#  A primeira posição é do controle e a segunda do servo
circuit.cx(q[0],q[1])

# Medimos o estado do Qubit
circuit.measure(q, c)

# Visualizamos o circuito
circuit.draw('mpl')<jupyter_output><empty_output><jupyter_text>**Execução de Um Circuito com Qubits** A computação quântica é essencialmente probabilística. Pense nela como uma simulação de Monte Carlo em planilha, onde ao invés do comando `aleatorio()` do Excel (em Português) você tem o circuito quântico. Sendo assim, cada vez que o circuito for executado, será gerada uma amostra dos possíveis resultados. Observe no entando que neste tipo de simulação, os resultados são sempre **discretos** isto é números inteiros que representam os estados que o circuito "decidiu" nos apresentar quando tentamos medi-lo.Temos então que criar o equivalente à tabela de dados da simulação de Monte Carlo no Excel. Esta "tabela" é gerada enviando o circuito para um dos *backends* que a IBM disponibiliza. O método mais rápido de executar a simulação é através do simulador `BasicAer` o que nada mais é que um simulador de Qubits que roda na própria máquina local. Este tipo de simulação é pseudo-aleatória, tal como qualquer simulação baseada em algoritmos clássicos (baseados em bits). O `BasicAer` é o ambiente local que irá rodar a simulação. A simulação em si precisa de um ambiente `backend`. Pense que ela roda em modo batch. O `BasicAer` precisa enviar o circuito para um *motor* rodar a mesma. No caso deste exemplo o *motor* ou *backend* é o `qasm_simulator`. Sendo assim, primeiro instanciamos a classe `BasicAer`, criando um objeto chamado `simulator` o qual irá rodar a simulação no backend `qasm_simulator`. Com o circuito (objeto de nome `circuit`) e o simulator (objeto de nome `simulator`) criados (isto é com as respectivas classes instanciadas), pedimos para o `circuit` ser executado pelo `simulator` um determinado número de vezes (`shots`, neste caso 1.000) através do comando qiskit `execute`.Neste caso estamos interessados nas contagens, para determinar que os Qubits entrelaçados mostraram apenas os estados $|{00}\rangle$ e $|{11}\rangle$ quando medidos. Sendo assim poderíamos ter obtido o dicionário python com os resultados fazendo diretamente: `job.result().get_counts(circuit)`<jupyter_code># Instanciando o simulador Qiskit Aer's Qasm
# Observe que este simulador roda na sua máquina local
simulator = qk.BasicAer.get_backend('qasm_simulator')

# Executando a simulação (1000 medidas)
job = qk.execute(circuit, simulator, shots=1000)

# Capturando os resultados
result = job.result()

# Obtendo as contagens dos estados medidos
counts = result.get_counts(circuit)

# Apresentando os resultados
print(counts)<jupyter_output>{'11': 485, '00': 515}<jupyter_text>**Execução do Circuito em Uma Máquina Real** Vamos agora executar nosso circuito em uma máquina quântica real. A célula abaixo precisa ser executada apenas se a `qiskit` ainda não estiver instalada no Google Colab (o que precisa ser feito uma vez por sessão). Por isso deixei a célula a seguir comentada.<jupyter_code>#!pip install qiskit[visualization]
# Vamos usar um computador quântico real
import qiskit as qk<jupyter_output><empty_output><jupyter_text>Primeiro devemos ir no site `https://quantum-computing.ibm.com`, nos registrar e obter a nossa API para código python. Ela é a "assinatura" para as máquinas da IBM que o "job" foi enviado por um usuário registrado. Por questões de privacidade eliminei o valor da chave deste Jupyter Notebook. Como a mesma já foi salva na área do meu ambiente Colab (através do comando `qk.IBMQ.load_account(chave)`), posso apenas carrega-la com `qk.IBMQ.load_account`. Por isso a célula abaixo está comentada.<jupyter_code># Obter a chave para uso da API IBM em : https://quantum-computing.ibm.com
# chave = 'A minha chave já foi salva neste ambiente uma vez'
# qk.IBMQ.save_account(chave)
# Carregar a conta na sessão atual
qk.IBMQ.load_account()<jupyter_output>ibmqfactory.load_account:WARNING:2021-05-28 21:37:16,733: Credentials are already in use. The existing account in the session will be replaced.<jupyter_text>Carregamos nosso usuário na célula anterior. Agora precisamos indicar um provedor de serviço. O provedor de serviço default é apresentado a seguir apenas por uma questão didática, pois ele já foi carregado pelo comando `qk.IBMQ.load_account()`. Para tornar nosso trabalho mais estruturado definimos este provedor como `provider_1`. O provedor precisa de um backend (uma máquina) tal como no caso anterior. Usaremos para o provedor default (`provider_1`) o backend do simulador da IBM `ibmq_qasm_simulator`. Observe que agora o simulador roda na nuvem.<jupyter_code># O provedor default (público) é hub='ibm-q', group='open, project='main'
# O código abaixo é executado para conhecimento
provider_1 = qk.IBMQ.get_provider(hub='ibm-q', group='open', project='main')

# No provedor público vamos utilizar o simulador de nuvem
backend_1 = provider_1.get_backend('ibmq_qasm_simulator')<jupyter_output><empty_output><jupyter_text>Utilizarei também o provedor de serviços para instituições de ensino da IBM. Os dados podem ser vistos a seguir. Neste caso chamaremos este provedor de `provider_2`. Para o caso do `provider_2` vamos utilizar como backend uma máquina real, por exemplo a `ibmq_jakarta`. Outras máquinas poderiam ter sido utilizadas. Para uma lista completa, devemos olhar as máquinas disponíveis para a nossa conta em `https://quantum-computing.ibm.com`.<jupyter_code># Utilizaremos também o provedor abaixo, o qual tem jobs ilimitados
provider_2 = qk.IBMQ.get_provider(hub='ibm-q-education', group='fgv-1', project='ml-business-app')

# Para este provedor utilizaremos a máquina ibmq_jakarta
backend_2 = provider_2.get_backend('ibmq_jakarta')<jupyter_output><empty_output><jupyter_text>Neste segundo exemplo vamos criar um circuito um pouco distinto. Vamos gerar um número ao acaso entre 0 e 7. Para isso precisamos de 3 Qubits. Cada Qubit vai gerar um número 0 ou 1 ao acaso. Ao final combinamos os 3 números binários de 1 dígito em um número binário de 3 dígitos e convertemos o mesmo para base decimal. O circuito pode ser visto a seguir.<jupyter_code># Com n Qubits pode-se gerar um número aleatório entre 0 e 2^n - 1
n = 3

q = qk.QuantumRegister(n)
c = qk.ClassicalRegister(n)
circ = qk.QuantumCircuit(q, c)

# Aplicando um Hadamart nos 3 Qubits
for i in range(n):
  circ.h(q[i])

# Efetuando a medida
circ.measure(q,c)

# Visualisando o circuito
circ.draw('mpl')<jupyter_output><empty_output><jupyter_text>O comando a seguir envia o pedido de uma amostra (um "shot") para o backend escolhido (`ibmq_jakarta`) através do nosso provedor (`provider_2`). Se olharmos a nossa fila, na nossa conta, no site `https://quantum-computing.ibm.com` veremos que temos um *job* enviado para a máquina `ibmq_jakarta`.<jupyter_code># qk.execute envia para o backend. Conferindo no iqmq explorer aparece o job
new_job = qk.execute(circ, backend_2, shots=1)<jupyter_output><empty_output><jupyter_text>Depois que o código for executado, o resultado é devolvido da mesma forma que na simulação local através de um dicionário python. Observe que no caso da simulação que gerou este arquivo, a máquina devolveu `110:1`, ou seja uma contagem (uma medição) e o resultado desta única medição foi o estado `110` do conjunto de três qubits. Observe que neste caso cada Qubit estava em sobreposição apenas consigo mesmo, ou seja os Qubits não estavam entrelaçados entre si.<jupyter_code># este resultado fica armazenado local. Porém só estará disponível depois 
#  do job ter sido executado. Ele retorna um dicionário python.
new_job.result().get_counts()<jupyter_output><empty_output><jupyter_text>A partir deste ponto são mostradas as etapas em linguagem python para converter a chave `110` deste dicionário python de resultados em um número decimal. A parte "quântica" do nosso programa Python terminou aqui.<jupyter_code># Extraimos as chaves deste dicionário python
new_job.result().get_counts().keys()
# Convertemos as chaves em uma lista
list(new_job.result().get_counts().keys())
# Obtemos o primeiro elemento da lista
list(new_job.result().get_counts().keys())[0]
# Convertemos em um número inteiro decimal
int(list(new_job.result().get_counts().keys())[0])
# Convertemos em um número inteiro binário e obtemos sua representação decimal
int(list(new_job.result().get_counts().keys())[0],2)<jupyter_output><empty_output><jupyter_text>Obtivemos então como resultado o número 6.  Exercícios de Criação e Execução de Programas em Qiskit Para manter as seções independentes, apresentamos abaixo o código para setup do ambiente. A parte de setup da qiskit é mantida inativa para evitar a instalação repetida em um mesmo jupyter notebook.<jupyter_code>executa = False

if executa:
  !pip install qiskit -q
  !pip install qiskit[visualization] -q

import numpy as np
np.set_printoptions(precision=3, suppress=True)
from matplotlib import pyplot as plt
%matplotlib inline

import pandas as pd
import sklearn as sk
import qiskit as qk<jupyter_output><empty_output><jupyter_text>Parte I: Obtenção de contagens **1o Circuito** Criação, Execução e Obtenção de Contagens<jupyter_code>from qiskit import QuantumCircuit
circuit = QuantumCircuit(2, 2)
circuit.h(0)
circuit.cx(0,1)
circuit.measure([0,1], [0,1])
display(circuit.draw('mpl'))

from qiskit.providers.aer import AerSimulator
AerSimulator().run(circuit, shots=1000).result().get_counts()<jupyter_output><empty_output><jupyter_text>**1o Circuito - 2a Simulação** Execução e Obtenção de Contagens<jupyter_code>AerSimulator().run(circuit, shots = 1000).result().get_counts()<jupyter_output><empty_output><jupyter_text>**2o Circuito** Criação, Execução e Obtenção de Contagens<jupyter_code>from qiskit import QuantumCircuit
circuito = QuantumCircuit(3,3)
for i in range(3):
  circuito.h(i)
  circuito.measure(i,i)
display(circuito.draw('mpl'))

from qiskit.providers.aer import AerSimulator
AerSimulator().run(circuito, shots = 1).result().get_counts()<jupyter_output><empty_output><jupyter_text>**3o Circuito** Criação, Execução e Obtenção de Contagens<jupyter_code>from qiskit import QuantumCircuit
qc = QuantumCircuit(4,4)
qc.x([0,1])
qc.cx([0,1],[2,2])
qc.ccx(0,1,3)
qc.measure([0,1,2,3],[0,1,2,3])
display(qc.draw(output='mpl'))

from qiskit.providers.aer import AerSimulator
AerSimulator().run(qc, shots = 2048).result().get_counts()<jupyter_output><empty_output><jupyter_text>**4o Circuito** Criação, Execução e Obtenção de Contagens<jupyter_code>from qiskit import QuantumCircuit
qc = QuantumCircuit(1,1)
qc.x(0)
qc.h(0)
qc.h(0)
qc.measure(0,0)
display(qc.draw(output='mpl'))

from qiskit.providers.aer import AerSimulator
AerSimulator().run(qc, shots=1).result().get_counts()<jupyter_output><empty_output><jupyter_text>Parte II: Observação do Vetor de Estados de um Circuito Quântico **Exercício 1** Criação de um circuito com um Qubit (registrador quântico), um Bit (registrador clássico), um gate NOT e uma medida. Determinação do vetor de estados do Qubit, da matriz de transformação do circuito (antes da medida) e do resultado de 1.000 medidas efetuadas no Qubit.<jupyter_code># Importação da biblioteca qiskit
import qiskit as qk

# Criação de um registrador quântico
qr = qk.QuantumRegister(1)

# Criação de um registrador clássico
cr = qk.ClassicalRegister(1)

# Criação de um circuito quântico composto de um registrador tipo 
#  Qubit e um registrador tipo Bit.
circuito = qk.QuantumCircuit(qr, cr)
print("Circuito 1 - Apenas os Registradores")
print(circuito.draw())
# Executar a célula anterior
# Inserção de uma porta lógica x (NOT Gate) no circuito
circuito.x(qr)
print("Circuito 1 - Registradores e gate X no registrador quântico")
print(circuito.draw())
# Executar a célula anterior
# Determinação do vetor de estados do registrador quântico
job = qk.execute(experiments = circuito, 
                 backend = qk.BasicAer.get_backend('statevector_simulator'))
print("Vetor de Estado do Registrador Quântico")
from qiskit.tools.visualization import plot_bloch_multivector
display(plot_bloch_multivector(job.result().get_statevector()))
# Executar a célula anterior
# Determinação da matriz de transformação do circuito (até este vetor de estados)
job = qk.execute(experiments = circuito,
                 backend = qk.BasicAer.get_backend('unitary_simulator'))
print("Matriz de Transformação (até este ponto)")
print(job.result().get_unitary())
# Executar a célula anterior
# Inserção de uma medida no registrador quântico
circuito.measure(qr, cr)
print("Circuito 1 - Registradores, gate X e medida no registrador quântico")
print(circuito.draw())
# Executar a célula anterior
# Execução de mil medidas no registrador quântico
print("Mil medidas do estado do registrador quântico")
job = qk.execute(experiments = circuito, 
                 backend = qk.BasicAer.get_backend('qasm_simulator'),
                 shots = 1000)
# Executar a célula anterior
# Contagem dos resultados das medidas no registrador quântico
print("Contagem dos Resultados")
print(job.result().get_counts())
# Executar a célula anterior
# Histograma dos resultados das medidas no registrador quântico
print("Histograma dos Resultados")
from qiskit.tools.visualization import plot_histogram
plot_histogram(data = job.result().get_counts(), figsize=(4,3))<jupyter_output>Histograma dos Resultados<jupyter_text>**Exercício 2** Teletransporte do estado de um Qubit para outro.<jupyter_code>qk.BasicAer.backends()
import qiskit as qk

circuito = qk.QuantumCircuit(2,2)
#circuito.h(0)
circuito.cnot(0,1)
circuito.barrier()
print(circuito.draw())
job = qk.execute(experiments = circuito,
                 backend = qk.BasicAer.get_backend('unitary_simulator'))

import numpy as np
np.set_printoptions(precision=2) 
display(np.real(job.result().get_unitary()))<jupyter_output>░ 
q_0: ──■───░─
     ┌─┴─┐ ░ 
q_1: ┤ X ├─░─
     └───┘ ░ 
c: 2/════════<jupyter_text>Parte III: Circuitos com várias portas lógicas quânticas<jupyter_code>!pip install qiskit
import qiskit as qk

provider0 = qk.BasicAer
backend0 = provider0.get_backend('qasm_simulator')

# Chave de APIs obtida em https://quantum-computing.ibm.com
chave1 = '4ae468408574abb7fd53a78dd2e0a256b2be7cd68be45c0741e7c670c0947e57eb8'
chave2 = '<KEY>'
chave = chave1 + chave2
qk.IBMQ.save_account(chave)

qk.IBMQ.load_account()

provider1 = qk.IBMQ.get_provider(hub='ibm-q', group='open', 
                                 project='main')
backend1 = provider1.get_backend('ibmq_qasm_simulator')

provider2 = qk.IBMQ.get_provider(hub='ibm-q-education', group='fgv-1', 
                                 project='ml-business-app')
backend2 = provider2.get_backend('ibmq_jakarta')
list(qk.__qiskit_version__.items())<jupyter_output><empty_output><jupyter_text>O primeiro circuito será composto de um Qubit e um bit. O Qubit é inicializado no estado $|{0}\rangle$, passa por uma porta X (NOT gate) e em seguida é feita uma medida.<jupyter_code>q = qk.QuantumRegister(1)
c = qk.ClassicalRegister(1)
circuito1 = qk.QuantumCircuit(q, c)
circuito1.reset(q[0])
circuito1.measure(q, c)
print(circuito1)<jupyter_output>┌─┐
q0_0: ─|0>─┤M├
           └╥┘
c0: 1/══════╩═
            0<jupyter_text>Abaixo executamos 10 amostras (*shots*) do circuito, no `backend2` o qual foi configurado para corresponder à maquina `ibmq_jakarta`.<jupyter_code>jobBackend2 = qk.execute(circuito1, backend2, shots=10)<jupyter_output><empty_output><jupyter_text>Lembre-se que neste caso, a impressão dos resultados deve ficar em outra célula, caso contrário o Jupyter notebook ficará "travado", a espera do job ser executado na máquina física correspondente.<jupyter_code>jobBackend2.result().get_counts()<jupyter_output><empty_output><jupyter_text>Para os demais circuitos e simulações deste Jupyter notebook vamos executa-las no simulador local `BasicAer`, o qual foi configurado com o nome de `backend0`.<jupyter_code>jobLocal0 = qk.execute(circuito1, backend0, shots=1000)
counts = jobLocal0.result().get_counts(circuito1)
print(counts)
q = qk.QuantumRegister(2)
c = qk.ClassicalRegister(2)
circuito2 = qk.QuantumCircuit(q, c)
circuito2.reset(q[0])
circuito2.measure(q[0], c[0])

circuito2.reset(q[1])
circuito2.measure(q[1], c[1])
circuito2.draw()
job2 = qk.execute(circuito2, backend0, shots=1000)
job2.result().get_counts()
q = qk.QuantumRegister(1)
c = qk.ClassicalRegister(1)
circuito3 = qk.QuantumCircuit(q, c)
circuito3.reset(q[0])
circuito3.h(q[0])
circuito3.measure(q[0],c[0])
print(circuito3.draw())
job3 = qk.execute(circuito3, backend0, shots=1000)
job3.result().get_counts()
q = qk.QuantumRegister(1)
c = qk.ClassicalRegister(1)
circuito4 = qk.QuantumCircuit(q, c)
circuito4.reset(q[0])
circuito4.x(q[0])
circuito4.h(q[0])
circuito4.h(q[0])
circuito4.measure(q[0], c[0])
print(circuito4)
job4 = qk.execute(circuito4, backend0, shots=1000)
job4.result().get_counts()
q = qk.QuantumRegister(2)
c = qk.ClassicalRegister(2)
circuito5 = qk.QuantumCircuit(q,c)
circuito5.reset(q)
circuito5.h(q)
circuito5.measure(q,c)
print(circuito5)
job5 = qk.execute(circuito5, backend0, shots=1000)
job5.result().get_counts()
q = qk.QuantumRegister(2)
c = qk.ClassicalRegister(2)
circuito6 = qk.QuantumCircuit(q, c)
circuito6.reset(q)
circuito6.h(q[0])
circuito6.cnot(q[0],q[1])
circuito6.measure(q,c)
print(circuito6)
job6 = qk.execute(circuito6, backend0, shots=1000)
job6.result().get_counts()<jupyter_output>┌───┐     ┌─┐   
q107_0: ─|0>─┤ H ├──■──┤M├───
             └───┘┌─┴─┐└╥┘┌─┐
q107_1: ─|0>──────┤ X ├─╫─┤M├
                  └───┘ ║ └╥┘
 c33: 2/════════════════╩══╩═
                        0  1<jupyter_text>Parte IV: Circuito de "tele-transporte" (do estado de Qubit para outro)<jupyter_code># Instalação do Pacote Qiskit (executar uma vez por sessão)
!pip install qiskit
# Importação da Biblioteca
import qiskit as qk
# Criação de um circuito com 3 Qubits e 3 Bits
circuito = qk.QuantumCircuit(3,3)
# Desenho do circuito até o momento
print(circuito.draw())
# Criação de um backend para simulação do vetor de estados
sim_estados = qk.BasicAer.get_backend("statevector_simulator")
# Criação de um job para visualização do vetor de estados neste
#  ponto do circuito
resultado = qk.execute(experiments = circuito,
                       backend = sim_estados).result()
# Ajuste do formato de impressão da numpy
import numpy as np
np.set_printoptions(precision=3, suppress=True)
# Impressão dos possíveis estados dos três qubits e
#  do vetor de estados correspondente. Observe que
#  o estado '000' aparece com o valor 1
print([np.binary_repr(x, width=3) for x in range(8)])
print(resultado.get_statevector(circuito))
# Importação da função para desenho da Bloch Sphere 
from qiskit.tools.visualization import plot_bloch_multivector
# Apresentamos as esferas de Bloch com a ordem dos Qubits 
#  invertida (mais significativo à esquerda), para tornar
#  mais fácil a visualização da sequência de estados
print("Representação do vetor de estados pela Bloch Sphere")
plot_bloch_multivector(resultado.get_statevector(circuito),
                               reverse_bits=True)
# Inserção de uma porta X (NOT gate) no Qubit 0
circuito.x(0)
circuito.draw()
# Inserção de uma barreira. Serve para preservar o circuito
# entre compilações e execuções.
circuito.barrier()
circuito.draw()
# Vetor de estado do circuito após o gate X ser aplicado no
#  Qubit 0. 
resultado = qk.execute(experiments = circuito,
                       backend = sim_estados).result()
print([np.binary_repr(x,width=3) for x in range(8)])
print(resultado.get_statevector())
#Observe a alteração do vetor na Bloch Sphere correspondente.
resultado = qk.execute(experiments = circuito, 
                       backend = sim_estados).result()
plot_bloch_multivector(resultado.get_statevector(), reverse_bits=True)
# Colocar o Qubit 1 em superposição através de
#  um Hadamard Gate
circuito.h(1)
circuito.draw()
# Vetor de Estados do Circuito após a colocação do Qubit
#  1 em sobreposição.
resultado = qk.execute(experiments = circuito,
                       backend = sim_estados).result()
print(resultado.get_statevector())
# Apresentando o statevector com valores inteiros.
#  Observe que a sobreposição no Qubit 1 faz com
#  que o vetor de estados tenha 1 nas posições do 
#  vetor relativas aos estados "001" e "011".
#  Lembre-se:
#  1o) As posições dos estados são: q2, q1, q0 e
#  2o) Os Qubits q2 e q0 estão em |0> e |1>
#  3o) O Qubit q1 está em sobreposição de |0> e |1>
resultado = qk.execute(experiments = circuito, 
                       backend = sim_estados).result()
print([np.binary_repr(x,width=3) for x in range(8)])
print(resultado.get_statevector()*np.sqrt(2))
plot_bloch_multivector(resultado.get_statevector(),
                       reverse_bits=True)
# Entrelaçamento entre os qubits 1 e 2 através de um
#  CNOT gate com controle em q1 e alvo em q2. 
circuito.cnot(1,2)
circuito.draw()
# O entrelaçamento faz os qubits q2 e q1 irem para |00> 
#  ou |11>. O qubit q0 continua em |1>. Logo os estados
#  do conjunto ficam |001> e |111>
resultado = qk.execute(circuito, sim_estados).result()
print([np.binary_repr(x, width=3) for x in range(8)])
print((resultado.get_statevector()*np.sqrt(2)))
# Tal como era de se esperar, não é mais possível mostrar
#  o estado de cada Qubit entrelaçado de forma individual
#  Observe que o Qubit 2 por exemplo não está mais no 
#  estado individual |0> em conjunto com o estado |0> do
#  Qubit 1. Os Qubits 2 e 1 formam agora um bloco 
#  indivisível o qual está em sobreposição de |00> e |11>
resultado = qk.execute(circuito, sim_estados).result()
plot_bloch_multivector(resultado.get_statevector(),
                       reverse_bits=True)
# CNOT gate entre o Qubit q0 (controle) e
#  o Qubit q1 (alvo)
circuito.cnot(0,1)
circuito.draw()
# Observe agora o resultado da aplicação de um CNOT 
#  gate em q0 (controle) e q1 (alvo). O estado inicial
#  era |q2, q1, q0> = |001> e |111>.  O CNOT com controle
#  em q0 inverte o estado de q1. Isto vai gerar como 
#  resultado |011> e |101>. O interessante aqui é que 
#  foi alterado o estado de um dos Qubits previamente
#  entrelaçados, mas isto não se refletiu no Qubit q2.

# Isto é importante. Pode-se afetar o estado de um dos
#  Qubits entrelaçado. Isto não vai se refletir no 
#  estado do 2o Qubit. O entrelaçamento só "aparece"
#  quando efetuamos uma medida. Isto quer dizer que
#  caso tivéssemos feito uma medida agora, o 
#  entrelaçamento entre q1 e q2 iria se manifestar 
#  pelos estados 01 e 10, os únicos que iriam
#  aparecer na saida.

resultado = qk.execute(circuito, sim_estados).result()
print([np.binary_repr(x,width=3) for x in range(8)])
print(resultado.get_statevector()*np.sqrt(2))
# Aplicação de um Hadamard gate em q0
circuito.h(0)
circuito.draw()
circuito.barrier()
circuito.draw()
# Criação de uma função lambda para geração dos
#  estados binários possíveis dos três qubits
estados = lambda n : [np.binary_repr(x, width=n) for x in range(2**n)]
estados(3)
resultado = qk.execute(circuito,
               qk.BasicAer.get_backend('statevector_simulator')).result()
print(estados(3))
print(resultado.get_statevector()*2)
# Duas medidas, em q0 e q1
circuito.measure(0,0)
circuito.draw()
circuito.measure(1,1)
circuito.draw()
circuito.barrier()
circuito.draw()
# Para avaliar os resultados das medidas nos qubits q0 e q1
#  criamos um novo simulador, agora de medidas
sim_valores = qk.BasicAer.get_backend('qasm_simulator')
# Para apresentar os resultados das medições, vamos utilizar
#  outro recurso gráfico da Qiskit: os histogramas de resultados
from qiskit.tools.visualization import plot_histogram
resultado = qk.execute(circuito, sim_valores, shots=1000).result()
plot_histogram(resultado.get_counts(), figsize=(5,3))
# Em seguida acrescentamos um CNOT em (1,2)
#  1 = controle, 2 = alvo
circuito.cnot(1,2)
circuito.draw()
 # Com sempre vamos observar o vetor de estados
 resultado = qk.execute(circuito, sim_estados).result()
 print(estados(3))
 print(resultado.get_statevector())
plot_bloch_multivector(resultado.get_statevector(),
                       reverse_bits = True)
# Por último incluimos um Z gate entre as portas 
#  q0 e q2 ...
circuito.cz(0,2)
circuito.draw()
# ... e uma medida em no Qubit 2
circuito.measure(2,2)
circuito.draw()
# Executando o circuito e obtendo as medidas
resultado = qk.execute(circuito, sim_valores, shots=1000).result()
plot_histogram(resultado.get_counts())<jupyter_output><empty_output><jupyter_text>Qiskit - Fourier Checking<jupyter_code>import qiskit.quantum_info as qi
from qiskit.circuit.library import FourierChecking
from qiskit.visualization import plot_histogram
f = [1,-1,-1,-1]
g = [1,1,-1,-1]
circ = FourierChecking(f=f, g=g)
circ.draw()
zero = qi.Statevector.from_label('00')
sv = zero.evolve(circ)
probs = sv.probabilities_dict()
plot_histogram(probs)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12444545730780918, 'fraction_numerical': 0.026177453661501062, 'mean_word_length': 4.715116783884692, 'pattern_counts': {'":': 0, '<': 310, '<?xml version=': 0, '>': 333, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '77', 'dir': 'jupyter-structured-clean-dedup', 'id': '312060', 'license': 'MIT', 'n_tokens_mistral': 24839, 'n_tokens_neox': 23890, 'n_words': 7595, 'path': 'QML_Full.ipynb', 'repo_name': 'gustavomirapalheta/Quantum-Computing-with-Qiskit', 'size': '65931'}