    Run in Google Colab

    View source on GitHub

# Explainable Recommendations

While the main objective of a recommender system is to identify the items to be recommended to a user, providing explanations to accompany the recommendations would be more persuasive as well as engender trust and transparency.  There are different types of explanations.  In this tutorial, we explore explainable recommendation approaches that rely on user product aspect-level sentiment for modeling explanations.

## 1. Setup

```
!pip install --quiet cornac==1.6.1
```

         |████████████████████████████████| 10.1MB 3.1MB/s

```
import os
import sys
from collections import defaultdict

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

import cornac
from cornac.utils import cache
from cornac.datasets import amazon_toy
from cornac.eval_methods import RatioSplit
from cornac.data import Reader, SentimentModality
from cornac.models import EFM, MTER, NMF, BPR

print(f"System version: {sys.version}")
print(f"Cornac version: {cornac.__version__}")

SEED = 42
VERBOSE = False
```

    System version: 3.6.9 (default, Apr 18 2020, 01:56:04)
    [GCC 8.4.0]
    Cornac version: 1.6.1

## 2. Aspect-Level Sentiments
To model fine-grained product aspect-ratings. Several works rely on sentiment analysis to extract aspect sentiment from product reviews. In other words, each review is now a list of aspect sentiments. Along with product rating, we also have aspect sentiments expressed in users' reviews. Here, we work with Toys and Games dataset, a sub-category of [Amazon reviews](http://jmcauley.ucsd.edu/data/amazon/).

Below are some examples of aspect-level sentiments that have been extracted from users' reviews of items.

```
sentiment = amazon_toy.load_sentiment()
samples = sentiment[:10]
pd.DataFrame.from_dict({
  "user": [tup[0] for tup in samples],
  "item": [tup[1] for tup in samples],
  "aspect-level sentiment": [tup[2] for tup in samples]
})
```

      user
      item
      aspect-level sentiment

      0
      A012468118FTQAINEI0OQ
      B00005BZM6
      [(paint, great, 1)]

      1
      A012468118FTQAINEI0OQ
      B001HA9JOA
      [(game, great, 1), (money, worth, 1)]

      2
      A012468118FTQAINEI0OQ
      B002BY2BVE
      [(paint, fun, 1), (item, well, 0)]

      3
      A012468118FTQAINEI0OQ
      B007U7M0LI
      [(price, sturdy, 1)]

      4
      A012468118FTQAINEI0OQ
      B00804BCO6
      [(gift, great, 1)]

      5
      A0182108CPDLPRCXQUZQ
      B002IUNLLK
      [(toy, best, 1), (heavy, cool, 1)]

      6
      A0182108CPDLPRCXQUZQ
      B007WYU7R8
      [(toy, great, 1)]

      7
      A0182108CPDLPRCXQUZQ
      B00ABY8WVO
      [(toy, love, 1)]

      8
      A0182108CPDLPRCXQUZQ
      B00AFP86KG
      [(toy, love, 1)]

      9
      A0182108CPDLPRCXQUZQ
      B00BJT861Q
      [(figure, well, 1), (toy, well, 1)]

```
# Load rating and sentiment information
reader = Reader(min_item_freq=20)
rating = amazon_toy.load_feedback(reader)

# Use Sentiment Modality for aspect-level sentiment data
sentiment_modality = SentimentModality(data=sentiment)

rs = RatioSplit(
    data=rating,
    test_size=0.2,
    exclude_unknowns=True,
    sentiment=sentiment_modality,
    verbose=VERBOSE,
    seed=SEED,
)
print("Total number of aspects:", rs.sentiment.num_aspects)
print("Total number of opinions:", rs.sentiment.num_opinions)

id_aspect_map = {v:k for k, v in rs.sentiment.aspect_id_map.items()}
id_opinion_map = {v:k for k, v in rs.sentiment.opinion_id_map.items()}
```

    rating_threshold = 1.0
    exclude_unknowns = True
    ---
    Training data:
    Number of users = 17433
    Number of items = 2180
    Number of ratings = 66758
    Max rating = 5.0
    Min rating = 1.0
    Global mean = 4.3
    ---
    Test data:
    Number of users = 8955
    Number of items = 2167
    Number of ratings = 15777
    Number of unknown users = 0
    Number of unknown items = 0
    ---
    Total users = 17433
    Total items = 2180
    Total number of aspects: 429
    Total number of opinions: 2604

## 3. Explicit Factor Model (EFM)

EFM model extends Non-negative Matrix Factorization (NMF) with the additional information from **aspect-level sentiments**.  The objective is to learn user, item, and aspect latent factors to explain user-item ratings, users' interest in certain aspects of the items, as well as the quality of items according to those aspects.  In a nutshell, EFM factorizes three matrices: *rating matrix*, *user-aspect attention matrix*, and *item-aspect quality matrix*.  Let's take a look at what the later two matrices are.

```
efm = EFM()
efm.train_set = rs.train_set
_, X, Y = efm._build_matrices(rs.train_set)
```

### User-Aspect Attention Matrix

Let $\mathcal{F} = \{f_1, f_2, \dots, f_F\}$ be the set of aspects (e.g., screen, earphone).

Let $\mathbf{X} \in \mathbb{R}^{N \times F}$ be a sparse aspect matrix for $N$ users and $F$ aspects, whereby each element $x_{if} \in \mathbf{X}$  indicates the degree of **attention** by user $i$ on aspect $f$, defined as follows:

\begin{equation}
x_{if} = \
\begin{cases}
0, & \text{if user $i$ never mentions aspect $f$} \\
1 + (N-1)\left(\frac{2}{1+\exp(-t_{if})}-1\right), & \text{otherwise}
\end{cases}
\end{equation}

where $N=5$ is the highest rating score, $t_{if}$ is the frequency of user $i$ mentions aspect $f$ across all her reviews.

For illustration purpose, we show a small matrix $\mathbf{X}$ of 5 users and 5 aspects below.

```
n_users = 5
n_aspects = 5
pd.DataFrame(
  data=X[:n_users, :n_aspects].A,
  index=[f"User {u + 1}" for u in np.arange(n_users)],
  columns=[f"{id_aspect_map[i]}" for i in np.arange(n_aspects)]
)
```

      paint
      game
      money
      item
      price

      User 1
      0.0
      0.000000
      0.0
      0.000000
      2.848469

      User 2
      0.0
      0.000000
      0.0
      0.000000
      0.000000

      User 3
      0.0
      4.620593
      0.0
      2.848469
      4.620593

      User 4
      0.0
      4.856110
      0.0
      0.000000
      0.000000

      User 5
      0.0
      0.000000
      0.0
      0.000000
      0.000000

In the example below, we can see that *User 4* finds the aspect *game* important, whereas *User 3* is concerned with *game* as well as *price*.

### Item-Aspect Quality Matrix

Let $\mathbf{Y} \in \mathbb{R}^{M \times F}$ be a sparse aspect matrix for $M$ items and $F$ aspects, whereby $y_{jf} \in \mathbf{Y}$ indicates the **quality** of item $j$ on aspect $f$, defined as follows:

\begin{equation}
y_{jf} = \
\begin{cases}
0, & \text{if item $j$ was never reviewed on aspect $f$} \\
1 + (N - 1) \left( \frac{1}{1+\exp(-s_{jf})} \right), & \text{otherwise}
\end{cases}
\end{equation}

where $s_{jf}$ is the sum of sentiment values with which item $j$ has been mentioned with regards to aspect $f$ across all its reviews.

We show a small matrix $Y$ of 5 items and 5 aspects below:

```
n_items = 5
n_aspects = 5
pd.DataFrame(
  data=Y[:n_items, :n_aspects].A,
  index=[f"Item {u + 1}" for u in np.arange(n_items)],
  columns=[f"{id_aspect_map[i]}" for i in np.arange(n_aspects)]
)
```

      paint
      game
      money
      item
      price

      Item 1
      3.924234
      0.000000
      3.000000
      0.000000
      3.924234

      Item 2
      0.000000
      0.000000
      0.000000
      4.523188
      4.523188

      Item 3
      0.000000
      4.523188
      0.000000
      0.000000
      0.000000

      Item 4
      0.000000
      0.000000
      0.000000
      0.000000
      3.924234

      Item 5
      3.924234
      0.000000
      3.924234
      4.523188
      4.523188

We see from the example above that *Item 3* has a positive quality in the aspect *game*, whereas *Item 5* has positive quality on the other 4 aspects.

### Optimization

As these matrices are sparse, for prediction, EFM jointly factorizes $X$ and $Y$ matrices along with rating matrix $R$.   Learning the latent factors can be done via minimizing the following loss function:

\begin{align}
&\mathcal{L}(\mathbf{U_1, U_2, V, H_1, H_2} | \lambda_x, \lambda_y, \lambda_u, \lambda_h, \lambda_v) = ||\mathbf{U_1} \mathbf{U_2}^T + \mathbf{H_1} \mathbf{H_2}^T - \mathbf{R}||_F^2 + \lambda_x ||\mathbf{U_1} \mathbf{V}^T - \mathbf{X}||_F^2 + \lambda_y ||\mathbf{U_2} \mathbf{V}^T - \mathbf{Y}||_F^2 + \lambda_u(||\mathbf{U_1}||_F^2+||\mathbf{U_2}||_F^2) + \lambda_h(||\mathbf{H_1}||_F^2+||\mathbf{H_2}||_F^2) + \lambda_v ||\mathbf{V}||_F^2 \\
&\text{such that: }  \forall_{i, k} u_{ik} \ge 0, \forall_{j, k} v_{jk} \ge 0
\end{align}

The can be solved as a constrained optimization problem.

Let's conduct an experiment with EFM model and compare with NMF as a baseline.

```
efm = EFM(
  num_explicit_factors=40,
  num_latent_factors=60,
  num_most_cared_aspects=15,
  rating_scale=5.0,
  alpha=0.85,
  lambda_x=1,
  lambda_y=1,
  lambda_u=0.01,
  lambda_h=0.01,
  lambda_v=0.01,
  max_iter=100,
  verbose=VERBOSE,
  seed=SEED,
)

# compare to baseline NMF
nmf = NMF(k=100, max_iter=100, verbose=VERBOSE, seed=SEED)

eval_metrics = [
  cornac.metrics.RMSE(),
  cornac.metrics.NDCG(k=50),
  cornac.metrics.AUC()
]

cornac.Experiment(
  eval_method=rs, models=[nmf, efm], metrics=eval_metrics
).run()
```

    TEST:
    ...
        |   RMSE |    AUC | NDCG@50 | Train (s) | Test (s)
    --- + ------ + ------ + ------- + --------- + --------
    NMF | 0.8027 | 0.5418 |  0.0093 |    4.6980 |   8.4513
    EFM | 0.7315 | 0.5536 |  0.0105 |   11.2728 |  12.2117

### Refining Ranking Prediction

With EFM model, you can refine the recommendation after training by experimenting with different values of:
*   `num_most_cared_aspects` ($k$): integer, value range $\in[0, 429]$ as we have $429$ aspects in total
*   `alpha` $\in [0,1]$

These parameters will affect ranking performance of the EFM model, as the ranking score is predicted as follow:

$$
ranking\_score = \alpha \cdot \frac{\sum_{c \in C_i}{\hat{x}_{if}\cdot\hat{y}_{jf}}}{k \cdot N} + (1-\alpha)\cdot\hat{r}_{ij}
$$

```
alpha = 0.9 # alpha value in range [0,1]
num_most_cared_aspects = 100

eval_metrics = [
  cornac.metrics.NDCG(k=50),
  cornac.metrics.AUC()
]

cornac.Experiment(
  eval_method=rs,
  models=[
    EFM(
      alpha=alpha,
      num_most_cared_aspects=num_most_cared_aspects,
      init_params={'U1': efm.U1, 'U2': efm.U2, 'H1': efm.H1, 'H2': efm.H2, 'V': efm.V},
      trainable=False,
      verbose=VERBOSE,
      seed=SEED
    )
  ],
  metrics=eval_metrics
).run()
```

    TEST:
    ...
        |    AUC | NDCG@50 | Train (s) | Test (s)
    --- + ------ + ------- + --------- + --------
    EFM | 0.5549 |  0.0107 |    0.0008 |  15.3882

### Recommendation Explanation with EFM

Given a user and an item, EFM model is able of predicting **user's attention scores** as well as **item's quality scores** regarding the aspects.  Those scores with the corresponding aspects will be the explanation on why a user *likes* or *dislikes* an item.

Let's take a look at an example below. Feel free to explore other users and items!

```
UIDX = 1
IIDX = 4
num_top_cared_aspects = 10

id_aspect_map = {v:k for k, v in rs.sentiment.aspect_id_map.items()}

predicted_user_aspect_scores = np.dot(efm.U1[UIDX], efm.V.T)
predicted_item_aspect_scores = np.dot(efm.U2[IIDX], efm.V.T)

top_cared_aspect_ids = (-predicted_user_aspect_scores).argsort()[:num_top_cared_aspects]
top_cared_aspects = [id_aspect_map[aid] for aid in top_cared_aspect_ids]
pd.DataFrame.from_dict({
  "aspect": top_cared_aspects,
  "user_aspect_attention_score": predicted_user_aspect_scores[top_cared_aspect_ids],
  "item_aspect_quality_score": predicted_item_aspect_scores[top_cared_aspect_ids]
})

```

      aspect
      user_aspect_attention_score
      item_aspect_quality_score

      0
      toy
      4.061583
      4.679122

      1
      pieces
      3.610668
      3.844794

      2
      game
      3.571418
      4.211304

      3
      furby
      3.571004
      4.787230

      4
      doll
      3.480351
      4.326976

      5
      quality
      3.468948
      3.952711

      6
      really
      3.368340
      4.586975

      7
      gift
      3.364289
      4.410737

      8
      also
      3.360865
      4.076249

      9
      puzzle
      3.294265
      4.726530

EFM takes an aspect with the **highest score** in `item_aspect_quality_score` as the well-performing aspect, and an aspect with the **lowest score** in `item_aspect_quality_score` as the poorly-performing aspect. See example explanations in their templates below.

```
perform_well_aspect = top_cared_aspects[predicted_item_aspect_scores[top_cared_aspect_ids].argmax()]
perform_poorly_aspect = top_cared_aspects[predicted_item_aspect_scores[top_cared_aspect_ids].argmin()]

explanation = \
f"You might interested in [{perform_well_aspect}], on which this product perform well. \n\
You might interested in [{perform_poorly_aspect}], on which this product perform poorly."
print("EFM explanation:")
print(explanation)
```

    EFM explanation:
    You might interested in [furby], on which this product perform well.
    You might interested in [pieces], on which this product perform poorly.

## 4. Multi-Task Explainable Recommendation (MTER)

MTER model extends the concept of exploiting information from *Aspect-Level Sentiments* with tensor factorization (using Tucker Decomposition).   The model takes in the input of three tensors.  Let's go through each of them and see how they are constructed.

### Tensor\#1: User by Item by Aspect ($\mathbf{X}$)

Let $\mathbf{R} \in \mathbb{R}^{N \times M}$ be a sparse rating matrix of $N$ users and $M$ items.

Let $\mathbf{X} \in \mathbb{R}_{+}^{N \times M \times F}$ be a 3-dimensional tensor, each element $x_{ijf}$ indicates a relationship between user $i$, item $j$, and aspect $f$:

\begin{equation}
x_{ijf} = \
\begin{cases}
0, & \text{if aspect $f$ has not been mentioned by user $i$ about item $j$} \\
1 + (N-1)\left(\frac{1}{1+\exp(-s_{ijf})}\right), & \text{otherwise}
\end{cases}
\end{equation}

where $s_{ijf}$ is the sum of sentiment values with which item $j$ has been mentioned by user $i$ with regards to aspect $f$.

We can extend $\mathbf{X}$ into $\mathbf{\tilde{X}}$ with the rating matrix $\mathbf{R}$ as the last slice or the $(F + 1)^{\mathrm{th}}$ aspect (i.e., $\tilde{x}_{ij(F+1)} = r_{ij}$).

### Tensor\#2: User by Aspect by Opinion ($\mathbf{Y}^{U}$)

Let $\mathbf{Y}^{U} \in \mathbb{R}_{+}^{N \times F \times O}$ be a 3-dimensional tensor, each element $y^U_{ifo}$ indicates a relationship between user $i$, aspect $f$, and opinion $o$:

\begin{equation}
y^U_{ifo} = \
\begin{cases}
0, & \text{if user $i$ has not been used opinion $o$ to describe aspect $f$ positively} \\
1 + (N-1)\left(\frac{1}{1+\exp(-t_{ifo})}\right), & \text{otherwise}
\end{cases}
\end{equation}

where $t_{ifo}$ is the frequency with which user $i$ employs opinion $o$ to describe aspect $f$ positively across all her reviews.

### Tensor\#3: Item by Aspect by Opinion ($\mathbf{Y}^{I}$)

Let $\mathbf{Y}^{I} \in \mathbb{R}_{+}^{M \times F \times O}$ be a 3-dimensional tensor, each element $y^I_{jfo}$ indicates a relationship between item $j$, aspect $f$, and opinion $o$:

\begin{equation}
y^I_{jfo} = \
\begin{cases}
0, & \text{if item $j$ has not been described positively with opinion $o$ on aspect $f$} \\
1 + (N-1)\left(\frac{1}{1+\exp(-t_{jfo})}\right), & \text{otherwise}
\end{cases}
\end{equation}

where $t_{jfo}$ is the frequency with which item $j$ has been described positively with opinion $o$ on aspect $f$ positively across all its reviews.

### Optimization

MTER employs Tucker Decomposition to jointly factorize three tensors $\mathbf{\tilde{X}}$, $\mathbf{Y}^U$, and $\mathbf{Y}^I$.   In addition, MTER also optimizes for a ranking objective akin to BPR where:
*  Positive triples $\mathbf{T} = \{ j >_{i} j' | x_{ij(F+1)} \in \mathbf{R}^+ \land x_{ij'(F+1)} \in \mathbf{R}^- \}$
*  For aspect (F + 1), which is the overall rating, user $i$ prefers item $j$ to item $j'$

Learning the latent factors can be done via minimizing the following loss function:

\begin{align}
&\mathcal{L}(\mathbf{U, V, Z, W, C_1, C_2, C_3} | \lambda_B, \lambda) = ||\mathbf{\tilde{X}} - \mathbf{\hat{X}}||_F^2 + ||\mathbf{Y}^U - \hat{\mathbf{Y}}^U||_F^2 + ||\mathbf{Y}^I - \hat{\mathbf{Y}}^I||_F^2 - \lambda_B \sum_{j >_i j'} \ln(1 + \exp{(-(\hat{x}_{ij(F+1)} - \hat{x}_{ij'(F+1)}))}) + \lambda(||\mathbf{U}||_F^2+||\mathbf{V}||_F^2+||\mathbf{Z}||_F^2+||\mathbf{W}||_F^2 +||\mathbf{C_1}||_F^2 +||\mathbf{C_2}||_F^2 +||\mathbf{C_3}||_F^2) \\
&\text{such that: }  \mathbf{U} \ge 0, \mathbf{V} \ge 0, \mathbf{Z} \ge 0, \mathbf{W} \ge 0, \mathbf{C_1} \ge 0, \mathbf{C_2} \ge 0, \mathbf{C_3} \ge 0
\end{align}

The can be solved as a constrained optimization problem.

Let's conduct an experiment with MTER model and compare with the BPR baseline.

```
mter = MTER(
  n_user_factors=10,
  n_item_factors=10,
  n_aspect_factors=10,
  n_opinion_factors=10,
  n_bpr_samples=1000,
  n_element_samples=50,
  lambda_reg=0.1,
  lambda_bpr=10,
  max_iter=3000,
  lr=0.5,
  verbose=VERBOSE,
  seed=SEED,
)

# compare to baseline BPR
bpr = BPR(k=10, verbose=VERBOSE, seed=SEED)

eval_metrics = [
  cornac.metrics.NDCG(k=50),
  cornac.metrics.AUC()
]

# Instantiate and run an experiment
cornac.Experiment(
  eval_method=rs, models=[bpr, mter], metrics=eval_metrics,
).run()
```

    TEST:
    ...
         |    AUC | NDCG@50 | Train (s) | Test (s)
    ---- + ------ + ------- + --------- + --------
    BPR  | 0.6271 |  0.0314 |    1.9081 |   5.7188
    MTER | 0.7185 |  0.0357 |   51.1022 |   6.8672

### Recommendation Explanation with MTER

* To provide recommendation to user $i$, we rank items $j$ in terms of the predicted rating scores: $\hat{x}_{ij(F+1)}$

* To determine which aspect $f$ of product $j$ a user $i$ cares about, we rank aspects $f$ in terms of: $\hat{x}_{ijf}$

* To determine which opinion phrases $o$ to use when describing aspect $f$ while recommending item $j$ to user $i$, we rank phrases in terms of: $\hat{y}^U_{ifo} \times \hat{y}^I_{jfo}$

Let's explore an example below on how we can generate explanations for recommendation by MTER model.

```
UIDX = 10
IIDX = 10
num_top_aspects = 2
num_top_opinions = 3

item_aspect_ids = np.array(list(set([
    tup[0]
    for idx in rs.sentiment.item_sentiment[IIDX].values()
    for tup in rs.sentiment.sentiment[idx]
])))

item_opinion_ids = np.array(list(set([
  tup[1]
  for idx in rs.sentiment.item_sentiment[IIDX].values()
  for tup in rs.sentiment.sentiment[idx]
])))

item_aspects = [id_aspect_map[idx] for idx in item_aspect_ids]

ts1 = np.einsum("abc,a->bc", mter.G1, mter.U[UIDX])
ts2 = np.einsum("bc,b->c", ts1, mter.I[IIDX])
predicted_aspect_scores = np.einsum("c,Mc->M", ts2, mter.A)

top_aspect_ids = item_aspect_ids[(-predicted_aspect_scores[item_aspect_ids]).argsort()[:num_top_aspects]]
top_aspects = [id_aspect_map[idx] for idx in top_aspect_ids]

top_aspect_opinions = []
mter_explanations = []
for top_aspect_id, top_aspect in zip(top_aspect_ids, top_aspects):
  ts1_G2 = np.einsum("abc,a->bc", mter.G2, mter.U[UIDX])
  ts2_G2 = np.einsum("bc,b->c", ts1_G2, mter.A[top_aspect_id])
  predicted_user_aspect_opinion_scores = np.einsum("c,Mc->M", ts2_G2, mter.O)

  ts1_G3 = np.einsum("abc,a->bc", mter.G3, mter.I[IIDX])
  ts2_G3 = np.einsum("bc,b->c", ts1_G3, mter.A[top_aspect_id])
  predicted_item_aspect_opinion_scores = np.einsum("c,Mc->M", ts2_G3, mter.O)

  predicted_aspect_opinion_scores = np.multiply(predicted_user_aspect_opinion_scores, predicted_item_aspect_opinion_scores)
  top_opinion_ids = item_opinion_ids[(-predicted_aspect_opinion_scores[item_opinion_ids]).argsort()[:num_top_opinions]]
  top_opinions = [id_opinion_map[idx] for idx in top_opinion_ids]
  top_aspect_opinions.append(top_opinions)

  # Generate explanation for top-1 aspect
  mter_explanations.append(f"Its {top_aspect} is [{'] ['.join(top_opinions)}].")

pd.DataFrame.from_dict({"aspect": top_aspects, "top_opinions": top_aspect_opinions, "explanation": mter_explanations})
```

      aspect
      top_opinions
      explanation

      0
      really
      [disappointed, great, like]
      Its really is [disappointed] [great] [like].

      1
      addition
      [disappointed, great, fun]
      Its addition is [disappointed] [great] [fun].

## References

1.   Zhang, Y., Lai, G., Zhang, M., Zhang, Y., Liu, Y., & Ma, S. (2014). Explicit factor models for explainable recommendation based on phrase-level sentiment analysis. In SIGIR (pp. 83-92).
2. Wang, N., Wang, H., Jia, Y., & Yin, Y. (2018). Explainable recommendation via multi-task learning in opinionated text data. In SIGIR (pp. 165-174).
4.   Cornac - A Comparative Framework for Multimodal Recommender Systems (https://cornac.preferred.ai/)


```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

### Library import

```python
from sympy import init_printing, Matrix, symbols, eye, Rational
init_printing()
```

# Matrix multiplication, inverse and transpose

## Multiplying matrices

In this section we approach matrix multiplication on four ways.  Each method provides its own unique insight into the process of multiplication.  We also take a look at _block multiplication_, where a matrix can be _broken up_ into specifically sized blocks that make multiplication easier.

### Method 1

Consider multiplying matrices $A$ and $B$ to result in $C$. We have already seen that the column size of the first must equal the row size of the second, $A_{n \times m} B_{m \times p} = C_{n \times p}$.

Every element in $C$ (with a row value of $i$ and a column value of $j$) is the dot product of the corresponding row vector in $A$ and the column vector in $B$.  In (1) we take the dot product of row `2` in the first matrix and column `1` in the second matrix to get $C_{21}$.

$$ { \begin{bmatrix} \cdots  & \cdots  & \cdots  \\ 3 & 2 & -1 \\ \cdots  & \cdots  & \cdots  \\ \cdots  & \cdots  & \cdots  \end{bmatrix} }_{ 4\times 3 }{ \begin{bmatrix} 1 & \vdots  \\ 2 & \vdots  \\ 1 & \vdots  \end{bmatrix} }_{ 3\times 2 }={ \begin{bmatrix} { c }_{ 11 } & { c }_{ 12 } \\ \left( 3\times 1 \right) +\left( 2\times 2 \right) +\left( -1\times 1 \right)  & { c }_{ 22 } \\ { c }_{ 31 } & { c }_{ 32 } \\ { c }_{ 41 } & { c }_{ 42 } \end{bmatrix} }_{ 4\times 2 } \tag{1}$$

### Method 2

Here we are concerned with the columns of $B$.  We note that each column in $C$ is the result of the matrix $A$ times the corresponding column in $B$.  In this example, it would be akin to a matrix multiplied by a vector $A \underline{x} = \underline{b}$.  $B$ is then made up from four column vectors.

### Method 3

Here every row in $A$ produces the same numbered row in $C$ by multiplying it with the matrix $B$.  The rows of $C$ are linear combinations of $B$.

### Method 4

In method 1 we looked at $\text{row}_A \times \text{col}_B$, which produced a single number in $C$.  What if we did $\text{col} \times \text{row}$?

The size of a column in $A$ might be written as $r_A \times 1$ and a row in $B$ as $1 \times s_B$.  The result in $C$ would then be $r_A \times s_B$.  Let's use `sympy` to investigate a simple example.  Here we have $A_{3 \times 1}$ and $B_{1 \times 2}$, with $C_{3 \times 2}$.

```python
A = Matrix([[2], [3], [4]])
B = Matrix([[1, 6]])
A, B
```

```python
C = A * B
C
```

So in method 4, for the example below, $C$ is the sum of *the columns of* $A$ times *the rows of* $B$.

$$ \begin{bmatrix} { a }_{ 11 } & { a }_{ 12 } \\ { a }_{ 21 } & { a }_{ 22 } \\ { a }_{ 31 } & { a }_{ 32 } \end{bmatrix}\begin{bmatrix} { b }_{ 11 } & { b }_{ 12 } \\ b_{ 21 } & { b }_{ 22 } \end{bmatrix}=\begin{bmatrix} { a }_{ 11 } \\ { a }_{ 21 } \\ { a }_{ 31 } \end{bmatrix}\begin{bmatrix} { b }_{ 11 } & { b }_{ 12 } \end{bmatrix}+\begin{bmatrix} { a }_{ 12 } \\ { a }_{ 22 } \\ { a }_{ 32 } \end{bmatrix}\begin{bmatrix} { b }_{ 21 } & { b }_{ 22 } \end{bmatrix}  \tag{2}$$

### Block multiplication

This is in essence, a combination of the above.  We do the following:
+ Both $A$ and $B$ can be broken into block of sizes that allow for multiplication

We see an example of this in (3).

$$ \begin{bmatrix} { A }_{ 1 } & { A }_{ 2 } \\ { A }_{ 3 } & { A }_{ 4 } \end{bmatrix}\begin{bmatrix} { B }_{ 1 } & { B }_{ 2 } \\ { B }_{ 3 } & { B }_{ 4 } \end{bmatrix}=\begin{bmatrix} { A }_{ 1 }{ B }_{ 1 }+{ A }_{ 2 }{ B }_{ 3 } & { A }_{ 1 }{ B }_{ 2 }+{ A }_{ 2 }{ B }_{ 4 } \\ { A }_{ 3 }{ B }_{ 1 }+{ A }_{ 4 }{ B }_{ 3 } & { A }_{ 3 }{ B }_{ 2 }+{ A }_{ 4 }{ B }_{ 4 } \end{bmatrix} \tag{3}$$

## Inverses

W e know by now that **if** the inverse of a matrix $A$ exists then $A^{-1} A = I$, the identity matrix.  This is a _left inverse_, but what about a _right inverse_, $A A^{-1}$?  This is also equal to the identity matrix (given that $A$ is invertible).  Invertible matrices are also called *non-singular* matrices.

This brings the question: "_What is a non-invertible matrix?_".  Non-invertible matrices are called *singular* matrices.  An example is shown in (4).

$$ \begin{bmatrix}1&3\\2&6\end{bmatrix} \tag{4}$$

Note how the elements of row `2` are just $2$ times the elements in row `1`.  The elements of row `2` are linear combinations of the elements in row `1`.  The same go for the columns, the first being a linear combination of the second, multiplying each element by $3$.  More profoundly, note that you could find a column vector $\underline{x}$ such that $A \times \underline{x}= \underline{0}$.

In (5) below we see that $3$ times column `1` in $A$ plus $-1$ times column `2` gives *nothing*, the zero vector.

$$ \begin{bmatrix}1&3\\2&6\end{bmatrix}\begin{bmatrix}3\\-1\end{bmatrix}=\begin{bmatrix}0\\0\end{bmatrix} \tag{5}$$

Let's construct as example, shown in (6) below as $A A^{-1} = I$ (given that $A^{-1}$ exists).

$$ \begin{bmatrix} 1 & 3 \\ 2 & 7 \end{bmatrix}\begin{bmatrix} a & c \\ b & d \end{bmatrix}=\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \tag{6}$$

In essence, we have to solve two systems.  $A$ times column `1` of $A^{-1}$ is column `1` of $I$.  This is the Gauss-Jordan idea of solving two systems at once.

$$ \begin{bmatrix} 1 & 3 \\ 2 & 7 \end{bmatrix}\begin{bmatrix} a \\ b \end{bmatrix}=\begin{bmatrix} 1 \\ 0 \end{bmatrix}\\ \begin{bmatrix} 1 & 3 \\ 2 & 7 \end{bmatrix}\begin{bmatrix} c \\ d \end{bmatrix}=\begin{bmatrix} 0 \\ 1 \end{bmatrix} \tag{7}$$

This will give us the two columns of $I$.  We proceed by creating an augmented matrix of the coefficients (note carefully!).

$$ \begin{bmatrix} 1 & 3 & 1 & 0 \\ 2 & 7 & 0 & 1 \end{bmatrix} \tag{8}$$

Now we use elementary row operations to reduced row-echelon form (leading $1$'s in the pivot positions, with $0$'s below and above each).

$$ \begin{bmatrix} 1 & 3 & 1 & 0 \\ 2 & 7 & 0 & 1 \end{bmatrix}\rightarrow \begin{bmatrix} 1 & 3 & 1 & 0 \\ 0 & 1 & -2 & 1 \end{bmatrix}\rightarrow \begin{bmatrix} 1 & 0 & 7 & -3 \\ 0 & 1 & -2 & 1 \end{bmatrix} \tag{9}$$

We now read off the two columns of $A^{-1}$.

 $$ \begin{bmatrix}7&-3\\-2&1\end{bmatrix} \tag{10}$$

## Example problems

### Example problem 1

Find the conditions on $a$ and $b$ that makes the matrix $A$ invertible and find $A^{-1}$.|

$$ A=\begin{bmatrix} a & b & b \\ a & a & b \\ a & a & a \end{bmatrix} \tag{11} $$

#### Solution

1. A matrix is singular (non-invertible) if we have a row or column of zeros, so $a \ne 0$
2. We can also not have similar columns, so $a \ne b$

Using Gauss-Jordan elimination we will have the following.

$$ \begin{bmatrix} a & b & b & 1 & 0 & 0 \\ a & a & b & 0 & 1 & 0 \\ a & a & a & 0 & 0 & 1 \end{bmatrix}\rightarrow \begin{bmatrix} a & b & b & 1 & 0 & 0 \\ 0 & a-b & 0 & -1 & 1 & 0 \\ 0 & a-b & a-b & -1 & 0 & 1 \end{bmatrix}\rightarrow \begin{bmatrix} a & b & b & 1 & 0 & 0 \\ 0 & a-b & 0 & -1 & 1 & 0 \\ 0 & 0 & a-b & 0 & -1 & 1 \end{bmatrix}\\ \rightarrow \begin{bmatrix} a & b & b & 1 & 0 & 0 \\ 0 & \frac { a-b }{ a-b }  & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  & 0 \\ 0 & 0 & \frac { a-b }{ a-b }  & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  \end{bmatrix}\rightarrow \begin{bmatrix} a & b & b & 1 & 0 & 0 \\ 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  & 0 \\ 0 & 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  \end{bmatrix}\\ \rightarrow \begin{bmatrix} a & b & 0 & 1 & \frac { 1 }{ a-b } \left( b \right)  & -\frac { 1 }{ a-b } \left( b \right)  \\ 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  & 0 \\ 0 & 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  \end{bmatrix}\rightarrow \begin{bmatrix} a & 0 & 0 & 1+\frac { b }{ a-b }  & 0 & -\frac { 1 }{ a-b } \left( b \right)  \\ 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  & 0 \\ 0 & 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  \end{bmatrix}\\ \rightarrow \begin{bmatrix} 1 & 0 & 0 & \frac { 1 }{ a-b }  & 0 & -\frac { 1 }{ a\left( a-b \right)  } \left( b \right)  \\ 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  & 0 \\ 0 & 0 & 1 & 0 & \frac { -1 }{ a-b }  & \frac { 1 }{ a-b }  \end{bmatrix}\\ { A }^{ -1 }=\frac { 1 }{ a-b } \begin{bmatrix} 1 & 0 & \frac { -b }{ a }  \\ -1 & 1 & 0 \\ 0 & -1 & 1 \end{bmatrix} \tag{12}$$

If we were to construct this matrix using `sympy` and invert it using the `.inv()` method, we can deduce our findings through simple algebra, i.e. $a \ne b$, $a \ne 0$.

```python
a, b = symbols('a b')
```

```python
A = Matrix([[a, b, b], [a, a, b], [a, a, a]])
A
```

```python
A.inv()
```

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_3_Matrix_multiplication_Inverses.ipynb


```python
from IPython.display import HTML

HTML('''
<form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code for QISKit exercises."></form>''')
```

<form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code for QISKit exercises."></form>

# 8. Shor's algorithm

Shor's factoring algorithm [1] is the most well-known example of a quantum algorithm outperforming the best known classical algorithms. The algorithm allows to factor a number $N_0$ which is the product of two prime numbers $N_0 = p \cdot q$, in polynomial time. This is possible thanks to a theorem in number theory which turns the problem of finding factors into the problem of finding the period of a periodic function. Using the quantum Fourier transform (QFT), one can then find the period of this function with high probability and complete the factorization.

The problem of factoring numbers has been studied for centuries and no efficient (polynomial time) algorithm has ever been found. The difficulty of factoring numbers is the basis on which the most widespread encryption standard, the RSA, is founded. Therefore, an algorithm which is capable of factoring numbers efficiently could have a huge impact on the security of electronic based interactions. The discovery of this algorithm by Peter Shor in 1994, led to an explosion of the field of quantum computation because of its important application.

However, experimental implementation of the algorithm still remains a challenge because of the errors introduced by the large number of physical qubits and gates required to execute the algorithm. Proof of principle demonstration of Shor's factoring algorithm to factor the smallest number $N_0=15$ using five superconducting qubits have been done for setups like NMR [2], trapped ions [3], photons [4-6], photonic chips [7] and superconducting qubits [8,9]. These experiments show how complex is to implement the actual algorithm, as even for the smallest number which allows the algorithm to be run ($N_0=15$), extreme simplifications must be made to run the algorithm [10].

## 8.1 Mathematical preliminaries

### 8.1.1 Modular arithmetic
When division between integers $a$ (the dividend) and $b$ (the divisor) is taught for the first time, one is usually introduced to the idea of quotient $q$ and remainder $r$. The quotient is the number of times the dividend $a$ contains the divisor $b$, the remainder is any left-over that added to the product of the quotient by the divisor returns the dividend, $r + q\times b = a$ or $r = a - q\times b$.

#### Example:
Consider the case $a=40$ and $b=30$, let us find the quotient and the remainder of the division of $a$ by $b$. $40/30 \rightarrow q = 1, \, r = 13$,

Therefore, the quotient is $q=1$ and the remainder is $r = 13$.

By looking at the division between integers in this way, one can easily understand the fundamentals of modular arithmetic. Modular arithmetic is a set of rules for handling operations between integer numbers. In modular arithmetic, integers multiple of a fixed number $N_0$ are considered equivalent. Thus, by selecting an integer $N_0$, the set of all integers is restricted to the integers in the interval $\left[ 0 , N_0-1 \right]$.
To visualize this, one can think about a twelve-hours clock. In a clock, the only integers allowed lie in the interval $\left[ 0 , 11 \right]$, once this interval is exceeded one goes back at the beginning of the interval. Thus, $9+5 = 2 \, \left( mod \, 12 \right)$.
More formally, two integers $a$ and $b$ are congruent modulo $N_0$ if their difference $a-b$ is an integer multiple of $N_0$. That is, $a-b = r \cdot N_0$, where $r$ is an integer, then $a \equiv b  \, \left( mod \, N_0 \right)$.
Equivalently, if $a \equiv b  \, \left( mod \, N_0 \right)$, then $\frac{a-b}{N_0}$ has zero remainder. Or, $b  \, \left( mod \, N_0 \right)$ is the remainder of the division $\frac{b}{N_0}=a$. Thus, both numbers have the same remainder when divided by $N_0$.

#### Example:
Equivalence of two integers modulo $N_0$:
$38 \equiv 14  \, \left( mod \, 12 \right)$
$38 - 14  = 24 = 2 \cdot 12 $

Both $\frac{38}{12}$ and $\frac{14}{12}$ have remainder $2$.

### 8.1.2 Continued Fraction Algorithm

The continued fraction algorithm is used to reduce a fraction $\frac{m}{n}$ to another fraction $\frac{u}{t}$. In general, the algorithm allows to rewrite any irrational number as a finite/infinite sum of an integer part plus the reciprocal of a number.
So, consider the fraction $\frac{m}{n}$, its continued fraction expansion is

$$ \frac{m}{n} = a_0 + \frac{1}{a_1 + \frac{1}{a_2+ \frac{1}{a_3+\frac{1}{1+\frac{1}{a_4+\frac{1}{a_5+\frac{1}{...}}}}}}} $$

Using the integers $a_i$, one can rewrite the fraction $\frac{m}{n}$ as $\frac{u}{t}$. To find $u_i$ and $t_i$ one can use the following formulas:

$u_0 = a_0$, $u_1=1+a_0a_1$, ..., $u_n = a_n d_n + d_{n-2}$
$t_0=1$, $t_1 = a_1$, ..., $t_n =a_n t_{n−1} +t_{n−2}$

Which then give the possible approximations for $\frac{m}{n}$:

$\frac{m}{n} \approx \frac{u_0}{t_0}$, $\frac{m}{n} \approx \frac{u_1}{t_1}$, ..., $\frac{m}{n} \approx \frac{u_n}{t_n}$

## 8.2 Factoring and order finding

The possibility of running an efficient algorithm for factoring a product of two prime number arises from: i. the connection between factoring and order finding, ii. the ability of quantum computers to deal efficiently with periodic functions.
In this Section, the connection between the problem of factoring and the problem of finding the period of a periodic function, also called $\textit{order finding}$ is explained.
It is important to note that this equivalency holds in the realm of modular arithmetic.
Let us start from the concept of order of a number. Given an integer $a$, the order of $a$ is the smallest integer number $r$ for which the following condition holds

\begin{equation}
a^r \text{mod} N_0 = 1 \tag{1}
\end{equation}

#### Example:
Consider the case $a=2$ and $N_0=21$, let us find the order of $a$ by trying different exponents $r$ sequentially until 	we find the one for which condition (1) is satisfied:
	$2^1 (\text{mod}  21) = 2$,
	$2^2 (\text{mod}  21) = 4$,
	$2^3 (\text{mod} 21) = 8$,
	$2^4 (\text{mod} 21) = 16$,
	$2^5 (\text{mod} 21) = 11$,
	$2^6 (\text{mod} 21) = 1$.

Therefore, the order of $a=2$ is $r=6$.

Now that we have clarified what is the order of a number, let us explore the connection between order finding and factoring.
Start by rewriting Eq. (1) as $a^r - 1  \text{mod} \, N_0 = 0$, which means that $a^r - 1$ is a multiple of $N_0$. Assuming that $r$ is even, we can write $ a^r - 1  \text{mod} \, N_0  = \left( a^{\frac{r}{2}} - 1\right)\left( a^{\frac{r}{2}} + 1\right)  \text{mod} \, N_0 $. Finally, we check if either $a^{\frac{r}{2}} - 1$ or $a^{\frac{r}{2}} + 1$ shares any factors with $N_0$. This can be checked tacking the greatest common divisor (gcd) of them, which outputs the biggest number that divides both inputs. If the output of $\text{gcd}\left( a^{\frac{r}{2}} \pm 1,N_0 \right)$ is greater than one, than that's one of the factors. The other one being $N_0/ \text{gcd}\left( a^{\frac{r}{2}} \pm 1,N_0 \right)$.

#### Example:
Again consider the case $a=2$ and $N_0=21$, in the previous example we found that the order is $r = 6$. Let us now show how to find the two prime factors whose product gives $21$.
$2^6 - 1 (\text{mod} \, 21) = \left( 2^3 +1 \right)\left( 2^3 -1 \right) (\text{mod} \, 21) $.
We need to check if the two numbers $ \left( 2^3 +1 \right) = 9$ and $ \left( 2^3 -1 \right)=7$ have any factors in common with $21$.
Let's start by checking $ \left( 2^3 +1 \right)$: $\text{gcd}\left( 9, 21\right) = 3$.
We obtained a $gcd \neq 1$, which means that there is a common factor between $a^{\frac{r}{2}} + 1$ and $N_0$. We can now calculate the first factor of $21$.
$\text{factor}_1 = \frac{21}{\text{gcd}\left( 9, 21\right)} = \frac{21}{3} = 7$.
The second factor follows immediately from $\text{factor}_2 = \frac{21}{\text{factor}_1} = \frac{21}{7} = 3$.

Therefore, we find the two prime factors whose product is $N_0=21$, $\text{factor}_1 = 7$ and $\text{factor}_2 = 3$. Thus, the order of $a=2$ is $r=6$.

It's important to notice that several assumptions are needed to translate the problem of factoring to the one of order finding. These are "weak points" of the algorithm, which fails to produce the correct result each time one of these assumptions is violated.

## 8.3 Quantum Fourier transform

The Quantum Fourier Transform (QFT) is the heart of Shor's factoring algorithm. Similarly to the discrete Fourier transform, which allows to compute the Fourier transform of a complex number, the QFT allows to compute the Fourier transform of a quantum state. This means that we can rewrite a generic state of a qubit register as a superposition of all the possible basis state vectors of the register, with a certain phase.

The QFT of one of the basis states $\lvert i \rangle$ of an $n$ qubit register is

\begin{equation}
QFT \lvert j \rangle = \frac{1}{2^{n/2}} \sum_{k=0}^{2^{n}-1} e^{\frac{2 \pi i}{2^n} j k} \lvert k \rangle
\end{equation}

The QFT on a generic state can then be derived from this definition

\begin{equation}
QFT \sum_{j=0}^{2^n-1} x_j \lvert j \rangle = \sum_{k=0}^{2^{n}-1}  \sum_{l=0}^{2^{n}-1} x_l e^{\frac{2 \pi i}{2^n} l k} \lvert k \rangle
\end{equation}

By writing the basis state $\lvert j \rangle$ in binary representation we adopt the following convention: $\lvert j_1 j_2 ... j_n \rangle$ correspond to $j= j_1 2^{n-1}+j_2 2^{n-2} ... j_n 2^{0}$. Following the same convention, we can also write numbers smaller than one as $0.j_1 j_2 ... j_n$  meaning $j_1/2^{0} + j_2/2^{1} +...+ j_n/2^{n}$ a  it is possible to rewrite the QFT in a very simple form

\begin{aligned}
QFT \lvert j \rangle & = \frac{1}{2^{n/2}} \sum_{k=0}^{2^{n}-1} e^{2 \pi i jk / 2^n} \lvert k \rangle \\
& = \frac{1}{2^{n/2}} \sum_{k_1=0}^{1}...\sum_{k_n=0}^{1} e^{2 \pi i \left(\sum_{l=1}^{n} k_l 2^{-l}\right) j} \lvert k_1 ... k_n \rangle \\
& = \frac{1}{2^{n/2}}\sum_{k_1=0}^{1}...\sum_{k_n=0}^{1} \bigotimes_{l=0}^n e^{2 \pi i j k_l2^{-l} } \lvert k_l \rangle \\
& = \frac{1}{2^{n/2}} \bigotimes_{l=1}^n  \left(\lvert 0 \rangle + e^{2 \pi i j 2^{-l} } \lvert 1 \rangle \right) \\
& = \frac{1}{2^{n/2}} \left( \lvert 0 \rangle + e^{2 \pi i[0.j_n]} \lvert 1 \rangle \right) \otimes \left( \lvert 0 \rangle + e^{2 \pi i[0.j_{n-1} j_{n}]} \lvert 1 \rangle \right) \otimes ...\otimes  \left(\lvert 0 \rangle + e^{2 \pi i[0.j_1j_2...j_{n-1}j_n]} \lvert 1 \rangle \right)
\end{aligned}

By writing the QFT in this way, it is easy to find a circuit which implements it. The QFT turns out to be composed of very simple gates such as Hadamard and control-rotations around the z-axis between pairs of qubits $CR_k$.

$$ CR_k =
\begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & e^{2 \pi i/ 2^k}
\end{pmatrix}$$

$$\text{1. Quantum circuit for the Quantum Fourier Transform.}$$

#### Example: QFT on two qubits
Let us calculate the QFT in the case of $n=2$ qubits

$$QFT\lvert j_1j_2\rangle = \frac{1}{\sqrt{8}} \left(\lvert 0 \rangle + e^{2 \pi i[0.j_2]} \lvert 1 \rangle\right) \otimes \left(\lvert 0 \rangle + e^{2 \pi i[0.j_1 j_2]} \lvert 1 \rangle \right) $$

The steps to creating the circuit for $\lvert k_1k_2\rangle = QFT\lvert j_1j_2\rangle$ would be:

Apply an Hadamard to $\lvert j_2 \rangle$, giving the state $\frac{1}{\sqrt{2}}\left(\lvert 0 \rangle + e^{2 \pi i 0.j_2} \lvert 1\rangle\right) = \frac{1}{\sqrt{2}}\left(\lvert 0 \rangle + (-1)^{j_2} \lvert 1 \rangle\right)$

Apply a $CR_2$ from $\lvert j_1 \rangle$ to $\lvert j_2 \rangle$ and an Hadamard to $\lvert j_1 \rangle$, giving the state $\frac{1}{\sqrt{2}}\left(\lvert0\rangle + e^{2 \pi i[0.j_1j_2]} \lvert1\rangle\right)$

Measure the qubits in reverse order, that is $k_2$ corresponds to the first qubit, $k_1$ to the second qubit.

$$\text{2. Quantum Fourier Transform for two qubits.}$$

```python
# QISKIT: QFT code

# calculate qft of two qubit as the example above
```

###  QISKit: implement the quantum Fourier transform

####  1) Fourier transform of two qubits in the $\lvert 00 \rangle $ state

```python
from initialize import *

def qft(circ, q, n):
    """n-qubit QFT on q in circ."""
    for j in range(n):
        for k in range(j):
            circ.cu1(3.14/float(2**(j-k)), q[j], q[k])
        circ.h(q[j])

#initialize quantum program
my_alg = initialize(circuit_name = 'fourier', qubit_number=2, bit_number=2, backend = 'local_qasm_simulator', shots = 1024)

#add gates to the circuit
qft(my_alg.q_circuit, my_alg.q_reg, 2) # qft of input state
my_alg.q_circuit.measure(my_alg.q_reg[0], my_alg.c_reg[0]) # measures first qubit
my_alg.q_circuit.measure(my_alg.q_reg[1], my_alg.c_reg[1]) # measures second qubit

# print list of gates in the circuit
print('List of gates:')
for circuit in my_alg.q_circuit:
    print(circuit.name)

#Execute the quantum algorithm
result = my_alg.Q_program.execute(my_alg.circ_name, backend=my_alg.backend, shots= my_alg.shots)

#Show the results obtained from the quantum algorithm
counts = result.get_counts(my_alg.circ_name)

print('\nThe measured outcomes of the circuits are:',counts)

# credits to:  https://github.com/QISKit/qiskit-tutorial
```

    List of gates:
    h
    cu1
    h
    measure
    measure

    The measured outcomes of the circuits are: {'00': 243, '01': 241, '10': 272, '11': 268}

## 8.4 Description of the algorithm

Shor's algorithm exploits both classical and quantum computation. Classical operations are carried out in steps where an efficient classical algorithm exists, while quantum operations are used to find the periodicity of the function needed to factor $N_0$. Here we present an outline of the algorithm.

$$\text{3. Quantum circuit for Shor's algorithm.}$$

To run the algorithm, we need two quantum registers. One contains the order or period, called period register, and the other contains the results of the computation, called computational register. The size of both registers depends on the number $N_0$ to be factored. In particular, the period register should contain a number of qubits $n_p$ in the interval $2 \text{log} N_0 \leq n_p < 2 \text{log} (2N_0) $ and the computational register should be large enough to be able to represent the number $N_0-1$, thus $n_q = \text{log} (N_0-1)$.

First, one needs to check if $N_0$ is even. If $N_0$ is even, one of the factor is $2$ and the other is $N_0/2$.

If $N_0$ is odd, a base $a$ is picked randomly among the numbers from $0$ to $N_0-1$. Then, check if $a$ is a factor of $N_0$, by checking if gcd$\left(a,N_0 \right) \neq 1$. If $a$ is has a common divisor with $N_0$, then one factor is given by gcd$\left(a,N_0 \right)$ and the other by $N_0/$gcd$\left(a,N_0 \right)$. If $a$ is co-prime with $N_0$ ( gcd$\left(a,N_0 \right) = 1$ ) then one needs to compute the function $a^x$ mod $N_0$, the modular exponentiation function (MEF), for $x=1,2,3,..Q-1$ where $N_0^2 \leq Q < 2N_0^2$ and $Q=2^{n_p}$.

At the beginning of the quantum algorithm used to compute the MEF we need two registers initialized to zero $\lvert 00...0 \rangle \lvert 00...0 \rangle $. The first register also known as the period register will stores all the possible values of the exponent $x$ by creating a uniform superposition of all possible bit strings through Hadamard gates on all qubits $\frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} \lvert x \rangle $, and the second register, called the computational register will store the results of the MEF $a^x$ mod $N_0$, $\lvert a^x $ mod $ N_0 \rangle$. Thus, after the first step, one has

\begin{equation}
	\frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} \lvert x \rangle \lvert a^x mod N_0 \rangle
\end{equation}

Then, we apply the quantum Fourier transform (QFT) to the first register, so that $\lvert x \rangle \rightarrow \frac{1}{\sqrt{Q}} \sum_{s=0}^{Q-1} e^{\frac{2\pi i s x}{Q}}\lvert s \rangle$. As a result of the QFT, interference between all the possible states occurs and only the periodic ones survive. That is, if one measures the first register, one will see a value of $s$ such that $\frac{sx}{Q}$ is an integer $d$ when $x$ a multiple of the period $r$. Which means that $\frac{s}{Q}=\frac{d}{r}$. By knowing the fraction $\frac{s}{Q}$ one can find the value of $r$ through the continued fraction algorithm.
Now, if $r$ is odd or $r=0$, the algorithm fails and one needs to restart by picking a different base $a$. If $r$ is even, one can factorize $a^r - 1$  mod $N_0$ into $\left( a^{\frac{r}{2}} - 1\right)\left( a^{\frac{r}{2}} + 1\right)$ mod $N_0$. The final step is to check if $a^{\frac{r}{2}} + 1 \, \text{mod} \, N_0 \neq -1$. If that's true, then $\text{gcd}\left(a^{\frac{r}{2}} + 1,N_0 \right)$ will be the first factor and $\text{gcd}\left(a^{\frac{r}{2}} - 1,N_0 \right)$ the other.

The execution of this version of the algorithm requires $n=log_2 \left( N_0 \right)$ qubits in the computational register to perform the modular exponentiation and another $2n$ qubits in the period register to perform the QFT. Thus, the algorithm requires a total number of $3n$ qubits.

## 8.5 Example of Shor's factoring algorithm

Let us see here an example of Shor's factoring algorithm for $N_0=21$. Since $21$ needs five bits to be represented in binary, we need at least $\text{log}_2Q = 2*5$ qubits for factoring.

 Check if $N_0=21$ is even: $21 \, \text{mod} \, 2=1$, $N_0$ is not even.

 Pick a base $a$ at random. Let's say $a=2$.

 Check if $a$ has any common factors with $N_0$: $\text{gcd}\left(a,N_0 \right) = 1$, it doesn't.

 Initialize two qubit registers with $n_p = 2\times5=10$ qubits, as $5$ qubits are needed to represent $21$ in binary. We call the first register the $period$ register and the second register the $computational$ register
    $$ \lvert \psi \rangle = \lvert 0 \rangle^{\otimes 10}_p \lvert 0 \rangle^{\otimes 10}_c $$

 Apply Hadamard gate on all ther qubits of the first register to create a uniform superposition of all $2^{10}$ possible values
    $$ \lvert \psi \rangle =\frac{1}{\sqrt{2^{10}}} \sum_{x=0}^{2^{10}-1} \lvert x \rangle_p \lvert 0 \rangle_c $$

 Apply the modular exponentiation function $a^x \, \text{mod} \, N_0$ on the second register, for each of the stored value of $x$ in the first register.

$$ \lvert \psi \rangle =\frac{1}{\sqrt{1024}} \sum_{x=0}^{1023} \lvert x \rangle_p \lvert 2^x \, \text{mod} \, 21 \rangle_c
        = \\ = \frac{1}{\sqrt{1024}} \left( \lvert 0 \rangle_p \lvert 1 \rangle_c  + \lvert 1 \rangle_p \lvert 2 \rangle_c + \lvert 2 \rangle_p \lvert 4 \rangle_c + \lvert 3 \rangle_p \lvert 8 \rangle_c + \lvert 4 \rangle_p \lvert 16 \rangle_c  + \lvert 5 \rangle_p \lvert 11 \rangle_c + \lvert 6 \rangle_p \lvert 1 \rangle_c + \lvert 7 \rangle_p \lvert 2 \rangle_c + \lvert 8 \rangle_p \lvert 4 \rangle_c ... \right) $$
        By looking at the values stored in the second register, we can find out what is their periodicity. In particular, it can be seen that the values start repeating with order $r=6$ (value of the first register). However, we need to do a few more steps to allow the quantum computer to find the answer by itself.

 To simplify the example, we will adopt the "Principle of implicit measurement": Without loss of generality, any qubits which are not measured at the end of the quantum circuit may be assumed to be measured. Thus, let us use the principle of implicit measurement on the second register. Since each term of the superposition has equal weight, each outcome is equally likely, therefore one will see one of the following values: $ \lvert 1 \rangle_c$ , $\lvert 2 \rangle_c$, $ \lvert 4 \rangle_c$, $\lvert 8 \rangle_c$, $\lvert 16 \rangle_c$, $\lvert 11 \rangle_c$; with probability $1/6$.
     Assume that the state $ \lvert 4 \rangle_c$ is measured, then we are left with the composite state:

     $$ \lvert \psi \rangle = \frac{\sqrt{6}}{\sqrt{1024}} \left( \lvert 2 \rangle_p \lvert 4 \rangle_c + \lvert 8 \rangle_p \lvert 4 \rangle_c + \lvert 14 \rangle_p \lvert 4 \rangle_c + \lvert 20 \rangle_p \lvert 4 \rangle_c  ... \right) $$

 Apply the QFT to the first register
     $$ \lvert x \rangle_p \rightarrow \frac{1}{\sqrt{1024}} \sum_{s=0}^{1023} e^{2\pi i \frac{s x}{1024}}\lvert s \rangle_p $$
     Which transform each of the terms in the period register as:
     $$ \lvert 2 \rangle_p \rightarrow \frac{1}{\sqrt{1024}} \sum_{s=0}^{1023} e^{2\pi i \frac{2 s}{1024}}\lvert s \rangle_p $$
     $$ \lvert 8 \rangle_p \rightarrow \frac{1}{\sqrt{1024}} \sum_{s=0}^{1023} e^{2\pi i \frac{8 s}{1024}}\lvert s \rangle_p $$
     $$ \lvert 14 \rangle_p \rightarrow \frac{1}{\sqrt{1024}} \sum_{s=0}^{1023} e^{2\pi i \frac{14 s}{1024}}\lvert s \rangle_p $$
    $$ \lvert 20 \rangle_p \rightarrow \frac{1}{\sqrt{1024}} \sum_{s=0}^{1023} e^{2\pi i \frac{20 s}{1024}}\lvert s \rangle_p $$
    $$...$$

    So, we can write:
    $$ \lvert \psi \rangle = \frac{\sqrt{6}}{1024} \sum_{s=0}^{1023} \left( e^{2\pi i \frac{2s}{1024}} + e^{2\pi i \frac{8s}{1024}} + e^{2\pi i \frac{14s}{1024}} + e^{2\pi i \frac{20s}{1024}}  ... \right)  \lvert s \rangle_p \lvert 4 \rangle_c  = \\
    = \frac{\sqrt{6}}{1024} \sum_{s=0}^{1023} e^{2\pi i \frac{2s}{1024}} \left( 1 + e^{2\pi i \frac{6s}{1024}} + e^{2\pi i \frac{12s}{1024}} + e^{2\pi i \frac{18s}{1024}}  ... \right)  \lvert s \rangle_p \lvert 4 \rangle_c$$

 Measure the period register. The probability of finding a certain value $\lvert s \rangle_p$ is:
    $$ P(s) = \lvert \frac{\sqrt{6}}{1024} e^{2\pi i \frac{2s}{1024}} \left( 1 + e^{2\pi i \frac{6s}{1024}} + e^{2\pi i \frac{12s}{1024}} + e^{2\pi i \frac{18s}{1024}}  ... \right) \rvert^2 $$

    Because of the possible sign difference between all different terms, the values of $s$ which have the highest likelyhood to be observed are the ones for which the phase terms all have the same sign and they add up. That is, $\frac{s}{1024}=\frac{d}{6}$, where $d$ is an integer. Therefore, a value $s=\frac{1024 \cdot d}{6}$ where $d=1,2,3,...$ will most likely be observed.
    Let us assume that the value $s=853$ is measured.

 To find the period from the value of $s$ measured, one then uses the continued fraction algorithm in the following way: we find the fraction $\frac{d}{r}$ which approximates the fraction $\frac{s}{Q}$ to a fixed precision $2*Q$
    $$\lvert \frac{853}{1024} - \frac{d}{r} \rvert < \frac{1}{2048} $$
    So, let's find $d$ and $r$ with the continued fraction algorithm:
    $$ \frac{853}{1024} = 0 + \frac{1}{1 + \frac{1}{4+ \frac{1}{1+\frac{1}{1+\frac{1}{1+\frac{1}{84+\frac{1}{2}}}}}}} $$
   which gives as possible fractions $\frac{d}{r}$: $\frac{1}{1}$, $\frac{4}{5}$, $\frac{5}{6}$, ...

   imposing the condition written above, we find that the only fraction satisfying it is $\frac{d}{r} = \frac{5}{6}$. Therefore, the period is $r=6$!

 Once the period has been found, we can find the factors of $N_0=21$ almost immediately. First check if $r$ is even then check if $a^{\frac{r}{2}} + 1 \, \text{mod} \, N_0 \neq -1 $ :
    $$ 6 \, \text{mod} \, 2 = 0 $$
    $$  2^{\frac{6}{2}} + 1 \, \text{mod} \, 21 = 9$$
    Therefore the two factors are: $ p = \text{gcd}\left(a^{\frac{r}{2}} + 1,N_0 \right)$ and $q = \text{gcd}\left(a^{\frac{r}{2}} - 1,N_0 \right)$
    $$p = \text{gcd}\left(9,21 \right) = 3$$
    $$q = \text{gcd}\left(7,21 \right) = 7$$

## Exercises

Calculate the following modular expression:

$ 54 \, \text{(mod 5)}$

$ 54 \times 35 \, \text{(mod 17)}$

$ 9^{2} \, \text{(mod 6 )}$

Rewrite the following fractions using the continued fraction algorithm

$ \frac{45}{251}$

$ \frac{71}{385}$

$ \frac{512}{1027}$

What is the minimum number of qubits needed to factor:

$ 15 $

$ 4,367,398 $

Derive the expression of the Quantum Fourier Transform of three qubits, similarly to how it is done in the example shown in Section 8.3

Following the steps highlighted in the example given in Section 8.5, show the factoring of $N_0=15$ with $a=2$.

Write a QISKit program that calculates the QFT of three qubits in the state $\lvert 010 \rangle$ .

## References

[1] P. Shor, in Proc. 35th Annu. Symp. on the Foundations of
Computer Science, edited by S. Goldwasser (IEEE Computer
Society Press, Los Alamitos, California, 1994),
p. 124-134.

[2] L. M. K. Vandersypen, M. Steffen, G. Breyta, C. S. Yannoni,
M. H. Sherwood, and I. L. Chuang, Nature 414,
883 (2001).

[3] Monz, T. et al., Science 351, 1068-1070 (2016).

[4] C. Y. Lu, D. E. Browne, T. Yang, and J. W. Pan, Physical
Review Letters 99, 250504 (2007).

[5] E. Martin-Lopez, A. Laing, T. Lawson, R. Alvarez, X.-Q.
Zhou, and J. L. O'Brien, Nat Photon advance online publication (2012).

[6] Lanyon, B. P. et al., Phys. Rev. Lett. 99, 250505 (2007).

[7] A. Politi, J. C. F. Matthews, and J. L. O' Brien, Science 25, 1221 (2009).

[8] E. Lucero, R. Barends, Y. Chen, J. Kelly, M. Mariantoni, A. Megrant, P. O'Malley, D. Sank, A. Vainsencher, J. Wenner, T. White, Y. Yin, A. N. Cleland and John M. Martinis, Nature Physics volume 8, 719-723 (2012).

[9] P. J. Coles et al. (2018), arXiv:1804.03719.

[10] J. A. Smolin, G. Smith, and A. Vargo, Nature (London) 499, 163-165 (2013).


# 1. Chapter 1 - Plausible Reasoning
As we tread further into the twenty first century, almost everyone is expected to memorize the mantra "we must make data driven decisions" (well, at least most people in the technology space, and certainly data scientists). However, I want us to pause for a moment and think about what that really means?

In an idealized, rigid, platonic world this may simply mean to cast our first intuitions aside, instead using metrics related to the problem at hand, past decision's outcomes, and so on. Now, in a simplistic system this is a satisfactory approach. Consider the following (overly simple) scenario; you want to figure out the fastest way to drive from your house to the grocery store at rush hour (note how _constrained_ this situation already is). You intuitively feel as though route $A$ will be faster, so in general you take that route. You know that on average it takes about 9 minutes. Then, one day your roommate decides to join you and recommends route $B$, stating that they always drive that way and that on average it takes 6 minutes. You give route $B$ a shot an sure enough it takes 6 and half minutes.

This is an example of a very simplistic scenario that is conducive to basic data driven decision making. You have (essentially) all the data/variables you need in order to represent the scenario at hand. In other words, the decision is based on a univariate function:

$$\text{Time spent driving to grocery story} = f(\text{route})$$

We have data regarding driving time of both routes, and in this toy example there is really nothing else we need to consider in order to make an optimal decision that reduces driving time.

It should be no surprise that this is _not_ how things work in the real world. The real world is messy, contains an abundance of variables, and these variables manifest into **uncertainty**. The question that I have become obsessed with is as follows:

> How can we reason optimally in complex and uncertain situations?

For instance, let's now say that your company sells widgets. You, as a person in marketing, are in charge of coming up with sales offerings around the holiday season. Your initial intuition is that if you give out 20 dollar coupons to anyone who makes a purchase, you will get more purchases. However, there is a competing hypothesis from your colleague that suggests offering a discount to customers who make over 1000 dollar in purchases would actually be more effective at generating revenue. You have some historical time series data, but _neither_ have specifically been conditioned upon the exact hypotheses you are both proposing (i.e., you have no data that was collected during a 20 dollar coupon period, or during a 1000 dollar purchase discount period). The data that you have is necessarily incomplete, and even if it wasn't we still must confront the following logical problem:

> How do we use data (frequencies of events) to estimate plausibilities of beliefs?

In other words, how can we use the data present to estimate how plausible on hypothesis is compared to the other? This question is the central focus of _Probability Theory: The Logic of Science_, by E.T. Jaynes. Often viewed as the first text to make probability theory a "hard" branch of mathematics (compared to a group of ad hoc methods), it is an incredibly ambitious and thought provoking book that should be on any data scientist's or statistician's bookshelf. With that said, at times it is rather dense, and I wanted to take the time to create a set of articles that serve as chapter summaries. Note, these do not mean to replace the original text; rather, they can be read in tandem to clear up any sources of confusion and ensure clear understanding.

With that said, let's begin digging into the book, starting with the preface and chapter 1.

## 0.1 Preface
Jaynes starts the book by stating who the intended audience is; while this is generally not very informative, here it actually has a good deal of merit! He states that the material is meant to help those who are in fields such as physics, chemistry, biology, geology, medicine, economics, sociology, engineering, operations research, etc.; any field where **inference** is needed. He adds a footnote stating that by inference he means:

> **Inference:** **Deductive** reasoning whenever enough information is at hand to permit it; **inductive** or _plausible_ reasoning when the necessary information is not available (as it almost never is in real problems). If a problem can be solved with deductive reasoning, probability theory is not needed for it. Thus, the topic of this book is **the optimal processing of incomplete information**.

As a data scientist this type of footnote should send tingles down your spine. In nearly every situation you will encounter you are often treading the line of making inferences based off of a combination of deductive and inductive reasoning; who wouldn't want to be making those inferences in an optimal way?

Now, for those unfamiliar with the concepts of deduction and induction I recommend checking out this section of my article on Bayesian Inference. But, for a quick recap we can think of **deduction** as forming some hypothesis about the state of the world or it's workings, gathering data about that state, and then seeing if our data confirms or denies our hypothesis. It can be visualized below:

That is deduction. We perform deduction every day with relative ease. On the other hand we have **induction**, which works in the opposite direction:

Here, we gather data about the world around us, and after picking up on a pattern we induce a hypothesis, and then work towards concluding it's validity. Now, it should be noted that this idea of reasoning from evidence to hypothesis can be thought of as a parallel of reasoning from effect to cause.

As a small example (that I came across when reading _The Book of Why_ by Judea Pearl, consider Sherlock Holmes. Imagine that Sherlock Holmes was called to investigate a broken window. Sure he could go about it deductively and, while driving to the location of the window (i.e. before gathering any data), generate the hypothesis that the it was a group of children who broke the window. This is what humans do with ease, daily. However, what truly made Sherlock Holmes so powerful was his ability to perform induction. In this case, he arrives at the scene of the broken window, sees glass scattered about, and surely a host of other interesting things; he takes all of this _data_ and then generates a hypothesis about how the window was broken.

If that example leads you to feel slightly intimidated by the process of induction, do not worry! Without digging into it too deeply, there is actually an _asymmetry_ going on here that can lead you into _chaos theory_. For this I will borrow another example, this time from Nassim Taleb. Imagine that you want to understand what happens when you leave a block of ice out at room temperature. You hypothesize that it will melt (form your hypothesis). You then take 10 blocks of ice, leave them all out at room temperature, and see that they do indeed all melt (collect data). You just performed a very straight forward deduction, making use of what is referred to as the **forward process**.

Consider the inverse case now. Imagine that you walk into your kitchen and see that there is a puddle of water on the ground. You try and gather data, seeing if there is a leak in a pipe anywhere, if a cup spilled, etc. There is an incredibly long list of ways that this water could have gotten there. Trying to determine (from what is most likely incomplete data) that the water is actually there due to a block of ice that melted, is incredibly challenging. This is known as the **backward process**. If you are interested in this type of problem I recommend looking at the [Butterfly Effect](https://en.wikipedia.org/wiki/Butterfly_effect); for now I will leave it here to prevent going down a rabbit hole.

### 0.2 The Theme of the Book
To give us a north star to focus on, I want to take a moment to highlight what is essentially the theme of the book:

> **Probability theory as extended logic**.

We will dig into this much further in subsequent posts, but what this book does is create a framework in which the rules of probability theory can be viewed as uniquely valid principles of _logic_ in general, leaving out reference to _chance_ or _random variables_. This allows for the imaginary distinction between probability theory and statistical inference to disappear, allowing logical unity as well as greater technical power.

This theme amounts to recognition that the mathematical rules of probability theory are not merely rules for calculating frequencies of "random variables"; they are also the unique and consistent rules for conducting inference (i.e. plausible reasoning) of any kind.

This set of rules will automatically include all **Bayesian** calculations, as well as all **frequentist** calculations. Never the less, our basic rules are broader than either of these, and in many applications the calculations will not fit into either category. As explained by Jaynes:

> The traditional frequentist methods which only use sampling distributions are usable and useful in particularly simple, idealized problems; however, the represent the most proscribed cases of probability theory, because they presuppose conditions (independent repetitions of a 'random experiment' but no relevant prior information) that are hardly ever met in real problems. This approach is quite inadequate for the current needs of science.

Jaynes proceeds to dig into the idea of **prior** information/knowledge, and how it is essential that it is included in data analysis and inference. He writes that a false premise built into a model which is never questioned cannot be removed by any amount of new data. The use of models which correctly represent the prior information that scientists have about the mechanism at work can prevent such folly in the future. By ignoring prior information we not only set ourselves up to fail in the inference we are trying to make, but we also risk stalling out scientific progression itself. _No amount of analyzing coin tossing data by a stochastic model could have led us to the discovery of Newtonian Mechanics, which alone determines those data_.

With our stage set we are ready to dive into what is one of the most thought provoking and eye opening books in the realm of mathematics and science. Enter chapter 1.

# 1. Chapter 1: Plausible Reasoning
Suppose that on some dark night a policeman walks down a street, apparently deserted. Sudddenly he hears a burglar alarm, looks across the street, and sees a jewelry store with a broken window. Then a gentleman wearing a mask comes crawling through the broken window, carrying a bag which turns out to be full of expensive jewelry. The policeman doesn't hesitate at all in deciding that this gentleman is dishonest. But the question is: **By what reasoning process does he arrive at this conclusion?**

#### 1.1 Deductive and Plausible Reasoning
Keep in mind the key theme of this book, these articles, and more importantly data science in general: _How can we reason optimally in complex and uncertain situations?_ It may seem as though this preface and introduction get slightly into the weeds; this is a book on probability after all, right? Jaynes goes to great length to work through the _philosophy_ behind this vantage point of probability (seen as extended logic instead of based off of the standard Kolmogorov axioms); it is not unreasonable to ask why. The key lies in the fact that Jaynes is not simply trying to replace one axiomatic framework with another, but rather he is trying to build up a way to _reason_ consistently and logically, and apply that reasoning to any area of inference. I recommend taking a moment to appreciate that and let it sink in. Many of the problems that Jaynes works this in the forthcoming chapters could have been solved with standard orthodox or bayesian statistics; what makes this framework so powerful is that it provides a way to _reason consistently_. You are not required to hold in your head a bag of ad hoc methods and techniques. Rather, you have a methodology that allows you to approach and reason about any problem logically and consistently. Surely this is what any scientifically minded problem solver would hope for!

With that said let's return to our problem. A bit of thought makes it clear that the policeman's conclusion was not a logical deduction from the evidence; there may have been a perfectly reasonable explanation for everything! For instance, it is certainly _possible_ that the man was the owner of the jewelery store and was coming home from a masquerade party, and as he passed his store some teenagers threw a rock at the window, and he was simply trying to protect his property.

So, clearly the policemans reasoning process was not strictly logical deduction. However, we can grant that it did possess a high degree of validity. The evidence did not make the gentleman's dishonesty _certain_, but it did make it extremely _plausible_. This is the type of reasoning that humans have become proficient with long before studying mathematical theories. We encounter an abundance of these decisions daily (will it rain or won't it?) where we do not have enough information to permit deductive reasoning; but still we must decide immediately what to do.

Now, inspite of how familiar this process is to all of us, the formation of plausible conclusions is very subtle. This book allows us to replace intuitive judgements with definite theorems, and ad hoc procedures are replaced by rules that are determined uniquely by some elementary and inescapable criteria of rationality.

Now, let's take a moment to try and place this within the context of aristotelian logic; that is the most appropriate starting point to dissect the difference between **[deductive reasoning](https://en.wikipedia.org/wiki/Deductive_reasoning)** and **plausible reasoning**.

#### Deductive Reasoning
Deductive reasoning is defined as the process of reasoning from one or more **statements** (premises) in order to reach a logically certain conclusion. We can think of deductive reasoning as follows:

1. All men are mortal. (First premise)
2. Socrates is a man. (Second premise)
3. Therefore, Socrates is mortal. (Conclusion)

And this is defined mathematically below (known as **[modus ponens](https://en.wikipedia.org/wiki/Deductive_reasoning#Modus_ponens)**, the law of detachment):

$$
\begin{equation}
\text{If A is true, then B is true} \\
\frac{\text{A is true}}
{\text{therefore, B is true}}
\end{equation}
$$

It is the primary deductive rule of inference. An example would be:
1. If it is raining, then there are clouds in the sky.
2. It is raining.
3. Therefore, there are clouds in the sky.

There is also the inverse (known as **[modus tollens](https://en.wikipedia.org/wiki/Deductive_reasoning#Modus_tollens)**, the law of contrapositive), another deductive rule of inference:

$$
\begin{equation}
\text{If A is true, then B is true} \\
\frac{\text{B is false}}
{\text{therefore, A is false}}
\end{equation}
$$

And we have the counter simple example:
1. If it is raining, then there are clouds in the sky.
2. There are no clouds in the sky.
3. Thus, it is not raining.

Both of the above are known as **strong syllogisms**. Now in general, Deductive reasoning ("top-down logic") contrasts with inductive reasoning ("bottom-up logic") in the following way; in deductive reasoning, a conclusion is reached reductively by applying general rules which hold over the entirety of a closed domain of discourse, narrowing the range under consideration until only the conclusion(s) is left. In inductive reasoning, the conclusion is reached by generalizing or extrapolating from specific cases to general rules, i.e., there is epistemic uncertainty (for example the black swan). However, the inductive reasoning mentioned here is not the same as induction used in mathematical proofs – mathematical induction is actually a form of deductive reasoning.

```javascript
%%javascript
MathJax.Hub.Config({
    TeX: { equationNumbers: { autoNumber: "AMS" } }
});
```

    <IPython.core.display.Javascript object>

```python

```


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Promijeni vidljivost <a href="javascript:code_toggle()">ovdje</a>.''')
display(tag)
```

Promijeni vidljivost ovdje.

```python
%matplotlib notebook
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
import ipywidgets as widgets
```

## Linearizacija - Jednostavno njihalo

Pri radu s modelima sustava, linearizacija je postupak modeliranja nelinearnog sustava s linearnom diferencijalnom jednadžbom u susjedstvu neke radne točke (obično točke ravnoteže). U ovom primjeru taj je postupak demonstriran pomoću jednostavnog njihala. Sila koja uzrokuje oscilacijsko gibanje njihala (prikazano na donjoj slici) definirana je kao $-mgsin\theta$. Jednadžba gibanja njihala definirana je kao:

\begin{equation}
    mL^2\frac{d^2\theta}{dt^2}=-mgsin\theta L.
\end{equation}

Nakon sređivanja dobivamo sljedeću nelinearnu diferencijalnu jednadžbu drugog reda:

\begin{equation}
    \frac{d^2\theta}{dt^2}+\frac{g}{L}sin\theta=0.
\end{equation}

U slučaju malih kutnih pomaka, vrijedi aproksimacija malog kuta (i.e. $sin\theta\approx\theta$) i dobiva se sljedeća linearna diferencijalna jednadžba drugog reda:

\begin{equation}
    \frac{d^2\theta}{dt^2}+\frac{g}{L}\theta=0.
\end{equation}

---

        Jednostavno njihalo

### Kako koristiti ovaj interaktivni primjer?

Pomičite klizače za promjenu duljine njihala $L$ i vrijednosti početnih uvjeta $\theta_0$ i $\dot{\theta_0}$.

```python
# create figure
fig = plt.figure(figsize=(9.8, 3),num='Diskretizacija - jednostavno njihalo')

# add sublot
ax = fig.add_subplot(111)
ax.set_title('Vremenski odziv')
ax.set_ylabel('izlaz')
ax.set_xlabel('$t$ [s]')
ax.axhline(y=0, xmin=-1, xmax=6, color='k', linewidth=1)

ax.grid(which='both', axis='both', color='lightgray')

nonlinear, = ax.plot([], [])
linear, = ax.plot([], [])

style = {'description_width': 'initial'}

g=9.81 # m/s^2

def model_nonlinear(ic,t,L):
    fi, fidot = ic
    return [fidot,-g/L*np.sin(fi)]

def model_linear(ic,t,L):
    fi, fidot = ic
    return [fidot,-g/L*fi]

def build_model(y0,ypika0,L):
    ic=[y0,ypika0]
    t=np.linspace(0,5,num=500)
    fi=odeint(model_nonlinear,ic,t,args=(L,))
    ys=fi[:,0]

    fi_linear=odeint(model_linear,ic,t,args=(L,))
    ys_linear=fi_linear[:,0]

    global nonlinear, linear

    ax.lines.remove(nonlinear)
    ax.lines.remove(linear)

    nonlinear, = ax.plot(t,ys,label='original',color='C0', linewidth=5)
    linear, = ax.plot(t,ys_linear,label='linearizacija',color='C3', linewidth=2)

    ax.legend()

    ax.relim()
    ax.autoscale_view()

L_slider=widgets.FloatSlider(value=0.3, min=.01, max=2., step=.01,
                                        description='$L$ [m]:',disabled=False,continuous_update=False,
                                        orientation='horizontal',readout=True,readout_format='.2f',)

ypika0_slider=widgets.FloatSlider(value=1, min=-3, max=3, step=0.1,
                                        description='$\dot \\theta_0$ [rad/s]:',disabled=False,continuous_update=False,
                                        orientation='horizontal',readout=True,readout_format='.2f',)

y0_slider=widgets.FloatSlider(value=1, min=-3, max=3, step=0.1,
                                        description='$\\theta_0$ [rad]:',disabled=False,continuous_update=False,
                                        orientation='horizontal',readout=True,readout_format='.2f',)

input_data=widgets.interactive_output(build_model, {'y0':y0_slider,'ypika0':ypika0_slider,'L':L_slider})

display(L_slider,y0_slider,ypika0_slider,input_data)
```

    <IPython.core.display.Javascript object>

    FloatSlider(value=0.3, continuous_update=False, description='$L$ [m]:', max=2.0, min=0.01, step=0.01)

    FloatSlider(value=1.0, continuous_update=False, description='$\\theta_0$ [rad]:', max=3.0, min=-3.0)

    FloatSlider(value=1.0, continuous_update=False, description='$\\dot \\theta_0$ [rad/s]:', max=3.0, min=-3.0)

    Output()


## Data-driven Design and Analyses of Structures and Materials (3dasm)

## Lecture 6

### Miguel A. Bessa | M.A.Bessa@tudelft.nl  | Associate Professor

**What:** A lecture of the "3dasm" course

**Where:** This notebook comes from this [repository](https://github.com/bessagroup/3dasm_course)

**Reference for entire course:** Murphy, Kevin P. *Probabilistic machine learning: an introduction*. MIT press, 2022. Available online [here](https://probml.github.io/pml-book/book1.html)

**How:** We try to follow Murphy's book closely, but the sequence of Chapters and Sections is different. The intention is to use notebooks as an introduction to the topic and Murphy's book as a resource.
* If working offline: Go through this notebook and read the book.
* If attending class in person: listen to me (!) but also go through the notebook in your laptop at the same time. Read the book.
* If attending lectures remotely: listen to me (!) via Zoom and (ideally) use two screens where you have the notebook open in 1 screen and you see the lectures on the other. Read the book.

**Optional reference (the "bible" by the "bishop"... pun intended 😆) :** Bishop, Christopher M. *Pattern recognition and machine learning*. Springer Verlag, 2006.

**References/resources to create this notebook:**
* [Car figure](https://korkortonline.se/en/theory/reaction-braking-stopping/)

Apologies in advance if I missed some reference used in this notebook. Please contact me if that is the case, and I will gladly include it here.

## **OPTION 1**. Run this notebook **locally in your computer**:
1. Confirm that you have the 3dasm conda environment (see Lecture 1).

2. Go to the 3dasm_course folder in your computer and pull the last updates of the [repository](https://github.com/bessagroup/3dasm_course):
```
git pull
```
3. Open command window and load jupyter notebook (it will open in your internet browser):
```
conda activate 3dasm
jupyter notebook
```
4. Open notebook of this Lecture.

## **OPTION 2**. Use **Google's Colab** (no installation required, but times out if idle):

1. go to https://colab.research.google.com
2. login
3. File > Open notebook
4. click on Github (no need to login or authorize anything)
5. paste the git link: https://github.com/bessagroup/3dasm_course
6. click search and then click on the notebook for this Lecture.

```python
# Basic plotting tools needed in Python.

import matplotlib.pyplot as plt # import plotting tools to create figures
import numpy as np # import numpy to handle a lot of things!
from IPython.display import display, Math # to print with Latex math

%config InlineBackend.figure_format = "retina" # render higher resolution images in the notebook
plt.style.use("seaborn") # style for plotting that comes from seaborn
plt.rcParams["figure.figsize"] = (8,4) # rescale figure size appropriately for slides
```

## Outline for today

* Continuation of previous lecture: Bayesian inference for one hidden rv
    - Prior
    - Likelihood
    - Marginal likelihood
    - Posterior
    - Gaussian pdf's product

**Reading material**: This notebook + Chapter 3

## Recap of Lecture 5: car stopping distance with known $x$ and $p(z_2)$

We focused on the car stopping distance problem with two rv's under the following conditions:
* We kept $x=75$ m/s.
* The "true" distribution of one of the rv's  was known: $p(z_2)=\mathcal{N}(\mu_{z_2}=0.1,\sigma_{z_2}^2=0.01^2)$
* But the distribution of the other rv ($z \equiv z_1$) is not known: $p(z)=?$

Under these conditions, recall the "true" model by observing the following plot, including some data observations.

```python
# This cell is hidden during presentation. It's just to define a function to plot the governing model of
# the car stopping distance problem. Defining a function that creates a plot allows to repeatedly run
# this function on cells used in this notebook.
def car_fig_2rvs(ax):
    x = np.linspace(3, 83, 1000)
    mu_z1 = 1.5; sigma_z1 = 0.5;  # parameters of the "true" p(z_1)
    mu_z2 = 0.1; sigma_z2 = 0.01; # parameters of the "true" p(z_2)
    mu_y = mu_z1*x + mu_z2*x**2 # From Homework of Lecture 4
    sigma_y = np.sqrt( (x*sigma_z1)**2 + (x**2*sigma_z2)**2 ) # From Homework of Lecture 4
    ax.set_xlabel("x (m/s)", fontsize=20) # create x-axis label with font size 20
    ax.set_ylabel("y (m)", fontsize=20) # create y-axis label with font size 20
    ax.set_title("Car stopping distance problem with two rv's", fontsize=20); # create title with font size 20
    ax.plot(x, mu_y, 'k:', label="Governing model $\mu_y$")
    ax.fill_between(x, mu_y - 1.9600 * sigma_y,
                    mu_y + 1.9600 * sigma_y,
                    color='k', alpha=0.2,
                    label='95% confidence interval ($\mu_y \pm 1.96\sigma_y$)') # plot 95% credence interval
    ax.legend(fontsize=15)
```

```python
# This cell is also hidden during presentation.
from scipy.stats import norm # import the normal dist, as we learned before!
def samples_y_with_2rvs(N_samples,x): # observations/measurements/samples for car stop. dist. prob. with 2 rv's
    mu_z1 = 1.5; sigma_z1 = 0.5;
    mu_z2 = 0.1; sigma_z2 = 0.01;
    samples_z1 = norm.rvs(mu_z1, sigma_z1, size=N_samples) # randomly draw samples from the normal dist.
    samples_z2 = norm.rvs(mu_z2, sigma_z2, size=N_samples) # randomly draw samples from the normal dist.
    samples_y = samples_z1*x + samples_z2*x**2 # compute the stopping distance for samples of z_1 and z_2
    return samples_y # return samples of y
```

```python
# vvvvvvvvvvv this is just a trick so that we can run this cell multiple times vvvvvvvvvvv
fig_car_new, ax_car_new = plt.subplots(1,2); plt.close() # create figure and close it
if fig_car_new.get_axes():
    del ax_car_new; del fig_car_new # delete figure and axes if they exist
    fig_car_new, ax_car_new = plt.subplots(1,2) # create them again
# ^^^^^^^^^^^ end of the trick ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
N_samples = 3 # CHANGE THIS NUMBER AND RE-RUN THE CELL
x = 75; empirical_y = samples_y_with_2rvs(N_samples, x); # Empirical measurements of N_samples at x=75
empirical_mu_y = np.mean(empirical_y); empirical_sigma_y = np.std(empirical_y); # empirical mean and std
car_fig_2rvs(ax_car_new[0]) # a function I created to include the background plot of the governing model
for i in range(2): # create two plots (one is zooming in on the error bar)
    ax_car_new[i].errorbar(x , empirical_mu_y,yerr=1.96*empirical_sigma_y, fmt='m*', markersize=15);
    ax_car_new[i].scatter(x*np.ones_like(empirical_y),empirical_y, s=40,
                          facecolors='none', edgecolors='k', linewidths=2.0)
print("Empirical mean[y] is",empirical_mu_y, "(real mean[y]=675)")
print("Empirical std[y] is",empirical_sigma_y,"(real std[y]=67.6)")
fig_car_new.set_size_inches(25, 5) # scale figure to be wider (since there are 2 subplots)
```

## Recap of Lecture 5: Summary of our model

1. The **observation distribution**:

$$
p(y|z) = \mathcal{N}\left(y | \mu_{y|z}=w z+b, \sigma_{y|z}^2\right) = \frac{1}{C_{y|z}} \exp\left[ -\frac{1}{2\sigma_{y|z}^2}(y-\mu_{y|z})^2\right]
$$

where $C_{y|z} = \sqrt{2\pi \sigma_{y|z}^2}$ is the **normalization constant** of the Gaussian pdf, and where $\mu_{y|z}=w z+b$, with $w$, $b$ and $\sigma_{y|z}^2$ being constants.

2. and the **prior distribution**: $p(z) = \frac{1}{C_z}$

where $C_z = z_{max}-z_{min}$ is the **normalization constant** of the Uniform pdf, i.e. the value that guarantees that $p(z)$ integrates to one.

### Recap of Lecture 5: Data

* Since we usually don't know the true process, we can only observe/collect data $y=\mathcal{D}_y$:

```python
print("Example of N=%1i data points for y at x=%1.1f m/s with :" % (N_samples,x), empirical_y)
```

    Example of N=3 data points for y at x=75.0 m/s with : [641.47373038 733.92797742 637.52728953]

## Recap of Lecture 5: Posterior from Bayes' rule applied to data

Use Bayes' rule applied to data to determine the posterior:

$\require{color}$
$$
{\color{green}p(z|y=\mathcal{D}_y)} = \frac{ {\color{blue}p(y=\mathcal{D}_y|z)}{\color{red}p(z)} } {p(y=\mathcal{D}_y)}
$$

That requires calculating the likelihood (here, it results from a product of Gaussian densities):

$$
{\color{blue}p(y=\mathcal{D}_y | z)} = \frac{1}{|w|^N} \cdot C \cdot \frac{1}{\sqrt{2\pi \sigma^2}} \exp\left[ -\frac{1}{2\sigma^2}(z-\mu)^2\right]
$$

where $\mu = \frac{w^2\sigma^2}{\sigma_{y|z}^2} \sum_{i=1}^N \mu_i$

$\sigma^2 = \frac{\sigma_{y|z}^2}{w^2 N}$, and

$C = \frac{1}{2\pi^{(N-1)/2}} \sqrt{\frac{\sigma^2}{\left( \frac{\sigma_{y|z}^2}{w^2}\right)^N}}
$

After calculating the likelihood, we determined the marginal likelihood:

$$
p(y=\mathcal{D}_y) = \frac{C}{|w|^N C_z}
$$

From which we got the posterior:

$$\require{color}\begin{align}
{\color{green}p(z|y=\mathcal{D}_y)} &= \frac{ p(y=\mathcal{D}_y|z)p(z) } {p(y=\mathcal{D}_y)} \\
&= \frac{1}{p(y=\mathcal{D}_y)} \cdot \frac{1}{|w|^N} C \cdot \mathcal{N}(z|\mu,\sigma^2) \cdot \frac{1}{C_z} \\
&= \mathcal{N}(z|\mu, \sigma^2)
\end{align}
$$

which is a **normalized** Gaussian pdf in $z$ with mean and variance as shown in the previous cell.

## Determining the Posterior Predictive Distribution (PPD) from the posterior

However, as we mentioned, Bayes' rule is just a way to calculate the posterior:

$$
p(z|y=\mathcal{D}_y) = \frac{ p(y=\mathcal{D}_y|z)p(z) } {p(y=\mathcal{D}_y)}
$$

What we really want is the Posterior Predictive Distribution (PPD) . This comes after calculating the posterior given some data $\mathcal{D}_y$:

$$\require{color}
{\color{orange}p(y|y=\mathcal{D}_y)} = \int p(y|z) p(z|y=\mathcal{D}_y) dz
$$

which is often written in simpler notation: $p(y|\mathcal{D}_y) = \int p(y|z) p(z|\mathcal{D}_y) dz$

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \int \underbrace{p(y|z)}_{\text{observation}\\ \text{distribution}} \overbrace{p(z|y=\mathcal{D}_y)}^{\text{posterior}} dz
$$

Considering the terms we found before, we get:

$$\begin{align}
p(y|\mathcal{D}_y) &= \int \underbrace{\frac{1}{|w|}\frac{1}{\sqrt{2\pi \left(\frac{\sigma_{y|z}}{w}\right)^2}} \exp\left\{ -\frac{1}{2\left(\frac{\sigma_{y|z}}{w}\right)^2}\left[z-\left(\frac{y-b}{w}\right)\right]^2\right\} }_{\text{observation}\\ \text{distribution}} \overbrace{\mathcal{N}(z|\mu, \sigma^2)}^{\text{posterior}} dz
\end{align}
$$

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \frac{1}{|w|} \int {\color{blue}\frac{1}{\sqrt{2\pi \left(\frac{\sigma_{y|z}}{w}\right)^2}} \exp\left\{ -\frac{1}{2\left(\frac{\sigma_{y|z}}{w}\right)^2}\left[z-\left(\frac{y-b}{w}\right)\right]^2\right\}} \mathcal{N}(z|\mu, \sigma^2) dz
$$

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \frac{1}{|w|} \int \mathcal{N}\left(z\left|\frac{y-b}{w}, \left(\frac{\sigma_{y|z}}{w}\right)^2\right.\right) \mathcal{N}(z|\mu, \sigma^2) dz
$$

This is (again!) the product of two Gaussians!

In Lecture 5 (and the Homework!) you saw (and demonstrated!) that the product of two or more univariate (and multivariate!) Gaussians is...

* Another Gaussian! Although it needs to be scaled by a constant...

So, we conclude that the PPD is an integral of a Gaussian:

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \frac{1}{|w|} \int  C^* \mathcal{N}\left(z|\mu^*, \left(\sigma^*\right)^2\right) dz
$$

where $\mu^* = \left(\sigma^* \right)^2 \left( \frac{\mu}{\sigma^2} + \frac{(y-b)/w}{\left(\frac{\sigma_{y|z}}{w}\right)^2} \right) = \left(\sigma^* \right)^2 \left( \frac{\mu}{\sigma^2} + \frac{(y-b)\cdot w}{\sigma_{y|z}^2} \right)$

$\left( \sigma^* \right)^2 = \frac{1}{\frac{1}{\sigma^2}+\frac{1}{\left( \frac{\sigma_{y|z}}{w}\right)^2}}= \frac{1}{\frac{1}{\sigma^2}+\frac{w^2}{\sigma_{y|z}^2}}$

$C^* = \frac{1}{\sqrt{2\pi \left( \sigma^2 + \frac{\sigma_{y|z}^2}{w^2} \right)}}\exp\left[ - \frac{\left(\mu - \frac{y-b}{w}\right)^2}{2\left( \sigma^2+\frac{\sigma_{y|z}^2}{w^2}\right)}\right]$

This integral is simple to solve!

$$\require{color}
\begin{align}
{\color{orange}p(y|\mathcal{D}_y)} &= \frac{1}{|w|} \int  C^* \mathcal{N}\left(z|\mu^*, \left(\sigma^*\right)^2\right) dz \\
&= \frac{C^*}{|w|} \int   {\color{blue}\mathcal{N}\left(z|\mu^*, \left(\sigma^*\right)^2\right)} dz
\end{align}
$$

What's the result of integrating the blue term?

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \frac{C^*}{|w|}
$$

## Exercise 1

Rewrite the PPD to show that it becomes:

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \mathcal{N}\left(y| b+\mu w, w^2\sigma^2+\sigma_{y|z}^2 \right)
$$

a normalized univariate Gaussian!

## A long way to show that the PPD is a simple Gaussian...

$$\require{color}
{\color{orange}p(y|\mathcal{D}_y)} = \mathcal{N}\left(y| b+\mu w, w^2\sigma^2+\sigma_{y|z}^2 \right)
$$

where we recall that each constant is:

$b = 0.1x^2 = 562.5$

$w = x = 75$

$\sigma_{y|z}^2 = (x^2 \sigma_{z_2})^2=(75^2\cdot0.01)^2=56.25^2$

$\sigma^2 = \frac{\sigma_{y|z}^2}{w^2 N} = \frac{(x^2\cdot\sigma_{z_2})^2}{x^2 N} = \frac{x^2\cdot\sigma_{z_2}^2}{N} $

$\mu = \frac{w^2 \sigma^2}{\sigma_{y|z}^2} \sum_{i=1}^{N} \mu_i = \cdots = \frac{\sum_{i=1}^N y_i}{w N}-\frac{b}{w}$

#### Note on algebra to determine $\mu$ parameter:

$$
\begin{align}
\mu &= \frac{w^2 \sigma^2}{\sigma_{y|z}^2} \sum_{i=1}^{N} \mu_i \\
&= \frac{\sum_{i=1}^{N} \mu_i}{N} = \frac{1}{N} \sum_{i=1}^{N} \left(\frac{y_i-b}{w}\right) \\
&= \frac{1}{w N} \sum_{i=1}^N\left( y_i-b\right) \\
&= \frac{1}{w N} \left( \sum_{i=1}^N y_i-N b\right) \\
& = \frac{\sum_{i=1}^N y_i}{w N}-\frac{b}{w}
\end{align}
$$

## A long way to show that the PPD is a simple Gaussian...

$$\require{color}
\begin{align}
{\color{orange}p(y|\mathcal{D}_y)} &= \mathcal{N}\left(y| b+\mu w, w^2\sigma^2+\sigma_{y|z}^2 \right) \\
&= \mathcal{N}\left(y \left| \left(\sum_{i=1}^N \frac{y_i}{N}\right), \sigma_{y|z}^2 \left(\frac{1}{N} + 1 \right) \right. \right)
\end{align}
$$

where $y_i$ are each of the $N$ data points of the observed data $\mathcal{D}_y$, and $\sigma_{y|z}^2 = (x^2 \sigma_{z_2})^2=(75^2\cdot0.01)^2=56.25^2$ is the variance arising from the contribution of $z_2$ on $y$.

* **Very Important Questions (VIQs)**: What does this result tell us? Did you expect this predicted distribution for $y$?

```python
fig_car_PPD, ax_car_PPD = plt.subplots(1,2); plt.close() # create figure and close it
if fig_car_new.get_axes():
    del ax_car_PPD; del fig_car_PPD; fig_car_PPD, ax_car_PPD = plt.subplots(1,2) # delete fig & axes & create them
N_samples = 30000 # CHANGE THIS NUMBER AND RE-RUN THE CELL
x = 75; empirical_y = samples_y_with_2rvs(N_samples, x); # Empirical measurements of N_samples at x=75
empirical_mu_y = np.mean(empirical_y); empirical_sigma_y = np.std(empirical_y); # empirical mean and std
# Calculate PPD mean and standard deviation:
PPD_mu_y = np.mean(empirical_y); sigma_z2 = 0.01; PPD_sigma_y = np.sqrt( (x**2*sigma_z2)**2*(1/N_samples + 1) )
car_fig_2rvs(ax_car_PPD[0]) # a function I created to include the background plot of the governing model
for i in range(2): # create two plots (one is zooming in on the error bar)
    ax_car_PPD[i].errorbar(x , empirical_mu_y,yerr=1.96*empirical_sigma_y, fmt='m*', markersize=15, elinewidth=6);
    ax_car_PPD[i].errorbar(x , PPD_mu_y,yerr=1.96*PPD_sigma_y, color='#F39C12', fmt='*', markersize=5, elinewidth=3);
    ax_car_PPD[i].scatter(x*np.ones_like(empirical_y),empirical_y, s=100,facecolors='none', edgecolors='k', linewidths=2.0)
print("PPD & empirical mean[y] are the same:",empirical_mu_y, "(real mean[y]=675)")
print("PPD std[y] is",PPD_sigma_y, "& empirical std[y] is",empirical_sigma_y,"(real std[y]=67.6)")
fig_car_PPD.set_size_inches(25, 5) # scale figure to be wider (since there are 2 subplots)
```

### Reflection on what we are observing

1. Generally speaking, our PPD is quite reasonable!
    * For few data points it is more reasonable than just calculating the standard deviation directly from the data.

2. However, as the number of data points increases it starts getting "overconfident" (see PPD as $N\rightarrow \infty$ or play with the figure above by increasing $N$).
    * This results from our choice of prior... Our belief was incorrect.
        * The hidden rv $z$ is actually a Gaussian distribution, instead of a noninformative Uniform distribution

Please keep this in your head:
* (Bayesian) ML is not magic. Every modeling choice you make affects the predictions you get.
* Of course, there are ways of getting "closer" to the truth! We'll take some steps in that direction in the remainder of the course.

# HOMEWORK

Consider the same problem, but now starting from a different model:

1. Same **observation distribution** as before:

$$
p(y|z) = \mathcal{N}\left(y | \mu_{y|z}=w z+b, \sigma_{y|z}^2\right) = \frac{1}{C_{y|z}} \exp\left[ -\frac{1}{2\sigma_{y|z}^2}(y-\mu_{y|z})^2\right]
$$

2. but now assuming a different **prior distribution**: $p(z) = \mathcal{N}\left(z| \overset{\scriptscriptstyle <}{\mu}_z=3, \overset{\scriptscriptstyle <}{\sigma}_z^2=2^2
\right)$

In my notation, the superscript $\overset{\scriptscriptstyle <}{(\cdot)}$ indicates a parameter of the prior distribution.

### Notes about the prior distribution

* We would have to be very lucky if our "belief" coincided with the "true" distribution of $z$.
    * Usually, we have beliefs but they are not really true (not talking about religion 😆).
        - Our hope is that our beliefs are at least reasonable!

* When defining a prior we are making a decision about two things:
    1. The distribution.
        * For example, in this exercise we are assuming that the prior is Gaussian (before we assumed a noninformative Uniform prior). In this case we hit the jackpot! But remember that we are cheating here... That's why we know the actual distribution of $z$ is a Gaussian!
    2. The parameters of the distribution.
        * For example, in this exercise we are assuming values that are not the true ones! This is normal! As I said, usually we don't know the truth about the "hidden" variable. Most times we don't even know how  many hidden variables we have...

### See you next class

Have fun!