```python
 import sys
! whoami
 print(sys.executable)
 print(sys.version)
 print(sys.version_info)
_alphaadvantage = 'KW2PCDA3R4PR0QWO'  # alphadvantage API key KW2PCDA3R4PR0QWO
_quantl = 'cBtBbexR-ZQtNfbx_fyk'  # quandl API key = cBtBbexR-ZQtNfbx_fyk
```

    compthink
    /opt/conda/envs/python/bin/python
    3.8.3 (default, Jul  2 2020, 16:21:59)
    [GCC 7.3.0]
    sys.version_info(major=3, minor=8, micro=3, releaselevel='final', serial=0)

# What is a Time-Series?

Time Series comprises of observations that are captured at regular intervals.
Time Series datasets have a strong temporal dependence (called serial correlation).
It can be used to forecast future observations based on previous ones.

In this example, we will be using the stock prices of a certain company. However, do not trade based on this forecast, remember the example is presented by a University associate professor, who are idiots when it comes to making money. Stock prices are dependent on various factors like supply and demand, company performance, the sentiment of the investors, here it is an example of temporally correlated data, later on we will examine other such data with hydrologic significance, and get into issues of causality for prediction making.

## Decomposing the Time Series:

The Time-Series can be divided into several parts as follows:

* Trend: The increase or decrease in the value of the data. This can further be divided into the global trend and local trend.

* Seasonality: It is the repetitive pattern that is visible in the series. This rise or fall in the values of the data is of fixed frequency. For example, the sales of Christmas trees are always greater during December and less for the rest. The tools of harmonic analysis are useful for this component.

* Cyclicity: It is the rise and fall in the value of the data which is not of a fixed frequency. It can be seen as an outcome of economic conditions or other external factors - this component is the `aperiodic` part of the signal, in many cases quite subtle.

* Noise: It is just some random data that we obtain after extracting the trend and the seasonal components from the time series.

The components of the time series can be an additive or multiplicative version.

An additive model is:

\begin{equation}
TS = Trend_t + Seasonality_t + Cyclicity_t + Noise_t
\end{equation}

The additive model is preferred when the magnitude of the seasonal pattern does not correlate with the data values.

A multiplicative (power-law) model is:

\begin{equation}
TS = Trend_t * Seasonality_t * Cyclicity_t * Noise_t
\end{equation}

The multiplicative model is preferred when the magnitude of the seasonal pattern increases or decreases with the increase or decrease in the data values.

In the additive version, the magnitude of the seasonal pattern remains to be uncorrelated with the rising trend whereas it is increasing with the increase in the trend of the time series in the multiplicative version.

As with all our data science examples, we start with how to get a dataset

## How to obtain stock price data?

We can obtain stock prices by creating an API (Application Programming Interface) request using the `requests` package. We can also use the `quandl` package which is designed to provide financial and economic data.

We will extract the prices of US stocks from alphavantage website by creating an API call. Using the code fragment below, we have extracted the daily stock prices of IBM.

### Getting API keys
API keys are passkeys to the remote data sets.  In this example I already have keys that I obtained from the data vendor, they are free, but will be used to send future RFMs for goods and services.

So first we attach support packages

```python
# attach support packages
import requests
import pandas as pd
import quandl
import numpy as np
from statsmodels.tsa.seasonal  import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.stattools import kpss
from statsmodels.tsa.api import ExponentialSmoothing, SimpleExpSmoothing, Holt
from pmdarima.arima import auto_arima
from math import sqrt
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
```

Then the data requests

```python
# Creating an API request to get the data for a stock.
req = requests.get('https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=IBM&outputsize=full&apikey=_alphaadvantage&datatype=csv')
# Transforming the content of the request to a dataframe.
request_content = req.content
data = list(map(lambda x: x.split(','),request_content.decode('utf-8').split("\r\n")))
dataframe = pd.DataFrame(data[1:], columns=data[0])
dataframe = dataframe.set_index('timestamp')
dataframe.index = pd.to_datetime(dataframe.index)
dataframe.head()
```

      open
      high
      low
      close
      adjusted_close
      volume
      dividend_amount
      split_coefficient

      timestamp

      2020-07-23
      129.1000
      129.3700
      127.1500
      127.3300
      127.3300
      4160885
      0.0000
      1.0000

      2020-07-22
      125.9000
      129.4700
      125.8000
      128.6700
      128.6700
      8195366
      0.0000
      1.0000

      2020-07-21
      131.1600
      132.1700
      125.8000
      126.0600
      126.0600
      15356679
      0.0000
      1.0000

      2020-07-20
      126.0700
      127.0700
      125.1400
      126.3700
      126.3700
      9737157
      0.0000
      1.0000

      2020-07-17
      124.3900
      125.6300
      123.2000
      125.1100
      125.1100
      3984000
      0.0000
      1.0000

For using `quandl` package to import data into python, you need to install `quandl` package. I entered the command `! sudo -H /opt/conda/envs/python/bin/python -m pip install quandl` into the JupyterHub service notebook so it should be good to go. We have imported the prices of Infosys (BOM500209) and will use these for our further analysis. More documentation on `quandl` and how to get the best out of it can be found at the vendor website.

```python
# Using quandl package to obtain data regarding a stock.
Stock_data = quandl.get("BSE/BOM500209",api_key="cBtBbexR-ZQtNfbx_fyk")
# Stock_data.info()
# Plot the Closing Price
plt.figure(figsize=(24,6))
plt.title('Closing Price for Infosys Inc.')
plt.plot(Stock_data['Close'], color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
```

```python
# Decomposition of trend, seasonality and residue of the original time series using seasonal decomposition tools
result = seasonal_decompose(Stock_data['Close'], model='multiplicative',period=30)
# print(result.trend)
# print(result.seasonal)
# print(result.resid)
# print(result.observed)
result.resid.describe()
```

    count    6019.000000
    mean        0.998432
    std         0.060188
    min         0.431963
    25%         0.977452
    50%         0.999661
    75%         1.020287
    max         1.618074
    Name: resid, dtype: float64

```python
# Plot the Closing Price
plt.figure(figsize=(24,6))
plt.title('Closing Price Observed for Infosys Inc.')
plt.plot(result.observed, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Trend
plt.figure(figsize=(24,6))
plt.title('Closing Price Trend for Infosys Inc.')
plt.plot(result.trend, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Seasonality
plt.figure(figsize=(24,6))
plt.title('Closing Price Seasonality for Infosys Inc.')
plt.plot(result.seasonal, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Residual
plt.figure(figsize=(24,6))
plt.title('Closing Price Residual for Infosys Inc.')
plt.plot(result.resid, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
```

The trend experiences some ups and downs as a stock generally does. It does not have a meaningful seasonal component. The residuals’ variance seems to remain the same except for a few observations.

If we repeat using an additive model, we obtain:

```python
# Decomposition of trend, seasonality and residue of the original time series using seasonal decomposition tools
result = seasonal_decompose(Stock_data['Close'], model='additive',period=30)
# print(result.trend)
# print(result.seasonal)
# print(result.resid)
# print(result.observed)
result.resid.describe()
```

    count    6019.000000
    mean       -0.016174
    std       261.421334
    min     -3573.768184
    25%       -46.704659
    50%        -1.848815
    75%        45.638016
    max      4534.650247
    Name: resid, dtype: float64

```python
# Plot the Closing Price
plt.figure(figsize=(24,6))
plt.title('Closing Price Observed for Infosys Inc.')
plt.plot(result.observed, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Trend
plt.figure(figsize=(24,6))
plt.title('Closing Price Trend for Infosys Inc.')
plt.plot(result.trend, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Seasonality
plt.figure(figsize=(24,6))
plt.title('Closing Price Seasonality for Infosys Inc.')
plt.plot(result.seasonal, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
# Plot the Closing Price Residual
plt.figure(figsize=(24,6))
plt.title('Closing Price Residual for Infosys Inc.')
plt.plot(result.resid, color='red',label='Original Time Series')
plt.legend(loc='best')
plt.show()
```

## Checking for Stationarity

For ARIMA (Auto-Regressive Integrated Moving Average) analysis, the time series has to be made stationary for further analysis. For a time series to be stationary, its statistical properties(mean, variance, etc) will be the same throughout the series, irrespective of the time at which you observe them. A stationary time series will have no long-term predictable patterns such as trends or seasonality. Time plots will show the series to roughly have a horizontal trend with the constant variance.

### Rolling Statistics (Moving Window "Averages")

We can plot the rolling mean and standard deviation to check if the statistics show an upward or downward trend. If these statistics vary over time, then the time series is highly likely to be non-stationary.
ADF and KPSS Test

To check the stationarity of the time series, we will also use the ADF (Augmented Dickey-Fuller) test and KPSS (Kwiatkowski–Phillips–Schmidt–Shintests) test. The null hypothesis of the ADF test is that the time series is not stationary whereas that for the KPSS is that it is stationary.

```python
#Testing for stationarity using ADF and KPSS Tests.

def stationarity_test(stock_close_price):
    # Calculating rolling mean and rolling standard deviation:
    rolling_mean = stock_close_price.rolling(30).mean()
    rolling_std_dev = stock_close_price.rolling(30).std()

    # Plotting the statistics:
    plt.figure(figsize=(24,6))
    plt.plot(rolling_mean, color='blue', label='Rolling Mean')
    plt.plot(rolling_std_dev, color='green', label = 'Rolling Std Dev')
    plt.plot(stock_close_price, color='red',label='Original Time Series')
    plt.legend(loc='best')
    plt.title('Rolling Mean and Standard Deviation')

    print("ADF Test:")
    adf_test = adfuller(stock_close_price,autolag='AIC')
    print('Null Hypothesis: Not Stationary')
    print('ADF Statistic: %f' % adf_test[0])
    print('p-value: %f' % adf_test[1])
    print('Critical Values:')
    for key, value in adf_test[4].items():
        print('\t%s: %.3f' % (key, value))

    print("KPSS Test:")
#    kpss_test = kpss(stock_close_price, regression='c', nlags=None, store=False)
    kpss_test = kpss(stock_close_price, regression='c', nlags='legacy', store=False)
    print('Null Hypothesis: Stationary')
    print('KPSS Statistic: %f' % kpss_test[0])
    print('p-value: %f' % kpss_test[1])
    print('Critical Values:')
    for key, value in kpss_test[3].items():
        print('\t%s: %.3f' % (key, value))

stationarity_test(Stock_data['Close'])
```

We can see the rolling mean trending up and down over time as the price of the stock increases and decreases respectively. The `p-value` of the ADF test turns out to be greater than `0.05` which means we cannot reject the null hypothesis at a significance level of 95%, in this case the series appears non-stationary.

The p-value of the KPSS test is below 0.05 which means we can reject the null hypothesis at a significance level of 95%, in this case the series appears non-stationary.

Both these tests suggest that the time series is not stationary, so we need to de-trend the series.

### How to de-trend the time series?

#### Differencing (Lag 1 Auto-Regressive) :
A new series is constructed by calculating the value at the current time by differencing the value of actual observation of current time and its previous time.

    value(t) = actual_observation(t) - actual_observation(t-1)

#### Transformation (Mapping to different scale):
Transforming the values using power, square root, log, etc can help to linearize the data. For example, taking a log of the values can help in obtaining a linear trend to the series with an exponential trend. log(exp(x))=x

#### Seasonal Differencing (Lag N Auto-Regressive):
The values of the time series are calculated by differencing between one observation and its previous Nth observation. This can help in removing the trend

    value(t) = actual_observation(t) - actual_observation(t-N)

#### Fitting a model:
We can fit a linear regression model to the time series. It will fit a linear trend on the time series. The values for the de-trended time series can be calculated by subtracting the actual observations with the values predicted by the model.

    value(t) = actual_observation(t) - predicted(t)

The three methods above are themselves particular models, so all De-Trending uses some kind of model fitting.

```python
#Testing for stationarity of de-trended time series using ADF and KPSS Tests.

# De-trending the time series
tau = 30 # Lag N value
Stock_data['Close_Detrend'] = (Stock_data['Close'] - Stock_data['Close'].shift(tau))

def stationarity_test(stock_close_price):
    # Calculating rolling mean and rolling standard deviation:
    rolling_mean = stock_close_price.rolling(tau).mean()
    rolling_std_dev = stock_close_price.rolling(tau).std()

    # Plotting the statistics:
    plt.figure(figsize=(24,6))
    plt.plot(rolling_mean, label='Rolling Mean',linewidth=2.0)
    plt.plot(rolling_std_dev, label = 'Rolling Std Dev',linewidth=2.0)
    plt.plot(stock_close_price,label='De-Trended Time Series')
    plt.legend(loc='best')
    plt.title('Rolling Mean and Standard Deviation')
    plt.tight_layout()
    plt.show()

    print("ADF Test:")
    adf_test = adfuller(stock_close_price,autolag='AIC')
    print('Null Hypothesis: Not Stationary')
    print('ADF Statistic: %f' % adf_test[0])
    print('p-value: %f' % adf_test[1])
    print('Critical Values:')
    for key, value in adf_test[4].items():
        print('\t%s: %.3f' % (key, value))

    print("KPSS Test:")
    kpss_test = kpss(stock_close_price, regression='c', nlags='legacy', store=False)
    print('Null Hypothesis: Stationary')
    print('KPSS Statistic: %f' % kpss_test[0])
    print('p-value: %f' % kpss_test[1])
    print('Critical Values:')
    for key, value in kpss_test[3].items():
        print('\t%s: %.3f' % (key, value))

stationarity_test(Stock_data['Close_Detrend'].dropna())

# PACF Plot
from statsmodels.graphics.tsaplots import plot_pacf
pacf = plot_pacf(Stock_data['Close_Detrend'].dropna(), lags=tau)
```

After de-trending the time series, ADF and KPSS tests indicate that the time-series is stationary. The Partial AutoCorrelation Function (PACF) Plot suggests that correlation exists at certain lags; Lags 0 (of course self-correlation),1,2,3,12,13,14 are evident, suggesting same week price influence, and prior week price influence.

### Model Building

To forecast the prices, we can use smoothing methods and ARIMA methods. Smoothing methods can be used for non-stationary data whereas ARIMA requires the time series to be stationary. We can also make use of auto_arima, which makes the series stationary and determines the optimal order for the ARIMA model.

For each of the methods, we will perform multiple fits for the optimization of the hyperparameters and use the optimal values for the final model.  First we will split the dataset into two partitions.  One for model training and one for performance testing.

```python
#split data into train and training set
Stock_data_arima = Stock_data['Close']
train_test_split_ratio = int(len(Stock_data_arima)*0.8)
train_data, test_data = Stock_data_arima[:train_test_split_ratio], Stock_data_arima[train_test_split_ratio:]

# Plotting the train and test sets.
plt.figure(figsize=(10,6))
plt.xlabel('Year')
plt.ylabel('Closing Price')
plt.plot(train_data, 'red', label='Train data')
plt.plot(test_data, 'black', label='Test data')
plt.legend(loc='best')
plt.show()
```

#### Simple Exponential Smoothing

Simple Exponential Smoothing or SES is used when the data does not contain any trend or seasonality. Smoothing Factor for level ($\alpha$) provides weightage to the influence of the observations. Larger values of $\alpha$ mean that more attention is given to the most recent past observation whereas smaller values indicate that more past observations are being considered for forecasting.

\begin{equation}
F_{t+1} = \alpha d_t + (1-\alpha)F_t
\end{equation}

```python
# Simple Exponential Smoothing Method
pred_values = test_data.copy()
pred_values = pd.DataFrame(pred_values)

Simple_Exponential_df = pd.DataFrame(columns = ['RMS','Smoothing Level'])

from itertools import permutations
perm = permutations(list(np.linspace(0.05,1,num=20)), 1)
for i in list(perm):
  fit_sim_exp = SimpleExpSmoothing(np.asarray(train_data)).fit(smoothing_level = i[0])
  pred_values['Simple_Exponential'] = fit_sim_exp.forecast(len(test_data))

  rms = round(sqrt(mean_squared_error(test_data.values, pred_values.Simple_Exponential)),3)
  Simple_Exponential_df = Simple_Exponential_df.append(other = {'RMS' : rms , 'Smoothing Level' : i[0]} , ignore_index=True)

opt_values = Simple_Exponential_df.loc[Simple_Exponential_df['RMS'] == min(Simple_Exponential_df['RMS']),['Smoothing Level']].values

# Using optimised values from the lists.
fit_sim_exp = SimpleExpSmoothing(np.asarray(train_data)).fit(smoothing_level = opt_values[0][0])
pred_values['Simple_Exponential'] = fit_sim_exp.forecast(len(test_data))

plt.figure(figsize=(16,8))
plt.plot(train_data, label='Train')
plt.plot(test_data, label='Test')
plt.plot(pred_values['Simple_Exponential'], label='Simple_Exponential')
plt.legend(loc='best')
plt.show()

rms_sim_exp = sqrt(mean_squared_error(test_data.values, pred_values.Simple_Exponential))
print("Simple Exponential Smoothing RMS :- " + str(round(rms_sim_exp,3)) + " & Smoothing Level :- "+str(round(opt_values[0][0],3)))
```

#### Holt’s Exponential Smoothing

Holt’s Exponential Smoothing takes the trend into account for forecasting the time series. It is used when there is a trend in the data and no seasonality. It calculates the Smoothing value (the first equation), which is the same used in SES for forecasting. Trend Coefficient ($\beta$) provides weightage to the difference in the consequent smoothing values and the previous trend estimate. The forecasting is a combination of the smoothing value and the trend estimate.

\begin{equation}
S_t = \alpha d_t + (1-\alpha)F_t \\
b_t = \beta(S_t - S_{t-1}) + (1-\beta)b_{t-1} \\
F_{t+1} = S_t + b_t \\
\end{equation}

```python
# Holt's Exponential Smoothing Method
holt_linear_df = pd.DataFrame(columns = ['RMS','Smoothing Level','Smoothing Slope'])

from itertools import permutations
perm = permutations(list(np.linspace(0.05,1,num=20)), 2)
for i in list(perm):
  fit_holt = Holt(np.asarray(train_data)).fit(smoothing_level = i[0],smoothing_slope=i[1])
  pred_values['Holt_linear'] = fit_holt.forecast(len(test_data))

  rms = round(sqrt(mean_squared_error(test_data.values, pred_values.Holt_linear)),3)
  holt_linear_df = holt_linear_df.append(other = {'RMS' : rms , 'Smoothing Level' : i[0], 'Smoothing Slope':i[1]} , ignore_index=True)

opt_values = holt_linear_df.loc[holt_linear_df['RMS'] == min(holt_linear_df['RMS']),['Smoothing Level','Smoothing Slope']].values

# Using optimised values from the lists.
fit_holt = Holt(np.asarray(train_data)).fit(smoothing_level = opt_values[0][0],smoothing_slope=opt_values[0][1])
pred_values['Holt_linear'] = fit_holt.forecast(len(test_data))

plt.figure(figsize=(16,8))
plt.plot(train_data, label='Train')
plt.plot(test_data, label='Test')
plt.plot(pred_values['Holt_linear'], label='Holt_linear')
plt.legend(loc='best')
plt.title('Holt Exponential Smoothing')
plt.show()

rms_holt_exp = sqrt(mean_squared_error(test_data.values, pred_values.Holt_linear))
print("Holt’s Exponential Smoothing RMS :- " + str(round(rms_holt_exp,3)) + " & Smoothing Level :- "+str(round(opt_values[0][0],3)) + " & Smoothing Slope :- "+str(round(opt_values[0][1],3)))
```

#### Holt-Winters Exponential Smoothing

Holt-Winters Exponential Smoothing takes trend as well as seasonality into account for forecasting the time series. It forecasts the values using equations for calculating the level component, trend component, and the seasonal component in the time series. According to the seasonal variations in the data, either additive or the multiplicative version is used.

    insert latex here
Additive Method (Source: otexts)

    insert latex here
Multiplicative Method (Source: otexts)

#### Auto-Regressive Integrated Moving Average (ARIMA)

ARIMA model is a combination of Auto-Regressive model and Moving Average model along with the Integration of differencing. Auto-Regressive model determines the relationship between an observation and a certain number of lagged observations. The Integrated part is the differencing of the actual observations to make the time series stationary. Moving Average determines the relationship between an observation and residual error obtained by using a moving average model on the lagged observations.

Auto-Regressive (p) -> Number of lag observations in the model. Also called as the lag order.

Integrated (d) -> The number of times the actual observations are differenced for stationarity. Also called as the degree of differencing.

Moving Average (q) -> Size of the moving average window. Also called as the order of moving average.

```python
# Auto ARIMA Method
arima_model = auto_arima(train_data,
                      start_p=1, start_q=1,
                      max_p=5, max_q=5,
                      test='adf',
                      trace=True,
                      alpha=0.05,
                      scoring='mse',
                      suppress_warnings=True,
                      seasonal = False
                      )
```

    Performing stepwise search to minimize aic
    Fit ARIMA(1,1,1)x(0,0,0,0) [intercept=True]; AIC=64028.729, BIC=64054.666, Time=1.372 seconds
    Fit ARIMA(0,1,0)x(0,0,0,0) [intercept=True]; AIC=64035.210, BIC=64048.178, Time=0.098 seconds
    Fit ARIMA(1,1,0)x(0,0,0,0) [intercept=True]; AIC=64031.326, BIC=64050.778, Time=0.203 seconds
    Fit ARIMA(0,1,1)x(0,0,0,0) [intercept=True]; AIC=64032.090, BIC=64051.542, Time=0.266 seconds
    Fit ARIMA(0,1,0)x(0,0,0,0) [intercept=False]; AIC=64033.212, BIC=64039.697, Time=0.052 seconds
    Fit ARIMA(2,1,1)x(0,0,0,0) [intercept=True]; AIC=64034.837, BIC=64067.258, Time=0.813 seconds
    Fit ARIMA(1,1,2)x(0,0,0,0) [intercept=True]; AIC=63997.555, BIC=64029.977, Time=1.036 seconds
    Fit ARIMA(0,1,2)x(0,0,0,0) [intercept=True]; AIC=64002.081, BIC=64028.018, Time=0.519 seconds
    Fit ARIMA(2,1,2)x(0,0,0,0) [intercept=True]; AIC=63996.866, BIC=64035.772, Time=1.649 seconds
    Fit ARIMA(3,1,2)x(0,0,0,0) [intercept=True]; AIC=63958.678, BIC=64004.068, Time=3.037 seconds
    Fit ARIMA(3,1,1)x(0,0,0,0) [intercept=True]; AIC=63958.019, BIC=63996.924, Time=1.916 seconds
    Fit ARIMA(3,1,0)x(0,0,0,0) [intercept=True]; AIC=63989.818, BIC=64022.239, Time=0.378 seconds
    Fit ARIMA(4,1,1)x(0,0,0,0) [intercept=True]; AIC=63959.200, BIC=64004.589, Time=2.240 seconds
    Fit ARIMA(2,1,0)x(0,0,0,0) [intercept=True]; AIC=64008.076, BIC=64034.013, Time=0.272 seconds
    Fit ARIMA(4,1,0)x(0,0,0,0) [intercept=True]; AIC=63977.186, BIC=64016.092, Time=0.518 seconds
    Fit ARIMA(4,1,2)x(0,0,0,0) [intercept=True]; AIC=63943.233, BIC=63995.107, Time=4.639 seconds
    Fit ARIMA(5,1,2)x(0,0,0,0) [intercept=True]; AIC=63939.568, BIC=63997.926, Time=6.334 seconds
    Fit ARIMA(5,1,1)x(0,0,0,0) [intercept=True]; AIC=63960.304, BIC=64012.178, Time=2.673 seconds
    Fit ARIMA(5,1,3)x(0,0,0,0) [intercept=True]; AIC=63965.602, BIC=64030.444, Time=2.788 seconds
    Fit ARIMA(4,1,3)x(0,0,0,0) [intercept=True]; AIC=63925.551, BIC=63983.910, Time=5.978 seconds
    Fit ARIMA(3,1,3)x(0,0,0,0) [intercept=True]; AIC=63916.330, BIC=63968.204, Time=5.615 seconds
    Fit ARIMA(2,1,3)x(0,0,0,0) [intercept=True]; AIC=63959.034, BIC=64004.424, Time=3.692 seconds
    Fit ARIMA(3,1,4)x(0,0,0,0) [intercept=True]; AIC=63933.501, BIC=63991.860, Time=7.792 seconds
    Fit ARIMA(2,1,4)x(0,0,0,0) [intercept=True]; AIC=63949.026, BIC=64000.900, Time=3.835 seconds
    Fit ARIMA(4,1,4)x(0,0,0,0) [intercept=True]; AIC=63868.935, BIC=63933.778, Time=10.674 seconds
    Fit ARIMA(5,1,4)x(0,0,0,0) [intercept=True]; AIC=63905.748, BIC=63977.075, Time=10.418 seconds

```python
# Fitting the final model with the order
fitted_model = arima_model.fit(train_data)
print(fitted_model.summary())
```

```python
# Forecasting the values.
forecast_values = fitted_model.predict(len(test_data), alpha=0.05)
fcv_series = pd.Series(forecast_values[0], index=test_data.index)
```

```python
#Plotting the predicted stock price and original price.
plt.figure(figsize=(12,5), dpi=100)
plt.plot(train_data, label='training')
plt.plot(test_data, label='Actual Stock Price')
plt.plot(fcv_series,label='Predicted Stock Price')
plt.title('Stock Price Prediction')
plt.xlabel('Time')
plt.ylabel('Close Price')
plt.legend(loc='upper left', fontsize=8)
plt.show()
```

```python
# Evaluating the model by calculating RMSE.
rms_auto_arima = sqrt(mean_squared_error(test_data.values, fcv_series))
print("Auto-Arima RMSE :- " + str(round(rms_auto_arima,3)))
```

```python

```

```python

```

```python

```

```python

```

```python
holt_linear_df.head()
```

```python

```


# Model Checking

After running an MCMC simulation, `sample` returns a `MutliTrace` object containing the samples for all the stochastic and deterministic random variables. The final step in Bayesian computation is model checking, in order to ensure that inferences derived from your sample are valid. There are two components to model checking:

1. Convergence diagnostics
2. Goodness of fit

Convergence diagnostics are intended to detect lack of convergence in the Markov chain Monte Carlo sample; it is used to ensure that you have not halted your sampling too early. However, a converged model is not guaranteed to be a good model. The second component of model checking, goodness of fit, is used to check the internal validity of the model, by comparing predictions from the model to the data used to fit the model.

## Convergence Diagnostics

Valid inferences from sequences of MCMC samples are based on the
assumption that the samples are derived from the true posterior
distribution of interest. Theory guarantees this condition as the number
of iterations approaches infinity. It is important, therefore, to
determine the **minimum number of samples** required to ensure a reasonable
approximation to the target posterior density. Unfortunately, no
universal threshold exists across all problems, so convergence must be
assessed independently each time MCMC estimation is performed. The
procedures for verifying convergence are collectively known as
*convergence diagnostics*.

One approach to analyzing convergence is **analytical**, whereby the
variance of the sample at different sections of the chain are compared
to that of the limiting distribution. These methods use distance metrics
to analyze convergence, or place theoretical bounds on the sample
variance, and though they are promising, they are generally difficult to
use and are not prominent in the MCMC literature. More common is a
**statistical** approach to assessing convergence. With this approach,
rather than considering the properties of the theoretical target
distribution, only the statistical properties of the observed chain are
analyzed. Reliance on the sample alone restricts such convergence
criteria to **heuristics**. As a result, convergence cannot be guaranteed.
Although evidence for lack of convergence using statistical convergence
diagnostics will correctly imply lack of convergence in the chain, the
absence of such evidence will not *guarantee* convergence in the chain.
Nevertheless, negative results for one or more criteria may provide some
measure of assurance to users that their sample will provide valid
inferences.

For most simple models, convergence will occur quickly, sometimes within
a the first several hundred iterations, after which all remaining
samples of the chain may be used to calculate posterior quantities. For
more complex models, convergence requires a significantly longer burn-in
period; sometimes orders of magnitude more samples are needed.
Frequently, lack of convergence will be caused by **poor mixing**.
Recall that *mixing* refers to the degree to which the Markov
chain explores the support of the posterior distribution. Poor mixing
may stem from inappropriate proposals (if one is using the
Metropolis-Hastings sampler) or from attempting to estimate models with
highly correlated variables.

```python
%matplotlib inline
import numpy as np
import seaborn as sns; sns.set_context('notebook')
```

```python
from pymc3 import Normal, Binomial, sample, Model
from pymc3.math import exp, invlogit

# Samples for each dose level
n = 5 * np.ones(4, dtype=int)
# Log-dose
dose = np.array([-.86, -.3, -.05, .73])
obs_deaths = np.array([0, 1, 3, 5])

with Model() as bioassay_model:

    # Logit-linear model parameters
    alpha = Normal('alpha', 0, sd=10)
    beta = Normal('beta', 0, sd=10)

    # Calculate probabilities of death
    theta = invlogit(alpha + beta * dose)

    # Data likelihood
    deaths = Binomial('deaths', n=n, p=theta, observed=obs_deaths)
```

    /Users/fonnescj/anaconda3/envs/dev/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
      from ._conv import register_converters as _register_converters

```python
from pymc3 import Metropolis

with bioassay_model:
    step = Metropolis(scaling=0.0001)
    bioassay_trace = sample(1000, step=step, init=None)
```

    Multiprocess sampling (2 chains in 2 jobs)
    CompoundStep
    >Metropolis: [beta]
    >Metropolis: [alpha]
    100%|██████████| 1500/1500 [00:01<00:00, 1270.83it/s]
    The estimated number of effective samples is smaller than 200 for some parameters.

```python
from pymc3 import traceplot

traceplot(bioassay_trace[500:], varnames=['alpha'])
```

### Informal Methods

The most straightforward approach for assessing convergence is based on
simply **plotting and inspecting traces and histograms** of the observed
MCMC sample. If the trace of values for each of the stochastics exhibits
asymptotic behavior over the last $m$ iterations, this may be
satisfactory evidence for convergence.

```python
with bioassay_model:
    bioassay_trace = sample(10000, init=None)

traceplot(bioassay_trace[9000:], varnames=['beta'])
```

A similar approach involves
plotting a histogram for every set of $k$ iterations (perhaps 50-100)
beyond some burn in threshold $n$; if the histograms are not visibly
different among the sample intervals, this may be considered some evidence for
convergence. Note that such diagnostics should be carried out for each
stochastic estimated by the MCMC algorithm, because convergent behavior
by one variable does not imply evidence for convergence for other
variables in the analysis.

```python
import matplotlib.pyplot as plt

beta_trace = bioassay_trace['beta']

fig, axes = plt.subplots(2, 5, figsize=(14,6))
axes = axes.ravel()
for i in range(10):
    axes[i].hist(beta_trace[500*i:500*(i+1)])
plt.tight_layout()
```

An extension of this approach can be taken
when multiple parallel chains are run, rather than just a single, long
chain. In this case, the final values of $c$ chains run for $n$
iterations are plotted in a histogram; just as above, this is repeated
every $k$ iterations thereafter, and the histograms of the endpoints are
plotted again and compared to the previous histogram. This is repeated
until consecutive histograms are indistinguishable.

Another *ad hoc* method for detecting lack of convergence is to examine
the traces of several MCMC chains initialized with different starting
values. Overlaying these traces on the same set of axes should (if
convergence has occurred) show each chain tending toward the same
equilibrium value, with approximately the same variance. Recall that the
tendency for some Markov chains to converge to the true (unknown) value
from diverse initial values is called *ergodicity*. This property is
guaranteed by the reversible chains constructed using MCMC, and should
be observable using this technique. Again, however, this approach is
only a heuristic method, and cannot always detect lack of convergence,
even though chains may appear ergodic.

```python
with bioassay_model:

    bioassay_trace = sample(20000, njobs=2, step=Metropolis(), start=[{'alpha':-3.}, {'alpha':3.}], init=None)
```

    Multiprocess sampling (2 chains in 2 jobs)
    CompoundStep
    >Metropolis: [beta]
    >Metropolis: [alpha]
    100%|██████████| 20500/20500 [00:05<00:00, 3833.41it/s]
    The number of effective samples is smaller than 10% for some parameters.

```python
plt.plot(bioassay_trace.get_values('alpha', chains=0)[:200], 'r--')
plt.plot(bioassay_trace.get_values('alpha', chains=1)[:200], 'k--')
```

A principal reason that evidence from informal techniques cannot
guarantee convergence is a phenomenon called ***metastability***. Chains may
appear to have converged to the true equilibrium value, displaying
excellent qualities by any of the methods described above. However,
after some period of stability around this value, the chain may suddenly
move to another region of the parameter space. This period
of metastability can sometimes be very long, and therefore escape
detection by these convergence diagnostics. Unfortunately, there is no
statistical technique available for detecting metastability.

### Formal Methods

Along with the *ad hoc* techniques described above, a number of more
formal methods exist which are prevalent in the literature. These are
considered more formal because they are based on existing statistical
methods, such as time series analysis.

PyMC currently includes three formal convergence diagnostic methods. The
first, proposed by [Geweke (1992)](http://projecteuclid.org/DPubS?service=UI&version=1.0&verb=Display&handle=euclid.ss/1177011446), is a time-series approach that
compares the mean and variance of segments from the beginning and end of
a single chain.

$$z = \frac{\bar{\theta}_a - \bar{\theta}_b}{\sqrt{S_a(0) + S_b(0)}}$$

where $a$ is the early interval and $b$ the late interval, and $S_i(0)$ is the spectral density estimate at zero frequency for chain segment $i$. If the
z-scores (theoretically distributed as standard normal variates) of
these two segments are similar, it can provide evidence for convergence.
PyMC calculates z-scores of the difference between various initial
segments along the chain, and the last 50% of the remaining chain. If
the chain has converged, the majority of points should fall within 2
standard deviations of zero.

In PyMC, diagnostic z-scores can be obtained by calling the `geweke` function. It
accepts either (1) a single trace, (2) a Node or Stochastic object, or
(4) an entire Model object:

```python
from pymc3 import geweke

with bioassay_model:
    tr = sample(2000)

z = geweke(tr, intervals=15)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    Multiprocess sampling (2 chains in 2 jobs)
    NUTS: [beta, alpha]
    100%|██████████| 2500/2500 [00:02<00:00, 888.45it/s]
    There were 51 divergences after tuning. Increase `target_accept` or reparameterize.
    There were 65 divergences after tuning. Increase `target_accept` or reparameterize.

```python
plt.scatter(*z[0]['alpha'].T)
plt.hlines([-1,1], 0, 1000, linestyles='dotted')
plt.xlim(0, 1000)
```

The arguments expected are the following:

- `x` : The trace of a variable.
- `first` : The fraction of series at the beginning of the trace.
- `last` : The fraction of series at the end to be compared with the section at the beginning.
- `intervals` : The number of segments.

Plotting the output displays the scores in series, making it is easy to
see departures from the standard normal assumption.

A second convergence diagnostic provided by PyMC is the Gelman-Rubin
statistic [Gelman and Rubin (1992)](http://projecteuclid.org/DPubS?service=UI&version=1.0&verb=Display&handle=euclid.ss/1177011136). This diagnostic uses multiple chains to
check for lack of convergence, and is based on the notion that if
multiple chains have converged, by definition they should appear very
similar to one another; if not, one or more of the chains has failed to
converge.

The Gelman-Rubin diagnostic uses an analysis of variance approach to
assessing convergence. That is, it calculates both the between-chain
varaince (B) and within-chain varaince (W), and assesses whether they
are different enough to worry about convergence. Assuming $m$ chains,
each of length $n$, quantities are calculated by:

$$\begin{align}B &= \frac{n}{m-1} \sum_{j=1}^m (\bar{\theta}_{.j} - \bar{\theta}_{..})^2 \\
W &= \frac{1}{m} \sum_{j=1}^m \left[ \frac{1}{n-1} \sum_{i=1}^n (\theta_{ij} - \bar{\theta}_{.j})^2 \right]
\end{align}$$

for each scalar estimand $\theta$. Using these values, an estimate of
the marginal posterior variance of $\theta$ can be calculated:

$$\hat{\text{Var}}(\theta | y) = \frac{n-1}{n} W + \frac{1}{n} B$$

Assuming $\theta$ was initialized to arbitrary starting points in each
chain, this quantity will overestimate the true marginal posterior
variance. At the same time, $W$ will tend to underestimate the
within-chain variance early in the sampling run. However, in the limit
as $n \rightarrow
\infty$, both quantities will converge to the true variance of $\theta$.
In light of this, the Gelman-Rubin statistic monitors convergence using
the ratio:

$$\hat{R} = \sqrt{\frac{\hat{\text{Var}}(\theta | y)}{W}}$$

This is called the potential scale reduction, since it is an estimate of
the potential reduction in the scale of $\theta$ as the number of
simulations tends to infinity. In practice, we look for values of
$\hat{R}$ close to one (say, less than 1.1) to be confident that a
particular estimand has converged. In PyMC, the function
`gelman_rubin` will calculate $\hat{R}$ for each stochastic node in
the passed model:

```python
from pymc3 import gelman_rubin

gelman_rubin(bioassay_trace)
```

    {'alpha': 1.0010012076568837, 'beta': 1.0008905531296362}

For the best results, each chain should be initialized to highly
dispersed starting values for each stochastic node.

By default, when calling the `forestplot` function using nodes with
multiple chains, the $\hat{R}$ values will be plotted alongside the
posterior intervals.

```python
from pymc3 import forestplot

forestplot(bioassay_trace)
```

## Autocorrelation

In general, samples drawn from MCMC algorithms will be autocorrelated. This is not a big deal, other than the fact that autocorrelated chains may require longer sampling in order to adequately characterize posterior quantities of interest. The calculation of autocorrelation is performed for each lag $i=1,2,\ldots,k$ (the correlation at lag 0 is, of course, 1) by:

$$\hat{\rho}_i = 1 - \frac{V_i}{2\hat{\text{Var}}(\theta | y)}$$

where $\hat{\text{Var}}(\theta | y)$ is the same estimated variance as calculated for the Gelman-Rubin statistic, and $V_i$ is the variogram at lag $i$ for $\theta$:

$$\text{V}_i = \frac{1}{m(n-i)}\sum_{j=1}^m \sum_{k=i+1}^n (\theta_{jk} - \theta_{j(k-i)})^2$$

This autocorrelation can be visualized using the `autocorrplot` function in PyMC3:

```python
from pymc3 import autocorrplot

autocorrplot(tr);
```

The effective sample size is estimated using the partial sum:

$$\hat{n}_{eff} = \frac{mn}{1 + 2\sum_{i=1}^T \hat{\rho}_i}$$

where $T$ is the first odd integer such that $\hat{\rho}_{T+1} + \hat{\rho}_{T+2}$ is negative.

```python
from pymc3 import effective_n

effective_n(bioassay_trace)
```

    {'alpha': 4277.0, 'beta': 3872.0}

It is tempting to want to **thin** the chain to eliminate the autocorrelation (\emph{e.g} taking every 20th sample from the traces above), but this is a waste of time. Since thinning deliberately throws out the majority of the samples, no efficiency is gained; you ultimately require more samples to achive a particular desired sample size.

## Goodness of Fit

Checking for model convergence is only the first step in the evaluation of MCMC model outputs. It is possible for an entirely unsuitable model to converge, so additional steps are needed to ensure that the estimated model adequately fits the data. One intuitive way of evaluating model fit is to compare model predictions with the observations used to fit the model. In other words, the fitted model can be used to simulate data, and the distribution of the simulated data should resemble the distribution of the actual data.

Fortunately, simulating data from the model is a natural component of the Bayesian modelling framework. Recall, from the discussion on imputation of missing data, the posterior predictive distribution:

$$p(\tilde{y}|y) = \int p(\tilde{y}|\theta) f(\theta|y) d\theta$$

Here, $\tilde{y}$ represents some hypothetical new data that would be expected, taking into account the posterior uncertainty in the model parameters. Sampling from the posterior predictive distribution is easy in PyMC3. The `sample_ppc` function draws samples from the posterior predictive distribution using only the trace from the MCMC run. It uses the parameter values drawn during sampling to simulate data from the model.

Consider the `gelman_bioassay` example, where deaths are modeled as a binomial random variable for which the probability of death is a logit-linear function of the dose of a particular drug. We can draw 1000 samples of the data from the posterior predictive distribution via:

```python
from pymc3 import sample_ppc

with bioassay_model:
    gof_trace = sample_ppc(bioassay_trace, samples=1000)
```

    100%|██████████| 1000/1000 [00:00<00:00, 2576.14it/s]

The degree to which simulated data correspond to observations can be evaluated in at least two ways. First, these quantities can simply be compared visually. This allows for a qualitative comparison of model-based replicates and observations. If there is poor fit, the true value of the data may appear in the tails of the histogram of replicated data, while a good fit will tend to show the true data in high-probability regions of the posterior predictive distribution.

```python
fig, axes = plt.subplots(1,4, figsize=(16, 5))
for i,x in enumerate(gof_trace['deaths'].T):
    axes[i].hist(x, alpha=0.6)
    axes[i].axvline(obs_deaths[i], color='red', linestyle='dashed')
```

The second is to calculate the percentile of the observed data relative to the simulated. This yields a "Bayesian p-value" that characterizes how extreme the observed data is relative to data generated from the model:

```python
from scipy.stats import percentileofscore

[percentileofscore(sim, obs) for obs,sim in zip(obs_deaths, gof_trace['deaths'].T)]
```

    [47.449999999999996, 59.650000000000006, 45.9, 51.949999999999996]

## References

Gelman, A., & Rubin, D. B. (1992). Inference from iterative simulation using multiple sequences. Statistical Science. A Review Journal of the Institute of Mathematical Statistics, 457–472.

Geweke, J., Berger, J. O., & Dawid, A. P. (1992). Evaluating the accuracy of sampling-based approaches to the calculation of posterior moments. In Bayesian Statistics 4.

Brooks, S. P., Catchpole, E. A., & Morgan, B. J. T. (2000). Bayesian Animal Survival Estimation. Statistical Science. A Review Journal of the Institute of Mathematical Statistics, 15(4), 357–376. doi:10.1214/ss/1177010123

Gelman, A., Meng, X., & Stern, H. (1996). Posterior predicitive assessment of model fitness via realized discrepencies with discussion. Statistica Sinica, 6, 733–807.

Raftery, A., & Lewis, S. (1992). One long run with diagnostics: Implementation strategies for Markov chain Monte Carlo. Statistical Science. A Review Journal of the Institute of Mathematical Statistics, 7, 493–497.


# QFT Kata Workbook

**What is this workbook?**
A workbook is a collection of problems, accompanied by solutions to them.
The explanations focus on the logical steps required to solve a problem; they illustrate the concepts that need to be applied to come up with a solution to the problem, explaining the mathematical steps required.

Note that a workbook should not be the primary source of knowledge on the subject matter; it assumes that you've already read a tutorial or a textbook and that you are now seeking to improve your problem-solving skills. You should attempt solving the tasks of the respective kata first, and turn to the workbook only if stuck. While a textbook emphasizes knowledge acquisition, a workbook emphasizes skill acquisition.

This workbook describes the solutions to the problems offered in the [QFT kata](./QFT.ipynb).
Since the tasks are offered as programming problems, the explanations also cover some elements of Q# that might be non-obvious for a first-time user.

**What you should know for this workbook**

You should be familiar with the following concepts before tackling the QFT kata (and this workbook):
1. Basic linear algebra
2. The concept of qubit and multi-qubit systems
3. Single-qubit and multi-qubit quantum gates
4. The discrete Fourier transform (DFT)

## Part I. Implementing Quantum Fourier Transform

This sequence of tasks uses the implementation of QFT described in Nielsen & Chuang.
All numbers in this kata use big endian encoding: most significant bit of the number
 is stored in the first (leftmost) bit/qubit.

 What this means is that we can represent a state in reversed bit-string format:

 $|x\rangle = x_1x_2..x_n$ = $x_12^{n-1} + x_22^{n-2}+...x_n2^{0}$

 We can also make use of the notation for decimals in binary format:

 $0.x_1x_2...x_n = \frac{x_1}{2^{-1}}+ \frac{x_2}{2^{-2}}+...\frac{x_n}{2^{n}}$

### Task 1.1. 1-qubit QFT

**Input:**

  A qubit in state $|\psi\rangle = x_0 |0\rangle + x_1 |1\rangle$.

**Goal:**

Apply QFT to this qubit, i.e., transform it to a state
$\frac{1}{\sqrt{2}} \big((x_0 + x_1) |0\rangle + (x_0 - x_1) |1\rangle\big)$.

In other words, transform a basis state $|j\rangle$ into a state $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot \frac{j}{2}}|1\rangle\big)$ .

### Solution

First, notice that we are dealing with a *single* qubit, which has $N = 2$ possible basis states.

The transformation of a state $x_0 |0\rangle + x_1 |1\rangle$ to a state $\frac{1}{\sqrt{2}} \big((x_0 + x_1) |0\rangle + (x_0 - x_1) |1\rangle\big)$ is actually just the expression for the transformation of an arbitrary state in the Z basis (a.k.a. the computational basis) to one in the X basis.

For a starting state $|0\rangle$, $x_0=1$ and $x_1=0$, so the new state will be  $\frac{1}{\sqrt{2}} \big(|0\rangle +|1\rangle\big) = |+\rangle$.
Similarly, for a starting state $|1\rangle$, $x_0=0$ and $x_1=1$, so the new state will be  $\frac{1}{\sqrt{2}} \big(|0\rangle -|1\rangle\big) = |-\rangle$.

We already know a gate that will do this: the Hadamard gate!

```qsharp
%kata T11_OneQubitQFT

operation OneQubitQFT (q : Qubit) : Unit is Adj+Ctl {
    H(q);
}
```

[Return to task 1.1 of the QFT kata.](./QFT.ipynb#Task-1.1.-1-qubit-QFT)

### Task 1.2. Rotation gate

**Inputs:**

  1. A qubit in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$.

  2. An integer k $\geq$ 0.

**Goal:**

Change the state of the qubit to $\alpha |0\rangle + \beta \cdot e^{\frac{2\pi i}{2^{k}}} |1\rangle$.

> Be careful about not introducing an extra global phase!
This is going to be important in the later tasks.

### Solution

Let's have a look at the gates available under the [Microsoft.Quantum.Intrinsic namespace](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.intrinsic).

To avoid adding the extra global phase, we have to use a gate that does not modify the $|0\rangle$ state, and only impacts $|1\rangle$: that is, $U|\psi\rangle = \alpha|0\rangle + \beta \cdot U|1\rangle$.

The [R1Frac gate](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.intrinsic.r1frac) does exactly that:

$$R1Frac(n,k) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\pi n/2^{k}} \end{bmatrix} $$

We specify $n=2$ to get the transformation required:

$$R1Frac(2,k) = \begin{bmatrix} 1 & 0 \\ 0 & e^{2\pi i/2^{k}} \end{bmatrix} $$

```qsharp
%kata T12_Rotation

operation Rotation (q : Qubit, k : Int) : Unit is Adj+Ctl {
    R1Frac(2, k, q);
}
```

[Return to task 1.2 of the QFT kata.](./QFT.ipynb#Task-1.2.-Rotation-gate)

### Task 1.3. Prepare binary fraction exponent (classical input)

**Inputs:**

  1. A qubit in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$.

  2. An array of $n$ bits $[j_1, j_2, ..., j_n]$, stored as `Int[]` ($ j_k \in \{0,1\}$).

**Goal:**

Change the state of the qubit to $\alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle$,
where $0.j_1 j_2 ... j_n$ is a binary fraction, similar to decimal fractions:

$$0.j_1 j_2 ... j_n = j_1 \cdot \frac{1}{2^1} + j_2 \cdot \frac{1}{2^2} + ... j_n \cdot \frac{1}{2^n}$$

### Solution

Since we can express the exponent of a sum as a product of exponents ($e^{a+b} = e^a \cdot e^b$), we can implement the required transformation as a sequence of rotation gates from task 1.2 with increasing **k**.

Each of the individual rotations will use the bit $j_k$ to decide whether to apply the rotation, and the index of that bit $k$ to define the rotation angle.
Note that we only want to apply the rotation if the bit $j_k$ is not zero.
This means that the gate we apply for each $k$ will be

$$U_k = \begin{cases}
I, j_k=0 \\
R1Frac(2,k), j_k=1
\end{cases}$$

Recall that

$$ R1Frac(2,k)\big( \alpha |0\rangle + \beta |1\rangle\big) = \alpha |0\rangle + \beta \cdot e^{2\pi i/2^{k}} |1\rangle = \alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0. \underset{k-1}{\underbrace{0\dots0}} 1} |1\rangle $$

This means that we can write the overall effect of the gate $U_k$ for each $k$ as

$$U_k\big( \alpha |0\rangle + \beta |1\rangle\big) = \alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0. \underset{k-1}{\underbrace{0\dots0}} j_k} |1\rangle$$

As we iterate, the resulting state will get closer and closer to the required one:

    <col width=100>
    <col width=500>

        Step $k$
        State after step $k$

        $1$
        $$ \alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1} |1\rangle $$

        $2$
        $$\alpha |0\rangle + \beta \cdot e^{2\pi i j_1 \cdot 2^{-1} + 2\pi i j_2 \cdot 2^{-2}} |1\rangle = \alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1j_2} |1\rangle $$

        ...
        ...

        $n$
        $$\alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1j_2 \dots j_n} |1\rangle $$

```qsharp
%kata T13_BinaryFractionClassical

operation BinaryFractionClassical (q : Qubit, j : Int[]) : Unit is Adj+Ctl {
    for ind in 0 .. Length(j) - 1 {
        if (j[ind] == 1) {
            Rotation(q, ind + 1);
        }
    }
}
```

[Return to task 1.3 of the QFT kata.](./QFT.ipynb#Task-1.3.-Prepare-binary-fraction-exponent-(classical-input))

### Task 1.4. Prepare binary fraction exponent (quantum input)

**Inputs:**

  1. A qubit in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$.

  2. A register of $n$ qubits in state $|j_1 j_2 ... j_n\rangle$.

**Goal:**

Change the state of the input
from $(\alpha |0\rangle + \beta |1\rangle) \otimes |j_1 j_2 ... j_n\rangle$
to $(\alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle) \otimes |j_1 j_2 ... j_n\rangle$,

where $0.j_1 j_2 ... j_n$ is a binary fraction corresponding to the basis state $j_1 j_2 ... j_n$ of the register.

> The register of qubits can be in superposition as well;
the behavior in this case is defined by behavior on the basis states and the linearity of unitary transformations.

### Solution
From the goals section, you can see that the register of *n* qubits remains unchanged, while the qubit acquires a conditional phase that depends on the value of these bits.

Because the register is now a quantum register and can be in a superposition of states, we cannot measure the register and then conditionally apply the operation depending on whether we measure a 1 or a 0. So, we have to convert our **Rotation** operation into a controlled operation. We can do this with the [**Controlled** functor](https://docs.microsoft.com/en-us/azure/quantum/user-guide/language/expressions/functorapplication#functor-application).

The **Rotation** operation in 1.2 was defined to be both adjointable (Adj) and controllable (Ctl) so all we need to do is go through the qubits in the register and apply the rotation operation conditionally.

A successive application of a *Controlled Rotation (controlqubit, (qubit, k))* operation will effect a controlled rotation operation *n* times, and transform the $\alpha |0\rangle + \beta |1\rangle$ state to $(\alpha |0\rangle + \beta \cdot e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle)$, while leaving the register $|j_1 j_2 ... j_n\rangle$ unchanged.

```qsharp
%kata T14_BinaryFractionQuantum

operation BinaryFractionQuantum (q : Qubit, jRegister : Qubit[]) : Unit is Adj+Ctl {
    for ind in 0 .. Length(jRegister) - 1 {
        Controlled Rotation([jRegister[ind]], (q, ind + 1));
    }
}
```

### Task 1.5. Prepare binary fraction exponent in place (quantum input)

**Input:**

 A register of $n$ qubits in state $|j_1 j_2 ... j_n \rangle$.

**Goal:**

Change the state of the register
from $|j_1\rangle \otimes |j_2 ... j_n\rangle$
to $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle \otimes |j_2 ... j_n\rangle\big)$.

> The register of qubits can be in superposition as well;
the behavior in this case is defined by behavior on the basis states and the linearity of unitary transformations.

  Need a hint? Click here
  This task is very similar to task 1.4, but the digit $j_1$ is encoded in-place, using task 1.1.

### Solution

First, note that a Hadamard gate applied to a single qubit,  $H|j_1\rangle$ will give either
$\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle)$ or $\frac{1}{\sqrt{2}}(|0\rangle-|1\rangle)$ depending on the state of $j_1$. This operation can also be written as
$$H|j_1\rangle= \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot \frac{j_1}{2}}|1\rangle\big)= \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1}|1\rangle\big)$$

Try it out on paper!

So if the starting register state is $|j_1 j_2 ... j_n \rangle$, application of a Hadamard gate to the first bit will result in:

$$\big(H_1\otimes I_n \big) \big(|j_1\rangle \otimes |j_2 ... j_n\rangle\big)= \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1}|1\rangle\big) \otimes |j_2 ... j_n\rangle $$

After this, it is just a matter of repreating the loop that will add the remaining phase terms via the controlled rotation operator.

After a repeat of the operations sequence in 1.4, applied to the first register qubit $j_1$, the qubit register's state will be $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle \otimes |j_2 ... j_n\rangle\big)$

```qsharp
%kata T15_BinaryFractionQuantumInPlace

operation BinaryFractionQuantumInPlace (register : Qubit[]) : Unit is Adj+Ctl {
     OneQubitQFT(register[0]);

        for ind in 1 .. Length(register) - 1 {
            Controlled Rotation([register[ind]], (register[0] , ind + 1));

        }

}
```

### Task 1.6. Reverse the order of qubits

**Input:**

 A register of $n$ qubits in state $|x_1 x_2 ... x_n \rangle$.

**Goal:**

Reverse the order of qubits, i.e., convert the state of the register to $|x_n ... x_2 x_1\rangle$.

### Solution

Have a look at what the circuit looks like drawn out:

$x_1$ ----------------

$x_2$ ----------------

$x_3$ ----------------

...

$x_n$ ----------------

This will read as $|x_1 x_2 ... x_n \rangle$. To reverse the order of the qubits, SWAP operations (denoted with X) must be implemented, switching out $x_1$ with $x_n$, $x_2$ with $x_n-1$ and so on:

$x_1$ ----------------X---  ...

$x_2$ ---------------|---X  ...

$x_3$ ---------------|---|  ...

...

$x_{n-2}$ ------------|---|  ...

$x_{n-1}$ ------------|---X  ...

$x_n$ --------------X---  ...

After N/2 of these swaps, the states will now be:

$x_n$ ----------------

$x_{n-1}$ -------------

$x_{n-2}$ -------------

...

$x_1$ ----------------

And the state of the register will be $|x_n ... x_2 x_1\rangle$.

```qsharp
%kata T16_ReverseRegister

operation ReverseRegister (register : Qubit[]) : Unit is Adj+Ctl {
    let N = Length(register);
    for ind in 0.. N/2-1{
    SWAP(register[ind], register[N - 1 - ind]);

    }
}
```

### Task 1.7. Quantum Fourier transform

**Input:**

 A register of $n$ qubits in state $|j_1 j_2 ... j_n \rangle$.

**Goal:**

Apply quantum Fourier transform to the input register, i.e., transform it to a state

$$\frac{1}{\sqrt{2^{n}}} \sum_{k=0}^{n-1} e^{2\pi i \cdot \frac{jk}{2^{n}}} |k\rangle = $$
$$\begin{align}= &\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_n} |1\rangle\big) \otimes \\
\otimes &\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_{n-1} j_n} |1\rangle\big) \otimes ... \\
\otimes &\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_{n-1} j_n} |1\rangle\big)\end{align}$$

> The register of qubits can be in superposition as well;
the behavior in this case is defined by behavior on the basis states and the linearity of unitary transformations.

> You can do this with a library call, but we recommend
implementing the algorithm yourself for learning purposes, using the previous tasks.

  Need a hint? Click here
Consider preparing a different state first and transforming it to the goal state:

$\frac{1}{\sqrt{2}} (|0\rangle + exp(2\pi i * 0.j_1 j_2 ... j_{n-1} j_n) |1\rangle) \otimes ... \otimes$
$\otimes \frac{1}{\sqrt{2}} (|0\rangle + exp(2\pi i * 0.j_{n-1} j_n) |1\rangle) \otimes$
$\otimes \frac{1}{\sqrt{2}} (|0\rangle + exp(2\pi i * 0.j_n) |1\rangle) \otimes$

### Solution

You've already found a way to prepare a binary fraction exponent in place from exercise 1.5: the state $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_n} |1\rangle\big) \otimes |j_2 ... j_n\rangle$ can be created by first applying the Hadamard gate to qubit $j_1$, then applying a succession of controlled rotations using qubits $j_k$ with increasing values of k, such that an additional phase term all the way up to $e^{2\pi i \cdot j_n/2^{n}}$ is added on each time.

A Hadamard gate on $j_1$, followed by successive application of the rotation gate as explained above covers the creation of the $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_{n-1} j_n} |1\rangle\big)\otimes |j_2 ... j_n\rangle$ state.

To create the remaining states, work backwards from this state. You can always reverse the order of the states using the operator in 1.6.

The next state in the sequence will be $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_2 ... j_{n-1} j_n} |1\rangle\big)$.

This is done by applying a Hadamard gate to qubit $j_2$ and then using qubits $j_3$ to $j_n$ to apply $n-1$ controlled rotation gates with $k=2^2$ to $k=2^{n-1}$ to $j_2$. After this operation, the total system state will be:

$ \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_{n-1} j_n} |1\rangle\big)\otimes
\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_2 ... j_{n-1} j_n} |1\rangle\big) \otimes |j_3 ... j_n\rangle$

This creates a pattern. For any qubit $j_k, k= 1 .. n$ :

1. Apply a Hadamard gate to $j_k$
2. Apply the controlled rotation operator $n-k$ times to qubit $j_k$, using qubits $j_k+1$ through $j_n$ as the control, with a phase corresponding to $2^2$ through $2^{n-k+1}$

These two steps, applied for each qubit, create an operator $U$. The effect of the operation $U$ is to create a total system state:

$$ U|j_1 j_2 ... j_n \rangle= \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_{n-1} j_n} |1\rangle\big)\otimes \\ \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_2 ... j_{n-1} j_n} |1\rangle\big)\otimes \\ \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_3 ... j_{n-1} j_n} |1\rangle\big)\otimes ... \\
\otimes \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_n} |1\rangle\big)
$$

The system state can then be flipped using the code created in 1.6.

A circuit summary of this process:

```qsharp
%kata T17_QuantumFourierTransform

operation QuantumFourierTransform (register : Qubit[]) : Unit is Adj+Ctl {
    let n = Length(register);
        for i in 0 .. n - 1 {
            BinaryFractionQuantumInPlace(register[i ...]);
        }
        ReverseRegister(register);
}
```

### Task 1.8. Inverse QFT

**Input:**

 A register of $n$ qubits in state $|j_1 j_2 ... j_n \rangle$.

**Goal:**

Apply inverse QFT to the input register, i.e., transform it to a state
$\frac{1}{\sqrt{2^{n}}} \sum_{k=0}^{n-1} e^{-2\pi i \cdot \frac{jk}{2^{n}}} |k\rangle$.

  Need a hint? Click here
Inverse QFT is literally the inverse transformation of QFT.
Do you know a quantum way to express this?

### Solution

Have a look at the documentation on the [Adjoint Functor](https://docs.microsoft.com/en-us/azure/quantum/user-guide/language/expressions/functorapplication#functor-application).
For an operation that defines a unitary transformation $U$ of the quantum state, Adjoint $U$ accesses the implementation of $U^{\dagger}$.

```qsharp
%kata T18_InverseQFT

operation InverseQFT (register : Qubit[]) : Unit is Adj+Ctl {
    Adjoint QuantumFourierTransform(register);
}
```

## Part II. Using the Quantum Fourier Transform

This section offers you tasks on state preparation and state analysis
that can be solved using QFT (or inverse QFT). It is possible to solve them
without QFT, but we recommend that you to try and come up with a QFT-based solution.

### Task 2.1. Prepare an equal superposition of all basis states

**Input:**

A register of $n$ qubits in state $|0...0\rangle$.

**Goal:**

Prepare an equal superposition of all basis vectors from $|0...0\rangle$ to $|1...1\rangle$
(i.e., state $\frac{1}{\sqrt{2^{n}}} \big(|0...0\rangle + ... + |1...1\rangle\big)$.

### Solution

Since the QFT of the state $|0...0\rangle$ is simply the superposition of all basis states, this exercise can be solved by simply calling the QFT library function.
Note that the library QFT function operates on a big endian register, so the input register is converted to a big endian type register first. All this does is ensure that the qubit with index 0 encodes the highest bit of an unsigned integer, becoming the state $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1 j_2 ... j_{n-1} j_n} |1\rangle\big)$ or $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot \frac{\textbf{j}}{2^{n}}}|1\rangle\big)$

```qsharp
%kata T21_PrepareEqualSuperposition

operation PrepareEqualSuperposition (register : Qubit[]) : Unit is Adj+Ctl {
    QFT(BigEndian(register));
}
```

### Task 2.2. Prepare a periodic state

**Inputs:**

  1. A register of $n$ qubits in state $|0...0\rangle$.

  2. An integer frequency F ($0 \leq F \leq 2^{n}-1$).

**Goal:**

Prepare a periodic state with frequency F on this register:

$$\frac{1}{\sqrt{2^{n}}} \sum_k e^{2\pi i \cdot \frac{Fk}{2^{n}}} |k\rangle$$

> For example, for $n = 2$ and $F = 1$ the goal state is $\frac{1}{2}\big(|0\rangle + i|1\rangle - |2\rangle - i|3\rangle\big)$.

> If you're using `DumpMachine` to debug your solution,
remember that this kata uses big endian encoding of states,
while `DumpMachine` uses little endian encoding.

  Need a hint? Click here
Which basis state can be mapped to this state using QFT?

### Solution

Recall the definition of the QFT:

For a state $|j\rangle$ that is one of the basis of states $\{|0\rangle, |1\rangle...|N-1\rangle\}$  $\epsilon$ $|k\rangle$, the QFT can be defined as

$$ QFT|j\rangle= \frac{1}{\sqrt{2^n}} \sum_{k=1}^{2^{n}-1} e^{2\pi i j k/2^{n}} |k\rangle$$

You can see that $F = j$. To apply the QFT an create a periodic state, the integer value of $F$ must be converted into a binary representation in the register. You can check that this is the case by writing $F=|j_1j_2\rangle = |01\rangle$ and working through the QFT operation to produce the state $\frac{1}{2}\big(|0\rangle + i|1\rangle - |2\rangle - i|3\rangle\big)$.

  Click to view steps

For $n=2$, $N=4$ and so:

 $$QFT |1\rangle = \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_2} |1\rangle\big) \otimes \frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0.j_1j_2} |1\rangle\big) = \\
 |00\rangle+ |01\rangle e^{2\pi i \cdot 0.j_1j_2}+ |10\rangle e^{2\pi i \cdot 0.j_2}+ |11\rangle e^{2\pi i \cdot (0.j_1j_2 + 0.j_2)}$$

 Evaluating for $j_1=0$ and $j_2=1$ will give $\frac{1}{2}\big(|0\rangle + i|1\rangle - |2\rangle - i|3\rangle\big)$.

In creating the code, make sure that you pay attention to whether library functions use little or big endian!  In particular, IntAsBoolArray uses little endian encoding, so the result must be reversed.

```qsharp
%kata T22_PreparePeriodicState

operation PreparePeriodicState (register : Qubit[], F : Int) : Unit is Adj+Ctl {

        let bitsBE = Reversed(IntAsBoolArray(F, Length(register)));
        ApplyPauliFromBitString(PauliX, true, bitsBE, register);

        QFT(BigEndian(register));
}
```

### Task 2.3. Prepare a periodic state with alternating $1$ and $-1$ amplitudes

**Input:**

A register of $n$ qubits in state $|0...0\rangle$.

**Goal:**

Prepare a periodic state with alternating $1$ and $-1$ amplitudes of basis states:

$$\frac{1}{\sqrt{2^{n}}} \big(|0\rangle - |1\rangle + |2\rangle - |3\rangle + ... - |2^{n}-1\rangle\big)$$

> For example, for $n = 2$ the goal state is $\frac{1}{2} \big(|0\rangle - |1\rangle + |2\rangle - |3\rangle\big)$.

  Need a hint? Click here
Which basis state can be mapped to this state using QFT?  Which frequency would allow you to use task 2.2 here?

### Solution

Because there is no imagimary part to this state, it is clear that the value of exponents for an arbitrary state  $\frac{1}{\sqrt{2}} \big(|0\rangle + e^{2\pi i \cdot 0...j_n} |1\rangle\big)$ must be either $2\pi i$ for a coefficient of 1 or $\pi i$ for a coefficient of -1. This means that only $j_1$ can have a value of 1: all other register qubits must be in the $|0...0\rangle$ state.

To prepare this state, you can apply an X gate to the first qubit of the register, either as X(qubit[0]) or as X(Head(register)). The *Head* function returns the first element of an array.

```qsharp
%kata T23_PrepareAlternatingState

operation PrepareAlternatingState (register : Qubit[]) : Unit is Adj+Ctl {
    X(Head(register));

    QFT(BigEndian(register));

}

```

### Task 2.4. Prepare an equal superposition of all even basis states

**Input:**

A register of $n$ qubits in state $|0...0\rangle$.

**Goal:**

Prepare an equal superposition of all even basis vectors:
$\frac{1}{\sqrt{2^{n-1}}} \big(|0\rangle + |2\rangle + ... + |2^{n}-2\rangle\big)$.

  Need a hint? Click here
Which superposition of two basis states can be mapped to this state using QFT?
    Use the solutions to tasks 2.1 and 2.3 to figure out the answer.

### Solution
You can see that the superposition of two states will give this result. If the periodic state of 2.3 is added to a simple QFT of the $|0....0\rangle$ given in 2.1,

$$\frac{1}{\sqrt{2^{n-1}}} \big(|0\rangle + |2\rangle + ... + |2^{n}-2\rangle\big)= \frac{1}{\sqrt{2^{n}}} \big(|0\rangle - |1\rangle + |2\rangle - |3\rangle + ... - |2^{n}-1\rangle\big) + \frac{1}{\sqrt{2^{n}}} \big(|0\rangle +|1\rangle + |2\rangle + |3\rangle + ... + |2^{n}-1\rangle\big)$$

The periodic state of 2.3 is created by applying the QFT to the $|100...0\rangle$ state, while the state of 2.1 is created by applying the QFT to the $|000...0\rangle$ state. To do both of these simultaneously and observe the cancellation effect, we can use the power of superpositions. Namely. we can create a superposition of the form:

$$ \frac{1}{\sqrt{2^{n}}} \big(|0\rangle + |1\rangle\big) \otimes |000...0\rangle $$
This can be done by applying a Hadamard gate to the first qubit in the register.
Then, by applying the QFT to the entire register, you will get:

$$ QFT\big(\frac{1}{\sqrt{2^{n}}} (|0\rangle + |1\rangle) \otimes |000...0\rangle\big)= QFT\big(\frac{1}{\sqrt{2^{n}}} \big(|000...0\rangle\big)|\big)+ QFT\big(\frac{1}{\sqrt{2^{n}}} \big(|100...0\rangle\big)\big)$$

This results in the desired end state.

```qsharp
%kata T24_PrepareEqualSuperpositionOfEvenStates

operation PrepareEqualSuperpositionOfEvenStates (register : Qubit[]) : Unit is Adj+Ctl {

    H(Head(register));
    QFT(BigEndian(register));
}

```

### Task 2.5. Prepare a square-wave signal

**Input:**

A register of $n\geq2$ qubits in state $|0...0\rangle$.

**Goal:**

Prepare a periodic state with alternating $1, 1, -1, -1$ amplitudes of basis states:
$$\frac{1}{\sqrt{2^{n}}} \big(|0\rangle + |1\rangle - |2\rangle - |3\rangle + ... - |2^{n}-2\rangle - |2^{n}-1\rangle\big)$$

  Need a hint? Click here
Which superposition of two basis states can be mapped to this state using QFT?
    Remember that sum of two complex amplitudes can be a real number if their imaginary parts cancel out.

### Solution

Writing out the state that we want to achieve will give:

$$ |0...00\rangle + |0...01\rangle - |0...10\rangle - |0...11\rangle $$
Written as a tensor product of single-qubit states, this gives:

$$\frac{1}{\sqrt{2^{n}}} \big(|0\rangle + |1\rangle) \otimes (|0\rangle + |1\rangle) ... \otimes (|0\rangle - |1\rangle) \otimes (|0\rangle + |1\rangle)$$

When expanded, this will give the state required: alternating positive and negative states with a period of 2.

This superposition state can be created by setting only $j_2$ to be 1, then applying the QFT. This will add the required phase of -1 on the second-to-last bit, but will also add a phase term of $i$ on the last bit.
$$QFT(|010...0\rangle) = \frac{1}{\sqrt{2^{n}}}  \big(|0\rangle + |1\rangle) \otimes (|0\rangle + |1\rangle) ... \otimes (|0\rangle - |1\rangle) \otimes (|0\rangle + i|1\rangle)$$
To counter this last phase term, we need cancel out that last $i$ in some form. This can be achieved by creating a superposition state $\frac{e^{(-i\pi/4)}}{\sqrt{2^{n}}} |010...0\rangle + \frac{e^{(i\pi/4)}}{\sqrt{2^{n}}} |110...0\rangle$.

Then,

$$ QFT\big(\frac{e^{(-i\pi/4)}}{\sqrt{2^{n}}} |010...0\rangle + \frac{e^{(i\pi/4)}}{\sqrt{2^{n}}} |110...0\rangle\big) = \\
\frac{(1-i)}{\sqrt{2^{n}}} \big( |0\rangle + |1\rangle) \otimes (|0\rangle + |1\rangle)... |0\rangle - |1\rangle) \otimes (|0\rangle + i|1\rangle)  \big) + \\
\frac{(1+i)}{\sqrt{2^{n}}} \big( |0\rangle + |1\rangle) \otimes (|0\rangle + |1\rangle)... |0\rangle - |1\rangle) \otimes (|0\rangle - i|1\rangle)  \big) $$

 This gives a final state of $$\frac{1}{\sqrt{2^{n}}} \big(|0\rangle + |1\rangle) \otimes (|0\rangle + |1\rangle) ... \otimes (|0\rangle - |1\rangle) \otimes (|0\rangle + |1\rangle)$$

 Creating the required superposition state is fairly easy; it can be done by using the [T gate](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.intrinsic.t) and its adjoint.
 You can use the within...apply statements to avoid writing down the X() operator twice.

```qsharp
%kata T25_PrepareSquareWaveSignal

operation PrepareSquareWaveSignal (register : Qubit[]) : Unit is Adj+Ctl {
        X(register[1]);
        // |010...0⟩
        H(register[0]);
        // |010...0⟩ + |110...0⟩
        T(register[0]);
        within { X(register[0]); }
        apply { Adjoint T(register[0]); }

        QFT(BigEndian(register));
}
```

### Task 2.6. Get the frequency of a signal

**Input:**

A register of $n\geq2$ qubits in state
$\frac{1}{\sqrt{2^{n}}} \sum_k e^{2\pi i \cdot \frac{Fk}{2^{n}}} |k\rangle$, $0\leq F\leq 2^{n}-1$.

**Goal:**

Return the frequency F of the "signal" encoded in this state.

## Solution

The original Fourier transform will give a time-domain signal's decomposition in the frequency domain, and the inverse Fourier transform will give a frequency-domain signal's time-domain representation.

Keeping this in mind, if we already have the QFT of a state *F* in standard format, the value of *F* can be found by applying the inverse QFT to this register and then measuring the qubits. Since the [QFT](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.canon.qft) operation is adjointable, this is very easy to do.

Note that the iQFT takes a big endian register as input, but the  **[ResultArrayAsInt](https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.convert.resultarrayasint)** function produces a non-negative integer in little endian format.

```qsharp
 %kata T26_Frequency

operation Frequency (register : Qubit[]) : Int {
         Adjoint QFT(BigEndian(register));
        let bitsBE = MultiM(register);
        return ResultArrayAsInt(Reversed(bitsBE));
        }

```

```qsharp

```


# [Shor's Algorithm Code used from IBM's Qiskit Tutorials](https://qiskit.org/textbook/ch-algorithms/shor.html)
## Code adapted for Cirq

```python
import unittest

import cirq
from cirq.ops import H, X, I
import random
import matplotlib.pyplot as plt
from math import gcd
import numpy as np
from numpy.random import randint

#import hypothesis.strategies as st
#from hypothesis import given, settings

from fractions import Fraction
from math import gcd # greatest common divisor
```

```python
# Specify variables
n_count = 8  # number of counting qubits
a = 7
N = 15
```

```python
class aMod15Gate(cirq.Gate):
    def __init__(self, a, power):
        super(aMod15Gate, self)
        self.a = a
        self.power = power

    def _num_qubits_(self):
        return 4

    def _decompose_(self, qubits):
        q0, q1, q2, q3 = qubits
        if self.a not in [2,7,8,11,13]:
            raise ValueError("'a' must be 2,7,8,11 or 13")
        for iteration in range(self.power):
            if self.a in [2,13]:
                yield cirq.SWAP(q0,q1)
                yield cirq.SWAP(q1,q2)
                yield cirq.SWAP(q2,q3)
            if self.a in [7,8]:
                yield cirq.SWAP(q2,q3)
                yield cirq.SWAP(q1,q2)
                yield cirq.SWAP(q0,q1)
            if self.a == 11:
                yield cirq.SWAP(q1,q3)
                yield cirq.SWAP(q0,q2)
            if self.a in [7,11,13]:
                yield cirq.X(q0)
                yield cirq.X(q1)
                yield cirq.X(q2)
                yield cirq.X(q3)

    def _circuit_diagram_info_(self, args):
        return "a mod 15"
```

```python
def qft_dagger_cirq(qc, qubits, n):
    for qubit in range(n//2):
        qc.append(cirq.SWAP(qubits[qubit], qubits[n-qubit-1]))
    for j in range(n):
        for m in range(j):
            qc.append((cirq.CZ**(-1/2**(j-m)))(qubits[m],qubits[j]))
        qc.append(cirq.H(qubits[j]))
```

```python
def qpe_amod15(a):
    n_count = 8
    qubits = cirq.LineQubit.range(4+n_count)
    qc = cirq.Circuit()
    for q in range(n_count):
        #print(q)
        qc.append(cirq.H(qubits[q]))     # Initialize counting qubits in state |+>
    qc.append(cirq.X(qubits[3+n_count])) # And auxiliary register in state |1>
    for q in range(n_count): # Do controlled-U operations
        qc.append(aMod15Gate(a, 2**q).on(qubits[8],qubits[9],qubits[10],qubits[11]).controlled_by(qubits[q]))
    qft_dagger_cirq(qc, qubits[:n_count], n_count) # Do inverse-QF
    qc.append(cirq.measure(*qubits[:8], key='m'))
    # Simulate Results
    simulator = cirq.Simulator()
    results = simulator.run(qc , repetitions =1)
    readings = np.array2string(results.measurements['m'][0], separator='')[1:-1][::-1]
    phase = int(readings,2)/(2**n_count)
    return phase
```

```python
phase = qpe_amod15(a) # Phase = s/r
Fraction(phase).limit_denominator(15)
```

    Fraction(3, 4)

```python
frac = Fraction(phase).limit_denominator(15)
s, r = frac.numerator, frac.denominator
print(r)
```

    4

```python
guesses = [gcd(a**(r//2)-1, N), gcd(a**(r//2)+1, N)]
print(guesses)
```

    [3, 5]

```python
def find_factor(coprime):
    a = coprime
    attempt = 0
    factors = []
    for i in range(100):
        attempt += 1
        #print("\nAttempt %i:" % attempt)
        phase = qpe_amod15(a) # Phase = s/r
        frac = Fraction(phase).limit_denominator(N) # Denominator should (hopefully!) tell us r
        r = frac.denominator
        #print("Result: r = %i" % r)
        if phase != 0:
            # Guesses for factors are gcd(x^{r/2} ±1 , 15)
            guesses = [gcd(a**(r//2)-1, N), gcd(a**(r//2)+1, N)]
            #print("Guessed Factors: %i and %i" % (guesses[0], guesses[1]))
            for guess in guesses:
                if guess not in [1,N] and (N % guess) == 0: # Check to see if guess is a factor
            #        print("*** Non-trivial factor found: %i ***" % guess)
                    factors += [guess]
            return factors

find_factor(7)
```

    [3]

```python
import fractions
import math
import random

import numpy as np
import sympy
from typing import Callable, List, Optional, Sequence, Union

import cirq
```

```python
"""Function to compute the elements of Z_n."""
def multiplicative_group(n: int) -> List[int]:
    """Returns the multiplicative group modulo n.

    Args:
        n: Modulus of the multiplicative group.
    """
    print("multiplicative group")
    assert n > 1
    group = [1]
    for x in range(2, n):
        if math.gcd(x, n) == 1:
            group.append(x)
    return group
```

```python
"""Defines the modular exponential operation used in Shor's algorithm."""
class ModularExp(cirq.ArithmeticOperation):
    """Quantum modular exponentiation.

    This class represents the unitary which multiplies base raised to exponent
    into the target modulo the given modulus. More precisely, it represents the
    unitary V which computes modular exponentiation x**e mod n:

        V|y⟩|e⟩ = |y * x**e mod n⟩ |e⟩     0 <= y < n
        V|y⟩|e⟩ = |y⟩ |e⟩                  n <= y

    where y is the target register, e is the exponent register, x is the base
    and n is the modulus. Consequently,

        V|y⟩|e⟩ = (U**e|y)|e⟩

    where U is the unitary defined as

        U|y⟩ = |y * x mod n⟩      0 <= y < n
        U|y⟩ = |y⟩                n <= y
    """
    def __init__(
        self,
        target: Sequence[cirq.Qid],
        exponent: Union[int, Sequence[cirq.Qid]],
        base: int,
        modulus: int
    ) -> None:
        if len(target) < modulus.bit_length():
            raise ValueError(f'Register with {len(target)} qubits is too small '
                             f'for modulus {modulus}')
        self.target = target
        self.exponent = exponent
        self.base = base
        self.modulus = modulus

    def registers(self) -> Sequence[Union[int, Sequence[cirq.Qid]]]:
        return self.target, self.exponent, self.base, self.modulus

    def with_registers(
            self,
            *new_registers: Union[int, Sequence['cirq.Qid']],
    ) -> cirq.ArithmeticOperation:
        if len(new_registers) != 4:
            raise ValueError(f'Expected 4 registers (target, exponent, base, '
                             f'modulus), but got {len(new_registers)}')
        target, exponent, base, modulus = new_registers
        if not isinstance(target, Sequence):
            raise ValueError(
                f'Target must be a qubit register, got {type(target)}')
        if not isinstance(base, int):
            raise ValueError(
                f'Base must be a classical constant, got {type(base)}')
        if not isinstance(modulus, int):
            raise ValueError(
                f'Modulus must be a classical constant, got {type(modulus)}')
        return ModularExp(target, exponent, base, modulus)

    def apply(self, *register_values: int) -> int:
        assert len(register_values) == 4
        target, exponent, base, modulus = register_values
        if target >= modulus:
            return target
        return (target * base**exponent) % modulus

    def _circuit_diagram_info_(
            self,
            args: cirq.CircuitDiagramInfoArgs,
    ) -> cirq.CircuitDiagramInfo:
        assert args.known_qubits is not None
        wire_symbols: List[str] = []
        t, e = 0, 0
        for qubit in args.known_qubits:
            if qubit in self.target:
                if t == 0:
                    if isinstance(self.exponent, Sequence):
                        e_str = 'e'
                    else:
                        e_str = str(self.exponent)
                    wire_symbols.append(
                        f'ModularExp(t*{self.base}**{e_str} % {self.modulus})')
                else:
                    wire_symbols.append('t' + str(t))
                t += 1
            if isinstance(self.exponent, Sequence) and qubit in self.exponent:
                wire_symbols.append('e' + str(e))
                e += 1
        return cirq.CircuitDiagramInfo(wire_symbols=tuple(wire_symbols))
```

```python
def make_order_finding_circuit(x: int, n: int) -> cirq.Circuit:
    """Returns quantum circuit which computes the order of x modulo n.

    The circuit uses Quantum Phase Estimation to compute an eigenvalue of
    the unitary

        U|y⟩ = |y * x mod n⟩      0 <= y < n
        U|y⟩ = |y⟩                n <= y

    Args:
        x: positive integer whose order modulo n is to be found
        n: modulus relative to which the order of x is to be found

    Returns:
        Quantum circuit for finding the order of x modulo n
    """
    L = n.bit_length()
    target = cirq.LineQubit.range(L)
    exponent = cirq.LineQubit.range(L, 3 * L + 3)
    return cirq.Circuit(
        cirq.X(target[L - 1]),
        cirq.H.on_each(*exponent),
        ModularExp(target, exponent, x, n),
        cirq.qft(*exponent, inverse=True),
        cirq.measure(*exponent, key='exponent'),
    )
```

```python
def process_measurement(result: cirq.Result, x: int, n: int) -> Optional[int]:
    """Interprets the output of the order finding circuit.

    Specifically, it determines s/r such that exp(2πis/r) is an eigenvalue
    of the unitary

        U|y⟩ = |xy mod n⟩  0 <= y < n
        U|y⟩ = |y⟩         n <= y

    then computes r (by continued fractions) if possible, and returns it.

    Args:
        result: result obtained by sampling the output of the
            circuit built by make_order_finding_circuit

    Returns:
        r, the order of x modulo n or None.
    """
    print("process measurement")
    # Read the output integer of the exponent register.
    exponent_as_integer = result.data["exponent"][0]
    exponent_num_bits = result.measurements["exponent"].shape[1]
    eigenphase = float(exponent_as_integer / 2**exponent_num_bits)

    # Run the continued fractions algorithm to determine f = s / r.
    f = fractions.Fraction.from_float(eigenphase).limit_denominator(n)

    # If the numerator is zero, the order finder failed.
    if f.numerator == 0:
        return None

    # Else, return the denominator if it is valid.
    r = f.denominator
    if x**r % n != 1:
        return None
    return r
```

```python
def quantum_order_finder(x: int, n: int) -> Optional[int]:
    """Computes smallest positive r such that x**r mod n == 1.

    Args:
        x: integer whose order is to be computed, must be greater than one
           and belong to the multiplicative group of integers modulo n (which
           consists of positive integers relatively prime to n),
        n: modulus of the multiplicative group.
    """
    print("quantum order finder")
    # Check that the integer x is a valid element of the multiplicative group
    # modulo n.
    if x < 2 or n <= x or math.gcd(x, n) > 1:
        raise ValueError(f'Invalid x={x} for modulus n={n}.')

    # Create the order finding circuit.
    circuit = make_order_finding_circuit(x, n)
    print(circuit)

    # Sample from the order finding circuit.
    measurement = cirq.sample(circuit)

    # Return the processed measurement result.
    return process_measurement(measurement, x, n)
```

```python
def find_factor_of_prime_power(n: int) -> Optional[int]:
    """Returns non-trivial factor of n if n is a prime power, else None."""
    print("factor of prime power")
    for k in range(2, math.floor(math.log2(n)) + 1):
        c = math.pow(n, 1 / k)
        c1 = math.floor(c)
        if c1**k == n:
            return c1
        c2 = math.ceil(c)
        if c2**k == n:
            return c2
    return None

def find_factor(
    n: int,
    order_finder: Callable[[int, int], Optional[int]] = quantum_order_finder,
    max_attempts: int = 30
) -> Optional[int]:
    """Returns a non-trivial factor of composite integer n.

    Args:
        n: Integer to factor.
        order_finder: Function for finding the order of elements of the
            multiplicative group of integers modulo n.
        max_attempts: number of random x's to try, also an upper limit
            on the number of order_finder invocations.

    Returns:
        Non-trivial factor of n or None if no such factor was found.
        Factor k of n is trivial if it is 1 or n.
    """
    # If the number is prime, there are no non-trivial factors.
    if sympy.isprime(n):
        print("n is prime!")
        return None

    # If the number is even, two is a non-trivial factor.
    #if n % 2 == 0:
    #    return 2

    # If n is a prime power, we can find a non-trivial factor efficiently.
    #c = find_factor_of_prime_power(n)
    #if c is not None:
    #    return c

    print("find factor")

    for _ in range(max_attempts):
        print("loop")
        # Choose a random number between 2 and n - 1.
        x = random.randint(2, n - 1)
        print("x " + str(x))

        # Most likely x and n will be relatively prime.
        c = math.gcd(x, n)
        print("c " + str(c))

        # If x and n are not relatively prime, we got lucky and found
        # a non-trivial factor.
        if 1 < c < n:
            return c

        # Compute the order r of x modulo n using the order finder.
        r = order_finder(x, n)
        print(r)

        # If the order finder failed, try again.
        if r is None:
            continue

        # If the order r is even, try again.
        if r % 2 != 0:
            continue

        # Compute the non-trivial factor.
        y = x**(r // 2) % n
        assert 1 < y < n
        c = math.gcd(y - 1, n)
        if 1 < c < n:
            return c

    print(f"Failed to find a non-trivial factor in {max_attempts} attempts.")
    return None
```

```python
find_factor(15)
```

    find factor
    loop
    x 7
    c 1
    quantum order finder
    0: ────────ModularExp(t*7**e % 15)────────────────────────────
               │
    1: ────────t1─────────────────────────────────────────────────
               │
    2: ────────t2─────────────────────────────────────────────────
               │
    3: ────X───t3─────────────────────────────────────────────────
               │
    4: ────H───e0────────────────────────qft^-1───M('exponent')───
               │                         │        │
    5: ────H───e1────────────────────────#2───────M───────────────
               │                         │        │
    6: ────H───e2────────────────────────#3───────M───────────────
               │                         │        │
    7: ────H───e3────────────────────────#4───────M───────────────
               │                         │        │
    8: ────H───e4────────────────────────#5───────M───────────────
               │                         │        │
    9: ────H───e5────────────────────────#6───────M───────────────
               │                         │        │
    10: ───H───e6────────────────────────#7───────M───────────────
               │                         │        │
    11: ───H───e7────────────────────────#8───────M───────────────
               │                         │        │
    12: ───H───e8────────────────────────#9───────M───────────────
               │                         │        │
    13: ───H───e9────────────────────────#10──────M───────────────
               │                         │        │
    14: ───H───e10───────────────────────#11──────M───────────────
    process measurement
    4

    3

```python

```

Shor's Algorithm/CirQ  Code/Shor's Algorithm CirQ.ipynb


# Lecture 7: Gambler's Ruin & Random Variables

## Stat 110, Prof. Joe Blitzstein, Harvard University

----

## Gambler's Ruin

Two gamblers $A$ and $B$ are successively playing a game until one wins all the money and the other is ruined (goes bankrupt). There is a sequence of rounds, with a one dollar bet each time. The rounds are independent events. Let $p = P(\text{A wins a certain round})$ and the inverse is $q = 1 - p$, by convention.

_What is the probability that $A$ wins the entire game?_

Some clarifications:

* there is a total of $N$ dollars in this closed system game (no other money comes into play)
* $A$ starts with $i$ dollars, $B$ starts with $N-i$ dollars

But where do we begin to solve this problem?

### Random Walk

A [random walk](https://en.wikipedia.org/wiki/Random_walk) between two points on a number line is very similar to the Gambler's Ruin.

How many rounds could a game last? Is it possible for the game to continue on to infinity?

Well, notice how this has a very nice __recursive nature__. If $A$ loses a round, the game can be seen as starting anew at $i-1$, and if he wins, the game would start anew at $i+1$. It is the same problem, but with a different starting condition.

### Strategy

Conditioning on the _first step_ is called __first step analysis__.

Let $P_i = P(\text{A wins the entire game|A starts with i dollars})$. Then from the Law of Total Probability, we have:

\begin{align}
    P_i &= p P_{i+1} + q P_{i-1} \text{, } & &\text{where }1 \lt i \lt N-1 \\
    & & & P_0 = 0 \\
    & & & P_N = 1 \\
\end{align}

See how this is a recursive equation? This is called a [__difference equation__](http://mathworld.wolfram.com/DifferenceEquation.html), which is a discrete analog of a differential equation.

### Solving the Difference Equation

\begin{align}
    P_i &= p P_{i+1} + q P_{i-1} & & \\
    \\
    \\
    P_i &= x^i & &\text{see what happens when we guess with a power} \\
    \Rightarrow x^i &= p x^{i+1} + q x^{i-1} \\
    0 &= p x^2 - x + q & &\text{factoring out } x^{i-1} \text{, we are left with a quadratic}\\
    \\
    x &= \frac{1 \pm \sqrt{1-4pq}}{2p} & &\text{solving with the quadratic formula} \\
      &= \frac{1 \pm \sqrt{(2p-1)^2}}{2p} & &\text{since }1-4pq = 1-4p(1-p) = 4p^2 - 4p - 1 = (2p -1)^2 \\
      &= \frac{1 \pm (2p-1)}{2p} \\
      &\in \left\{1, \frac{q}{p} \right\} \\
    \\
    \\
    P_i &= A(1)^i + B\left(\frac{q}{p}\right)^i & &\text{if } p \neq q~~~~ \text{(general solution for difference equation)} \\
    \Rightarrow B &= -A & &\text{from }P_0 = 1\\
    \Rightarrow 1 &= A(1)^N + B\left(\frac{q}{p}\right)^N & &\text{from } P_N = 1\\
    &= A(1)^N - A\left(\frac{q}{p}\right)^N \\
    &= A\left(1-\frac{q}{p}\right)^N \\
    \\
    \\
    \therefore P_i &=
        \begin{cases}
            \frac{1-\left(\frac{q}{p}\right)^i}{1-\left(\frac{q}{p}\right)^N}  & \quad \text{ if } p \neq q \\
            \frac{i}{N}  & \quad \text{ if } p = q \\
        \end{cases}
\end{align}

### Example calculations of $P_i$ over a range of $N$

Assuming an unfair game where $p=0.49$, $q=0.51$:

```python
import math

def gamblers_ruin(i, p, q, N):
    if math.isclose(p,q):
        return i/N
    else:
        return ((1 - (q/p)**i)) / (1 - (q/p)**N)

p = 0.49
q = 1.0 - p

N = 20
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))

N = 100
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))

N = 200
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))
```

    With N=20 and p=0.49, probability that A wins all is 0.40
    With N=100 and p=0.49, probability that A wins all is 0.12
    With N=200 and p=0.49, probability that A wins all is 0.02

And assuming a fair game where $p = q = 0.5$:

```python
p = 0.5
q = 1.0 - p

N = 20
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))

N = 100
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))

N = 200
i = N/2
print("With N={} and p={}, probability that A wins all is {:.2f}".format(N, p, gamblers_ruin(i, p, q, N)))
```

    With N=20 and p=0.5, probability that A wins all is 0.50
    With N=100 and p=0.5, probability that A wins all is 0.50
    With N=200 and p=0.5, probability that A wins all is 0.50

#### Could the game ever continue forever on to infinity?

Recall that we have the following solution to the difference equation for the Gambler's Ruin game:

\begin{align}
    P_i &=
        \begin{cases}
            \frac{1-\left(\frac{q}{p}\right)^i}{1-\left(\frac{q}{p}\right)^N}  & \quad \text{ if } p \neq q \\
            \frac{i}{N}  & \quad \text{ if } p = q \\
        \end{cases}
\end{align}

The only time you'd think the game could continue on to infinity is when $p=q$. But

\begin{align}
    P(\Omega) &= 1\\
    &= P(\text{A wins all}) + P(\text{B wins all}) \\
    &= P_i + P_{N-i} \\
    &= \frac{i}{N} + \frac{N-i}{N}
\end{align}

The above implies that aside from the case where $A$ wins all, and the case where $B$ wins all, there is no other event in $\Omega$ to consider, hence the game can never continue on to infinity without either side winning.

This also means that unless $p=q$, you __will__ lose your money, and the only question is how fast will you lose it.

----

# Random Variables

Consider these statements:

\begin{align}
    x + 2 &= 9 \\
    x &= 7
\end{align}

_What is a variable?_
* variable $x$ is a symbol that we use as a substitute for an arbitrary _constant_ value.

_What is a __random__ variable?_

* This is not a _variable_, but a __function from the sample space $S$ to $\mathbb{R}$__.
* It is a "summary" of an aspect of the experiment (this is where the randomness comes from)

Here are a few of the most useful _discrete random variables_.

----

## Bernoulli Distribution

### Description
A probability distribution of a random variable that takes the value 1 in the case of a success with probability $p$; or takes the value 0 in case of a failure with probability $1-p$.

A most common example would be a coin toss, where heads might be considered a success with probability $p=0.5$ if the coin is a fair.

A random variable $x$ has the Bernoulli distribution if
- $x \in \{0, 1\}$
- $P(x=1) = p$
- $P(x=0) = 1-p$

### Notation

$X \sim \operatorname{Bern}(p)$

### Parameters

$0 < p < 1 \text{, } p \in \mathbb{R}$

### Probability mass function

The probability mass function $P(x)$ over possible values $x$

\begin{align}
  P(x) =
  \begin{cases}
    1-p, &\text{ if } x = 0 \\
    p, &\text{ if } x = 1 \\
  \end{cases} \\
\end{align}

### Expected value

\begin{align}
  \mathbb{E}(X) &= 1 P(X=1) + 0 P(X=0) \\
                &= p
\end{align}

### Special case: Indicator random variables (r.v.)

\begin{align}
  &X =
  \begin{cases}
    1, &\text{ if event A occurs} \\
    0, &\text{ otherwise} \\
  \end{cases} \\
  \\
  \\
  \Rightarrow &\mathbb{E}(X) = P(A)
\end{align}

## Binomial Distribution

### Description

The distribution of the number of successes in $n$ independent Bernoulli trials $\operatorname{Bern}(p)$, where the chance of success $p$ is the same for all trials $n$.

Another case might be a string of indicator random variables.

### Notation

$X \sim \operatorname{Bin}(n, p)$

### Parameters

- $n \in \mathbb{N}$
- $p \in [0,1]$

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

from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,
                               AutoMinorLocator)
from scipy.stats import binom

%matplotlib inline

plt.xkcd()

_, ax = plt.subplots(figsize=(12,8))

# a few Binomial parameters n and p
pop_sizes = [240, 120, 60, 24]
p_values = [0.2, 0.3, 0.4, 0.8]
params = list(zip(pop_sizes, p_values))

# colorblind-safe, qualitative color scheme
colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c']

for i,(n,p) in enumerate(params):
    x = np.arange(binom.ppf(0.01, n, p), binom.ppf(0.99, n, p))
    y = binom.pmf(x, n, p)
    ax.plot(x, y, 'o', ms=8, color=colors[i], label='n={}, p={}'.format(n,p))
    ax.vlines(x, 0, y, color=colors[i], alpha=0.3)

# legend styling
legend = ax.legend()
for label in legend.get_texts():
    label.set_fontsize('large')
for label in legend.get_lines():
    label.set_linewidth(1.5)

# y-axis
ax.set_ylim([0.0, 0.23])
ax.set_ylabel(r'$P(x=k)$')

# x-axis
ax.set_xlim([10, 65])
ax.set_xlabel('# of successes k out of n Bernoulli trials')

# x-axis tick formatting
majorLocator = MultipleLocator(5)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1)
ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(majorFormatter)

ax.grid(color='grey', linestyle='-', linewidth=0.3)

plt.suptitle(r'Binomial PMF: $P(x=k) = \binom{n}{k} p^k (1-p)^{n-k}$')

plt.show()
```

### Probability mass function

\begin{align}
    P(x=k) &= \binom{n}{k} p^k (1-p)^{n-k}
\end{align}

### Expected value

\begin{align}
    \mathbb{E}(X) = np
\end{align}

## In parting...

Now think about this true statement as we move on to Lecture 3:

\begin{align}
    X &\sim \operatorname{Bin}(n,p) \text{, } Y \sim \operatorname{Bin}(m,p) \\
    \rightarrow X+Y &\sim \operatorname{Bin}(n+m, p)
\end{align}

----

## Appendix A: Solving $P_i$ when $p=q$ using l'Hopital's Rule

To solve for for the case where $p = q$, let $x = \frac{q}{p}$.

\begin{align}
    lim_{x \rightarrow 1}{\frac{1-x^i}{1-x^N}} &= lim_{x\rightarrow1}{\frac{ix^{i-1}}{Nx^{N-1}}} &\text{ by l'Hopital's Rule} \\
    &= \frac{i}{N}
\end{align}

----

View [Lecture 7: Gambler's Ruin and Random Variables | Statistics 110](http://bit.ly/2PmMbdV) on YouTube.