# Tutorial 1b: Data frames

(c) 2017 Justin Bois. This work is licensed under a [Creative Commons Attribution License CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). All code contained herein is licensed under an [MIT license](https://opensource.org/licenses/MIT).

```python
import numpy as np
import pandas as pd

import bokeh.io
import bokeh.plotting

from bokeh.models import Legend
from bokeh.plotting import figure, show, output_file

bokeh.io.output_notebook()
```

    Loading BokehJS ...

In this tutorial, we will learn how to load data stored on disk into a Python data structure. We will use Pandas to read in CSV (comma separated value) files and store the results in the very handy Pandas DataFrame. This incredibly flexible and powerful data structure will be a centerpiece in the rest of this course and beyond. In this tutorial, we will learn about what a data frame is and how to use it.

The data set we will use comes from a fun paper about the adhesive properties of frog tongues. The reference is Kleinteich and Gorb, Tongue adhesion in the horned frog Ceratophrys sp., *Sci. Rep.*, 4, 5225, 2014.

## The data file
The data are contained in the file `frog_tongue_adhesion.csv`. Let's look at its contents:

```python
with open('data/frog_tongue_adhesion.csv', 'r') as f:
    for _ in range(20):
        print(next(f), end='')
```

    # These data are from the paper,
    #   Kleinteich and Gorb, Sci. Rep., 4, 5225, 2014.
    # It was featured in the New York Times.
    #    http://www.nytimes.com/2014/08/25/science/a-frog-thats-a-living-breathing-pac-man.html
    #
    # The authors included the data in their supplemental information.
    #
    # Importantly, the ID refers to the identifites of the frogs they tested.
    #   I:   adult, 63 mm snout-vent-length (SVL) and 63.1 g body weight,
    #        Ceratophrys cranwelli crossed with Ceratophrys cornuta
    #   II:  adult, 70 mm SVL and 72.7 g body weight,
    #        Ceratophrys cranwelli crossed with Ceratophrys cornuta
    #   III: juvenile, 28 mm SVL and 12.7 g body weight, Ceratophrys cranwelli
    #   IV:  juvenile, 31 mm SVL and 12.7 g body weight, Ceratophrys cranwelli
    date,ID,trial number,impact force (mN),impact time (ms),impact force / body weight,adhesive force (mN),time frog pulls on target (ms),adhesive force / body weight,adhesive impulse (N-s),total contact area (mm2),contact area without mucus (mm2),contact area with mucus / contact area without mucus,contact pressure (Pa),adhesive strength (Pa)
    2013_02_26,I,3,1205,46,1.95,-785,884,1.27,-0.290,387,70,0.82,3117,-2030
    2013_02_26,I,4,2527,44,4.08,-983,248,1.59,-0.181,101,94,0.07,24923,-9695
    2013_03_01,I,1,1745,34,2.82,-850,211,1.37,-0.157,83,79,0.05,21020,-10239
    2013_03_01,I,2,1556,41,2.51,-455,1025,0.74,-0.170,330,158,0.52,4718,-1381
    2013_03_01,I,3,493,36,0.80,-974,499,1.57,-0.423,245,216,0.12,2012,-3975

## Loading a data set
We use `pd.read_csv()` to load the data set. The data are stored in a **DataFrame**. Let's load a `DataFrame`

```python
df = pd.read_csv('data/frog_tongue_adhesion.csv', comment='#')

# Look at the DataFrame
df.head()
```

      date
      ID
      trial number
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      contact area with mucus / contact area without mucus
      contact pressure (Pa)
      adhesive strength (Pa)

      0
      2013_02_26
      I
      3
      1205
      46
      1.95
      -785
      884
      1.27
      -0.290
      387
      70
      0.82
      3117
      -2030

      1
      2013_02_26
      I
      4
      2527
      44
      4.08
      -983
      248
      1.59
      -0.181
      101
      94
      0.07
      24923
      -9695

      2
      2013_03_01
      I
      1
      1745
      34
      2.82
      -850
      211
      1.37
      -0.157
      83
      79
      0.05
      21020
      -10239

      3
      2013_03_01
      I
      2
      1556
      41
      2.51
      -455
      1025
      0.74
      -0.170
      330
      158
      0.52
      4718
      -1381

      4
      2013_03_01
      I
      3
      493
      36
      0.80
      -974
      499
      1.57
      -0.423
      245
      216
      0.12
      2012
      -3975

```python
# We can access a column of data (slice the data) using the column name
df.head()['impact force (mN)']
```

    0    1205
    1    2527
    2    1745
    3    1556
    4     493
    Name: impact force (mN), dtype: int64

Clearly, the indexing of the rows is preserved. The data were interpreted as integer type (`dtype = int64`), so we want to convert them into floats using `.astype()` method.

```python
# Use df.astype() method to convert it to a NumPy float 64 data type.
df['impact force (mN)'] = df['impact force (mN)'].astype(float)

# Let's check if it worked well
df['impact force (mN)'].dtype
```

    dtype('float64')

Now let's select only the specific impact forces, say, with the impact force above two Newtons. Pandas `DataFrame` can be conveniently sliced with the Booleans.

```python
# Generate True/False array of rows for indexing
inds = df['impact force (mN)'] >= 2000.0

# Take a look
inds.head()
```

    0    False
    1     True
    2    False
    3    False
    4    False
    Name: impact force (mN), dtype: bool

Now we have an array of Booleans that is of the same length as the `DataFrame` itself. Now, we can use the `.loc` featore of a `DataFrame` to slice what we want out of the `DataFrame`.

```python
# Slice out rows we want (with big force)
df_big_force = df.loc[inds,:]

# Let's look at the sliced rows;
# there will be just a couple of high-force valuea
df_big_force
```

      date
      ID
      trial number
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      contact area with mucus / contact area without mucus
      contact pressure (Pa)
      adhesive strength (Pa)

      1
      2013_02_26
      I
      4
      2527.0
      44
      4.08
      -983
      248
      1.59
      -0.181
      101
      94
      0.07
      24923
      -9695

      5
      2013_03_01
      I
      4
      2276.0
      31
      3.68
      -592
      969
      0.96
      -0.176
      341
      106
      0.69
      6676
      -1737

      8
      2013_03_05
      I
      3
      2641.0
      50
      4.27
      -690
      491
      1.12
      -0.239
      269
      224
      0.17
      9824
      -2568

      17
      2013_03_15
      I
      3
      2032.0
      60
      3.28
      -652
      486
      1.05
      -0.257
      147
      134
      0.09
      13784
      -4425

Using `.loc` allows us to index by row and column. We chose all columns (using `:`) and put an array of Booleans for the rows. We get back a `DataFrame` with the rows associated with `True` in the Booleans' array.

Now we only have the strikes of high force. It can be noted that the original indexing of rowas was retained. This is a good idea, as the indices do not have to be integers.

    To overcome this, we can use `iloc` attribute of a `DataFrame`, which give the indexing with sequential integers.

```python
# Indexing using iloc, which enables indexing by the corresponding
# integer sequence
df_big_force['impact force (mN)'].iloc[3]
```

    2032.0

## Tidy data
The data in our `DataFrame` are tidy. This concept comes from the development of databases, but has been generalized to data processing in the recent years. Tidy data refers to data in a tabular form with the following format:

1. Each variable forms a column,
2. Each observation forms a row.
3. Each type of observational unit forms a separate table.

## More data extraction
To extract a single observation (i. e. a single experiment), we can extract a row and see all of the measured quantities from a given strike using `.loc`.

```python
# Slice out experiment with index 42
df.loc[42,:]
```

    date                                                    2013_05_27
    ID                                                             III
    trial number                                                     3
    impact force (mN)                                              324
    impact time (ms)                                               105
    impact force / body weight                                    2.61
    adhesive force (mN)                                           -172
    time frog pulls on target (ms)                                 619
    adhesive force / body weight                                  1.38
    adhesive impulse (N-s)                                      -0.079
    total contact area (mm2)                                        55
    contact area without mucus (mm2)                                23
    contact area with mucus / contact area without mucus          0.37
    contact pressure (Pa)                                         5946
    adhesive strength (Pa)                                       -3149
    Name: 42, dtype: object

`DataFrame` conveniently arranges the indices describing the elements of the row as column headings. This is a Pandas `Series` object.

Slicing out a single index is not very meaningful, because the indices are arbitrary. We can instead look at our lab notebook and look at a trial number 3 on May 27, 2013, of frog III. This is a more common and indeed meaningful, use case.

```python
# Set up Boolean slicing
date = df['date'] == '2013_05_27'
trial = df['trial number'] == 3
ID = df['ID'] == 'III'

# Slice out the row
df.loc[date & trial & ID]
```

      date
      ID
      trial number
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      contact area with mucus / contact area without mucus
      contact pressure (Pa)
      adhesive strength (Pa)

      42
      2013_05_27
      III
      3
      324.0
      105
      2.61
      -172
      619
      1.38
      -0.079
      55
      23
      0.37
      5946
      -3149

The difference is that the returned object (slice) is a `DataFrame` instead of `Series`. We can easily make it a `Series` object using `iloc`. Note that in most use cases, this is jsut a matter of convenience for viewing and nothing more.

```python
df.loc[date & trial & ID, :].iloc[0]
```

    date                                                    2013_05_27
    ID                                                             III
    trial number                                                     3
    impact force (mN)                                              324
    impact time (ms)                                               105
    impact force / body weight                                    2.61
    adhesive force (mN)                                           -172
    time frog pulls on target (ms)                                 619
    adhesive force / body weight                                  1.38
    adhesive impulse (N-s)                                      -0.079
    total contact area (mm2)                                        55
    contact area without mucus (mm2)                                23
    contact area with mucus / contact area without mucus          0.37
    contact pressure (Pa)                                         5946
    adhesive strength (Pa)                                       -3149
    Name: 42, dtype: object

## Renaming columns
The lengthy syntax of access column names can be annoying, so it can be useful to change the names of the columns. For instance, now, to access the ratio of contact area with mucus to contact area without mucus, we would have to do following.

```python
# Set up criteria for our search in the DataFrame
date = df['date'] == '2013_05_27'
trial = df['trial number'] == 3
ID = df['ID'] == 'III'

# When indexind DataFrames, use & for Boolean and (and / for or; - for not)
df.loc[date & trial & ID, 'contact area with mucus / \
contact area without mucus']

```

    42    0.37
    Name: contact area with mucus / contact area without mucus, dtype: float64

The reason to use the verbose nature of the column headings is to avoid ambiguity. Also, many plotting packages, including *HoloViews* can automatically label axes based on headers in `DataFrame`s. To use shorter names, we do this.

```python
# Make a dictionary to rename columns
rename_dict = {'trial number' : 'trial',
               'contact area with mucus / contact area without mucus' :
               'ca_ratio'}

# Rename the columns
df = df.rename(columns=rename_dict)

# Try out the new column name
df.loc[date & trial & ID, 'ca_ratio']

# Indexing of dictionaries looks syntactically similar to cols in DataFrames
rename_dict['trial number']
```

    'trial'

## Computing with DataFrames and adding columns
The `DataFrame`s are convenient for the organization and selection of data, but how about computations and storing the results of computations in the new columns?

As a simple example, let's say we want to have a column with the impact force i nunits of Newtons instead of milliNewtons, so we can divide `impact force (mN)` columns elementwise by 1000, just as with Numpy arrays.

```python
# Add a new columns with impact force in units of Newtons
df['impact force (N)'] = df['impact force (mN)'] / 1000

# Take a look
df.head()
```

      date
      ID
      trial
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      ca_ratio
      contact pressure (Pa)
      adhesive strength (Pa)
      impact force (N)

      0
      2013_02_26
      I
      3
      1205.0
      46
      1.95
      -785
      884
      1.27
      -0.290
      387
      70
      0.82
      3117
      -2030
      1.205

      1
      2013_02_26
      I
      4
      2527.0
      44
      4.08
      -983
      248
      1.59
      -0.181
      101
      94
      0.07
      24923
      -9695
      2.527

      2
      2013_03_01
      I
      1
      1745.0
      34
      2.82
      -850
      211
      1.37
      -0.157
      83
      79
      0.05
      21020
      -10239
      1.745

      3
      2013_03_01
      I
      2
      1556.0
      41
      2.51
      -455
      1025
      0.74
      -0.170
      330
      158
      0.52
      4718
      -1381
      1.556

      4
      2013_03_01
      I
      3
      493.0
      36
      0.80
      -974
      499
      1.57
      -0.423
      245
      216
      0.12
      2012
      -3975
      0.493

The new column was created by assigning it to Pandas `Series` we calculated.

We can do other calculations on `DataFrame`s besides elementwise calculations. For example, if we wanted the mean impact force in units of milliNewtons, we can do this...

```python
df['impact force (mN)'].mean()
```

    801.6875

To compute all sorts of useful summary statistics all at once about the `DataFrame`, we can use `describe()` method, which gives the count, mean, standard deviation, minimum, 25th percentile, median, 75th percentiel, and maximum for each column.

```python
df.describe()
```

      trial
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      ca_ratio
      contact pressure (Pa)
      adhesive strength (Pa)
      impact force (N)

      count
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000
      80.000000

      mean
      2.400000
      801.687500
      39.062500
      2.920375
      -397.762500
      1132.450000
      1.444875
      -0.187462
      166.475000
      61.400000
      0.569000
      6073.162500
      -3005.875000
      0.801687

      std
      1.164887
      587.836143
      19.558639
      1.581092
      228.675562
      747.172695
      0.659858
      0.134058
      98.696052
      58.532821
      0.341862
      5515.265706
      2525.468421
      0.587836

      min
      1.000000
      22.000000
      6.000000
      0.170000
      -983.000000
      189.000000
      0.220000
      -0.768000
      19.000000
      0.000000
      0.010000
      397.000000
      -17652.000000
      0.022000

      25%
      1.000000
      456.000000
      29.750000
      1.470000
      -567.750000
      682.250000
      0.990000
      -0.277250
      104.750000
      16.750000
      0.280000
      2579.250000
      -3443.250000
      0.456000

      50%
      2.000000
      601.000000
      34.000000
      3.030000
      -335.000000
      927.000000
      1.320000
      -0.165000
      134.500000
      43.000000
      0.665000
      4678.000000
      -2185.500000
      0.601000

      75%
      3.000000
      1005.000000
      42.000000
      4.277500
      -224.500000
      1381.250000
      1.772500
      -0.081250
      238.250000
      92.500000
      0.885000
      7249.750000
      -1736.000000
      1.005000

      max
      5.000000
      2641.000000
      143.000000
      6.490000
      -92.000000
      4251.000000
      3.400000
      -0.001000
      455.000000
      260.000000
      1.000000
      28641.000000
      -678.000000
      2.641000

Now let's say we want to compute the mean impact force of each frog. We can combine Boolean indexing with looping to do that.

```python
for frog in df['ID'].unique():
    inds = df['ID'] == frog
    mean_impact_force = df.loc[inds, 'impact force (mN)'].mean()
    print('{0:s}: {1:.1f} mN'.format(frog, mean_impact_force))
```

    I: 1530.2 mN
    II: 707.4 mN
    III: 550.1 mN
    IV: 419.1 mN

We used the `.unique()` method to get the unique entries in the `ID` column. We can also use Panda's `.groupby()` function to do this much more cleanly and efficiently, but for now, it is good to appreciate the ability to pull out the data needed and do computations with it.

## Creating a DataFrame from scratch
Let's construct our own `DataFrame` from scratch, which would contain information about each frog.

One way to do this is to first create a dictionary with the respective fields, and then convert it into a `DataFrame` by instantiating a `pd.DataFrame` class with it.

```python
# Create a dictionary with the appropriate fields
data_dict = {'ID': ['I', 'II', 'III', 'IV'],
             'age': ['adult', 'adult', 'juvenile', 'juvenile'],
             ' SVL (mm)': [63, 70, 28, 31],
             'weight (g)': [63.1, 72.7, 12.7, 12.7],
             'species': ['cross', 'cross', 'cranwelli', 'cranwelli']}

# Make it into a DataFrame
df_frog_info = pd.DataFrame(data=data_dict)

# Take a look
df_frog_info

```

      ID
      age
      SVL (mm)
      weight (g)
      species

      0
      I
      adult
      63
      63.1
      cross

      1
      II
      adult
      70
      72.7
      cross

      2
      III
      juvenile
      28
      12.7
      cranwelli

      3
      IV
      juvenile
      31
      12.7
      cranwelli

In some instances, the data sets are not small enough to construct a dictionary by hand. Oftentimes, we have a two-dimensional array of data that we want to make into a `DataFrame`. For instance, we can have a Numpy array with two columns, one for snout-vent length and one for the weight.

```python
data = np.array([[63, 70, 28, 31], \
                 [63.1, 72.7, 12.7, 12.7]]).transpose()

# Let's verify this
data
```

    array([[63. , 63.1],
           [70. , 72.7],
           [28. , 12.7],
           [31. , 12.7]])

To turn the data above into `DataFrame`, we specify the `column` keyword argument too.

```python
df_demo = pd.DataFrame(data=data, columns=['SVL (mm)', 'weight (g)'])

# Take a look
df_demo
```

      SVL (mm)
      weight (g)

      0
      63.0
      63.1

      1
      70.0
      72.7

      2
      28.0
      12.7

      3
      31.0
      12.7

In general, any two-dimensional Numpy array can be converted into a `DataFrame` in this way. We only need to supply column names.

## Merging DataFrames
For each row in the `DataFrame` we can add the relevant value in each column. We will do these operations on a copy of `df` using the `copy()` method.

```python
# Make a copy of df
df_copy = df.copy()

# Build each column
for col in df_frog_info.columns[df_frog_info.columns != 'ID']:
    # Make a new column with empty values
    df_copy[col] = np.empty(len(df_copy))

    # Add in each entry, row by row
    for i, r in df_copy.iterrows():
        ind = df_frog_info['ID'] == r['ID']
        df_copy.loc[i, col] = df_frog_info.loc[ind, col].iloc[0]

# Take a look at the updated DataFrame
df_copy.head()
```

      date
      ID
      trial
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      ca_ratio
      contact pressure (Pa)
      adhesive strength (Pa)
      impact force (N)
      age
      SVL (mm)
      weight (g)
      species

      0
      2013_02_26
      I
      3
      1205.0
      46
      1.95
      -785
      884
      1.27
      -0.290
      387
      70
      0.82
      3117
      -2030
      1.205
      adult
      63.0
      63.1
      cross

      1
      2013_02_26
      I
      4
      2527.0
      44
      4.08
      -983
      248
      1.59
      -0.181
      101
      94
      0.07
      24923
      -9695
      2.527
      adult
      63.0
      63.1
      cross

      2
      2013_03_01
      I
      1
      1745.0
      34
      2.82
      -850
      211
      1.37
      -0.157
      83
      79
      0.05
      21020
      -10239
      1.745
      adult
      63.0
      63.1
      cross

      3
      2013_03_01
      I
      2
      1556.0
      41
      2.51
      -455
      1025
      0.74
      -0.170
      330
      158
      0.52
      4718
      -1381
      1.556
      adult
      63.0
      63.1
      cross

      4
      2013_03_01
      I
      3
      493.0
      36
      0.80
      -974
      499
      1.57
      -0.423
      245
      216
      0.12
      2012
      -3975
      0.493
      adult
      63.0
      63.1
      cross

We used the `iterrows()` method of the `df_copy` data frame. The iterator gives an index (which we called `i`) and a row of a `DataFrame` (which we called `r`). This methods, and the analogous one for iterating over columns, `iteritems()`, can be useful.

However, much better method is to use the Pandas's built-in `merge()` method. Called with all the default kwargs, this function finds a common columns between two `DataFramee`S (in this case, the `ID` column), and then uses those columns to merge them ,filling in values that amtch in the common column. This is exactly what we want.

```python
df = df.merge(df_frog_info)

# Check it
df.head()
```

      date
      ID
      trial
      impact force (mN)
      impact time (ms)
      impact force / body weight
      adhesive force (mN)
      time frog pulls on target (ms)
      adhesive force / body weight
      adhesive impulse (N-s)
      total contact area (mm2)
      contact area without mucus (mm2)
      ca_ratio
      contact pressure (Pa)
      adhesive strength (Pa)
      impact force (N)
      age
      SVL (mm)
      weight (g)
      species

      0
      2013_02_26
      I
      3
      1205.0
      46
      1.95
      -785
      884
      1.27
      -0.290
      387
      70
      0.82
      3117
      -2030
      1.205
      adult
      63
      63.1
      cross

      1
      2013_02_26
      I
      4
      2527.0
      44
      4.08
      -983
      248
      1.59
      -0.181
      101
      94
      0.07
      24923
      -9695
      2.527
      adult
      63
      63.1
      cross

      2
      2013_03_01
      I
      1
      1745.0
      34
      2.82
      -850
      211
      1.37
      -0.157
      83
      79
      0.05
      21020
      -10239
      1.745
      adult
      63
      63.1
      cross

      3
      2013_03_01
      I
      2
      1556.0
      41
      2.51
      -455
      1025
      0.74
      -0.170
      330
      158
      0.52
      4718
      -1381
      1.556
      adult
      63
      63.1
      cross

      4
      2013_03_01
      I
      3
      493.0
      36
      0.80
      -974
      499
      1.57
      -0.423
      245
      216
      0.12
      2012
      -3975
      0.493
      adult
      63
      63.1
      cross

## Plotting how impact forces correlate with other metrics
Let's say that we want to know how the impact forces correlate with other measures about the impact. Let's make a scatter plot of adhesion forces vs. impact forces.

```python
# Set up the plot
p = bokeh.plotting.figure(plot_height=300,
                          plot_width=500,
                          x_axis_label='impact force (mN)',
                          y_axis_label='adhesive force (mN)')

# Add a scatter plot
p.circle(df['impact force (mN)'], df['adhesive force (mN)'])

bokeh.io.show(p)
```

We can see some correlation between the adhesive forces and the impact forces. The stronger the impact force, the stronger the adhesive force, with some exceptions.

## Making subplots
Now let's learn how to make subplots in Bokeh. Let's say we want to plot the adhesive force, total contact area, impact time, and contact pressure against impact force. To lay these out as subplots, we make the respective `bokeh.plotting.figure.Figure` objects one by one and put them in a list. We then use the utilities from `bokeh.layouts` to make the subplots. Let's start by making the list of figure objects. It will help to wirte a quick function to generate a plot.

```python
def scatter(df, x, y, p=None, color='#30a2da',
            plot_height=300, plot_width=500):
    """Populate a figure with a scatter plot."""
    p = bokeh.plotting.figure(plot_height=plot_height,
                              plot_width=plot_width,
                              x_axis_label=x,
                              y_axis_label=y)

    p.circle(df[x], df[y], color=color)

    return p
```

Now we can build a list of plots

```python
# To be plotted
cols = ['impact time (ms)',
        'adhesive force (mN)',
        'total contact area (mm2)',
        'contact pressure (Pa)']

plots = []
for col in cols:
    plots.append(scatter(df,
                         'impact force (mN)',
                         col,
                         plot_height=200,
                         plot_width=400))

# Line up the plots in a column
bokeh.io.show(bokeh.layouts.column(plots))
```

There are some things that are to be cleared:

* Plots share the same x-axis, so we want to link the axes. This is accomplisehed by setting the `x_range` attribute of the respective `bokeh.plotting.figure.Figure` objects.
* All the x-axes are the same, so we do not need to label them all.

```python
for p in plots[:-1]:
    # Set each plot's x_range to be that of the last plot
    p.x_range = plots[-1].x_range

    # Only have x_axis label on bottom plot
    p.xaxis.axis_label = None

# To show only a single toolbar for a set of subplots, we should do this
bokeh.io.show(bokeh.layouts.gridplot(plots, ncols=1))
```

To arrange the plots in a 2x2 gird, we use ncols=2 as the kwarg. However, we have to supply an axis label for the plot in the lower left corner.

```python
plots[2].xaxis.axis_label = 'impact force (mN)'
bokeh.io.show(bokeh.layouts.gridplot(plots, ncols=2))
```

To have the blank spaces on the two-dimensional lsit of plots, we can use `bokeh.layouts.gridplot()` again. Let's leave out the contact pressure plot

```python
# Make 2D list of plots (put None for no plot)
plots = [[plots[0], plots[1]],
         [plots[2], None]]

# Show using gridplot without an ncols kwatg
bokeh.io.show(bokeh.layouts.gridplot(plots))
```

## Plotting the distribution of impact forces
To simply plot the distribution of impact forces, we can do a couple of things. Probably the most familiar thing is to plot them as a histogram. To do that with Bokeh, we can first compute the edges and heights of the bars of the histogram using Numpy, and then add them to the plot using the `quad()` method of Bokeh figures, which plots the filled rectangles.

```python
# Compute the histogram
heights, edges = np.histogram(df['impact force (mN)'])

# Set up the plot
p = bokeh.plotting.figure(plot_height=300,
                          plot_width=500,
                          x_axis_label='impact force (mN)',
                          y_axis_label='count')

# Generate the histogram
p.quad(top=heights, bottom=0, left=edges[:-1], right=edges[1:])

bokeh.io.show(p)
```

From looking at the histogram, there might be some bimodalidy. However, there is a better way to visualize the data - use of ECDF...
## Empirical cumulative distribution functions (ECDFs)
The primary reason why histogram might not be the best way to display the distributions of measurements is the **binning biais**. To create a histogram, we necessarily have to consider not the exact values of the measurements, but rather place them in bins. We do not plot all of the data, and the choice of bins can change what we can infer from the plot.

Instead, we should look at the **empirical cumulative distribution function** (or ECDF). The ECDF evaluated at $x$ for a set of measurements is defined as
\begin{align}
ECDF(x) = fraction \ of \ measurements \leq x
\end{align}
Let's write a function that will generate $x$ values of ECDFs and $y$ values of ECDFs.

```python
def ecdf(data):
    """Return the ECDF of the data."""
    x = np.sort(data)
    y = np.arange(1, len(data)+1) / len(data)

    return x, y
```

```python
# Get the values of the impact force for ECDF calculation
# For adult frogs
inds_ad = df['age'] == 'adult'
vals_ad = df.loc[inds_ad,'impact force (mN)']

# For juvenile frogs
inds_juv = df['age'] == 'juvenile'
vals_juv = df.loc[inds_juv,'impact force (mN)']

# Calculate the ECDFs for both adult and juvenile frogs
xad, yad = ecdf(vals_ad)
xjuv, yjuv = ecdf(vals_juv)

# Plot the ECDFs for the adult and juvenile frogs
# Set up the plot
p = bokeh.plotting.figure(plot_height=300,
                          plot_width=600,
                          x_axis_label='impact force (mN)',
                          y_axis_label='ECDF')

# Add a scatter plot
p = figure(toolbar_location="above")

p1 = p.circle(xad, yad, color='blue')
p2 = p.circle(xjuv, yjuv, color='red')

legend = Legend(items=[
    ('adult', [p1]),
    ('juvenile', [p2])
    ])
# Add legend
p.add_layout(legend)

p.xaxis.axis_label = 'impact force (mN)'
p.yaxis.axis_label = 'ECDF'

bokeh.io.show(p)
```

From the ECDF graph above, it is apparent that there is a difference in the impact forces between adult frogs and the juvenile frogs, explaining the slight bimodality of the histogram.
## Conclusions: what we learned?
* How to lead data from CSVfiles into Pandas `DataFrame`s.
* `DataFrame`s are useful objects for looking at data.


# Conclusion
> Concluding the research

- toc: true
- badges: false
- comments: true
- hide: false
- categories: [jupyter]
- permalink: /conclusion
- image: images/conclusion.jpg
- sectionnr: 7

In the previous chapters I have first explained photon bunching and photon statistics qualitatively and then explored its impact on the DESHIMA spectrometer quantitatively. I have shown a model for the probabilistics behind photon bunching, showing that it is not the detection itself that triggers bunching, but rather a change in the underlying photon probability. I have also discussed the coherence time, defined as the timescale below which the non-stochastic effects of photon bunching take hold. The coherence time is related to the bandwidth by

$$\begin{equation}
\tau_\mathrm{coh}\approx\frac{1}{\Delta\nu}
\end{equation}$$

After exploring photon statistics, I discussed photon noise induced by both Poisson statistics and photon bunching. The total noise equivalent power induced by photon noise is given by{%cite zmuidzinas_2003%}:

$$\begin{equation}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2=\frac{1}{\tau}\int_0^\infty h\nu\eta\left(\nu\right)\mathrm{PSD} + \eta^2\left(\nu\right)\mathrm{PSD}^2d\nu
\end{equation}$$

Whereas the approximation given by {%cite zmuidzinas_2003%} and used previously in calculating the sensitivity of the DESHIMA system{% cite endo_2019%} is found by approximating a very narrow bandwidth ($\nu\gg\Delta\nu$) and then approximating the integral by

$$\begin{equation}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2=\frac{1}{\tau}\left(h\nu\eta_0\mathrm{PSD}\Delta\nu+\eta_0^2\mathrm{PSD}^2\Delta\nu\right)\label{NEP_int}
\end{equation}$$

this approximation overestimates the photon bunching effects for a filter $\eta\left(\nu\right)$ with a Lorentzian shape. I have shown that, in the case of a Lorentzian filter and a flat $\mathrm{PSD}$, eq. \eqref{NEP_int} collapses to

$$\begin{equation}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2=\frac{1}{\tau}\left(h\nu\eta_0\mathrm{PSD}\Delta\nu+\frac{2}{\pi}\eta_0^2\mathrm{PSD}^2\Delta\nu\right)
\end{equation}$$

with $\Delta\nu$ the $\mathrm{FWHM}$ of the filter. This factor of $\pi/2$ is explained by the width of the Lorentzian filter. Previously the bandwidth of the filters was assumed to be negligible, resulting in an overestimation of the bunching. Because the photons that are impinging on the detector span a bigger bandwidth they bunch less.

The definition for the noise equivalent power $\mathrm{NEP}_{\tau=0.5s}$ means that it is defined at an integration time of $\tau=0.5\:s$. For different integration times the $\mathrm{NEP}_\tau$ is defined as:

$$\begin{equation}
\mathrm{NEP}_{\tau} = \frac{1}{\sqrt{2\tau}}\mathrm{NEP}_{\tau=0.5\mathrm{s}}
\end{equation}$$

However, this assumes that the integration time is much bigger than the coherence time of the detected photons $\tau\gg t_\mathrm{coh}$. Due to correlation of photons within the coherence time the $\mathrm{NEP}_{\tau}$ drops when the integration time approaches and subceeds the coherence time.

Besides this algebraic result for a Lorentzian filter I have also modified the existing `deshima-sensitivity`{% cite deshima-sensitivity %} model to calculate the integral in eq. \eqref{NEP_int} not just for mathematical filters, but for arbitrary filter shapes loaded in via a file. This allows researchers to compare the sensitivity of various designs in software.

To verify the changes to the model I have compared it with the old model and confirmed that in the case of perfect Lorentzian filter the latter overestimated the bunching noise by a factor of $\pi/2$ , even on average for a non-flat $\mathrm{PSD}$. Other than this, the changes affect the power and noise in local extrema of the $\mathrm{PSD}$, where the old model didn't integrate over the full range of the filter and therefore took the $\mathrm{PSD}$ as a flat spectrum locally.

## The future of this research
Because the model now more closely resembles the physics that is occurring inside the filter section of a DESHIMA spectrometer, it can be used to compare different filter designs in the `deshima-sensitivity` package itself. This is an important tool to compare filter topologies differing from Lorentzian shapes. Paired with methods to accurately describe the coupling of a source to the detector it can be used to very accurately calculate the signal to noise ratio of a specific filter profile, aiding in the experimental design of different filter profiles. Such research is in progress and as such the model will immediately be put to use.

The improved accuracy of the approximation in the bunching term for a Lorentzian filter can prove useful when designing the DESHIMA spectrometer, as it gives a more physically rigorous target of the maximum sensitivity the DESHIMA spectrometer can strive towards.

Finally, this thesis gives a thorough overview of photon statistics in astronomical measurement and of photon bunching in particular and as such can be a great teaching tool. My thesis supervisor, dr. Akira Endo, has been very interested in using this as teaching material for courses he gives on the subject and I am looking forward to helping other students understand photon statistics and photon bunching better.

Previous Chapter

Appendix

## Bibliography
{% bibliography --cited_in_order %}


# The Variational Quantum Eigensolver algorithm

```python
!pip install qiskit
```

    Requirement already satisfied: qiskit in /usr/local/lib/python3.7/dist-packages (0.32.0)
    Requirement already satisfied: qiskit-aer==0.9.1 in /usr/local/lib/python3.7/dist-packages (from qiskit) (0.9.1)
    Requirement already satisfied: qiskit-ignis==0.6.0 in /usr/local/lib/python3.7/dist-packages (from qiskit) (0.6.0)
    Requirement already satisfied: qiskit-aqua==0.9.5 in /usr/local/lib/python3.7/dist-packages (from qiskit) (0.9.5)
    Requirement already satisfied: qiskit-ibmq-provider==0.18.0 in /usr/local/lib/python3.7/dist-packages (from qiskit) (0.18.0)
    Requirement already satisfied: qiskit-terra==0.18.3 in /usr/local/lib/python3.7/dist-packages (from qiskit) (0.18.3)
    Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.4.1)
    Requirement already satisfied: numpy>=1.16.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.19.5)
    Requirement already satisfied: yfinance>=0.1.62 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.1.66)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (5.4.8)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.0.1)
    Requirement already satisfied: fastdtw<=0.3.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.3.4)
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (57.4.0)
    Requirement already satisfied: quandl in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (3.7.0)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.1.5)
    Requirement already satisfied: dlx<=1.0.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.0.4)
    Requirement already satisfied: docplex>=2.21.207 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (2.22.213)
    Requirement already satisfied: retworkx>=0.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.10.2)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.7.1)
    Requirement already satisfied: h5py<3.3.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (3.1.0)
    Requirement already satisfied: websocket-client>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (1.2.1)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (2.8.2)
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (1.24.3)
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (2.23.0)
    Requirement already satisfied: requests-ntlm>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (1.1.0)
    Requirement already satisfied: python-constraint>=1.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (1.4.0)
    Requirement already satisfied: fastjsonschema>=2.10 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (2.15.1)
    Requirement already satisfied: tweedledum<2.0,>=1.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (1.1.1)
    Requirement already satisfied: ply>=3.10 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (3.11)
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (2.6.0)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (0.3.4)
    Requirement already satisfied: symengine>0.7 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (0.8.1)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from docplex>=2.21.207->qiskit-aqua==0.9.5->qiskit) (1.15.0)
    Requirement already satisfied: cached-property in /usr/local/lib/python3.7/dist-packages (from h5py<3.3.0->qiskit-aqua==0.9.5->qiskit) (1.5.2)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (2021.10.8)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (3.0.4)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (2.10)
    Requirement already satisfied: cryptography>=1.3 in /usr/local/lib/python3.7/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (35.0.0)
    Requirement already satisfied: ntlm-auth>=1.0.2 in /usr/local/lib/python3.7/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (1.5.0)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (1.15.0)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (2.21)
    Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.5->qiskit) (3.0.0)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.5->qiskit) (1.1.0)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-aqua==0.9.5->qiskit) (1.2.1)
    Requirement already satisfied: lxml>=4.5.1 in /usr/local/lib/python3.7/dist-packages (from yfinance>=0.1.62->qiskit-aqua==0.9.5->qiskit) (4.6.4)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from yfinance>=0.1.62->qiskit-aqua==0.9.5->qiskit) (0.0.10)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->qiskit-aqua==0.9.5->qiskit) (2018.9)
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.5->qiskit) (8.11.0)
    Requirement already satisfied: inflection>=0.3.1 in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.5->qiskit) (0.5.1)

The goal of this notebook is to guide through a VQE implemenation for a Ising type Hamiltonian. This notebook is a squeleton code and some parts must be improved. The "examples" has to be taken as inspiration and improved. The VQE algorithms permit to compute the ground state energy of physical systems. First some import.

```python
# Comment !!!
from qiskit.opflow import Z, I, X, Y
from qiskit.opflow import CircuitStateFn, StateFn, CircuitSampler, PauliExpectation, ListOp
from qiskit.providers.aer import AerSimulator
from qiskit.utils import QuantumInstance
from qiskit.circuit import QuantumCircuit, QuantumRegister, Parameter, ParameterVector
from qiskit.algorithms.optimizers import ADAM, SPSA
from qiskit.circuit.library import TwoLocal
from qiskit.circuit.library.n_local import EfficientSU2, RealAmplitudes
from qiskit import Aer, execute

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

The first step is to implement the Transverse Ising Hamiltonian with periodic boundary conditions wand an external field using the qiskit.opflow module. We wish to implement the following Hamiltonian
\begin{equation}
H=J_z\sum_{i=0}^{N}\sigma_i^z\sigma_{i+1}^z + h\sum_{i=0}^{N}\sigma_i^x,
\end{equation}
In the opflow module, the Pauli operators are given by I, X, Y and Z and you may use @ or * for the matrix multiplication and ^ for the tensor product.

```python
# implement the Transverse Ising Hamiltonian H with periodic bounday conditions and parameters N, J and h.
n_qubits = 3

N = 3

h = 1
J = 1

H = 0
for i in range(N-1):
    H += (J * (I^i)^(Z^Z)^(I^(N-2-i))) + (h * (I^i)^X^(I^(N-1-i)))
# Boundary Condition
H += (J * Z^(I^(N-2))^Z) + (h * (I^(N-1))^X)

# to contract similar terms together
H = H.reduce()
print(H)
```

    1.0 * ZZI
    + 1.0 * ZIZ
    + 1.0 * IZZ
    + 1.0 * XII
    + 1.0 * IXI
    + 1.0 * IIX

### The Ansatz
Now that we have the Hamiltonian operator, we will need an ansatz for our wavefunction. In quantum computing, an ansatz is a circuit that at the end will produce a qubit state $\left |\psi \right >$. In this case, we need a variational ansatz, so some gates will contains parameters that are going to be iteratively optimized (as an example, rotation on the $x,y,z$ axis).

There is no analytical way to choose an ansatz for the system: there are empirical rules based on similarity with what we are studying.
Some ansätze come from classical computational chemistry, such as the highly accurate [q-UCCSD](https://arxiv.org/pdf/1506.00443.pdf),  but mostly we have to consider some circuits that can be run on current devices, so they have to contain few two qubits gates and be relatively shallow: these ansätze are called hardware-efficient.

What we are going to consider is one of the so-called hardware-efficient ansätze.
The system does not contain many qubits, so our trial ansatz will be very simple : a layer of rotations around the $y$-axis followed by CNOTs and again a layer of rotations. This simple structure can be easily extended both in depth (adding more CNOTs and rotation) and in width, to study bigger system, therefore is widely used.

As an introduction, try to implement the circuit defined above, with 3 qubits.
One you get an understanding, you can try to reliate to the template class [TwoLocal](https://qiskit.org/documentation/stubs/qiskit.circuit.library.TwoLocal.html), [EfficientSU2](https://qiskit.org/documentation/stubs/qiskit.circuit.library.EfficientSU2.html) or [RealAmplitude](https://qiskit.org/documentation/stubs/qiskit.circuit.library.RealAmplitudes.html) to design quickly any quantum circuit you like.

Hint: Try to work in a general way in order to to remain flexible.
Hint: You can use the ParameterVector class to create parametrized circuits and bind numerical values at the end.

```python
n_qubits = 3
n_layers = 2
params = ParameterVector('θ',(n_qubits*6))

qr = QuantumRegister(n_qubits)
ansatz = QuantumCircuit(qr)

# Example for the first layer

for k in range(n_layers-1):
    for i in range(n_qubits):
        ansatz.ry(params[i + n_qubits*k], i)
    ansatz.barrier()
    for i in range(n_qubits):
        ansatz.cx(i, (i+1) % n_qubits)
    ansatz.barrier()
for i in range(n_qubits):
    ansatz.ry(params[i + n_qubits*(n_layers-1)], i)
ansatz.barrier()

print(ansatz.draw())

```

              ┌──────────┐ ░           ┌───┐ ░ ┌──────────┐ ░
    q84222_0: ┤ Ry(θ[0]) ├─░───■───────┤ X ├─░─┤ Ry(θ[3]) ├─░─
              ├──────────┤ ░ ┌─┴─┐     └─┬─┘ ░ ├──────────┤ ░
    q84222_1: ┤ Ry(θ[1]) ├─░─┤ X ├──■────┼───░─┤ Ry(θ[4]) ├─░─
              ├──────────┤ ░ └───┘┌─┴─┐  │   ░ ├──────────┤ ░
    q84222_2: ┤ Ry(θ[2]) ├─░──────┤ X ├──■───░─┤ Ry(θ[5]) ├─░─
              └──────────┘ ░      └───┘      ░ └──────────┘ ░

### The VQE algorithm

The core of the Variational Quantum Eigensolver is to optimize a parametrized trial wavefunction to minimize the energy expectation of the Hamiltonian of interest. By the variational theorem, this would yield a good estimate of the true groundstate and its energy.
In the following, we will implement the VQE algorithm. For this purpose, we need the expectation value of the Hamiltonian, its gradient with respect to the ansatz's parameters and an optimizer to update them.
We remember, that for an involutive quantum gate (i.e. any single qubit rotation), the gradient of the expectation value with respect to its parameter $\theta$ is given by

$$\frac{d}{d\theta}\left<\psi(\theta)|H|\psi(\theta)\right> =$$
$$\frac{1}{2} \left[\left<\psi(\theta+\pi/2)|H|\psi(\theta+\pi/2)\right> - \left<\psi(\theta-\pi/2)|H|\psi(\theta-\pi/2)\right>\right].$$

We will compute the expectation value by sampling the wavefunction. For example, $$ \left< ZZ\right>= [\text{counts}(00) + \text{counts}(11) - \text{counts}(01) - \text{counts}(10) ]/ \text{shots}.$$ Note that we can only measure in the Z basis. To measure another Pauli string P, we will have to append a unitary U after the ansatz to perform a basis rotation. U has to be chosen such that
$$U^\dagger Z^N U = P.$$

If the identity is present in the Pauli string, we will act on the remaining subspace. That means, you can perform the same technique by forgetting the dimension where $I$ is present.
Note that in qiskit, we read the qubits from right to left. That means that XZ = $X_1Z_0$

```python
noise_model = None # the noise model used to simulate the hardware, for now, leave it to None (no noise)

class VQE:
    def __init__(self,ansatz, qr, H):
        self.H = H
        self.ansatz = ansatz
        self.qr = qr
        self.backend = Aer.get_backend('qasm_simulator',noise = noise_model)
        self.shots = 2**12 # number of sample used to approximate the expectation value
        #choose some classical optimizer. You may take a gradient based or gradient free optimizer
        # and play with the hyperparameters as well.
        self.optimizer = ADAM(maxiter=1, tol=1e-06, lr=0.1, beta_1=0.9, beta_2=0.99, amsgrad=False)

    def expectation_value(self,parameters):

        '''
        return the expectation value of the quantum circuit wrt to the observable H

        '''

        result = 0

        for i in range(len(self.H)): #loop over the pauli terms
            ansatz = self.ansatz.bind_parameters(parameters)   #insert the parameters
            coeff = self.H[i].primitive.coeffs                 # get the coefficient
            pauli = self.H[i].primitive.table.to_labels()[0]   #get the pauli string

            observable, measure_which = self.basis_change(pauli)  #get which qubits to measure and the basis change

            ansatz.append(observable,self.qr)  # append the basis change
            ansatz.measure_all()          # measure all the qubits

            job = execute(ansatz, self.backend, shots=self.shots)
            counts = job.result().get_counts()
            result = result + coeff * self.expectation_from_counts(counts,measure_which)

        return result.real

    def basis_change(self,pauli):
        '''transform the measurement basis as function of the observable
        return: -quantum circuit to append to the ansatz
                -qubit to measure
        '''
        observable = QuantumCircuit(len(pauli))   #the basis transformation circuit U to append to the ansatz

        measure_which = []  #which qubit we will have to measure
        for i in range(len(pauli)):
            if pauli[i] == 'X':
                observable.h(i)
                measure_which += [i]
            elif pauli[i] == 'Y':
                observable.s(i)
                observable.h(i)
                observable.s(i)
                measure_which += [i]
            elif pauli[i] == 'Z':
                measure_which += [i]

        return observable, measure_which

    def expectation_from_counts(self,counts,measure_which):
        '''compute the expectation value from counts
        Tipp: look at the parity of the state
        remember: in qiskit we read the qubits from right to left

        '''

        total_counts = np.sum([counts[key] for key in counts])
        expectation = 0

        if len(measure_which)==0:
            # return the expectation value when the pauli is the identity
            # just sum the probabilities
            probs = np.array(counts.values()) / total_counts
            return np.sum(probs)

        for state in counts:
            parity = (-1) ** np.bitwise_xor.reduce([int(b) for b in state]) # your code goes here, compute the parity of the state
            expectation += parity * counts[state] # your code goes here

        return expectation / total_counts

    def gradient(self,parameters):
        '''
        return the gradient of the quantum circuit

        '''

        gradients = np.zeros_like(parameters)
        for i,p in enumerate(parameters):

            shift = np.zeros_like(parameters)
            shift[i] = np.pi/2
            gradients[i] = 0.5 * (self.expectation_value(p + shift) - self.expectation_value(p - shift))

        return gradients

    def update(self,parameters):
        '''
        update the parameters with the classical optimizer

        '''
        parameters, loss, it = self.optimizer.optimize(parameters.size,
                        lambda param: self.expectation_value(param),
                        gradient_function= lambda param: self.gradient(param),initial_point=parameters)

        return loss, parameters

```

```python
%matplotlib inline
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)

# Here we choose some initial weights and optimize them
np.random.seed(1)
weights = np.pi * np.random.normal(0,1,size = len(ansatz.parameters))

vqe  = VQE(ansatz, qr, H)
exact_energy = min(np.real(np.linalg.eig(H.to_matrix())[0]))   # exact smallest eigenvalue
loss = []
epoch = 10
for i in range(epoch):
    # update the parameters and save the loss in loss
    loss_tmp, weights = vqe.update(weights)
    loss.append(loss_tmp)
    print(i, loss_tmp)

#plot the learning curve
ax.clear()
ax.plot(loss,'b.',label='VQE')
ax.plot(exact_energy*np.ones_like(range(epoch)),'k--',label='exact')
ax.plot(range(epoch), loss, label='loss')
ax.legend()
ax.set_xlim([-0.2, epoch])
fig.show()
fig.canvas.draw()
```

Once you have a working script, you can try to imrpove it. For example you could try to use an ansatz with less parameters as possible, or with less CNOT gates as possible. CNOT gates are expensive and with a relativ high error rates so it is good to design ansatz with few of them. You could also try to explore more complexe Hamiltonian, for example with more qubits, more interaction or let vary the constant J. Another thing which is worth exploring is the optimizer itself. The SPSA optimizer is a solid choice as it uses a fix number of points to estimate the gradients and is therefore quicker than gradient descent and also robust to shot noise. A last thing you could play with, is to add noise to the hardware, as shown below.

```python
import qiskit.providers.aer.noise as noise
error_1 = noise.depolarizing_error(0.001, 1)  #error rate of single qubit gates
error_2 = noise.depolarizing_error(0.01, 2)   #error rate of double qubit gates

noise_model = noise.NoiseModel()
noise_model.add_all_qubit_quantum_error(error_1, ['u1,u2,u3'])  # add the single-qubit gates where you want to have noise
noise_model.add_all_qubit_quantum_error(error_2, ['cx'])       # add the double-qubits gates where you want to have noise

#Then you can just use this noise model in the quantum instance of the VQE
#to automatically incoporate it in the computations
```

```python

```


     Histograms of Oriented Gradients (HOG)

Introduction

As we saw with the ORB algorithm, we can use keypoints in images to do keypoint-based matching to detect objects in images. These type of algorithms work great when you want to detect objects that have a lot of consistent internal features that are not affected by the background. For example, these algorithms work well for facial detection because faces have a lot of consistent internal features that don’t get affected by the image background, such as the eyes, nose, and mouth. However, these type of algorithms don’t work so well when attempting to do more general object recognition, say for example, pedestrian detection in images. The reason is that people don’t have consistent internal features, like faces do, because the body shape and style of every person is different (see Fig. 1). This means that every person is going to have a different set of internal features, and so we need something that can more generally describe a person.

Fig. 1. - Pedestrians.

One option is to try to detect pedestrians by their contours instead.  Detecting objects in images by their contours (boundaries) is very challenging because we have to deal with the difficulties brought about by the contrast between the background and the foreground. For example, suppose you wanted to detect a pedestrian in an image that is walking in front of a white building and she is wearing a white coat and black pants (see Fig. 2). We can see in Fig. 2, that since the background of the image is mostly white, the black pants are going to have a very high contrast, but the coat, since it is white as well, is going to have very low contrast. In this case, detecting the edges of pants is going to be easy but detecting the edges of the coat is going to be very difficult. This is where **HOG** comes in. HOG stands for **Histograms of Oriented Gradients** and it was first introduced by Navneet Dalal and Bill Triggs in 2005.

Fig. 2. - High and Low Contrast.

The HOG algorithm works by creating histograms of the distribution of gradient orientations in an image and then normalizing them in a very special way. This special normalization is what makes HOG so effective at detecting the edges of objects even in cases where the contrast is very low. These normalized histograms are put together into a feature vector, known as the HOG descriptor, that can be used to train a machine learning algorithm, such as a Support Vector Machine (SVM), to detect objects in images based on their boundaries (edges). Due to its great success and reliability, HOG has become one of the most widely used algorithms in computer vison for object detection.

In this notebook, you will learn:

* How the HOG algorithm works
* How to use OpenCV to create a HOG descriptor
* How to visualize the HOG descriptor.

# The HOG Algorithm

As its name suggests, the HOG algorithm, is based on creating histograms from the orientation of image gradients. The HOG algorithm is implemented in a series of steps:

1. Given the image of particular object, set a detection window (region of interest) that covers the entire object in the image (see Fig. 3).

2. Calculate the magnitude and direction of the gradient for each individual pixel in the detection window.

3. Divide the detection window into connected *cells* of pixels, with all cells being of the same size (see Fig. 3). The size of the cells is a free parameter and it is usually chosen so as to match the scale of the features that want to be detected. For example, in a 64 x 128 pixel detection window, square cells 6 to 8 pixels wide are suitable for detecting human limbs.

4. Create a Histogram for each cell, by first grouping the gradient directions of all pixels in each cell into a particular number of orientation (angular) bins; and then adding up the gradient magnitudes of the gradients in each angular bin (see Fig. 3). The number of bins in the histogram is a free parameter and it is usually set to 9 angular bins.

5. Group adjacent cells into *blocks* (see Fig. 3). The number of cells in each block is a free parameter and all blocks must be of the same size. The distance between each block (known as the stride) is a free parameter but it is usually set to half the block size, in which case you will get overlapping blocks (*see video below*).  The HOG algorithm has been shown empirically to work better with overlapping blocks.

6. Use the cells contained within each block to normalize the cell histograms in that block (see Fig. 3). If you have overlapping blocks this means that most cells will be normalized with respect to different blocks (*see video below*). Therefore, the same cell may have several different normalizations.

7. Collect all the normalized histograms from all the blocks into a single feature vector called the HOG descriptor.

8. Use the resulting HOG descriptors from many images of the same type of object to train a machine learning algorithm, such as an SVM, to detect those type of objects in images. For example, you could use the HOG descriptors from many images of pedestrians to train an SVM to detect pedestrians in images. The training is done with both positive a negative examples of the object you want detect in the image.

9. Once the SVM has been trained, a sliding window approach is used to try to detect and locate objects in images.  Detecting an object in the image entails finding the part of the image that looks similar to the HOG pattern learned by the SVM.

Fig. 3. - HOG Diagram.

Vid. 1. - HOG Animation.

# Why The HOG Algorithm Works

As we learned above, HOG creates histograms by adding the magnitude of the gradients in particular orientations in localized portions of the image called *cells*.  By doing this we guarantee that stronger gradients will contribute more to the magnitude of their respective angular bin, while the effects of weak and randomly oriented gradients resulting from noise are minimized. In this manner the histograms tell us the dominant gradient orientation of each cell.

### Dealing with contrast

Now, the magnitude of the dominant orientation can vary widely due to variations in local illumination and the contrast between the background and the foreground.

To account for the background-foreground contrast differences, the HOG algorithm tries to detect edges locally.  In order to do this, it defines groups of cells, called **blocks**, and normalizes the histograms using this local group of cells. By normalizing locally, the HOG algorithm can detect the edges in each block very reliably; this is called **block normalization**.

In addition to using block normalization, the HOG algorithm also uses overlapping blocks to increase its performance. By using overlapping blocks, each cell contributes several independent components to the final HOG descriptor, where each component corresponds to a cell being normalized with respect to a different block. This may seem redundant but, it has been shown empirically that by normalizing each cell several times with respect to different local blocks, the performance of the HOG algorithm increases dramatically.

### Loading Images and Importing Resources

The first step in building our HOG descriptor is to load the required packages into Python and to load our image.

We start by using OpenCV to load an image of a triangle tile. Since, the `cv2.imread()` function loads images as BGR we will convert our image to RGB so we can display it with the correct colors. As usual we will convert our BGR image to Gray Scale for analysis.

```python
import cv2
import numpy as np
import matplotlib.pyplot as plt

# Set the default figure size
plt.rcParams['figure.figsize'] = [17.0, 7.0]

# Load the image
image = cv2.imread('./images/triangle_tile.jpeg')

# Convert the original image to RGB
original_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Convert the original image to gray scale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Print the shape of the original and gray scale images
print('The original image has shape: ', original_image.shape)
print('The gray scale image has shape: ', gray_image.shape)

# Display the images
plt.subplot(121)
plt.imshow(original_image)
plt.title('Original Image')
plt.subplot(122)
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Scale Image')
plt.show()
```

    The original image has shape:  (250, 250, 3)
    The gray scale image has shape:  (250, 250)

    <matplotlib.figure.Figure at 0x7ff48ffc2e10>

# Creating The HOG Descriptor

We will be using OpenCV’s `HOGDescriptor` class to create the HOG descriptor. The parameters of the HOG descriptor are setup using the `HOGDescriptor()` function. The parameters of the `HOGDescriptor()` function and their default values are given below:

`cv2.HOGDescriptor(win_size = (64, 128),
                  block_size = (16, 16),
                  block_stride = (8, 8),
                  cell_size = (8, 8),
                  nbins = 9,
                  win_sigma = DEFAULT_WIN_SIGMA,
                  threshold_L2hys = 0.2,
                  gamma_correction = true,
                  nlevels = DEFAULT_NLEVELS)`

Parameters:

* **win_size** – *Size*
Size of detection window in pixels (*width, height*). Defines the region of interest. Must be an integer multiple of cell size.

* **block_size** – *Size*
Block size in pixels (*width, height*). Defines how many cells are in each block. Must be an integer multiple of cell size and it must be smaller than the detection window. The smaller the block the finer detail you will get.

* **block_stride** – *Size*
Block stride in pixels (*horizontal, vertical*). It must be an integer multiple of cell size. The `block_stride` defines the distance between adjecent blocks, for example, 8 pixels horizontally and 8 pixels vertically. Longer `block_strides` makes the algorithm run faster (because less blocks are evaluated) but the algorithm may not perform as well.

* **cell_size** – *Size*
Cell size in pixels (*width, height*). Determines the size fo your cell. The smaller the cell the finer detail you will get.

* **nbins** – *int*
Number of bins for the histograms. Determines the number of angular bins used to make the histograms. With more bins you capture more gradient directions. HOG uses unsigned gradients, so the angular bins will have values between 0 and 180 degrees.

* **win_sigma** – *double*
Gaussian smoothing window parameter. The performance of the HOG algorithm can be improved by smoothing the pixels near the edges of the blocks by applying a Gaussian spatial window to each pixel before computing the histograms.

* **threshold_L2hys** – *double*
L2-Hys (Lowe-style clipped L2 norm) normalization method shrinkage. The L2-Hys method is used to normalize the blocks and it consists of an L2-norm followed by clipping and a renormalization. The clipping limits the maximum value of the descriptor vector for each block to have the value of the given threshold (0.2 by default). After the clipping the descriptor vector is renormalized as described in *IJCV*, 60(2):91-110, 2004.

* **gamma_correction** – *bool*
Flag to specify whether the gamma correction preprocessing is required or not. Performing gamma correction slightly increases the performance of the HOG algorithm.

* **nlevels** – *int*
Maximum number of detection window increases.

As we can see, the  `cv2.HOGDescriptor()`function supports a wide range of parameters. The first few arguments (`block_size, block_stride, cell_size`, and `nbins`) are probably the ones you are most likely to change. The other parameters can be safely left at their default values and you will get good results.

In the code below, we will use the `cv2.HOGDescriptor()`function to set the cell size, block size, block stride, and the number of bins for the histograms of the HOG descriptor. We will then use `.compute(image)`method to compute the HOG descriptor (feature vector) for the given `image`.

```python
# Specify the parameters for our HOG descriptor

# Cell Size in pixels (width, height). Must be smaller than the size of the detection window
# and must be chosen so that the resulting Block Size is smaller than the detection window.
cell_size = (6, 6)

# Number of cells per block in each direction (x, y). Must be chosen so that the resulting
# Block Size is smaller than the detection window
num_cells_per_block = (2, 2)

# Block Size in pixels (width, height). Must be an integer multiple of Cell Size.
# The Block Size must be smaller than the detection window
block_size = (num_cells_per_block[0] * cell_size[0],
              num_cells_per_block[1] * cell_size[1])

# Calculate the number of cells that fit in our image in the x and y directions
x_cells = gray_image.shape[1] // cell_size[0]
y_cells = gray_image.shape[0] // cell_size[1]

# Horizontal distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (x_cells - num_cells_per_block[0]) / h_stride = integer.
h_stride = 1

# Vertical distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (y_cells - num_cells_per_block[1]) / v_stride = integer.
v_stride = 1

# Block Stride in pixels (horizantal, vertical). Must be an integer multiple of Cell Size
block_stride = (cell_size[0] * h_stride, cell_size[1] * v_stride)

# Number of gradient orientation bins
num_bins = 9

# Specify the size of the detection window (Region of Interest) in pixels (width, height).
# It must be an integer multiple of Cell Size and it must cover the entire image. Because
# the detection window must be an integer multiple of cell size, depending on the size of
# your cells, the resulting detection window might be slightly smaller than the image.
# This is perfectly ok.
win_size = (x_cells * cell_size[0] , y_cells * cell_size[1])

# Print the shape of the gray scale image for reference
print('\nThe gray scale image has shape: ', gray_image.shape)
print()

# Print the parameters of our HOG descriptor
print('HOG Descriptor Parameters:\n')
print('Window Size:', win_size)
print('Cell Size:', cell_size)
print('Block Size:', block_size)
print('Block Stride:', block_stride)
print('Number of Bins:', num_bins)
print()

# Set the parameters of the HOG descriptor using the variables defined above
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, num_bins)

# Compute the HOG Descriptor for the gray scale image
hog_descriptor = hog.compute(gray_image)
```

    The gray scale image has shape:  (250, 250)

    HOG Descriptor Parameters:

    Window Size: (246, 246)
    Cell Size: (6, 6)
    Block Size: (12, 12)
    Block Stride: (6, 6)
    Number of Bins: 9

# Number of Elements In The HOG Descriptor

The resulting HOG Descriptor (feature vector), contains the normalized histograms from all cells from all blocks in the detection window concatenated in one long vector. Therefore, the size of the HOG feature vector will be given by the total number of blocks in the detection window, multiplied by the number of cells per block, times the number of orientation bins:

\begin{equation}
\mbox{total_elements} = (\mbox{total_number_of_blocks})\mbox{ } \times \mbox{ } (\mbox{number_cells_per_block})\mbox{ } \times \mbox{ } (\mbox{number_of_bins})
\end{equation}

If we don’t have overlapping blocks (*i.e.* the `block_stride`equals the `block_size`), the total number of blocks can be easily calculated by dividing the size of the detection window by the block size. However, in the general case we have to take into account the fact that we have overlapping blocks. To find the total number of blocks in the general case (*i.e.* for any `block_stride` and `block_size`), we can use the formula given below:

\begin{equation}
\mbox{Total}_i = \left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right)\left( \frac{\mbox{window_size}_i}{\mbox{block_size}_i} \right) - \left [\left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right) - 1 \right]; \mbox{  for  } i = x,y
\end{equation}

Where Total$_x$, is the total number of blocks along the width of the detection window, and Total$_y$, is the total number of blocks along the height of the detection window. This formula for Total$_x$ and Total$_y$, takes into account the extra blocks that result from overlapping. After calculating Total$_x$ and Total$_y$, we can get the total number of blocks in the detection window by multiplying Total$_x$  $\times$ Total$_y$. The above formula can be simplified considerably because the `block_size`, `block_stride`, and `window_size`are all defined in terms of the `cell_size`. By making all the appropriate substitutions and cancelations the above formula reduces to:

\begin{equation}
\mbox{Total}_i = \left(\frac{\mbox{cells}_i - \mbox{num_cells_per_block}_i}{N_i}\right) + 1\mbox{  }; \mbox{  for  } i = x,y
\end{equation}

Where cells$_x$ is the total number of cells along the width of the detection window, and cells$_y$, is the total number of cells along the height of the detection window. And $N_x$ is the horizontal block stride in units of `cell_size` and $N_y$ is the vertical block stride in units of `cell_size`.

Let's calculate what the number of elements for the HOG feature vector should be and check that it matches the shape of the HOG Descriptor calculated above.

```python
# Calculate the total number of blocks along the width of the detection window
tot_bx = np.uint32(((x_cells - num_cells_per_block[0]) / h_stride) + 1)

# Calculate the total number of blocks along the height of the detection window
tot_by = np.uint32(((y_cells - num_cells_per_block[1]) / v_stride) + 1)

# Calculate the total number of elements in the feature vector
tot_els = (tot_bx) * (tot_by) * num_cells_per_block[0] * num_cells_per_block[1] * num_bins

# Print the total number of elements the HOG feature vector should have
print('\nThe total number of elements in the HOG Feature Vector should be: ',
      tot_bx, 'x',
      tot_by, 'x',
      num_cells_per_block[0], 'x',
      num_cells_per_block[1], 'x',
      num_bins, '=',
      tot_els)

# Print the shape of the HOG Descriptor to see that it matches the above
print('\nThe HOG Descriptor has shape:', hog_descriptor.shape)
print()
```

    The total number of elements in the HOG Feature Vector should be:  40 x 40 x 2 x 2 x 9 = 57600

    The HOG Descriptor has shape: (57600, 1)

# Visualizing The HOG Descriptor

We can visualize the HOG Descriptor by plotting the histogram associated with each cell as a collection of vectors. To do this, we will plot each bin in the histogram as a single vector whose magnitude is given by the height of the bin and its orientation is given by the angular bin that its associated with. Since any given cell might have multiple histograms associated with it, due to the overlapping blocks, we will choose to average all the histograms for each cell to produce a single histogram for each cell.

OpenCV has no easy way to visualize the HOG Descriptor, so we have to do some manipulation first in order to visualize it. We will start by reshaping the HOG Descriptor in order to make our calculations easier.  We will then compute the average histogram of each cell and finally we will convert the histogram bins into vectors. Once we have the vectors, we plot the corresponding vectors for each cell in an image.

The code below produces an interactive plot so that you can interact with the figure. The figure contains:
* the grayscale image,
* the HOG Descriptor (feature vector),
* a zoomed-in portion of the HOG Descriptor, and
* the histogram of the selected cell.

**You can click anywhere on the gray scale image or the HOG Descriptor image to select a particular cell**. Once you click on either image a *magenta* rectangle will appear showing the cell you selected. The Zoom Window will show you a zoomed in version of the HOG descriptor around the selected cell; and the histogram plot will show you the corresponding histogram for the selected cell. The interactive window also has buttons at the bottom that allow for other functionality, such as panning, and giving you the option to save the figure if desired. The home button returns the figure to its default value.

**NOTE**: If you are running this notebook in the Udacity workspace, there is around a 2 second lag in the interactive plot. This means that if you click in the image to zoom in, it will take about 2 seconds for the plot to refresh.

```python
%matplotlib notebook

import copy
import matplotlib.patches as patches

# Set the default figure size
plt.rcParams['figure.figsize'] = [9.8, 9]

# Reshape the feature vector to [blocks_y, blocks_x, num_cells_per_block_x, num_cells_per_block_y, num_bins].
# The blocks_x and blocks_y will be transposed so that the first index (blocks_y) referes to the row number
# and the second index to the column number. This will be useful later when we plot the feature vector, so
# that the feature vector indexing matches the image indexing.
hog_descriptor_reshaped = hog_descriptor.reshape(tot_bx,
                                                 tot_by,
                                                 num_cells_per_block[0],
                                                 num_cells_per_block[1],
                                                 num_bins).transpose((1, 0, 2, 3, 4))

# Print the shape of the feature vector for reference
print('The feature vector has shape:', hog_descriptor.shape)

# Print the reshaped feature vector
print('The reshaped feature vector has shape:', hog_descriptor_reshaped.shape)

# Create an array that will hold the average gradients for each cell
ave_grad = np.zeros((y_cells, x_cells, num_bins))

# Print the shape of the ave_grad array for reference
print('The average gradient array has shape: ', ave_grad.shape)

# Create an array that will count the number of histograms per cell
hist_counter = np.zeros((y_cells, x_cells, 1))

# Add up all the histograms for each cell and count the number of histograms per cell
for i in range (num_cells_per_block[0]):
    for j in range(num_cells_per_block[1]):
        ave_grad[i:tot_by + i,
                 j:tot_bx + j] += hog_descriptor_reshaped[:, :, i, j, :]

        hist_counter[i:tot_by + i,
                     j:tot_bx + j] += 1

# Calculate the average gradient for each cell
ave_grad /= hist_counter

# Calculate the total number of vectors we have in all the cells.
len_vecs = ave_grad.shape[0] * ave_grad.shape[1] * ave_grad.shape[2]

# Create an array that has num_bins equally spaced between 0 and 180 degress in radians.
deg = np.linspace(0, np.pi, num_bins, endpoint = False)

# Each cell will have a histogram with num_bins. For each cell, plot each bin as a vector (with its magnitude
# equal to the height of the bin in the histogram, and its angle corresponding to the bin in the histogram).
# To do this, create rank 1 arrays that will hold the (x,y)-coordinate of all the vectors in all the cells in the
# image. Also, create the rank 1 arrays that will hold all the (U,V)-components of all the vectors in all the
# cells in the image. Create the arrays that will hold all the vector positons and components.
U = np.zeros((len_vecs))
V = np.zeros((len_vecs))
X = np.zeros((len_vecs))
Y = np.zeros((len_vecs))

# Set the counter to zero
counter = 0

# Use the cosine and sine functions to calculate the vector components (U,V) from their maginitudes. Remember the
# cosine and sine functions take angles in radians. Calculate the vector positions and magnitudes from the
# average gradient array
for i in range(ave_grad.shape[0]):
    for j in range(ave_grad.shape[1]):
        for k in range(ave_grad.shape[2]):
            U[counter] = ave_grad[i,j,k] * np.cos(deg[k])
            V[counter] = ave_grad[i,j,k] * np.sin(deg[k])

            X[counter] = (cell_size[0] / 2) + (cell_size[0] * i)
            Y[counter] = (cell_size[1] / 2) + (cell_size[1] * j)

            counter = counter + 1

# Create the bins in degress to plot our histogram.
angle_axis = np.linspace(0, 180, num_bins, endpoint = False)
angle_axis += ((angle_axis[1] - angle_axis[0]) / 2)

# Create a figure with 4 subplots arranged in 2 x 2
fig, ((a,b),(c,d)) = plt.subplots(2,2)

# Set the title of each subplot
a.set(title = 'Gray Scale Image\n(Click to Zoom)')
b.set(title = 'HOG Descriptor\n(Click to Zoom)')
c.set(title = 'Zoom Window', xlim = (0, 18), ylim = (0, 18), autoscale_on = False)
d.set(title = 'Histogram of Gradients')

# Plot the gray scale image
a.imshow(gray_image, cmap = 'gray')
a.set_aspect(aspect = 1)

# Plot the feature vector (HOG Descriptor)
b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
b.invert_yaxis()
b.set_aspect(aspect = 1)
b.set_facecolor('black')

# Define function for interactive zoom
def onpress(event):

    #Unless the left mouse button is pressed do nothing
    if event.button != 1:
        return

    # Only accept clicks for subplots a and b
    if event.inaxes in [a, b]:

        # Get mouse click coordinates
        x, y = event.xdata, event.ydata

        # Select the cell closest to the mouse click coordinates
        cell_num_x = np.uint32(x / cell_size[0])
        cell_num_y = np.uint32(y / cell_size[1])

        # Set the edge coordinates of the rectangle patch
        edgex = x - (x % cell_size[0])
        edgey = y - (y % cell_size[1])

        # Create a rectangle patch that matches the the cell selected above
        rect = patches.Rectangle((edgex, edgey),
                                  cell_size[0], cell_size[1],
                                  linewidth = 1,
                                  edgecolor = 'magenta',
                                  facecolor='none')

        # A single patch can only be used in a single plot. Create copies
        # of the patch to use in the other subplots
        rect2 = copy.copy(rect)
        rect3 = copy.copy(rect)

        # Update all subplots
        a.clear()
        a.set(title = 'Gray Scale Image\n(Click to Zoom)')
        a.imshow(gray_image, cmap = 'gray')
        a.set_aspect(aspect = 1)
        a.add_patch(rect)

        b.clear()
        b.set(title = 'HOG Descriptor\n(Click to Zoom)')
        b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
        b.invert_yaxis()
        b.set_aspect(aspect = 1)
        b.set_facecolor('black')
        b.add_patch(rect2)

        c.clear()
        c.set(title = 'Zoom Window')
        c.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 1)
        c.set_xlim(edgex - cell_size[0], edgex + (2 * cell_size[0]))
        c.set_ylim(edgey - cell_size[1], edgey + (2 * cell_size[1]))
        c.invert_yaxis()
        c.set_aspect(aspect = 1)
        c.set_facecolor('black')
        c.add_patch(rect3)

        d.clear()
        d.set(title = 'Histogram of Gradients')
        d.grid()
        d.set_xlim(0, 180)
        d.set_xticks(angle_axis)
        d.set_xlabel('Angle')
        d.bar(angle_axis,
              ave_grad[cell_num_y, cell_num_x, :],
              180 // num_bins,
              align = 'center',
              alpha = 0.5,
              linewidth = 1.2,
              edgecolor = 'k')

        fig.canvas.draw()

# Create a connection between the figure and the mouse click
fig.canvas.mpl_connect('button_press_event', onpress)
plt.show()
```

    The feature vector has shape: (57600, 1)
    The reshaped feature vector has shape: (40, 40, 2, 2, 9)
    The average gradient array has shape:  (41, 41, 9)

    <IPython.core.display.Javascript object>

# Understanding The Histograms

Let's take a look at a couple of snapshots of the above figure to see if the histograms for the selected cell make sense. Let's start looking at a cell that is inside a triangle and not near an edge:

Fig. 4. - Histograms Inside a Triangle.

In this case, since the triangle is nearly all of the same color there shouldn't be any dominant gradient in the selected cell. As we can clearly see in the Zoom Window and the histogram, this is indeed the case. We have many gradients but none of them clearly dominates over the other.

Now let’s take a look at a cell that is near a horizontal edge:

Fig. 5. - Histograms Near a Horizontal Edge.

Remember that edges are areas of an image where the intensity changes abruptly. In these cases, we will have a high intensity gradient in some particular direction. This is exactly what we see in the corresponding histogram and Zoom Window for the selected cell.  In the Zoom Window, we can see that the dominant gradient is pointing up, almost at 90 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 90-degree bin in the histogram to dominate strongly over the others. This is in fact what we see.

Now let’s take a look at a cell that is near a vertical edge:

Fig. 6. - Histograms Near a Vertical Edge.

In this case we expect the dominant gradient in the cell to be horizontal, close to 180 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 170-degree bin in the histogram to dominate strongly over the others. This is what we see in the histogram but we also see that there is another dominant gradient in the cell, namely the one in the 10-degree bin. The reason for this, is because the HOG algorithm is using unsigned gradients, which means 0 degrees and 180 degrees are considered the same. Therefore, when the histograms are being created, angles between 160 and 180 degrees, contribute proportionally to both the 10-degree bin and the 170-degree bin. This results in there being two dominant gradients in the cell near the vertical edge instead of just one.

To conclude let’s take a look at a cell that is near a diagonal edge.

Fig. 7. - Histograms Near a Diagonal Edge.

To understand what we are seeing, let’s first remember that gradients have an *x*-component, and a *y*-component, just like vectors. Therefore, the resulting orientation of a gradient is going to be given by the vector sum of its components. For this reason, on vertical edges the gradients are horizontal, because they only have an x-component, as we saw in Figure 4. While on horizontal edges the gradients are vertical, because they only have a y-component, as we saw in Figure 3. Consequently, on diagonal edges, the gradients are also going to be diagonal because both the *x* and *y* components are non-zero.  Since the diagonal edges in the image are close to 45 degrees, we should expect to see a dominant gradient orientation in the 50-degree bin. This is in fact what we see in the histogram but, just like in Figure 4., we see there are two dominant gradients instead of just one. The reason for this is that when the histograms are being created, angles that are near the boundaries of bins, contribute proportionally to the adjacent bins.  For example, a gradient with an angle of 40 degrees, is right in the middle of the 30-degree and 50-degree bin. Therefore, the magnitude of the gradient is split evenly into the 30-degree and 50-degree bin. This results in there being two dominant gradients in the cell near the diagonal edge instead of just one.

Now that you know how HOG is implemented, in the workspace you will find a notebook named *Examples*. In there, you will be able set your own paramters for the HOG descriptor for various images. Have fun!

```python

```


# OpenQEMIST and Rigetti example

This notebook shows how OpenQEMIST can be combined with the Rigetti stack to use the Variational Quantum Eigensolver (VQE) as an electronic structure solver, and combine it with a problem-decomposition technique such as Density Matrix Embedding Theory (DMET).

## VQE Example

This tutorial assumes that the user has correctly set up and configured the OpenQEMIST package. The Variational Quantum Eigensolver (VQE)$^{1,2}$ is a hybrid quantum-classical algorithm for simulating quantum systems. We here focus on VQE within the context of solving the molecular electronic structure problem for the ground-state energy of a molecular system. In VQE, we first prepare the trial wavefunction (quantum state) $\vert \Psi(\vec{\theta}) \rangle = U(\vec{\theta}) \vert 0 \rangle$ based on an ansatz that depends on $m$ parameters defining $\vec{\theta}=(\theta_1, \theta_2, \ldots, \theta_m)$. The expectation value of the Hamiltonian ($\hat{H}$), $\langle \Psi(\vec{\theta}) \vert \hat{H} \vert \Psi(\vec{\theta}) \rangle$, will then be simulated.

The expectation value can be minimized based on the variational principle,

\begin{equation}
E = \min_{\vec{\theta}} \frac{\langle \Psi(\vec{\theta}) \vert \hat{H} \vert \Psi(\vec{\theta}) \rangle}{\langle \Psi(\vec{\theta}) \vert \Psi(\vec{\theta}) \rangle} \geq E_{\text{gs}}\nonumber
\end{equation}

which ensures that the energy computed will be an upper bound to the true ground-state energy $E_{\text{gs}}$. This allows us using classical minimizers to find optimal parameters $\vec{\theta}$ for the ground-state energy $E_{\text{gs}}$.

VQE can be performed using OpenQEMIST in conjuction with the Rigetti stack for calculating the ground state energy of a molecular system. The unitary coupled-cluster ansatz can be used to prepare the trial wavefunction $\vert \Psi(\vec{\theta}) \rangle$. In this notebook, we will show you an example using a small molecule, the hydrogen molecule (H$_\text{2}$), for a simulation using VQE.

```python
from openqemist.electronic_structure_solvers import VQESolver, FCISolver
from openqemist.quantum_solvers import RigettiParametricSolver

from pyscf import gto

# Build the molecule
H2 = [['H', [0.0,   0.0,   0.0]], ['H', [0.0,   0.0,   0.74137727]]]
mol = gto.Mole()
mol.atom = H2
mol.basis = "sto-3g"
mol.charge = 0
mol.spin = 0
mol.build()

# Configure the solver object
vqe_solver = VQESolver()
vqe_solver.hardware_backend_type = RigettiParametricSolver
vqe_solver.ansatz_type = RigettiParametricSolver.Ansatze.UCCSD
```

We can now simulate the molecule and get its energy.

```python
energy_fci = FCISolver().simulate(mol)
energy_vqe = vqe_solver.simulate(mol)

print("\nFCI energy = ", energy_fci)
print("VQE energy = ", energy_vqe)
```

It is possible to use different initial parameters for the optimization:

```python
# Using custom initial parameters
# Getting the dimension of the initial parameters vector
num_var_params = vqe_solver.hardware_backend.amplitude_dimension
# Set the intial parameters for the solver
vqe_solver.initial_var_params = [0.01 for i in range(num_var_params)]

vqe_solver.simulate(mol)
```

## Using the QVM shot-based simulator
To use the QVM, we can use the `backend_parameters` attribute of the `VQESolver` object. The VQE object then configures the hardware backend automatically. Because the QVM is slower than the default wavefunction simulator backend, we specify an optimizer function that returns after a few iterations, in the interest of showing the usage of the solver in a reasonable time. See the documentation for more details about using custom optimizers. This interface is what would also be used to target a QPU backend in the future.

```python
def quick_optimizer(backend, amplitudes):
        from scipy.optimize import minimize

        print("Running using custom optimizer.")

        # We use a force the optimizer to return after 2 iterations.
        result = minimize(backend, amplitudes, method='COBYLA',
                options={'disp':True, 'maxiter':2})

        return result.fun

vqe = VQESolver()
vqe.optimizer = quick_optimizer
```

To use the QVM, we can use the `backend_parameters` attribute of the `VQESolver` object. The VQE object then configures the hardware backend automatically. We can then run the simulation with the object. The number of shots can also be set with this parameter.

Note that because we restricted the optimizer to 2 iterations and reduced the number of shots, the resulting energy will not be accurate.

```python
vqe.hardware_backend_type = RigettiParametricSolver
vqe.ansatz_type = RigettiParametricSolver.Ansatze.UCCSD
vqe.backend_parameters = {'backend': '4q-qvm', 'n_shots': 10}

energy = vqe.simulate(mol)
print("Unconverged QMV energy: ", energy)
```

## DMET Example

At the current early stage of quantum hardware, the available computational resource is yet very limited. Thus, it is still challenging to perform accurate electronic structure calculations on actual quantum hardware. Simulation on classical computer requires large computational cost as well. Therefore, we need to reduce the problem size while maintaining the accuracy of electronic structure calculation to solve a problem for small sized molecules to perform quantum simulations.

Density Matrix Embedding Theory (DMET)$^{3,4}$ is a powerful problem decomposition technique to reduce the problem size, while maintaining the accuracy of the electronic structure calculation. The DMET method decomposes a molecule into fragments, and each fragment is treated as an open quantum system that is entangled with each of the other fragments, all taken together to be that fragment's surrounding environment (or "bath"). VQE algorithm can be used with DMET using OpenQEMIST in conjuction with the Rigetti stack.

In this notebook, we will show you an example of H$_\text{4}$ molecule for DMET simulation using VQE as an electronic structure solver.

```python
from openqemist.problem_decomposition import DMETProblemDecomposition
from openqemist.problem_decomposition.electron_localization import meta_lowdin_localization

H4 = [['H', [0.7071067811865476,   0.0,                 0.0]],
      ['H', [0.0,                  0.7071067811865476,  0.0]],
      ['H', [-1.0071067811865476,  0.0,                 0.0]],
      ['H', [0.0,                 -1.0071067811865476,  0.0]]]

mol = gto.Mole()
mol.atom = H4
mol.basis = "minao"
mol.charge = 0
mol.spin = 0
mol.build()

dmet = DMETProblemDecomposition()
dmet.verbose = True
dmet.electron_localization_method = meta_lowdin_localization
# Set the DMET object to use the solver that we configured above
dmet.electronic_structure_solver = vqe_solver
energy_vqe = dmet.simulate(mol, [1,1,1,1])

print("The DMET energy is: ", energy_vqe)
```

## References
1. Alberto Peruzzo, Jarrod McClean, Peter Shadbolt, Man-Hong Yung, Xiao-Qi Zhou, Peter J. Love, Alán Aspuru-Guzik, and Jeremy L. O'Brien, "A variational eigenvalue solver on a photonic quantum processor", Nat. Commun., 5, 4213 (2014).
2. Jarrod R. McClean, Jonathan Romero, Ryan Babbush, and Alán Aspuru-Guzik, "The theory of variational hybrid quantum-classical algorithms", New J. Phys., 18, 023023 (2016).
3. Gerald Knizia and Garnet K.-L. Chan, "Density Matrix Embedding: A Simple Alternative to Dynamical Mean-Field Theory", Phys. Rev. Lett., 109, 186404 (2012).
4. Sebastian Wouters, Carlos A. Jiménez-Hoyos, Qiming Sun, and Garnet K.-L. Chan, "A Practical Guide to Density Matrix Embedding Theory in Quantum Chemistry", J. Chem. Theory Comput., 12, pp. 2706–2719 (2016).