# Pricing Uncertainty Induced by Climate Change
by [Michael Barnett](https://sites.google.com/site/michaelduglasbarnett/home), [William Brock](https://www.ssc.wisc.edu/~wbrock/) and [Lars Peter Hansen](https://larspeterhansen.org/).

The latest draft of the paper can be found [here](https://larspeterhansen.org/research/papers/).

Notebook by: Jiaming Wang

## Overview

This notebook provides the source code and explanations for how we solve the model setting with __climate damages to consumption__. Users can find computational details for the model setting with __climate damages to growth__ in the notebook for the [Growth Damages Model](GrowthModel.ipynb).

Table of Contents

1  Overview

2  Code and Illustration

2.1  Choosing key parameters

2.2  Solving the HJB equation with consumption damages

2.2.1  Remark: Choices of model hyper parameters

2.2.2  Remark: Discretization of state spaces

2.2.3  Remark: Accounting for uncertainty about consumption damages

2.2.3.1  Low damage model

2.2.3.2  High damage model

2.2.3.3  Distorted model probabilities

2.2.3.4  Weighted damage models

2.2.4  Remark:  Details for solving HJB PDE

2.2.5  Remark: Conjugate gradient solver for linear system

2.2.6  Remark: PDE boundary conditions

2.2.7  Remark: Tolerance level and conergence criteria for HJB

2.2.8  Remark: Solving time and error analysis

2.3  Simulation

2.4  SCC Calculation Feyman Kac

2.4.1  Remark: Convergence criteria for Feyman Kac

2.5  Probabilities

3  Results

3.1  Implied worst case densities

3.2  SCC Decomposition

3.3  Emission trajectory

## Code and Illustration

```python
# Required packages
import os
import sys
sys.path.append('./src')
from supportfunctions import *
sys.stdout.flush()
```

### Choosing key parameters

```python
# Damage function choices
damageSpec = 'Weighted'  # Choose among "High"(Weitzman), 'Low'(Nordhaus) and 'Weighted' (arithmeticAverage of the two)

if damageSpec == 'High':
    weight = 0.0
elif damageSpec == 'Low':
    weight = 1.0
else:
    weight = 0.5

ξp =  1 / 4000  # Ambiguity Averse Paramter
# We stored solutions for ξp =  1 / 4000 to which we referred as "Ambiguity Averse" and ξp = 1000 as “Ambiguity Neutral” in the paper
# Sensible choices are from 0.0002 to 4000, while for parameters input over 0.01 the final results won't alter as much

if ξp < 1:
    aversespec = "Averse"
else:
    aversespec = 'Neutral'

smart_guess = False
model = 'model1'
filename = 'BBH_' + model + '_' + damageSpec + '_' + aversespec
filename
```

```python
# Loading Smart guesses
# Do Not run this cell if you don't want to use smart guesses, it might take longer than 18 hours. See remarks in section 2.2.8
guess = pickle.load(open('./data/{}guess.pickle'.format(damageSpec + aversespec), "rb", -1))
v0_guess = guess['v0']
q_guess = guess['q']
e_guess = guess['e']
# base_guess = guess['base']
# worst_guess = guess['worst']
smart_guess = True
```

### Solving the HJB equation with consumption damages

We now outline the code used to solve the HJB equation for the damages to consumption model. We summarize the steps for the numerical algorithm and refer to those steps in the code below. More details are provided in the remarks.

To solve the nonlinear partial differential equations that characterize the HJB equations for the planner's problems for our model, we use a so-called implicit, finite-difference scheme and a conjugate gradient method. Consultations with Joseph Huang, Paymon Khorrami and Fabrice Tourre played an important role in the software implementation. We briefly outline the steps to this numerical solution method below.

Recall that the HJB equation of interest for the consumption damages model includes both minimization and maximization:

\begin{align*} 0 = \max_{a \in {\mathbb A}} \min_{q > 0, \int q P(d\theta) =1 } \min_{g \in {\mathbb R}^m} & - \delta V(x)  + \delta (1 - \kappa) \left[ \log \left( {\alpha}  - i - j \right)
+ k -  d   \right] + \delta \kappa \left( \log e +  r \right) \cr &
+ {\frac {\partial V}{\partial x}} (x) \cdot \left[\int_\Theta  \mu_X(x,a \mid \theta) q(\theta) P(d\theta)  + \sigma_X(x) g\right] \cr &
+{\frac 1 2} \textrm{trace} \left[\sigma_X(x)' {\frac {\partial^2 V}{\partial x \partial x'}}(x) \sigma_X(x) \right] \cr &
+ {\frac {\xi_m} 2} g'g + \xi_p \int_\Theta [\log q(\theta)]  q(\theta) P(d \theta).
\end{align*}

We proceed recursively as follows:

1) start with a value function guess ${\widetilde V}(x)$ and a decision function ${\widetilde a}(x)$;

2) given $({\widetilde V}, {\widetilde a})$, solve the minimization problem embedded in the HJB equation and produce an exponentially-tilted  density ${\widehat  q}$ and drift distortion ${\widehat g}$  conditioned on $x$ and  using the  approach described   in section D of [Online Appendix](http://larspeterhansen.org/wp-content/uploads/2020/01/RFSPaper_BBH_final_appendix.pdf);

3) compute the implied relative entropy from the change in prior:
$$
{\widehat {\mathbb I}}(x) = \int_\Theta [\log {\widehat q}(\theta)]  {\widehat q}(\theta) P(d \theta);
$$

4)  solve the following maximization problem by choice of $a=(i,j,e)$:

\begin{align*}
& \delta (1 - \kappa)  \log \left( {\alpha}  - i - j \right)
 + \delta \kappa  \log e   \cr &
+ {\frac {\partial V}{\partial x}} (x) \cdot \int_\Theta  \mu_X\left(x,a   \mid \theta \right) {\widehat q}(\theta \mid x ) P(d\theta);
\end{align*}

   a) Compute ${\hat i}$ and ${\hat j}$ by solving the two first-order conditions for $i$ and $j$ with cobweb-style iterations.  Cobweb iterations converge or diverge depending the relative slopes of supply and demand functions.  By shrinking the step size, these slopes can be altered.

Expand the two equation system by adding a third equation that defines  a common "price" $p$,
$$
p =  {\frac {\delta (1-\kappa)}  {\alpha  - i - j} } = g(i+j).
$$
Write the two first-order conditions as
$$
p = {\frac {\phi_0 \phi_1 V_k(x) }{ 1 + \phi_1 i}} = f_1(i)
$$
$$
p  = V_r(x)  \left(  {\psi_0 \psi_1} \right) j^{\psi_1 - 1}  \exp\left[ \psi_1(k -  r)\right]  = f_2(j).
$$

Given $p$  and for step size $\tilde{\epsilon}$, compute

* $i^* = (f_1)^{-1}(p)$

* $j^* = (f_2)^{-1}(p)$

* $p^* = {\eta} g(i^* + j^*) + \left(1 - {\eta} \right) p$

* set $p  = p^*$.

Iterate to convergence.

b) Compute ${\hat e}$ by solving the first-order conditions
$$
{\frac {\delta \kappa} e} +  {\frac d {d e}} \left[V_x(x)  \cdot \int_\Theta  \mu_X\left(x, i, j, a  \mid \theta \right) {\widehat q}(\theta \mid x ) P(d\theta)\right] = 0 .
$$
These first-order conditions turn out not to depend on $(i,j)$.

5) use the minimization output from step (2) and  maximization output from step (4) and construct an adjusted drift using the following formula, which is the analog to formula (20) from the paper:
\begin{equation*}
{\widehat \mu}(x) = \int_\Theta  \mu_X\left(x, {\widehat a}  \mid \theta \right) {\widehat q}(\theta \mid x ) P(d\theta) + \sigma_X(x) {\widehat g}(x);
\end{equation*}

6)  construct the linear equation system for a new value function $V = {\widehat V}$:

\begin{align*}
0 =  & - \delta V(x)  + \delta (1 - \kappa) \left( \log \left[ {\alpha}  - {\widehat i}(x)  - {\widehat j} (x)  \right]
+ k -  d   \right) + \delta \kappa \left[ \log {\widehat e}(x)   +  r \right] \cr
& + {\frac {\partial V}{\partial x}} (x) \cdot {\widehat \mu}(x)
+{\frac 1 2} \textrm{trace} \left[\sigma_X(x)' {\frac {\partial^2 V}{\partial x \partial x'}}(x) \sigma_X(x) \right] \cr
& + {\frac {\xi_m} 2} {\widehat g}(x) \cdot {\widehat g}(x)   + \xi_p {\widehat {\mathbb I}}(x);
\end{align*}

7) modify this equation by adding a so-called "false transient" to the left-hand side:

\begin{align*}
{\frac {V(x) - {\widetilde V}(x)} \epsilon}  =  & - \delta V(x)  + \delta (1 - \kappa) \left( \log \left[ {\alpha}  - {\widehat i}(x)  - {\widehat j} (x)  \right]
+ k -  d    \right) + \delta \kappa \left[ \log {\widehat e}(x)   +  r \right] \cr
& + {\frac {\partial V}{\partial x}} (x) \cdot {\widehat \mu}(x)
+{\frac 1 2} \textrm{trace} \left[\sigma_X(x)' {\frac {\partial^2 V}{\partial x \partial x'}}(x) \sigma_X(x) \right] \cr
& + {\frac {\xi_m} 2} {\widehat g}(x) \cdot {\widehat g}(x)   + \xi_p {\widehat {\mathbb I}}(x);
\end{align*}

8) solve the linear system from step (7) for $V= {\widehat V}$ using a conjugate-gradient method;

9) set ${\widetilde V} = {\widehat V}$ and ${\widetilde a} = {\widehat a}$ and repeat steps (2) - (8)  until convergence.

```python
start_time = time.time()

McD = np.loadtxt('./data/TCRE_MacDougallEtAl2017_update.txt')
par_lambda_McD = McD / 1000

β𝘧 = np.mean(par_lambda_McD)  # Climate sensitivity parameter, MacDougall (2017)
σᵦ = np.var(par_lambda_McD, ddof = 1)  # varaiance of climate sensitivity parameters
λ = 1.0 / σᵦ

# Parameters as defined in the paper
δ = 0.01
κ = 0.032
σ𝘨 = 0.02
σ𝘬 = 0.0161
σ𝘳 = 0.0339
α = 0.115000000000000
ϕ0 = 0.0600
ϕ1 = 16.666666666666668
μk = -0.034977443912449
ψ0 = 0.112733407891680
ψ1 = 0.142857142857143

# parameters for damage function settings
power = 2
γ1 = 0.00017675
γ2 = 2. * 0.0022
γ2_plus = 2. * 0.0197
γ̄2_plus = weight * 0 + (1 - weight) * γ2_plus

σ1 = 0
σ2 = 0
ρ12 = 0
F̄ = 2
crit = 2
F0 = 1

xi_d = -1 * (1 - κ)

# See Remark 2.1.1 regarding the choice of ε and η
# False Trasient Time step
ε = 0.3
# Cobweb learning rate
η = 0.05

# Specifying Tolerance level
tol = 1e-8

# Grids Specification

# Coarse Grids
# nR = 30
# nF = 40
# nK = 25
# R = np.linspace(R_min, R_max, nR)
# F = np.linspace(F_min, F_max, nF)
# K = np.linspace(K_min, K_max, nK)

# hR = R[1] - R[0]
# hF = F[1] - F[0]
# hK = K[1] - K[0]

# Dense Grids

R_min = 0
R_max = 9
F_min = 0
F_max = 4000
K_min = 0
K_max = 18

hR = 0.05
hF = 25
hK = 0.15

R = np.arange(R_min, R_max + hR, hR)
nR = len(R)
F = np.arange(F_min, F_max + hF, hF)
nF = len(F)
K = np.arange(K_min, K_max + hK, hK)
nK = len(K)

# Discretization of the state space for numerical PDE solution.
# See Remark 2.1.2
(R_mat, F_mat, K_mat) = np.meshgrid(R,F,K, indexing = 'ij')
stateSpace = np.hstack([R_mat.reshape(-1,1,order = 'F'),F_mat.reshape(-1,1,order = 'F'),K_mat.reshape(-1,1,order = 'F')])

# Inputs for function quad_int
# Integrating across parameter distribution
quadrature = 'legendre'
n = 30
a = β𝘧 - 5 * np.sqrt(σᵦ)
b = β𝘧 + 5 * np.sqrt(σᵦ)

v0 = κ * R_mat + (1-κ) * K_mat - β𝘧 * F_mat

FC_Err = 1
episode = 0

if smart_guess:
    v0 = v0_guess
    q = q_guess
    e_star = e_guess
    episode = 1
    ε = 0.2

while episode == 0 or FC_Err > tol:
    vold = v0.copy()
    # Applying finite difference scheme to the value function
    v0_dr = finiteDiff(v0,0,1,hR)
    v0_df = finiteDiff(v0,1,1,hF)
    v0_dk = finiteDiff(v0,2,1,hK)

    v0_drr = finiteDiff(v0,0,2,hR)
    v0_drr[v0_dr < 1e-16] = 0
    v0_dr[v0_dr < 1e-16] = 1e-16
    v0_dff = finiteDiff(v0,1,2,hF)
    v0_dkk = finiteDiff(v0,2,2,hK)
    if episode > 2000:
        ε = 0.1
    elif episode > 1000:
        ε = 0.2
    else:
        pass

    if episode == 0:
        # First time into the loop
        B1 = v0_dr - xi_d * (γ1 + γ2 * F_mat * β𝘧 + γ2_plus * (F_mat * β𝘧 - F̄) ** (power - 1) * (F_mat >= (crit / β𝘧))) * β𝘧 * np.exp(R_mat) - v0_df * np.exp(R_mat)
        C1 = - δ * κ
        e = -C1 / B1
        e_hat = e
        Acoeff = np.ones(R_mat.shape)
        Bcoeff = ((δ * (1 - κ) * ϕ1 + ϕ0 * ϕ1 * v0_dk) * δ * (1 - κ) / (v0_dr * ψ0 * 0.5) * np.exp(0.5 * (R_mat - K_mat))) / (δ * (1 - κ) * ϕ1)
        Ccoeff = -α  - 1 / ϕ1
        j = ((-Bcoeff + np.sqrt(Bcoeff ** 2 - 4 * Acoeff * Ccoeff)) / (2 * Acoeff)) ** 2
        i = α - j - (δ * (1 - κ)) / (v0_dr * ψ0 * 0.5) * j ** 0.5 * np.exp(0.5 * (R_mat - K_mat))
        q = δ * (1 - κ) / (α - i - j)
    else:
        e_hat = e_star

        # Step 4 (a) : Cobeweb scheme to update controls i and j; q is an intermediary variable that determines i and j
        Converged = 0
        nums = 0
        while Converged == 0:
            i_star = (ϕ0 * ϕ1 * v0_dk / q - 1) / ϕ1
            j_star = (q * np.exp(ψ1 * (R_mat - K_mat)) / (v0_dr * ψ0 * ψ1)) ** (1 / (ψ1 - 1))
            if α > np.max(i_star + j_star):
                q_star = η * δ * (1 - κ) / (α - i_star - j_star) + (1 - η) * q
            else:
                q_star = 2 * q
            if np.max(abs(q - q_star) / η) <= 1e-5:
                Converged = 1
                q = q_star
                i = i_star
                j = j_star
            else:
                q = q_star
                i = i_star
                j = j_star

            nums += 1
        if episode % 100 == 0:
            print('Cobweb Passed, iterations: {}, i error: {:10f}, j error: {:10f}'.format(nums, np.max(i - i_star), np.max(j - j_star)))

    a1 = np.zeros(R_mat.shape)
    b1 = xi_d * e_hat * np.exp(R_mat) * γ1
    c1 = 2 * xi_d * e_hat * np.exp(R_mat) * F_mat * γ2
    λ̃1 = λ + c1 / ξp
    β̃1 = β𝘧 - c1 * β𝘧 / (ξp * λ̃1) -  b1 /  (ξp * λ̃1)
    I1 = a1 - 0.5 * np.log(λ) * ξp + 0.5 * np.log(λ̃1) * ξp + 0.5 * λ * β𝘧 ** 2 * ξp - 0.5 * λ̃1 * (β̃1) ** 2 * ξp
    R1 = 1 / ξp * (I1 - (a1 + b1 * β̃1 + c1 / 2 * β̃1 ** 2 + c1 / 2 / λ̃1))
    J1_without_e = xi_d * (γ1 * β̃1 + γ2 * F_mat * (β̃1 ** 2 + 1 / λ̃1)) * np.exp(R_mat)

    π̃1 = weight * np.exp(-1 / ξp * I1)

    # Step (2), solve minimization problem in HJB and calculate drift distortion
    # See remark 2.1.3 for more details
    if episode == 0 or (smart_guess and episode == 1):
        @nb.jit(nopython = True, parallel = True)
        def scale_2_fnc(x, e_hat):
            return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e_hat)  * normpdf(x,β𝘧,np.sqrt(σᵦ))

        @nb.jit(nopython = True, parallel = True)
        def q2_tilde_fnc(x, e_hat, scale_2):
            return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e_hat) / scale_2

        @nb.jit(nopython = True, parallel = True)
        def J2_without_e_fnc(x, e_hat, scale_2):
            return xi_d * np.exp(R_mat) * q2_tilde_fnc(x, e_hat, scale_2) * (γ1 * x + γ2 * F_mat * x ** 2 + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * normpdf(x,β𝘧,np.sqrt(σᵦ))

        (xs,ws) = quad_points_legendre(n)
        xs = (b-a) * 0.5  * xs + (a + b) * 0.5
        s = np.prod((b-a) * 0.5)

    scale_2 = np.zeros(F_mat.shape)
    for i_iter in range(n):
        scale_2 += ws[i_iter] * scale_2_fnc(xs[i_iter], e_hat)
    scale_2 = s * scale_2

    I2 = -1 * ξp * np.log(scale_2)

    J2_without_e = np.zeros(F_mat.shape)
    for i_iter in range(n):
        J2_without_e += ws[i_iter] * J2_without_e_fnc(xs[i_iter], e_hat, scale_2)
    J2_without_e = s * J2_without_e
    J2_with_e = J2_without_e * e_hat

    R2 = (I2 - J2_with_e) / ξp
    π̃2 = (1 - weight) * np.exp(-1 / ξp * I2)
    π̃1_norm = π̃1 / (π̃1 + π̃2)
    π̃2_norm = 1 - π̃1_norm

    # step 4 (b) updating e based on first order conditions
    expec_e_sum = (π̃1_norm * J1_without_e + π̃2_norm * J2_without_e)

    B1 = v0_dr - v0_df * np.exp(R_mat) - expec_e_sum
    C1 = -δ * κ
    e = -C1 / B1
    e_star = e

    J1 = J1_without_e * e_star
    J2 = J2_without_e * e_star

    # Step (3) calculating implied entropies
    I_term = -1 * ξp * np.log(π̃1 + π̃2)

    R1 = (I1 - J1) / ξp
    R2 = (I2 - J2) / ξp

    # Step (5) solving for adjusted drift
    drift_distort = (π̃1_norm * J1 + π̃2_norm * J2)

    if weight == 0 or weight == 1:
        RE = π̃1_norm * R1 + π̃2_norm * R2
    else:
        RE = π̃1_norm * R1 + π̃2_norm * R2 + π̃1_norm * np.log(
            π̃1_norm / weight) + π̃2_norm * np.log(π̃2_norm / (1 - weight))

    RE_total = ξp * RE

    # Step (6) and (7) Formulating HJB False Transient parameters
    # See remark 2.1.4 for more details
    A = -δ * np.ones(R_mat.shape)
    B_r = -e_star + ψ0 * (j ** ψ1) * np.exp(ψ1 * (K_mat - R_mat)) - 0.5 * (σ𝘳 ** 2)
    B_f = e_star * np.exp(R_mat)
    B_k = μk + ϕ0 * np.log(1 + i * ϕ1) - 0.5 * (σ𝘬 ** 2)
    C_rr = 0.5 * σ𝘳 ** 2 * np.ones(R_mat.shape)
    C_ff = np.zeros(R_mat.shape)
    C_kk = 0.5 * σ𝘬 ** 2 * np.ones(R_mat.shape)
    D = δ * κ * np.log(e_star) + δ * κ * R_mat + δ * (1 - κ) * (np.log(α - i - j) + K_mat) + drift_distort + RE_total # + I_term

    # Step (8) solving linear system using a conjugate-gradient method in C++
    # See remark 2.1.5, 2.1.6 for more details
    out = PDESolver(stateSpace, A, B_r, B_f, B_k, C_rr, C_ff, C_kk, D, v0, ε, solverType = 'False Transient')
    out_comp = out[2].reshape(v0.shape,order = "F")

    # Calculating PDE error and False Transient error
    PDE_rhs = A * v0 + B_r * v0_dr + B_f * v0_df + B_k * v0_dk + C_rr * v0_drr + C_kk * v0_dkk + C_ff * v0_dff + D
    PDE_Err = np.max(abs(PDE_rhs))
    FC_Err = np.max(abs((out_comp - v0)))
    if episode % 1 == 0:
        print("Episode {:d}: PDE Error: {:.10f}; False Transient Error: {:.10f}; Iterations: {:d}; CG Error: {:.10f}" .format(episode, PDE_Err, FC_Err, out[0], out[1]))
    episode += 1

    # step 9: keep iterating until convergence
    v0 = out_comp

print("Episode {:d}: PDE Error: {:.10f}; False Transient Error: {:.10f}; Iterations: {:d}; CG Error: {:.10f}" .format(episode, PDE_Err, FC_Err, out[0], out[1]))
print("--- %s seconds ---" % (time.time() - start_time))

```

#### Remark: Choices of model hyper parameters
The choices of ${\eta}$ in step (4) and $\epsilon$ in step (7) are made by trading off increases in speed of convergence, achieved by increasing their magnitudes, and enhancing stability of the iterative algorithm, achieved by decreasing their magnitudes.

#### Remark: Discretization of state spaces
We discretize the state space of $x$ using a set number of points along each of the three dimensions and impose a  fixed step size between points for each of these dimensions.  For interior points, we approximate the first derivatives using a first-order upwind scheme while the second derivatives are calculated using a central difference scheme. Upwind schemes are one-sided difference approximations that use the sign of the drifts for the states to determine the direction of the difference.  (See, for instance,  "An Introduction to Finite Difference Methods for PDE methods in Finance" by Agnes Tourin, Fields Institute.)  At boundary points we sometimes only have one option used in the approximation. We use a symmetric second difference approximation whenever possible and switch to a one-sided approximation as needed at boundary points.  With this construction, we have reduced the right-hand side of equaton in step (7) as a matrix applied to the value function at the chosen set of discrete points.

#### Remark: Accounting for uncertainty about consumption damages

In calculating consumption damage uncertainty, the term of interest is the contribution of smooth ambiguity to the planner's HJB equation:

\begin{eqnarray}
(\kappa -1) \left[ \gamma_1 \beta + \gamma_2 \beta^2 f + \gamma_2^+\beta\left(\beta f - {\overline \gamma} \right)^2 \mathbb{1}(\beta f > {\overline \gamma} ) \right] \exp(r) e.
\end{eqnarray}

Calculating this contibution to the HJB equation depends on a weighted average of two conditional models: the low damage model and the high damage model. The weighting of these conditional contributions is calculated using either the baseline parameter distribution or the ambiguity aversion adjusted or worst-case parameter distribution.

##### Low damage model

For the low damage model $\gamma_2^+ = 0$ and so we can derive a quasi-analytical expression for the impact of ambiguity aversion:

We begin by computing
\begin{eqnarray}
{\mathcal I}_1  = - \xi_p \log E\left( \exp\left[ - {\frac 1 {\xi_p}} \left(  {\sf a}_1 + {\sf b}_1 \beta  + {\frac {{\sf c}_1} 2}   \beta ^2 \right) \right] \mid i \right),
\end{eqnarray}
where
\begin{align*}
{\sf a}_1& = 0,  \cr
 {\sf b}_1   & =  (\kappa - 1) {\widehat e} \exp(r)  \gamma_1, \cr
{\sf c}_1   & =  (\kappa -1) {\widehat e} \exp(r)  f \gamma_2.
\end{align*}

We exploit the exponential quadratic form of the normal density function by __completing-the-square__ to derive our expression. Specifically, we compute the precision for the worst-case distribution by equating the log-quadratic terms from the distributional expression for the altered distribution of $\beta$
\begin{eqnarray}
 \lambda  + {\frac {{\sf c}_1}{\xi_p}} = {\widetilde \lambda}_1,
\end{eqnarray}
where $\lambda$ is the precision for the baseline distribution and ${\widetilde \lambda}_i$ is the precision for the worst-case distribution.

To calculate the worst-case mean, we equate the linear terms from the distributional expression for the altered distribution of $\beta$
\begin{eqnarray}
{\overline \beta} \lambda - {\frac 1 {\xi_p}}  {\sf b}_1 = {\widetilde \beta}_1 {\widetilde \lambda}_1.
\end{eqnarray}
Thus, the worst-case mean is:

\begin{eqnarray}
{\widetilde \beta}_1  =  {\overline \beta}\left({\frac {\lambda}{{\widetilde \lambda}_1}} \right)   - {\frac 1 {\xi_p {{\widetilde \lambda}_1 }}} {\sf b}_1  = {\overline \beta} -  {\frac {{\sf c}_1}{\xi_p \widetilde \lambda_i}}{\overline \beta}
-  {\frac 1 {\xi_p {{\widetilde \lambda}_1 }}} {\sf b}_1
\end{eqnarray}

Finally, we compute ${\mathcal I}_i$ by a) bringing it inside an exponential term,  b) multiplying this by the worst-case normal density that we just deduced, and c) equating the constant terms:

\begin{equation*}
- {\frac 1 {\xi_p}} {\mathcal I}_1   +   {\frac 1 2} \log {\widetilde \lambda}_1 - {\frac 1   2} \left({\widetilde  \beta}_1 \right)^2 {\widetilde \lambda}_1  = - {\frac 1 {\xi_p}} {\sf a}_1 + {\frac 1  2} \log \lambda - {\frac 1 2} \left({\overline \beta} \right)^2 \lambda .
\end{equation*}

Therefore

\begin{eqnarray*}
{\mathcal I}_1 & = & {\sf a}_1  -   {\frac {\xi_p}  2} \log \lambda + {\frac {\xi_p}  2} \log {\widetilde \lambda}_1
 + {\frac {\xi_p \lambda}  2} \left({\overline \beta} \right)^2   - {\frac {\xi_p {\widetilde \lambda}_1}  2} \left({\widetilde  \beta}_1 \right)^2, \cr
{\mathcal R}_1 & = & {\frac 1 {\xi_p}} \left({\mathcal I}_1 -  \left[ {\sf a}_1  + {\sf b}_1 {\widetilde \beta}_1
+ {\sf c}_1  \left({\widetilde \beta}_1 \right)^2 + {\frac {{\sf c}_1} { {\widetilde \lambda}_1 }} \right]  \right), \cr
{\mathcal J}_1(e) & = & (\kappa -1)  \left( \gamma_1 {\widetilde \beta}_1 + \gamma_2 \left[  \left({\widetilde \beta}_1\right)^2
+ {\frac 1 {\widetilde \lambda}_1}\right]
f\right)  \exp(r) e,
\end{eqnarray*}

where ${\mathcal I}$ is the smooth damage ambiguity contribution to the planner's HJB equation; ${\mathcal R }$ measures the relative entropies of the model; and ${\mathcal J}$ measures the drift distortion altered by the worst-case density due to concerns about ambiguity.

##### High damage model

We must proceed differently for the high damage model with numerical computation. We begin by calculating
\begin{eqnarray}
{\rm num}_2(\beta)  = \exp\left( - {\frac 1 \xi_p} (\kappa -1) \left[ \gamma_1 \beta + \gamma_2 \beta^2 f + \gamma_2^+ \beta\left(\beta f - {\overline \gamma} \right) \mathbb{1}(\beta f > {\overline \gamma} ) \right] \exp(r) {\widehat e}\right),
\end{eqnarray}
and then compute
\begin{eqnarray}
{\rm scale}_2 = \int_\beta {\rm num}_2(\beta) p(\beta) d\beta
\end{eqnarray}

via "Gauss-Legendre"  quadrature. We then form a density conditioned on model two:
\begin{eqnarray}
{\widetilde q}_2(\beta)  = {\frac {{\rm num}(\beta)}{{\rm scale}2}}
\end{eqnarray}

relative to $p(\beta)$. We call ${\widetilde q}_2(\beta)$  the implied ambiguity-adjusted probabilities.

Next, we compute
\begin{eqnarray}
{\mathcal  I}_2 =  -\xi_p \log {\rm scale}_2,
\end{eqnarray}

along with

\begin{eqnarray}
{\mathcal J}_2(e) = {\frac {(\kappa - 1) \left( \displaystyle\int_\beta    \left[ \gamma_1 \beta + \gamma_2 \beta^2 f + \gamma_2^+\beta\left(\beta f - {\overline \gamma} \right) \mathbb{1}(\beta f > {\overline \gamma} ) \right]
{\rm num}_2(\beta)p(\beta) d \beta \right) \exp(r) e}{{\rm scale}_2}},
\end{eqnarray}

using "Gauss-Legendre" quadratre for the numerator integral.
In computing the integral term that involves high damage models, we choose "Gauss-Legendre" quadrature with 30 integration points on $[\beta_f-5\sigma_f,\beta_f+5\sigma_f] $. The __quad_int__ function replicates this calculation and can be found at './src/supportfunctions.py' .

Notice that ${\mathcal J}_2(e)$ can be computed for an arbitrary $e$ since the numerator integral does not depend on $e$.

With these computations in hand, we form the relative entropy conditioned on model two:
\begin{eqnarray}
{\mathcal R}_2 = {\frac 1 {\xi_p}} \left[{\mathcal I}_2 - {\mathcal J}_2\left( {\widehat e} \right) \right] .
\end{eqnarray}
Notice that ${\mathcal J}_2(e)$ can be computed for an arbitrary $e$ since the numerator integral does not depend on $e$.

##### Distorted model probabilities

Worst-case probabilities over models satisfy

\begin{eqnarray}
{\widetilde \pi}_i \  \ \propto  \  \ {\widehat \pi}_i \exp\left( -{\frac 1 {\xi_p}} {\mathcal I}_i \right),
\end{eqnarray}

where the constant of proportionality scales the right-hand side so that the resulting ${\tilde \pi}_i$ sum to one.

We start by forming

\begin{eqnarray}
\delta\alpha(\log e) - v_{r}e +v_{F}\exp(r) e + \left[ {\widetilde \pi}_1 {\mathcal J}_1(e) + {\widetilde \pi}_2 {\mathcal J}_2(e) \right],
\end{eqnarray}

and maximize with respect to $e$ with solution $e^*$.
Also, we update the objective with a new entropy penalty give by

\begin{eqnarray}
\xi_p {\mathcal R},
\end{eqnarray}

where

\begin{eqnarray}
{\mathcal R} =  {\widetilde \pi}_1 {\mathcal R}_1 + {\widetilde \pi}_2 {\mathcal R}_2  + {\widetilde \pi}_1 \left( \log {\widetilde \pi}_1 - \log {\widehat \pi}_1 \right) + {\widetilde \pi}_2 \left( \log {\widetilde \pi}_2 - \log {\widehat \pi}_2\right),
\end{eqnarray}

and with the optimized contribution

\begin{eqnarray}
\delta\alpha(\log e^*) - v_{r}e^* + \left[ {\widetilde \pi}_1 {\mathcal J}_1(e^*) + {\widetilde \pi}_2 {\mathcal J}_2(e^*) \right]
\end{eqnarray}

along with the other contributions to the HJB equation.

We then let ${\widehat e} = e^*$ and repeat.

##### Weighted damage models

The weighted damage models seting calculates ${\mathcal I}$, ${\mathcal J}$ and ${\mathcal R}$ using a weighted average of the low damage model and high damage model, where the weights come from thevdistorted model probabilities ${\widetilde \pi}$.

#### Remark:  Details for solving HJB PDE
Recall that in order to solve the prefernce HJB model, we transform the question in solving a linear system of equations.
\begin{eqnarray}
{\frac {V(x) - {\widetilde V}(x)} \epsilon}  & =  &   V(x) \cdot A(x)+{\frac {\partial V}{\partial x}} (x) \cdot  B(x)   + {\frac {\partial^2 V}{\partial x \partial x'}}(x) \cdot C(x)+ D(x)
\end{eqnarray}
Where
\begin{eqnarray}
A(x) &=& - \delta \\
B(x) &=& {\widehat \mu}(x)\\
C(x) &=& {\frac 1 2} \textrm{trace} \left[\sigma_X(x)' I \sigma_X(x) \right] \\
D(x) &=& \delta (1 - \kappa) \left( \log \left[ {\alpha}  - {\widehat i}(x)  - {\widehat j} (x)  \right] + k -  d  \right) + \delta \kappa \left[ \log {\widehat e}(x)   +  r \right] + {\frac {\xi_m} 2} {\widehat g}(x) \cdot {\widehat g}(x)   + \xi_p {\widehat {\mathbb I}}(x) \\
\end{eqnarray}

Multiplying by $\epsilon$ and rearranging the above equation gives:

\begin{align*} [\epsilon  A(x) - 1] V(x) + \epsilon {\widehat B}(x) \cdot {\frac {\partial V}{\partial x}} (x) + \epsilon \hskip{.05in}\rm{trace}\left[\frac {\partial^2 V}{\partial x \partial x'}(x)C(x)
\right] + \epsilon {\widehat D}(x) + {\widetilde V}(x) = 0
\end{align*}

which yields the linear system of equation we aimed to solve in our c++ solver.

Precisely speaking our value function is a three-dimension tensor $ V(R,T,K) $ with default grid size of (181,161,121) in "F" order. In order to solve the linear system, we reshape $ V(x) $ into a one dimension array with size of (3526061,1) and conduct similar adjustments to $ A(x) $, $ B(x) $, $ C(x) $ and $ D(x) $ respectively.

In "F" order, when we call our initial value function at grid point (i,j,k) $ V(i,j,k) $, we instead call equivalent reshaped value fnction $V(i + j * N_i + k * N_i * N_j)$ where $N_i$, $N_j$ and $N_k$ stands for number of grid poitns in that dimension.

#### Remark: Conjugate gradient solver for linear system

We solve the matrix counterpart to the equation in step (8) using the conjugate gradient algorithm.  This is a well known iterative algorithm designed to  solve a minimization problem:  $\frac 1 2 (\Lambda y - \lambda)'(\Lambda y - \lambda)$ for a  nonsingular matrix $\Lambda$ and vector $\lambda$.   The $y$ that minimizes this expression  satisfies the linear equation $\Lambda y = \lambda$.  The matrix $\Lambda$ and vector $\lambda$ come from the numerical approximation of below equation.  We measure the conjugate gradient error by

\begin{equation}
\sqrt{{\frac {(\Lambda y - \lambda)'(\Lambda y - \lambda)}{\lambda'\lambda}}}.
\end{equation}

We prespecify a conjugate gradient error bound and a bound on the difference in value functions between iterations and take as the starting point for conjugate gradient the output from the previous iteration.  We achieve convergence when the difference in value functions between iterations satisfies a prespecified error bound.  Upon convergence, we compute the maximum error for the matrix approximation to the right-hand side of equation system in step (7). We call this the maximum pde error.

#### Remark: PDE boundary conditions
While we are computing one-sided difference approximations at boundary points, we are not imposing additional boundary conditions on our finite state space as is often done when solving pde's with regular boundaries. Instead we aim to approximate pde solutions for the stochastic differential equation with unattainable boundaries.

#### Remark: Tolerance level and conergence criteria for HJB

In choosing the tolerance level, we tested the time it takes to solve the PDE numerically is decreasing with the size of $\epsilon$, while the stability of the program is also decreasing with $\epsilon$.
\begin{eqnarray*}
\max_x |V(x)-\widetilde{V}(x)| < tol
\tag{0}
\end{eqnarray*}

We choose time varying $\epsilon$ to balance the efficiency of the program and for room to explore different magnitude of the uncertainty.
* We used variable step sizes to speed program up while ensuring convergence: 0.3 for the first 1000 iterations, 0.2 for 1001-2000 iterations and 0.1 going forward.
*  For averse low damage case, normal variable step sizes scheme didn't work so we used 0.05 after 7000 iterations.
*  We used fixed step size at 0.1 for growth neutral case to ensure convergence

The table belows lists the convergence criteria and final errors for solving the HJB equations for the different consumption damages settings. We solved all models listed in this table for the setting with damages to consumption by the false transient algorithm. Outer loop convergence was evaluted on the change in value functions.

| ambiguity    |    damage    | Cobweb tolerance |  outer loop tolerance  | CG tolerance |  PDE Error | Iterations |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|averse|  high  | 1e-5 | 1e-8 | 1e-10 | 6.88e-6 | 10394|
|averse| weighted | 1e-5 | 1e-8 | 1e-10 | 6.25e-6| 10318|
|averse|low| 1e-5 | 1e-8 | 1e-10 | 3.83e-6| 13596 |
|neutral|high  | 1e-5 | 1e-8 | 1e-10 | 5.67e-6| 10424 |
|neutral|weighted  | 1e-5 | 1e-8 | 1e-10 | 4.60e-6| 10427|
|neutral|low  | 1e-5 | 1e-8 | 1e-10 | 3.73e-6| 20200 |

#### Remark: Solving time and error analysis

Below we outline the time to run our code for each tollerance level, referencing to the weighted damage models, ambiguity averse case. The green line denotes the cumulative running time while the red and blue lines denote error levels. Notice that the false transient error decreases log linearly across time while the PDE error its a lower bound that varies case by case.

For the weighted damage models, ambiguity averse case, it took 10318 iterations to reach an outer loop error (defined by equation $(0)$) of 1e-8, a region where the PDE error essentially reaces its lower bound. It takes up to 18 hours to reach this tolerance level solving the HJB directly as decribed. The user can solve a coarse grid size version much faster. For example, it only takes 5 minutes to solve a similar problem of grid size 30 * 40 * 25.

```python
error_log = pickle.load(open("./data/erroranalysis.pickle", "rb", -1))
x = np.arange(0,len(error_log['lhs_err'])+1)
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x = x, y = error_log['rhs_err'], name = "PDE error (right)",), secondary_y=True)
fig.add_trace(go.Scatter(x = x, y = error_log['lhs_err'], name = "False Transient error (right)",), secondary_y=True)
fig.add_trace(go.Scatter(x = x[2:], y = error_log['run_time'] / 3600, name = "Running times", ),secondary_y=False)
fig.update_layout(title_text="errors and runtime by iterations")

# Set x-axis title
fig.update_xaxes(title_text="Iterations")

# Set y-axes titles
fig.update_layout(yaxis2 = dict(title="log10 scale Error", type='log'), yaxis1 = dict(title="total time spent (hours)"))
fig.show()
```

### Simulation

Here we simulated the trajectory given our model solutions. The initial values for the model solution simulations are given below:

|Variable|Initial Value |
|-|-|
| $Y_0$       |  80  |
| $K_0^{\alpha}$ |666.67|
| $R_0$ | 650 |
| $F_0$ | 320 |
| $D_0$ | $\nabla \Gamma(F_0)$ |

The values for GDP comes from the World Bank database and the capital value is implied by the assumed productivity parameter and this GDP value. The values are in line with Hambel et al. (2018) and Pindyck and Wang (2013). The value for reserves comes from estimates of existing recoverable reserves of oil and coal from the EIA and BP (2018) and is consistent with the empirical measures of reserves cited by McGlade and Ekins (2015), who provide further details on this data, Golosov et al (2014), and Rogner (1997).
The initial values of atmospheric temperature anomaly and atmospheric carbon concentration come from the NASA-GISS, NOAA, and Berkeley Earth datasets. The initial value of damages is implied by our damage function and the initial value of cumulative emissions.

We remind readers here that evolution processes for log reserves (R), cumulative emssions(F) and log capitals (K) are:

\begin{equation}
d\log R_t = - \left( {\frac {E_t}{R_t} } \right) dt + \psi_0  \left({\frac {J_t}{R_t}} \right)^{\psi_1} dt -
{\frac {|\sigma_R|^2} 2} dt  + \sigma_R \cdot dW_t,
\end{equation}

\begin{equation}
d F_t =  E_t dt,
\end{equation}

\begin{equation}
d \log K_t = \zeta_{K}(Z_{t})dt +  \phi_0 \log \left(1 + \phi_1 {\frac {I_t}{K_t}} \right) dt - {\frac  {| \sigma_K |^2} 2 dt}
+\sigma_{K}\cdot dW_{t}.
\end{equation}

We set all volatility parameters to 0.

For numerical stability, we used linear interpolation in presenting plots in the paper. We provide code for both linear and cubic spline interpolation below.

```python
# Our customized interpolation class, which supports Cubic Spline and Linear interpolation for grid data
class GridInterp():

    def __init__(self, grids, values, method = 'Linear'):

        # unpacking
        self.grids = grids
        (self.xs, self.ys, self.zs) = grids
        self.nx = len(self.xs)
        self.ny = len(self.ys)
        self.nz = len(self.zs)

        self.values = values

        assert (self.nx, self.ny, self.nz) == values.shape, "ValueError: Dimensions not match"
        self.method = method

    def get_value(self, x, y, z):

        if self.method == 'Linear':

            func = RegularGridInterpolator(self.grids, self.values)
            return func([x,y,z])[0]

        elif self.method == 'Spline':

            func1 = CubicSpline(self.xs, self.values)
            yzSpace = func1(x)

            func2 = CubicSpline(self.ys, yzSpace)
            zSpace = func2(y)

            func3 = CubicSpline(self.zs, zSpace)
            return func3(z)

        else:
            raise ValueError('Method Not Supported')
```

```python
method = 'Linear' # Specify interpolating methods
T = 100
pers = 4 * T
dt = T / pers
nDims = 5
its = 1

gridpoints = (R, F, K)

# emissions
e_func_r = GridInterp(gridpoints, e, method)
def e_func(x):
    return e_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# investment in reserves
j_func_r = GridInterp(gridpoints, j, method)
def j_func(x):
    return max(j_func_r.get_value(np.log(x[0]), x[2], np.log(x[1])), 0)

# investment in productive capitals
i_func_r = GridInterp(gridpoints, i, method)
def i_func(x):
    return i_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# first derivative of Value function w.r.t R
v_drfunc_r = GridInterp(gridpoints, v0_dr, method)
def v_drfunc(x):
    return v_drfunc_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# first derivative of Value function w.r.t F
v_dffunc_r = GridInterp(gridpoints, v0_df, method)
def v_dffunc(x):
    return v_dffunc_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# first derivative of Value function w.r.t K
v_dkfunc_r = GridInterp(gridpoints, v0_dk, method)
def v_dkfunc(x):
    return v_dkfunc_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# Value function
v_func_r = GridInterp(gridpoints, v0, method)
def v_func(x):
    return v_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# Distorted model probabilities
pi_tilde_1_func_r = GridInterp(gridpoints, π̃1 / (π̃1 + π̃2), method)
def pi_tilde_1_func(x):
    return pi_tilde_1_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

pi_tilde_2_func_r = GridInterp(gridpoints, π̃2 / (π̃1 + π̃2), method)
def pi_tilde_2_func(x):
    return pi_tilde_2_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# ambiguity adjusted probabilities
def scale_2_fnc(x):
    return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e_hat)  * norm.pdf(x,β𝘧,np.sqrt(σᵦ))

scale_2 = quad_int(scale_2_fnc, a, b, n, 'legendre')
def q2_tilde_fnc(x):
    return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e_hat) / scale_2

# Below are distorted model drifts for low damage and high damage models, see remark 3 for more details
def base_model_drift_func(x):
    return np.exp(R_mat) * e * (γ1 * x + γ2 * x ** 2 * F_mat + γ̄2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * norm.pdf(x,β𝘧,np.sqrt(σᵦ))
base_model_drift =  quad_int(base_model_drift_func, a, b, n, 'legendre')

mean_nordhaus = β̃1
lambda_tilde_nordhaus = λ̃1
nordhaus_model_drift = (γ1 * mean_nordhaus + γ2 * (1 / lambda_tilde_nordhaus + mean_nordhaus ** 2) * F_mat) * np.exp(R_mat) * e

def weitzman_model_drift_func(x):
    return np.exp(R_mat) * e * q2_tilde_fnc(x) * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄ ) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * norm.pdf(x,β𝘧,np.sqrt(σᵦ))
weitzman_model_drift = quad_int(weitzman_model_drift_func, a, b, n, 'legendre')

nordhaus_drift_func_r = GridInterp(gridpoints, nordhaus_model_drift, method)
def nordhaus_drift_func(x):
    return nordhaus_drift_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

weitzman_drift_func_r = GridInterp(gridpoints, weitzman_model_drift, method)
def weitzman_drift_func(x):
    return weitzman_drift_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

base_drift_func_r = GridInterp(gridpoints, base_model_drift, method)
def base_drift_func (x):
    return base_drift_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

# drifts for each diffusion process
def muR(x):
    return -e_func(x) + ψ0 * (j_func(x) * x[1] / x[0]) ** ψ1
def muK(x):
    return (μk + ϕ0 * np.log(1 + i_func(x) * ϕ1))
def muF(x):
    return e_func(x) * x[0]
def muD_base(x):
    return base_drift_func(x)
def muD_tilted(x):
    return pi_tilde_1_func(x) * nordhaus_drift_func(x) + (1 - pi_tilde_1_func(x)) * weitzman_drift_func(x)

# volatilities for each process
def sigmaR(x):
    return np.zeros(x[:5].shape)
def sigmaK(x):
    return np.zeros(x[:5].shape)
def sigmaF(x):
    return np.zeros(x[:5].shape)
def sigmaD(x):
    return np.zeros(x[:5].shape)

# initial points
R_0 = 650
K_0 = 80 / α
F_0 = 870 - 580
initial_val = np.array([R_0, K_0, F_0])
D_0_base = muD_base(initial_val)
D_0_tilted = muD_tilted(initial_val)

# Set bounds
R_max_sim = np.exp(max(R))
K_max_sim = np.exp(max(K))
F_max_sim = max(F)
D_max_sim = 5.0

R_min_sim = np.exp(min(R))
K_min_sim = np.exp(min(K))
F_min_sim = min(F)
D_min_sim = -5

upperbounds = np.array([R_max_sim, K_max_sim, F_max_sim, D_max_sim, D_max_sim])
lowerbounds = np.array([R_min_sim, K_min_sim, F_min_sim, D_min_sim, D_min_sim])

hists = np.zeros([pers, nDims, its])
e_hists = np.zeros([pers,its])
j_hists = np.zeros([pers,its])
i_hists = np.zeros([pers,its])

v_dr_hists = np.zeros([pers,its])
v_df_hists = np.zeros([pers,its])
v_dk_hists = np.zeros([pers,its])
v_hists = np.zeros([pers,its])

# Simulations
for iters in range(0,its): # simulation path, currently it's only one path
    hist = np.zeros([pers,nDims])
    e_hist = np.zeros([pers,1])
    i_hist = np.zeros([pers,1])
    j_hist = np.zeros([pers,1])

    v_dr_hist = np.zeros([pers,1])
    v_df_hist = np.zeros([pers,1])
    v_dk_hist = np.zeros([pers,1])
    v_hist = np.zeros([pers,1])

    hist[0,:] = [R_0, K_0, F_0, D_0_base, D_0_tilted]
    e_hist[0] = e_func(hist[0,:]) * hist[0,0]
    i_hist[0] = i_func(hist[0,:]) * hist[0,1]
    j_hist[0] = j_func(hist[0,:]) * hist[0,0]
    v_dr_hist[0] = v_drfunc(hist[0,:])
    v_df_hist[0] = v_dffunc(hist[0,:])
    v_dk_hist[0] = v_dkfunc(hist[0,:])
    v_hist[0] = v_func(hist[0,:])

    for tm in range(1,pers): # time
        shock = norm.rvs(0,np.sqrt(dt),nDims)
        # print(muR(hist[tm-1,:]))
        hist[tm,0] = cap(hist[tm-1,0] * np.exp((muR(hist[tm-1,:])- 0.5 * sum((sigmaR(hist[tm-1,:])) ** 2))* dt + sigmaR(hist[tm-1,:]).dot(shock)),lowerbounds[0], upperbounds[0])
        hist[tm,1] = cap(hist[tm-1,1] * np.exp((muK(hist[tm-1,:])- 0.5 * sum((sigmaK(hist[tm-1,:])) ** 2))* dt + sigmaK(hist[tm-1,:]).dot(shock)),lowerbounds[1], upperbounds[1])
        hist[tm,2] = cap(hist[tm-1,2] + muF(hist[tm-1,:]) * dt + sigmaF(hist[tm-1,:]).dot(shock), lowerbounds[2], upperbounds[2])
        hist[tm,3] = cap(hist[tm-1,3] + muD_base(hist[tm-1,:]) * dt + sigmaD(hist[tm-1,:]).dot(shock), lowerbounds[3], upperbounds[3])
        hist[tm,4] = cap(hist[tm-1,4] + muD_tilted(hist[tm-1,:]) * dt + sigmaD(hist[tm-1,:]).dot(shock), lowerbounds[4], upperbounds[4])

        e_hist[tm] = e_func(hist[tm-1,:]) * hist[tm-1,0]
        i_hist[tm] = i_func(hist[tm-1,:]) * hist[tm-1,1]
        j_hist[tm] = j_func(hist[tm-1,:]) * hist[tm-1,0]

        v_dr_hist[tm] = v_drfunc(hist[tm-1,:])
        v_df_hist[tm] = v_dffunc(hist[tm-1,:])
        v_dk_hist[tm] = v_dkfunc(hist[tm-1,:])
        v_hist[tm] = v_func(hist[tm-1,:])

    hists[:,:,iters] = hist
    e_hists[:,[iters]] = e_hist
    i_hists[:,[iters]] = i_hist
    j_hists[:,[iters]] = j_hist

    v_dr_hists[:,[iters]] = v_dr_hist
    v_df_hists[:,[iters]] = v_df_hist
    v_dk_hists[:,[iters]] = v_dk_hist
    v_hists[:,[iters]] = v_hist
```

### SCC Calculation Feyman Kac

This part details the steps to calculate and decompose social cost of carbon. More details about economic meanings and derivation coule be found in Section 3.1.2, Section 4.7 and online appendix B.

The term social cost of carbon (SCC) refers to the social marginal rate of substitution between emissions and consumption.  It is a shadow price of the resource allocation problem for a hypothetical planner and given by equation $(1)$.

\begin{equation}
{scc} (x) = \left[{\frac {V_r(x )}{\exp(r) }} - V_f(x)
+  (1 - \kappa )  \left([\nabla \Gamma](\beta f) \beta  + \zeta_D(z) \cdot \begin{bmatrix} 1 \cr 0 \end{bmatrix} \right)\right]
\left[{\frac {\alpha  - i^*(x)  - j^*(x) } {\delta (1 - \kappa)}}\right]
\tag{1}
\end{equation}

We decomposes social cost cost carbon into three parts: social cost induced by non-externality, social cost induced by externality and social cost induced by uncertainty.

The first term in formula $(1)$ accounts for __social cost induced by non-externality__ which we called "private component" in the code. It has nothing to do with damages to the climate in our model and represents resource scarcity.

The __social cost induced by externality__ was captured by:

\begin{equation} \label{external_cost}
ecc(x)  = - V_f(x)   +  (1-\kappa)   \left([\nabla \Gamma](\beta f) \beta  + \zeta_D(z) \cdot \begin{bmatrix} 1 \cr 0 \end{bmatrix} \right),
\end{equation}

scaled by current period marginal utility of consumption,
or equivalently as we showed in online appendix B,

\begin{align} \label{discounted_cost}
ecc(x) =
 (1-\kappa) & {\mathbb E} \left[ \int_0^\infty \exp(-\delta \tau)
  \left[\nabla^2 \Gamma\right]( \beta F_{t+\tau} ) \beta^2 E_{t+\tau} d \tau \mid X_t = x \right] + \left[\nabla \Gamma \right](\beta F_t) \beta  + \zeta_D(Z_t) \cdot \begin{bmatrix} 1 \cr 0 \end{bmatrix}
\tag{2}
\end{align}

divided by the current period marinal utility of consumption.

An more interesting component for us to investigate would be __social cost induced by uncerntainty__. As an alternative to evaluating the discounted value using the ambiguity-adjusted probability ${\widetilde q}_2(\beta)$, suppose we use the original unadjusted probabilities to evaluate the expected discounted value of the future marginal social costs.  Call this $\overline{ecc}(x)$. We take the difference between the two:

\begin{equation}
ucc^*(x) = \left[ ecc^*(x)  - \overline{ecc}(x)  \right]
\end{equation}
\begin{eqnarray}
ecc^*(x) = (1-\kappa) \int_{\Theta} \left( [\nabla \Gamma ](\beta f) \beta + \zeta_D(z) \cdot \begin{bmatrix} 1 \cr 0 \end{bmatrix} \right) q^*(\theta | x) P(d\theta) + exp(\delta t)  \int_0^\infty \exp(- \delta t) \mathbb{E} \left[ \Psi^*(X_{\tau}) \mid X_t \right] d \tau
\tag{3}\\
\overline{ecc}(x) = (1-\kappa) \int_{\Theta} \left( [\nabla \Gamma ](\beta f) \beta + \zeta_D(z) \cdot \begin{bmatrix} 1 \cr 0 \end{bmatrix} \right) P(d\theta) + \exp(\delta t)  \int_0^\infty \exp(- \delta t) \mathbb{E} \left[ \bar{\Psi}(X_{\tau}) \mid X_t \right] d \tau \tag{4}\\
\end{eqnarray}

where$\Psi^*(x)$ and $\bar{\Psi}(x)$ are given by
\begin{align}
\Psi^*(x) & = (1-\kappa) \int_{\Theta} [ \nabla^2  \Gamma ] ( \beta f ) \beta^2  e^*(x) \exp(r) q^*(\theta|x)P(d\theta) \tag{5}\\
\bar{\Psi}(x) & = (1-\kappa) \int_{\Theta} [ \nabla^2  \Gamma ] ( \beta f ) \beta^2  e^*(x) \exp(r) P(d\theta) \tag{6}
\end{align}

$[\nabla^2 \Gamma]$ denotes the second derivative of $\Gamma$, and $e^*$ denotes the socially choice of emissions.

Applying the Feyman-Kac (F-K) formula to these expressions gives alternative PDE characterizations of $\Phi^*(X)$ and $\bar{\Phi}(X)$:

\begin{align*}
-\delta \Phi^*(x) + {\frac {\partial \Phi^*}{\partial x}} (x) \cdot \mu_X[x,a^*(x)] +{\frac 1 2} \textrm{trace} \left[\sigma_X(x)' {\frac {\partial^2 \Phi^*}{\partial x \partial x'}}(x) \sigma_X(x) \right] + \Psi^*(x) & = 0 \tag{7} \\
-\delta \bar{\Phi}(x) + {\frac {\partial \bar{\Phi}}{\partial x}} (x) \cdot \mu_X[x,a^*(x)] +{\frac 1 2} \textrm{trace} \left[\sigma_X(x)' {\frac {\partial^2 \bar{\Phi}}{\partial x \partial x'}}(x) \sigma_X(x) \right] + \bar{\Psi}(x) & = 0. \tag{8}
\end{align*}

We again solve the PDE using the conjugate gradient solver using the above form.

As was the case with the planner's value function, we need to solve the PDEs that result from the F-K formula numerically. We approximate the derivatives of $\Phi^*(x)$ and $\bar{\Phi}(x)$ with the same finite difference schemes as outlined in the Numerical Methods for the HJB Equation section. Solving for $\Phi^*(x)$ and $\bar{\Phi}(x)$ after plugging in the finite difference approximations of the derivatives to the F-K expressions results in unconditionally linear equations for $\Phi^*(x)$ and $\bar{\Phi}(x)$. We can therefore solve for $\Phi^*(x)$ and $\bar{\Phi}(x)$ directly from these linear equations by approximately inverting the linear systems using the conjugate gradient method. This is done without the use of the false transient or other iterative schemes as we do not face the same nonlinearities and instabilities that arose in the HJB equation.

```python
if ξp > 100:  # We consider for ξp level over 100 as

    # Under ambiguity neutrality, uncertainty wouldn't play a role in our decomposition.
    # Below three lines replicates equation (1)
    MC = δ * (1-κ) / (α * np.exp(K_mat) - i * np.exp(K_mat) - j * np.exp(R_mat))      # Marginal utility of consumption
    ME = δ * κ / (e * np.exp(R_mat))      # Marginal utility of emission
    SCC = 1000 * ME / MC
    SCC_func_r = GridInterp(gridpoints, SCC, method)

    def SCC_func(x):
        return SCC_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    SCC_values = np.zeros([pers,its])

    # Interpolating values from simulated trajectory
    for tm in range(pers):
        for path in range(its):
            SCC_values[tm, path] = SCC_func(hists[tm,:,path])

    SCC_total = np.mean(SCC_values,axis = 1)

    SCCs['SCC'] = SCC_total

else:

    # Solving Feyman Kac PDE for equation (8)

    # below three lines replicates euqation (6)
    def base_model_flow_func(x):
        return (γ2 * x ** 2 + γ̄2_plus * x ** 2 * ((x * F_mat - F̄) >=0)) * np.exp(R_mat) * e *  norm.pdf(x,β𝘧,np.sqrt(σᵦ))
    base_model_flow = quad_int(base_model_flow_func, a, b, n, 'legendre')
    flow_base = base_model_flow

    # input for solver

    A = -δ * np.ones(R_mat.shape)
    B_r = -e + ψ0 * (j ** ψ1) * np.exp(ψ1 * (K_mat - R_mat)) - 0.5 * (σ𝘳 ** 2)
    B_k = μk + ϕ0 * np.log(1 + i * ϕ1) - 0.5 * (σ𝘬 ** 2)
    B_f = e * np.exp(R_mat)
    C_rr = 0.5 * σ𝘳 ** 2 * np.ones(R_mat.shape)
    C_kk = 0.5 * σ𝘬 ** 2 * np.ones(R_mat.shape)
    C_ff = np.zeros(R_mat.shape)
    D = flow_base

    if smart_guess:
        out = PDESolver(stateSpace, A, B_r, B_f, B_k, C_rr, C_ff, C_kk, D, base_guess, ε, smartguess = True, solverType='Feyman Kac')

    else:
        base_guess = np.zeros(R_mat.shape)
        out = PDESolver(stateSpace, A, B_r, B_f, B_k, C_rr, C_ff, C_kk, D, base_guess, ε, smartguess = False, solverType='Feyman Kac')

    v0_base = out[2].reshape(v0.shape, order="F")
    v0_base = v0_base

    # Calculating PDE Error
    v0_dr_base = finiteDiff(v0_base,0,1,hR)
    v0_df_base = finiteDiff(v0_base,1,1,hF)
    v0_dk_base = finiteDiff(v0_base,2,1,hK)

    v0_drr_base = finiteDiff(v0_base,0,2,hR)
    v0_dff_base = finiteDiff(v0_base,1,2,hF)
    v0_dkk_base = finiteDiff(v0_base,2,2,hK)

    v0_drr_base[v0_dr_base < 1e-16] = 0
    v0_dr_base[v0_dr_base < 1e-16] = 1e-16

    PDE_rhs = A * v0_base + B_r * v0_dr_base + B_f * v0_df_base + B_k * v0_dk_base + C_rr * v0_drr_base + C_kk * v0_dkk_base + C_ff * v0_dff_base + D
    PDE_Err = np.max(abs(PDE_rhs))
    print("Feyman Kac Base Model Solved. PDE Error: {:.10f}; Iterations: {:d}; CG Error: {:.10f}".format(PDE_Err, out[0], out[1]))

    # Solving Feyman Kac PDE for equation (7)

    # Generates the ambiguity averse adjusted probability functions
    mean_nordhaus = β̃1
    lambda_tilde_nordhaus = λ̃1
    def scale_2_fnc(x):
        return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e)  * norm.pdf(x,β𝘧,np.sqrt(σᵦ))

    scale_2 = quad_int(scale_2_fnc, a, b, n, 'legendre')

    def q2_tilde_fnc(x):
        return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 * F_mat + γ2_plus * x * (x * F_mat - F̄) ** (power - 1) * ((x * F_mat - F̄) >= 0)) * np.exp(R_mat) * e) / scale_2

    ## Construction of equation (5)
    # See Remark 3 for more details
    nordhaus_model_flow = (γ2 * (1 / lambda_tilde_nordhaus + mean_nordhaus ** 2)) * np.exp(R_mat) * e
    def weitzman_model_flow_func(x):
        return q2_tilde_fnc(x) * (γ2 * x ** 2 + γ2_plus * x ** 2 * ((x * F_mat - F̄) >= 0 )) * np.exp(R_mat) * e * norm.pdf(x,β𝘧,np.sqrt(σᵦ))
    weitzman_model_flow = quad_int(weitzman_model_flow_func, a, b, n, 'legendre')

    I1 = a1 - 0.5 * np.log(λ) * ξp + 0.5 * np.log(λ̃1) * ξp + 0.5 * λ * β𝘧 ** 2 * ξp - 0.5 * λ̃1 * (β̃1) ** 2 * ξp
    I2 = -1 * ξp * np.log(scale_2)
    π̃1 = (weight) * np.exp(-1 / ξp * I1)
    π̃2 = (1 - weight) * np.exp(-1 / ξp * I2)
    π̃1_norm = π̃1 / (π̃1 + π̃2)
    π̃2_norm = 1 - π̃1_norm

    flow_tilted = π̃1_norm * nordhaus_model_flow + π̃2_norm * weitzman_model_flow

    # Formulating Feyman Kac PDE
    A = -δ * np.ones(R_mat.shape)
    B_r = -e + ψ0 * (j ** ψ1) * np.exp(ψ1 * (K_mat - R_mat)) - 0.5 * (σ𝘳 ** 2)
    B_k = μk + ϕ0 * np.log(1 + i * ϕ1) - 0.5 * (σ𝘬 ** 2)
    B_f = e * np.exp(R_mat)
    C_rr = 0.5 * σ𝘳 ** 2 * np.ones(R_mat.shape)
    C_kk = 0.5 * σ𝘬 ** 2 * np.ones(R_mat.shape)
    C_ff = np.zeros(R_mat.shape)
    D = flow_tilted

    if smart_guess:
        out = PDESolver(stateSpace, A, B_r, B_f, B_k, C_rr, C_ff, C_kk, D, worst_guess, ε, smartguess = True, solverType='Feyman Kac')

    else:
        worst_guess = np.zeros(R_mat.shape)
        out = PDESolver(stateSpace, A, B_r, B_f, B_k, C_rr, C_ff, C_kk, D, worst_guess, ε, smartguess = False, solverType='Feyman Kac')
    v0_worst = out[2].reshape(v0.shape, order="F")
    v0_worst = v0_worst

    # Calculating PDE Error for solving worst case Feyman Kac
    v0_dr_worst = finiteDiff(v0_worst,0,1,hR)
    v0_df_worst = finiteDiff(v0_worst,1,1,hF)
    v0_dk_worst = finiteDiff(v0_worst,2,1,hK)

    v0_drr_worst = finiteDiff(v0_worst,0,2,hR)
    v0_dff_worst = finiteDiff(v0_worst,1,2,hF)
    v0_dkk_worst = finiteDiff(v0_worst,2,2,hK)
    v0_drr_worst[v0_dr_worst < 1e-16] = 0
    v0_dr_worst[v0_dr_worst < 1e-16] = 1e-16

    PDE_rhs = A * v0_worst + B_r * v0_dr_worst + B_f * v0_df_worst + B_k * v0_dk_worst + C_rr * v0_drr_worst + C_kk * v0_dkk_worst + C_ff * v0_dff_worst + D
    PDE_Err = np.max(abs(PDE_rhs))
    print("Feyman Kac Worst Model Solved. PDE Error: {:.10f}; Iterations: {:d}; CG Error: {:.10f}".format(PDE_Err, out[0], out[1]))

    # SCC decomposition

    v0_dr = finiteDiff(v0,0,1,hR)
    v0_df = finiteDiff(v0,1,1,hF)
    v0_dk = finiteDiff(v0,2,1,hK)
    v0_dr[v0_dr < 1e-16] = 1e-16

    gridpoints = (R, F, K)  # can modify

    # Total SCC, equation (1)
    MC = δ * (1-κ) / (α * np.exp(K_mat) - i * np.exp(K_mat) - j * np.exp(R_mat))
    ME = δ * κ / (e * np.exp(R_mat))
    SCC = 1000 * ME / MC
    SCC_func_r = GridInterp(gridpoints, SCC, method)

    def SCC_func(x):
        return SCC_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # private SCC, first part of equation (1)
    ME1 = v0_dr * np.exp(-R_mat)
    SCC1 = 1000 * ME1 / MC
    SCC1_func_r = GridInterp(gridpoints, SCC1, method)
    def SCC1_func(x):
        return SCC1_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # Feyman Kac solution pluged in equaion (6) - second part of equation (4)
    ME2_base = (1-κ) * v0_base
    SCC2_base = 1000 * ME2_base / MC
    SCC2_base_func_r = GridInterp(gridpoints, SCC2_base, method)
    def SCC2_base_func(x):
        return SCC2_base_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # first part of equation (4)
    def V_d_baseline_func(x):
        return xi_d * (γ1 * x + γ2 * F_mat * x** 2 +
                        γ̄2_plus * x * (x * F_mat - F̄) * (power - 1)
                        * ((x * F_mat - F̄) >= 0 )) * norm.pdf(x, β𝘧, np.sqrt(σᵦ))
    V_d_baseline = quad_int(V_d_baseline_func, a, b, n, 'legendre')
    ME2b = -V_d_baseline
    SCC2_V_d_baseline = 1000 * ME2b / MC
    SCC2_V_d_baseline_func_r = GridInterp(gridpoints, SCC2_V_d_baseline, method)
    def SCC2_V_d_baseline_func(x):
        return SCC2_V_d_baseline_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # Feyman Kac solution pluged in equaion (5) - second part of equation (3)
    ME2_tilt = (1-κ) * v0_worst
    SCC2_tilt = 1000 * ME2_tilt / MC
    SCC2_tilt_func_r = GridInterp(gridpoints, SCC2_tilt, method)
    def SCC2_tilt_func(x):
        return SCC2_tilt_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # first part of equation (3)
    ME2b = -expec_e_sum * np.exp(-R_mat)
    SCC2_V_d_tilt_ = 1000 * ME2b / MC
    SCC2_V_d_tilt_func_r = GridInterp(gridpoints, SCC2_V_d_tilt_, method)
    def SCC2_V_d_tilt_func(x):
        return SCC2_V_d_tilt_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

    # Interpolating via simulated trajectories
    SCC_values = np.zeros([pers,its])
    SCC1_values = np.zeros([pers,its])
    SCC2_base_values = np.zeros([pers,its])
    SCC2_tilt_values = np.zeros([pers,its])
    SCC2_V_d_baseline_values = np.zeros([pers,its])
    SCC2_V_d_tilt_values = np.zeros([pers,its])

    for tm in range(pers): # time horizons
        for path in range(its): # number of simulation path, currently it's only one path
            SCC_values[tm, path] = SCC_func(hists[tm,:,path])
            SCC1_values[tm, path] = SCC1_func(hists[tm,:,path])
            SCC2_base_values[tm, path] = SCC2_base_func(hists[tm,:,path])
            SCC2_tilt_values[tm, path] = SCC2_tilt_func(hists[tm,:,path])
            SCC2_V_d_baseline_values[tm, path] = SCC2_V_d_baseline_func(hists[tm,:,path])
            SCC2_V_d_tilt_values[tm, path] = SCC2_V_d_tilt_func(hists[tm,:,path])

    SCC_total = np.mean(SCC_values,axis = 1)     # Total SCC,
    SCC_private = np.mean(SCC1_values,axis = 1)  # Private SCC, non externality
    SCC2_FK_base = np.mean(SCC2_base_values,axis = 1)  # ecc_bar, expectation of future horizons, second part of equation (4)
    SCC2_FK_tilt = np.mean(SCC2_tilt_values,axis = 1)  # ecc_star, expectation of future horizons, second part of equation (3)
    SCC2_V_d_baseline = np.mean(SCC2_V_d_baseline_values,axis = 1) # ecc_bar first part; instantaneous contribution, second part of equation (4)
    SCC2_V_d_tilt = np.mean(SCC2_V_d_tilt_values,axis = 1)  # ecc_star first part; instantaneous contribution, second part of equation (3)

    SCCs = {}
    SCCs['SCC'] = SCC_total # total
    SCCs['SCC1'] = SCC_private # private
    SCCs['SCC2'] = SCC2_FK_base + SCC2_V_d_baseline   # external
    SCCs['SCC3'] = SCC2_V_d_tilt - SCC2_V_d_baseline + SCC2_FK_tilt - SCC2_FK_base  # uncertainty
```

#### Remark: Convergence criteria for Feyman Kac
Below table listed the convergence criteria and final errors for solving HJBs at different preference setting which we used in the paper. As the statespace was over 3 millions, it's even slow for Conjugate Gradient to converge to a small error. Here we cap the max iteration in conjugate gradient to 400,000.

| ambiguity    |   damage specification   | iterations | CG Error |  PDE Error |
|:-:|:-:|:-:|:-:|:-:|
|averse | weighted, base  | 400,000 | 3e-6 | 3.29e-8|
|averse | weighted, worst | 400,000 | 4e-6 | 6.94e-8|

### Probabilities

Here we caculated and plotted the ${\widetilde q}_2(\beta)$  implied Ambiguity-Adjusted Probabilities for each model, please see remark 3 for more details

```python
REs = {}
Dists = {}
a = β𝘧 - 5 * np.sqrt(σᵦ)
b = β𝘧 + 5 * np.sqrt(σᵦ)
a_10std = β𝘧 - 10 * np.sqrt(σᵦ)
b_10std = β𝘧 + 10 * np.sqrt(σᵦ)

RE_func_r = GridInterp(gridpoints, RE, method)
def RE_func(x):
    return RE_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

e_func_r = GridInterp(gridpoints, e, method)
def e_func(x):
    return e_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

pi_tilde_1_func_r = GridInterp(gridpoints, π̃1, method)
def pi_tilde_1_func(x):
    return pi_tilde_1_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

lambda_tilde_1_func_r = GridInterp(gridpoints, λ̃1, method)
def lambda_tilde_1_func(x):
    return lambda_tilde_1_func_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

beta_tilde_1_r = GridInterp(gridpoints, β̃1, method)
def beta_tilde_1_func(x):
    return beta_tilde_1_r.get_value(np.log(x[0]), x[2], np.log(x[1]))

RE_plot = np.zeros(pers)
weight_plot = np.zeros(pers)
beta_f_space = np.linspace(a_10std,b_10std,200)

#Relative Entropy

if damageSpec == 'low':
    nordhaus_mean = np.zeros(pers)
    nordhaus_std = np.zeros(pers)

    for tm in range(pers):
        RE_plot[tm] = RE_func(hists[tm,:,0])
        weight_plot[tm] = pi_tilde_1_func(hists[tm,:,0])
        nordhaus_mean[tm] = beta_tilde_1_func(hists[tm,:,0])
        nordhaus_std[tm] = 1 / np.sqrt(lambda_tilde_1_func(hists[tm,:,0]))

    REs['RE'] = RE_plot
    REs['Weights'] = weight_plot
    REs['Shifted Mean'] = nordhaus_mean
    REs['Shifted Std'] = nordhaus_std

else:
    for tm in range(pers):
        RE_plot[tm] = RE_func(hists[tm,:,0])
        weight_plot[tm] = pi_tilde_1_func(hists[tm,:,0])

    REs['RE'] = RE_plot
    REs['Weights'] = weight_plot

original_dist = norm.pdf(beta_f_space, β𝘧, np.sqrt(σᵦ))
Dists['Original'] = original_dist

# probabilities (R,K,F)

for tm in [1,100,200,300,400]:
    R0 = hists[tm-1,0,0]
    K0 = hists[tm-1,1,0]
    F0 = hists[tm-1,2,0]

    # Weitzman
    def scale_2_fnc_prob(x):
        return np.exp(-1 / ξp * xi_d * (γ1 * x + γ2 * x ** 2 *  F0 + γ2_plus * x * (x * F0 - F̄) ** (power - 1) * ((x * F0 - F̄) >= 0)) * R0 * e_func([R0, K0, F0])) * norm.pdf(x, β𝘧, np.sqrt(σᵦ))
    scale_2_prob = quad_int(scale_2_fnc_prob, a, b, n, 'legendre')

    q2_tilde_fnc_prob = np.exp(-1 / ξp * xi_d * (γ1 * beta_f_space + γ2 * beta_f_space ** 2 * F0 + γ2_plus * beta_f_space * (beta_f_space * F0 - F̄) ** (power - 1) * ((beta_f_space * F0 - F̄) >= 0)) * R0* e_func([R0, K0, F0])) / scale_2_prob * norm.pdf(beta_f_space, β𝘧, np.sqrt(σᵦ))
    weitzman = q2_tilde_fnc_prob

    # Nordhaus
    mean_distort_nordhaus = beta_tilde_1_func([R0, K0, F0]) - β𝘧
    lambda_tilde_nordhaus = lambda_tilde_1_func([R0, K0, F0])
    nordhaus = norm.pdf(beta_f_space, mean_distort_nordhaus + β𝘧, 1 / np.sqrt(lambda_tilde_nordhaus))

    # weights
    Dists_weight = pi_tilde_1_func([R0, K0, F0])
    if damageSpec == 'High':
        Dists['Weitzman_year' + str(int((tm) / 4))] = weitzman
    elif damageSpec == 'Low':
        Dists['Nordhaus_year' + str(int((tm) / 4))] = nordhaus
    elif damageSpec == 'Weighted':
        Dists['Weitzman_year' + str(int((tm) / 4))] = weitzman
        Dists['Nordhaus_year' + str(int((tm) / 4))] = nordhaus
        Dists['Weighted_year' + str(int((tm) / 4))] = nordhaus * Dists_weight + weitzman * (1 - Dists_weight)

```

## Results

### Implied worst case densities

```python
# Plotting worst case densities
densityPlot(beta_f_space, Dists, damageSpec)
```

### SCC Decomposition

```python
# Plotting social cost of carbon decomposition
SCCDecomposePlot(SCCs, damageSpec)
```

### Emission trajectory

```python
# Plotting emissions
emissionPlot(damageSpec, ξp, e_hists)
```

```python

```


## 2. Feladatsor

##  Adams-módszerek

,,... and my undertaking must have ended here, if I had depended upon my own resources. But at this point Professor J.C. Adams furnished me with a perfectly satisfactory method of calculating by quadratures the exact theoretical forms of drops of fluids from the Differential Equation of Laplace,...''

(F. Bashforth, 1883)

### Adams-Bashforth módszerek (AB módszerek)

$$y_{i+1}=y_i+h\sum_{j=1}^kb_j\ f_{i-j+1}$$
Jelölések:
+ $k$ lépések száma
+ $p=k$ módszer rendje
+ lokális csonkolási hiba $C_{p+1}h^{p+1}f^{(p)}(\xi)$

$p_{}$
$k_{}$
$b_1$
$b_2$
$b_3$
$b_4$
$b_5$
$b_6$
$C_{p+1}$

$$2$$
$$2$$
$$\frac{3}{2}$$
$$-\frac{1}{2}$$

$$\frac{5}{12}$$

$$3$$
$$3$$
$$\frac{23}{12}$$
$$-\frac{16}{12}$$
$$\frac{5}{12}$$

$$\frac{3}{8}$$

$$4$$
$$4$$
$$\frac{55}{24}$$
$$-\frac{59}{24}$$
$$\frac{37}{24}$$
$$-\frac{9}{24}$$

$$\frac{251}{720}$$

$$5$$
$$5$$
$$\frac{1901}{720}$$
$$-\frac{2774}{720}$$
$$\frac{2616}{720}$$
$$-\frac{1274}{720}$$
$$\frac{251}{720}$$

$$\frac{95}{288}$$

$$6$$
$$6$$
$$\frac{4277}{1440}$$
$$-\frac{7923}{1440}$$
$$\frac{9982}{1440}$$
$$-\frac{7298}{1440}$$
$$\frac{2877}{1440}$$
$$-\frac{475}{1440}$$
$$\frac{19087}{60480}$$

1.feladat. Tekintsük az alábbi kezdetiérték-problémát:

\begin{cases}
u'(t)= u(t)+t&\\
u(0)=2, &
\end{cases}

ahol $t\in [0,1]$. Számoljuk ki a feladat közelítő megoldását AB3 módszer segítségével, ha

(a) egy RK3 módszerrel indítjuk el az AB3 módszert,

(b) az EE módszerrel indítjuk el az AB3 módszert,

(c) egy RK2 módszerrel indítjuk el az AB3 módszert.

A pontos és a numerikus eredményről készítsünk ábrát! Mit tapasztalunk az egyes esetekben konvergencia rendet illetően, ha maximum normában mérünk és

+ a pontos megoldásból számolunk,
+ finom rács technikával számolunk?

```octave
[h,t,y]=AB3(0,1,2,5);
plot(t,y,'*');
hold on
exact=3*exp(t)-t-1;
plot(t,exact);
hold off
abs(exact-y)

```

```octave
%% Maxmimum norma pontos megoldasbol
clear all
N=8;
for i=1:7
    [h,t,y]=AB3(0,1,2,N*2^(i-1));
    exact=3*exp(t)-t-1;
    norma(i)=norm(exact-y,'inf');
end
for i=1:length(norma)-1
    rate(i)=log2(norma(i)/norma(i+1));
end
rate
```

    rate =

       2.8176   2.9107   2.9557   2.9779   2.9890   2.9945

```octave
%% Max norma finom racs megoldasbol
clear all
[h,t,y]=AB3(0,1,2,2^14);
finommo=y;
N=2^7;
for i=1:3
    [h,t,y]=AB3(0,1,2,N*2^(i-1));
    exact=finommo(1:2^(8-i):2^14+1);
    norma2(i)=norm(y-exact,'inf');
end
for i=1:length(norma2)-1
    rate(i)=log2(norma2(i)/norma2(i+1));
end
rate

```

    rate =

       2.9839   2.9920

2.feladat. Tekintsük az előző félévben vizsgált matematikai inga gerjesztésmentes mozgásegyenletét az alábbi egyszerűsített alakban
\begin{cases}
u''(t)=-0.3u'(t)-sin(u(t))&\\
u(0)=\pi/2&\\
u'(0)=0&
\end{cases}

$ŧ\in [0,15]$ adott kezdeti feltételek mellett. Írjuk fel a fenti másodrendű egyenletet rendszer alakban! A kapott rendszerre alkalmazzuk az AB3 módszert RK3 módszerrel indítva. Mérjük meg rendszerre vonatkozóan a konvergencia rendjét finom rács technikával kettes normára vonatkozóan!

```octave
% ered=zeros(2,1);
% ered(1)=y(2);
% ered(2)=-0.3*y(2)-sin(y(1));
[h,t,y]=AB3sys(0,15,[pi/2; 0],1000);
plot(t,y(1,:),t,y(2,:),'r-')
legend('elso komponens','masodik')
```

```octave
%% Rendszer esetre AB3 kettes normaban

[h,t,y]=AB3sys(0,15,[pi/2; 0],2^16);
finommo=y;
size(finommo)

N=2^6;
for i=1:3
    [h,t,y]=AB3sys(0,15,[pi/2; 0],N*2^(i-1));
    exact1=finommo(1,1:2^(11-i):2^16+1);
    exact2=finommo(2,1:2^(11-i):2^16+1);
    lepeskoz(i)=h;
    normakomp1(i)=(lepeskoz(i)*sum((abs(exact1-y(1,:))).^2))^(1/2);
    normakomp2(i)=(lepeskoz(i)*sum((abs(exact2-y(2,:))).^2))^(1/2);
end

for i=1:length(normakomp2)-1
    ratekomp1(i)=log2(normakomp1(i)/normakomp1(i+1));
    ratekomp2(i)=log2(normakomp2(i)/normakomp2(i+1));

end
ratekomp1
ratekomp2
min(min(ratekomp1, ratekomp2))
a=[ratekomp1(end) ratekomp2(end)]
min(a)

```

    ans =

           2   65537

    ratekomp1 =

       3.0100   3.0156   3.0098

    ratekomp2 =

       3.0083   3.0141   3.0089

    ans =  3.0083

3.feladat. [Házi feladat] Oldjuk meg a 2.feladatot AB4 módszerrel és RK2, RK3, RK4 módszerrel indítva. Mérjük meg rendszerre vonatkozóan a konvergencia rendjét finom rács technikával egyes normára vonatkozóan! Az eredményeinkről készítsünk informatív táblázatot is!

```octave

```

### Adams-Moulton (AM) és Prediktor-Korrektor módszerek

$$y_{i+1}=y_i+h\sum_{j=0}^kb_j\ f_{i-j+1}$$
Jelölések:
+ $k$ lépések száma
+ $p=k+1$ módszer rendje
+ lokális csonkolási hiba $C_{p+1}h^{p+1}f^{(p)}(\xi)$

$p_{}$
$k_{}$
$b_0$
$b_1$
$b_2$
$b_3$
$b_4$
$b_5$
$C_{p+1}$

$$2$$
$$1$$
$$\frac{1}{2}$$
$$\frac{1}{2}$$

$$-\frac{1}{12}$$

$$3$$
$$2$$
$$\frac{5}{12}$$
$$\frac{8}{12}$$
$$-\frac{1}{12}$$

$$-\frac{1}{24}$$

$$4$$
$$3$$
$$\frac{9}{24}$$
$$\frac{19}{24}$$
$$-\frac{5}{24}$$
$$\frac{1}{24}$$

$$-\frac{19}{720}$$

$$5$$
$$4$$
$$\frac{251}{720}$$
$$\frac{646}{720}$$
$$-\frac{264}{720}$$
$$\frac{106}{720}$$
$$-\frac{19}{720}$$

$$-\frac{3}{160}$$

$$6$$
$$5$$
$$\frac{475}{1440}$$
$$\frac{1427}{1440}$$
$$-\frac{798}{1440}$$
$$\frac{482}{1440}$$
$$-\frac{173}{1440}$$
$$\frac{27}{1440}$$
$$-\frac{863}{60480}$$

4.feladat. Tekintsük az 1.feladatban szereplő kezdeti érték feladatot. Számoljuk ki a feladat közelítő megoldását AM3 prediktor-korrektor módszer segítségével, ha ehhez RK3 és AB3 módszereket használunk.

A pontos és a numerikus eredményről készítsünk ábrát! Mit tapasztalunk az egyes esetekben konvergencia rendet illetően, ha maximum normában mérünk és

+ a pontos megoldásból számolunk,
+ finom rács technikával számolunk?

```octave
format long
[h,t,y]=AM3(0,1,2,100);
%plot(t,y,'*');
%hold on
exact=3*exp(t)-t-1;
%plot(t,exact);
%hold off
norm(y-exact,'inf')
```

    ans =    3.113447499814015e-07

```octave
%% Maxmimum norma pontos megoldasbol
clear all
N=8;
for i=1:7
    [h,t,y]=AM3(0,1,2,N*2^(i-1));
    exact=3*exp(t)-t-1;
    norma(i)=norm(exact-y,'inf');
end
%norma
for i=1:length(norma)-1
    rate(i)=log2(norma(i)/norma(i+1));
end
rate
```

    rate =

     Columns 1 through 4:

       1.285455068540164   2.493321507870607   2.782777587493904   2.898122365526921

     Columns 5 and 6:

       2.950542503329654   2.975621439796173

```octave
%% Max norma EE
clear all
[h,t,y]=AM3(0,1,2,2^14);
finommo=y;
N=2^7;
for i=1:3
    [h,t,y]=AM3(0,1,2,N*2^(i-1));
    exact=finommo(1:2^(8-i):2^14+1);
    norma2(i)=norm(y-exact,'inf');
end
for i=1:length(norma2)-1
    rate(i)=log2(norma2(i)/norma2(i+1));
end
rate
```

    rate =

       2.9748   2.9876

5.feladat. Tekintsük az 2.feladatban szereplő kezdeti érték feladatot. Számoljuk ki a feladat közelítő megoldását AM3 prediktor-korrektor módszer segítségével, ha ehhez RK3 és AB3 módszereket használunk. Mérjük meg rendszerre vonatkozóan a konvergencia rendjét finom rács technikával kettes normára vonatkozóan!

```octave

```

```octave

```

### Milne-Simpson prediktor-korrektor módszer

6.feladat. [Two-Mass and Spring system] Egy kéttömegű lengőendszer vertikális kitérését Hooke törvénye alapján egy másodrendű közönséges differenciálegyenlet rendszerrel írhatunk le. Ennek alakja

\begin{align}
m_1x_1^{"}&=-s_1x_1+s_2(x_2-x_1)\\
m_2x_2^{"}&=-s_2(x_2-x_1)
\end{align}

Az elsőrendű rendszer alakja ekkor
\begin{align}
u_1^{'}&=u_2\\
u_2^{'}&=-\frac{s_1}{m_1}u_1+\frac{s_2}{m_1}(u_3-u_1)\\
u_3^{'}&=u_4\\
u_4^{'}&=-\frac{s_2}{m_2}(u_3-u_1)
\end{align}
ahol $m_1$ és $m_2$ adott tömegek, $s_1$ és $s_2$ adott rugóhoz tartozó állandók. A feladathoz tartozó kezdeti érték vektora
$$u_1(0)=\alpha_1,\quad u_2(0)=\alpha_2,\quad u_3(0)=\alpha_3,\quad u_4(0)=\alpha_4$$

Az egyszerűség kedvéért $m_1=10$kg, $m_2=2$kg, $s_1=100$, $s_2=120$, $\alpha_1=0.5$, $\alpha_2=0$, $\alpha_3=0.25$, $\alpha_4=0$. A kitérést a tömeg egyensúlyi helyzetétől mérve pozitív irányítással lefelé értjük. A kilengést és a hosszt magában foglaló ábárzoláshoz szükséges paraméterek értékei $r_1=10$ és $r_2=15$.

A feladat megoldására a Milne-Simpson prediktor-korrektor módszert alkalmazzuk.
\begin{align}
yP_{i+1}&=y_{i-3}+\frac{4}{3}h(2f_i-f_{i-1}+2f_{i-2})\\
yC_{i+1}&=y_{i-1}+\frac{1}{3}h(f_{i+1}+4f_{i}+f_{i-1})
\end{align}

Vizsgáljuk meg, hogy mekkora a kitérés mértéke az egyes tömegekre nézve az első 5 másodpercben. Ábrázoljuk ezt! Finomrács technika segítségével mérjük meg a módszer becsült rendjét kettes normában!

```octave

```

```octave

```


# "Intro til Anvendt Matematik og Python opfriskning"
> "19 April 2021 - HA-AAUBS"

- toc: true
- branch: master
- badges: true
- comments: true
- author: Roman Jurowetzki
- categories: [intro, forelæsning]

# Intro til Anvendt Matematik og Python opfriskning

- Matematik bruges i finance, økonomistyring, data science, tech og meget andet - men også helt sikkert senere hvis I skal videre med en kandidat.
- Analytiske skills er meget [eftertragtede på arbejdsmarkedet](https://youtu.be/u2oupkbxddc
)
> [Ny DI-analyse viser](https://www.danskindustri.dk/tech-der-taller/analysearkiv/analyser/2020/10/kompetencer-til-et-digitalt-arbejdsliv/), at den digitale omstilling i virksomheder ikke kan drives af it-specialisterne alene. Der er i stærkt stigende omfang behov for, at samfundsvidenskabelige profiler også har gode digitale kompetencer.

### Hvad sker her fra idag til 21 Juni?

- overblik over linkeær algebra og calculus (ikke meget mere end B niveau)
- Brug gerne fx https://www.webmatematik.dk/
- $\LaTeX$ [cheat-sheet](http://tug.ctan.org/info/undergradmath/undergradmath.pdf)
- [Markdown cheatsheet](https://www.markdownguide.org/cheat-sheet/)
- Lære at **bruge** matematik - ikke være matematiker¨
- lære fra et data/computer science perspektiv, hvor det handler mest at kunne implementere matematik direkte og bruge til fx at bygge en søgemaskine, recommender system, visualisere eller automatisere BI
- "computational tilgang" - Python som tool
- Danglish

### Pingvin Motivation og Intuition - Fra Data og Statistik til Liniær Algebra

Pingvin data: https://github.com/allisonhorst/palmerpenguins

Vi bygger en søgemaskine til pingviner 🤔

Antagelse:
- Pingviner kan bedst lide at være sammen med dem, der ligner dem mest

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

np.set_printoptions(suppress=True)

import seaborn as sns
sns.set(color_codes=True, rc={'figure.figsize':(10,8)})
```

```python
pinguins = pd.read_csv("https://github.com/allisonhorst/palmerpenguins/raw/5b5891f01b52ae26ad8cb9755ec93672f49328a8/data/penguins_size.csv")
```

```python
pinguins.head()
```

      species_short
      island
      culmen_length_mm
      culmen_depth_mm
      flipper_length_mm
      body_mass_g
      sex

      0
      Adelie
      Torgersen
      39.1
      18.7
      181.0
      3750.0
      MALE

      1
      Adelie
      Torgersen
      39.5
      17.4
      186.0
      3800.0
      FEMALE

      2
      Adelie
      Torgersen
      40.3
      18.0
      195.0
      3250.0
      FEMALE

      3
      Adelie
      Torgersen
      NaN
      NaN
      NaN
      NaN
      NaN

      4
      Adelie
      Torgersen
      36.7
      19.3
      193.0
      3450.0
      FEMALE

```python
pinguins = pinguins.dropna()
pinguins.species_short.value_counts()
```

    Adelie       146
    Gentoo       120
    Chinstrap     68
    Name: species_short, dtype: int64

```python
pinguins.index = range(len(pinguins))
```

```python
# Hvordan ser vores data ud?

sns.pairplot(pinguins, hue='species_short', kind="reg", corner=True, markers=["o", "s", "D"], plot_kws={'line_kws':{'color':'white'}})
```

Vi danner alle variable om til Z-scores (så de er på samme skala)

$Z = \frac{x-\mu}{\sigma} $

x = værdi,
$\mu$ = gennemsnit, $\sigma$ = stadnardafvigelse

```python
# scaling - vi tager kun de 4 nummeriske variable

from sklearn.preprocessing import StandardScaler
scaled_pinguins = StandardScaler().fit_transform(pinguins.loc[:,'culmen_length_mm':'body_mass_g'])
```

```python
# plot af alle skalerede variable, som nu har gennemsnit ~ 0 og std ~ 1

for i in range(4):
  sns.kdeplot(scaled_pinguins[:,i])
```

```python
print(scaled_pinguins.shape)
scaled_pinguins
```

    (334, 4)

    array([[-0.89765322,  0.78348666, -1.42952144, -0.57122888],
           [-0.82429023,  0.12189602, -1.07240838, -0.50901123],
           [-0.67756427,  0.42724555, -0.42960487, -1.19340546],
           ...,
           [ 1.17485108, -0.74326098,  1.49880565,  1.91747742],
           [ 0.22113229, -1.20128527,  0.78457953,  1.23308319],
           [ 1.08314735, -0.53969463,  0.85600214,  1.48195382]])

```python
# pinguin 1 kan representeres som en 4D række-vektor

scaled_pinguins[0,:]
```

    array([-0.89765322,  0.78348666, -1.42952144, -0.57122888])

Nu bruger vi noget, som vi måske kommer til at se på helt til sidst i Liniær Algebra, næmlig Principal Component Analysis eller PCA.
- læs mere om PCA og hvordan man [bygger det fra bunden](https://towardsdatascience.com/principal-component-analysis-pca-from-scratch-in-python-7f3e2a540c51))
- Hvis du er meget interesseret - [læs her](https://jakevdp.github.io/PythonDataScienceHandbook/05.09-principal-component-analysis.html)

Vi bruger 2 components (dvs. vores 4D vektorer bliver skrumpet til 2D hvor PCA forsøger at beholde så meget information som muligt

```python
# import PCA
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
```

```python
# Transform penguin matrix med PCA

pca_pinguins = pca.fit_transform(scaled_pinguins)
```

```python
print(pca_pinguins.shape)
pca_pinguins
```

    (334, 2)

    array([[-1.85848815,  0.03167633],
           [-1.32072197, -0.44347275],
           [-1.3816875 , -0.16108641],
           [-1.89089671, -0.01455592],
           [-1.92583912,  0.81617939],
           [-1.77762564, -0.36627297],
           [-0.82367924,  0.49925942],
           [-1.80358549, -0.24403619],
           [-1.96223043,  0.99612874],
           [-1.57564793,  0.57324551],
           [-1.75275951, -0.61168037],
           [-1.58122862,  0.0856511 ],
           [-0.81030574,  1.29279471],
           [-2.35538369, -0.64766742],
           [-1.01078942,  1.97342517],
           [-2.41302575, -0.30928293],
           [-2.11868773, -0.13743642],
           [-1.8624334 , -0.11174636],
           [-1.50972042, -0.2900524 ],
           [-1.58542467, -0.60385974],
           [-1.93370793, -0.3000802 ],
           [-1.76786874,  0.1386897 ],
           [-1.70849198, -0.18710269],
           [-2.71911792, -0.20123026],
           [-1.68714221,  0.28568225],
           [-1.88418751, -0.78150116],
           [-1.91542962, -0.40774928],
           [-1.66169617, -0.3281584 ],
           [-1.52330869,  0.3265888 ],
           [-1.45129345, -0.98913219],
           [-1.44661497,  1.05681027],
           [-1.64019046,  0.54659799],
           [-1.73809895,  0.27380468],
           [-2.41302455,  0.06626194],
           [-1.14237972,  0.35686806],
           [-2.30156091, -0.59486072],
           [-0.97680879,  0.11731425],
           [-2.31421828, -0.45107126],
           [-0.58355348,  1.05513585],
           [-2.01535054, -0.99768614],
           [-0.88503963,  0.21090366],
           [-1.93433513,  0.34353064],
           [-1.78801577, -0.65926027],
           [-1.41519347,  1.43771814],
           [-1.57862681, -0.33941082],
           [-1.1514131 ,  0.27749756],
           [-1.87115481, -0.76967488],
           [-0.79212056,  0.7107703 ],
           [-2.45321183, -0.79702106],
           [-1.26918564,  0.24411376],
           [-1.55364072, -0.48183981],
           [-1.22556619,  0.24686034],
           [-2.26331137, -1.18987656],
           [-1.5289907 ,  0.03261369],
           [-2.02082165, -1.12705855],
           [-1.14220218,  1.31192619],
           [-1.57536169, -0.83431984],
           [-0.93217697,  0.08260562],
           [-2.24970519, -0.99756479],
           [-0.91834758,  0.04746611],
           [-1.34635177, -1.40975392],
           [-1.24575322,  0.44992581],
           [-1.80562444, -1.23394502],
           [-0.59718006,  0.68528227],
           [-2.11691838, -0.47616688],
           [-1.27423813, -0.00597043],
           [-1.03080507, -0.53328743],
           [-0.4093727 ,  0.89598575],
           [-1.57723661, -0.85266145],
           [-0.59144135,  0.41165003],
           [-0.94475845, -0.53921533],
           [-1.9325889 ,  0.12031081],
           [-1.46057066, -1.35695193],
           [-0.94265366,  0.5534561 ],
           [-1.97428973, -1.12097589],
           [-0.05110489,  0.10149606],
           [-1.79698012, -0.18595097],
           [-1.53120411, -0.07993517],
           [-1.68681301, -0.56525116],
           [-1.60218691,  0.90767348],
           [-1.84898196,  0.05413884],
           [-1.86251474, -0.27237218],
           [-1.56029005,  0.16741527],
           [-1.62730125,  0.03916956],
           [-1.27054087, -0.63810036],
           [-0.20528913,  0.0705198 ],
           [-2.03180825, -1.21024999],
           [-1.01009833, -0.08788429],
           [-1.87558453, -0.89514837],
           [-0.26931022,  0.36210228],
           [-1.58473976, -0.12062016],
           [-0.68968725,  0.14542363],
           [-2.53370098, -1.76391725],
           [-0.78438121,  0.44055212],
           [-1.60064657, -0.74297136],
           [-0.39159612,  0.86745855],
           [-1.80524059, -1.27810498],
           [-1.51811021,  0.46451655],
           [-2.00793156, -0.21479991],
           [-1.86249805,  0.16103591],
           [-0.85354542, -0.62371609],
           [-1.7242771 ,  0.47562703],
           [-1.98930061, -0.82071409],
           [-0.21832718,  0.70838641],
           [-0.74267334, -0.9553643 ],
           [-0.65000735,  1.48052518],
           [-1.48716525, -0.35414245],
           [-0.74516412,  0.75300592],
           [-1.70901921,  0.91373168],
           [-0.63760493,  0.3035442 ],
           [-1.84748182, -0.78891878],
           [-1.61551852,  0.56999598],
           [-1.73956083, -1.06625566],
           [-1.63304201,  0.17484148],
           [-1.95786103, -0.94976616],
           [-1.66924381,  0.30466555],
           [-1.83256732, -0.56469998],
           [-0.67599318,  0.2242132 ],
           [-1.88627082, -1.59585662],
           [-0.88224796,  0.34884588],
           [-1.57274297, -0.48761496],
           [-0.62465259,  0.19193082],
           [-1.60836388, -0.68903797],
           [ 0.06521825,  0.33480786],
           [-1.6656762 , -0.39502924],
           [-1.13948936,  0.65783143],
           [-1.68571439, -0.32240195],
           [-0.7133827 , -0.1508346 ],
           [-1.69311177, -0.55220151],
           [-0.97484512, -0.21545089],
           [-1.88686119, -0.8908373 ],
           [-1.11507456,  0.74764799],
           [-1.66039222, -1.12171344],
           [-0.80956404, -0.17407396],
           [-1.18678397, -0.52275364],
           [-1.36972834, -0.43324764],
           [-1.9802787 , -2.09921529],
           [-1.02614623, -0.47840046],
           [-1.68170873, -1.0024529 ],
           [-1.77042737,  0.01265338],
           [-1.11705145,  0.05278004],
           [-2.06986815, -0.39058871],
           [-1.56170623, -0.69788286],
           [-1.35022667, -0.35018122],
           [-1.57792032, -0.96035616],
           [-0.62336652,  0.24669442],
           [-0.79846205,  0.50554672],
           [-0.39439897,  1.57845766],
           [-0.51992067,  1.57593487],
           [-1.2002597 ,  0.70859854],
           [-0.30943898,  1.98176877],
           [-0.33120905,  0.36625062],
           [-1.64046708,  0.5540034 ],
           [-0.08340163,  1.18233005],
           [-0.47513545,  0.91733722],
           [-0.42207312,  1.86564417],
           [-0.52340043,  0.50483586],
           [-0.5836264 ,  2.07723524],
           [-0.78644771,  0.33437546],
           [ 0.36512925,  1.24896512],
           [-0.716766  ,  0.12204011],
           [-0.06457657,  1.69011138],
           [-0.84036631,  1.7575375 ],
           [-0.1383651 ,  1.74972927],
           [-1.06570177,  0.77221009],
           [ 0.10425761,  1.01111645],
           [-1.40207485, -0.18443901],
           [-0.66045307,  0.55476098],
           [-1.4249049 , -0.44318217],
           [-0.51617466,  1.59371365],
           [-0.79506126,  0.50983804],
           [ 0.08575621,  1.62100751],
           [-0.30641584,  1.1429426 ],
           [-0.23792806,  1.31301282],
           [-0.69082522,  0.4725771 ],
           [ 0.55212389,  2.15455465],
           [-1.41090541, -0.66880259],
           [ 0.17005616,  2.60767092],
           [-1.19560645, -0.43752143],
           [ 0.25625192,  1.42716264],
           [-0.4827569 ,  1.15257349],
           [ 0.07033431,  0.21133563],
           [-0.42533337,  0.82296016],
           [ 0.72027306,  2.37482104],
           [-1.04812691, -0.05170828],
           [ 0.59645115,  2.18667147],
           [ 0.13339909,  1.4778854 ],
           [-0.8454379 ,  0.32295545],
           [-0.47766374,  1.48297061],
           [-0.53382163,  0.03253412],
           [-0.1481579 ,  1.00936156],
           [ 0.45765551,  1.31604999],
           [-0.64995848,  0.89286346],
           [ 0.43487899,  1.55212341],
           [-0.9224245 ,  1.35578984],
           [-0.03522139,  0.64592968],
           [-0.19178663,  0.06147432],
           [ 0.06384104,  1.53733287],
           [-0.63337267,  0.18431789],
           [ 0.01410864,  1.75337696],
           [-1.31760535, -0.19481417],
           [-0.33579468,  1.4960773 ],
           [-0.8544232 , -0.1878347 ],
           [-0.14295232,  1.67969894],
           [-0.05667388,  1.31013207],
           [-1.07879228,  1.01603429],
           [ 0.2097212 ,  1.79662485],
           [-0.50983242, -0.01457644],
           [-0.45577945,  0.0684179 ],
           [ 0.54836251,  2.35368336],
           [-0.74499913,  0.24941819],
           [-0.37241751,  0.99552342],
           [ 0.48732476,  1.48854221],
           [-0.21822344,  1.26585686],
           [ 1.59071497, -1.33843338],
           [ 2.88821701,  0.46645254],
           [ 1.54847963, -0.69238243],
           [ 2.61753833,  0.01710052],
           [ 2.23153155, -0.56048335],
           [ 1.55597907, -1.16855744],
           [ 1.45307282, -0.82107349],
           [ 2.02200947, -0.35367268],
           [ 1.16646236, -1.57686769],
           [ 1.81100589, -0.3083651 ],
           [ 1.28292167, -1.69506952],
           [ 2.16640073,  0.25584878],
           [ 1.66536127, -1.18716234],
           [ 2.50314801, -0.38993216],
           [ 1.03469459, -0.8339397 ],
           [ 2.51908961,  0.15586721],
           [ 0.90822766, -1.70320071],
           [ 3.0850515 , -0.01346766],
           [ 1.45752665, -0.77392447],
           [ 2.45514933, -0.19848214],
           [ 2.81716449, -0.32487746],
           [ 1.75005823, -0.87429741],
           [ 1.37362376, -0.77711474],
           [ 1.61969734, -0.21121195],
           [ 1.85181644, -1.68352649],
           [ 1.7796048 , -0.51173115],
           [ 2.31750969, -0.3125334 ],
           [ 1.56885346, -0.65252073],
           [ 2.57698609,  0.0441166 ],
           [ 2.22968194, -0.28160455],
           [ 1.16744693, -1.28013214],
           [ 1.45469922, -0.87250101],
           [ 3.78344227,  1.84264657],
           [ 2.33020098, -0.29529015],
           [ 2.13802247,  0.25779876],
           [ 1.58828206, -1.47939725],
           [ 1.45832988,  0.20612014],
           [ 1.1085949 , -1.42376297],
           [ 1.75641654,  0.03910498],
           [ 0.70655528, -1.56462829],
           [ 2.70998445,  0.29910444],
           [ 1.24452985, -1.24376351],
           [ 1.89269738, -0.19842088],
           [ 2.57894726,  0.34276262],
           [ 1.76133269, -1.29133952],
           [ 1.15208493, -1.15022256],
           [ 2.59992345,  0.32969321],
           [ 1.9634441 , -1.37308192],
           [ 1.69926047, -0.30905704],
           [ 1.62718021, -0.84602434],
           [ 2.52525925, -0.63053081],
           [ 1.15397642, -0.97324357],
           [ 2.47604047, -0.11631108],
           [ 1.90077017, -0.76888111],
           [ 1.79892558, -0.51472715],
           [ 0.99676443, -1.32921445],
           [ 1.88762448, -0.62604689],
           [ 0.92749174, -1.13858936],
           [ 2.77502282,  0.08974534],
           [ 1.07339971, -1.21348814],
           [ 2.21256953, -0.56050899],
           [ 1.4703067 , -1.10778796],
           [ 3.37470586,  0.6941716 ],
           [ 1.82881683, -0.94523108],
           [ 2.76992345,  0.64662567],
           [ 2.89419179,  0.37987269],
           [ 1.67879231, -1.19881895],
           [ 2.8197687 ,  0.00115036],
           [ 1.73473319, -0.40820863],
           [ 1.881646  , -0.28360075],
           [ 2.09970271, -0.07541008],
           [ 2.02465685, -0.5787431 ],
           [ 1.59244922, -0.55646981],
           [ 2.90122823,  0.19996284],
           [ 1.489794  , -0.77062789],
           [ 2.77293646,  0.61258452],
           [ 1.72968811, -1.17002539],
           [ 2.35146558, -0.00203546],
           [ 1.70250446, -0.46915262],
           [ 2.69662309,  0.43242808],
           [ 1.60924136, -0.60696185],
           [ 2.48295544,  0.26939799],
           [ 1.58116057, -1.20424695],
           [ 2.60059975,  0.94912049],
           [ 1.47901512, -1.13913382],
           [ 2.65532487, -0.28165015],
           [ 1.84216381, -0.82446107],
           [ 2.8178673 ,  0.9673151 ],
           [ 1.9373088 , -0.41067132],
           [ 2.62084786,  1.00390974],
           [ 1.48871194, -0.85483971],
           [ 2.6059389 ,  0.32342627],
           [ 1.51570358, -0.87417606],
           [ 2.56991388,  0.26339212],
           [ 1.83311562,  0.11963357],
           [ 2.08223426, -0.64434532],
           [ 1.29388278, -0.59018557],
           [ 2.42519833,  0.62448265],
           [ 1.99323669, -0.30933538],
           [ 3.08560832,  1.39088134],
           [ 1.70421711, -0.24027573],
           [ 2.85851941, -0.17840657],
           [ 1.90809635,  0.00784873],
           [ 1.01552351, -1.19896643],
           [ 2.68259459,  0.61669439],
           [ 1.12297944, -1.31799847],
           [ 1.97233694, -0.25531874],
           [ 2.09787229,  0.00546566],
           [ 3.08274259,  0.30581002],
           [ 1.15289065, -0.80158911],
           [ 2.87611271,  0.61318567],
           [ 1.57769415, -0.97294216],
           [ 3.47583522,  0.92237997],
           [ 1.45305952, -0.46620568],
           [ 2.68444037,  0.31891116],
           [ 1.99481532, -0.97348544],
           [ 1.82945126, -0.78166158],
           [ 2.74811252,  0.26970751],
           [ 1.71059971, -0.72411071],
           [ 2.01503039,  0.33995817]])

Nu bruger vi denne 2D matrix og plotter, hvor 1.kollonne = x; 2. kolonne = y; vi bruger farver fra pingvin-arter i vores start-data

```python
sns.scatterplot(x = pca_pinguins[:,0], y = pca_pinguins[:,1], hue = pinguins['species_short'] )
```

Hvordan finder vi så en buddy for en given pingvin?

- det er den, der er tættest på 🤖

**Eucledian Distance**

**Vi kan også gå fra 2D til n-D**

$d(\vec{u}, \vec{v}) = \| \vec{u} - \vec{v} \| = \sqrt{(u_1 - v_1)^2 + (u_2 - v_2)^2 ... (u_n - v_n)^2}$

fx

Vi kan regne ED mellem
$\vec{u} = (2, 3, 4, 2)$

og

$\vec{v} = (1, -2, 1, 3)$

$\begin{align} d(\vec{u}, \vec{v}) = \| \vec{u} - \vec{v} \| = \sqrt{(2-1)^2 + (3+2)^2 + (4-1)^2 + (2-3)^2} \\ d(\vec{u}, \vec{v}) = \| \vec{u} - \vec{v} \| = \sqrt{1 + 25 + 9 + 1} \\ d(\vec{u}, \vec{v}) = \| \vec{u} - \vec{v} \| = \sqrt{36} \\ d(\vec{u}, \vec{v}) = \| \vec{u} - \vec{v} \| = 6 \end{align}$

```python
# hvor tæt er de første 2
print(scaled_pinguins[0,:])
print(scaled_pinguins[1,:])
```

    [-0.89765322  0.78348666 -1.42952144 -0.57122888]
    [-0.82429023  0.12189602 -1.07240838 -0.50901123]

```python
# kvardarod er ikke standard og skal importeres
from math import sqrt
```

```python
# manuelt
sqrt((-0.89765322--0.82429023)**2 + (0.78348666-0.12189602)**2 + (-1.42952144--1.07240838)**2 + (-0.57122888--0.50901123)**2)
```

    0.7579479380745329

```python
# med numpy
np.linalg.norm(scaled_pinguins[0,:] - scaled_pinguins[1,:])
```

    0.757947942517268

```python
np.linalg.norm(scaled_pinguins[0,:] - scaled_pinguins[2,:])
```

    1.249913482211539

```python
pinguins.iloc[:5,:]
```

      species_short
      island
      culmen_length_mm
      culmen_depth_mm
      flipper_length_mm
      body_mass_g
      sex

      0
      Adelie
      Torgersen
      39.1
      18.7
      181.0
      3750.0
      MALE

      1
      Adelie
      Torgersen
      39.5
      17.4
      186.0
      3800.0
      FEMALE

      2
      Adelie
      Torgersen
      40.3
      18.0
      195.0
      3250.0
      FEMALE

      3
      Adelie
      Torgersen
      36.7
      19.3
      193.0
      3450.0
      FEMALE

      4
      Adelie
      Torgersen
      39.3
      20.6
      190.0
      3650.0
      MALE

```python
pinguins.iloc[-5:,:]
```

      species_short
      island
      culmen_length_mm
      culmen_depth_mm
      flipper_length_mm
      body_mass_g
      sex

      329
      Gentoo
      Biscoe
      47.2
      13.7
      214.0
      4925.0
      FEMALE

      330
      Gentoo
      Biscoe
      46.8
      14.3
      215.0
      4850.0
      FEMALE

      331
      Gentoo
      Biscoe
      50.4
      15.7
      222.0
      5750.0
      MALE

      332
      Gentoo
      Biscoe
      45.2
      14.8
      212.0
      5200.0
      FEMALE

      333
      Gentoo
      Biscoe
      49.9
      16.1
      213.0
      5400.0
      MALE

```python
np.linalg.norm(scaled_pinguins[0,:] - scaled_pinguins[333,:])
```

    3.887615834331366

```python
np.linalg.norm(scaled_pinguins[0,:] - scaled_pinguins[331,:])
```

    4.6254719817752035

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

```python
# This code draws the x and y axis as lines.

points = [0,1,2,333,331]

fig, ax = plt.subplots()

ax.scatter(pca_pinguins[[points],0], pca_pinguins[[points],1])

plt.axhline(0, c='black', lw=0.5)
plt.axvline(0, c='black', lw=0.5)
plt.xlim(-2,3)
plt.ylim(-1,1)

plt.quiver(0, 0, pca_pinguins[0,0], pca_pinguins[0,1], angles='xy', scale_units='xy', scale=1, color='blue')
plt.quiver(0, 0, pca_pinguins[1,0], pca_pinguins[1,1], angles='xy', scale_units='xy', scale=1, color='green')
plt.quiver(0, 0, pca_pinguins[2,0], pca_pinguins[2,1], angles='xy', scale_units='xy', scale=1, color='yellow')
plt.quiver(0, 0, pca_pinguins[333,0], pca_pinguins[333,1], angles='xy', scale_units='xy', scale=1, color='violet')
plt.quiver(0, 0, pca_pinguins[331,0], pca_pinguins[331,1], angles='xy', scale_units='xy', scale=1, color='black')

for i in points:
    ax.annotate(str(i), (pca_pinguins[i,0], pca_pinguins[i,1]))
```

Man kunne nu enten skrive noget, som gentager denne beregning for alle kombinationer...eller

```python
from sklearn.metrics.pairwise import euclidean_distances
```

```python
euclidean_matrix = euclidean_distances(scaled_pinguins)
```

```python
print(euclidean_matrix.shape)
euclidean_matrix
```

    (334, 334)

    array([[0.        , 0.75794794, 1.24991348, ..., 4.62547198, 3.65359902,
            3.88761583],
           [0.75794794, 0.        , 0.99817767, ..., 4.15259574, 3.05401605,
            3.42910036],
           [1.24991348, 0.99817767, 0.        , ..., 4.26589822, 3.28965941,
            3.58404958],
           ...,
           [4.62547198, 4.15259574, 4.26589822, ..., 0.        , 1.44840609,
            0.80791459],
           [3.65359902, 3.05401605, 3.28965941, ..., 1.44840609, 0.        ,
            1.11705413],
           [3.88761583, 3.42910036, 3.58404958, ..., 0.80791459, 1.11705413,
            0.        ]])

```python
np.argmin(euclidean_matrix[0,:])
```

    0

```python
np.argsort(euclidean_matrix[0,:])[:3]
```

    array([  0, 139,  16])

```python
scaled_pinguins[[0,139,16],:]
```

    array([[-0.89765322,  0.78348666, -1.42952144, -0.57122888],
           [-0.91599396,  0.78348666, -1.14383099, -0.6956642 ],
           [-1.15442366,  0.78348666, -1.50094405, -0.75788186]])

```python
euclidean_distances(scaled_pinguins[[0,139,16],:])
```

    array([[0.        , 0.31215311, 0.32537914],
           [0.31215311, 0.        , 0.43387728],
           [0.32537914, 0.43387728, 0.        ]])

### Python fresh-up

- Simple datatyper
- Grundlæggende matematiske operationer
- Lister
- Funktioner
- Control Flow

#### Simple datatyper
- Integers - hele tal **6**
- Floating-Point Numbers - decimaltal **3.2**
- Boolean - digital data type / bit **True / False**
- String - text **Roman*

```python
i = 6
print(i, type(i))
```

    6 <class 'int'>

```python
x = 3.2
print(x, type(x))
```

    3.2 <class 'float'>

```python
t = i == 6
print(t, type(t))
```

    True <class 'bool'>

```python
s = 'Hello'
print(s, type(s))
```

    Hello <class 'str'>

#### Grundlæggende matematiske operationer

```python
a = 2.0
b = 3.0
print(a+b, a*b, a-b, a/b, a**2, a+b**2, (a+b)**2)
```

    5.0 6.0 -1.0 0.6666666666666666 4.0 11.0 25.0

```python
c = a + b
print(c)
```

    5.0

```python
a + b == c
```

    True

```python
a + b < c
```

    False

#### Lister

man kan pakke alt i en liste :-)

```python
l = ['Eskil', 1.0, sqrt]
type(l)
```

    list

```python
l[2]
```

    <function math.sqrt>

```python
l[0]
```

    'Eskil'

```python
l.append('Roman')
```

```python
l
```

    ['Eskil', 1.0, <function math.sqrt>, 'Roman']

```python
l.extend(['Marie',37])
```

```python
l
```

    ['Eskil', 1.0, <function math.sqrt>, 'Roman', 'Marie', 37]

```python
l.pop(2)
```

    <function math.sqrt>

```python
l
```

    ['Eskil', 1.0, 'Roman', 'Marie', 37]

#### Funktioner

Funktioner har (normalt) in og outputs. $a$ og $b$ er vores input her og funktionen producerer $\sqrt{a^2 + b^2}$ som output.

Vi prøver lige ...

$\begin{align} a^2 + b^2 = c^2 \rightarrow c = \sqrt{a^2 + b^2} \end{align}$

```python
def pythagoras(a, b):
    return sqrt(a**2 + b**2)
```

```python
pythagoras(1,2)
```

    2.23606797749979

```python
# Hvis man gør det rigtigt, så er det en god ide at kommentere hvad der sker.
# Her er det en no-brainer men funktioner kan blive indviklede og
# det er good-practice at skrive "docstrings" til en anden eller en selv (i)

def pythagoras(a, b):
    """
    Computes the length of the hypotenuse of a right triangle

    Arguments
    a, b: the two lengths of the right triangle
    """

    return sqrt(a**2 + b**2)
```

##### Mini-assignment
* Lav en funktion, som tager to punkter $(x_1, y_1), (x_2, y_2)$ på en linje og beregner hældning $a$

$$ y = ax + b$$

$$ a = \frac{y_2- y_1}{x_2 - x_1}$$

```python
plt.plot((1,2), (2,3), 'ro-')
plt.plot((1,2), (2,2), 'bo-')
plt.plot((2,2), (2,3), 'bo-')
```

```python

```

```python
# slope(1,2,2,3)
```

#### Control flow

```python
def isNegative(n):
    if n < 0:
        return True
    else:
        return False
```

##### Mini-assignment
* Lav en funktion `KtoC` som regner Kelvin om til Celcius
$$ C = K - 273.15 \quad \text{ved} \quad C\geq - 273.15$$
Funktionen udgiver `None` hvis $C < -273.15$

```python
list(range(10))
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

```python
# for-loop

even = [] # tom liste
for i in range(10):
    even.append(i*2)

print(even)
```

    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

```python
# list-comprehension

even = [2*i for i in range(10)]

print(even)
```

    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

##### Mini-assignment
1. Beregn summen af integers  1 ... 100 ved at bruge `sum`, list-comprehension, for-loop
2. Beregn summen af integers  1 ... 100 ved at bruge partial-sum formula
$$ \sum_{k=1}^n k = 1 + 2 + \cdots + (n-1) + n = \frac{n(n+1)}{2}$$

### Matematik fresh-up
alle øvelser taget fra https://tutorial.math.lamar.edu/Problems/Alg/Preliminaries.aspx

Erfaringen viser, at det er en god idé at få sig en god routine med at løse matematiske problemer.

- Integer Exponents
- Rational Exponents
- Radicals
- Polynomials

Vi arbejder old-school med papir men bruger også `SymPy` for at tjekke vores løsninger

#### Integer Exponents

$- {6^2} + 4 \cdot {3^2}$

${\left( {2{w^4}{v^{ - 5}}} \right)^{ - 2}}$ (løsning med kun positive eksponenter!)

```python
from sympy import *
```

```python
simplify(-6**2+4*3**2)
```

$\displaystyle 0$

```python
w, v = symbols('w v')
simplify((2*w**4*v**-5)**-2)
```

$\displaystyle \frac{v^{10}}{4 w^{8}}$

#### Rational Exponents
${\left( { - 125} \right)^{\frac{1}{3}}}$

${\left( {{a^3}\,{b^{ - \,\,\frac{1}{4}}}} \right)^{\frac{2}{3}}}$

```python
simplify(-125**(1/3), rational=True)
```

$\displaystyle -5.0$

```python
a, b = symbols('a b')
simplify((a**3*b**(-1/4))**(2/3), rational=True)
```

$\displaystyle \left(\frac{a^{3}}{\sqrt[4]{b}}\right)^{\frac{2}{3}}$

#### Radicals
$$\begin{array}{c} \sqrt[7]{y}\\ \sqrt[3]{{{x^2}}} \\ \sqrt[3]{{ - 512}} \\ \sqrt x \left( {4 - 3\sqrt x } \right)\end{array}$$

```python
x, y, z = symbols('x, y , z')
```

```python
simplify((x**2)**(1/3), rational=True)
```

$\displaystyle \sqrt[3]{x^{2}}$

```python
simplify(-512**(1/3), rational=True)
```

$\displaystyle -8.0$

```python
simplify(sqrt(x)*(4 - 3*sqrt(x)), rational = True)
```

$\displaystyle 4 \sqrt{x} - 3 x$

#### Polynomials

$$(4{x^3} - 2{x^2} + 1) + (7{x^2} + 12x)$$

```python
simplify((4*x**3-2*x**2+1)+(7*x**2+12*x))
```

$\displaystyle 4 x^{3} + 5 x^{2} + 12 x + 1$