# 1 Introduction

### 1-1 Quantum Simulation
One of the leading uses for quantum computers will be to simulate quantum systems such as molecules or engineered materials. Actually executing a quantum simulation on a current quantum computer, however, can be difficult and error prone. Your objective for this Open Science Prize is to significantly improve the state-of-the-art for quantum simulation (details in Section 2).

The quantum system you will simulate is a quantum spin-1/2 model, where each qubit represents a quantum [spin-1/2](https://en.wikipedia.org/wiki/Spin-1/2) particle in a 1D chain. Quantum spin models have some amazing properties and uses. Computationally speaking, [certain optimization problems can be mapped](https://en.wikipedia.org/wiki/Spin-1/2) to spin models and hence minimized on a quantum computer. Physically speaking, spin models show a variety of quantum behavior such as large entangled states, quantum phases of matter, quantum many-body affects, and many other open questions in physics research.

There are many different spin models, but we will focus on the [$XXX$ Heisenberg spin model](https://en.wikipedia.org/wiki/Quantum_Heisenberg_model#XXX_model). Though it is not as well known as the [transverse-field Ising model](https://en.wikipedia.org/wiki/Transverse-field_Ising_model), the $XXX$ Heisenberg model is of practical and theoretical interest. It can also be difficult to simulate, which is where you come in.

The notebook is organized as follows:
1) Section 1 (labeled 1-X) provides you with brief introductions to the $XXX$ Heisenberg model, time evolving a Hamiltonian, and decomposing a time evolution operator into gates for a quantum computer to execute.
2) Section 2 (labeled 2-X) outlines the specific requirements and boundaries associated with completing and competing in the Open Science Prize. Continuing from Section 1, the notebook showns an example solution.

```python
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 16})  # enlarge matplotlib fonts

# Import qubit states Zero (|0>) and One (|1>), and Pauli operators (X, Y, Z)
from qiskit.opflow import Zero, One, I, X, Y, Z

# Suppress warnings
import warnings
warnings.filterwarnings('ignore')
```

### 1-2 The $XXX$ Heisenberg Spin Model

To simulate a quantum system, we first need to define how the quantum particles that make up the system interact with each other. In quantum physics, this is done by defining a [Hamiltonian operator](https://en.wikipedia.org/wiki/Hamiltonian_(quantum_mechanics)). For this Open Science Prize, you will work with the $XXX$ Heisenberg Hamiltonian $H_{\text{Heis}}$ as defined below

$$
\begin{equation}
H_{\text{Heis}} = \sum_{\langle ij \rangle}^{N} J \left(\sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}\right).
\end{equation}
$$

$N$ is the number of spin-1/2 particles in model. The operators $\sigma_x$, $\sigma_y$, and $\sigma_z$ are [Pauli operators](https://en.wikipedia.org/wiki/Pauli_matrices) where the $i$ and $j$ superscripts label which qubit they act on. For example, $\sigma_x^{(1)}$ would be the $\sigma_x$ operator acting on only qubit 1 (which is the 2nd qubit since indexing starts at 0). This version of the general Heisenberg spin model is called $XXX$ because the same $J$ value multiplies each pair of Pauli operators. The sum notation $\langle ij \rangle$ means the sum is over nearest neighbors (only qubits next to each other interact), and $J$ is the interaction strength, which we will set $J=1$.

You will work with the explicit case of $N=3$ with the 3 spins arranged in a line. Written out fully, the Hamiltonian is
$$
H_{\text{Heis3}} = \sigma_x^{(0)}\sigma_x^{(1)} + \sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(0)}\sigma_z^{(1)} + \sigma_z^{(1)}\sigma_z^{(2)}.
$$
Now that we have a Hamiltonian ($H_{\text{Heis3}}$), we can use it to determine how the quantum system of 3 spin-1/2 particles changes in time.

*See the supplementary material for further details on $H_{\text{Heis}}$, $H_{\text{Heis3}}$ and its matrix repsentation.*

```python
# Returns the matrix representation of the XXX Heisenberg model for 3 spin-1/2 particles in a line
def H_heis3():
    # Interactions (I is the identity matrix; X, Y, and Z are Pauli matricies; ^ is a tensor product)
    XXs = (I^X^X) + (X^X^I)
    YYs = (I^Y^Y) + (Y^Y^I)
    ZZs = (I^Z^Z) + (Z^Z^I)

    # Sum interactions
    H = XXs + YYs + ZZs

    # Return Hamiltonian
    return H
```

### 1-3 Time Evolution

Knowing the Hamiltonian, we can determine how quantum states of that system evolve in time by solving the [Schrödinger equation](https://en.wikipedia.org/wiki/Schrödinger_equation)
$$
i\hbar \dfrac{d}{dt}|\psi(t)\rangle = H |\psi(t)\rangle
$$

For simplicity, let's set $\hbar = 1$. We know that the Hamiltonian $H_{\text{heis3}}$ does not change in time, so the solution to the Schrödinger equation is an exponential of the Hamiltonian operator
$$
\begin{align}
U_{\text{Heis3}}(t) &= e^{-it H_\text{Heis3}} = \exp\left(-it H_\text{Heis3}\right) \\
U_{\text{Heis3}}(t) &= \exp\left[-it \sum_{\langle ij \rangle}^{N=3} \left(\sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}\right) \right] \\
U_{\text{Heis3}}(t) &= \exp\left[-it \left(\sigma_x^{(0)}\sigma_x^{(1)} + \sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(0)}\sigma_z^{(1)} + \sigma_z^{(1)}\sigma_z^{(2)}\right) \right]
\end{align}
$$.

Now that we have the time evolution operator $U_{\text{Heis3}}(t)$, we can simulate changes in a state of the system ($|\psi(t)\rangle$) over time $|\psi(t)\rangle = U_{\text{Heis3}}(t)|\psi(t=0)\rangle$.

*See the supplementary material for more information on exponentiating an operator or matrix.*

```python
# Returns the matrix representation of U_heis3(t) for a given time t assuming an XXX Heisenberg Hamiltonian for 3 spins-1/2 particles in a line
def U_heis3(t):
    # Compute XXX Hamiltonian for 3 spins in a line
    H = H_heis3()

    # Return the exponential of -i multipled by time t multipled by the 3 spin XXX Heisenberg Hamilonian
    return (t * H).exp_i()
```

### 1-4 Classical Simulation of $H_{\text{Heis3}}$

Now that we have the operator $U_{\text{Heis3}}(t)$ which describes how any state of the quantum system changes in time, we can simulate the $XXX$ Heisenberg spin model! Just for fun, let's simulate the system using a classical computer first.

(Fun fact: Notice the size of $U_{\text{Heis3}}(t)$ in matrix form. It's representated by an $8$x$8$ matrix. This is because there are $2^3=8$ states in the $N=3$ system. If the simulation were of $50$ particles ($N=50$), $U_{\text{Heis}}(t)$ would be approximately $10^{15}$x$10^{15}$! Well beyond the capacity of today's computers.)

```python
# Define array of time points
ts = np.linspace(0, np.pi, 100)

# Define initial state |110>
initial_state = One^One^Zero

# Compute probability of remaining in |110> state over the array of time points
 # ~initial_state gives the bra of the initial state (<110|)
 # @ is short hand for matrix multiplication
 # U_heis3(t) is the unitary time evolution at time t
 # t needs to be wrapped with float(t) to avoid a bug
 # (...).eval() returns the inner product <110|U_heis3(t)|110>
 #  np.abs(...)**2 is the modulus squared of the innner product which is the expectation value, or probability, of remaining in |110>
probs_110 = [np.abs((~initial_state @ U_heis3(float(t)) @ initial_state).eval())**2 for t in ts]

# Plot evolution of |110>
plt.plot(ts, probs_110)
plt.xlabel('time')
plt.ylabel(r'probability of state $|110\rangle$')
plt.title(r'Evolution of state $|110\rangle$ under $H_{Heis3}$')
plt.grid()
plt.show()
```

### 1-5 Decomposition of $U_{\text{Heis3}}(t)$ Into Quantum Gates

To execute $U_{\text{Heis3}}(t)$ on a [circuit model](https://qiskit.org/documentation/apidoc/circuit.html) quantum computer, we must decompose $U_{\text{Heis3}}(t)$ into a product of single and two-qubit gates that are native to that quantum computer. There are a [variety of techniques](https://en.wikipedia.org/wiki/Hamiltonian_simulation) to do this. For this Open Science Prize, you will only use [Trotterization](https://en.wikipedia.org/wiki/Hamiltonian_simulation#Product_Formulas) (which is sometimes refered to as Suzuki-Trotterization decomposition, Trotter formulas, product formulas, and other names).

Below, we show an example Trotterization as outlined in \[1-2\].

Since the [Pauli operators do not commute](https://en.wikipedia.org/wiki/Pauli_matrices#Commutation_relations) with each other, the exponential $U_{\text{Heis3}}(t)$ cannot be split into a product of simpler exponentials. However, we can approximate $U_{\text{Heis3}}(t)$ as a product of simpler exponentials through Trotterization. Consider a subsystem of 2 spin-1/2 particles within the larger 3 spin system. The Hamiltonian on spins $i$ and $j$ ($i,j \in \{0,1,2\}$) would be $H^{(i,j)}_{\text{Heis2}} = \sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}$. Rewritting $U_{\text{Heis3}}(t)$ in terms of the two possible subsystems within the total $N=3$ system you will simulate,

$$
U_{\text{Heis3}}(t) = \exp\left[-i t \left(H^{(0,1)}_{\text{Heis2}} + H^{(1,2)}_{\text{Heis2}} \right)\right].
$$

$H^{(0,1)}_{\text{Heis2}}$ and $H^{(1,2)}_{\text{Heis2}}$ do not commute, so $U_{\text{Heis3}}(t) \neq \exp\left(-i t H^{(0,1)}_{\text{Heis2}}\right) \exp\left(-i t H^{(1,2)}_{\text{Heis2}} \right)$. But, this product decomposition can be approximated with Trotterization which says $U_{\text{Heis3}}(t)$ is approximately a short evolution of $H^{(0,1)}_{\text{Heis2}}$ (time = $t/n$) and followed by a short evolution of $H^{(1,2)}_{\text{Heis2}}$ (time = $t/n$) repeated $n$ times

$$
\begin{align}
U_{\text{Heis3}}(t) &= \exp\left[-i t \left(H^{(0,1)}_{\text{Heis2}} + H^{(1,2)}_{\text{Heis2}} \right)\right] \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}H^{(0,1)}_{\text{Heis2}}\right) \exp\left(\dfrac{-it}{n}H^{(1,2)}_{\text{Heis2}} \right)\right]^n.
\end{align}
$$

$n$ is the number of Trotter steps, and as $n$ increases, the approximation becomes more accurate. (Note that how a untiary is split up into subsystems for Trotterization is not necessarily unique.) The decomposition goes further. Within each 2 spin subsystems, the Pauli operator pairs ($\sigma_x^{(i)}\sigma_x^{(j)}$, $\sigma_y^{(i)}\sigma_y^{(j)}$, and $\sigma_z^{(i)}\sigma_z^{(j)}$) commute. This means we can decompose the exponential of a subsystem Hamiltonian ($H^{(i,j)}_{\text{Heis2}}$) into a product of even simpler exponentials getting us closer to a gate implementation of $U_{\text{Heis3}}(t)$

$$
\begin{align}
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}H^{(0,1)}_{\text{Heis2}}\right) \exp\left(\dfrac{-it}{n}H^{(1,2)}_{\text{Heis2}} \right)\right]^n \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}\left(\sigma_x^{(0)}\sigma_x^{(1)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_z^{(0)}\sigma_z^{(1)}\right)\right) \exp\left(\dfrac{-it}{n}\left(\sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(1)}\sigma_z^{(2)}\right)\right)\right]^{n} \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}\sigma_x^{(0)}\sigma_x^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_y^{(0)}\sigma_y^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_z^{(0)}\sigma_z^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_x^{(1)}\sigma_x^{(2)}\right) \exp\left(\dfrac{-it}{n}\sigma_y^{(1)}\sigma_y^{(2)}\right) \exp\left(\dfrac{-it}{n}\sigma_z^{(1)}\sigma_z^{(2)}\right)\right]^{n}
\end{align}
$$

For simplicity, and to use more common notation, let's rename the products with $XX(2t) = \exp\left(-it \sigma_x\sigma_x\right)$, $YY(2t) = \exp\left(-it \sigma_y\sigma_y\right)$, and $ZZ(2t) = \exp\left(-it \sigma_z\sigma_z\right)$ and rewrite the Trotterized $U_{\text{Heis3}}(t)$
$$
U_{\text{Heis3}}(t) \approx \left[XX\left(\frac{2t}{n}\right)^{(0,1)} YY\left(\frac{2t}{n}\right)^{(0,1)} ZZ\left(\frac{2t}{n}\right)^{(0,1)} XX\left(\frac{2t}{n}\right)^{(1,2)} YY\left(\frac{2t}{n}\right)^{(1,2)} ZZ\left(\frac{2t}{n}\right)^{(1,2)} \right]^{n}
$$
And that's it! We have now approximately decomposed $U_{\text{Heis3}}(t)$ into two-qubit gates $XX(t)$, $YY(t)$, and $ZZ(t)$. These gates are not native to superconducting qubits, but in Section 2, they will be further decomposed into native single and two-qubit gates with *further details found in the supplementary material.*

\[1\] Y. Salathe, et al., *Digital Quantum Simulation of Spin Models with Circuit Quantum Electrodynamics*, [Phys. Rev. X **5**, 021027 (2015)](https://link.aps.org/doi/10.1103/PhysRevX.5.021027)

\[2\] F. Tacchino, et al., *Quantum Computers as Universal Quantum Simulators: State-of-the-Art and Perspectives*, [Adv. Quantum Technol. *3* 3 (2020)](https://doi.org/10.1002/qute.201900052) \[[free arxiv version](https://arxiv.org/abs/1907.03505)\]

# 2 The Open Science Prize
### 2-1 Contest Details
The Open Science Prize requirements and judging criteria can be found in the terms and conditions when registering on the [website](https://ibmquantumawards.bemyapp.com/#/event). Below is an outline of those requirements.

You are to time evolve the state $|110\rangle$ to time $t=\pi$ under the $XXX$ Heisenberg model Hamiltonian $H_{\text{heis3}}$ as defined in Section 1. This will be done on the ibmq_jakarta quantum computer. From left to right, the qubits in $|110\rangle$ correspond to ibmq_jakarta's qubits 5, 3, and 1. The evolution must be done using Trotterization with at least 4 steps ($n\geq4$). You will be judged on the clarity, originiality, and creativity of your solution, as well as the fidelity of the evolved state at time $t=\pi$ using [state tomography](https://qiskit.org/documentation/tutorials/noise/8_tomography.html). State tomography is a way of determining the exact quantum state--even phase information--at the end of a quantum circuit by running the circuit multiple times and measuring in different bases. The closer your final quantum state to the expected state, the higher the fidelity. Ibmq_jakarta's qubits 0, 2, 4, and 6 will not be evaluated by the state tomography calculation, so you may use them as you wish. You do not need to follow the same decomposition given in the example above, but your solution must be a Trotterization of $U_{\text{heis3}}(t)$ with at least 4 steps ($n\geq4$). You may find it useful to implement techniques that reduce errors and improve qubit coherence.

### 2-2 Import Qiskit

Feel free to import packages as needed. However, only free packages and those obtained through ```pip install``` or ```conda install``` are allowed.

```python
# Importing standard Qiskit modules
from qiskit import QuantumCircuit, QuantumRegister, IBMQ, execute, transpile
from qiskit.providers.aer import QasmSimulator
from qiskit.tools.monitor import job_monitor
from qiskit.circuit import Parameter

# Import state tomography modules
from qiskit.ignis.verification.tomography import state_tomography_circuits, StateTomographyFitter
from qiskit.quantum_info import state_fidelity

# suppress warnings
import warnings
warnings.filterwarnings('ignore')
```

### 2-3 Quantum Devices
Connect to IBM provider and connect to a real or simulated backend. Final submissions must be run on a real backend, but simulated devices are faster for debugging and testing.

```python
# load IBMQ Account data

# IBMQ.save_account(TOKEN)  # replace TOKEN with your API token string (https://quantum-computing.ibm.com/lab/docs/iql/manage/account/ibmq)
provider = IBMQ.load_account()
```

```python
# Get backend for experiment
provider = IBMQ.get_provider(hub='ibm-q-community', group='ibmquantumawards', project='open-science-22')
jakarta = provider.get_backend('ibmq_jakarta')
# properties = jakarta.properties()

# Simulated backend based on ibmq_jakarta's device noise profile
sim_noisy_jakarta = QasmSimulator.from_backend(provider.get_backend('ibmq_jakarta'))

# Noiseless simulated backend
sim = QasmSimulator()
```

### 2-4 Decomposition of $U_{\text{Heis3}}(t)$ into Quantum Gates (Example)

The following circuit code is written based on the example given in Section 1. This is where you write your solution.

YOUR TROTTERIZATION GOES HERE -- START (beginning of example)

```python
# Parameterize variable t to be evaluated at t=pi later
t = Parameter('t')
```

```python
# Build a subcircuit for XX(t) two-qubit gate
XX_qr = QuantumRegister(2)
XX_qc = QuantumCircuit(XX_qr, name='XX')

XX_qc.ry(np.pi/2,[0,1])
XX_qc.cnot(0,1)
XX_qc.rz(2 * t, 1)
XX_qc.cnot(0,1)
XX_qc.ry(-np.pi/2,[0,1])

# Convert custom quantum circuit into a gate
XX = XX_qc.to_instruction()
```

```python
# Build a subcircuit for YY(t) two-qubit gate
YY_qr = QuantumRegister(2)
YY_qc = QuantumCircuit(YY_qr, name='YY')

YY_qc.rx(np.pi/2,[0,1])
YY_qc.cnot(0,1)
YY_qc.rz(2 * t, 1)
YY_qc.cnot(0,1)
YY_qc.rx(-np.pi/2,[0,1])

# Convert custom quantum circuit into a gate
YY = YY_qc.to_instruction()
```

```python
# Build a subcircuit for ZZ(t) two-qubit gate
ZZ_qr = QuantumRegister(2)
ZZ_qc = QuantumCircuit(ZZ_qr, name='ZZ')

ZZ_qc.cnot(0,1)
ZZ_qc.rz(2 * t, 1)
ZZ_qc.cnot(0,1)

# Convert custom quantum circuit into a gate
ZZ = ZZ_qc.to_instruction()
```

Combine subcircuits into single gate representing one ($n=1$) trotter step.

```python
# Combine subcircuits into a single multiqubit gate representing a single trotter step
num_qubits = 3

Trot_qr = QuantumRegister(num_qubits)
Trot_qc = QuantumCircuit(Trot_qr, name='Trot')

for i in range(0, num_qubits - 1):
    Trot_qc.append(ZZ, [Trot_qr[i], Trot_qr[i+1]])
    Trot_qc.append(YY, [Trot_qr[i], Trot_qr[i+1]])
    Trot_qc.append(XX, [Trot_qr[i], Trot_qr[i+1]])

# Convert custom quantum circuit into a gate
Trot_gate = Trot_qc.to_instruction()
```

YOUR TROTTERIZATION GOES HERE -- FINISH (end of example)

### 2-5 Trotterized Time Evolution

Time evolve the state $|110\rangle$ from $\theta=0$ to $\theta=\pi$ under $H_{\text{heis3}}$. Although jakarta has 7 qubits, only the qubits 1, 3, and 5 will be used for the state $|110\rangle$. From left to right, the qubits in $|110\rangle$ corresponds to the qubits 5, 3, and 1 on jakarta. State tomography will evaluate how well the quantum simulation matches to the expected state at $\theta= \pi$. (Note qubits 0, 2, 4, and 6 are not part of the state tomography calculation.)

You choose how many trotter steps (```trotter_steps```) to take in the simulation, but it must be at least 4. The code below is written to be generic enough for you may add to it if necessary for your solution.

```python
# The final time of the state evolution
target_time = np.pi

# Number of trotter steps
trotter_steps = 4  ### CAN BE >= 4

# Initialize quantum circuit for 3 qubits
qr = QuantumRegister(7)
qc = QuantumCircuit(qr)

# Prepare initial state (remember we are only evolving 3 of the 7 qubits on jakarta qubits (q_5, q_3, q_1) corresponding to the state |110>)
qc.x([3,5])  # DO NOT MODIFY (|q_5,q_3,q_1> = |110>)

# Simulate time evolution under H_heis3 Hamiltonian
for _ in range(trotter_steps):
    qc.append(Trot_gate, [qr[1], qr[3], qr[5]])

# Evaluate simulation at target_time (t=pi) meaning each trotter step evolves pi/trotter_steps in time
qc = qc.bind_parameters({t: target_time/trotter_steps})

# Generate state tomography circuits to evaluate fidelity of simulation
st_qcs = state_tomography_circuits(qc, [qr[1], qr[3], qr[5]])

# Display circuit for confirmation
# st_qcs[-1].decompose().draw()  # view decomposition of trotter gates
st_qcs[-1].draw()  # only view trotter gates
```

<pre style="word-wrap: normal;white-space: pre;background: #fff0;line-height: 1.1;font-family: "Courier New",Courier,monospace">                                                                    ░
q4_0: ──────────────────────────────────────────────────────────────░──────────
           ┌────────────┐┌────────────┐┌────────────┐┌────────────┐ ░ ┌─┐
q4_1: ─────┤0           ├┤0           ├┤0           ├┤0           ├─░─┤M├──────
           │            ││            ││            ││            │ ░ └╥┘
q4_2: ─────┤            ├┤            ├┤            ├┤            ├─░──╫───────
      ┌───┐│            ││            ││            ││            │ ░  ║ ┌─┐
q4_3: ┤ X ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├─░──╫─┤M├───
      └───┘│            ││            ││            ││            │ ░  ║ └╥┘
q4_4: ─────┤            ├┤            ├┤            ├┤            ├─░──╫──╫────
      ┌───┐│            ││            ││            ││            │ ░  ║  ║ ┌─┐
q4_5: ┤ X ├┤2           ├┤2           ├┤2           ├┤2           ├─░──╫──╫─┤M├
      └───┘└────────────┘└────────────┘└────────────┘└────────────┘ ░  ║  ║ └╥┘
q4_6: ──────────────────────────────────────────────────────────────░──╫──╫──╫─
                                                                    ░  ║  ║  ║
c0: 3/═════════════════════════════════════════════════════════════════╩══╩══╩═
                                                                       0  1  2 </pre>

### 2-6 Execute
For your final submission, you will need to execute your solution on a real backend with 8 repetitions. For faster debugging, considering using a simulated backend and/or 1 repetition.

```python
shots = 8192
reps = 8
backend = sim_noisy_jakarta
# reps = 8
# backend = jakarta

jobs = []
for _ in range(reps):
    # execute
    job = execute(st_qcs, backend, shots=shots)
    print('Job ID', job.job_id())
    jobs.append(job)
```

    Job ID 69578ef6-8d0d-4413-88c8-46179605d4d6
    Job ID 0d5068ed-6957-4a5e-9956-e5c924377a26
    Job ID 568ff236-6b75-4ad5-b6a3-c67a733b1f30
    Job ID 8f795bf9-9092-423c-bce0-8bbafd0b588e
    Job ID bd3b6064-dca9-4611-9817-1050c1ff6c41
    Job ID eb0ffe57-fabd-46e6-a7bd-468d5a5645e7
    Job ID 2a44d28d-2fee-4b40-b576-be625eee43fc
    Job ID df446ee1-4eb5-48ec-a246-903d8ca63c6d

We can monitor the status of the jobs using Qiskit's job monitoring tools.

```python
for job in jobs:
    job_monitor(job)
    try:
        if job.error_message() is not None:
            print(job.error_message())
    except:
        pass
```

    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run

### 2-7 Results Analysis
Extract the results for the completed jobs and compute the state tomography fidelity for each repetition. You may choose to include other post-processing analyses here as well.

```python
# Compute the state tomography based on the st_qcs quantum circuits and the results from those ciricuits
def state_tomo(result, st_qcs):
    # The expected final state; necessary to determine state tomography fidelity
    target_state = (One^One^Zero).to_matrix()  # DO NOT MODIFY (|q_5,q_3,q_1> = |110>)
    # Fit state tomography results
    tomo_fitter = StateTomographyFitter(result, st_qcs)
    rho_fit = tomo_fitter.fit(method='lstsq')
    # Compute fidelity
    fid = state_fidelity(rho_fit, target_state)
    return fid

# Compute tomography fidelities for each repetition
fids = []
for job in jobs:
    fid = state_tomo(job.result(), st_qcs)
    fids.append(fid)

print('state tomography fidelity = {:.4f} \u00B1 {:.4f}'.format(np.mean(fids), np.std(fids)))
```

    state tomography fidelity = 0.0378 ± 0.0013

```python
import qiskit.tools.jupyter
%qiskit_version_table
```

<h3>Version Information</h3><table><tr><th>Qiskit Software</th><th>Version</th></tr><tr><td><code>qiskit-terra</code></td><td>0.18.3</td></tr><tr><td><code>qiskit-aer</code></td><td>0.9.1</td></tr><tr><td><code>qiskit-ignis</code></td><td>0.6.0</td></tr><tr><td><code>qiskit-ibmq-provider</code></td><td>0.18.1</td></tr><tr><td><code>qiskit-aqua</code></td><td>0.9.5</td></tr><tr><td><code>qiskit</code></td><td>0.32.1</td></tr><tr><td><code>qiskit-nature</code></td><td>0.1.3</td></tr><tr><th>System information</th></tr><tr><td>Python</td><td>3.8.8 (default, Apr 13 2021, 12:59:45)
[Clang 10.0.0 ]</td></tr><tr><td>OS</td><td>Darwin</td></tr><tr><td>CPUs</td><td>8</td></tr><tr><td>Memory (Gb)</td><td>64.0</td></tr><tr><td colspan='2'>Mon Nov 29 09:12:47 2021 EST</td></tr></table>


# 2019 CUPT Problem9 Hurricane Ball

Two steel balls that are joined together can be spun at incredibly high frequency by first spinning them by hand and then blowing on them through a tube, e.g. a drinking straw. Explain and investigate this phenomenon.

通过起始时用手旋转，并使用一根管子（如吸管）朝其吹气，连在一起的两个钢球能以极高的频率旋转。解释并探究这一现象。

```python
import numpy as np
import pandas as pd
import sympy as sp
import matplotlib as mp
```

## 对飓风球运动全过程的分析

分析实验拍摄的视频中可知：飓风球被手拨动后开始公转，但一开始并没有自转角速度；在公转的过程中，飓风球两球心连线的与水平方向的夹角逐渐增大（也就是飓风球逐渐立起，在立起的过程中还存在章动）并且获得自转角速度，公转角速度减小，自转角速度增大；当这样的过程达到一定程度时，飓风球与桌面之间不再有相对滑动，而转变为纯滚动，此时飓风球达到一个相对稳定的旋转状态；若对飓风球吹气则可使其旋转进一步加速，达到最高超过$30$Hz的公转频率；当停止吹气加速后，飓风球的旋转逐渐减慢（公转角速度与自转角速度同时减小），两球心连线与桌面之间的夹角逐渐减小，这一减速的过程往往能够持续分钟级别的时间，这一阶段中飓风球与桌面之间仍为纯滚动；当飓风球完全倒下（即两球心连线水平，两个球同时与桌面接触），滚动摩擦变为滑动摩擦，使飓风球剩余的动能迅速耗散；最终飓风球静止。

在飓风球两球均接触桌面旋转到一球离开桌面的过程中，飓风球从一个对称的状态变为一个非对称的状态，为何使其中一球抬起，而非另一球？这是因为实施实验的条件并非都是完全对称的，可能是两个小球的质量有细微的差异，又或是两个小球与桌面的摩擦系数有细微的差异，再或是桌面上存在凸起或凹陷，使飓风球运动过程中其中一小球在某一瞬间不受桌面的支持力......这些都是可能造成不对称运动的原因。

### 飓风球启动到无滑动稳定转动阶段

#### 参数设定

设每个小球质量为$m$，半径为$R$，重力加速度记为$g$。

在飓风球启动到无滑动稳定转动的过程中，飓风球中一球始终与桌面接触，故系统自由度为$5$，因此设$5$个自由度：
* 飓风球质心在水平桌面上投影点的$x$,$y$坐标
* 飓风球的三个欧拉角：公转角$\varphi$，自转角$\psi$和两球心连线与竖直方向的夹角$\theta$

俯视图

侧视图

```python
m = sp.Symbol('m')
R = sp.Symbol('R')
g = sp.Symbol('g')
x = sp.Symbol('x')
y = sp.Symbol('y')
phi = sp.Symbol('phi')
theta = sp.Symbol('theta')
psi = sp.Symbol('psi')
x1 = sp.Symbol('x1')
y1 = sp.Symbol('y1')
phi1 = sp.Symbol('phi1')
theta1 = sp.Symbol('theta1')
psi1 = sp.Symbol('psi1')
x2 = sp.Symbol('x2')
y2 = sp.Symbol('y2')
phi2 = sp.Symbol('phi2')
theta2 = sp.Symbol('theta2')
psi2 = sp.Symbol('psi2')
```

#### 受力分析

为简化问题我们忽略空气阻力，此时飓风球受到三个力的作用：

* 重力（沿竖直方向）
* 桌面对飓风球的支持力（沿竖直方向）
* 飓风球与桌面接触点（下简称接触点）相对于桌面运动从而受到的摩擦力（沿水平桌面）
---
##### 静摩擦力的表示
飓风球受到的摩擦力为**静摩擦**，其**大小**为飓风球与桌面静摩擦系数$\mu$与桌面对飓风球的支持力$\vec{N}$大小的乘积
$$f=\mu N.$$

```python
mu = sp.Symbol('mu')
N = sp.Symbol('N')
f = mu * N
```

而**摩擦力的方向**与接触点相对于桌面运动的方向相反，随着飓风球的转动而不断变化。要求飓风球的摩擦力方向，需知接触点相对于桌面的运动速度$\vec{v}$。这一速度可视为四个速度的矢量叠加，分别是：

* 飓风球质心的速度
$$\vec{v}_c=\dot{x}\vec{i}+\dot{y}\vec{j}$$
其中$\vec{i}$和$\vec{j}$分别是沿$x$轴和$y$轴方向的单位矢量

* 角速度$\varphi$分量引起的接触点相对于质心的速度（沿桌面的分量）
$$\vec{v}_{\varphi}=R\dot{\varphi}\sin\theta(\sin\varphi\vec{i}-\cos\varphi\vec{j})$$
* 角速度$\vec{\psi}$分量引起的接触点相对于质心的速度$\vec{v}_{\psi}$（沿桌面的分量）
$$\vec{v}_{\psi}=R\dot{\psi}\sin\theta(-\sin\varphi\vec{i}+\cos\varphi\vec{j})$$
* 角速度$\theta$分量引起的接触点相对于质心的速度$\vec{v}_{\theta}$沿桌面的分量
$$\vec{v}_{\theta}=2R\dot{\theta}\cos^2\frac{\theta}{2}(-\cos\varphi\vec{i}-\sin\varphi\vec{j})$$
从而
$$\vec{v}=\vec{v}_v+\vec{v}_{\varphi}+\vec{v}_{\psi}+\vec{v}_{\theta}\\
=(x_1+R\dot{\varphi}\sin\theta\sin\varphi-R\dot{\psi}\sin\theta\sin\varphi-2R\dot{\theta}\cos^2\frac{\theta}{2}\cos\varphi)\vec{i}+(\dot{y}-R\dot{\varphi}\sin\theta\cos\varphi+R\dot{\psi}\sin\theta\cos\phi-2R\dot{\theta}\cos^2\frac{\theta}{2}\sin\varphi)\vec{j}\triangleq v_x\vec{i}+v_j\vec{j}$$

```python
vx = x1 + R * phi1 * sp.sin(theta) * sp.sin(phi) - R * psi1 * sp.sin(theta) * sp.sin(phi) - 2 * R * theta1 * sp.cos(theta / 2)**2 * sp.cos(phi)
print('v_x=')
vx
```

    v_x=

$\displaystyle R \phi_{1} \sin{\left(\phi \right)} \sin{\left(\theta \right)} - R \psi_{1} \sin{\left(\phi \right)} \sin{\left(\theta \right)} - 2 R \theta_{1} \cos{\left(\phi \right)} \cos^{2}{\left(\frac{\theta}{2} \right)} + x_{1}$

```python
vy = y1 - R * phi1 * sp.sin(theta) * sp.cos(psi) - R * psi1 * sp.sin(theta) * sp.cos(phi) - 2 * R * theta1 * sp.cos(theta / 2)**2 * sp.sin(phi)
print('v_y=')
vy
```

    v_y=

$\displaystyle - R \phi_{1} \sin{\left(\theta \right)} \cos{\left(\psi \right)} - R \psi_{1} \sin{\left(\theta \right)} \cos{\left(\phi \right)} - 2 R \theta_{1} \sin{\left(\phi \right)} \cos^{2}{\left(\frac{\theta}{2} \right)} + y_{1}$

```python
v = sp.sqrt(vx**2 + vy**2)
```

从而飓风球受到的摩擦力为
$$\vec{f}=-f(\frac{v_x}{v}\vec{i}+\frac{v_y}{v}\vec{j})\triangleq f_x\vec{i}+f_y\vec{j}$$

```python
fx = - f * vx / v
fy = - f * vy / v
```

#### 飓风球系统动力学方程

于是，我们可以在实验室坐标系的三个正交方向上列出关于系统质心的动力学方程：

* 沿$x$轴方向受到摩擦力的$x$分量
$$2m\ddot{x}=f_x$$
* 沿$y$轴方向受到摩擦力的$y$分量
$$2m\ddot{y}=f_y$$
* 沿$z$轴方向受到重力和桌面对飓风球的支持力
$$2m\ddot{z}=N-mg$$
其中飓风球质心的$z$坐标可表示为
$$z=R+R\cos\theta$$
其二阶导为
$$\ddot{z}=R(-\ddot{\theta}\sin\theta-\dot{\theta}^2\cos\theta)$$

```python
equ1 = 2 * m * x2 - fx
equ2 = 2 * m * y2 - fy
equ3 = 2 * m * R * (-theta2 * sp.sin(theta) - theta1**2 * sp.cos(theta))
```

#### 飓风球系统的欧拉动力学方程

此外，还可列出系统的三个欧拉动力学方程：
$$I_1\dot{\omega}_1-(I_2-I_3)\omega_2\omega_3=M_1\\
I_2\dot{\omega}_2-(I_3-I_1)\omega_3\omega_1=M_2\\
I_3\dot{\omega}_3-(I_1-I_2)\omega_1\omega_2=M_3$$
其中3轴沿两球心连线斜向上，另外的1轴和2轴与之形成一个右手正交坐标系，3轴随飓风球公转（$\varphi$）的同时，1轴和2轴绕着3轴自转（$\psi$）。

飓风球绕着这三个轴的转动惯量分别为
$$I_1=I_2=\frac{14}{5}mR^2\\
I_3=\frac{4}{5}mR^2$$

```python
I1 = 14 / 5 * m * R**2
I2 = 14 / 5 * m * R**2
I3 = 4 / 5 * m * R**2
```

三个欧拉角分别为
$$\omega_1=\dot{\varphi}\sin\theta\sin\psi+\dot{\theta}\cos\psi\\
\omega_2=\dot{\varphi}\sin\theta\cos\psi-\dot{\theta}\sin\psi\\
\omega_3=\dot{\varphi}\cos\theta+\dot{\psi}$$

```python
omega1 = phi1 * sp.sin(theta) * sp.sin(psi) + theta1 * sp.cos(psi)
omega2 = phi1 * sp.sin(theta) * sp.cos(psi) - theta1 * sp.sin(psi)
omega3 = phi1 * sp.cos(theta) + psi1
```

这三个欧拉角的一阶导分别为
$$\dot{\omega}_1=\ddot{\varphi}\sin\theta\sin\psi+\dot{\varphi}\dot{\theta}\cos\theta\sin\psi+\dot{\varphi}\dot{\psi}\sin\theta\cos\psi+\ddot{\theta}\cos\psi-\dot{\theta}\dot{\psi}\sin\psi\\
\dot{\omega}_2=\ddot{\varphi}\sin\theta\cos\psi+\dot{\varphi}\dot{\theta}\cos\theta\cos\psi-\dot{\varphi}\dot{\psi}\sin\theta\sin\psi-\ddot{\theta}\sin\psi-\dot{\theta}\dot{\psi}\cos\psi\\
\dot{\omega}_3=\ddot{\varphi}\cos\theta-\dot{\varphi}\dot{\theta}\sin\theta+\ddot{\psi}$$

```python
omega11 = phi2 * sp.sin(theta) * sp.sin(psi) + phi1 * theta1 * sp.cos(theta) * sp.sin(psi) + phi1 * psi1 * sp.sin(theta) * sp.sin(psi) + theta2 * sp.cos(psi) - theta1 * psi1 * sp.sin(psi)
omega21 = phi2 * sp.sin(theta) * sp.cos(psi) + phi1 * theta1 * sp.cos(theta) * sp.cos(psi) - phi1 * psi1 * sp.sin(theta) * sp.cos(psi) - theta2 * sp.sin(psi) - theta1 * psi1 * sp.cos(psi)
omega31 = phi2 * sp.cos(theta) - phi1 * theta1 * sp.sin(theta) + psi2
```

设1'轴垂直于3轴且与桌面平行，2'轴垂直于3轴和2'轴且斜向上，那么沿着1'轴，2'轴和3轴的力矩分别为
$$M_1'=2R(-f_x\cos\varphi-f_y\sin\varphi)\cos^2\frac{\theta}{2}+NR\sin\theta\\
M_2'=2R(f_x\sin\varphi-f_y\cos\varphi)\cos^2\frac{\theta}{2}\\
M_3=R(-f_x\sin\varphi+f_y\cos\varphi)\sin\theta$$
1'轴与1轴，2'轴和2轴之间的夹角均为$\psi$，故沿着1轴和2轴的力矩分别为
$$M_1=M_1'\cos\psi+M_2'\sin\psi\\
M_2=-M_1'\sin\psi+M_2'\cos\psi$$

```python
M10 = 2 * R * (-fx * sp.cos(phi) - fy * sp.sin(phi)) * sp.cos(theta / 2)**2 + N * R * sp.sin(theta)
M20 = 2 * R * (fx * sp.sin(phi) - fy * sp.cos(phi)) * sp.cos(theta / 2)**2
M3 = R * (-fx * sp.sin(phi) + fy * sp.cos(phi)) * sp.sin(theta)
M1 = M10 * sp.cos(psi) + M20 * sp.sin(psi)
M2 = -M10 * sp.sin(psi) + M20 * sp.cos(psi)
```

将以上所得转动惯量、角速度、角速度一阶导和力矩代入三个欧拉动力学方程

```python
equ4 = I1 * omega11 - (I2 - I3) * omega2 * omega3 - M1
equ5 = I2 * omega21 - (I3 - I1) * omega3 * omega1 - M2
equ6 = I3 * omega31 - (I1 - I2) * omega1 * omega2 - M3
```

将以上六个方程化为适合求解的形式

```python
# sp.solvers.solve([equ1,equ2,equ3,equ4,equ5,equ6],x2,y2,phi2,theta2,psi2,N)
```

似乎比较难算，稍微简化一下

```python
m = sp.Symbol('m')
R = sp.Symbol('R')
g = sp.Symbol('g')
x = sp.Symbol('x')
y = sp.Symbol('y')
phi = sp.Symbol('phi')
theta = sp.Symbol('theta')
psi = sp.Symbol('psi')
x1 = sp.Symbol('x1')
y1 = sp.Symbol('y1')
phi1 = sp.Symbol('phi1')
theta1 = sp.Symbol('theta1')
psi1 = sp.Symbol('psi1')
x2 = sp.Symbol('x2')
y2 = sp.Symbol('y2')
phi2 = sp.Symbol('phi2')
theta2 = sp.Symbol('theta2')
psi2 = sp.Symbol('psi2')
mu = sp.Symbol('mu')
N = sp.Symbol('N')
xcomponant = sp.Symbol('xcomponant')
ycomponant = sp.Symbol('ycomponant')
fx = mu * N * xcomponant
fy = mu * N * ycomponant
I1 = sp.Symbol('I1')
I2 = sp.Symbol('I2')
I3 = sp.Symbol('I3')
omega1 = sp.Symbol('omega1')
omega2 = sp.Symbol('omega2')
omega3 = sp.Symbol('omega3')
omega11 = phi2 * sp.sin(theta) * sp.sin(psi) + phi1 * theta1 * sp.cos(theta) * sp.sin(psi) + phi1 * psi1 * sp.sin(theta) * sp.sin(psi) + theta2 * sp.cos(psi) - theta1 * psi1 * sp.sin(psi)
omega21 = phi2 * sp.sin(theta) * sp.cos(psi) + phi1 * theta1 * sp.cos(theta) * sp.cos(psi) - phi1 * psi1 * sp.sin(theta) * sp.cos(psi) - theta2 * sp.sin(psi) - theta1 * psi1 * sp.cos(psi)
omega31 = phi2 * sp.cos(theta) - phi1 * theta1 * sp.sin(theta) + psi2
M10 = 2 * R * (-fx * sp.cos(phi) - fy * sp.sin(phi)) * sp.cos(theta / 2)**2 + N * R * sp.sin(theta)
M20 = 2 * R * (fx * sp.sin(phi) - fy * sp.cos(phi)) * sp.cos(theta / 2)**2
M3 = R * (-fx * sp.sin(phi) + fy * sp.cos(phi)) * sp.sin(theta)
M1 = M10 * sp.cos(psi) + M20 * sp.sin(psi)
M2 = -M10 * sp.sin(psi) + M20 * sp.cos(psi)
equ1 = 2 * m * x2 - fx
equ2 = 2 * m * y2 - fy
equ3 = 2 * m * R * (-theta2 * sp.sin(theta) - theta1**2 * sp.cos(theta)) - N + 2 * m * g
equ4 = I1 * omega11 - (I2 - I3) * omega2 * omega3 - M1
equ5 = I2 * omega21 - (I3 - I1) * omega3 * omega1 - M2
equ6 = I3 * omega31 - (I1 - I2) * omega1 * omega2 - M3
sol = sp.solvers.solve([equ1,equ2,equ3,equ4,equ5,equ6],x2,y2,phi2,theta2,psi2,N)
```

#### 定量模拟
以半径$R=20mm=2\times10^{-2}m$（公差$\pm0.002mm$），质量$m=32.93g=3.293\times10^{-2}kg$（公差$\pm0.01g$）的两个小钢球组成的飓风球在光滑不锈钢板上的运动为例（实验地点为上海，重力加速度$g=9.7964m/s^2$）。

要模拟出出其运动情况，还需要测量小球与桌面之间的滑动摩擦系数。

我们通过分析小球在已知倾角斜面上下滑的视频，得到其加速度，进而推测出小球与桌面之间的滑动摩擦系数。

测量小球与桌面之间的滑动摩擦系数实验图，为了防止小球在下滑的过程中发生滚动，我们用热熔胶将三个小球粘为一个整体，从斜面上滑下

```python

```

### 飓风球启动至无滑动旋转阶段（考虑空气阻力）

飓风球在旋转过程中受到的空气阻力很复杂。为了简化问题，我们通过实验得到单个小球受空气阻力与小球相对空气运动速度之间的关系，认为飓风球运动中受到的空气阻力为一个小球受到的空气的两倍

首先我们通过

```python

```

### 飓风球吹气加速阶段
#### 小球与桌面材料之间滚动摩擦系数的获得

与之前获得小球与不锈钢板之间滑动摩擦系数的方法类似，我们将小球从不锈钢板斜面顶端静止释放，通过逐帧分析小球在斜面上滚动的视频，得到小球在斜面上滚下时的加速度，从而

---

我们

```python
data = pd.read_excel('Experiment/小球与不锈钢平面之间滚动摩擦系数的测量.xlsx')
data
```

      小球在水平桌面上滚动摩擦系数的测量实验数据记录表
      Unnamed: 1

      0
      小球材质
      不锈钢

      1
      小球直径/mm
      20

      2
      小球质量/g
      32.93

      3
      斜面倾角/°
      5

      4
      时间t/s
      小球滚下的距离x/m

      5
      0.0443444
      -0.00161633

      6
      0.0776667
      -0.00323439

      7
      0.110989
      -0.00485245

      8
      0.144433
      -0.00663231

      9
      0.177778
      -0.0088976

      10
      0.211022
      -0.0113247

      11
      0.244267
      -0.0138327

      12
      0.277511
      -0.0168267

      13
      0.311011
      -0.0193349

      14
      0.344278
      -0.0223021

      15
      0.377833
      -0.0257048

      16
      0.411022
      -0.0285944

      17
      0.444211
      -0.0328319

      18
      0.477622
      -0.0361657

      19
      0.510811
      -0.0397116

      20
      0.544144
      -0.0438308

      21
      0.577478
      -0.0480683

      22
      0.610944
      -0.0529696

      23
      0.644278
      -0.0573666

      24
      0.677611
      -0.0621947

      25
      0.7108
      -0.0668072

      26
      0.744122
      -0.0719967

      27
      0.777611
      -0.0769409

      28
      0.810867
      -0.0824423

      29
      0.844122
      -0.0879908

      30
      0.877622
      -0.0939541

      31
      0.910956
      -0.100104

      32
      0.9441
      -0.106467

      33
      0.977611
      -0.112642

      34
      1.01077
      -0.118634

      35
      1.04409
      -0.125717

      36
      1.0776
      -0.13274

      37
      1.11092
      -0.139397

      38
      1.14424
      -0.14684

      39
      1.17757
      -0.154917

      40
      1.21089
      -0.162174

      41
      1.24407
      -0.170302

      42
      1.27757
      -0.177652

      43
      1.3109
      -0.186778

      44
      1.34423
      -0.19527

      45
      1.37757
      -0.203623

      46
      1.4109
      -0.211708

      47
      1.44423
      -0.220678

      48
      1.47757
      -0.229605

      49
      1.5109
      -0.23803

      50
      1.54423
      -0.248253

      51
      1.57757
      -0.258051

      52
      1.6109
      -0.267423

      53
      1.64423
      -0.278358

      54
      1.67757
      -0.28755

      55
      1.7109
      -0.297426

      56
      1.74399
      -0.307687

      57
      1.77747
      -0.318402

      58
      1.81094
      -0.329298

      59
      1.84411
      -0.340194

```python
t = data.loc[5:59,['小球在水平桌面上滚动摩擦系数的测量实验数据记录表']]
t.index
```

```python

```


## First Assignment

#### 1) Apply the appropriate string methods to the **x** variable (as '.upper') to change it exactly to: "$Dichlorodiphenyltrichloroethane$".

```python
x = "DiClOrod  IFeNi  lTRicLOr oETaNo  DiChlorod iPHeny lTrichL oroEThaNe"

x1 = str.lower(x)
x1 = x1.replace(" ", "")
x2 = x1[27:]
x2 = x2.capitalize()

print(x2)# I know to complicated, but I´m learning
```

    Dichlorodiphenyltrichloroethane

#### 2) Assign respectively the values: 'word', 15, 3.14 and 'list' to variables A, B, C and D in a single line of code. Then, print them in that same order on a single line separated by a space, using only one print statement.

```python
dic= {'A':'word', 'B':'15', 'C':'3.14','D': 'list'}
print(dic)
```

    {'A': 'word', 'B': '15', 'C': '3.14', 'D': 'list'}

#### 3) Use the **input()** function to receive an input in the form **'68.4 1.71'**, that is, two floating point numbers in a line separated by space. Then, assign these numbers to the variables ** p ** and ** h ** respectively, which represent an individual's weight and height (hint: take a look at the '.split ()' method). With this data, calculate the individual's Body Mass Index (BMI) from the following relationship:

\begin{equation}
BMI = \dfrac{weight}{height^2}
\end{equation}

```python
init = input('initinput: ') # split issue with not separated numbers 77.4 1.78
p,h = [float(init) for init in init.split(" ")]

print(p)
print(h)

bmi = p/(h**2)
print(bmi)
```

    initinput: 77.4 1.78
    77.4
    1.78
    24.428733745739176

#### This value can also be classified according to ranges of values, following to the table below. Use conditional structures to classify and print the classification assigned to the individual.

<\center>

```python
if (bmi < 18.5):
    print("Underweight")
elif (18.5 <= bmi):
    print("Healthy weight")
elif (25 <= bmi):
    print("Overweight")
elif (30 <= bmi):
    print("Obese")
else:
    print("Not working")
```

    Healthy weight

#### 4) Receive an integer as an input and, using a loop, calculate the factorial of this number, that is, the product of all the integers from one to the number provided.

```python
n = int(input("use number:")) # 7
print(type(init))

init = 1

for i in range(1, n+1):
    init = init*i

print("factorial of ", n, ' is ', init)

```

    use number:8
    <class 'str'>
    factorial of  8  is  40320

#### 5) Using a while loop and the input function, read an indefinite number of integers until the number read is -1. Present the sum of all these numbers in the form of a print, excluding the -1 read at the end.

```python
num = int(input("Enter a number: "))
list= []
count= 0
while num > -1:
    list.append(num)
    count = count+num
    num= num-1
else:
    print(list)
    print(count)
```

    Enter a number: 3
    [3, 2, 1, 0]
    6

#### 6) Read the **name** of an employee, his **number of hours worked** and the **sallary per hour** in a single line separated by space. Next, calculate the **total salary** for this employee and show it to two decimal places.

```python
# understood that the info should be stored in a string
import math

employ = "Martin 13.20223 15.62231"
name, hour, salary = [employ for employ in employ.split(" ")]
hour, salary = float(hour), float(salary)

income = hour * salary

print(round(income, 2))

```

    206.25

#### 7) Read three floating point values **A**, **B** and **C** respectively. Then calculate and show:

```python
A,B,C = input("Enter three points").split(',')
A = float(A)
B = float(B)
C = float(C)
print (A, B, C)
```

    Enter three points2.3,2.4,2.5
    2.3 2.4 2.5

    a) the area of the triangle rectangle with A as the base and C as the height.

```python
area = (A*C)/2
print(area)
```

    2.875

    b) the area of the circle of radius C. (pi = 3.14159)

```python
import math # To be honest I had to google the function

pi = math.pi

area = (C*2)*pi

print(area)
```

    15.707963267948966

    c) the area of the trapezoid that has A and B for bases and C for height.

```python
area = 0.5*(A+B)*C
print(area)
```

    5.874999999999999

    d) the area of the square that has side B.

```python
area = B*B
print(area)
```

    5.76

    e) the area of the rectangle that has sides A and B.

```python
area = A*B
print(area)
```

    5.52

#### 8) Read **3 floating point values** and calculate the **roots of the second degree equation** formed by these coefficients (using [this formula](https://en.wikipedia.org/wiki/Quadratic_equation)). If it is not possible to calculate the roots, display the message **“There are no real roots”**.

```python
import cmath    # solution sarah
x,y,z = input("Enter three numbers").split(',')
x = float(x)
y = float(y)
z = float(z)

# calculate the discriminant
d = (y**2) - (4*x*z)

# find two solutions
try:
    sol1 = (-y-cmath.sqrt(d))/(2*x)
    sol2 = (-y+cmath.sqrt(d))/(2*x)
except ValueError:
    print("there are no real roots")

print('The solution are {0} and {1}'.format(sol1,sol2))
```

    There are two solutions (-0.3003436994436531, -2.3782277291277754)

```python
#  (a*x^2) + b*x + c = 0

# solution lukas

a = float(input("a is:"))
b = float(input("b is:"))
c = float(input("c is:"))

w = b**2 - 4 * a *c  # diskriminante quadratische gleichung

if w>0: # is r positiv there are two possible solutions for x
    x1 = (((-b) + (math.sqrt(w)))/(2*a))
    x2 = (((-b) - (math.sqrt(w)))/(2*a))
    print('There are two solutions', (x1,x2))
elif w == 0:
    x = (-b)/2*a
    print('One solution')
elif w < 0:
        print('There are no real roots')

```

    a is:2.24
    b is:6.0
    c is:1.6
    There are two solutions (-0.3003436994436531, -2.3782277291277754)

#### 9) Read four floating point numerical values corresponding to the coordinates of two geographical coordinates in the cartesian plane. Each point will come in a line with its coordinates separated by space. Then calculate and show the distance between these two points.

(obs: $d=\sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$)

```python
# calculation
import math

lat1, lon1, lat2, lon2 = 40.4165000, -3.7025600, 48.2084900, 16.3720800  # coordinaten madridd und wien through assignement
mad = [40.4165000, -3.7025600]
wie = [48.2084900, 16.3720800]

distance =  math.sqrt(((mad[0]-wie[0])**2)+((mad[1]-wie[1])**2))
print(distance)
```

    21.533840328415646

#### 10) Read ** two floating point numbers ** on a line that represent **coordinates of a cartesian point**. With this, use **conditional structures** to determine if you are at the origin, printing the message **'origin'**, in one of the axes, printing **'x axis'** or **'y axis'**; or in one of the four quadrants, printing **'q1'**, **'q2**', **'q3'** or **'q4'**.

```python
# only digits as input allowed, also practicing while loop

while True:
    try:
        x = float(input('Enter the Value for X'))
        y = float(input('Enter the Value for Y'))
    except ValueError:
        print('x and y have to be digits')
        continue
    else:
        print('Both ', x, ' and ', y, ' are digits')
        break

if x == 0 and y == 0:
    print('Point is at the origin')

elif x > 0 and y > 0:
    print('q1')

elif x < 0 and y > 0:
    print('q2')

elif x < 0 and y < 0:
    print('q3')

elif x > 0 and y < 0:
    print ('q4')

else:
    print('not working what so ever')
```

    Enter the Value for X2.0
    Enter the Value for Y-3
    Both  2.0  and  -3.0  are digits
    q4

#### 11) Read an integer that represents a phone code for international dialing. Then, inform to which city the code belongs to, considering the picture below [(from this table)](https://en.wikipedia.org/wiki/Telephone_numbers_in_Europe).

<\center>

```python
# make a dictionaire with countrycodes and match the code e.g. dictionaire with 5 keys
# not for every point in data table

pho = int(input("Enter a phone code: "))
countrydic = {'Austria':43, 'Belgium':32,'Bulgaria':359, 'Croatia':385, 'Cyprus':357, 'Czech Republic':420, 'Denmark':45, 'Estonia':372, 'Finland':358, 'France':33}

for value in countrydic.items():
    if pho in value:
        print(value)
```

    Enter a phone code: 43
    ('Austria', 43)

#### 12) Write a piece of code that reads 6 values in a row. Next, show the number of positive values entered. On the next line, print the average of the values to one decimal place.

```python
N = int(input('Enter 6 numbers'))                 # Enter 6 to enter 6 values
init = []
count_pos = 0

for num in range(0,6):
    n = int(input())
    if (n>0):
        init.append(n)

        count_pos += 1
        avg = sum(init)/(count_pos)

print(count_pos)
print(round(avg, 1))
```

    Enter multiple numbers4
    3
    2
    3
    4
    5
    6
    6
    3.8

#### 13) Read an integer **N**. Then print the **square of each of the even values**, from 1 to N, including N, if applicable, arranged one per line.

```python
N = int(input('Use a number ')) # I´don´t know how to include N when N = odd?

for num in range(1, N+1):     # seems to work now, I don´t know why
    if (num % 2 == 0):
        squared_and_even = (num**2)
        print("The square of ", num, " is", squared_and_even)

    else:
        continue
        # print("wrong, odd or both")

```

    Use a number 9
    The square of  2  is 4
    The square of  4  is 16
    The square of  6  is 36
    The square of  8  is 64

#### 14) Using **input()**, read an integer and print its classification as **'even / odd'** and **'positive / negative'** . The two classes for the number must be printed on the same line separated by a space. In the case of zero, print only **'null'**.

```python
def even_odd(n):
    if(N==0):
        return "null"
    elif(N % 2)==0:
        return "even"
    elif(N % 2)==1:
        return "odd"

def pos_neg(n):
    if(n>0):
        return "positive"
    elif(n<0):
        return "negative"
    elif(n==0):
        return "neither positive or negative"

N = int(input('Enter the digit '))

print("The class of this number is defined as", even_odd(N), "and", pos_neg(N))

```

    Enter the digit 12
    The class of this number is defined as even and positive

## Challenge
#### 15) Ordering problems are recurrent in the history of programming. Over time, several algorithms have been developed to fulfill this function. The simplest of these algorithms is the [**Bubble Sort**](https://en.wikipedia.org/wiki/Bubble_sort), which is based on comparisons of elements two by two in a loop of passes through the elements. Your mission, if you decide to accept it, will be to input six whole numbers ramdonly ordered. Then implement the **Bubble Sort** principle to order these six numbers **using only loop and conditional structure**. At the end, print the six numbers in ascending order on a single line separated by space.

```python

for i in range(6,1):
    list1 = [int(item) for item in input("Enter the list items : ").split(',')]
    for j in range(0,6-i-1):
        if arr[j] > arr[j+1]:
            arr[j], arr[j+1] = arr[j+1], arr[j]

            print("Sorted array is:")
            for i in range(len(list1)):
            print ("%d" %arr[i])

```


# Building a Spam Filter with Naive Bayes

In this project, I'm going to study the practical side of the Naive Bayes algorithm building a spam filter for SMS messages.

To classify messages as spam or non-spam the computer:

1. Learns how humans classify messages.
1. Uses that human knowledge to estimate probabilities for new messages — probabilities for spam and non-spam.
3. Classifies a new message based on these probability values — if the probability for spam is greater, then it classifies the message as spam. Otherwise, it classifies it as non-spam.

So the first task is to "teach" the computer how to classify messages. To do that, I'll use the multinomial Naive Bayes algorithm along with a dataset of 5,572 SMS messages that are already classified by humans.

The dataset was put together by Tiago A. Almeida and José María Gómez Hidalgo, and it can be downloaded from the [The UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/datasets/sms+spam+collection). You can also download the dataset directly [from this link](https://dq-content.s3.amazonaws.com/433/SMSSpamCollection). The data collection process is described in more details on [this page](http://www.dt.fee.unicamp.br/~tiago/smsspamcollection/#composition), where you can also find some of the authors' papers.

# Exploring the Data

Let's start by reading in the dataset.

```
# Import libraries
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report

sms_spam = pd.read_csv('SMSSpamCollection', sep='\t', header=None, names=['Label', 'SMS'])

print(sms_spam.shape)
sms_spam.head()
```

    (5572, 2)

      Label
      SMS

      0
      ham
      Go until jurong point, crazy.. Available only ...

      1
      ham
      Ok lar... Joking wif u oni...

      2
      spam
      Free entry in 2 a wkly comp to win FA Cup fina...

      3
      ham
      U dun say so early hor... U c already then say...

      4
      ham
      Nah I don't think he goes to usf, he lives aro...

```
sms_spam['Label'].value_counts(normalize=True)
```

    ham     0.865937
    spam    0.134063
    Name: Label, dtype: float64

There are about 87% of the messages that are ham ("ham" means non-spam), and the remaining 13% are spam. This sample looks representative, since in practice most messages that people receive are ham.

## Training and Test Set

I'm now going to split our dataset into a training and a test set using the `sklearn.model_selection.train_test_split()` function, where the training set accounts for 80% of the data, and the test set for the remaining 20%.

```
#Splitting the data
training_set, test_set = train_test_split(sms_spam, test_size=0.2, random_state=1)
test_set = test_set.copy() # to add a new column at the end

print(training_set.shape)
print(test_set.shape)
```

    (4457, 2)
    (1115, 2)

I'll now analyze the percentage of spam and ham messages in the training and test sets. I expect the percentages to be close to what I have in the full dataset, where about 87% of the messages are ham, and the remaining 13% are spam.

```
training_set['Label'].value_counts(normalize=True)
```

    ham     0.86538
    spam    0.13462
    Name: Label, dtype: float64

```
test_set['Label'].value_counts(normalize=True)
```

    ham     0.868161
    spam    0.131839
    Name: Label, dtype: float64

The results look good! I'll now move on to cleaning the data set.

# Data Preprocessing

To calculate all the probabilities required by the algorithm, I'll first need to perform a bit of data cleaning to bring the data in a format that will allow me to extract easily all the information I need.

## Data Cleaning

Essentially, I want to change the data set to a *bag-of-words* format using `sklearn.feature_extraction.text.CountVectorizer()` function.

```
# Creating Bag of Words
count_vect = CountVectorizer()
word_counts = count_vect.fit_transform(training_set["SMS"])
word_counts = pd.DataFrame(word_counts.A, columns=count_vect.get_feature_names(), index=training_set.index)

print(word_counts.shape)
word_counts.head()
```

    (4457, 7714)

      00
      000
      008704050406
      0121
      01223585236
      01223585334
      0125698789
      02
      0207
      02072069400
      02073162414
      02085076972
      021
      03
      04
      0430
      05
      050703
      0578
      06
      07
      07008009200
      07090201529
      07090298926
      07123456789
      07732584351
      07734396839
      07742676969
      07753741225
      0776xxxxxxx
      07781482378
      07786200117
      078
      07801543489
      07808
      07808247860
      07808726822
      07815296484
      07821230901
      07880867867
      ...
      yogasana
      yor
      yorge
      you
      youdoing
      youi
      youphone
      your
      youre
      yourinclusive
      yourjob
      yours
      yourself
      youwanna
      yowifes
      yoyyooo
      yr
      yrs
      ything
      yummmm
      yummy
      yun
      yunny
      yuo
      yuou
      yup
      zac
      zaher
      zealand
      zebra
      zed
      zeros
      zhong
      zindgi
      zoe
      zoom
      zouk
      zyada
      èn
      〨ud

      1642
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      2
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      2899
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      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
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      480
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      2
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      3485
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      157
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

5 rows × 7714 columns

```
training_set_clean = pd.concat([training_set, word_counts], axis=1)

print(training_set_clean.shape)
training_set_clean.head()
```

    (4457, 7716)

      Label
      SMS
      00
      000
      008704050406
      0121
      01223585236
      01223585334
      0125698789
      02
      0207
      02072069400
      02073162414
      02085076972
      021
      03
      04
      0430
      05
      050703
      0578
      06
      07
      07008009200
      07090201529
      07090298926
      07123456789
      07732584351
      07734396839
      07742676969
      07753741225
      0776xxxxxxx
      07781482378
      07786200117
      078
      07801543489
      07808
      07808247860
      07808726822
      07815296484
      ...
      yogasana
      yor
      yorge
      you
      youdoing
      youi
      youphone
      your
      youre
      yourinclusive
      yourjob
      yours
      yourself
      youwanna
      yowifes
      yoyyooo
      yr
      yrs
      ything
      yummmm
      yummy
      yun
      yunny
      yuo
      yuou
      yup
      zac
      zaher
      zealand
      zebra
      zed
      zeros
      zhong
      zindgi
      zoe
      zoom
      zouk
      zyada
      èn
      〨ud

      1642
      ham
      Hi , where are you? We're at  and they're not ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      2
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      2899
      ham
      If you r @ home then come down within 5 min
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      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
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      480
      ham
      When're you guys getting back? G said you were...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      2
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      3485
      ham
      Tell my  bad character which u Dnt lik in me. ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

      157
      ham
      I'm leaving my house now...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0

5 rows × 7716 columns

## Getting the Vocabulary

Let's now get the vocabularyas a dataframe, which in this context means a dictionary with all the unique words in our training set with their count.

```
vocabulary = word_counts.sum().T.to_frame(name="n_word")

print(vocabulary.shape)
vocabulary.head()
```

    (7714, 1)

      n_word

      00
      5

      000
      24

      008704050406
      2

      0121
      1

      01223585236
      1

# Calculating Naive Bayes Parameters

Now, I can begin creating the spam filter. The Naive Bayes algorithm will need to answer these two probability questions to be able to classify new messages:

\begin{equation}
P(Spam | w_1,w_2, ..., w_n) \propto P(Spam) \cdot \prod_{i=1}^{n}P(w_i|Spam)
\end{equation}

\begin{equation}
P(Ham | w_1,w_2, ..., w_n) \propto P(Ham) \cdot \prod_{i=1}^{n}P(w_i|Ham)
\end{equation}

Also, to calculate $P(w_i|Spam)$ and $P(w_i|Ham)$ inside the formulas above, I'll need to use these equations:

\begin{equation}
P(w_i|Spam) = \frac{N_{w_i|Spam} + \alpha}{N_{Spam} + \alpha \cdot N_{Vocabulary}}
\end{equation}

\begin{equation}
P(w_i|Ham) = \frac{N_{w_i|Ham} + \alpha}{N_{Ham} + \alpha \cdot N_{Vocabulary}}
\end{equation}

where:

- $N_{w_i|Spam}$ is the number of times the word $w_i$ occurs in spam messages;
- $ N_{w_i|Ham}$ is the number of times the word $w_i$ occurs in non-spam messages;
- $N_{Spam}$ is the total number of words in spam messages;
- $ N_{Ham}$ is the total number of words in non-spam messages;
- $N_{Vocabulary}$ is the total number of words in the vocabulary;
- $ \alpha$ is the smoothing parameter.

Some of the terms in the four equations above will have the same value for every new message. I can calculate the value of these terms once and avoid doing the computations again when a new messages comes in.

I'll also use Laplace smoothing and set $\alpha = 1$.

```
# Isolating spam and ham messages
spam_messages = training_set_clean.loc[training_set_clean['Label'] == 'spam']
ham_messages = training_set_clean.loc[training_set_clean['Label'] == 'ham']

# P(Spam) and P(Ham)
p_spam = spam_messages.shape[0] / training_set_clean.shape[0]
p_ham = ham_messages.shape[0] / training_set_clean.shape[0]

# N_w|Spam
vocabulary["n_word_given_spam"] = spam_messages.iloc[:,2:].sum()

# N_Spam
n_spam = vocabulary["n_word_given_spam"].sum()

# N_w|Ham
vocabulary["n_word_given_ham"] = ham_messages.iloc[:,2:].sum()

# N_Ham
n_ham = vocabulary["n_word_given_ham"].sum()

# N_Vocabulary
n_vocabulary = vocabulary.shape[0]

# Laplace smoothing
alpha = 1
```

Now that I have the constant terms calculated above, I can move on with calculating the parameters $P(w_i|Spam)$ and $P(w_i|Ham)$. Each parameter will thus be a conditional probability value associated with each word in the vocabulary.

The parameters are calculated using the formulas:

\begin{equation}
P(w_i|Spam) = \frac{N_{w_i|Spam} + \alpha}{N_{Spam} + \alpha \cdot N_{Vocabulary}}
\end{equation}

\begin{equation}
P(w_i|Ham) = \frac{N_{w_i|Ham} + \alpha}{N_{Ham} + \alpha \cdot N_{Vocabulary}}
\end{equation}

```
# Calculate parameters
vocabulary["p_word_given_spam"] = (vocabulary["n_word_given_spam"]+alpha)/(n_spam+alpha*n_vocabulary)
vocabulary["p_word_given_ham"] = (vocabulary["n_word_given_ham"]+alpha)/(n_ham+alpha*n_vocabulary)

print(vocabulary.shape)
vocabulary.head()
```

    (7714, 5)

      n_word
      n_word_given_spam
      n_word_given_ham
      p_word_given_spam
      p_word_given_ham

      00
      5
      5
      0
      0.000276
      0.000017

      000
      24
      24
      0
      0.001151
      0.000017

      008704050406
      2
      2
      0
      0.000138
      0.000017

      0121
      1
      1
      0
      0.000092
      0.000017

      01223585236
      1
      1
      0
      0.000092
      0.000017

# Classification of New Messages

Now that I have all parameters calculated, I can start creating the spam filter `message_classification()`. The spam filter can be understood as a function that:

- Takes in as input a new message $(w_1, w_2, ..., w_n)$.
- Calculates $P(Spam|w_1, w_2, ..., w_n)$ and $P(Ham|w_1, w_2, ..., w_n)$.
- Compares the values of $P(Spam|w_1, w_2, ..., w_n)$ and $P(Ham|w_1, w_2, ..., w_n)$, and:
    - If $P(Ham|w_1, w_2, ..., w_n) > P(Spam|w_1, w_2, ..., w_n)$, then the message is classified as ham.
    - If $P(Ham|w_1, w_2, ..., w_n) \leq P(Spam|w_1, w_2, ..., w_n)$, then the message is classified as spam.

```
# Classification function
def message_classification(message):

  # Transform the message with the training vocabulary
  message = count_vect.transform([message])
  message = pd.Series(message.A[0], index=count_vect.get_feature_names())
  message = message.loc[message!=0]

  # Calculate the naive probabilities
  p_spam_given_message = vocabulary.loc[message.index,"p_word_given_spam"].pow(message).prod() * p_spam
  p_ham_given_message = vocabulary.loc[message.index,"p_word_given_ham"].pow(message).prod() * p_ham

  if p_ham_given_message > p_spam_given_message:
    return 'ham'
  else:
    return 'spam'
```

```
message_classification("WINNER!! This is the secret code to unlock the money: C3421.")
```

    'spam'

```
message_classification("Sounds good, Tom, then see u there")
```

    'ham'

## Measuring the Spam Filter's Results

The two results above look promising, but let's see how well the filter does on the test set.

Let's create a new column in our test set with classification results.

```
test_set['predicted'] = test_set['SMS'].apply(message_classification)

test_set.head()
```

      Label
      SMS
      predicted

      1078
      ham
      Yep, by the pretty sculpture
      ham

      4028
      ham
      Yes, princess. Are you going to make me moan?
      ham

      958
      ham
      Welp apparently he retired
      ham

      4642
      ham
      Havent.
      ham

      4674
      ham
      I forgot 2 ask ü all smth.. There's a card on ...
      ham

Now, let's evaluate the spam filter to find out how well does.

I'm going to use two functions for this goal, `sklearn.metrics.confusion_matrix()` and `sklearn.metrics.classification_report()`.

```
unique_label = np.unique([test_set["Label"], test_set["predicted"]])
cmtx = pd.DataFrame(
  confusion_matrix(test_set["Label"], test_set["predicted"], labels=unique_label),
  index=['true:{:}'.format(x) for x in unique_label],
  columns=['pred:{:}'.format(x) for x in unique_label]
)
print("Confusion Matrix\n")
print(cmtx)
print("\n------------------------------------------------\n")
print("Classification Report\n")
print(classification_report(test_set["Label"], test_set["predicted"]))
```

    Confusion Matrix

               pred:ham  pred:spam
    true:ham        965          3
    true:spam         8        139

    ------------------------------------------------

    Classification Report

                  precision    recall  f1-score   support

             ham       0.99      1.00      0.99       968
            spam       0.98      0.95      0.96       147

        accuracy                           0.99      1115
       macro avg       0.99      0.97      0.98      1115
    weighted avg       0.99      0.99      0.99      1115

The accuracy and f1-score are both 99%, which is really good. Our spam filter looked at 1,115 messages that it hasn't seen in training, and classified 1,104 correctly.

# Conclusion

In this project, I build a spam filter for SMS messages using the multinomial Naive Bayes algorithm. The filter had an accuracy of 99% on the test set I used, which is a pretty good result. My initial goal was an accuracy of over 80%, and I managed to do way better than that.