# ESG-score using a MiniMax Model

## Problem setting:

As a Business-idea firm xy plans to launch a new app, in which clients are able to invest in a socially responsible manner. The app has to be able to compute for every portfolio an Environmental, Social and Governance (ESG) score between 0 and 100, where a higher score indicates a more responsible investment. Using an optimization process for each client a customized portfolio that takes into account the provided target values should be derived. Since firm xy is based in Switzerland, the initial investments universe comprises the stocks listed on SMI. The files prices1617.csv, prices18.csv and scores.csv contain the weekly closing prices of the SMI and its constituents for the years 2016-2017 and 2018 as well as the latest ESG scores for all SMI constituents, respectively.

```python
# Import packages
import pandas as pd
import numpy as np
import gurobipy as gb
import matplotlib.pyplot as plt
```

```python
# Define precision of number display
pd.set_option('precision', 5)
```

```python
# Import prices
weekly_prices = pd.read_csv('prices1617.csv', index_col='date')
# Look at first five rows
weekly_prices.head()
```

      SGS
      SWISSCOM
      GIVAUDAN
      ZURICH INSURANCE
      NOVARTIS
      ROCHE
      CREDIT SUISSE
      ADECCO
      LAFARGEHOLCIM
      ABB
      ...
      LONZA
      SWISS LIFE
      GEBERIT
      NESTLE
      JULIUS BAER
      SWISS RE
      RICHEMONT
      UBS
      SIKA
      SMI

      date

      2016-01-01
      1911
      503.0
      1823
      258.4
      86.80
      276.4
      20.929
      68.047
      50.30
      17.96
      ...
      151.078
      271.3
      340.2
      74.55
      48.66
      98.15
      72.10
      19.52
      60.335
      8818.09

      2016-01-08
      1757
      488.3
      1714
      241.4
      82.10
      263.0
      19.028
      62.368
      46.00
      16.43
      ...
      143.668
      257.2
      318.1
      70.30
      44.18
      93.25
      66.05
      17.46
      58.584
      8257.28

      2016-01-15
      1770
      478.2
      1772
      242.8
      80.35
      256.5
      18.054
      59.356
      42.26
      16.18
      ...
      138.017
      262.3
      332.3
      70.10
      42.02
      92.60
      63.10
      16.71
      58.451
      8107.13

      2016-01-22
      1932
      489.0
      1812
      224.4
      82.90
      265.3
      17.359
      60.837
      44.12
      16.77
      ...
      143.575
      256.8
      342.4
      72.10
      42.59
      92.60
      64.35
      16.83
      58.818
      8271.11

      2016-01-29
      1983
      507.5
      1907
      225.8
      78.50
      263.9
      17.369
      61.726
      42.92
      17.57
      ...
      144.779
      259.6
      361.8
      75.20
      43.25
      94.80
      66.25
      16.83
      60.951
      8319.81

5 rows × 21 columns

```python
# Compute weekly returns
weekly_returns = weekly_prices / weekly_prices.shift(1) - 1
# Drop NA-return (first period)
weekly_returns = weekly_returns.dropna()
# Look at first five rows
weekly_returns.head()
```

      SGS
      SWISSCOM
      GIVAUDAN
      ZURICH INSURANCE
      NOVARTIS
      ROCHE
      CREDIT SUISSE
      ADECCO
      LAFARGEHOLCIM
      ABB
      ...
      LONZA
      SWISS LIFE
      GEBERIT
      NESTLE
      JULIUS BAER
      SWISS RE
      RICHEMONT
      UBS
      SIKA
      SMI

      date

      2016-01-08
      -0.08059
      -0.02922
      -0.05979
      -0.06579
      -0.05415
      -0.04848
      -0.09083
      -0.08346
      -0.08549
      -0.08519
      ...
      -0.04905
      -0.05197
      -0.06496
      -0.05701
      -0.09207
      -0.04992
      -0.08391
      -0.10553
      -0.02902
      -0.06360

      2016-01-15
      0.00740
      -0.02068
      0.03384
      0.00580
      -0.02132
      -0.02471
      -0.05119
      -0.04829
      -0.08130
      -0.01522
      ...
      -0.03933
      0.01983
      0.04464
      -0.00284
      -0.04889
      -0.00697
      -0.04466
      -0.04296
      -0.00227
      -0.01818

      2016-01-22
      0.09153
      0.02258
      0.02257
      -0.07578
      0.03174
      0.03431
      -0.03850
      0.02495
      0.04401
      0.03646
      ...
      0.04027
      -0.02097
      0.03039
      0.02853
      0.01356
      0.00000
      0.01981
      0.00718
      0.00628
      0.02023

      2016-01-29
      0.02640
      0.03783
      0.05243
      0.00624
      -0.05308
      -0.00528
      0.00058
      0.01461
      -0.02720
      0.04770
      ...
      0.00839
      0.01090
      0.05666
      0.04300
      0.01550
      0.02376
      0.02953
      0.00000
      0.03626
      0.00589

      2016-02-05
      -0.00050
      -0.04926
      -0.02727
      -0.05492
      -0.05860
      -0.04433
      -0.16944
      -0.04240
      -0.09110
      0.00171
      ...
      -0.06206
      -0.06895
      -0.02073
      -0.02460
      -0.08393
      -0.05432
      -0.01132
      -0.09626
      -0.00710
      -0.04323

5 rows × 21 columns

```python
# Compute expected returns
all_expected_returns = weekly_returns.mean()
all_expected_returns
```

    SGS                 0.00303
    SWISSCOM            0.00048
    GIVAUDAN            0.00229
    ZURICH INSURANCE    0.00178
    NOVARTIS           -0.00014
    ROCHE              -0.00075
    CREDIT SUISSE      -0.00044
    ADECCO              0.00132
    LAFARGEHOLCIM       0.00155
    ABB                 0.00394
    SWATCH              0.00190
    LONZA               0.00575
    SWISS LIFE          0.00269
    GEBERIT             0.00250
    NESTLE              0.00129
    JULIUS BAER         0.00250
    SWISS RE           -0.00038
    RICHEMONT           0.00243
    UBS                 0.00002
    SIKA                0.00768
    SMI                 0.00076
    dtype: float64

```python
# Get array of stocks (without index SMI)
stocks = weekly_returns.columns.drop('SMI')
```

## Portfolio 1 (P1):
As a first idea for a portfolio selection an equally weighted portfolio approach is chosen. This portfolio is referred by P1.
In a first step the variance, mean-absolute deviation, Beta (relative to SMI), minimum return and the expected downside $(r_t =1\%)$ for the portfolio P1 as well as for the SMI are computed.

```python
# Create equally weighted portfolio
weights_p1 = pd.Series(1 / len(stocks), index=stocks)
weekly_returns['P1'] = weekly_returns[stocks].dot(weights_p1)
```

```python
# Calculate variance
weekly_returns[['P1', 'SMI']].var()
```

    P1     0.00037
    SMI    0.00032
    dtype: float64

```python
# Calculate mean-absolute deviation
weekly_returns[['P1', 'SMI']].mad()
```

    P1     0.01401
    SMI    0.01338
    dtype: float64

```python
# Compute the beta of P1
weekly_returns['P1'].cov(weekly_returns['SMI']) / weekly_returns['SMI'].var()
```

    0.9763988106997943

```python
# Compute the beta of SMI
weekly_returns['SMI'].cov(weekly_returns['SMI']) / weekly_returns['SMI'].var()
```

    1.0

```python
# Compute the minimum return
weekly_returns[['P1', 'SMI']].min()
```

    P1    -0.06835
    SMI   -0.06360
    dtype: float64

```python
# Define target return
target_return = 0.01

# Compute expected downside with respect to given target return
np.maximum(0, -(weekly_returns[['P1', 'SMI']] - target_return)
          ).sum() / weekly_returns['P1'].size
```

    P1     0.01160
    SMI    0.01221
    dtype: float64

### Note:
In a next step the ESG Environmental, ESG Governance and ESG Social scores of P1 are computed.

```python
# Import ratings
scores = pd.read_csv('scores.csv', index_col='scores')
```

```python
# Look at first five rows
scores.head()
```

      SGS
      SWISSCOM
      GIVAUDAN
      ZURICH INSURANCE
      NOVARTIS
      ROCHE
      CREDIT SUISSE
      ADECCO
      LAFARGEHOLCIM
      ABB
      SWATCH
      LONZA
      SWISS LIFE
      GEBERIT
      NESTLE
      JULIUS BAER
      SWISS RE
      RICHEMONT
      UBS
      SIKA

      scores

      ESG Environmental
      80
      89
      72
      93
      75
      97
      88
      70
      60
      96
      62
      97
      81
      91
      85
      79
      92
      66
      91
      85

      ESG Social
      97
      61
      63
      75
      85
      95
      82
      77
      81
      96
      38
      97
      71
      74
      94
      66
      70
      71
      97
      49

      ESG Governance
      75
      74
      66
      81
      66
      68
      77
      77
      68
      79
      16
      58
      34
      53
      49
      77
      71
      38
      89
      45

```python
# Get ESG scores for P1
scores.dot(weights_p1)
```

    scores
    ESG Environmental    82.45
    ESG Social           76.95
    ESG Governance       63.05
    dtype: float64

### Question:
Does a distinct relationship between the expected returns and the average ESG scores exist? Do more sustainable stocks tend to have greatly inferior performance than less sustainable stocks?

```python
# Get average ESG scores
average_esg_score = scores.mean()
```

```python
# Visualize average ESG scores and expected returns in scatterplot
plt.figure(figsize=(9, 3), dpi=120)
plt.scatter(average_esg_score, all_expected_returns[stocks], alpha=0.6)
plt.xlabel('ESG average score')
plt.ylabel('Expected return');
```

The data does not support the claim. It appears that no distinct relationship between the expected returns and the average ESG scores exist.

## Portfolio 2 (P2):
As an alternative the firm xy has the idea to implement a MiniMax model as the underlying portfolio selection model (P2). Further a blacklist is introduced that consist of two pharmaceutical companies: Novartis and Roche. The weights for stocks on the blacklist must be zero in all subsequent portfolios.

### Note:
A MiniMax(MM) model trades off the minimum portfolio rerun against the expected portfolio return. Therefore the minimum portfolio return is a downside risk measure. It is possible to formulate a MM model as a linear optimization problem.

Some technical characteristics of the MM model:
* Minimum return is homogeneous
* Minimum return is monotone
* Minimum return is subadditive
* Minimum return is translational invariant

=> Therefore the Minimum return is coherent

Notation:
* $\alpha$ Minimum portfolio return over all scenarios

\begin{align}
Max. \alpha\\
\sum_{i=n}^n r_{il}x_i \geq \alpha \quad (l \in \Omega)\\
\sum_{i=n}^n \bar r_ix_i = \mu \\
\sum_{i=n}^n x_i =1 \\
x_i \geq 0 \quad (i =1,...,n) \\
\alpha \in \Re
\end{align}

```python
# Get array of scenarios
scenarios = weekly_returns.index
# Compute expected returns
expected_returns = all_expected_returns[stocks]
# Define target portfolio return
target_return = all_expected_returns['SMI']
```

```python
# Create model
m = gb.Model('MM')

# Add a variable for each stock
x = pd.Series(m.addVars(stocks), index=stocks)

# Add a variable which represents the minimum portfolio return
minimum_return = m.addVar(lb=weekly_returns[stocks].min().min())

# Define portfolio return
portfolio_return = expected_returns.dot(x)

# Add objective function to model
m.setObjective(minimum_return, gb.GRB.MAXIMIZE)

# Add minimum return constraints
for l in scenarios:
    m.addConstr(weekly_returns.loc[l, stocks].dot(x) >= minimum_return)

# Add budget constraint
m.addConstr(x.sum() == 1);

# Add target return constraint
target_return_constr = m.addConstr(portfolio_return == target_return)

# Add blacklist constraints
m.addConstr(x.loc['ROCHE'] == 0)
m.addConstr(x.loc['NOVARTIS'] == 0);

# Specify Gurobi options
m.setParam('OutputFlag', 0)

# Run optimization
m.optimize()

# Get weights of stocks in optimal solution
weights_p2 = pd.Series([var.X for var in x], index=stocks)

# Get ESG scores for P2
scores[stocks].dot(weights_p2)
```

### Question:
What is the minimum return of P2 and how does it stand up compared to the SMI?

```python
# Get minimum return of P2
minimum_return_p2 = minimum_return.X
minimum_return_p2
```

    -0.03953015537895124

```python
# Is minimum return of P2 bigger than minimum return of SMI?
minimum_return.X > weekly_returns['SMI'].min()
```

    True

The minimum return of P2 (-0.03953) is larger (less negative) than the minimum return of the SMI (-0.06360) and thus, the SMI is not efficient

```python
# Get weights of stocks in optimal solution
weights_p2
```

    SGS                 0.00000
    SWISSCOM            0.72773
    GIVAUDAN            0.00000
    ZURICH INSURANCE    0.00000
    NOVARTIS            0.00000
    ROCHE               0.00000
    CREDIT SUISSE       0.00000
    ADECCO              0.00000
    LAFARGEHOLCIM       0.00000
    ABB                 0.00000
    SWATCH              0.06426
    LONZA               0.00000
    SWISS LIFE          0.00000
    GEBERIT             0.00000
    NESTLE              0.20606
    JULIUS BAER         0.00000
    SWISS RE            0.00000
    RICHEMONT           0.00000
    UBS                 0.00000
    SIKA                0.00196
    dtype: float64

## Note:
Adjust the target return constraint of the model such that the optimal portfolio has an expected return equal to or greater than the target return (P3).

\begin{align}
Max. \alpha\\
\sum_{i=n}^n r_{il}x_i \geq \alpha \quad (l \in \Omega)\\
\sum_{i=n}^n \bar r_ix_i \geq \mu \\
\sum_{i=n}^n x_i =1 \\
x_i \geq 0 \quad (i =1,...,n) \\
\alpha \in \Re
\end{align}

```python
# Remove old target return constraint
m.remove(target_return_constr)
```

```python
# Add new target return constraint
target_return_constr = m.addConstr(portfolio_return >= target_return)
```

```python
# Run optimization
m.optimize()
```

```python
# Get minimum return of P3
minimum_return.X
```

    -0.03195969273382321

The minimum return impoves from -3.95% in P2 to -3.2% in P3.

```python
# Get weights of stocks in optimal solution
weights_p3 = pd.Series([var.X for var in x], index=stocks)
weights_p3
```

    SGS                 0.00000
    SWISSCOM            0.52387
    GIVAUDAN            0.00000
    ZURICH INSURANCE    0.00000
    NOVARTIS            0.00000
    ROCHE               0.00000
    CREDIT SUISSE       0.00000
    ADECCO              0.00000
    LAFARGEHOLCIM       0.00000
    ABB                 0.00000
    SWATCH              0.00000
    LONZA               0.00000
    SWISS LIFE          0.00000
    GEBERIT             0.00000
    NESTLE              0.10118
    JULIUS BAER         0.00000
    SWISS RE            0.00000
    RICHEMONT           0.00000
    UBS                 0.00000
    SIKA                0.37495
    dtype: float64

## Portfolio 4 (P4)
In order to develop a variant of the minimax model that additionally takes into account the investors' target values for the three ESG score a separate penalty for each of the three ESG scores is implemented. If the portfolio score is equal to or greater than the target value, then the penalty is zero. If the portfolio score is smaller than the target value, then the penalty corresponds to the difference between the target value and the portfolio score.
$penalty_{ESG} = max(taregt ESG score - portfolio ESG score,0)$

However, firm xy decides that a linear increasing penalty is not appropriate. It should be ensured that large shortfalls are penalized heavily. The structure of the three penalty functions can be seen in the following graphs:

Additionally the already introduced constraints have to hold:
* The portfolio return does not fall below a prespecified minimum portfolio return $\alpha$ in any scenario.
* The expected portfolio return is equal to or greater than a prespecified target return µ.
* The sum of the weights is equal to 1.
* All portfolio weights are non-negative.
* The weights for stocks on the blacklist must be zero.

Formulate a linear optimization problem in which the sum of the three penalties (given the structures in the graphs) is minimized subject to all constraints.

### Note:
The resulting optimization problem:

Notation:
* $S_i^E$: ESG Envionmental score of stock i
* $S_i^S$: ESG Social score of stock i
* $S_i^G$: ESG Governance score of stock i
* $S_t^E$: Target ESG Environmental value
* $S_t^S$: Target ESG Socail value
* $S_t^G$: Target ESG Governance value
* $x_i$: weight of asset i
* $\delta_{Ej}$:  Shortfall of portfolio ESG Environmental score with respect to target ESG Environmental value in jth segment of penalty function (j = 1; 2)
* $\delta_{Sj}$: Shortfall of portfolio ESG Social score with respect to target ESG Social value in jth segment of penalty function (j = 1; 2)
* $\delta_{Gj}$: Shortfall of portfolio ESG Governance score with respect to target ESG Governance value in jth segment of penalty function (j = 1; 2)

\begin{align}
Min. (8/10)\delta_{E1} + (12/10)\delta_{E2} + (10/10)\delta_{S1} + (20/10)\delta_{S2} + (4/10) \delta_{G1} + (8/10)\delta_{G2}\\
\sum_{i=n}^n r_{il}x_i \geq \alpha \quad (l \in \Omega)\\
\sum_{i=n}^n \bar r_ix_i \geq \mu \\
\sum_{i=n}^n x_i =1 \\
\sum_{i=n}^n S_i^E x_i  \geq S_t^E - \delta_{E1} - \delta_{E2}\\
\sum_{i=n}^n S_i^S x_i  \geq S_t^S - \delta_{S1} - \delta_{S2}\\
\sum_{i=n}^n S_i^G x_i  \geq S_t^G - \delta_{G1} - \delta_{G2}\\
\delta_{E1} \leq 10 \\
\delta_{S1} \leq 10 \\
\delta_{G1} \leq 10 \\
x_i \geq 0 \quad (i =1,...,n) \\
x_i = 0 \quad (i = Novartis, Roche)\\
\delta_{E1},\delta_{E2},\delta_{S1},\delta_{S2},\delta_{G1},\delta_{G2} \geq 0\\
\end{align}

```python
# Define target ESG values
S_t_E = 100
S_t_S = 100
S_t_G = 100
```

```python
# Create model
m = gb.Model('MM2')

# Add a variable for each stock
x = pd.Series(m.addVars(stocks), index=stocks)

# Add variables for ESG environmental penalties
delta_e1 = m.addVar()
delta_e2 = m.addVar()

# Add variables for ESG social penalties
delta_s1 = m.addVar()
delta_s2 = m.addVar()

# Add variables for ESG governance penalties
delta_g1 = m.addVar()
delta_g2 = m.addVar()

# Define minimum return
minimum_return = minimum_return_p2

# Define portfolio return
portfolio_return = expected_returns.dot(x)

# Define portfolio environmental score
portfolio_environmental_score = scores.loc['ESG Environmental', stocks].dot(x)

# Define portfolio social score
portfolio_social_score = scores.loc['ESG Social', stocks].dot(x)

# Define portfolio governance score
portfolio_governance_score = scores.loc['ESG Governance', stocks].dot(x)

# Add objective function to model
m.setObjective((8 / 10) * delta_e1 + (12 / 10) * delta_e2
               + (10 / 10) * delta_s1 + (20 / 10) * delta_s2
               + (4 / 10) * delta_g1 + (8 / 10) * delta_g2, gb.GRB.MINIMIZE)

# Add constraints for ESG penalties to model
ESG_environmental_penalty_constraint = m.addConstr(
    portfolio_environmental_score >= S_t_E - delta_e1 - delta_e2)
ESG_social_penalty_constraint = m.addConstr(
    portfolio_social_score >= S_t_S - delta_s1 - delta_s2)
ESG_governance_penalty_constraint = m.addConstr(
    portfolio_governance_score >= S_t_G - delta_g1 - delta_g2)
m.addConstr(delta_e1 <= 10)
m.addConstr(delta_s1 <= 10)
m.addConstr(delta_g1 <= 10);

# Add minimum return constraints
for l in scenarios:
    m.addConstr(weekly_returns.loc[l, stocks].dot(x) >= minimum_return)

# Add budget constraint
m.addConstr(x.sum() == 1);

# Add blacklist constraints
m.addConstr(x.loc['ROCHE'] == 0)
m.addConstr(x.loc['NOVARTIS'] == 0);

# Add target return constraint
m.addConstr(portfolio_return >= target_return);

# Specify Gurobi options
m.setParam('OutputFlag', 0)

# Run optimization
m.optimize()

# Get portfolio return
portfolio_return.getValue()
```

    0.004028207115997125

```python
# Get weights of stocks in optimal solution
weights_p4 = pd.Series([var.X for var in x], index=stocks)
weights_p4
```

    SGS                 0.00000
    SWISSCOM            0.29316
    GIVAUDAN            0.00000
    ZURICH INSURANCE    0.00000
    NOVARTIS            0.00000
    ROCHE               0.00000
    CREDIT SUISSE       0.00000
    ADECCO              0.00000
    LAFARGEHOLCIM       0.00000
    ABB                 0.00000
    SWATCH              0.00000
    LONZA               0.31895
    SWISS LIFE          0.00000
    GEBERIT             0.00000
    NESTLE              0.14513
    JULIUS BAER         0.00000
    SWISS RE            0.00000
    RICHEMONT           0.00000
    UBS                 0.00000
    SIKA                0.24276
    dtype: float64

```python
# Get ESG ratings P3
scores[stocks].dot(weights_p3)
```

    scores
    ESG Environmental    87.09548
    ESG Social           59.83969
    ESG Governance       60.59696
    dtype: float64

```python
# Get ESG Environmental score for P4
portfolio_environmental_score.getValue()
```

    90.0

```python
# Get ESG Social score for P4
portfolio_social_score.getValue()
```

    74.3583947757379

```python
# Get ESG Governance score for P4
portfolio_governance_score.getValue()
```

    58.22836587606057

### Question:
Assume that the model and the parameters $\alpha$ and $\mu$ are given from above. Is it possible to consturct a portfolio with its Envionmental score beeing at least 70 and 65 for the Social and Governance scores? If yes, what is the expected portfolio return?

```python
# Adjust right-hand side of ESG penalty constraints
ESG_environmental_penalty_constraint.rhs = 70
ESG_social_penalty_constraint.rhs = 65
ESG_governance_penalty_constraint.rhs = 65
```

```python
# Specify Gurobi options
m.setParam('OutputFlag', 0)
```

```python
# Run optimization
m.optimize()
```

```python
# Get portfolio return
portfolio_return.getValue()
```

    0.0008908322172577389

```python
# Get objective function value
m.ObjVal
```

    0.0

```python
# Get ESG Environmental score
portfolio_environmental_score.getValue()
```

    87.44291684099434

```python
# Get ESG Social score
portfolio_social_score.getValue()
```

    72.74805099526803

```python
# Get ESG Governance score
portfolio_governance_score.getValue()
```

    65.00000000000001

### Note:
Yes, such a portfolio is possible to construct without having to take on a penalty which is indidicated by the objective value beeing equal to zero. Further such a portfolio would have a expected return of 0.18%.

### Question:
Given an investor whose main concern only consists of the highest possible Governance score? How would such a portfolio look like and what are the associated ESG-scores?

```python
# Adjust right-hand side of ESG penalty constraints
ESG_environmental_penalty_constraint.rhs = 0
ESG_social_penalty_constraint.rhs = 0
ESG_governance_penalty_constraint.rhs = 100
```

```python
# Specify Gurobi options
m.setParam('OutputFlag', 0)
```

```python
# Run optimization
m.optimize()
```

```python
# Get portfolio return
portfolio_return.getValue()
```

    0.001378912909258574

```python
# Get objective function value
m.ObjVal
```

    16.899489938619062

```python
# Get ESG Environmental score
portfolio_environmental_score.getValue()
```

    90.10740054392176

```python
# Get ESG Social score
portfolio_social_score.getValue()
```

    66.65023035929603

```python
# Get ESG Governance score
portfolio_governance_score.getValue()
```

    73.87563757672618

### Note:
A portfolio ESG Governance score of 73.88 can be achieved. The portfolio ESG Social score takes a value of 66.65 and the portfolio ESG Enviornmental score a value of 90.11.

## Perfomance evalation
In order to evaluate our portfolios (P1-P4) and the SMI we use a second datasample containing of the weekly closing prices from the year 2018. Imagine that 1'000'000 CHF are invesed in each portflio and that it is possible to buy a fracitonal number of units.

```python
# Import prices
weekly_prices_evaluation = pd.read_csv('prices18.csv', index_col='date')
weekly_prices_evaluation.head()
```

      SGS
      SWISSCOM
      GIVAUDAN
      ZURICH INSURANCE
      NOVARTIS
      ROCHE
      CREDIT SUISSE
      ADECCO
      LAFARGEHOLCIM
      ABB
      ...
      LONZA
      SWISS LIFE
      GEBERIT
      NESTLE
      JULIUS BAER
      SWISS RE
      RICHEMONT
      UBS
      SIKA
      SMI

      date

      2018-01-05
      2602
      524.8
      2327
      300.1
      84.08
      252.20
      17.640
      78.90
      58.00
      26.92
      ...
      270.9
      350.7
      436.7
      83.74
      61.46
      92.26
      89.54
      18.360
      134.503
      9556.98

      2018-01-12
      2575
      520.4
      2270
      313.6
      83.88
      245.80
      18.290
      79.42
      58.90
      27.13
      ...
      264.5
      356.0
      430.0
      82.48
      63.24
      92.96
      89.34
      18.895
      138.419
      9546.61

      2018-01-19
      2635
      520.0
      2295
      317.6
      83.38
      234.50
      18.280
      78.90
      59.58
      26.98
      ...
      270.2
      358.8
      463.0
      82.28
      63.86
      94.46
      90.10
      19.325
      139.003
      9509.77

      2018-01-26
      2559
      518.8
      2211
      315.3
      87.74
      231.45
      18.605
      78.78
      58.94
      26.56
      ...
      270.6
      357.0
      450.5
      81.00
      63.66
      93.54
      88.80
      19.355
      136.419
      9515.56

      2018-02-02
      2469
      506.8
      2179
      307.8
      82.64
      224.15
      18.060
      75.30
      56.28
      25.67
      ...
      251.6
      351.2
      435.6
      79.42
      63.58
      92.22
      88.78
      18.940
      130.336
      9220.69

5 rows × 21 columns

```python
# get budget
budget=1000000
```

```python
# get budget per stock
budget_per_stock_p1 = budget * weights_p1
budget_per_stock_p2 = budget * weights_p2
budget_per_stock_p3 = budget * weights_p3
budget_per_stock_p4 = budget * weights_p4
```

```python
unit_per_stock_p1 = (budget_per_stock_p1 / weekly_prices_evaluation.loc['2018-01-05', stocks])
unit_per_stock_p2 = (budget_per_stock_p2 / weekly_prices_evaluation.loc['2018-01-05', stocks])
unit_per_stock_p3 = (budget_per_stock_p3 / weekly_prices_evaluation.loc['2018-01-05', stocks])
unit_per_stock_p4 = (budget_per_stock_p4 / weekly_prices_evaluation.loc['2018-01-05', stocks])
unit_smi = (budget / weekly_prices_evaluation.loc['2018-01-05', 'SMI'])
```

```python
# Convert index from string to date
weekly_prices_evaluation.index = pd.to_datetime(weekly_prices_evaluation.index)
```

```python
# Compute value development of portfolios
value_p1_evaluation = weekly_prices_evaluation[stocks].dot(unit_per_stock_p1)
value_p2_evaluation = weekly_prices_evaluation[stocks].dot(unit_per_stock_p2)
value_p3_evaluation = weekly_prices_evaluation[stocks].dot(unit_per_stock_p3)
value_p4_evaluation = weekly_prices_evaluation[stocks].dot(unit_per_stock_p4)
value_smi_evaluation = weekly_prices_evaluation['SMI'] * unit_smi
```

```python
%matplotlib inline
# Line plot of value development of portfolios
plt.figure(figsize=(17, 5), dpi=120)
plt.plot(value_p1_evaluation, label='P1')
plt.plot(value_p2_evaluation, label='P2')
plt.plot(value_p3_evaluation, label='P3')
plt.plot(value_p4_evaluation, label='P4')
plt.plot(value_smi_evaluation, label='SMI')
plt.axhline(1000000, color='b', linestyle='--')
plt.xlabel('Date')
plt.ylabel('Value CHF')
plt.legend();
```

```python
# Get values on 28th December, 2018
print('Value P1:', value_p1_evaluation.loc['2018-12-28'].round(2))
print('Value P2:', value_p2_evaluation.loc['2018-12-28'].round(2))
print('Value P3:', value_p3_evaluation.loc['2018-12-28'].round(2))
print('Value P4:', value_p4_evaluation.loc['2018-12-28'].round(2))
print('Value SMI:', value_smi_evaluation.loc['2018-12-28'].round(2))
```

    Value P1: 832696.03
    Value P2: 895256.94
    Value P3: 912630.88
    Value P4: 925445.21
    Value SMI: 882004.57

## Question:
Compute  portfolio return and the minimum portfolio return of P1-P4 and the SMI for the years 2016-2017 as well as for the year 2018 and compare them to one another?

### 2016-2017

```python
# Get unit per stock (assumption: investment of 1,000,000 CHF at beginning of year 2016)
unit_per_stock_p1 = (budget_per_stock_p1
                     / weekly_prices.loc['2016-01-01', stocks])
unit_per_stock_p2 = (budget_per_stock_p2
                     / weekly_prices.loc['2016-01-01', stocks])
unit_per_stock_p3 = (budget_per_stock_p3
                     / weekly_prices.loc['2016-01-01', stocks])
unit_per_stock_p4 = (budget_per_stock_p4
                     / weekly_prices.loc['2016-01-01', stocks])
unit_smi = (budget
            / weekly_prices.loc['2016-01-01', 'SMI'])
```

```python
# Compute value development of portfolios
value_p1 = weekly_prices[stocks].dot(unit_per_stock_p1)
value_p2 = weekly_prices[stocks].dot(unit_per_stock_p2)
value_p3 = weekly_prices[stocks].dot(unit_per_stock_p3)
value_p4 = weekly_prices[stocks].dot(unit_per_stock_p4)
value_smi = weekly_prices['SMI'] * unit_smi
```

```python
# Compute weekly returns for P1-P4
return_p1 = value_p1 / value_p1.shift(1) - 1
return_p2 = value_p2 / value_p2.shift(1) - 1
return_p3 = value_p3 / value_p3.shift(1) - 1
return_p4 = value_p4 / value_p4.shift(1) - 1
return_smi = value_smi / value_smi.shift(1) - 1
```

```python
# Drop NA-return in first period
return_p1 = return_p1.dropna()
return_p2 = return_p2.dropna()
return_p3 = return_p3.dropna()
return_p4 = return_p4.dropna()
return_smi = return_smi.dropna()
```

```python
# Get average portfolio return (in-sample)
print('In-sample')
print('Average return P1:', return_p1.mean())
print('Average return P2:', return_p2.mean())
print('Average return P3:', return_p3.mean())
print('Average return P4:', return_p4.mean())
print('Average return SMI:', return_smi.mean())
```

    In-sample
    Average return P1: 0.0019377459805753254
    Average return P2: 0.000705162492983614
    Average return P3: 0.0037536328656517706
    Average return P4: 0.004306274155725259
    Average return SMI: 0.0007559592715326025

```python
# Get minimum portfolio return (in-sample)
print('In-sample')
print('Minimum return P1:', return_p1.min())
print('Minimum return P2:', return_p2.min())
print('Minimum return P3:', return_p3.min())
print('Minimum return P4:', return_p4.min())
print('Minimum return SMI:', return_smi.min())
```

    In-sample
    Minimum return P1: -0.0683472154943835
    Minimum return P2: -0.039585853183499164
    Minimum return P3: -0.03968848468003894
    Minimum return P4: -0.039530155378951104
    Minimum return SMI: -0.06359767251184767

### 2018

```python
# Compute weekly returns for P1-P4
return_p1_evaluation = value_p1_evaluation / value_p1_evaluation.shift(1) - 1
return_p2_evaluation = value_p2_evaluation / value_p2_evaluation.shift(1) - 1
return_p3_evaluation = value_p3_evaluation / value_p3_evaluation.shift(1) - 1
return_p4_evaluation = value_p4_evaluation / value_p4_evaluation.shift(1) - 1
return_smi_evaluation = value_smi_evaluation / value_smi_evaluation.shift(1) - 1
```

```python
# Drop NA-return in first period
return_p1_evaluation = return_p1_evaluation.dropna()
return_p2_evaluation = return_p2_evaluation.dropna()
return_p3_evaluation = return_p3_evaluation.dropna()
return_p4_evaluation = return_p4_evaluation.dropna()
return_smi_evaluation = return_smi_evaluation.dropna()
```

```python
# Get average portfolio return (out-of-sample)
print('Out-of-sample')
print('Average return P1:', return_p1_evaluation.mean())
print('Average return P2:', return_p2_evaluation.mean())
print('Average return P3:', return_p3_evaluation.mean())
print('Average return P4:', return_p4_evaluation.mean())
print('Average return SMI:', return_smi_evaluation.mean())
```

    Out-of-sample
    Average return P1: -0.0033525014939166094
    Average return P2: -0.0019677108825276166
    Average return P3: -0.001528400014162195
    Average return P4: -0.0011511241950784045
    Average return SMI: -0.0022559037116565627

```python
# Get minimum portfolio return (out-of-sample)
print('Out-of-sample')
print('Minimum return P1:', return_p1_evaluation.min())
print('Minimum return P2:', return_p2_evaluation.min())
print('Minimum return P3:', return_p3_evaluation.min())
print('Minimum return P4:', return_p4_evaluation.min())
print('Minimum return SMI:', return_smi_evaluation.min())
```

    Out-of-sample
    Minimum return P1: -0.06117459158716576
    Minimum return P2: -0.0634980532456666
    Minimum return P3: -0.056372769211856744
    Minimum return P4: -0.06604151320746465
    Minimum return SMI: -0.058421875152510294

```python

```

ESG-Score optimized portfolio using MiniMax/ESG-score using MiniMax Model.ipynb


## 0. Introduction
Objectifs pédagogiques:
1. Être capable de calculer les notions de risque quadratique d'un estimateur.
1. Être capable de calculer le biais d'un estimateur.
1. Renforcer la comprehénsion qu'un estimateur est une variable aléatoire.

Commençons par importer quelques modules.

```
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import seaborn as sns
# settings for seaborn plotting style
sns.set(color_codes=True)
# settings for seaborn plot sizes
sns.set(rc={'figure.figsize':(5,5)})
```

    /usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
      import pandas.util.testing as tm

## 1. Risque quadratique d'un estimateur
Le risque quadratique est l'une des mesure les plus brute de la performance d'un estimateur. Cependant il offre un premier moyen de comparer divers estimateurs.

Plaçons nous dans le cas ou le jeu de données est issu d'une loi normale centrée réduite $\mathcal{N}(0,1)$.

```
# Create a Standard Gaussian and sample from it
num_sample = 10000

# generate random numbers from N(0,1)
data_normal = norm.rvs(size=num_sample,loc=0,scale=1)

ax = sns.distplot(data_normal,
                  bins=100,
                  kde=True,
                  color='skyblue',
                  hist_kws={"linewidth": 20,'alpha':1})
ax.set(xlabel='Loi normale', ylabel='Fréquence')

# data = g.sample([num_sample]).numpy()

```

En connaissant uniquement la moyenne, l'estimateur de la variance issu du maxium de vraisemblance peut être utile car il incorpore la valeur de la moyenne.

### Questions
* Implémenter l'estimateur de la moyenne et de la variance que vous avez trouvé avec le maximum de vraisemblance que nous noterons $T_{n}$ et $S_{n}$
* Implémenter un estimateur de la variance empririque $S_{n}^{2}$
* Implémenter un estimateur de la variance empririque corrigée $\hat S_{n}^{2}$

Pour rappel en notant la loi normale $\mathcal{N}(\mu,\sigma^{2})$
\begin{align}
T_{n} &= \frac{1}{n} \sum_{i=1}^{n} x_{i} \\
S_{n} &= \frac{1}{n} \sum_{i=1}^{n} (x_{i}-\mu)^{2}\\
S_{n}^{2} &= \frac{1}{n} \sum_{i=1}^{n} (x_{i}-T_{n})^{2}\\
\hat S_{n}^{2} &= \frac{1}{n-1} \sum_{i=1}^{n} (x_{i}-T_{n})^{2}
\end{align}

Vous utiliserez les fonctions ci-dessous à remplir.

```
def estimateur_moyenne_vraisemblance(data):
  """
  Input:
    data: Tableau de nombre: le jeu de données
  Output:
    moyenne: estimation de la moyenne
  """
  length = len(data)
  moyenne = 0
  for i in range(length):
    moyenne = moyenne + data[i]
  return moyenne / length

def estimateur_variance_vraisemblance(data, moyenne):
  """
  Input:
    data: Tableau de nombre: le jeu de données
    moyenne: la moyenne connue
  Output:
    var: estimation de la variance
  """
  length = len(data)
  variance = 0
  for i in range(length):
    variance = variance + (data[i]-moyenne)**2
  return variance / length

def estimateur_variance_empirique(data):
  """
  Input:
    data: Tableau de nombre: le jeu de données
  Output:
    var: estimation de la variance
  """
  length = len(data)
  moyenne = estimateur_moyenne_vraisemblance(data)
  variance = 0
  for i in range(length):
    variance = variance + (data[i]-moyenne)**2
  return variance / length

def estimateur_variance_empirique_corrigee(data):
  """
  Input:
    data: Tableau de nombre: le jeu de données
  Output:
    var: estimation de la variance
  """
  length = len(data)
  moyenne = estimateur_moyenne_vraisemblance(data)
  variance = 0
  for i in range(length):
    variance = variance + (data[i]-moyenne)**2
  return variance / (length-1)
```

```
# generate random numbers from N(0,1)
num_sample = 5
data_normal = norm.rvs(size=num_sample, loc=0, scale=1)

# Estimations
moyenne = estimateur_moyenne_vraisemblance(data_normal)
var_vraisemblance = estimateur_variance_vraisemblance(data_normal, 0)
var_empirique = estimateur_variance_empirique(data_normal)

print("Moyenne: {}",moyenne)
print("Variance vraisemblance: ", var_vraisemblance)
print("Variance empirique: ", var_empirique)
```

    Moyenne: {} -0.16555148152533544
    Variance vraisemblance:  0.1391702385661771
    Variance empirique:  0.11176294553094361

Nous connaissons les valeurs des paramètres, nous pouvons calculer le risque quadratique de nos estimateurs:

* Rappelez la formule du risque quadratique.
* Éxécutez la cellule de code suivante pour estimer la moyenne de l'échantillon `data_normal` avec $T_{n}$ et sa variance avec respectivement $S_{n},S_{n}^{2}$ et $\hat S_{n}^{2}$.
* Calculez Le risque quadratique associé aux estimateurs: $T_{n}, S_{n}, S_{n}^{2}, \hat S_{n}^{2}$ en utilisant la fonction `risque_quad` à remplir.
* Comparez les divers estimateurs de la variance. Lequel est le plus bas ?

```

```

```
# 1 Generer des donnees aleatoires suivant une loi normale from N(0,1)
num_sample = 5
data_normal = norm.rvs(size=num_sample, loc=0, scale=1)

# 2. Obtenir une estimation de la variance avec chaque estimateur
variance_vraisemblance = estimateur_variance_vraisemblance(data_normal, 0)
variance_empirique = estimateur_variance_empirique(data_normal)
variance_empirique_corrigee = estimateur_variance_empirique_corrigee(data_normal)

# 3. Calculer le risque empirique pour chaque estimateur
def risque_quad(estimation, parametre):
  """
  Input:
    estimation: Float, estimation donnee par un estimateur
    parametre: Float, la veritable valeur du parametre
  Return:
    risque_quadratique: Float. Risque quadratique de l'estimateur
  """
  return (estimation-parametre)**2

# On connait la variance de nos donnees: 1
risque_var_empirique = risque_quad(variance_empirique, 1)
risque_var_vraisemblance = risque_quad(variance_vraisemblance, 1)
risque_var_empirique_corrige = risque_quad(variance_empirique_corrigee, 1)

print("Risque quadratique variance empirique", risque_var_empirique)
print("Risque quadratique variance vraisemblance", risque_var_vraisemblance)
print("Risque quadratique variance empirique corrige", risque_var_empirique_corrige)
```

    Risque quadratique variance empirique 0.7837840635598172
    Risque quadratique variance vraisemblance 0.77402836881981
    Risque quadratique variance empirique corrige 0.7338402250781214

Dans le cas ou le jeu de données connu on peut utiliser $S_{n}$ pour estimer la variance. Dans le cas contraire, il est nécessaire d'utiliser des estimateurs se basant uniquement sur les données comme $S_{n}^{2}, \hat S_{n}^{2}$.

## 2. Biais et variance d'un estimateur

On veut maintenant calculer le biais d'un estimateur. On va s'intéresser aux estimateurs de la variance, car nous avons vu que l'estimateur de la variance empirique était biaisé. On va illustrer ce que ça signifie.

Dans la suite on continue de considérer la variable aléatoire $X \sim \mathcal{N}(0,1)$

### Question
* Rapellez la définition du biais d'un estimateur
* Remplissez le code de `esperance_estimateur` ci dessous pour calculer l'espérance d'un estimateur, vous utiliserez cette fonction pour calculer le biais.

```

def esperance_estimateur(data, estimateur, taille_echantillon, iteration):
  """
  Input:
    data: Jeux de données complet
    estimateur: Estimateur à évaluer
    taille_echantillon: nombre d'éléments à tirer dans le jeu de données pour effectuer une estimation
    iteration: Nombre d'itération pour estimer l'espérance.
  Output:
    biais: le biais de l'estimateur
  """
  estimation = 0
  for i in range(iteration):
    echantillon = np.random.choice(data, taille_echantillon)
    estimation = estimation + estimateur(echantillon)
  esperance_estimateur = estimation / iteration
  return esperance_estimateur

```

On va comparer plusieurs estimateurs de la variance, dans la suite l'indice représente le nombre d'exemples utilisé pour chaque estimation.
* Comparez le biais de la variance empirique $S_{10}^{2}$ et de la variance empirique corrigée $\hat S_{10}^{2}$.
* Comparez le biais de la variance empirique $S_{100}^{2}$ et de la variance empirique corrigée $\hat S_{100}^{2}$.

Qu'observez-vous ?

```
# Create a Standard Gaussian and sample from it
num_sample = 10000
# generate random numbers from N(0,1)
data_normal = norm.rvs(size=num_sample,loc=0,scale=1)

```

```

esperance_var_emp = esperance_estimateur(data_normal, estimateur_variance_empirique, 15, 50)
esperance_var_corr = esperance_estimateur(data_normal, estimateur_variance_empirique_corrigee, 15, 50)

```

    0.9324079338421366

```
print("Biais estimateur variance empirique", (1-esperance_var_emp))
print("Biais estimateur variance empirique corrigee", 1-esperance_var_corr)
```

    Biais estimateur variance empirique 0.0675920661578634
    Biais estimateur variance empirique corrigee -0.02453540939903709

## 3. Ouverture
On a vu la notion de biais d'un estimateur, dans la suite, on s'intéressera aux propriétés de convergences de ces derniers. maintenant, on va illustrer la convergence d'un estimateur. On va donc voir pourquoi les big data permettent souvent d'obtenir de meilleurs estimateurs.

```

```


# Applications

[hpl 1: Remaining: comment on boundary conditions for diffusion of substance
and heat conduction (include Robin/cooling).]

## Diffusion of a substance

The first process to be considered is a substance that gets
transported through a fluid at rest by pure diffusion. We consider an
arbitrary volume $V$ of this fluid, containing the substance with
concentration function $c(\x,t)$. Physically, we can think of a very
small volume with centroid $\x$ at time $t$ and assign the ratio of
the volume of the substance and the total volume to $c(\x, t)$.  This
means that the mass of the substance in a small volume $\Delta V$ is
approximately $\varrho c\Delta V$, where $\varrho$ is the density of
the substance. Consequently, the total mass of the substance inside
the volume $V$ is the sum of all $\varrho c\Delta V$, which becomes
the volume integral $\int_V\varrho cdV$.

Let us reason how the mass of the substance changes and thereby derive
a PDE governing the concentration $c$.  Suppose the substance flows
out of $V$ with a flux $\q$.  If $\Delta S$ is a small part of the
boundary $\partial V$ of $V$, the volume of the substance flowing out
through $dS$ in a small time interval $\Delta t$ is $\varrho
\q\cdot\boldsymbol{n} \Delta t \Delta S$, where $\boldsymbol{n}$ is an outward
unit normal to the boundary $\partial V$, see [Figure](#diffu:app:substance:fig1).  We realize that only the normal
component of $\q$ is able to transport mass in and out of $V$.  The
total outflow of the mass of the substance in a small time interval
$\Delta t$ becomes the surface integral

$$
\int\limits_{\partial V} \varrho\q\cdot\boldsymbol{n} \Delta t\,  dS\thinspace .
$$

Assuming conservation of mass, this outflow of mass must be balanced by
a loss of mass inside the volume. The increase of mass inside the volume,
during a small time interval $\Delta t$, is

$$
\int\limits_V \varrho (c(\x,t+\Delta t) - c(\x,t)) dV,
$$

assuming $\varrho$ is constant, which is reasonable. The outflow of mass
balances the loss of mass in $V$, which is the increase with a minus sign.
Setting the two contributions equal to each other ensures balance of
mass inside $V$. Dividing by $\Delta t$ gives

$$
\int\limits_V \varrho \frac{c(\x,t+\Delta t) - c(\x,t)}{\Delta t} dV =
- \int\limits_{\partial V} \varrho\q\cdot\boldsymbol{n}\, dS\thinspace .
$$

Note the minus sign on the right-hand side: the left-hand side expresses
loss of mass, while the integral on the right-hand side is the gain of
mass.

Now, letting $\Delta t\rightarrow 0$, we have

$$
\frac{c(\x,t+\Delta t) - c(\x,t)}{\Delta t} \rightarrow
\frac{\partial c}{\partial t},
$$

so

$$
\begin{equation}
\int\limits_V \varrho \frac{\partial c}{\partial t}dV +
\int\limits_{\partial V} \varrho\q\cdot\boldsymbol{n}\, dS = 0\thinspace .
\label{diffu:app:substance:integral} \tag{1}
\end{equation}
$$

To arrive at a PDE,
we express the surface integral as a volume integral using Gauss'
divergence theorem:

$$
\int\limits_V (\varrho \frac{\partial c}{\partial t} +
\nabla\cdot(\varrho \q))dV = 0\thinspace .
$$

Since $\varrho$ is constant, we can divide by this quantity.  If the
integral is to vanish for an arbitrary volume $V$, the integrand must
vanish too, and we get the mass conservation PDE for the substance:

$$
\begin{equation}
\frac{\partial c}{\partial t} +
\nabla\cdot \q = 0\thinspace .
\label{diffu:app:substance:massconv} \tag{2}
\end{equation}
$$

An arbitrary volume of a fluid.

A fundamental problem is that this is a scalar PDE for four unknowns:
$c$ and the three components of $\q$. We therefore need additional
equations. Here, Fick's law comes at rescue: it models how the flux
$\q$ of the substance is related to the concentration $c$. Diffusion
is recognized by mass flowing from regions with high concentration to
regions of low concentration. This principle suggests that $\q$ is
proportional to the negative gradient of $c$:

$$
\begin{equation}
\q = -\dfc\nabla c,
\label{diffu:app:substance:Fick} \tag{3}
\end{equation}
$$

where $\dfc$ is an empirically determined constant.
The relation ([3](#diffu:app:substance:Fick)) is known as Fick's law.
Inserting ([3](#diffu:app:substance:Fick)) in
([2](#diffu:app:substance:massconv)) gives a scalar PDE for the
concentration $c$:

$$
\begin{equation}
\frac{\partial c}{\partial t} = \dfc\nabla^2 c\thinspace .
\label{diffu:app:substance:PDE} \tag{4}
\end{equation}
$$

## Heat conduction

Heat conduction is a well-known diffusion process. The governing PDE
is in this case based on the first law of thermodynamics: the increase
in energy of a system is equal to the work done on the system, plus
the supplied heat. Here, we shall consider media at rest and neglect work
done on the system. The principle then reduces to a balance between
increase in internal energy and supplied heat flow by conduction.

Let $e(x,t)$ be the *internal energy* per unit mass. The increase of
the internal energy in a small volume $\Delta V$ in a small time
interval $\Delta t$ is then

$$
\varrho (e(\x,t+\Delta t) - e(\x,t))\Delta V,
$$

where $\varrho$ is the density of the material subject to heat conduction.
In an arbitrary volume $V$, as depicted in
[Figure](#diffu:app:substance:fig1), the corresponding increase in internal
energy becomes the volume integral

$$
\int\limits_V \varrho (e(\x,t+\Delta t) - e(\x,t))dV\thinspace .
$$

This increase in internal energy is balanced by heat supplied by
conduction. Let $\q$ be the heat flow per time unit. Through the
surface $\partial V$ of $V$ the following amount of heat flows out
of $V$ during a time interval $\Delta t$:

$$
\int\limits_{\partial V} \q\cdot\boldsymbol{n}\Delta t\, dS\thinspace .
$$

The simplified version of the first law of thermodynamics then states that

$$
\int\limits_V \varrho (e(\x,t+\Delta t) - e(\x,t))dV = -
\int\limits_{\partial V} \q\cdot\boldsymbol{n}\Delta t\, dS\thinspace .
$$

The minus sign on the right-hand side ensures that the integral there
models net *inflow* of heat (since $\boldsymbol{n}$ is an outward unit normal,
$\q\cdot\boldsymbol{n}$ models *outflow*).
Dividing by $\Delta t$ and notifying that

$$
\lim_{\Delta t\rightarrow 0}
\frac{e(\x,t+\Delta t) - e(\x,t)}{\Delta t} =
\frac{\partial e}{\partial t},
$$

we get (in the limit $\Delta t\rightarrow 0$)

$$
\int\limits_V \varrho \frac{\partial e}{\partial t} dV +
\int\limits_{\partial V} \q\cdot\boldsymbol{n}\Delta t\, dS = 0\thinspace .
$$

This is the integral equation for heat conduction, but we aim at a PDE.
The next step is therefore to transform the surface integral to a
volume integral via Gauss' divergence theorem. The result is

$$
\int\limits_V\left( \varrho \frac{\partial e}{\partial t}
+ \nabla\cdot\q\right) dV = 0\thinspace .
$$

If this equality is to hold for all volumes $V$, the integrand must vanish,
and we have the PDE

$$
\begin{equation}
\varrho \frac{\partial e}{\partial t} = -\nabla\cdot\q\thinspace .
\label{diffu:app:heat:PDE1} \tag{5}
\end{equation}
$$

Sometimes the supplied heat can come from the medium itself. This is
the case, for instance, when radioactive rock generates heat.  Let us
add this effect. If $f(\x,t)$ is the supplied heat per unit volume per
unit time, the heat supplied in a small volume is $f\Delta t\Delta V$,
and inside an arbitrary volume $V$ the supplied generated heat becomes

$$
\int\limits_V f\Delta t dV\thinspace .
$$

Adding this to the integral statement of the (simplified) first law of
thermodynamics, and continuing the derivation, leads to the PDE

$$
\begin{equation}
\varrho \frac{\partial e}{\partial t} = -\nabla\cdot\q + f\thinspace .
\label{diffu:app:heat:PDE2} \tag{6}
\end{equation}
$$

There are four unknown scalar fields: $e$ and $\q$. Moreover, the
temperature $T$, which is our primary quantity to compute, does not
enter the model yet. We need an additional equation, called the
*equation of state*, relating $e$, $V=1/\varrho=$, and $T$:
$e=e(V,T)$. By the chain rule we have

$$
\frac{\partial e}{\partial t} =
\left.\frac{\partial e}{\partial T}\right\vert_{V}
\frac{\partial T}{\partial t} +
\left.\frac{\partial e}{\partial V}\right\vert_{T}
\frac{\partial V}{\partial t}\thinspace .
$$

The first coefficient $\partial e/\partial T$ is called *specific heat
capacity at constant volume*, denoted by $c_v$:

$$
c_v = \left.\frac{\partial e}{\partial T}\right\vert_{V}\thinspace .
$$

The specific heat capacity will in general vary with $T$, but taking it as a
constant is a good approximation in many applications.

The term $\partial e/\partial V$ models effects due to compressibility
and volume expansion. These effects are often small and can be
neglected.  We shall do so here. Using $\partial e/\partial t =
c_v\partial T/\partial t$ in the PDE gives

$$
\varrho c_v\frac{\partial T}{\partial t} = -\nabla\cdot\q + f\thinspace .
$$

We still have four unknown scalar fields ($T$ and $\q$). To close the
system, we need a relation between the heat flux $\q$ and the temperature $T$
called *Fourier's law*:

$$
\q = -k\nabla T,
$$

which simply states that heat flows from hot to cold areas, along the
path of greatest variation. In a solid medium, $k$ depends on the
material of the medium, and in multi-material media one must regard
$k$ as spatially dependent. In a fluid, it is common to assume that
$k$ is constant. The value of $k$ reflects how easy heat is
conducted through the medium, and $k$ is named the *coefficient of
heat conduction*.

We now have one scalar PDE for the unknown temperature field $T(\x,t)$:

$$
\begin{equation}
\varrho  c_v\frac{\partial T}{\partial t} = \nabla\cdot(k\nabla T) + f\thinspace .
\label{diffu:app:heat:PDE} \tag{7}
\end{equation}
$$

## Porous media flow

The requirement of mass balance for
flow of a single, incompressible fluid through a deformable (elastic) porous
medium leads to the equation

$$
S\frac{\partial p}{\partial t} + \nabla\cdot(\q - \alpha\frac{\partial\u}{\partial t}) = 0,
$$

where $p$ is the fluid pressure, $\q$ is the fluid velocity, $\u$
is the displacement (deformation) of the medium, $S$ is the storage
coefficient of the medium (related to the compressibility of the
fluid and the material in the medium), and $\alpha$ is another coefficient.
In many circumstances, the last term with $\u$ can be neglected,
an assumption that decouples the equation above from a model for
the deformation of the medium. The famous *Darcy's law* relates
$\q$ to $p$:

$$
\q = -\frac{K}{\mu}(\nabla p - \varrho\g),
$$

where $K$ is the permeability of the medium, $\mu$ is the dynamic
viscosity of the fluid, $\varrho$ is the density of the fluid, and $\g$
is the acceleration of gravity, here taken as $\g = -g\kk$.
Combining the two equations results in the diffusion model

$$
\begin{equation}
S\frac{\partial p}{\partial t} = \mu^{-1}\nabla(K\nabla p) + \frac{\varrho g}{\mu}\frac{\partial K}{\partial z}\thinspace .
\label{diffu:app:porous:PDE} \tag{8}
\end{equation}
$$

Boundary conditions consist of specifying $p$ or $\q\cdot\boldsymbol{n}$ (i.e., normal velocity) at
 each point of the boundary.

## Potential fluid flow

Let $\v$ be the velocity of a fluid. The condition $\nabla\times\v =0$
is relevant for many flows, especially in geophysics when viscous effects
are negligible. From vector calculus it is known
that $\nabla\times\v =0$ implies that $v$ can be derived
from a scalar potential field $\phi$: $\v = \nabla\phi$. If the
fluid is incompressible, $\nabla\cdot\v = 0$, it follows that
$\nabla\cdot\nabla\phi = 0$, or

$$
\begin{equation}
\nabla^2\phi = 0\thinspace .
\label{_auto1} \tag{9}
\end{equation}
$$

This Laplace equation is sufficient for determining $\phi$ and thereby
describe the fluid motion. This type of flow is known as
[potential flow](https://en.wikipedia.org/wiki/Potential_flow).
One very important application where potential flow is a good model
is water waves.
As boundary condition we must prescribe
$\v\cdot\boldsymbol{n} =\partial\phi/\partial n$. This gives rise to
what is known as a pure Neumann problem and will cause numerical
difficulties because $\phi$ and $\phi$ plus any constant are two
solutions of the problem. The simplest remedy is to fix the value of
$\phi$ at a point.

## Streamlines for 2D fluid flow

The streamlines in a two-dimensional
stationary fluid flow are lines tangential to the flow.
The [stream function](https://en.wikipedia.org/wiki/Stream_function)
$\psi$ is often introduced in two-dimensional flow
such that its contour
lines, $\psi = \hbox{const}$, gives the streamlines. The relation
between $\psi$ and the velocity field $\v=(u,v)$ is

$$
u = \frac{\partial\psi}{\partial y},\quad v = -
\frac{\partial\psi}{\partial x}\thinspace .
$$

mathcal{I}_t follows that $\nabla\v = \psi_{yx} - \psi_{xy}=0$, so the stream function
can only be used for incompressible flows.
Since

$$
\nabla\times\v = \left(\frac{\partial v}{\partial y} - \frac{\partial u}{\partial x}\right)\kk \equiv \omega\kk,
$$

we can derive the relation

$$
\begin{equation}
\nabla^2\psi = -\omega,
\label{_auto2} \tag{10}
\end{equation}
$$

which is a governing equation for the stream function $\psi(x,y)$
if the vorticity $\omega$ is known.

## The potential of an electric field

Under the assumption of time independence, Maxwell's equations
for the electric field $\boldsymbol{E}$ become

$$
\begin{align*}
\nabla\cdot\boldsymbol{E} &= \frac{\rho}{\epsilon_0},\\
\nabla\times\boldsymbol{E} &= 0,
\end{align*}
$$

where $\rho$ is the electric charge density and $\epsilon_0$ is
the electric permittivity of free space (i.e., vacuum).
Since $\nabla\times\boldsymbol{E}=0$, $\boldsymbol{E}$ can be derived from a potential
$\varphi$, $\boldsymbol{E} = -\nabla\varphi$. The electric field potential is
therefore governed by the Poisson equation

$$
\begin{equation}
\nabla^2\varphi = -\frac{\rho}{\epsilon_0}\thinspace .
\label{_auto3} \tag{11}
\end{equation}
$$

If the medium is heterogeneous, $\rho$ will depend on the spatial location $\boldsymbol{r}$.
Also, $\epsilon_0$ must be exchanged with an electric permittivity function
$\epsilon(\boldsymbol{r})$.

Each point of the boundary must be accompanied by, either a Dirichlet condition
$\varphi(\boldsymbol{r}) = \varphi_D(\boldsymbol{r})$, or a Neumann condition
$\frac{\partial\varphi(\boldsymbol{r})}{\partial n} = \varphi_N(\boldsymbol{r})$.

[hpl 2: What are appropriate boundary conditions?]
[hpl 3: Svein, can you get this generalized to electric field inside
some heterogeneous medium? Think we get some variable coefficient
equation in that case (anisotropic conduction?). Can you figure it out?]
[sl 4: is this what you were thinking of?]

## Development of flow between two flat plates

Diffusion equations may also arise as simplified versions of other
mathematical models, especially in fluid flow. Consider a fluid
flowing between two flat, parallel plates. The velocity is
uni-directional, say along the $z$ axis, and depends only on the
distance $x$ from the plates; $\u = u(x,t)\kk$.  The flow is governed
by the Navier-Stokes equations,

$$
\begin{align*}
\varrho\frac{\partial\u}{\partial t} + \varrho\u\cdot\nabla\u &= -\nabla p
+ \mu\nabla^2\u + \varrho\f,\\
\nabla\cdot\u &= 0,
\end{align*}
$$

where $p$ is the pressure field, unknown along with the velocity $\u$,
$\varrho$ is the fluid density, $\mu$ the dynamic viscosity, and $\f$
is some external body force. The geometric restrictions of flow between
two flat plates puts restrictions on the velocity, $\u = u(x,t)\ii$,
and the $z$ component of the Navier-Stokes equations collapses to
a diffusion equation:

$$
\varrho\frac{\partial u}{\partial t} = - \frac{\partial p}{\partial z} +
\mu\frac{\partial^2 u}{\partial z^2}
+ \varrho f_z,
$$

if $f_z$ is the component of $\f$ in the $z$ direction.

The boundary conditions are derived from the fact that the fluid sticks to
the plates, which means $\u=0$ at the plates. Say the location of the
plates are $z=0$ and $z=L$. We then have

$$
u(0,t)=u(L,t)=0\thinspace .
$$

One can easily show that $\partial p/\partial z$ must be a constant or
just a function of time $t$. We set $\partial p/\partial z = -\beta(t)$.
The body force could be a component of gravity, if desired, set as
$f_z = \gamma g$. Switching from $z$ to $x$ as independent variable gives
a very standard one-dimensional diffusion equation:

$$
\varrho\frac{\partial u}{\partial t} =
\mu\frac{\partial^2 u}{\partial x^2} + \beta(t) + \varrho\gamma g,\quad
x\in [0,L],\ t\in (0,T]\thinspace .
$$

The boundary conditions are

$$
u(0,t)=u(L,t)=0,
$$

while some initial condition

$$
u(x,0) = I(x)
$$

must also be prescribed.

The flow is driven by either the pressure gradient $\beta$ or gravity, or
a combination of both. One may also consider one moving plate that
drives the fluid. If the plate at $x=L$ moves with velocity $U_L(t)$, we have
the adjusted boundary condition

$$
u(L,t) = U_L(t)\thinspace .
$$

[hpl 5: Exercises based on this diffusion model.]

## Flow in a straight tube

Now we consider viscous fluid flow in a straight tube with radius $R$
and rigid walls.
The governing equations are the Navier-Stokes equations, but as
in the section [Development of flow between two flat plates](#diffu:app:Couette), it is natural to assume that the
velocity is directed along the tube, and that it is axi-symmetric.
These assumptions reduced the velocity field to $\u = u(r,x,t)\ii$, if the
$x$ axis is directed along the tube. From the equation of continuity,
$\nabla\cdot\u = 0$, we see that $u$ must be independent of $x$.
Inserting $\u = u(r,t)\ii$ in the Navier-Stokes equations, expressed
in axi-symmetric cylindrical coordinates, results in

$$
\begin{equation}
\varrho\frac{\partial u}{\partial t} =
\mu\frac{1}{r}\frac{\partial}{\partial r}\left(
r\frac{\partial u}{\partial r}\right) + \beta(t) + \varrho\gamma g,\quad
r\in [0,R],\ t\in (0,T]\thinspace .
\label{diffu:app:pipeflow:pde} \tag{12}
\end{equation}
$$

Here, $\beta(t) = -\partial p/\partial x$ is the pressure gradient along
the tube. The associated boundary condition is $u(R,t)=0$.

## Tribology: thin film fluid flow

Thin fluid films are extremely important inside machinery to reduce friction
between gliding surfaces. The mathematical model for the fluid motion takes
the form of a diffusion problem and is quickly derived here.
We consider two solid surfaces whose distance is described by a
gap function $h(x,y)$. The space between these surfaces is filled with
a fluid with dynamic viscosity $\mu$. The fluid may move partially
because of pressure gradients and partially because the surfaces
move. Let $U\ii + V\jj$ be the relative velocity of the two surfaces and
$p$ the pressure in the fluid. The mathematical model builds on two
principles: 1) conservation of mass, 2) assumption of locally quasi-static
flow between flat plates.

The conservation of mass equation reads $\nabla\cdot\u$, where $\u$ is
the local fluid velocity. For thin films the detailed variation between
the surfaces is not of interest, so $\nabla\cdot\u = 0$ is integrated
(average) in the direction perpendicular to the surfaces. This gives
rise to the alternative mass conservation equation

$$
\nabla\cdot\q = 0,\quad \q = \int\limits_0^{h(x,y)}\u dz,
$$

where $z$ is the coordinate perpendicular to the surfaces, and $\q$
is then the volume flux in the fluid gap.

Locally, we may assume that we have steady flow between two flat
surfaces, with a pressure gradient and where the lower surface is at rest
and the upper moves with velocity $U\ii + V\jj$.
The corresponding mathematical problem is actually the limit problem
in the section [Development of flow between two flat plates](#diffu:app:Couette) as $t\rightarrow\infty$. The limit
problem can be solved analytically, and the local volume flux becomes

$$
\q(x,y,z) = \int\limits_0^{h}\u(x,y,z) dz
= -\frac{h^3}{12\mu}\nabla p + \frac{1}{2} Uh\ii + \frac{1}{2} Vh\jj\thinspace .
$$

The idea is to use this expression locally
also when the surfaces are not flat,
but slowly varying, and if $U$, $V$, or $p$ varies in time, provided the
time variation is sufficiently slow. This is a common quasi-static
approximation, much used in mathematical modeling.

Inserting the expression for $\q$ via $p$, $U$, and $V$ in the
equation $\nabla\q = 0$ gives a diffusion PDE for $p$:

$$
\begin{equation}
\nabla\cdot\left(\frac{h^3}{12\mu}\nabla p\right) =
\frac{1}{2}\frac{\partial}{\partial x}(hU)
+ \frac{1}{2}\frac{\partial}{\partial x}(hV)\thinspace .
\label{_auto4} \tag{13}
\end{equation}
$$

The boundary conditions must involve $p$ or $\q$ at the boundary.

[hpl 6: Exercise!]

## Propagation of electrical signals in the brain

One can make a model of how electrical signals are propagated along the
neuronal fibers that receive synaptic inputs in the brain. The signal
propagation is one-dimensional and can, in the simplest cases, be
governed by the [Cable equation](http://en.wikipedia.org/wiki/Cable_equation):

$$
\begin{equation}
c_m \frac{\partial V}{\partial t} =
\frac{1}{r_l}\frac{\partial^2 V}{\partial x^2} - \frac{1}{r_m}V
\label{}
\end{equation}
$$

where $V(x,t)$ is the voltage to be determined,
$c_m$ is capacitance of the neuronal fiber, while
$r_l$ and $r_m$ are measures of the resistance.
The boundary conditions are often taken as $V=0$ at a short circuit or open end,
$\partial V/\partial x=0$ at a sealed end, or $\partial V/\partial x \propto
V$ where there is an injection of current.


# Quantum Walk Search Algorithm

Quantum walks are the quantum equivalence of a classical Markov chain and have become key in many quantum algorithms. In this section, we implement a quantum walk search algorithm that finds marked elements in a graph. The algorithm has a quadratic speedup compared to its classical counterpart.
## Contents
1. [Classical Markov chains](#markov)
2. [Quantum walks](#qwalks)
    1. [Coined quantum walkes](#coined)
    2. [Szedgedy quantum walk](#szegedy)
    3. [Equivalence of coined and Szegedy quantum walks](#equivalence)
3. [Example: Quantum walk on hypercube](#example1)
4. [Quantum walk search algorithm](#qwalkalgo)
5. [Example: quantum walk search on 4-dimensional hypercube](#qiskiti)
6. [References](#references)

## 1.Classical Markov chains
A Markov chain is a stochastic process often used to model real-life processes. It consists of states and associated transition probabilities, which describe the probabilities to move between the states in each time step. In discrete-time Markov chains, which we work with here, the time steps are discrete. Markov chains satisfy the Markov property, which means that the next step of the process only depends on the current and not any of the previous steps. A Markov chain has an associated transition matrix, P, which describes the probability to move between each of the states. We show an example of a Markov chain and its associated transition matrix $P$ below.
\begin{equation}
P=
\begin{pmatrix}
0.1 & 0.3 & 0.3\\
0.1 & 0.1 & 0.2 \\
0.8 & 0.6 & 0.5
\end{pmatrix}
\label{eq:matrix_example}
\end{equation}

Given a transition matrix $P$, we can obtain the probability distribution after $t$ time steps by $P^t$.

## 2.Quantum walks
Quantum walks are the quantum equivalent of the classical Markov chain. Due to superposition, a quantum walk will take all possible paths simultaneously until we measure the circuit. Due to quantum interference, some state will cancel out. This makes quantum walk algorithms faster than classical ones, since we can design them in such way that wrong answers quickly cancells out. There are two common models for quantum walks, coined quantum walks and Szegedy quantum walks, which are equivalent under certain circumstances. A coined quantum walk is a walk on the vertices of the graph while Szegedy quantum walk takes place on the edges. Before we show how we can implement a quantum walk, we will introduce both models.

 $\newcommand{\ket}[1]{\left|{#1}\right\rangle}$
 $\newcommand{\bra}[1]{\left\langle{#1}\right|}$
### 2.A Coined quantum walks
A simple example of a coined quantum walk is a walk on the infinite integer line. In this case, we represent the walker's position with an integer, $\{\ket{j} : j \in  \mathbb{Z} \}$, since the walker can walk all integers in $\mathbb{Z}$. A coin decides how the walker should move. On the integer line, the walker can go either left or right. Therefore, the coin's computational basis is $\{\ket{0}, \ket{1}\}$, and we move the walker in one direction if the coin is $\ket{0}$ and in the other direction if the coin is $\ket{1}$.

A coined quantum is a walk on the nodes in a graph, and we refer to the nodes as states. The walker can move between states that are connected with an edge. In the coin model, we have two quantum states and two operators. The first state is the position state, which represents the walker's position. For the walk above, this is an integer since the walker can be anywhere on the integer line. The other state is the coin state. The coin state decides how the walker should move in the next step. We can represent both the coin state and the position state by vectors in Hilbert space. If we can express the coin state by a vector in $\mathcal{H}_C$ and the position state with a vector in $\mathcal{H}_P$, we can express the quantum space for the entire walker as $\mathcal{H} = \mathcal{H}_C \otimes \mathcal{H}_P$.

As we mentioned, the model also has two operators; the coin operator, $C$, and the shift operator, $S$. The coin operator acts on $\mathcal{H}_C$ during each time step and puts the walker in superposition, so it walks all possible paths simultaneously. For the walk on the integer line, this means that it moves both to the left and right in each time step. There are different coin operators, but the most common ones are the Hadamard coin and the Grover coin. A Hadamard coin is a Hadamard gate that puts the walker in an equal superposition:

\begin{equation}
    H = \frac{1}{\sqrt{2}}
\begin{bmatrix}
1 & 1 \\
1 & -1
\end{bmatrix}
\end{equation}

A Grover coin is the Grover diffusion operator from Grover's algorithm. We define it as

\begin{equation}
    G =
    \begin{bmatrix}
\frac{2}{n} -1 & \frac{2}{n} & \ldots & \frac{2}{n}\\
\frac{2}{n} & \frac{2}{n} - 1  & \ldots & \frac{2}{n} \\
\vdots & \vdots & \ddots & \vdots \\
\frac{2}{n} & \frac{2}{n} & \ldots & \frac{2}{n} -1
\end{bmatrix}
\end{equation}

Like the Hadamard coin, a Grover coin puts the walker in superposition. However, it behaves a bit differently. If we apply a Grover coin to a walker in position $\ket{000}$, we obtain the state vector probabilities shown in the figure below. As we can see, the coin does not put the walker in an equal superposition as the Hadamard coin does. Instead, $\ket{000}$ has a much larger probability than the other states.

The other operator in the model, the shift operator, acts on $\mathcal{H}_P$ and moves the walker to the next position. For the walk on the integer line, the shift operator moves the walker to the left of the coin is $\ket{0}$ and to the right if the coin is $\ket{1}$:

\begin{equation}
    S \ket{0}\ket{j} = \ket{0}\ket{j+1}
\end{equation}

\begin{equation}
    S \ket{1}\ket{j} = \ket{1}\ket{j-1}
\end{equation}

With the shift operator defined as above, we can represent one step of the coined quantum as the unitary operator $U$ given by
\begin{equation}
    U = SC,
\end{equation}

where C is the coin operator. For the quantum walk on the integer line we use the Hadamard coin, but C can be either Hadamard coin, Grover coin or any other coin operator.

We can also look several steps ahead. We can express the quantum state $\ket{\psi}$ after $t$ time steps as
\begin{equation}
    \ket{\psi (t)} = U^t \ket{\psi(0)},
\end{equation}
where $\ket{\psi(0)}$ is the initial state and U is the operator for one step of the walk [1].

Coined quantum walks are most suitable for regular graphs, graphs where all nodes have the same number of neighbors [2]. An alternative quantum walk model that is more convenient for non-regular graphs is the Szegedy model that we will look at next.

### 2.B Szedgedy quantum walk
While a coined walk is a walk on the graph's nodes, a Szegedy walk is a walk on the edges on a bipartite double-cover of the original graph. The double-cover graph is a graph with twice as many vertices as the original graph. Two nodes in the bipartite double-cover graph are connected with an edge if and only if the nodes are also connected in the original graph. To create this model, we start with the transition probability matrix P for the classical walk. As described in section 1, we represent a classical discrete-time random walk by a transition matrix $P$. For any $N$-vertex graph with $N \times N$ transition matrix $P$, we can define the corresponding discrete-time quantum walk as a unitary operation on the Hilbert space $\mathcal{H}^N \otimes \mathcal{H}^N$. Let $P_{jk}$ define the probability that we make a transition from state $j$ to $k$. Before we define the walk, we define the normalized states

\begin{equation}
    \ket{\psi_j} := \sum_{k=1}^N \sqrt{P_{kj}} \ket{j,k}, \; j=1,...,N
\end{equation}

and the projection onto ${\ket{\psi_j}}:j=1,...,N$

\begin{equation}
    \Pi := \sum_{j=1}^N \ket{\psi_j} \bra{\psi_j}
\label{eq:sz_pi}
\end{equation}

We also introduce the shift operator S:

\begin{equation}
    S := \sum_{j,k=1}^N \ket{j,k} \bra{k,j}
\label{eq:sz_s}
\end{equation}

With $S$ and $\Pi$ defined as above we can introduce one step of the discrete-time quantum walk:

\begin{equation}
    U := S(2 \Pi - 1),
\label{eq:sz_op}
\end{equation}

where $(2 \Pi - 1)$ is the reflection operator. We also define $t$ steps of the walk as $U^t$ [2].

### 2.C Equivalence of coined and Szegedy quantum walks
It is known that a coined walk with Grover coin is equivalent to Szegedy quantum walk. For more details, we refer to this article [3] by Thomas G. Wong, where he also shows the equivalence between the operators in the two models.

## 3. Example: Implementing quantum walk on a hypercube
A hypercube is the $n$-dimensional counterpart of the $3$-dimensional cube. All nodes have degree $n$, and the hypercube has a total of $N=2^n$ nodes. We can represent the nodes in a hypercube graph by $n$-tuples of binary numbers. The binary representation of the neighbors of a node will differ by only one binary number. For example, in the 4-dimensional hypercube, the neighbors to $0000$ are $0001$, $0010$, $0100$, and $1000$. Thus, a node is connected to all nodes to which the Hamming distance is 1. The edges are also labeled. Two neighboring nodes that differ in the a:th bit are connected by the edge labeled $a$.

The Hilbert space representing a coined quantum walk on the hypercube is $\mathcal{H} = \mathcal{H}^n \otimes \mathcal{H}^{2^n}$, where $\mathcal{H}^n$ denotes the coin space and $\mathcal{H}^{2^n}$ the walker's position. The computational basis is

\begin{equation}
    \big\{ \ket{a,\vec{v}}, 0 \leq a \leq n-1, \vec{v} \in  \{(00...00), (00...01), ....., (11...11 )\} \big\}.
\end{equation}

The value of the coin computational basis $a$, which is associated with edge $a$, decides where the walker should move. If the $a=0$, the walker will go to the node where the first binary value differs from the current node. If $a=1$, the walker will move to the node in which the second value differs from the current, et cetera.  Let $\vec{e}_a$ be an n-tuple where all binary values, except the value with index $a$, are $0$. Then the shift operator $S$ moves the walker from the state $\ket{a} \ket{\vec{v}}$ to $\ket{\vec{v} \oplus \vec{e}_a}$:

\begin{equation}
    S \ket{a} \ket{\vec{v}} = \ket{a} \ket{\vec{v} \oplus \vec{e}_a}.
\end{equation}

We use the Grover coin, $G$, for this walk. Thus, the evolution operator is

\begin{equation}
    U = SG.
\end{equation}

We will now show how we can implement a quantum walk on a 4-dimensional hypercube. We need to implement the coin operator and the shift operator. We start by importing all necessary libraries from Qiskit.

```python
# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, execute, Aer, IBMQ, QuantumRegister, ClassicalRegister
from qiskit.compiler import transpile, assemble
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from qiskit.circuit.library import QFT
from numpy import pi
from qiskit.quantum_info import Statevector
from matplotlib import pyplot as plt
import numpy as np
# Loading your IBM Q account(s)
provider = IBMQ.load_account()
```

The circuit will have 6 qubits, 4 that represents the position and 2 that represents the coin. As we mentioned previously, the coin is a Grover coin, which is the diffuser in Grover's algorithm. We start by implementing this.

```python
one_step_circuit = QuantumCircuit(6, name=' ONE STEP')
# Coin operator
one_step_circuit.h([4,5])
one_step_circuit.z([4,5])
one_step_circuit.cz(4,5)
one_step_circuit.h([4,5])
one_step_circuit.draw()
```

Now, let us implement the shift operator. We know that the walker can only move to neighboring nodes, and all neighboring nodes differ by only one bit. We want to move the walker according to the coin, and we move the walker by applying a NOT gate to one of the node qubits. If the coin is in state $\ket{11}$, we move the walker to the state in which the first node qubit differ. If the coin is $\ket{10}$ or $\ket{01}$, the walker moves to the state where the second and third qubit, respectively, differ. Finally, if the Grover coin is $\ket{00}$, we flip the fourth qubit. We implement this with CCNOT- and NOT gates after the Grover coin. Together, they are one step of a quantum walk on a 4-dimensional hypercube.

```python
# Shift operator function for 4d-hypercube
def shift_operator(circuit):
    for i in range(0,4):
        circuit.x(4)
        if i%2==0:
            circuit.x(5)
        circuit.ccx(4,5,i)

shift_operator(one_step_circuit)

one_step_gate = one_step_circuit.to_instruction()
one_step_circuit.draw()
```

#### 4.Quantum walk search algorithm
We will now implement a quantum walk search algorithm that finds a marked vertex in a graph. First, we describe the algorithm, then we go through its implementation. The quantum walk search algorithm solves the problem of finding marked vertices in a graph by a quantum walk. That is, we mark some set of vertices $|M|$, start at an arbitrary node in the graph and move according to the walk until we find the marked nodes. The basis states in the quantum walk search algorithm have two registers, one corresponding to the current node and the other corresponding to the previous node. That is, the basis states corresponds to the edges in the graph. We denote the quantum walk based on the classical Markov chain with transition matrix $P$ by the unitary operation $W(P)$ on $\mathcal{H}$. We also define $\ket{p_x} = \sum_y \sqrt{P_{xy}}\ket{y}$ as the uniform superposition over the node $x$'s neighbors. Let $\ket{x}\ket{y}$ be a basis state. We define the basis state $\ket{x}\ket{y}$ as ''good'' if $x$ is a marked node. Otherwise, we refer to it as ''bad''. We now introduce ''good'' and ''bad'' states:

\begin{equation}
    \ket{G} = \frac{1}{\sqrt{|M|}} \sum_{x \in M} \ket{x} \ket{p_x}, \;
    \ket{B} = \frac{1}{\sqrt{N-|M|}} \sum_{x \notin M} \ket{x} \ket{p_x},
\end{equation}

which are the superpositions over good and bad basis states. Next, let us define $\epsilon = |M|/N$ and $\theta = \arcsin(\sqrt{\epsilon})$.

In short, the algorithm consists of three steps:

1. Set up the initial state $\ket{U} = \frac{1}{\sqrt{N}} \sum_{x} \ket{x} \ket{p_x} = \sin{\theta} \ket{G} + \cos{\theta} \ket{B}$, a uniform superposition over all edges
2. Repeat $O(1/\sqrt{\epsilon})$ times:

    (a) Reflect through $\ket{B}$

    (b) Reflect through $\ket{U}$

3. Do a measurement in the computational basis

We can easily implement step $1$ with Hadamard gates and the reflection through $\ket{B}$ with a phase oracle that shifts the phase of $x$ if $x$ is in the first register, and leaves the circuit unchanged otherwise.

Step 2(b) is equivalent to finding a unitary $R(P)$ that performs the following mapping:
\begin{align}
\label{eq:mapping_1}
    \ket{U} &\mapsto \ket{U}, \: \text{and} \\
    \ket{\psi} &\mapsto -\ket{\psi}, \: \forall \ket{\psi} \text{in the span of eigenvectors of $W(P)$ that are orthogonal to $\ket{U}$}
\label{eq:mapping_2}
\end{align}

To find this operator we apply phase estimation on $W(P)$. Above we defined $W(P)$ as the evolution operator for the random walk. As we saw in section 2.A, this is a unitary operator. From this follows that the eigenvalues of $W(P)$ have norm $1$. Because of this, we can write the eigenvalues of $W(P)$ on the form $e^{\pm 2i\theta_j}$. The unitary $W(P)$ has one eigenvector with corresponding eigenvalue $1$, which is $\ket{U}$. This is given by $\theta_1=0$. $R(P)$ will find this vector $\ket{U}$ by adding a register with auxiliary qubits and perform phase estimation with precision $O(1/\sqrt{\delta})$, where $\delta$ is the spectral gap of $P$. To do this, we need to apply $W(P)$ $O(1/\sqrt{\delta})$ times. Let $\ket{w}$ be an eigenvector of $W(P)$ with eigenvalue $e^{\pm 2i\theta_j}$. Assume that $\tilde{\theta_j}$ is the best approximation to $\theta_j$ given by the phase estimation. The operation $R(P)$ that performs the mappings in for $\ket{w}$ in step 2(b) is given by [4]

\begin{equation}
    \ket{w} \ket{0} \mapsto \ket{w} \ket{\tilde{\theta_j}} \mapsto (-1)^{|\tilde{\theta_j} \neq 0|} \ket{w} \ket{\tilde{\theta_j}} \mapsto (-1)^{|\tilde{\theta_j} \neq 0|} \ket{w} \ket{0}
\end{equation}

## 5.Example: Quantum walk search on 4-dimensional hypercube

The quantum walk search algorithm makes it possible to find a marked set of nodes in $O(1/\sqrt{\epsilon})$ steps, $\epsilon =  |M|/N$, where $M$ is the number of marked nodes and $N$ is the total number of nodes. This algorithm is originally used with Szegedy quantum walks, where we use two node registers to represent the quantum state. However, a coined walk with a Grover coin is equivalent to a Szegedy quantum walk and since implementations of coined walks are less complex in general, we choose to implement the algorithm with a coined walk. We will use the 4-dimensional hypercube that we implemented in section 3.

In short, we will implement the algorithm as follows. We achieve step 1, a uniform superposition over all edges, by applying Hadamard gates to the node qubits as well as the coin qubits. For step 2(a), we implement a phase oracle. Step 2(b) is implemented by a phase estimation on one step of the quantum walk on the hypercube followed by marking all quantum states where $\theta \neq 0$. We do this by rotating an auxiliary qubit. In the last part of this step, we reverse the phase estimation. The number of theta qubits depends on the precision of $\theta$.

Below, we implement the quantum walk search algorithm on the 4-dimensional hypercube.

For this algorithm we will need to use the inverse of the one step gate implemented earlier. We get this by using the built in circuit function inverse().

```python
one_step_circuit.inverse().draw()
```

The inversed one step gate will be used to reverse the phase estimation later. We need to make controlled gates from both the one step gate that we implemented in section 3 and its inverse. We will later use them depending on the value of the control qubit.

```python
# Make controlled gates
inv_cont_one_step = one_step_circuit.inverse().control()
inv_cont_one_step_gate = inv_cont_one_step.to_instruction()
cont_one_step = one_step_circuit.control()
cont_one_step_gate = cont_one_step.to_instruction()
```

Both the controlled one step gate and the controlled inversed one step gate will be used in the phase estimation. Another thing we will use in the phase estimation is the Quantum Fourier Transform. Qiskit has a function, QFT, which implements the Quantum Fourier Transform. The phase estimation uses the inverse Quantum Fourier Transform but we also will need to use the ordinary QFT to reverse the phase estimation.

```python
inv_qft_gate = QFT(4, inverse=True).to_instruction()
qft_gate = QFT(4, inverse=False).to_instruction()

QFT(4, inverse=True).decompose().draw("mpl")
```

Before we implement the phase estimation, we implement a phase oracle that marks the states 1011 and 1111. Then, we make it a circuit. This is step 2(a) of the algorithm.

```python
phase_circuit =  QuantumCircuit(6, name=' phase oracle ')
# Mark 1011
phase_circuit.x(2)
phase_circuit.h(3)
phase_circuit.mct([0,1,2], 3)
phase_circuit.h(3)
phase_circuit.x(2)
# Mark 1111
phase_circuit.h(3)
phase_circuit.mct([0,1,2],3)
phase_circuit.h(3)
phase_oracle_gate = phase_circuit.to_instruction()
# Phase oracle circuit
phase_oracle_circuit =  QuantumCircuit(11, name=' PHASE ORACLE CIRCUIT ')
phase_oracle_circuit.append(phase_oracle_gate, [4,5,6,7,8,9])
phase_circuit.draw()
```

We will now implement a gate that rotates an auxiliary qubit if the other qubits are non-zero. We will use this gate in the phase estimation, where it will rotate the auxiliary qubit if $\theta \neq 0$.

```python
# Mark q_4 if the other qubits are non-zero
mark_auxiliary_circuit = QuantumCircuit(5, name=' mark auxiliary ')
mark_auxiliary_circuit.x([0,1,2,3,4])
mark_auxiliary_circuit.mct([0,1,2,3], 4)
mark_auxiliary_circuit.z(4)
mark_auxiliary_circuit.mct([0,1,2,3], 4)
mark_auxiliary_circuit.x([0,1,2,3,4])

mark_auxiliary_gate = mark_auxiliary_circuit.to_instruction()
mark_auxiliary_circuit.draw()
```

Now, we will implement step 2(b) of the algorithm. This step consists of a phase estimation one step of the quantum walk followed by an auxiliary qubit that we rotate if $\theta \neq 0$. For this, we use the mark_auxiliary_gate that we just created.  Thereafter, we reverse the phase estimation.

```python
# Phase estimation
phase_estimation_circuit = QuantumCircuit(11, name=' phase estimation ')
phase_estimation_circuit.h([0,1,2,3])
for i in range(0,4):
    stop = 2**i
    for j in range(0,stop):
        phase_estimation_circuit.append(cont_one_step, [i,4,5,6,7,8,9])

# Inverse fourier transform
phase_estimation_circuit.append(inv_qft_gate, [0,1,2,3])

# Mark all angles theta that are not 0 with an auxiliary qubit
phase_estimation_circuit.append(mark_auxiliary_gate, [0,1,2,3,10])

# Reverse phase estimation
phase_estimation_circuit.append(qft_gate, [0,1,2,3])

for i in range(3,-1,-1):
    stop = 2**i
    for j in range(0,stop):
        phase_estimation_circuit.append(inv_cont_one_step, [i,4,5,6,7,8,9])
phase_estimation_circuit.barrier(range(0,10))
phase_estimation_circuit.h([0,1,2,3])

# Make phase estimation gate
phase_estimation_gate = phase_estimation_circuit.to_instruction()
phase_estimation_circuit.draw()
```

Now we implement the whole quantum walk search algorithm using the gates we made previously. We start by applying Hadamard gates to node and coin qubits, which is step 1 in the algorithm. Thereafter, we iteratively apply the phase oracle gate and the phase estimation gate (steps 2(a) and 2(b)). We need $O(1/\sqrt{\epsilon})$ iterations as stated in the description of the algorithm in section 4. Lastly, we measure the node qubits.

```python
# Implementation of the full quantum walk search algorithm
theta_q = QuantumRegister(4, 'theta')
node_q = QuantumRegister(4, 'node')
coin_q = QuantumRegister(2, 'coin')
auxiliary_q = QuantumRegister(1, 'auxiliary')
creg_c2 = ClassicalRegister(4, 'c')
circuit = QuantumCircuit(theta_q, node_q, coin_q, auxiliary_q, creg_c2)
# Apply Hadamard gates to the qubits that represent the nodes and the coin
circuit.h([4,5,6,7,8,9])
iterations = 2

for i in range(0,iterations):
    circuit.append(phase_oracle_gate, [4,5,6,7,8,9])
    circuit.append(phase_estimation_gate, [0,1,2,3,4,5,6,7,8,9,10])

circuit.measure(node_q[0], creg_c2[0])
circuit.measure(node_q[1], creg_c2[1])
circuit.measure(node_q[2], creg_c2[2])
circuit.measure(node_q[3], creg_c2[3])
circuit.draw()
```

Finally we run the implementation on the qasm simulator. We see that the circuit collapse to the marked states a clear majority of the times.

```python
backend = Aer.get_backend('qasm_simulator')
job = execute( circuit, backend, shots=1024 )
hist = job.result().get_counts()
plot_histogram( hist )
```

## 6. References

1. Renato Portugal. Quantum Walks and Search Algorithms. New York, NY: Springer New York, 2013
2. Markus G. Kuhn.Some Introductory Notes on Quantum Computing. Apr. 2000
3. Thomas G. Wong. “Equivalence of Szegedy’s and coined quantum walks”. In: Quantum InformationProcessing 16.9 (July 2017). ISSN: 1573-1332. DOI:10.1007/s11128-017-1667-y. URL:http://dx.doi.org/10.1007/s11128-017-1667-y.37
4. Ronald de Wolf. Quantum Computing: Lecture Notes. 2021. arXiv:1907.09415 [quant-ph]

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

    /usr/local/anaconda3/lib/python3.7/site-packages/qiskit/aqua/__init__.py:86: DeprecationWarning: The package qiskit.aqua is deprecated. It was moved/refactored to qiskit-terra For more information see <https://github.com/Qiskit/qiskit-aqua/blob/main/README.md#migration-guide>
      warn_package('aqua', 'qiskit-terra')

Version Information

Qiskit Software	Version

qiskit-terra	0.18.0

qiskit-aer	0.8.2

qiskit-ignis	0.6.0

qiskit-ibmq-provider	0.15.0

qiskit-aqua	0.9.4

qiskit	0.28.0

System information

Python	3.7.7 (default, May  6 2020, 04:59:01)
[Clang 4.0.1 (tags/RELEASE_401/final)]

OS	Darwin

CPUs	8

Memory (Gb)	32.0

Tue Oct 05 13:51:31 2021 BST


```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Class 11: Introduction to Dynamic Optimization: A Two-Period Cake-Eating Problem

Dynamic optimization, the optimal choice over elements in a time series, is at the heart of macroeconomic theory. People and firms routinely make decisions that affect their future opportunities. For example, a household chooses how much of its income to save and therefore allocates some of its current consumption to the future. In this Notebook, we consider a simple two-period dynamic optimization model. A person has some initial quantity of cake and chooses the optimal way to eat it over the next two periods. We solve for the optimal consumption path.

## Preferences

Suppose that a person has only two periods to eat cake. Let $C_0$ denote the quantity of cake consumed in period 0 and let $C_1$ denote the quantity consumed in period 1. The person's utility in period 0 from consuming $C_0$ and $C_1$ is given by:

\begin{align}
U(C_0,C_1) & = \log C_0 + \beta \log C_1,
\end{align}

where $0\leqslant\beta\leqslant 1$ is the person's *subjective discount factor*. $U$ is an *intertemporal utility function* and $\log C$ is the *flow* of utility generated each period. The presence of $\beta$ indicates the the person places lower weight on consumption in the future. The utility function implies indifference curves of the form:

\begin{align}
C_1 & =  e^{ \frac{\bar{U} - \log C_0}{\beta}},
\end{align}

where $\bar{U}$ is some given level of utility.

### Example: Plot Indifference Curves for Different Utility Levels

For $C_0$ between 0.01 and 10 and $\beta=0.95$, plot indifference curves for $\bar{U}=1, 2, 3, 4$. Set x- and y-axis limits to [0,10].

```python
# Define a function for computing c1 given beta value and array of c0 values
def indifference_curve(u,beta,c0):

    return np.exp((u-np.log(c0))/beta)

# Create variable called 'c0' that stores c0 values from 0.01 to 10
c0 = np.arange(0.01,10,0.001)

# Create a variable called 'beta' that stores the value of beta
beta = 0.95

# Plot indifference curves
plt.plot(c0,indifference_curve(u=1,beta=beta,c0=c0),lw=3,alpha=0.75,label='u=1')
plt.plot(c0,indifference_curve(u=2,beta=beta,c0=c0),lw=3,alpha=0.75,label='u=2')
plt.plot(c0,indifference_curve(u=3,beta=beta,c0=c0),lw=3,alpha=0.75,label='u=3')
plt.plot(c0,indifference_curve(u=4,beta=beta,c0=c0),lw=3,alpha=0.75,label='u=4')

plt.xlim([0,10])
plt.ylim([0,10])
plt.xlabel('$C_0$',fontsize=14)
plt.ylabel('$C_1$',fontsize=14)
plt.title('Indifference curves of $\log C_0 + 0.95 \log C_1$')

# Place legend to right of figure. PROVIDED
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))

plt.grid()
plt.tight_layout()
```

### Example: Plot Indifference Curves for Different $\beta$ Values

For $C_0$ between 0.01 and 30 and $\bar{U} = 3$, plot indifference curves for $\beta=1, 0.95,0.5,0.25$. Set x-axis limits to [0,30] and y-axis limits to [0,10].

```python
# Create a variable called u that stores the value of utility for the indifference curves
u = 3

# Create variable called 'c0' that stores c0 values from 0.01 to 30
c0 = np.arange(0.01,30,0.001)

# Plot indifference curves
plt.plot(c0,indifference_curve(u=u,beta=1,c0=c0),lw=3,alpha=0.75,label='$\\beta=1$')
plt.plot(c0,indifference_curve(u=u,beta=0.95,c0=c0),lw=3,alpha=0.75,label='$\\beta=0.95$')
plt.plot(c0,indifference_curve(u=u,beta=0.5,c0=c0),lw=3,alpha=0.75,label='$\\beta=0.5$')
plt.plot(c0,indifference_curve(u=u,beta=0.25,c0=c0),lw=3,alpha=0.75,label='$\\beta=0.25$')

plt.xlim([0,30])
plt.ylim([0,10])
plt.xlabel('$C_0$',fontsize=14)
plt.ylabel('$C_1$',fontsize=14)
plt.title('Indifference curves of $\log C_0 + \\beta \log C_1$')

# Place legend to right of figure. PROVIDED
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))

plt.grid()
plt.tight_layout()
```

## Budget Constraints and the Boundary Condition

Let $K_t$ denote the quantity of cake available in period $t$ and let $K_0$ denote the initial quantity of cake. The cake neither grows nor depreciates over time so the person faces the following budget constraints each period:

\begin{align}
C_0 & =  K_0 - K_1\\
C_1 & =  K_1 - K_2.
\end{align}

Finally, the person's problem is subject to a *boundary condition* that imposes a limit on the endpoint of the problem. Since the person only consumes cake in periods 0 and 1, there is no value in leaving leftover cake at the end of period 1. This reasoning implies the boundary condition:

\begin{align}
K_2 & = 0.
\end{align}

The boundary condition allows us to eliminate $K_2$ from the period 1 budget constraint. Combining the two budget constraints to eliminate $K_1$ implies an *intertemporal budget constraint* that links consumption in period 0 with consumption in period 1:

\begin{align}
C_0 & =  K_0 - C_1.
\end{align}

Notice that the implied price of $C_0$ in terms of $C_1$ is 1.

### Example: Plot the Intertemporal Budget Constraint

For $C_0$ between 0.01 and 20 and plot indifference curves for $K_0=5, 10, 15, 20$. Set x-axis limits to [0,10] and y-axis limits to [0,30].

```python
# Define a function for computing c1 given beta value and array of c0 values
def bc(k0,c0):
    '''Function for computing the intertemporal budget constraint for the two-period cake-eating problem

            Args:
            k0 (float):         initial quantity of cake
            c0 (numpy array):   array of initial consumption values

        Returns:
            numpy array
    '''

    k1 = k0-c0
    k1[k1<0]=0

    return k1

# Create variable called 'c0' that stores c0 values from 0.01 to 10
c0 = np.arange(0.01,10,0.001)

# Plot budget constraints
plt.plot(c0,bc(5,c0),lw=3,alpha=0.75,label='$K_0=5$')
plt.plot(c0,bc(10,c0),lw=3,alpha=0.75,label='$K_0=10$')
plt.plot(c0,bc(15,c0),lw=3,alpha=0.75,label='$K_0=15$')
plt.plot(c0,bc(20,c0),lw=3,alpha=0.75,label='$K_0=20$')

plt.xlabel('$C_0$',fontsize=14)
plt.ylabel('$C_1$',fontsize=14)
plt.title('Budget Constraints: $C_1 = K_0 - C_1$')

# Place legend to right of figure. PROVIDED
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))

plt.grid()
plt.tight_layout()
```

## Optimization

The person's problem is to choose $C_0$, $C_1$, $K_1$, and $K_2$ to maximize his utility subject to the two budget constraints and the boundary condition. However, the problem can be simplified considerably. Use the boundary condition to eliminate $K_2$ from the budget constraints. Then use the budget constraints to substitute $C_0$ and $C_1$ out of the utility function. The result is an unconstrained optimization problem in $K_1$.

\begin{align}
\max_{K_1} \; \log (K_0 - K_1) + \beta \log K_1,
\end{align}

Take the derivative with respect to $K_1$ and set the derivative equal to zero to obtain the *first-order* or *optimality* condition:

\begin{align}
-\frac{1}{K_0 - K_1} + \frac{\beta}{K_1} & = 0.
\end{align}

Solve for $K_1$ to find the optimal cake holding for period 1:

\begin{align}
\boxed{K_1  = \frac{\beta}{1+\beta}K_0}
\end{align}

Then use the budget constraints to infer optimal cake consumption in period 0:
\begin{align}
\boxed{C_0  = \frac{1}{1+\beta}K_0}
\end{align}

and period 1:

\begin{align}
\boxed{C_1  = \frac{\beta}{1+\beta}K_0}.
\end{align}

Notice that $C_0 + C_1 = K_0$ so all of the cake is eaten by the end.

### Example: Solve for the Optimal Consumption Path

Compute $K_1, C_0, C_1$ for $\beta = 0.9$ and $K_0 = 20$.

```python
# Define a function that returns the solution to the two-period cake-eating problem
def two_period_cake(beta,cake0):

    '''Function for computing solution to two-period cake-eating problem

            Args:
            beta (float):   subjective discount factor
            cake0 (float):  initial quantity of cake

        Returns:
            numpy array, numpy array
    '''

    # Initialize arrays for cake and consumption
    cake = np.zeros(3)
    consumption = np.zeros(2)

    # Assign values to cake array elements
    cake[0] = cake0
    cake[1] = beta/(1+beta)*cake[0]

    # Assign values to consumption array elements as implied by the budget constraints
    consumption[0] = cake[0]-cake[1]
    consumption[1] = cake[1]

    return cake,consumption

# Create a cariable called 'cake0' that stores initial cake value
cake0 = 20

# Create a cariable called 'beta' that stores beta value
beta = 0.9

# Compute the optimal path
cake,consumption = two_period_cake(beta=beta,cake0=cake0)
print('k[1]:',round(cake[1],5))
print('c[0]:',round(consumption[0],5))
print('c[1]:',round(consumption[1],5))
```

    k[1]: 9.47368
    c[0]: 10.52632
    c[1]: 9.47368

$\beta <1$ means that the person places greater weight on consumption in period 0 and therefore the person chooses to consume a greater share of the cake in period 0.