```python
import numpy as np
from itertools import *
from sympy.combinatorics.graycode import GrayCode
from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister, execute, Aer
from qiskit.tools.visualization import circuit_drawer
```

    C:\Users\Chams\Anaconda2\envs\hackathon\lib\site-packages\qiskit\__init__.py:67: DeprecationWarning: Using Qiskit with Python 3.6 is deprecated as of the 0.17.0 release. Support for running Qiskit with Python 3.6 will be removed in a future release.
      "future release.", DeprecationWarning)

# Generating partition of G
The following functions generate the partition into a maximal collection of invariant subsets

```python
def fullSymDecomp(n,start,end):
    ''' Returns dictionary with FULLY symmetric group G partition (decimal values) into maximal subsets
        n: total number of qubits
        start: position of the first element of the fully symmetric group (starting from 0)
        end: position of the last element of the fully symmetric group
    '''
    Gpart = {}
    for i in range(end-start+1):
        Gpart[i] = []
        for comb in list(combinations(range(end-start),i)):
            cval = 0
            for el in comb:
                cval += 2**(n-1-(el+start))
            Gpart[i].append(cval)
    return Gpart

def combine(sys):
    ''' Return dictionary with G partition (decimal values) into maximal subsets by combining the fully Symmetric blocks
        sys: list with the sizes of the permutation sub-groups '''
    start = 0
    n = sum(sys)
    Gpart = fullSymDecomp(n,0,sys[0])
    if len(sys) > 1:
        start = sys[0]
        for gsize in sys[1:]:
            end = start+gsize
            oldGpart = Gpart
            Gpart = fullSymDecomp(n,start,end)
            newGpart = {}
            count = 0
            for oldkey in oldGpart:
                for key in Gpart:
                    newGpart[count] = []
                    for el in Gpart[key]:
                        for oldel in oldGpart[oldkey]:
                            newGpart[count].append(el+oldel)
                    count += 1
            start += gsize
            Gpart = newGpart
    return Gpart

def convertGToBinary(Gpart,n):
    ''' Convert the decimal indices used to construct the subsets into binary arrays
        n: total number of qubits '''
    binGpart = {k: [("{0:0"+str(n)+"b}").format(el) for el in v] for k, v in Gpart.items()}
    return binGpart

def verifyPermutation(sys,pi):
    start = 0
    for i in range(len(sys)):
        expsum,actsum = 0,0
        for j in range(start,start+sys[i]):
            expsum += j+1
            actsum += int(pi[j])
        if expsum != actsum:
            raise TypeError('Your input permutation does not belong to the defined group')
        start += sys[i]
```

# Angles for the controlled rotations
The definition of the angles for the initial and controlled rotations used in the quantum circuit to generate the transversal

```python
def alpha1(T,n):
    ''' Calculation of initial rotation angle
        INPUT T: list with the indices (converted to decimal) of the elements in the transversal
              n: total number of qubits in current fully symmetric block of the circuit
        OUTPUT: rotation angle \alpha_1
    '''
    sum1,sum2 = 0,0
    for el in T:
        if el >= 2**(n-1): sum1 += 1 # as we work with a uniform superposition, the denominators of the coefficents cancel out,
                                     #   so just +|1|^2 = +1
        else: sum2 += 1
    if sum2 == 0:
        if sum1 == 0:
            raise ZeroDivisionError('Found: 0/0')
        else: return np.pi/2
    else:
        return np.arctan(np.sqrt(sum1/sum2))

def ControlledAlpha(T,n,j,control):
    ''' Calculation of controlled \alpha_j
        INPUT T: list of elements of the transversal
              n: total number of qubits in current fully symmetric block of the circuit
              j: target qubit index (starting from 0 up to n-1, reading the arrays from left to right)
              control: control qubits state in decimal form (calculated for the full qubit array,
                       assuming all the rest are zeros, e.g. '01XX' -> '0100' -> control = 4)
    '''
    sum1,sum2 = 0,0
    for el in T:
        if el >= control and el < control + 2**(n-j):
            if (el-control) >= 2**(n-j-1): sum1 += 1
            else: sum2 += 1
    if sum2 == 0:
        if sum1 == 0:
            raise ZeroDivisionError('Found: 0/0') # as we showed, this should never happen
        else: return np.pi/2
    else:
        return np.arctan(np.sqrt(sum1/sum2))
```

# Quantum subroutines
+ Controlled-n Ry gate implementation
+ convert a control from mix of zeros and ones to full ones, or unconvert full ones to desired control

```python
def nControlledRy(start,target,theta,q,circ):
    ''' Adds to the quantum circuit a rotation around 'y' gate controlled by any number of qubits, i.e. the rotation
            is performed if all the control qubits are 1. The control qubits are defined in block, e.g. "from qubit0 to qubit4"
        INPUT start: first qubit used as control
              target: qubit over which the Ry gate is applied (therefore, the control is from qubit 'start' to 'target-1')
              theta: desried angle of rotation (in radians)
              q: quantum register with circuit's qubits
              circ: original circuit in where the nC-Ry gate is implemented
        OUTPUT: new circuit with the nC-Ry(theta) gate added
    '''
    theta = theta/2**(target-start+1-3) # redefine theta adapted for the circuit (see report)
    a = GrayCode(target-start)
    gray = list(a.generate_gray(start='0'*(target-start-1)+'1')) # gray code starting at '00..01' instead of '10..00'
                                                                 #  -> will read them from right to left
    ## ------------------------
    ## Implementing the algorithm to generate a controlled-n following a gray code as explained in the report
    ## ------------------------
    prevel = gray[0]
    lm = start
    parity = 1
    circ.cu3(parity*theta,0,0,control_qubit=q[lm],target_qubit=q[target])
    for el in gray[1:]:
        parity *= -1
        val = int(el,2) - int(prevel,2)
        ind = int(np.log2(abs(val)))+start
        if ind > lm:
            lm = ind
            circ.cx(control_qubit=q[ind-1],target_qubit=q[ind])
        else:
            circ.cx(control_qubit=q[ind],target_qubit=q[lm])
        circ.cu3(parity*theta,0,0,control_qubit=q[lm],target_qubit=q[target])
        prevel = el
    return circ

def AdjustControl(control,n,start,target,q,circ):
    ''' Apply X gates (NOT) to the control qubits to convert them to a full ones control (and the opposite process as well)
        INPUT control: decimal value of the desired control obtained as 'CTRLXXXX' -> 'CTRL0000' -> bin-to-dec('CTRL0000')
              n: total number of qubits in current fully symmetric block of the circuit
              start: first qubit used as control
              target: qubit over which the Ry gate is applied
              q: quantum register with circuit's qubits
              circ: original circuit in where the nC-Ry gate is implemented
        OUTPUT new circuit with the adjusted control
    '''
    ctr = ("{0:0"+str(n)+"b}").format(control)
    for i in range(target-start):
        if int(ctr[i]) == 0:
            circ.x(q[i+start])
    return circ
```

# Transversal quantum generator for the full symmetric group
The following function puts all together to output a circuit that generates a quantum transversal in uniform superposition

```python
def FullSymQSuperposition(T,n,start,q,circ):
    ''' Circuit generator to create a quantum transversal for the FULLY symmetric group
        INPUT T: on demand fully symmetric group transversal
              n: total number of qubits in current fully symmetric block of the circuit
              start: first qubit of the fully symmetric block
              q: quantum register with circuit's qubits
              circ: original circuit in where the nC-Ry gate is implemented
        OUTPUT quantum circuit generating an on-demand fully symmetric group transversal '''
    for target in range(start,start+n):
        if target == start:
            circ.u3(alpha1(T,n)*2,0,0,q[start])
            circ.z(q[start])
        else:
            for ctl in range(2**(target-start)):
                states = [int(v) for v in list(("{0:0"+str(target-start)+"b}").format(ctl))]
                control = int(("{0:0"+str(target-start)+"b}").format(ctl) + '0'*(n-target+start),2) # calculate control's decimal
                                                                                # value within subblock
                for el in T:
                    if el >= control and el < control + 2**(n-target+start): # checking whether T element contains control array
                        theta = ControlledAlpha(T,n,target-start,control)
                        circ = AdjustControl(control,n,start,target,q,circ)
                        circ = nControlledRy(start,target,theta,q,circ)
                        circ = AdjustControl(control,n,start,target,q,circ)
                        break
            circ.z(q[target]) # convert Ry rotation into the desired form [[cos\th sin\th],[sin\th -cos\th]]
    return circ
```

# Interactive solver
+ Introduce your symmetry group. If composed, separate blocks with 'x'. e.g. 3 for S3, and 2x1 for S2xS1
+ Check whether you want to input a permutation (permuteT) or on-demand transversal (onDemandT) and input the corresponding information
+ Choose the number of shots for the circuit simulation
+ Choose the file name (or path/filename) for your output circuits (in '.tex' and '.png' format)

(Some errors are raised by the program if some of the inputs are wrong in some way. In case of facing such error, please skip the traceback part and go directly to the end where you will see: TypeError: "description of what went wrong")

OUTPUT:
+ Transversal and eventually the permuted transversal
+ QASM simulations results given by the number of measurements for each superimposed state
+ Result verifications
+ Drawing of the circuit exported in .png and .tex format

```python
from ipywidgets import widgets
from IPython.display import display,clear_output

class quantumTransversal(object):
    def __init__(self):
        # defaults
        self.permT = False
        self.ondemand = False
        self.sys = [3]
        self.n = sum(self.sys)
        self.pi = '123'
        self.T,self.odT,self.pT = [],[],[]
        self.nshots = 1024
        self.filename = 'output'
    def defineSys(self,symgroup):
        sys = []
        for el in symgroup.split('x'):
            if len(el) > 0:
                sys.append(int(el))
        self.sys = sys
        self.n = sum(sys)
    def permuteT(self,permuteT):
        self.permT = permuteT
    def isOnDemand(self,onDemandT):
        self.ondemand = onDemandT
    def onDemandT(self,onDemandT):
        odT = []
        for el in onDemandT.split(','):
            if len(el) > 0: odT.append(int(el,2))
        self.odT = odT
    def permutation(self,T_permutation):
        if type(T_permutation) == str:
            self.pi = T_permutation
        else: raise TypeError("Wrong input format. Please enter a length 'n' string, such as '132'")
    def shotN(self,NoOfShots):
        if len(NoOfShots) > 0:
            self.nshots = int(NoOfShots)
    def setfilename(self,path_filename):
        self.filename = path_filename

    def buildT(self):
        G = combine(self.sys)
        print('G partition into maximal collection of subsets:')
        print(convertGToBinary(G,self.n))
        print('')
        T = []
        for key in G:
            T.append(G[key][0])
        self.T = T
    def applyPermutation(self):
        if len(self.pi) != self.n:
            raise TypeError('Wrong permutator length. Please reenter a length "n" string for the permutator')
        newT = []
        verifyPermutation(self.sys,self.pi)
        for el in self.T:
            oldt = ("{0:0"+str(self.n)+"b}").format(el)
            newt = ''
            for i in self.pi:
                newt += str(oldt[int(i)-1])
            newT.append(int(newt,2))
        self.pT = newT

    def run(self,b):
        with out:
            clear_output()
            qT.buildT()
            if self.ondemand: T = self.odT
            else: T = self.T
            print('Transversal')
            print([("{0:0"+str(self.n)+"b}").format(el) for el in T])
            print('')
            if self.permT:
                qT.applyPermutation()
                print('Permuted Transversal')
                print([("{0:0"+str(self.n)+"b}").format(el) for el in self.pT])
                print('')
                T = self.pT

            q = QuantumRegister(self.n,'qubit')
            c = ClassicalRegister(self.n,'bit')
            circ = QuantumCircuit(q,c)

            if not self.ondemand:
                start = 0
                for i in range(len(self.sys)):
                    ns = self.sys[i]
                    Ts = [int(("{0:0"+str(self.n)+"b}").format(t)[start:start+ns],2) for t in T]
                    circ = FullSymQSuperposition(Ts,ns,start,q,circ)
                    start += ns
            else:
                circ = FullSymQSuperposition(T,self.n,0,q,circ)

            print('Generating quantum circuit...')
            print('')
            print('QASM simulation results out of a total of ' + str(self.nshots) + ' shots')
            circ.measure(q,c)
            # Execute the circuit
            job = execute(circ, backend = Aer.get_backend('qasm_simulator'), shots=self.nshots)
            result = job.result()
            reversedOutput = {key[::-1]:result.get_counts(circ)[key] for key in result.get_counts(circ)}
            sortedOutput = {key:reversedOutput[key] for key in sorted(reversedOutput)}
            print(sortedOutput)
            print('')

            print('Testing results...')
            fail,count = 0,0
            for key in sortedOutput:
                count += 1
                if int(key,2) not in T:
                    print('Wrong output state :(')
                    fail = 1
                    break
            if fail == 0:
                if count != len(T):
                    print('Wrong output state :(')
                else:
                    print('Perfect match of superimposed states!')

            avgcount = self.nshots/len(T)
            std = 0
            for key in sortedOutput:
                std += abs(sortedOutput[key]-avgcount)**2
            std = (std/(len(T)-1))**0.5/self.nshots

            print('The relative standard deviation of the measurement results is ... ' + str(round(len(T)*std*100)) + ' % ,')
            print('while the expected value for a uniform distribution is .......... ' + \
                  str(round(len(T)*((len(T)-1)/self.nshots/len(T)**2)**0.5*100))+ ' %')

            print('')
            print('Drawing the circuit...')
            circuit_drawer(circ,output='mpl',filename=self.filename)
            circuit_drawer(circ,output='latex_source',filename=self.filename+'.tex')
            print('A .png and latex source file with your circuit have been generated under your specified path/filename')

qT = quantumTransversal()
widgets.interact(qT.defineSys,symgroup='2x1')
widgets.interact(qT.permuteT,permuteT=False)
widgets.interact(qT.permutation,T_permutation='123')
widgets.interact(qT.isOnDemand,onDemandT=False)
widgets.interact(qT.onDemandT,onDemandT='000,001,011,111')
widgets.interact(qT.shotN,NoOfShots='1024')
widgets.interact(qT.setfilename,path_filename='output')

button = widgets.Button(description="Run")
display(button)

out = widgets.Output()
display(out)

button.on_click(qT.run)
```

    interactive(children=(Text(value='2x1', description='symgroup'), Output()), _dom_classes=('widget-interact',))

    interactive(children=(Checkbox(value=False, description='permuteT'), Output()), _dom_classes=('widget-interact…

    interactive(children=(Text(value='123', description='T_permutation'), Output()), _dom_classes=('widget-interac…

    interactive(children=(Checkbox(value=False, description='onDemandT'), Output()), _dom_classes=('widget-interac…

    interactive(children=(Text(value='000,001,011,111', description='onDemandT'), Output()), _dom_classes=('widget…

    interactive(children=(Text(value='1024', description='NoOfShots'), Output()), _dom_classes=('widget-interact',…

    interactive(children=(Text(value='output', description='path_filename'), Output()), _dom_classes=('widget-inte…

    Button(description='Run', style=ButtonStyle())

    Output()

```python

```


# Neural Networks

Neural Networks are a whole new area of study and application that can be intimidating, but which represents some of the most powerful tools and techniques that we possess in machine learning today. In spite of the hype surrounding these topics I hope that you will come to see them as just another tool in your toolbag with their own strengths and weaknesses. They are useful, but they are not a silver bullet, and they are not always preferable to other -perhaps more simple- machine learning methods.

The goal of this week is to familiarize you with the fundamental theory, terminology and libraries that will enable you to approach different neural network architectures (called topologies) in a sophisticated manner. This week will not be a run-through of the history of Neural Networks and each of the individual advancements leading up to current technologies -we don't have time for that. We will spend some time on some older methods, but only to the degree that they will help introduce us to relevant terminology and understand more complex versions of these technologies.

## "Neural" Networks

Neural Networks aren't exactly a new technology, but recent breakthroughs have revitalized the area. For example the "Perceptron" -one of the basic building blocks of the technology- was invented in 1957.

Artificial Neural Networks are a computational model that was inspired by how neural networks in the brain process information. In the brain electrochemical signals flow from earlier neurons through the dendrites of the cell toward the cell body. If the received signals surpass a certain threshold with a given timing then the neuron fires sending a large spike of energy down the axon and through the axon terminals to other neurons down the line.

In Artificial Neural Networks the neurons or "nodes" are similar in that they receive inputs and pass on their signal to the next layer of nodes if a certain threshold is reached, but that's about where the similarities end. Remember that ANNs are not brains. Don't fall into the common trap of assuming that if an Artificial Neural Network has as many nodes as the human brain that it will be just as powerful or just as capable. The goal with ANNs is not to create a realistic model of the brain but to craft robust algorithms and data structures that can model the complex relationships found in data.

## Starting off Very Simple

Portions of this part of lecture taken liberally from Jay Alammar's "[A Visual and Interactive Guide to the Basics of Neural Networks](http://jalammar.github.io/visual-interactive-guide-basics-neural-networks/)" -> Jay's work here will be very good for helping us connect some of the principles of Neural Networks to past concepts, and for establishing some beginning terminology, but we're going to need more than this post if we want to start writing and using neural networks with code. I also want to elaborate a little bit on what he has written.

### Buying a House

Say you want to buy a house, you're quoted $400,000 for a 2000 square foot house. Is that good? Is that bad? How can we know? Well, we can gather up some more prices of other homes in the area and their square footages to compare.

| Square Footage | Price  |
|----------------|--------|
| 2104           | 399900 |
| 1600           | 329900 |
| 2400           | 369000 |

Ok, so now we have a little bit more data to compare to, how can we compare the price that we're getting to the prices of these houses? We can't compare price directly because all of these homes are different sizes, so we decide to calculate an average price per square foot for these three homes and we'll compare that to the average square footage price for the house that we have been looking at.

It turns out that these houses cost about $180 per square foot. Now, assuming that information we gathered about houses in the area is representative of housing prices in the area we can create a little diagram for ourselves to help us evaluate the price of any home:

What does our neural network predict should be the price of the house that we are looking at?

## Node Maps

We've created our first *extremely* basic neural network, it takes an input, modifies it by a weight (180) and reports an output. Please, instead of thinking about neural networks as if they were brains think about them as a function or a "mapping" from inputs to outputs just like we have established many times before.

What we have here above is what's known as a "Node Map" it's a visual diagram of the architecture or "topology" of our neural network. It's kind of like a flow chart in that it shows the path from inputs to outputs. They are usually color coded and help us understand at a very high level, some of the differences in architecture between kinds of neural networks. Just like with all the areas of machine learning that we have studied before there is a "[zoo](http://www.asimovinstitute.org/neural-network-zoo/)" of neural network architectures:

### Types of Layers:

There are three main types of neuron layers in a typical NN topology:

### Input or Visible Layers

The Input Layer is what receives input from our dataset. Sometimes it is called the visible layer because it's the only part that is exposed to our data and that our data interacts with directly. Typically node maps are drawn with one input node for each of the different inputs/features/columns of our dataset that will be passed to the network.

### Hidden Layers

Layers after the input layer are called Hidden Layers. This is because they cannot be accessed except through the input layer. They're inside of the network and they perform their functions, but we don't directly interact with them. The simplest possible network is to have a single neuron in the hidden layer that just outputs the value. "Deep Learning" apart from being a big buzzword simply means that we are using a Neural Network that has multiple hidden layers. "Deep Learning" is a big part of the renewed hype around ANNs because it allows networks that are structured in specific ways to accomplish tasks that were previously out of reach (image recognition for example).

### Output Layers

The final layer is called the Output Layer. The purpose of the output layer is to output a vector of values that is in a format that is suitable for the type of problem that we're trying to address. Typically the output value is modified by an "activation function" to transform it into a format that makes sense for our context, here's a couple of examples:

- NNs applied to a regression problem might have a single output node with no activation function because what we want is an unbounded continuous value.

- NNS applied to a binary classification problem might use a sigmoid function as its activation function in order to squishify values down to represent a probability. Outputs in this case would represent the probability of predicting the primary class of interest. We can turn this into a class-specific prediction by rounding the outputted sigmoid probability up to 1 or down to 0.

- NNS applied to multiclass classification problems might have multiple output nodes in the output layer, one for each class that we're trying to predict. This output layer would probably employ what's called a "softmax function" for accomplishing this. Don't worry about how that activation function works just yet, we'll get to it soon.

## Back to our House Problem

 Is a neural network that predicts home prices based on the average square footage of homes in the are a good one? How can we evaluate the quality of our predictions?

We evaluate the quality of our predictions just like we would any machine learning algorithm. We look at what the algorithm predicted and compare it to the actual price to see how far off we are. We know that our predictions are getting better if our predictions get closer to the actual prices in our training data / testing data.

So how is our Neural Network doing?

```
import pandas as pd
import matplotlib.pyplot as plt

x = [2104, 1600, 2400]
y = [399.9, 329.9, 369]
predictions = [379, 288, 432]
difference = [21, 42, -63]
difference_squared = [449, 1756, 3969]

df = pd.DataFrame({'sqft': x, "price_in_thous": y, "predictions": predictions,
                   "difference": difference, "diff_squared": difference_squared})
df.head()
```

      diff_squared
      difference
      predictions
      price_in_thous
      sqft

      0
      449
      21
      379
      399.9
      2104

      1
      1756
      42
      288
      329.9
      1600

      2
      3969
      -63
      432
      369.0
      2400

```
plt.scatter(df.sqft, df.price_in_thous)
plt.xlabel("Square Feet")
plt.ylabel("Price in Thousands of Dollars")
plt.title("Home Price Estimates")

# Plot the line
price_per_sqft = .180

y_hat = [price_per_sqft*x for x in df.sqft]
plt.plot(df.sqft, y_hat, color="red")

plt.show()
```

## Look Familiar?

Well it should because we've just plotted a regression line hypothesis based on the average square foot price of the three homes that we looked at. Our average error for this line is:

```
print("Mean Squared Error:", df.diff_squared.mean())
```

    Mean Squared Error: 2058.0

Now, we didn't fit this line using any regression techniques so it's likely that it's **not** the line of best fit. But we could try out other lines and see if the error gets bigger or smaller. Well it just so happens that in this two dimensional world that if our line is stuck to the origin on one end then we don't have all of the freedom that we want to control our predictions. We need some way to slide it up and down the y axis so that we can make any line that we want in this two-dimensional world. This y-intercept is a constant value, or in other words it's always weighted by 1. We're going to call this y-intercept our "bias" term. We'll define this a little bit better in a minute.

Now I don't love this diagram because it shows our "bias" term as if it was a new horizontal layer, in reality the bias term interacts with a single layer to affect all layers after the hidden layer that it is associated with. This diagram will give you a better idea:

```
import numpy as np

weight = .100
bias = 150

X = np.array([2104, 1600, 2400])
y = np.array([399.9, 329.9, 369])
predictions = [weight*x + bias for x in X]
print("Predictions:", predictions)
difference = y-predictions
print("Difference:", difference)
difference_squared = difference**2
print("Difference Squared:", difference_squared)
MSE = difference_squared.sum()/len(difference_squared)
print("Mean Squared Error", MSE)
```

    Predictions: [360.4, 310.0, 390.0]
    Difference: [ 39.5  19.9 -21. ]
    Difference Squared: [1560.25  396.01  441.  ]
    Mean Squared Error 799.0866666666665

```
plt.scatter(X, y)
plt.xlabel("Square Feet")
plt.ylabel("Price in Thousands of Dollars")
plt.title("Home Price Estimates")

# Plot the line
price_per_sqft = .180

y_hat = [weight*x + bias for x in X]
plt.plot(X, y_hat, color="green")

y_hat = [price_per_sqft*x for x in df.sqft]
plt.plot(df.sqft, y_hat, color="red")

plt.show()
```

The bias value in coordination with our weight gives our network the full range of motion that it needs to find the best way to explain the patterns in the data.

Well, what if instead of only looking at square footage we collected a second data point like number of bathrooms there is in each house. Lets also get a few more data points while we're at it:

```
sqft = [2104, 1600, 2400, 1416, 3000, 1985, 1534, 1427, 1380, 1419]
bathrooms = [3, 3, 3, 2, 4, 4, 3, 3, 3, 3]
price = [399900, 329900, 369000, 232000, 539000, 299900, 314900, 198999,
        212000, 242500]

df = pd.DataFrame({'sqft': sqft, 'bathrooms': bathrooms, 'price': price})
df.head(10)
```

      bathrooms
      price
      sqft

      0
      3
      399900
      2104

      1
      3
      329900
      1600

      2
      3
      369000
      2400

      3
      2
      232000
      1416

      4
      4
      539000
      3000

      5
      4
      299900
      1985

      6
      3
      314900
      1534

      7
      3
      198999
      1427

      8
      3
      212000
      1380

      9
      3
      242500
      1419

## Two Input Nodes

Now that we have two inputs or two x variables how might we modify the architecture of our network to accept these inputs?

You'll notice that now we need a weight for each one of our input nodes, or for each feature of our dataset. If we were to write out the calculation that is happening as our data flows through this network it might look something like this:

## I hope this looks extremely familiar.

Now we have a new network that takes in two inputs. But now instead of guessing at slope and coefficient values and evaluating them. How can we explore more combinations of weights and bias until we find the values that minimize our Mean-Squared Error?

[Lets Try It!](http://jalammar.github.io/visual-interactive-guide-basics-neural-networks/)

## ANNs are not Linear Regression

It just so happens that the one that we have defined in this way acts the same way. We've defined a neural network that does the same thing as Linear Regression for a couple of different reasons

1) To familiarize you with node maps (even if I don't love the ones in the article).

2) To introduce the concepts of "weights" and "biases" in a context that you're already familiar with.

3) Each layer in a feed-forward neural network affects the next layer by a weighted sum of inputs plus a bias term.

4) To make it plain to you that we can search out the optimal weights and biases of a neural network through gradient descent if we have a loss function that evaluates the quality of our predictions compared to the y values in our training data.

# Why are Neural Networks so Powerful?

So if we can use a very simple neural network to represent a linear regression problem, what do neural networks with lots of additional features/layers represent? Why can't we just use linear regression for everything?

## Nonlinearities

Linear regression is built to fit more or less linear models in n-dimensional space. While it is true that we can fit non-linear features using linear regression as we include polynomial features, the reality is that in order to fit really curvy nonlinear patterns in data in really complex high dimensional features spaces, the number of polynomial terms that we would have to include in a linear or logistic regression model faces a problem of combinatorial explosion in terms of the number of features that would be required.

Well what kinds of data exhibit these really strong nonlinearities? Well, how about images for example:

Think about all of the different forms a car could take in an image yet our human brains recognize them as cars flawlessly. Think about all of the complexity that we would have to take into account. How could you even begin to feature engineer a dataset of pixel values in order to give a regression algorithm something that it could cue on as being a car or not a car?

[Andrew Ng can probably explain this better than I can](https://www.youtube.com/watch?v=1ZhtwInuOD0)

The interactions between layers of neurons in neural networks in a way accounts for that combinatorial explosion within the structure of the algorithm as needed instead of us having to provide it beforehand.

# The Perceptron

The first and simplest kind of neural network that we could talk about is the perceptron. A perceptron is just a single node or neuron of a neural network with nothing else. It can take any number of inputs and spit out an output. What a neuron does is it takes each of the input values, multplies each of them by a weight, sums all of these products up, and then passes the sum through what is called an "activation function" the result of which is the final value.

I really like figure 2.1 found in this [pdf](http://www.uta.fi/sis/tie/neuro/index/Neurocomputing2.pdf) even though it doesn't have bias term represented there.

If we were to write what is happening in some verbose mathematical notation, it might look something like this:

\begin{align}
 y = sigmoid(\sum(weight_{1}input_{1} + weight_{2}input_{2} + weight_{3}input_{3}) + bias)
\end{align}

Understanding what happens with a single neuron is important because this is the same pattern that will take place for all of our networks.

When imagining a neural network I like to think about the arrows as representing the weights, like a wire that has a certain amount of resistance and only lets a certain amount of current through. And I like to think about the node itself as containing the prescribed activation function that neuron will use to decide how much signal to pass onto the next layer.

# Activation Functions (transfer functions)

In Neural Networks, each node has an activation function. Each node in a given layer typically has the same activation function. These activation functions are the biggest piece of neural networks that have been inspired by actual biology. The activation function decides whether a cell "fires" or not. Sometimes it is said that the cell is "activated" or not. In Artificial Neural Networks activation functions decide how much signal to pass onto the next layer. This is why they are sometimes referred to as transfer functions because they determine how much signal is transferred to the next layer.

## Common Activation Functions:

# Implementing a Perceptron from scratch in Python

### Establish training data

```
import numpy as np
np.random.seed(1)

inputs = np.array([[0,0,1],
                   [1,1,1],
                   [1,0,1],
                   [0,1,1]])

correct_outputs = [[0],
                  [1],
                  [1],
                  [0]]
```

### Sigmoid activation function and its derivative for updating weights

```
def sigmoid(x):
  return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
  return sigmoid(x) * (1 - sigmoid(x))
```

## Updating weights with derivative of sigmoid function:

### Initialize random weights for our three inputs

```
weights = 2 * np.random.random((3,1)) - 1
weights
```

    array([[ 0.07763347],
           [-0.16161097],
           [ 0.370439  ]])

### Calculate weighted sum of inputs and weights

```
weighted_sum = np.dot(inputs, weights)
weighted_sum
```

    array([[0.370439  ],
           [0.2864615 ],
           [0.44807247],
           [0.20882803]])

### Output the activated value for the end of 1 training epoch

```
activated_output = sigmoid(weighted_sum)
activated_output
```

    array([[0.59156505],
           [0.57112963],
           [0.61018085],
           [0.55201811]])

### take difference of output and true values to calculate error

```
error = correct_outputs - activated_output
error
```

    array([[-0.59156505],
           [ 0.42887037],
           [ 0.38981915],
           [-0.55201811]])

```
adjustments = error * sigmoid_derivative(activated_output) ### Gradient Descent / Backpropagation Magic
adjustments
```

    array([[-0.13567152],
           [ 0.09892855],
           [ 0.08891826],
           [-0.12800298]])

### update weights

```
weights += np.dot(inputs.T, adjustments)
weights
```

    array([[ 0.26548028],
           [-0.1906854 ],
           [ 0.29461131]])

### Put it all together

```
for iteration in range(10000):

  # Weighted sum of inputs and weights
  weighted_sum = np.dot(inputs, weights)

  # Activate with sigmoid function
  activated_output = sigmoid(weighted_sum)

  # Calculate Error
  error = correct_outputs - activated_output

  # Calculate weight adjustments with sigmoid_derivative
  adjustments = error * sigmoid_derivative(activated_output)

  # Update weights
  weights += np.dot(inputs.T, adjustments)

print('optimized weights after training: ')
print(weights)

print("Output After Training:")
print(activated_output)
```

    optimized weights after training:
    [[15.03811832]
     [-0.4073112 ]
     [-7.23243198]]
    Output After Training:
    [[7.22311394e-04]
     [9.99388067e-01]
     [9.99592713e-01]
     [4.80768887e-04]]

# Perceptrons can classify linearly separable classes

<https://www.bogotobogo.com/python/scikit-learn/Perceptron_Model_with_Iris_DataSet.php>

```
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)

# Grab 50 versicolor and 50 virginica
df.iloc[145:150, 0:5]
```

      0
      1
      2
      3
      4

      145
      6.7
      3.0
      5.2
      2.3
      Iris-virginica

      146
      6.3
      2.5
      5.0
      1.9
      Iris-virginica

      147
      6.5
      3.0
      5.2
      2.0
      Iris-virginica

      148
      6.2
      3.4
      5.4
      2.3
      Iris-virginica

      149
      5.9
      3.0
      5.1
      1.8
      Iris-virginica

```
y = df.iloc[0:100, 4].values
y
```

    array(['Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
           'Iris-setosa', 'Iris-setosa', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
           'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor'],
          dtype=object)

```
y = np.where(y == 'Iris-setosa', -1, 1)
y
```

    array([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
           -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
           -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  1,
            1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
            1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
            1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1])

```
X = df.iloc[0:100, [0, 2]].values
X
```

    array([[5.1, 1.4],
           [4.9, 1.4],
           [4.7, 1.3],
           [4.6, 1.5],
           [5. , 1.4],
           [5.4, 1.7],
           [4.6, 1.4],
           [5. , 1.5],
           [4.4, 1.4],
           [4.9, 1.5],
           [5.4, 1.5],
           [4.8, 1.6],
           [4.8, 1.4],
           [4.3, 1.1],
           [5.8, 1.2],
           [5.7, 1.5],
           [5.4, 1.3],
           [5.1, 1.4],
           [5.7, 1.7],
           [5.1, 1.5],
           [5.4, 1.7],
           [5.1, 1.5],
           [4.6, 1. ],
           [5.1, 1.7],
           [4.8, 1.9],
           [5. , 1.6],
           [5. , 1.6],
           [5.2, 1.5],
           [5.2, 1.4],
           [4.7, 1.6],
           [4.8, 1.6],
           [5.4, 1.5],
           [5.2, 1.5],
           [5.5, 1.4],
           [4.9, 1.5],
           [5. , 1.2],
           [5.5, 1.3],
           [4.9, 1.5],
           [4.4, 1.3],
           [5.1, 1.5],
           [5. , 1.3],
           [4.5, 1.3],
           [4.4, 1.3],
           [5. , 1.6],
           [5.1, 1.9],
           [4.8, 1.4],
           [5.1, 1.6],
           [4.6, 1.4],
           [5.3, 1.5],
           [5. , 1.4],
           [7. , 4.7],
           [6.4, 4.5],
           [6.9, 4.9],
           [5.5, 4. ],
           [6.5, 4.6],
           [5.7, 4.5],
           [6.3, 4.7],
           [4.9, 3.3],
           [6.6, 4.6],
           [5.2, 3.9],
           [5. , 3.5],
           [5.9, 4.2],
           [6. , 4. ],
           [6.1, 4.7],
           [5.6, 3.6],
           [6.7, 4.4],
           [5.6, 4.5],
           [5.8, 4.1],
           [6.2, 4.5],
           [5.6, 3.9],
           [5.9, 4.8],
           [6.1, 4. ],
           [6.3, 4.9],
           [6.1, 4.7],
           [6.4, 4.3],
           [6.6, 4.4],
           [6.8, 4.8],
           [6.7, 5. ],
           [6. , 4.5],
           [5.7, 3.5],
           [5.5, 3.8],
           [5.5, 3.7],
           [5.8, 3.9],
           [6. , 5.1],
           [5.4, 4.5],
           [6. , 4.5],
           [6.7, 4.7],
           [6.3, 4.4],
           [5.6, 4.1],
           [5.5, 4. ],
           [5.5, 4.4],
           [6.1, 4.6],
           [5.8, 4. ],
           [5. , 3.3],
           [5.6, 4.2],
           [5.7, 4.2],
           [5.7, 4.2],
           [6.2, 4.3],
           [5.1, 3. ],
           [5.7, 4.1]])

```
plt.scatter(X[:50, 0], X[:50, 1], color='red', marker='o', label='setosa')
plt.scatter(X[50:100, 0], X[50:100, 1], color='blue', marker='x', label='versicolor')
plt.xlabel('petal length')
plt.ylabel('sepal length')
plt.legend(loc='upper left')
plt.show()
```

```
class Perceptron(object):
  def __init__(self, rate = 0.01, niter = 10):
    self.rate = rate
    self.niter = niter

  def fit(self, X, y):
    """Fit training data
    X : Training vectors, X.shape : [#samples, #features]
    y : Target values, y.shape : [#samples]
    """

    # weights
    self.weight = np.zeros(1 + X.shape[1])

    # Number of misclassifications
    self.errors = []  # Number of misclassifications

    for i in range(self.niter):
      err = 0
      for xi, target in zip(X, y):
        delta_w = self.rate * (target - self.predict(xi))
        self.weight[1:] += delta_w * xi
        self.weight[0] += delta_w
        err += int(delta_w != 0.0)
      self.errors.append(err)
    return self

  def net_input(self, X):
    """Calculate net input"""
    return np.dot(X, self.weight[1:]) + self.weight[0]

  def predict(self, X):
    """Return class label after unit step"""
    return np.where(self.net_input(X) >= 0.0, 1, -1)
```

```
pn = Perceptron(0.1, 10)
pn.fit(X, y)
plt.plot(range(1, len(pn.errors) + 1), pn.errors, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Number of misclassifications')
plt.show()
```

```
from matplotlib.colors import ListedColormap
def plot_decision_regions(X, y, classifier, resolution=0.02):
  # setup marker generator and color map
  markers = ('s', 'x', 'o', '^', 'v')
  colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
  cmap = ListedColormap(colors[:len(np.unique(y))])

  # plot the decision surface
  x1_min, x1_max = X[:,  0].min() - 1, X[:, 0].max() + 1
  x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
  xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
  np.arange(x2_min, x2_max, resolution))
  Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
  Z = Z.reshape(xx1.shape)
  plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
  plt.xlim(xx1.min(), xx1.max())
  plt.ylim(xx2.min(), xx2.max())

  # plot class samples
  for idx, cl in enumerate(np.unique(y)):
    plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
    alpha=0.8, c=cmap(idx),
    marker=markers[idx], label=cl)
```

```
>>> plot_decision_regions(X, y, classifier=pn)
>>> plt.xlabel('sepal length [cm]')
>>> plt.ylabel('petal length [cm]')
>>> plt.legend(loc='upper left')
plt.show()
```

## Additional Resources:

- 3 Blue 1 Brown Neural Network Video: <https://youtu.be/aircAruvnKk>
- Andrew Ng Neural Network Introduction Videos: <https://www.youtube.com/watch?v=1ZhtwInuOD0&list=PLLssT5z_DsK-h9vYZkQkYNWcItqhlRJLN&index=43>


```
# Requirements - qiskit
!pip install qiskit
```

```

from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import *

# Three-Step process : Encode the input, do computation, and extract output

#Exercise 1 - Draw a circuit with 6 qubits, 6 output bits and one-one measure.

n_qubit = 6
n_output = n_qubit
circuit_1 = QuantumCircuit(n_qubit, n_output)

for i in range(6):
  circuit_1.measure(i,i)

circuit_1.draw()

```

    Requirement already satisfied: qiskit in /usr/local/lib/python3.6/dist-packages (0.20.0)
    Requirement already satisfied: qiskit-aer==0.6.1 in /usr/local/lib/python3.6/dist-packages (from qiskit) (0.6.1)
    Requirement already satisfied: qiskit-aqua==0.7.5 in /usr/local/lib/python3.6/dist-packages (from qiskit) (0.7.5)
    Requirement already satisfied: qiskit-ignis==0.4.0 in /usr/local/lib/python3.6/dist-packages (from qiskit) (0.4.0)
    Requirement already satisfied: qiskit-ibmq-provider==0.8.0 in /usr/local/lib/python3.6/dist-packages (from qiskit) (0.8.0)
    Requirement already satisfied: qiskit-terra==0.15.1 in /usr/local/lib/python3.6/dist-packages (from qiskit) (0.15.1)
    Requirement already satisfied: numpy>=1.16.3; python_version > "3.5" in /usr/local/lib/python3.6/dist-packages (from qiskit-aer==0.6.1->qiskit) (1.18.5)
    Requirement already satisfied: pybind11>=2.4 in /usr/local/lib/python3.6/dist-packages (from qiskit-aer==0.6.1->qiskit) (2.5.0)
    Requirement already satisfied: scipy>=1.0; python_version > "3.5" in /usr/local/lib/python3.6/dist-packages (from qiskit-aer==0.6.1->qiskit) (1.4.1)
    Requirement already satisfied: cython>=0.27.1 in /usr/local/lib/python3.6/dist-packages (from qiskit-aer==0.6.1->qiskit) (0.29.21)
    Requirement already satisfied: pandas in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (1.0.5)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (5.4.8)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (0.22.2.post1)
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (49.6.0)
    Requirement already satisfied: networkx>=2.2 in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (2.5)
    Requirement already satisfied: quandl in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (3.5.2)
    Requirement already satisfied: docplex in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (2.15.194)
    Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (2.10.0)
    Requirement already satisfied: yfinance in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (0.1.54)
    Requirement already satisfied: dlx in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (1.0.4)
    Requirement already satisfied: fastdtw in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (0.3.4)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.6/dist-packages (from qiskit-aqua==0.7.5->qiskit) (1.6.2)
    Requirement already satisfied: websockets<8,>=7; python_version <= "3.6" in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (7.0)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (2.8.1)
    Requirement already satisfied: nest-asyncio!=1.1.0,>=1.0.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (1.4.0)
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (2.23.0)
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (1.24.3)
    Requirement already satisfied: requests-ntlm>=1.1.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-ibmq-provider==0.8.0->qiskit) (1.1.0)
    Requirement already satisfied: retworkx>=0.4.0 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (0.4.0)
    Requirement already satisfied: python-constraint>=1.4 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (1.4.0)
    Requirement already satisfied: contextvars>=2.4; python_version < "3.7" in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (2.4)
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (2.6.0)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (0.3.2)
    Requirement already satisfied: ply>=3.10 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (3.11)
    Requirement already satisfied: fastjsonschema>=2.10 in /usr/local/lib/python3.6/dist-packages (from qiskit-terra==0.15.1->qiskit) (2.14.5)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.6/dist-packages (from pandas->qiskit-aqua==0.7.5->qiskit) (2018.9)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.6/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.7.5->qiskit) (0.16.0)
    Requirement already satisfied: decorator>=4.3.0 in /usr/local/lib/python3.6/dist-packages (from networkx>=2.2->qiskit-aqua==0.7.5->qiskit) (4.4.2)
    Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from quandl->qiskit-aqua==0.7.5->qiskit) (1.15.0)
    Requirement already satisfied: inflection>=0.3.1 in /usr/local/lib/python3.6/dist-packages (from quandl->qiskit-aqua==0.7.5->qiskit) (0.5.1)
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.6/dist-packages (from quandl->qiskit-aqua==0.7.5->qiskit) (8.4.0)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.6/dist-packages (from yfinance->qiskit-aqua==0.7.5->qiskit) (0.0.9)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.6/dist-packages (from sympy>=1.3->qiskit-aqua==0.7.5->qiskit) (1.1.0)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.8.0->qiskit) (2020.6.20)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.8.0->qiskit) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.8.0->qiskit) (3.0.4)
    Requirement already satisfied: ntlm-auth>=1.0.2 in /usr/local/lib/python3.6/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.8.0->qiskit) (1.5.0)
    Requirement already satisfied: cryptography>=1.3 in /usr/local/lib/python3.6/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.8.0->qiskit) (3.1)
    Requirement already satisfied: immutables>=0.9 in /usr/local/lib/python3.6/dist-packages (from contextvars>=2.4; python_version < "3.7"->qiskit-terra==0.15.1->qiskit) (0.14)
    Requirement already satisfied: cffi!=1.11.3,>=1.8 in /usr/local/lib/python3.6/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.8.0->qiskit) (1.14.2)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.6/dist-packages (from cffi!=1.11.3,>=1.8->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.8.0->qiskit) (2.20)

     ┌─┐
q_0: ┤M├───────────────
     └╥┘┌─┐
q_1: ─╫─┤M├────────────
      ║ └╥┘┌─┐
q_2: ─╫──╫─┤M├─────────
      ║  ║ └╥┘┌─┐
q_3: ─╫──╫──╫─┤M├──────
      ║  ║  ║ └╥┘┌─┐
q_4: ─╫──╫──╫──╫─┤M├───
      ║  ║  ║  ║ └╥┘┌─┐
q_5: ─╫──╫──╫──╫──╫─┤M├
      ║  ║  ║  ║  ║ └╥┘
c: 6/═╩══╩══╩══╩══╩══╩═
      0  1  2  3  4  5

```
counts =  execute(circuit_1, Aer.get_backend('qasm_simulator')).result()
plot_histogram(counts.get_counts())
```

```
#Creating an Adder Circuit

# NOT Gate for Qubits is performed by function x

n=5

circuit_2 = QuantumCircuit(n,n)
# There are 5 qubits. The next line specifies to NOT the thrid qubit

circuit_2.x(3)

circuit_2.draw()
```

q_0: ─────

q_1: ─────

q_2: ─────
     ┌───┐
q_3: ┤ X ├
     └───┘
q_4: ─────

c: 5/═════

```
for l in range(n):
  circuit_2.measure(l,l)

circuit_2.draw()
```

          ┌─┐
q_0: ─────┤M├─────────
          └╥┘┌─┐
q_1: ──────╫─┤M├──────
           ║ └╥┘┌─┐
q_2: ──────╫──╫─┤M├───
     ┌───┐ ║  ║ └╥┘┌─┐
q_3: ┤ X ├─╫──╫──╫─┤M├
     └┬─┬┘ ║  ║  ║ └╥┘
q_4: ─┤M├──╫──╫──╫──╫─
      └╥┘  ║  ║  ║  ║
c: 5/══╩═══╩══╩══╩══╩═
       4   0  1  2  3

```

counts_2 = execute(circuit_2, Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_2)

# Notice - Qiskit numbers the bit in a string from right to left

```

```
print("We wrote 2^3 in a 5-simple bit computer")
```

    We wrote 2^3 in a 5-simple bit computer

**Qubit Half Adder Cicuit Diagram**

**Let's start**

```
# Role of XOR for rightmost bit (whether two input bits are different or not =) is done by CNOT gate, or controlled-NOT gate.
#Function for this in Qiskit is cx

circuit_3 = QuantumCircuit(3,3)
circuit_3.x(2)
circuit_3.x(1)

# 1 XOR 1 is 0 . Result is stored in the target quibit (one with bigger circle/square)
circuit_3.cx(1,2)

for m in range(3):
  circuit_3.measure(m,m)

circuit_3.draw()

```

          ┌─┐
q_0: ─────┤M├───────────
     ┌───┐└╥┘     ┌─┐
q_1: ┤ X ├─╫───■──┤M├───
     ├───┤ ║ ┌─┴─┐└╥┘┌─┐
q_2: ┤ X ├─╫─┤ X ├─╫─┤M├
     └───┘ ║ └───┘ ║ └╥┘
c: 3/══════╩═══════╩══╩═
           0       1  2

```
counts_3 = execute(circuit_3, Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_3)

```

```
# The need for something not CNOT gate
circuit_4 = QuantumCircuit(2,2)
#Input is 01. Reading from right to left
circuit_4.x(0)
#Input is for CNOT gate
circuit_4.cx(0,1)
# Format of this function is ----> circuit_4.cx(control_qubit, target_qubit)
# Basically, it does ---> NOT on the target if control is 1

circuit_4.measure(0,0)
circuit_4.measure(1,1)

circuit_4.draw()
```

     ┌───┐     ┌─┐
q_0: ┤ X ├──■──┤M├───
     └───┘┌─┴─┐└╥┘┌─┐
q_1: ─────┤ X ├─╫─┤M├
          └───┘ ║ └╥┘
c: 2/═══════════╩══╩═
                0  1

```
#Finding the output

counts_4 = execute(circuit_4, Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_4)
```

```
#We need to solve this issue. We don't wish to override the existing input bits

circuit_ha_1 = QuantumCircuit(4, 2)
#Encoding as required. Let's try this for the above example - 01
circuit_ha_1.x(0)

#Introducing Barriers
circuit_ha_1.barrier()

# NOTE = PERFORMING XOR WITH ANY INPUT BIT 0 OR 1 RESULTS IN THE SAME BIT.
# HENCE, NOTE THE OPERATION WE PERFORM. We copy the input bits to third qubits (first output bit)
# and then perform XOR.

#use cnots to write XOR of the inputs on qubit 2
circuit_ha_1.cx(0,2) #Input bit 1 copied to third qubit by performing cnot with it
circuit_ha_1.cx(1,2) #Input bit 2 is cnot with third qubit, containing value of first input bit. Result stored in it only.
circuit_ha_1.barrier()

#extracting outputs
circuit_ha_1.measure(2, 0) #extract XOR value
circuit_ha_1.measure(3,1)

circuit_ha_1.draw()
```

     ┌───┐ ░            ░
q_0: ┤ X ├─░───■────────░───────
     └───┘ ░   │        ░
q_1: ──────░───┼────■───░───────
           ░ ┌─┴─┐┌─┴─┐ ░ ┌─┐
q_2: ──────░─┤ X ├┤ X ├─░─┤M├───
           ░ └───┘└───┘ ░ └╥┘┌─┐
q_3: ──────░────────────░──╫─┤M├
           ░            ░  ║ └╥┘
c: 2/══════════════════════╩══╩═
                           0  1

```
# Half-way there !
# If we notice, the second output bit will always remain zero for all cases.
# We know that for our half-adder, there exists one case where the second output bit is one
# This case is 1+1 = 10  (in decimal 1+1=2 )

#Our Method - If both the input bits are 1, then output is 1. It resembles a CNOT for two qubits... or simply speaking a simple AND (a cnot for two bits)
# AND in qubits is the famous "TOfolli gate"
# Function is ccx
```

**Let's build finally the  - Quantum Half Adder**

```
half_adder = QuantumCircuit(4, 2)
#Input - 11
half_adder.x(0)
half_adder.x(1)

half_adder.barrier()

#Two CNOTs
half_adder.cx(0,2)
half_adder.cx(1,2)
#Toffoli for the special case
half_adder.ccx(0,1,3) #format - .ccx(first input bit, second input bit, output bit)

#second barrier
half_adder.barrier()

#measures
half_adder.measure(2,0)
half_adder.measure(3,1)

#let's execute
counts_ha = execute(half_adder,Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_ha)
```

```

```

**Done !**

```
import qiskit
qiskit.__qiskit_version__
```

    {'qiskit': None,
     'qiskit-aer': '0.6.1',
     'qiskit-aqua': '0.7.5',
     'qiskit-ibmq-provider': '0.8.0',
     'qiskit-ignis': '0.4.0',
     'qiskit-terra': '0.15.1'}

**Let's check this half-adder for other inputs 01, 10 and 00**

**Expected results are -**

**0+0 = 00**

**1+0 = 01**

**0+1 = 01**

```
half_adder_00 = QuantumCircuit(4, 2)
#Input - 00

half_adder_00.barrier()

#Two CNOTs
half_adder_00.cx(0,2)
half_adder_00.cx(1,2)
#Toffoli for the special case
half_adder_00.ccx(0,1,3) #format - .ccx(first input bit, second input bit, output bit)

#second barrier
half_adder_00.barrier()

#measures
half_adder_00.measure(2,0)
half_adder_00.measure(3,1)

#let's execute
counts_ha = execute(half_adder_00,Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_ha)
```

```
half_adder_10 = QuantumCircuit(4, 2)
#Input - 10
half_adder_10.x(1)

half_adder_10.barrier()

#Two CNOTs
half_adder_10.cx(0,2)
half_adder_10.cx(1,2)
#Toffoli for the special case
half_adder_10.ccx(0,1,3) #format - .ccx(first input bit, second input bit, output bit)

#second barrier
half_adder_10.barrier()

#measures
half_adder_10.measure(2,0)
half_adder_10.measure(3,1)

#let's execute
counts_ha = execute(half_adder_10,Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_ha)
```

```
half_adder_01 = QuantumCircuit(4, 2)
#Input - 01
half_adder_01.x(0)

half_adder_01.barrier()

#Two CNOTs
half_adder_01.cx(0,2)
half_adder_01.cx(1,2)
#Toffoli for the special case
half_adder_01.ccx(0,1,3) #format - .ccx(first input bit, second input bit, output bit)

#second barrier
half_adder_01.barrier()

#measures
half_adder_01.measure(2,0)
half_adder_01.measure(3,1)

#let's execute
counts_ha = execute(half_adder_01,Aer.get_backend('qasm_simulator')).result().get_counts()
plot_histogram(counts_ha)
```

**Our Half-Adder works perfectly fine. This is the example of a Quantum Half Adder**

Thanks. Ajay


# Biomechanical analysis of vertical jumps

> Marcos Duarte
> [Laboratory of Biomechanics and Motor Control](http://pesquisa.ufabc.edu.br/bmclab)
> Federal University of ABC, Brazil

Contents

1  Python setup

2  Introduction

3  Center of gravity

3.1  Measurement of the jump height from flight time

3.1.1  Beware: the flight time you measure not always is the flight time you want

3.2  Measurement of the jump height using a force platform

3.3  Force platform

3.3.1  Ground reaction force during vertical jump

3.4  Jump height from the impulse measurement

3.5  Kinetic analysis of a vertical jump

4  Further reading

5  Video lectures on the internet

6  Problems

7  References

## Python setup

```python
import numpy as np
from scipy.integrate import cumtrapz
%matplotlib inline
import matplotlib.pyplot as plt
from IPython.display import YouTubeVideo
```

## Introduction

A vertical jump is the act of raising (in the vertical direction) the body center of gravity using your own muscle forces and jumping into the air ([Wikipedia](https://en.wikipedia.org/wiki/Vertical_jump)).

Besides being an important element in several sports, the vertical jump can be used to train and evaluate some physical capacities (e.g., strength and power) of a person.

In this notebook we will study some aspects of the biomechanics of vertical jumps.

## Center of gravity

Center of gravity or center of mass is a measure of the average location in space of the body considering all body segments, their mass and their position.
From Mechanics, the exact definitions of these quantities are:

- **Center of mass (CM)**: The center of mass (or barycenter) is the unique point at the center of a distribution of mass in space that has the property that the weighted position vectors relative to this point sum to zero. SI unit: $m$ (vector).
- **Center of gravity (CG)**: Center of gravity is the point in an object around which the resultant torque due to gravity forces vanishes. Near the surface of the earth, where the gravity acts downward as a parallel force field, the center of gravity and the center of mass are the same. SI unit: $m$ (vector).

The mathematical definition for the center of mass or center of gravity of a system with N objects (or particles), each with mass $m_i$ and position $r_i$ is:

\begin{equation}
\begin{array}{l l}
\vec{r}_{cm} = \dfrac{1}{M}\sum_{i=1}^N m_{i}\vec{r}_i \quad\quad \text{where} \quad M = \sum_{i=1}^N m_{i} \\
\vec{r}_{cg} = \dfrac{1}{Mg}\sum_{i=1}^N m_{i}g_{i}\vec{r}_i \quad \text{where} \quad Mg = \sum_{i=1}^N m_{i}g_{i}
\end{array}
\label{}
\end{equation}

If we consider $g$ constant:

\begin{equation}
\begin{array}{l l}
\vec{r}_{cg} = \dfrac{g}{Mg}\sum_{i=1}^N m_{i} \: \vec{r}_i = \dfrac{1}{M}\sum_{i=1}^N m_{i}\:\vec{r}_i \\
\\
\vec{r}_{cg} = \vec{r}_{cm}
\end{array}
\label{}
\end{equation}

This means that how much a person jumps is measured by the displacement of his or her center of gravity, and not by how much the feet was raised in space. For instance, the following Youtube video entitled "Highest Vertical jump 62 inches" (157.5 cm!) shows a great vertical jump, but in fact its height is 'just' about half of that:

```python
YouTubeVideo('NUyql5IFTNY')
```

So, the true measurement of the height of a vertical jump is the displacement of the center of gravity in the vertical direction. A problem is that to measure the center of gravity position is usually too complicated because we would have to estimate the position of each body segment.
Instead, there are alternative methods with varying degrees of accuracy for measuring the height of vertical jump ([see here for a list](http://www.topendsports.com/testing/equipment-verticaljump.htm)):

 - [**Sargent Jump Test**](http://www.brianmac.co.uk/sgtjump.htm). How high you can reach an object.
 - **Flight time measurement**. From Newton's laws of motion, the height of a jump is related to the flight time under controlled conditions. One can measure the flight time using a [contact mat](http://www.probotics.org/JustJump/verticalJump.htm), light sensor, [accelerometer](http://www.jssm.org/vol11/n1/17/v11n1-17pdf.pdf), video (the number of frames the jumper is in the air), attaching a cable to the jumper's waist and measuring the displacement of this cable, etc.
 - **Force platform measurements**. A device called [force platform](https://en.wikipedia.org/wiki/Force_platform) which measures the forces applied on the ground as a function of time can also be used to measure the flight time and then the height of vertical jump. But the real utility of the force platform is that it can actually measure the force produced by the jumper to find the displacement of the center of gravity using Newton's laws of motion.

### Measurement of the jump height from flight time

We can estimate the height of a jump measuring for how long the jumper stayed in the air during the jump (flight time). At the vertical direction and ignoring the air resistance, the only external force that acts on the body in the air is the (constant) gravitational force, with magnitude $P = -mg$, where $g$ is the acceleration of gravity (about $9.8 m/s^2$ on the surface of the Earth). Using the equation of motion for a body with constant acceleration, the height of the center of gravity of a body in the air at a certain time is:

\begin{equation}
h(t) = h_0 + v_0 t - \frac{gt^2}{2}
\label{}
\end{equation}

At the maximum height ($h$, the jump height), the vertical velocity of the body is zero. We can take use this property to calculate the jump height from the time of falling:

\begin{equation}
h = \frac{gt_{fall}^2}{2}
\label{}
\end{equation}

Because the time of falling is equal to the time of rising, $t_{flight} = t_{rise} + t_{fall} = 2 t_{fall}$, the jump height as a function of the flight time is:

\begin{equation}
h = \frac{gt_{\text{flight}}^2}{8}
\label{}
\end{equation}

This simple equation is the principle of measurement of the jump height any an instrument that measures the time the feet is not in contact with ground (using a pressure mat or a photocell sensor).
There are (were) even shoes using this principle of measurement:

```python
YouTubeVideo('QXnQ4ENjTuY')
```

#### Beware: the flight time you measure not always is the flight time you want

However, the flight time, measured as the time without contact with the ground, during a jump is not necessarily equal to the flight time of the body center of gravity (which is the measure we need to estimate the actual height jump).

For example, if the jumper flexes knees and hips at the landing phase, the measured flight time will be larger but not the flight time of the body center of gravity. Because that, a more accurate method is to use a force platform as we will see now.

### Measurement of the jump height using a force platform

Let's draw the [free body diagram](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/FreeBodyDiagram.ipynb) for a person performing a jump:

A person during the propulsion phase of jumping and the corresponding free body diagram drawn for the center of mass. GRF(t) is the time-varying ground reaction force.

So, according to the Euler's version of the Newton's second law (for the motion of the body center of mass), the dynamics (as a function of time) for the body center of mass during a jump is given by:

\begin{equation}
GRF(t) - mg = ma(t)
\label{}
\end{equation}

Where $GRF(t)$ is the ground reaction force applied by the ground on the jumper, $m$ is the subject mass, and $a(t)$ the center of mass acceleration.

### Force platform

Force platform or force plate is an instrument for measuring the forces generated by a body acting on the platform. A force plate is an electromechanical transducer that measures force typically by measuring the deformation (strain) on its sensors and converting that to electrical signals. These electrical signals are converted back to force and moment of force using calibration factors.

Figure. A force plate and its coordinate system convention (from Kistler Force Plate Formulae).

Usually the force platform is placed on the floor and we are interested not in the force applied on the force platform, but in its reaction, the force that the force platform applied on the jumper, which, according to Newton's third law of motion, has the same magnitude and line of action but opposite direction. Because of that, usually the forces measured by the force platform are referred as ground reaction forces.

Most of the commercial force platforms are able to measure the vectors force, $[F_X,\, F_Y,\, F_Z]$, and moment of force, $[M_X,\, M_Y,\, M_Z]$, from which the [center of pressure](https://en.wikipedia.org/wiki/Center_of_pressure_(terrestrial_locomotion) (COP, the point of application of the resultant force on the force plate) can be calculated $[COP_X,\, COP_Y]$. Because of that, these force platforms are known as six-components. Force platforms that can measure only the vertical force component and two moments of force (or the two COPs) are known as three-components.

Read more about force platforms in chapter 5 or Winter's book and in Cross (1999).

#### Ground reaction force during vertical jump

Here is a video from Youtube of a person jumping and a plot of the ground reaction force measured with a force platform:

```python
YouTubeVideo('qN3apht8zRs')
```

Here is a plot of the vertical component of the ground reaction force measured with a force platform during a vertical jump with countermovement:

```python
# load data file
GRFv = np.loadtxt('https://raw.githubusercontent.com/BMClab/BMC/master/data/GRFZjump.txt',
                  skiprows=0, unpack=False)
#GRFv = np.loadtxt('./../data/GRFZjump.txt', skiprows=0, unpack=False)

# sampling frequency (Hz)
freq = 600
time = np.arange(0, len(GRFv)/freq, 1/freq)
# gravitational acceleration, m/s2
g = 9.8
# subject's mass
m = np.mean(GRFv[0:int(freq/10)])/g
# plot GRF data
fig, ax = plt.subplots(1, 1, figsize=(10, 4))
ax.plot(time, GRFv, 'b', linewidth='3')
# plot subject's weight
ax.plot([time[0], time[-1]], [m*g, m*g], 'k', linewidth='2')
ax.set_xlabel('Time [s]', fontsize=16)
ax.set_ylabel('Vertical GRF [N]', fontsize=16)
ax.set_xticks(np.arange(time[0], time[-1], .2))
ax.grid()
plt.tight_layout()
plt.show()
```

This graph of the vertical component of the ground reaction force shown above has very interesting things:

 1. At the beginning the jumper was standing still and the GRF is equal to the jumper's body weight;
 2. After the jumper started the movement to jump there is a phase where GRF is lower than the body weight even with the jumper completely on the ground;
 3. The GRF increases to about two times the body weight while the jumper is still on the ground;
 4. When the jumper is in the air is clearly indicated by the zero values of GRF;
 5. After landing the GRF reaches a peak of about six times the jumper's body weight.

These are all interesting things but we will discuss only some of them next.
We can detect the takeoff and landing instants by detecting the first and last time GRF is under a certain threshold (close to zero):

```python
# make sure GRFv has no offset
GRFv -= np.min(GRFv)

limiar = 10

inds = np.where(GRFv < limiar)
i1 = inds[0][0]
i2 = inds[0][-1]
print('Takeoff instant {:.3f} s'.format(time[i1]))
print('Landing instant {:.3f} s'.format(time[i2]))
```

    Takeoff instant 0.907 s
    Landing instant 1.323 s

The jump height can be calculated from these instants using the kinematic expression:

```python
h = g*(time[i2]-time[i1])**2/8
print('Jump height {:.3f} m'.format(h))
```

    Jump height 0.213 m

We can detect the beginning of movement by detecting when GRFv starts to go below the body weight:

```python
inds = np.where(GRFv < 0.95*m*g)
i0 = inds[0][0]
print('Jump start {:.3f} s'.format(time[i0]))
```

    Jump start 0.188 s

### Jump height from the impulse measurement

We know that the [mechanical impulse](http://nbviewer.ipython.org/github/BMClab/bmc/blob/master/notebooks/KineticsFundamentalConcepts.ipynb#Impulse) is equal to the change in linear momentum of a body, which for a body with constant mass is simply the body mass times the change in velocity. For a time-varying force, the impulse is:

\begin{equation}
\vec{Imp} = \int_{t_0}^{t_f} \vec{F}(t) \mathrm{d}t = m [\vec{v}_{t_f} - \vec{v}_{t_0}]
\label{}
\end{equation}

For the analysis of a vertical jump, in the vertical direction $F(t) = GRF(t) - mg$ and $F(t)$ is discrete in time (sampled at intervals $\Delta t$):

\begin{equation}
Imp = \sum_{t_0}^{t_f} F(t)\Delta t = m [v_{t_f} - v_{t_0}]
\label{}
\end{equation}

For a jump starting from a rest position, the initial velocity is zero. This means that the change in velocity is equal to the final velocity (at the moment of takeoff). Therefore, we can calculate the final velocity of the propulsion phase as:

\begin{equation}
\begin{array}{l l}
\sum_{t_0}^{t_f} F(t)\Delta t = m[v_f-0] \implies \\
v_f = \dfrac{\sum_{t_0}^{t_f} F(t)\Delta t}{m}
\end{array}
\label{}
\end{equation}

This final velocity is the initial velocity of the aerial phase of the jump.
If we know the initial velocity of the jump, it's straightforward to calculate the jump height, for example, using the [Torricelli's equation](http://en.wikipedia.org/wiki/Torricelli's_equation):

\begin{equation}
v_f^2 = v_0^2 - 2gh
\label{}
\end{equation}

Where $v_i$ and $v_f$ are the initial and final velocities and $h$ is the change in the position of the body center of gravity (the height of the vertical jump) and we considered just the first part of the jump, the upward phase.

From Mechanics we know that the velocities at takeoff and at landing are equal in magnitude and that the time of the upward phase is equal to the time of the downward phase of this ballistic flight.

For the upward phase of the vertical jump:

\begin{equation}
\begin{array}{l l}
0 = v_0^2 - 2gh \implies \\
h = \dfrac{v_0^2}{2g}
\end{array}
\label{}
\end{equation}

We could also have calculated the jump height from the velocity using the [principle of conservation of the mechanical energy](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/KineticsFundamentalConcepts.ipynb#Principles-of-conservation). The sum of the potential and kinetic energies at the instants beginning of the jump ($t_0$) and highest point of the jump trajectory ($t_{hmax}$) are equal:

\begin{equation}
mgh(t_0) + \frac{mv^2(t_0)}{2} = mgh(t_{hmax}) + \frac{mv^2(t_{hmax})}{2}
\label{}
\end{equation}

\begin{equation}
0 + \frac{mv^2(t_0)}{2} =  mgh(t_{hmax}) + 0
\label{}
\end{equation}

\begin{equation}
h(t_{hmax}) = \frac{v^2(t_0)}{2g}
\label{}
\end{equation}

Same expression as before.

### Kinetic analysis of a vertical jump

The force plate can also be used to calculate other mechanical variables to characterize the subject's performance (see Dowling and Vamos 1993) for a list of such variables). For example,

Resultant vertical force on the subject:

\begin{equation}
F(t) = GRF(t) - mg
\label{}
\end{equation}

Vertical acceleration of the center of mass:

\begin{equation}
a(t) = \frac{F(t)}{m}
\label{}
\end{equation}

Vertical velocity of the center of mass (initial velocity equals zero):

\begin{equation}
v(t) = \sum_{t_0}^{t_f} a(t) \Delta t
\label{}
\end{equation}

Vertical displacement of the center of mass (initial displacement equals zero):

\begin{equation}
h(t) = \sum_{t_0}^{t_f} v(t) \Delta t
\label{}
\end{equation}

Power due to the resultant vertical force:

\begin{equation}
P(t) = GRF(t)v(t)
\label{}
\end{equation}

Note that we used the vertical ground reaction force for the calculation of mechanical power, not the resultant force on the body, to represent the total power the jumper is producing. One could argue that during standing the jumper is not actually 'generating' any force (so we should not use GRF), but in this phase, the jumper's speed is zero, and the power is zero.

Let's work with the ground reaction force data from a vertical jump and calculate such quantities.
For now, let's ignore the landing and focus on the propulsion phase of the jump.

```python
time = time[i0:i1+1] - time[i0]
# resultant vertical force
F = GRFv[i0:i1+1] - m*g
# vertical acceleration of the center of mass
a = F/m
# vertical velocity of the center of mass
v = cumtrapz(a, dx=1/freq, initial=0)
# vertical displacement of the center of mass
h = cumtrapz(v, dx=1/freq, initial=0)
# power due to the resultant vertical force
P = (F+m*g)*v
```

And here are the plots for these quantities:

```python
fig, axs = plt.subplots(5, 1, sharex=True, squeeze=True, figsize=(8, 8))
axs[0].plot(time, F, 'b', lw='4', label='Force')
axs[1].plot(time, a, 'b', lw='4', label='Acceleration')
axs[2].plot(time, v, 'b', lw='4', label='Velocity')
axs[3].plot(time, h, 'b', lw='4', label='Displacement')
axs[4].plot(time, P, 'b', lw='4', label='Power')
axs[4].set_xlabel('Time [$s$]', fontsize=14)
axs[4].set_xticks(np.arange(time[0], time[-1], .1))
axs[4].set_xlim([time[0], time[-1]])
ylabel = ['F [$N$]', 'a [$m/s^2$]', 'v [$m/s$]', 'h [$m$]', 'P [$W$]']
for (axi, ylabeli) in zip(axs, ylabel):
    axi.axhline(0, c='r', linewidth='2', alpha=.6)
    axi.set_ylabel(ylabeli, fontsize=14)
    axi.text(.03, .75, axi.get_legend_handles_labels()[1][0],
             transform=axi.transAxes, fontsize=14, c='r')
    axi.yaxis.set_major_locator(plt.MaxNLocator(4))
    axi.yaxis.set_label_coords(-.1, 0.5)
    axi.grid(True)
plt.tight_layout(h_pad=0)
plt.show()
```

Looking at these graphs, we can see that the instant where the jumper has the lowest position is approximately at 0.44 s, where the velocity is also zero. Up to this instant the jumper was moving down (countermovement phase) and from this instant on the jumper starts to rise until loses contact with the ground. This instant also indicates when the power starts to be positive. Looking at the graph of the force, the total area under the curve (the impulse) up to this instant should be zero because if the jumper started at rest and came to zero velocity again, the impulse is zero.

This turning point is used to indicate the change from eccentric (negative power) to concentric (positive power) phase of the jump. Although this instant was detected based on the movement of the center of mass, this roughly indicates the change in the net muscle activation in the body to perform the jump.

Let's visualize these phases in the plots:

```python
inds = np.where(v > 0)
iec = inds[0][0]
print('Instant of the lowest position {:.3f} s'.format(time[iec]))
```

    Instant of the lowest position 0.438 s

```python
fig, axs = plt.subplots(1, 2, sharex=True, squeeze=True, figsize=(12, 4))
axs[0].plot(time, F+m*g, 'b', linewidth='2')
axs[0].plot([time[0], time[-1]], [m*g, m*g], 'k', linewidth='1')
axs[0].fill_between(time[0:iec], F[0:iec]+m*g, m*g, color=[1, 0, 0, .2])
axs[0].fill_between(time[iec:-1], F[iec:-1]+m*g, m*g, color=[0, 1, 0, .2])
axs[0].plot([], [], linewidth=10, color=[1, 0, 0, .2], label='Eccentric')
axs[0].plot([], [], linewidth=10, color=[0, 1, 0, .2], label='Concentric')
axs[0].legend(frameon=False, loc='upper left', fontsize=12)
axs[0].set_ylabel('Vertical GRF [$N$]', fontsize=14)
axs[0].set_xlabel('Time [$s$]', fontsize=14)
axs[0].yaxis.set_major_locator(plt.MaxNLocator(5))
axs[1].plot(time, P, 'b', linewidth='2')
axs[1].plot([time[0], time[-1]], [0, 0], 'k', linewidth='1')
axs[1].fill_between(time[0:iec], P[0:iec], color=[1, 0, 0, .2])
axs[1].fill_between(time[iec:-1], P[iec:-1], color=[0, 1, 0, .2])
axs[1].set_ylabel('Power [$W$]', fontsize=14)
axs[1].set_xlabel('Time [$s$]', fontsize=14)
axs[1].set_xticks(np.arange(time[0], time[-1], .1))
axs[1].yaxis.set_major_locator(plt.MaxNLocator(5))
axs[1].set_xlim([time[0], time[-1]])
plt.tight_layout()
plt.show()
```

## Further reading

- [The Physics of Nike+ Hyperdunk](https://www.wired.com/2012/09/the-physics-of-nike-hyperdunk-2/)
- Linthorne NP (2001) Analysis of standing vertical jumps using a force platform. American Journal of Physics, 69, 1198-1204.

## Video lectures on the internet

- [Physics breakdown of a jump](https://youtu.be/ZaGreqCFOJM)
- [Recap of the force plate analysis](https://youtu.be/u05pLtC-T1s)

## Problems

1. The mechanical work ($W$) by the vertical force is defined as:
$$ W = \int_{h_0}^{h_f} F(h) \mathrm{d}h $$
Which can also be calculated as the variation in the kinetic energy or the integral of mechanical power:
$$ W = \Delta E_K = \int_{t_0}^{t_f} P(t) \mathrm{d}t $$
 a) Use the data of the vertical jump in this text to plot the graph for the ground reaction force versus displacement.
 b) Calculate the mechanical work produced in the entire jump and in the eccentric and concentric phases using the two methods described above.

2. For the dataset in this text, calculate all the variables described in Dowling, Vamos (1993).

## References

- Cross R (1999) [Standing, walking, running, and jumping on a force plate](https://pdfs.semanticscholar.org/1ad0/3557c2bc1ac8e02d89946a17eeadfb7d8a78.pdf). American Journal of Physics, 67, 4, 304-309.
- Dowling JJ, Vamos L (1993) [Identification of Kinetic and Temporal Factors Related to Vertical Jump Performance](./../refs/Dowling93JABvert_jump.pdf). Journal of Applied Biomechanics, 9, 95-110.
- Linthorne NP (2001) Analysis of standing vertical jumps using a force platform. American Journal of Physics, 69, 1198-1204.
- Winter DA (2009) [Kinetics: Forces and Moments of Force](https://elearning2.uniroma1.it/pluginfile.php/92521/mod_folder/content/0/Biomechanics%20and%20Motor%20Control%20of%20Human%20Movement%20-%20ch5.pdf). Chapter 5 of [Biomechanics and motor control of human movement](http://books.google.com.br/books?id=_bFHL08IWfwC). 4 ed. Hoboken, EUA: Wiley.


```python
#@markdown Configuración Inicial
from IPython.utils import io

from google.colab.data_table import DataTable
from IPython.display import Javascript
from IPython.display import display, Markdown, Latex
from IPython.display import Audio, Image
from IPython.display import IFrame, HTML
with io.capture_output() as capt:
  !pip install gradio

import gradio as gr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random

def dLatex(self):
  return display(Latex(self))

def dMarkdown(self):
  return display(Markdown(self))

# Avoids scroll-in-the-scroll in the entire Notebook
# https://stackoverflow.com/a/66891328
def resize_colab_cell():
  display(Javascript(
      'google.colab.output.setIframeHeight(0, true, {maxHeight: 5000})'
      ))
get_ipython().events.register('pre_run_cell', resize_colab_cell)

def dLatex(self):
  return display(Latex(self))

def dMarkdown(self):
  return display(Markdown(self))
```

# Formula Somigliana

\begin{align}
\gamma_0(\varphi)
&=
\frac{
  a\gamma_a\cos^2(\varphi)
  +
  b
  \gamma_b\sin^2(\varphi)
  }{
  \sqrt{
    a^2\cos^2(\varphi)
    +
    b^2\sin^2(\varphi)
  }
}
\\
&=
\gamma_a
\frac{
  1+
  p\sin^2(\varphi)
  }{
  \sqrt{
    1-
    e^2\sin^2(\varphi)
  }
}
\end{align}

* $a$: Radio Ecuatorial
* $b$: Radio Polar
* $\gamma_a$: Gravedad Ecuatorial
* $\gamma_b$: Gravedad Polar
* $\varphi$: Latitud

\begin{align}
p
&=
\frac{b\,\gamma_b}{a\,\gamma_a} - 1
\\
e^2
&=
1 - \frac{b^2}{a^2}
\end{align}

```python
#@markdown Según el elipsoide de referencia GSR80:
#@markdown \begin{align}
#@markdown a&=6\,378.137\,\phantom{0000}\,\mathrm{km}
#@markdown &
#@markdown \gamma_a&=978.032\,677\,15\,\mathrm{Gal}
#@markdown \\
#@markdown b&=6\,356.752\,3141\, \mathrm{km}
#@markdown &
#@markdown \gamma_b&=983.218\,636\,85\, \mathrm{Gal}
#@markdown \end{align}

GSR80 = {
    'ra': 6_378.137,
    'rb': 6_356.752_3141,
    'ya': 978.032_677_15,
    'yb': 983.218_636_85,
    'c_list': [5.279_0414e-3, 2.327_18e-5, 1.262e-7, 7e-10]
    }

def Somigliana(lat, ellipsoid=GSR80):
  phi = np.radians(lat)
  ra, rb = [GSR80[key] for key in ('ra','rb')]
  ya, yb = [GSR80[key] for key in ('ya','yb')]
  Nu = ra*ya*np.cos(phi)**2 +\
       rb*yb*np.sin(phi)**2
  De = np.sqrt(
       ra**2*np.cos(phi)**2 +\
       rb**2*np.sin(phi)**2)
  gamma_0 = Nu/De
  return gamma_0
```

    <IPython.core.display.Javascript object>

```python
#@markdown Para los siguientes se usa una aproximación del tipo
#@markdown \begin{align}
#@markdown \gamma_0(\varphi)
#@markdown =
#@markdown \gamma_a
#@markdown (1 + \beta_0\sin^2 \varphi + \beta_1\sin^2 2\varphi + \cdots)
#@markdown \end{align}

def Int_gravity(lat, parameters):
  phi = np.radians(lat)
  ya   = parameters['ya']
  beta = parameters['beta']
  gamma_0 = ya*(1 +\
                beta[0]*np.sin(phi)**2 +\
                beta[1]*np.sin(2*phi)**2)
  return gamma_0
```

    <IPython.core.display.Javascript object>

```python
#@title 1930
#@markdown $$\gamma_a = 978.049\,\mathrm{Gal} \quad \beta_0 = 5.2884 \cdot 10^{-3} \quad \beta_1 = -5.9 \cdot 10^{-6}$$

par1930 = {
    'ya': 978.049,
    'beta': [5.2884e-3, -5.9e-6]
}
```

    <IPython.core.display.Javascript object>

```python
#@title 1948
#@markdown $$\gamma_a = 978.0373\,\mathrm{Gal} \quad \beta_0 = 5.2891 \cdot 10^{-3} \quad \beta_1 = -5.9 \cdot 10^{-6}$$

par1948 = {
    'ya': 978.0373,
    'beta': [5.2891e-3, -5.9e-6]
}
```

    <IPython.core.display.Javascript object>

```python
#@title 1967
#@markdown $$\gamma_a = 978.0318\,\mathrm{Gal} \quad \beta_0 = 5.3024 \cdot 10^{-3} \quad \beta_1 = -5.9 \cdot 10^{-6}$$

par1967 = {
    'ya': 978.0318,
    'beta': [5.3024e-3, -5.9e-6]
}
```

    <IPython.core.display.Javascript object>

```python
#@title 1980
#@markdown $$\gamma_a = 978.0327,\mathrm{Gal} \quad \beta_0 = 5.3024 \cdot 10^{-3} \quad \beta_1 = -5.8 \cdot 10^{-6}$$

par1980 = {
    'ya': 978.0327,
    'beta': [5.3024e-3, -5.8e-6]
}
```

    <IPython.core.display.Javascript object>

```python
#@title GRS-1980
#@markdown $$\gamma_0(\varphi) = \gamma_a (1 + c_1\sin^2 \varphi + c_2\sin^4 \varphi + c_3\sin^6 \varphi + c_4\sin^8\varphi + \cdots)$$
#@markdown
#@markdown Donde
#@markdown
#@markdown * $\gamma_a = 978.032\,677\,15\,\mathrm{Gal}$
#@markdown * $c_0 = 5.279\,0414\cdot{10}^{-3}$
#@markdown * $c_1 = 2.327\,18\cdot{10}^{-5}$
#@markdown * $c_2 = 1.262\cdot{10}^{-7}$
#@markdown * $c_3 = 7\cdot{10}^{-10}$

def GRS_grav(lat, ellipsoid=GSR80):
  phi = np.radians(lat)
  ya = ellipsoid['ya']
  c  = ellipsoid['c_list']
  gamma_0 = ya*(1 +\
                c[0]*np.sin(phi)**2 +\
                c[1]*np.sin(phi)**4 +\
                c[2]*np.sin(phi)**6 +\
                c[3]*np.sin(phi)**8 )
  return gamma_0
```

    <IPython.core.display.Javascript object>

```python
#@title 1984
#@markdown \begin{align}
#@markdown g_{0}
#@markdown =
#@markdown \gamma_a
#@markdown \frac{
#@markdown   1+p\sin^{2}(\varphi)
#@markdown }{
#@markdown   \sqrt{1-e^2\sin^{2}(2\varphi)}
#@markdown }
#@markdown \end{align}

#@markdown Donde:
#@markdown * $\gamma_a=978.032\,677\,14\,\mathrm{Gal}$
#@markdown * $p\phantom{^2} = 0.001\,931\,851\,386\,39$
#@markdown * $e^2 = 0.006\,694\,379\,990\,13$

par1984 = {
    'ya': 978.032_677_14,
    'p' : 0.001_931_851_386_39,
    'e2': 0.006_694_379_990_13
}

def g1984(lat, par):
  phi = np.radians(lat)
  ya, p, e2 = [par[key] for key in ('ya', 'p', 'e2')]
  Nu = 1 + p*np.sin(phi)**2
  De = np.sqrt(1-e2*np.sin(phi)**2)
  gamma_0 = ya*Nu/De
  return gamma_0
```

    <IPython.core.display.Javascript object>

```python
#@title Tabla
def dic_grav(lat):
  dic = {
      'Formula Somigliana'  : Somigliana(lat, GSR80),
      'Internacional 1930'  : Int_gravity(lat, par1930),
      'Internacional 1948'  : Int_gravity(lat, par1948),
      'Internacional 1967'  : Int_gravity(lat, par1967),
      'Internacional 1980_1': Int_gravity(lat, par1980),
      'Internacional 1980_2': GRS_grav(lat, GSR80),
      'Internacional 1984'  : g1984(lat, par1984),
      }
  return dic

lats = np.linspace(-90, +90, 181, endpoint=True, dtype='int')
data=np.vectorize(dic_grav)(lats)
df = pd.DataFrame(list(data), index=lats)
df.index.name='Lat'
display(DataTable(df, num_rows_per_page=10))
```

    <IPython.core.display.Javascript object>

      Formula Somigliana
      Internacional 1930
      Internacional 1948
      Internacional 1967
      Internacional 1980_1
      Internacional 1980_2
      Internacional 1984

      Lat

      -90
      983.218637
      983.221314
      983.210237
      983.217716
      983.218621
      983.218637
      983.218637

      -89
      983.217050
      983.219732
      983.208654
      983.216129
      983.217034
      983.217050
      983.217050

      -88
      983.212292
      983.214987
      983.203908
      983.211371
      983.212277
      983.212292
      983.212292

      -87
      983.204369
      983.207084
      983.196005
      983.203448
      983.204354
      983.204369
      983.204369

      -86
      983.193291
      983.196034
      983.184954
      983.192370
      983.193276
      983.193291
      983.193291

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      86
      983.193291
      983.196034
      983.184954
      983.192370
      983.193276
      983.193291
      983.193291

      87
      983.204369
      983.207084
      983.196005
      983.203448
      983.204354
      983.204369
      983.204369

      88
      983.212292
      983.214987
      983.203908
      983.211371
      983.212277
      983.212292
      983.212292

      89
      983.217050
      983.219732
      983.208654
      983.216129
      983.217034
      983.217050
      983.217050

      90
      983.218637
      983.221314
      983.210237
      983.217716
      983.218621
      983.218637
      983.218637

181 rows × 7 columns

```python
#@title Gradio
def my_plot(lat, col):
  fig, ax = plt.subplots()
  df.subtract(df[col], axis=0).plot(ax=ax)
  ax.legend(bbox_to_anchor=(1.01, 1))
  ax.set_ylabel('$\gamma_0$ diff (Gal)')
  ax.axvline(x=lat, c='k', lw=1)
  fig.tight_layout(pad=0)
  plt.close()
  tab = {key: f'{value:.6f} Gal' for key, value in dic_grav(lat).items()}
  return fig, tab,

iface = gr.Interface(
    fn=my_plot,
    inputs=[gr.inputs.Slider(-90, +90 , 1, default=0, label='Latitud'),
            gr.inputs.Dropdown(list(df.columns), label='Modelo')],
    outputs=[gr.outputs.Image(type='plot', label='diff'),
             gr.outputs.KeyValues(label='Gravedad')],
    live=True,
    allow_flagging=False,
    allow_screenshot=False,
    title='Gravedad Teorica',
    description='Valores Teoricos de Gravedad',
    article = """<p style='text-align: center'>
    <a href='https://en.wikipedia.org/wiki/Theoretical_gravity'>
    Wikipedia | Theoretical Gravity</a></p>""",
    examples=[[6,'Formula Somigliana']],
    theme='huggingface', # "default", "compact" or "huggingface"
    layout='vertical' # 'horizontal', 'unaligned', 'vertical'
    )

with io.capture_output() as captured:
    iface.launch(inline=True)
print(iface.share_url)
IFrame(src=iface.share_url, width=1200, height=1000)
```

    <IPython.core.display.Javascript object>

    https://45853.gradio.app