# Examples and overview

You now have all the basic tools to solve interesting economic models. The trick is to be able to combine what you know to solve problems in practice. We firstly briefly recap, with a focus solving optimization problems and non-linear equations. Afterwards, we consider a number of examples.

1. The consumer problem
2. A worker-capitalist production economy
3. The inaugurual project from 2020 (labor supply and taxation)

```python
# magic to reload modules automatically
%load_ext autoreload
%autoreload 2

# standard imports
from types import SimpleNamespace # new? explained below
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
```

# Recap

2. **Primitives:** types, operators, copy vs. view, conditionals, loops, functions, classes
3. **Optimize, print and plot:** mathematics (numpy), printing, figures (matplotlib), solving optimization problems and equations (scipy.optimize)
4. **Random numbers and simulation:** random numbers (numpy.random), save/load (pickle), interactive figures (ipywidgets)
5. **Workflow and debugging:** structuring, naming, commenting, debugging (assert, try-except), modules

**Sum up:** Lots and lots of information. The important thing is not to remember it all, but to know where to look for answers.

## Optimize, optimize, optimize

**The two most important tools:**

1. Solving optimization problems with `scipy.optimize.minimize` and `scipy.optimize.minimize_scalar`
2. Solving equations with `scipy.optimize.root` and `scipy.optimize.root_scalar`

**Problem:** A bit of a black box...

* **Lecture 10:** Details on solving equations.
* **Lecture 11:** Details on numerical optimization.
* **Now:** Compare with a) a *loop search* and b) a *hand-written optimizer*.

### Loops vs. optimizer

**Consider a simple maximization problem:**

$$
\max_x f(x) = \max_x -3(x-2)^2+1
$$

**Solution:**

$$
f^\prime(x) = 0 \Leftrightarrow -6(x-2) = 0 \Leftrightarrow x = 2.0
$$

```python
def f_func(x):
    return -3*(x-2)**2 + 1
```

**Rough solution with loop:**

```python
N = 100
x_vec = np.linspace(-10,10,N)
f_vec = np.empty(N)

f_best = -np.inf # initial maximum
x_best = np.nan # not-a-number

for i,x in enumerate(x_vec):
    f_now = f_vec[i] = f_func(x)
    if f_now > f_best:
        x_best = x
        f_best = f_now

print(f'best with loop is {f_best:.8f} at x = {x_best:.8f}')
```

    best with loop is 0.98041016 at x = 1.91919192

**Question:** Not quite right, how to improve?

**Plot:**

```python
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

ax.plot(x_vec,f_vec,ls='--',lw=2,color='black',label='$f(x)$')
ax.plot(x_best,f_best,ls='',marker='s',label='best')

ax.set_xlabel('x')
ax.set_ylabel('f')
ax.legend(loc='lower center',frameon=True);
```

**Solution with** `scipy.optimize.minimize_scalar` ([documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html#scipy.optimize.minimize_scalar)):

```python
obj = lambda x: -f_func(x) # here the input is a scalar
res = optimize.minimize_scalar(obj,bracket=(-10,10),method='brent')
x = res.x
f = -res.fun

print(f'best is {f:.8f} at x = {x:.8f}')
```

    best is 1.00000000 at x = 2.00000000

**Solution with** `scipy.optimize.minimize` ([documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize)):

```python
x_guess = [0]
obj = lambda x: -f_func(x[0]) # now the input is a vector
res = optimize.minimize(obj,x_guess,method='Nelder-Mead')
x = res.x[0]
f = -res.fun

print(f'best is {f:.8f} at x = {x:.8f}')
```

    best is 1.00000000 at x = 2.00000000

**Solution with** `scipy.optimize.root_scalar` ([documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root_scalar.html)):

Find derivative and solve via FOC:

```python
def fp_func(x):
    return -6*(x-2)
```

```python
obj = lambda x: fp_func(x)
res = optimize.root_scalar(obj,bracket=(-10,10),method='bisect')
x = res.root
f = f_func(res.root)

print(f'best is {f:.8f} at x = {x:.8f}')
```

    best is 1.00000000 at x = 2.00000000

**Solution with** `scipy.optimize.root` ([documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root.html)):

```python
x_guess = [0]
obj = lambda x: fp_func(x[0])
res = optimize.root(obj,x_guess,method='hybr')
x = res.x[0]
f = f_func(x)

print(f'best is {f:.8f} at x = {x:.8f}')
```

    best is 1.00000000 at x = 2.00000000

### Gradient descent optimizer

**Algorithm:** `minimize_gradient_descent()`

1. Choose tolerance $\epsilon>0$, step size $\alpha > 0$, and guess on $x_0$, set $n=0$.
2. Compute  $f(x_n)$ and $f^\prime(x_n) \approx \frac{f(x_n+\Delta)-f(x_n)}{\Delta}$.
3. If $|f^\prime(x_n)| < \epsilon$ then stop.
4. Compute new guess "down the hill":

  $$
  x_{n+1} = x_{n} - \alpha f^\prime(x_n)
  $$

5. Set $n = n + 1$ and return to step 2.

**Code for algorithm:**

```python
def gradient_descent(f,x0,alpha=0.5,Delta=1e-8,max_iter=500,eps=1e-8):
    """ minimize function with gradient descent

    Args:

        f (callable): function
        x0 (float): initial value
        alpha (float,optional): step size factor in search
        Delta (float,optional): step size in numerical derivative
        max_iter (int,optional): maximum number of iterations
        eps (float,optional): tolerance

    Returns:

        x (float): minimum
        fx (float): funciton value at minimum
        trials (list): og dicts with keys x, value and derivative

    """

    # step 1: initialize
    x = x0
    n = 0
    trials = []

    # step 2-4:
    while n < max_iter:

        # step 2: compute function value and derivative
        fx = f(x)
        fp = (f(x+Delta)-fx)/Delta

        trials.append({'x':x,'fx':fx,'fp':fp})

        # step 3: check convergence
        print(f'n = {n:3d}: x = {x:12.8f}, f = {fx:12.8f}, fp = {fp:12.8f}')
        if np.abs(fp) < eps:
            break

        # step 4: update x
        x -= alpha*fp

        # step 5: update n
        n += 1

    return x,fx,trials
```

**New example:**

$$
\max_x f(x) = \max_x -\sin(x)+0.05x^2
$$

**Call the optimizer:**

```python
x0 = 0.0
f = lambda x: -np.sin(x)+0.05*x**2

x,fx,trials = gradient_descent(f,x0)

print(f'best with gradient_descent is {fx:.8f} at x = {x:.8f}')
```

    n =   0: x =   0.00000000, f =   0.00000000, fp =  -1.00000000
    n =   1: x =   0.50000000, f =  -0.46692554, fp =  -0.82758256
    n =   2: x =   0.91379128, f =  -0.75007422, fp =  -0.51936899
    n =   3: x =   1.17347578, f =  -0.85324884, fp =  -0.26960142
    n =   4: x =   1.30827649, f =  -0.88015974, fp =  -0.12868722
    n =   5: x =   1.37262010, f =  -0.88622298, fp =  -0.05961956
    n =   6: x =   1.40242988, f =  -0.88751934, fp =  -0.02732913
    n =   7: x =   1.41609444, f =  -0.88779134, fp =  -0.01247611
    n =   8: x =   1.42233250, f =  -0.88784799, fp =  -0.00568577
    n =   9: x =   1.42517538, f =  -0.88785975, fp =  -0.00258928
    n =  10: x =   1.42647003, f =  -0.88786219, fp =  -0.00117876
    n =  11: x =   1.42705940, f =  -0.88786269, fp =  -0.00053654
    n =  12: x =   1.42732767, f =  -0.88786280, fp =  -0.00024420
    n =  13: x =   1.42744978, f =  -0.88786282, fp =  -0.00011116
    n =  14: x =   1.42750535, f =  -0.88786283, fp =  -0.00005059
    n =  15: x =   1.42753065, f =  -0.88786283, fp =  -0.00002303
    n =  16: x =   1.42754216, f =  -0.88786283, fp =  -0.00001048
    n =  17: x =   1.42754740, f =  -0.88786283, fp =  -0.00000477
    n =  18: x =   1.42754979, f =  -0.88786283, fp =  -0.00000216
    n =  19: x =   1.42755087, f =  -0.88786283, fp =  -0.00000098
    n =  20: x =   1.42755136, f =  -0.88786283, fp =  -0.00000046
    n =  21: x =   1.42755159, f =  -0.88786283, fp =  -0.00000020
    n =  22: x =   1.42755169, f =  -0.88786283, fp =  -0.00000009
    n =  23: x =   1.42755173, f =  -0.88786283, fp =  -0.00000004
    n =  24: x =   1.42755175, f =  -0.88786283, fp =  -0.00000002
    n =  25: x =   1.42755177, f =  -0.88786283, fp =  -0.00000001
    n =  26: x =   1.42755177, f =  -0.88786283, fp =   0.00000000
    best with gradient_descent is -0.88786283 at x = 1.42755177

**Illusstration:**

```python
fig = plt.figure(figsize=(10,10))

# a. main figure
ax = fig.add_subplot(2,2,(1,2))

trial_x_vec = [trial['x'] for trial in trials]
trial_f_vec = [trial['fx'] for trial in trials]
trial_fp_vec = [trial['fp'] for trial in trials]

ax.plot(x_vec,f(x_vec),ls='--',lw=2,color='black',label='$f(x)$')
ax.plot(trial_x_vec,trial_f_vec,ls='',marker='s',ms=4,color='blue',label='iterations')

ax.set_xlabel('$x$')
ax.set_ylabel('$f$')
ax.legend(loc='upper center',frameon=True)

# sub figure 1
ax = fig.add_subplot(2,2,3)
ax.plot(np.arange(len(trials)),trial_x_vec)
ax.set_xlabel('iteration')
ax.set_ylabel('x')

# sub figure 2
ax = fig.add_subplot(2,2,4)
ax.plot(np.arange(len(trials)),trial_fp_vec)
ax.set_xlabel('iteration')
ax.set_ylabel('derivative of f');
```

**Question:** Can we guess on any initial value of $x_0$?

**What to take a look inside?** Turn on the [debugger](https://jupyterlab.readthedocs.io/en/stable/user/debugger.html). Add a *breakpoint* in the beginning of `gradient_descent`. Solve the problem again, and *step in* by pressing `F11`.

# The consumer problem

$$
\begin{aligned}
V(p_{1},p_{2},m) & = \max_{x_{1},x_{2}} \left(\alpha^{\frac{1}{\sigma}}x_{1}^{\frac{\sigma-1}{\sigma}}+(1-\alpha)^{\frac{1}{\sigma}}x_{2}^{\frac{\sigma-1}{\sigma}}\right)^{\frac{\sigma}{\sigma-1}}\\
 \text{s.t.}\\
p_{1}x_{1}+p_{2}x_{2} & \leq m,\,\,\,p_{1},p_{2},m>0\\
x_{1},x_{2} & \geq 0
\end{aligned}
$$

**Goal:** Create a model-class to solve this problem.

1. Let `model` be a class
1. Let `model.par` contain all parameters (e.g. `model.par.alpha`)
1. Let `model.sol` contain the solution (e.g. `model.sol.x1`)

**SimpleNamespace():** Like a dictionary, but e.g. `par.alpha` instead of `par['alpha']`.

```python
par = SimpleNamespace()
par.alpha = 0.5
par.sigma = 0.1

print(f'alpha = {par.alpha:6.3f}')
print(f'sigma = {par.sigma:6.3f}')
```

    alpha =  0.500
    sigma =  0.100

Can always be interfaced as a dictionary with `__dict__`:

```python
for k,v in par.__dict__.items():
    print(f'{k:5s} = {v:6.3f}')
```

    alpha =  0.500
    sigma =  0.100

**Utility function:**

```python
def u_func(model,x1,x2):

    par = model.par

    u_x1 = par.alpha**(1/par.sigma)*x1**((par.sigma-1)/par.sigma)
    u_x2 = (1-par.alpha)**(1/par.sigma)*x2**((par.sigma-1)/par.sigma)

    return (u_x1+u_x2)**(par.sigma/(par.sigma-1))
```

**Solution function:**

```python
def solve(model):

    par = model.par
    sol = model.sol

    # a. objective function (to minimize)
    obj = lambda x: -model.u_func(x[0],x[1]) # minimize -> negtive of utility

    # b. constraints and bounds
    budget_constraint = lambda x: par.m-par.p1*x[0]-par.p2*x[1] # violated if negative
    constraints = ({'type':'ineq','fun':budget_constraint})
    bounds = ((1e-8,par.m/par.p1-1e-8),(1e-8,par.m/par.p2-1e-8))

    # why all these 1e-8? To avoid ever having x1 = 0 or x2 = 0

    # c. call solver
    x0 = [(par.m/par.p1)/2,(par.m/par.p2)/2]
    result = optimize.minimize(obj,x0,method='SLSQP',bounds=bounds,constraints=constraints)

    # d. save
    sol.x1 = result.x[0]
    sol.x2 = result.x[1]
    sol.u = model.u_func(sol.x1,sol.x2)
```

**Create consumer class:**

```python
class ConsumerClass:

    def __init__(self):

        # this is called automatically when a consumer is created

        # a. parameters
        par = self.par = SimpleNamespace()
        par.alpha = 0.5
        par.sigma = 0.1
        par.mu = 0.5
        par.p1 = 1
        par.p2 = 2
        par.m = 10

        # b. solution
        sol = self.sol = SimpleNamespace()
        sol.x1 = np.nan
        sol.x2 = np.nan
        sol.u = np.nan

    u_func = u_func
    solve = solve

```

**Solve consumer problem**:

```python
jeppe = ConsumerClass() # calls __init__()
jeppe.solve()
print(f'(x1,x2) = ({jeppe.sol.x1:.3f},{jeppe.sol.x2:.3f}), u = {jeppe.sol.u:.3f}')
```

    (x1,x2) = (3.489,3.256), u = 6.705

Easy to loop over:

```python
for alpha in np.linspace(0.3,0.7,5):
    jeppe.par.alpha = alpha
    jeppe.solve()
    print(f'alpha = {alpha:.3f} -> (x1,x2) = ({jeppe.sol.x1:.3f},{jeppe.sol.x2:.3f}), u = {jeppe.sol.u:.3f}')
```

    alpha = 0.300 -> (x1,x2) = (1.868,4.066), u = 5.906
    alpha = 0.400 -> (x1,x2) = (2.632,3.684), u = 6.282
    alpha = 0.500 -> (x1,x2) = (3.489,3.256), u = 6.705
    alpha = 0.600 -> (x1,x2) = (4.456,2.772), u = 7.186
    alpha = 0.700 -> (x1,x2) = (5.556,2.222), u = 7.737

**Question:** Anything you want to test?

# A worker-capitalist production economy

Consider an economy consisting of $N_w$ **workers**, and $N_c$ **capitalists** and a single **firm** owned equally by the capitalists.

**Workers:** Consume, $c_w$, at a price $p$, and supply labor, $\ell_w$, at a wage of $w$. Maximize utility:

$$\max_{c_w\geq0,\ell_w\in[0,1]} \log (c_w+\kappa)- \omega \ell_w^{\eta} \text{ s.t } pc_w \leq w \ell_w,\,\,\,\omega,\kappa > 0, \eta \geq 1$$

Equivalently, substituting in the budget constraint with equality:

$$\max_{\ell_w\in[0,1]} \log \left( \frac{w \ell_w}{p}+\kappa \right)- \omega \ell_w^{\eta}$$

Denote ***optimal behavior*** $c_w^{\star}(p,w)$ and $\ell_w^{\star}(p,w)$.

**Capitalists:** Consume, $c_c$, at a price $p$, supply labor, $\ell_c$, at a wage $w$, and receives profits $\pi$. Maximize utility:

$$\max_{c_c\geq0,\ell_c\in[0,1]} \log (c_c+\kappa) - \omega \ell_c^{\eta} \text{ s.t } pc_c = w \ell_c + \pi, ,\,\,\,\omega,\kappa > 0, \eta \geq 1$$

Equivalently, substituting in the budget constraint with equality:

$$\max_{\ell_c\in[0,1]} \log \left( \frac{w \ell_c + \pi}{p}+\kappa \right)- \omega \ell_c^{\eta}$$

Denote ***optimal behavior*** $c_c^{\star}(p,w,\pi)$ and $\ell_c^{\star}(p,w,\pi)$.

**Firm:** Use the production function $f(\ell) = \ell^\alpha, \alpha \in (0,1)$. Maximize profits:

$$\max_{\ell\geq0} p f(\ell) - w\ell $$

Denote ***optional behavior*** by $\ell^{\star}(p,w)$.

Implied ***production*** is $y^{\star}(p,w) = f(\ell^{\star}(p,w))$ and implied ***total profits*** are $\Pi^\star(p,w) = py^{\star}(p,w) - w\ell^{\star}(p,w)$

**Equilibrium:** A set of prices $(p,w)$ such that workers, capitalists and firms act optimally given prices and profit, and

1. **Goods market clears**: $N_w c_w^{\star}(p,w) + N_c c_c^{\star}(p,w,\pi) = y^\star(p,w)$
2. **Labor market clears**: $N_w \ell_w^{\star}(p,w) + N_c \ell_c^{\star}(p,w,\pi) = \ell^\star(p,w)$
3. **Profits received equal profits distributed**: $\pi = \frac{py^{\star}(p,w) - w\ell^{\star}(p,w)}{N_c}$

**Note I:** We can use $p=1$ as numeraire.

**Note II:** *Walras' Law* imply that if one of the markets clear, then the other one does too.

## Parameters

Choose parameters:

```python
par = SimpleNamespace()
par.kappa = 0.1
par.omega = 10
par.eta = 1.50
par.alpha = 0.50
par.Nw = 99
par.Nc = 1
```

## Workers

```python
def utility_w(c,l,par):
    """ utility of workers """

    return np.log(c+par.kappa)-par.omega*l**par.eta

def workers(p,w,par):
    """ maximize utility for workers """

    # a. solve
    obj = lambda l: -utility_w((w*l)/p,l,par)
    res = optimize.minimize_scalar(obj,bounds=(0,1),method='bounded')

    # b. save
    l_w_star = res.x
    c_w_star = (w*l_w_star)/p

    return c_w_star,l_w_star
```

**Small test:**

```python
p = 1
for w in [0.5,1,1.5]:
    c,l = workers(p,w,par)
    print(f'w = {w:.2f} -> c = {c:.2f}, l = {l:.2f}')
```

    w = 0.50 -> c = 0.03, l = 0.06
    w = 1.00 -> c = 0.11, l = 0.11
    w = 1.50 -> c = 0.18, l = 0.12

## Capitalists

```python
def utility_c(c,l,par):
    """ utility of capitalists """

    return np.log(c+par.kappa)-par.omega*l**par.eta

def capitalists(p,w,pi,par):
    """ maximize utility of capitalists """

    # a. solve
    obj = lambda l: -utility_c((w*l+pi)/p,l,par) # subsittute in the budget constraint
    res = optimize.minimize_scalar(obj,bounds=(0,1),method='bounded')

    # b. save
    l_c_star = res.x
    c_c_star = (w*l_c_star+pi)/p

    return c_c_star,l_c_star
```

**Small test:**

```python
p = 1
pi = 0.1
for w in [0.5,1,1.5]:
    c,l = capitalists(p,w,pi,par)
    print(f'w = {w:.2f} -> c = {c:.2f}, l = {l:.2f}')
```

    w = 0.50 -> c = 0.11, l = 0.02
    w = 1.00 -> c = 0.16, l = 0.06
    w = 1.50 -> c = 0.23, l = 0.09

**Question:** Any idea for another test?

## Firm

```python
def firm(p,w,par):
    """ maximize firm profits """

    # a. solve
    f = lambda l: l**par.alpha
    obj = lambda l: -(p*f(l)-w*l)
    x0 = [0.0]
    res = optimize.minimize(obj,x0,bounds=((0,None),),method='L-BFGS-B')

    # b. save
    l_star = res.x[0]
    y_star = f(l_star)
    Pi = p*y_star - w*l_star

    return y_star,l_star,Pi
```

**Small test:**

```python
p = 1
for w in [0.5,1,1.5]:
    y,l,Pi = firm(p,w,par)
    print(f'w = {w:.2f} -> y = {y:.2f}, l = {l:.2f}, Pi = {Pi:.2f}')
```

    w = 0.50 -> y = 1.00, l = 1.00, Pi = 0.50
    w = 1.00 -> y = 0.50, l = 0.25, Pi = 0.25
    w = 1.50 -> y = 0.33, l = 0.11, Pi = 0.17

## Equilibrium

```python
def evaluate_equilibrium(w,par,p=None,do_print=False):
    """ evaluate equilirium """

    # a. normalize output price
    p = 1 if p is None else p

    # b. optimal behavior of firm
    y_star,l_star,Pi = firm(p,w,par)
    pi = Pi/par.Nc

    # c. optimal behavior of households
    c_w_star,l_w_star = workers(p,w,par)
    c_c_star,l_c_star = capitalists(p,w,pi,par)

    # d. market clearing
    goods_mkt_clearing = par.Nw*c_w_star + par.Nc*c_c_star - y_star
    labor_mkt_clearing = par.Nw*l_w_star + par.Nc*l_c_star - l_star

    if do_print:

        u_w = utility_w(c_w_star,l_w_star,par)
        print(f'workers      : c = {c_w_star:6.4f}, l = {l_w_star:6.4f}, u = {u_w:7.4f}')
        u_c = utility_c(c_c_star,l_c_star,par)
        print(f'capitalists  : c = {c_c_star:6.4f}, l = {l_c_star:6.4f}, u = {u_c:7.4f}')
        print(f'goods market : {goods_mkt_clearing:.8f}')
        print(f'labor market : {labor_mkt_clearing:.8f}')

    else:

        return goods_mkt_clearing

```

**Step 1:** Perform rough grid search to check when the goods market clears.

```python
num_w = 10
grid_w = np.linspace(0.1,1.5,num_w)
grid_mkt_clearing = np.zeros(num_w)

for i,w in enumerate(grid_w):
    grid_mkt_clearing[i] = evaluate_equilibrium(w,par)
    print(f'w = {w:.2f} -> excess demand = {grid_mkt_clearing[i]:12.8f}')
```

    w = 0.10 -> excess demand =  -2.45597063
    w = 0.26 -> excess demand =  -0.33115179
    w = 0.41 -> excess demand =   1.47824268
    w = 0.57 -> excess demand =   3.60037473
    w = 0.72 -> excess demand =   5.89988125
    w = 0.88 -> excess demand =   8.29317023
    w = 1.03 -> excess demand =  10.74049294
    w = 1.19 -> excess demand =  13.22157416
    w = 1.34 -> excess demand =  15.72439188
    w = 1.50 -> excess demand =  18.24150339

**Step 2:** Find where *excess demand* changes sign - the equilibrium price must be within this range

```python
left = np.max(grid_w[grid_mkt_clearing < 0])
right = np.min(grid_w[grid_mkt_clearing > 0])
print(f'equilibrium price must be in [{left:.2f},{right:.2f}]')
```

    equilibrium price must be in [0.26,0.41]

**Step 3:** Use equation-solver / root-finder

```python
res = optimize.root_scalar(evaluate_equilibrium,bracket=[left,right],method='bisect',args=(par,))
w = res.root
print(f'the equilibrium wage is {w:.4f}')
```

    the equilibrium wage is 0.2864

**Show details:**

```python
evaluate_equilibrium(w,par,do_print=True)
```

    workers      : c = 0.0088, l = 0.0308, u = -2.2721
    capitalists  : c = 0.8731, l = 0.0004, u = -0.0274
    goods market : 0.00000004
    labor market : 0.00000013

**Check I:** Does both markets clear?

**Check II:** Can we multiply both prices with the same factor? I.e. can we change the numeraire?

```python
fac = 100
p_ = fac*1.0
w_ = fac*w
evaluate_equilibrium(w_,par,p=p_,do_print=True)
```

    workers      : c = 0.0088, l = 0.0308, u = -2.2721
    capitalists  : c = 0.8731, l = 0.0004, u = -0.0274
    goods market : -0.00000240
    labor market : -0.00000840

## Experiments

It is easy to extend this model in many directions:

1. Should workers and capitalists have different tastes or producitvity?
1. Should there be government redistribution?
2. Other ideas?

## Using a class

```python
from WorkerCapitalistEconomy import WorkerCapitalistEconomyClass
```

**Look at `WorkerCapitalistEconomy.py`:** Same code, but written as a class!

```python
model = WorkerCapitalistEconomyClass()
print(model.par.kappa) # excess the class data with .
```

    0.1

```python
model.find_equilibrium()
```

    grid search:
     w = 0.10 ->  -2.45597063
     w = 0.26 ->  -0.33115179
     w = 0.41 ->   1.47824268
     w = 0.57 ->   3.60037473
     w = 0.72 ->   5.89988125
     w = 0.88 ->   8.29317023
     w = 1.03 ->  10.74049294
     w = 1.19 ->  13.22157416
     w = 1.34 ->  15.72439188
     w = 1.50 ->  18.24150339

    equilibrium price must be in [0.26,0.41]

    the equilibrium wage is 0.2864

    workers      : c = 0.0088, l = 0.0308, u = -2.2721
    capitalists  : c = 0.8731, l = 0.0004, u = -0.0274
    goods market : 0.00000004
    labor market : 0.00000013

**Benefit I:** Fewer inputs and outputs, less risk of wrong ordering.

**Benefit II of class-based solution:** Easy access to all data.
E.g. capitalists share of total consumption.

```python
C_w = model.par.Nw*model.sol.c_w_star
C_c = model.par.Nc*model.sol.c_c_star
print(f'capitalists share of total consumption is: {C_c/(C_c+C_w):.2f}')
```

    capitalists share of total consumption is: 0.50

**Benefit III of class-based solution:** Easy to experiment with different parameters.

```python
model.par.kappa = model.par.kappa/100 # lower kappa
model.find_equilibrium()
```

    grid search:
     w = 0.10 ->  -0.93720172
     w = 0.26 ->   3.11578788
     w = 0.41 ->   6.01856761
     w = 0.57 ->   8.72067391
     w = 0.72 ->  11.35650742
     w = 0.88 ->  13.96702518
     w = 1.03 ->  16.56693675
     w = 1.19 ->  19.16044548
     w = 1.34 ->  21.74865061
     w = 1.50 ->  24.33227156

    equilibrium price must be in [0.10,0.26]

    the equilibrium wage is 0.1260

    workers      : c = 0.0200, l = 0.1592, u = -4.4959
    capitalists  : c = 1.9848, l = 0.0000, u =  0.6860
    goods market : 0.00000037
    labor market : 0.00000291

```python
for k,v in model.sol.__dict__.items():
    print(f'{k:20s} = {v:6.2f}')
```

    p                    =   1.00
    w                    =   0.13
    l_star               =  15.76
    y_star               =   3.97
    Pi                   =   1.98
    pi                   =   1.98
    l_w_star             =   0.16
    c_w_star             =   0.02
    l_c_star             =   0.00
    c_c_star             =   1.98
    goods_mkt_clearing   =   0.00
    labor_mkt_clearing   =   0.00

# Inaugural project from 2020 (labor supply and taxation)

Consider a consumer solving the following maximization problem

$$\begin{eqnarray}
c^{\star},\ell^{\star} & = & \arg\max_{c,\ell}\log(c)-\nu\frac{\ell^{1+\frac{1}{\varepsilon}}}{1+\frac{1}{\varepsilon}}\\
 & \text{s.t.} \\
x & = & m+w\ell-\left[\tau_{0}w\ell+\tau_{1}\max\{w\ell-\kappa,0\}\right] \\
c & \in & [0,x] \\
\ell & \in & [0,1]
\end{eqnarray}$$

where $c$ is consumption, $\ell$ is labor supply, $m$ is cash-on-hand,
$w$ is the wage rate, $\tau_{0}$ is the standard labor income tax,
$\tau_{1}$ is the top bracket labor income tax, $\kappa$ is the
cut-off for the top labor income bracket, $x$ is total resources,
$\nu$ scales the disutility of labor, and $\varepsilon$ is the Frisch
elasticity of labor supply.

Note that utility is monotonically increasing in consumption. This implies that
$$\begin{equation}
c^{\star}=x
\end{equation}$$

**Question 1:** Construct a function which solves the consumer given the parameters.

We choose the following parameter values

$$
m=1,\,\nu=10,\,\varepsilon=0.3,\,\tau_{0}=0.4,\,\tau_{1}=0.1,\,\kappa=0.4
$$

**Question 2:** Plot $\ell^{\star}$ and $c^{\star}$ as functions of $w$ in
the range $0.5$ to $1.5$.

Consider a population with $N=1,000$ individuals indexed by $i$.

Assume the distribution of wages is uniform such that

$$w_{i}\sim\mathcal{U}(0.5,1.5).$$

Denote the optimal choices of individual $i$ by $\ell_{i}^{\star}$ and $c_{i}^{\star}$.

**Question 3:** Calculate the total tax revenue given by $T=\sum_{i=1}^{N}\left[\tau_{0}w_{i}\ell_{i}^{\star}+\tau_{1}\max\{w_{i}\ell_{i}^{\star}-\kappa,0\}\right].$

**Question 4:** What would the tax revenue be if instead $\varepsilon=0.1$?

Consider a politician who wishes to maximize the tax revenue.

**Question 5:** Which $\tau_{0}$, $\tau_{1}$ and $\kappa$ would you suggest her to implement? Report the tax revenue you expect to obtain.

## Solution of question 1+2

All the basic functions are written in `LaborSupplyModel.py`.

```python
import LaborSupplyModel as LSM
```

Define all **parameters**:

```python
m = 1
nu = 10
frisch = 0.3
tau0 = 0.4
tau1 = 0.1
kappa = 0.4
```

**Allocate** arrays for solutions:

```python
N = 1_000
w_vec = np.linspace(0.5,1.5,N)
l_vec = np.zeros(N)
c_vec = np.zeros(N)
```

**Solve:**

```python
for i in range(N):
    l_vec[i] = LSM.find_optimal_labor_supply(nu,frisch,m,w_vec[i],tau0,tau1,kappa)
    c_vec[i] = LSM.implied_c(l_vec[i],m,w_vec[i],tau0,tau1,kappa)
```

**Plot results:**

```python
fig = plt.figure(figsize=(12,4))

ax = fig.add_subplot(1,2,1)
ax.plot(w_vec,l_vec,'-')
ax.set_ylabel('labor supply, $\ell$')
ax.set_xlabel('wage, $w$')
ax.set_title('Labor suppply')

ax = fig.add_subplot(1,2,2)
ax.plot(w_vec,c_vec,'-')
ax.set_ylabel('consumption, $c$')
ax.set_xlabel('wage, $w$')
ax.set_title('Consumption');
```

## Solution of question 3

Calculate **tax revnue** using that a equally spaced vector approximates a uniform distribution:

```python
T = np.sum(LSM.implied_tax(l_vec,w_vec,tau0,tau1,kappa))
print(f'total tax revenue is: {T:.4f}')
```

    total tax revenue is: 163.0262

Using **random sampling** is also a possibility:

```python
# a. set seed
np.random.seed(1917)

# b. run replications
reps = 50
T_vec = np.zeros(reps)
for rep in range(reps):

    # i. draw randow wages
    w_vec_ = np.random.uniform(0.5,1.5,size=N)

    # ii. find labor supply
    l_vec_ = np.zeros(N)
    for i in range(N):
        l_vec_[i] = LSM.find_optimal_labor_supply(nu,frisch,m,w_vec_[i],tau0,tau1,kappa)

    # iii. find tax revenue
    T_vec[rep] = np.sum(LSM.implied_tax(l_vec_,w_vec_,tau0,tau1,kappa))

    if rep < 10 or rep%10 == 0:
        print(f'{rep:2d}: {T_vec[rep]:.4f}')

# c. mean
print(f'mean: {np.mean(T_vec):.4f} [{np.min(T_vec):.4f} {np.max(T_vec):.4f}]')
```

     0: 159.7256
     1: 164.1675
     2: 166.3045
     3: 164.1948
     4: 162.4999
     5: 162.3964
     6: 164.1743
     7: 162.2987
     8: 163.5471
     9: 159.5859
    10: 165.9245
    20: 165.1277
    30: 162.2696
    40: 160.8294
    mean: 162.8388 [159.4760 166.3045]

## Question 4

**Re-solve** with $\epsilon = 0.1$:

```python
frisch_low = 0.1
l_vec_frisch_low = np.zeros(N)
for i in range(N):
    l_vec_frisch_low[i] = LSM.find_optimal_labor_supply(nu,frisch_low,m,w_vec[i],tau0,tau1,kappa)
```

Re-calculate **tax revenue**:

```python
T_frisch_low = np.sum(LSM.implied_tax(l_vec_frisch_low,w_vec,tau0,tau1,kappa))
print(f'total tax revenue is: {T_frisch_low:.4f}')
```

    total tax revenue is: 319.6932

**Conclusion:** Higher tax revenue because of lower Frish elasticity.

## Question 5

Define function to calculate **tax revenue for guess of tax parameters**:

```python
def tax_revenue(nu,frisch,m,w_vec,tau0,tau1,kappa):
    """ find total tax revenue and labor and consumpty

    Args:

        nu (float): disutility of labor supply
        frisch (float): frisch elasticity of labor supply
        m (float): cash-on-hand
        w_vec (np.array): wage
        tau0 (float): standard labor tax
        tau1 (float): top bracket labor income tax
        kappa (float): cut-off for the top labor income bracket

    Returns:

        (float): total tax revenue

    """

    # a. optimal labor supply
    N = w_vec.size
    l_vec = np.zeros(N)
    for i in range(N):
        l_vec[i] = LSM.find_optimal_labor_supply(nu,frisch,m,w_vec[i],tau0,tau1,kappa)

    # b. taxes
    T = np.sum(LSM.implied_tax(l_vec,w_vec,tau0,tau1,kappa))

    return T
```

Define **objective function for optimizer**:

```python
def obj(x,nu,frisch_low,m,w_vec):
    """ find negative of total tax revenue

    Args:

        x (np.array): tax parameters
        nu (float): disutility of labor supply
        frisch (float): frisch elasticity of labor supply
        m (float): cash-on-hand
        w_vec (np.array): wage

    Returns:

        (float): minus total tax revenue

    """

    global it

    # a. determine parameters
    tau0 = x[0]
    if x.size > 1:
        tau1 = x[1]
        kappa = x[2]
    else:
        tau1 = 0.0
        kappa = 0.0

    # b. calculate tax revnue
    T = tax_revenue(nu,frisch_low,m,w_vec,tau0,tau1,kappa)

    # c. print
    print(f'{it:3d}: tau0 = {tau0:10.8f}, tau1 = {tau1:10.8f}, kappa = {kappa:10.8f} -> T = {T:12.8f},')

    it += 1

    return -T
```

**Solve:**

```python
# a. initial guess and bounds
x0 = np.array([tau0,tau1,kappa])
bounds = ((0,0.99),(0,0.99),(0,1.5))

# b. call solver
it = 0
result = optimize.minimize(obj,x0,
    method='SLSQP',bounds=bounds,
    args=(nu,frisch,m,w_vec)
)
```

      0: tau0 = 0.40000000, tau1 = 0.10000000, kappa = 0.40000000 -> T = 163.02616378,
      1: tau0 = 0.40000001, tau1 = 0.10000000, kappa = 0.40000000 -> T = 163.02616851,
      2: tau0 = 0.40000000, tau1 = 0.10000001, kappa = 0.40000000 -> T = 163.02616360,
      3: tau0 = 0.40000000, tau1 = 0.10000000, kappa = 0.40000001 -> T = 163.02616297,
      4: tau0 = 0.99000000, tau1 = 0.00000000, kappa = 0.00000000 -> T = 126.64019548,
      5: tau0 = 0.65141630, tau1 = 0.05738707, kappa = 0.22954827 -> T = 229.72387032,
      6: tau0 = 0.65141632, tau1 = 0.05738707, kappa = 0.22954827 -> T = 229.72387232,
      7: tau0 = 0.65141630, tau1 = 0.05738708, kappa = 0.22954827 -> T = 229.72386916,
      8: tau0 = 0.65141630, tau1 = 0.05738707, kappa = 0.22954828 -> T = 229.72386990,
      9: tau0 = 0.83055594, tau1 = 0.00000000, kappa = 0.00000000 -> T = 244.37775906,
     10: tau0 = 0.83055595, tau1 = 0.00000000, kappa = 0.00000000 -> T = 244.37775743,
     11: tau0 = 0.83055594, tau1 = 0.00000002, kappa = 0.00000000 -> T = 244.37775743,
     12: tau0 = 0.83055594, tau1 = 0.00000000, kappa = 0.00000002 -> T = 244.37775906,
     13: tau0 = 0.74158445, tau1 = 0.00000000, kappa = 0.00000000 -> T = 244.91817967,
     14: tau0 = 0.78344698, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.68817873,
     15: tau0 = 0.78344700, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.68817879,
     16: tau0 = 0.78344698, tau1 = 0.00000002, kappa = 0.00000000 -> T = 246.68817879,
     17: tau0 = 0.78344698, tau1 = 0.00000000, kappa = 0.00000002 -> T = 246.68817873,
     18: tau0 = 0.78292530, tau1 = 0.00218034, kappa = 0.00231170 -> T = 246.68671540,
     19: tau0 = 0.78323341, tau1 = 0.00089262, kappa = 0.00094640 -> T = 246.68955160,
     20: tau0 = 0.78323342, tau1 = 0.00089262, kappa = 0.00094640 -> T = 246.68955164,
     21: tau0 = 0.78323341, tau1 = 0.00089263, kappa = 0.00094640 -> T = 246.68955162,
     22: tau0 = 0.78323341, tau1 = 0.00089262, kappa = 0.00094641 -> T = 246.68955158,
     23: tau0 = 0.78944641, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.67582763,
     24: tau0 = 0.78489861, tau1 = 0.00065338, kappa = 0.00069275 -> T = 246.69171799,
     25: tau0 = 0.78489863, tau1 = 0.00065338, kappa = 0.00069275 -> T = 246.69171798,
     26: tau0 = 0.78489861, tau1 = 0.00065339, kappa = 0.00069275 -> T = 246.69171797,
     27: tau0 = 0.78489861, tau1 = 0.00065338, kappa = 0.00069276 -> T = 246.69171798,
     28: tau0 = 0.78529735, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69219011,
     29: tau0 = 0.78529737, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69219012,
     30: tau0 = 0.78529735, tau1 = 0.00000001, kappa = 0.00000000 -> T = 246.69219012,
     31: tau0 = 0.78529735, tau1 = 0.00000000, kappa = 0.00000001 -> T = 246.69219011,
     32: tau0 = 0.78563630, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69217763,
     33: tau0 = 0.78544626, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221629,
     34: tau0 = 0.78544627, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221629,
     35: tau0 = 0.78544626, tau1 = 0.00000001, kappa = 0.00000000 -> T = 246.69221629,
     36: tau0 = 0.78544626, tau1 = 0.00000000, kappa = 0.00000001 -> T = 246.69221629,

**Have we found the global optimum?**

**Same result with another initial guess?**

```python
# a. initial guess and bounds
x0 = np.array([0.1,0.1,0.1])
bounds = ((0,0.99),(0,0.99),(0,1.5))

# b. call solver
it = 0
result = optimize.minimize(obj,x0,
    method='SLSQP',bounds=bounds,
    args=(nu,frisch,m,w_vec)
)
```

      0: tau0 = 0.10000000, tau1 = 0.10000000, kappa = 0.10000000 -> T =  76.28076412,
      1: tau0 = 0.10000001, tau1 = 0.10000000, kappa = 0.10000000 -> T =  76.28077023,
      2: tau0 = 0.10000000, tau1 = 0.10000001, kappa = 0.10000000 -> T =  76.28076871,
      3: tau0 = 0.10000000, tau1 = 0.10000000, kappa = 0.10000001 -> T =  76.28076261,
      4: tau0 = 0.99000000, tau1 = 0.99000000, kappa = 0.00000000 -> T =   0.01035071,
      5: tau0 = 0.49818245, tau1 = 0.49818245, kappa = 0.05526040 -> T =  67.40157676,
      6: tau0 = 0.29318084, tau1 = 0.29318084, kappa = 0.07829429 -> T = 194.35829361,
      7: tau0 = 0.29318085, tau1 = 0.29318084, kappa = 0.07829429 -> T = 194.35829721,
      8: tau0 = 0.29318084, tau1 = 0.29318085, kappa = 0.07829429 -> T = 194.35829598,
      9: tau0 = 0.29318084, tau1 = 0.29318084, kappa = 0.07829430 -> T = 194.35828902,
     10: tau0 = 0.98999997, tau1 = 0.02089256, kappa = 0.00000004 -> T =   0.00506983,
     11: tau0 = 0.44431374, tau1 = 0.23412431, kappa = 0.06131307 -> T = 222.07538676,
     12: tau0 = 0.44431375, tau1 = 0.23412431, kappa = 0.06131307 -> T = 222.07538912,
     13: tau0 = 0.44431374, tau1 = 0.23412433, kappa = 0.06131307 -> T = 222.07538815,
     14: tau0 = 0.44431374, tau1 = 0.23412431, kappa = 0.06131309 -> T = 222.07538306,
     15: tau0 = 0.80863245, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.11525914,
     16: tau0 = 0.80863247, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.11525838,
     17: tau0 = 0.80863245, tau1 = 0.00000001, kappa = 0.00000000 -> T = 246.11525838,
     18: tau0 = 0.80863245, tau1 = 0.00000000, kappa = 0.00000001 -> T = 246.11525914,
     19: tau0 = 0.74188618, tau1 = 0.00000000, kappa = 0.00000000 -> T = 244.94144160,
     20: tau0 = 0.78380481, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.68949142,
     21: tau0 = 0.78380483, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.68949147,
     22: tau0 = 0.78380481, tau1 = 0.00000001, kappa = 0.00000000 -> T = 246.68949147,
     23: tau0 = 0.78380481, tau1 = 0.00000000, kappa = 0.00000001 -> T = 246.68949142,
     24: tau0 = 0.78530195, tau1 = 0.00000000, kappa = 0.00001074 -> T = 246.69219127,
     25: tau0 = 0.78530197, tau1 = 0.00000000, kappa = 0.00001074 -> T = 246.69219128,
     26: tau0 = 0.78530195, tau1 = 0.00000001, kappa = 0.00001074 -> T = 246.69219128,
     27: tau0 = 0.78530195, tau1 = 0.00000000, kappa = 0.00001076 -> T = 246.69219127,
     28: tau0 = 0.78542437, tau1 = 0.00000000, kappa = 0.00001058 -> T = 246.69221412,
     29: tau0 = 0.78542438, tau1 = 0.00000000, kappa = 0.00001058 -> T = 246.69221412,
     30: tau0 = 0.78542437, tau1 = 0.00000001, kappa = 0.00001058 -> T = 246.69221412,
     31: tau0 = 0.78542437, tau1 = 0.00000000, kappa = 0.00001060 -> T = 246.69221412,

**Can we improve if we force $\tau_1 = \kappa = 0$?**

```python
# a. initial guess and bounds
x0 = np.array([result.x[0]])
bounds = ((0,0.99),)

# b. call solver
it = 0
result = optimize.minimize(obj,x0,
    method='SLSQP',bounds=bounds,
    args=(nu,frisch,m,w_vec)
)
```

      0: tau0 = 0.78542437, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221412,
      1: tau0 = 0.78542438, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221412,
      2: tau0 = 0.80214800, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.39765662,
      3: tau0 = 0.78709673, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.68943799,
      4: tau0 = 0.78559161, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69219185,
      5: tau0 = 0.78544109, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221634,
      6: tau0 = 0.78544111, tau1 = 0.00000000, kappa = 0.00000000 -> T = 246.69221634,

**Can we improve if fix $\kappa$ to some value?**

```python
def obj_kappa(x,nu,frisch_low,m,w_vec,kappa):
    """ find negative of total tax revenue

    Args:

        x (np.array): tax parameters
        nu (float): disutility of labor supply
        frisch (float): frisch elasticity of labor supply
        m (float): cash-on-hand
        w_vec (np.array): wage
        kappa (float): cut-off for the top labor income bracket

    Returns:

        (float): minus total tax revenue

    """

    global it

    # a. determine parameters
    tau0 = x[0]
    tau1 = x[1]

    # b. calculate tax revnue
    T = tax_revenue(nu,frisch_low,m,w_vec,tau0,tau1,kappa)

    # c. print
    print(f' {it:3d}: tau0 = {tau0:10.8f}, tau1 = {tau1:10.8f} -> T = {T:12.8f},')

    it += 1

    return -T
```

```python
# a. initial guess and bounds
x0 = np.array([0.1,0.1])
bounds = ((0,0.99),(0,0.99))

# b. call solver
for kappa in [0.05,0.10,0.15]:

    print(f'kappa = {kappa:.3f}')
    it = 0
    result = optimize.minimize(obj_kappa,x0,
        method='SLSQP',bounds=bounds,
        args=(nu,frisch,m,w_vec,kappa)
    )
    print('')

```

    kappa = 0.050
       0: tau0 = 0.10000000, tau1 = 0.10000000 -> T =  81.36739300,
       1: tau0 = 0.10000001, tau1 = 0.10000000 -> T =  81.36739911,
       2: tau0 = 0.10000000, tau1 = 0.10000001 -> T =  81.36739835,
       3: tau0 = 0.99000000, tau1 = 0.99000000 -> T =  49.49767585,
       4: tau0 = 0.52520516, tau1 = 0.52520516 -> T =  26.26236071,
       5: tau0 = 0.28194740, tau1 = 0.28194740 -> T = 198.06712916,
       6: tau0 = 0.28194741, tau1 = 0.28194740 -> T = 198.06713300,
       7: tau0 = 0.28194740, tau1 = 0.28194741 -> T = 198.06713222,
       8: tau0 = 0.99000000, tau1 = 0.06545131 -> T =  49.49844200,
       9: tau0 = 0.45250159, tau1 = 0.22979829 -> T = 225.71732910,
      10: tau0 = 0.45250160, tau1 = 0.22979829 -> T = 225.71733140,
      11: tau0 = 0.45250159, tau1 = 0.22979830 -> T = 225.71733061,
      12: tau0 = 0.85947869, tau1 = 0.00000000 -> T = 239.89872701,
      13: tau0 = 0.85947871, tau1 = 0.00000000 -> T = 239.89872396,
      14: tau0 = 0.85947869, tau1 = 0.00000001 -> T = 239.89872316,
      15: tau0 = 0.75860649, tau1 = 0.00000000 -> T = 246.00412507,
      16: tau0 = 0.75860651, tau1 = 0.00000000 -> T = 246.00412582,
      17: tau0 = 0.75860649, tau1 = 0.00000001 -> T = 246.00412502,
      18: tau0 = 0.77834099, tau1 = 0.00000000 -> T = 246.64182853,
      19: tau0 = 0.77834101, tau1 = 0.00000000 -> T = 246.64182874,
      20: tau0 = 0.77834099, tau1 = 0.00000001 -> T = 246.64182795,
      21: tau0 = 0.78606953, tau1 = 0.00000000 -> T = 246.69181708,
      22: tau0 = 0.78606955, tau1 = 0.00000000 -> T = 246.69181707,
      23: tau0 = 0.78606953, tau1 = 0.00000001 -> T = 246.69181627,
      24: tau0 = 0.78548170, tau1 = 0.00000000 -> T = 246.69221392,
      25: tau0 = 0.78548171, tau1 = 0.00000000 -> T = 246.69221392,
      26: tau0 = 0.78548170, tau1 = 0.00000001 -> T = 246.69221313,
      27: tau0 = 0.78542392, tau1 = 0.00000000 -> T = 246.69221411,
      28: tau0 = 0.78545186, tau1 = 0.00000000 -> T = 246.69221609,
      29: tau0 = 0.78545187, tau1 = 0.00000000 -> T = 246.69221609,
      30: tau0 = 0.78545186, tau1 = 0.00000001 -> T = 246.69221529,
      31: tau0 = 0.78542858, tau1 = 0.00000000 -> T = 246.69221416,
      32: tau0 = 0.78544774, tau1 = 0.00000000 -> T = 246.69221625,

    kappa = 0.100
       0: tau0 = 0.10000000, tau1 = 0.10000000 -> T =  76.28076412,
       1: tau0 = 0.10000001, tau1 = 0.10000000 -> T =  76.28077023,
       2: tau0 = 0.10000000, tau1 = 0.10000001 -> T =  76.28076871,
       3: tau0 = 0.98999999, tau1 = 0.98999999 -> T =  90.59922224,
       4: tau0 = 0.55520801, tau1 = 0.55520802 -> T =  55.52239483,
       5: tau0 = 0.31400131, tau1 = 0.31400131 -> T = 194.79427716,
       6: tau0 = 0.31400132, tau1 = 0.31400131 -> T = 194.79428024,
       7: tau0 = 0.31400131, tau1 = 0.31400132 -> T = 194.79427867,
       8: tau0 = 0.99000000, tau1 = 0.00000000 -> T = 126.64019550,
       9: tau0 = 0.52121141, tau1 = 0.21775224 -> T = 221.53210126,
      10: tau0 = 0.52121143, tau1 = 0.21775224 -> T = 221.53210246,
      11: tau0 = 0.52121141, tau1 = 0.21775225 -> T = 221.53210087,
      12: tau0 = 0.81041550, tau1 = 0.00000000 -> T = 246.02007389,
      13: tau0 = 0.81041552, tau1 = 0.00000000 -> T = 246.02007306,
      14: tau0 = 0.81041550, tau1 = 0.00000001 -> T = 246.02007147,
      15: tau0 = 0.78172715, tau1 = 0.00000000 -> T = 246.67830312,
      16: tau0 = 0.78172717, tau1 = 0.00000000 -> T = 246.67830323,
      17: tau0 = 0.78172715, tau1 = 0.00000001 -> T = 246.67830164,
      18: tau0 = 0.78510558, tau1 = 0.00000000 -> T = 246.69209528,
      19: tau0 = 0.78510559, tau1 = 0.00000000 -> T = 246.69209529,
      20: tau0 = 0.78510558, tau1 = 0.00000001 -> T = 246.69209370,
      21: tau0 = 0.78543659, tau1 = 0.00000000 -> T = 246.69221396,
      22: tau0 = 0.78543661, tau1 = 0.00000000 -> T = 246.69221396,
      23: tau0 = 0.78543659, tau1 = 0.00000001 -> T = 246.69221237,

    kappa = 0.150
       0: tau0 = 0.10000000, tau1 = 0.10000000 -> T =  71.19449111,
       1: tau0 = 0.10000001, tau1 = 0.10000000 -> T =  71.19449721,
       2: tau0 = 0.10000000, tau1 = 0.10000001 -> T =  71.19449494,
       3: tau0 = 0.99000000, tau1 = 0.99000000 -> T = 115.66422795,
       4: tau0 = 0.58111293, tau1 = 0.58111293 -> T =  87.16776654,
       5: tau0 = 0.35318417, tau1 = 0.35318417 -> T = 185.23989576,
       6: tau0 = 0.35318419, tau1 = 0.35318417 -> T = 185.23989691,
       7: tau0 = 0.35318417, tau1 = 0.35318419 -> T = 185.23989606,
       8: tau0 = 0.70496498, tau1 = 0.00000000 -> T = 241.12513664,
       9: tau0 = 0.70496500, tau1 = 0.00000000 -> T = 241.12513855,
      10: tau0 = 0.70496498, tau1 = 0.00000001 -> T = 241.12513622,
      11: tau0 = 0.76468955, tau1 = 0.00000010 -> T = 246.27526239,
      12: tau0 = 0.76468956, tau1 = 0.00000010 -> T = 246.27526298,
      13: tau0 = 0.76468955, tau1 = 0.00000011 -> T = 246.27526071,
      14: tau0 = 0.79120338, tau1 = 0.00000000 -> T = 246.65813176,
      15: tau0 = 0.79120340, tau1 = 0.00000000 -> T = 246.65813158,
      16: tau0 = 0.79120338, tau1 = 0.00000001 -> T = 246.65812937,
      17: tau0 = 0.78503003, tau1 = 0.00000003 -> T = 246.69203732,
      18: tau0 = 0.78503004, tau1 = 0.00000003 -> T = 246.69203733,
      19: tau0 = 0.78503003, tau1 = 0.00000004 -> T = 246.69203511,
      20: tau0 = 0.78541803, tau1 = 0.00000008 -> T = 246.69220251,
      21: tau0 = 0.78541805, tau1 = 0.00000008 -> T = 246.69220251,
      22: tau0 = 0.78541803, tau1 = 0.00000009 -> T = 246.69220029,
      23: tau0 = 0.78543099, tau1 = 0.00000004 -> T = 246.69220883,
      24: tau0 = 0.78543100, tau1 = 0.00000004 -> T = 246.69220883,
      25: tau0 = 0.78543099, tau1 = 0.00000005 -> T = 246.69220661,
      26: tau0 = 0.78543063, tau1 = 0.00000000 -> T = 246.69221417,

**Suggestions for other tests?**

# Summary

**Main takeway:** You are actually already equipped to solve a lot of interesting economic models!!!

1. Structure your code (with modules, SimpleNamespaces, functions, classes etc.)
2. Use optimizers and root-finding to find (optimal) agent behavior
3. Use root-finding to find equilibrium prices
4. Use random numbers to simulate behavior

**Your challenge:** Come up with ideas for models to solve... and just get started.

**Next time:** Pandas, the central Python package for working with data.

**My own research:** Uses the [EconModel](https://github.com/NumEconCopenhagen/EconModel) package.


# The Python symbolic math library -- `sympy`

The [`sympy`](http://sympy.org) library allows you to perform symbolic mathematical manipulations in a wide variety of contexts. This library works in a way similar to Mathematica - using functions and methods to perform these manipulations. In this computational problem, you will use a few `sympy` tools to compute the gradient of some scalar functions.

To perform computational tasks with Python, you will often need to import a variety of libraries. The first of these you will learn about is `sympy`. In this problem, you will learn about two ways to compute the gradient symbolically with sympy. You may use either of these to solve the problem posed.

### [Skip to problem to solve](./HW1-GradientProblem.ipynb#Problems-to-solve)

### Getting Started with Jupyter and Python Libraries

First things first, you will need to import methods, functions, and classes from the `sympy` library and set up the printing of the symbolic math. With `sympy`, mathematical symbols are typically printed in plain text unless you specify otherwise. We will use [mathjax](https://www.mathjax.org/) for printing the mathematical symbols, which makes the symbols look like the mathematical symbols you see in your textbook.

The two lines below: (1) import all the methods from sympy and (2) set the printing method to use mathjax.

```python
from sympy import *                # import methods from sympy
init_printing(use_latex='mathjax') # set up printing using mathjax
```

### Specifying symbolic variables

To perform symbolic manipulations, `sympy` needs to know which variables are symbolic, that is, which ones are meant to represent variables that appear in your mathematical functions. In this problem, you will use functions of three variables $(x, y, z)$. So, you need to tell `sympy` that $x, y,$ and $z$ are variables. You do this with the `symbols` function.

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

### Constructing scalar functions

With the symbolic variables specified, you may now construct a scalar function of these variables by assigning a new variable to a mathematical function that you specify. For example, below three different functions are constructed to illustrate some of the mathematical syntax that Python uses.

* $f(x,y,z) = x^2+y+z^3$
* $g(x,y,z) = e^{x}\sin(y)\log(z)$
* $h(x,y,z) = \dfrac{\sqrt{x}}{x^2+y^2+z^2}$

Notice that Jupyter will not print the function when the assignment is performed. But if you call the function by its name alone, it will.

```python
f = x**2 + y + z **3    # Two stars indiciate that a quantity is raised to a power
f
```

$$x^{2} + y + z^{3}$$

```python
g = exp(x)*sin(y)*log(z)   # Known mathematical functions such as e, sin, etc. are defined in Python
g
```

$$e^{x} \log{\left (z \right )} \sin{\left (y \right )}$$

```python
h = sqrt(x)/(x**2+y**2+z**2)    #Pay attention to parantheses to ensure proper order of operations
h
```

$$\frac{\sqrt{x}}{x^{2} + y^{2} + z^{2}}$$

### Method 1 - Computing the gradient using the `diff` function

The function `diff` can be used to [compute derivatives with respect to specified variables](http://docs.sympy.org/latest/tutorial/calculus.html#derivatives). To compute the gradient of the function $f$, you would compute partial derivatives with respect to each variable:

$$\nabla f(x,y,z,) = \dfrac{\partial f}{\partial x} \hat{x} + \dfrac{\partial f}{\partial y} \hat{y} + \dfrac{\partial f}{\partial z} \hat{z}$$

Because the `diff` function computes a derivative with respect to a single variable, it can be used to compute each of the partial derivatives that form the gradient. For example,

$$\dfrac{\partial f}{\partial x} = 2x$$
$$\dfrac{\partial f}{\partial y} = 1$$
$$\dfrac{\partial f}{\partial z} = 3z^2$$

To compute the first of these partial derivatives, we call `diff(f,x)`. The others are similarly called.

```python
diff(f,x)
```

$$2 x$$

```python
diff(f,y)
```

$$1$$

```python
diff(f,z)
```

$$3 z^{2}$$

### Simplifying your work with `sympy.vector`

As you can tell, computing gradients one partial derivative at a time can be somewhat time consuming. Not to mention that performing more sophisticated vector operations (like taking the divergence or curl of a vector field) are pretty difficult to do with just the `diff` function. Fortunately, [sympy has a vector library](http://docs.sympy.org/dev/modules/physics/vector/index.html) to help with these kinds of calculations. To do these kinds of calculations, you will first import the Cartesian coordinate system class (`CoordSysCartesian`) and then construct a Cartesian coordinate system that we can call `R`.

*All that the two statements below do is provide you with the tools that will allow you to specify vector and scalar functions in a Cartesian Coordinate System that we call "R." You will get more practice with specifiying vector functions using sympy.vector in another homework problem.*

```python
from sympy.vector import CoordSysCartesian   # import the Cartesian coordinate system class
R = CoordSysCartesian('R')                   # construct a coordinate system called 'R'
```

### Method 2 - Computing gradients with the `delop` method

The `sympy.vector` library has a method called `delop`, which stands for *del operator*. This method will allow the del operator to act on scalar and vector functions in different ways depending on how it is called.

In this example, we use the `delop` method to compute the gradient of the same function $f(x,y,z) = x^2+y+z^3$ above. To use the `delop` method, we have to construct the scalar function $f$ in a slightly different way than was done before.

```python
f = R.x**2 + R.y + R.z**3 ## Rewriting the function f (it replaces the old definition) so we can use the delop method
```

When we define the function $f$ again, we have to use the "dot structure" to specify each variable in the function using the coordinate system (`R`) that we initialized above. So, `x` becomes `R.x`, `y` becomes `R.y`, and `z` becomes `R.z`.

The gradient of this function is taken by using the `delop` method. Simply calling the `delop` method (below) doesn't perform the calculation, but shows you what calculation will be performed.

```python
R.delop(f)   # Shows the calculation that will be performed
```

$$(\frac{\partial}{\partial \mathbf{{x}_{R}}}\left(\mathbf{{x}_{R}}^{2} + \mathbf{{y}_{R}} + \mathbf{{z}_{R}}^{3}\right))\mathbf{\hat{i}_{R}} + (\frac{\partial}{\partial \mathbf{{y}_{R}}}\left(\mathbf{{x}_{R}}^{2} + \mathbf{{y}_{R}} + \mathbf{{z}_{R}}^{3}\right))\mathbf{\hat{j}_{R}} + (\frac{\partial}{\partial \mathbf{{z}_{R}}}\left(\mathbf{{x}_{R}}^{2} + \mathbf{{y}_{R}} + \mathbf{{z}_{R}}^{3}\right))\mathbf{\hat{k}_{R}}$$

To indicate that you want the gradient computed, you must use the `doit()` method. This call is very similar to the one above with the addition of `.doit()` to the end.

```python
R.delop(f).doit()    # Do the calculation
```

$$(2 \mathbf{{x}_{R}})\mathbf{\hat{i}_{R}} + \mathbf{\hat{j}_{R}} + (3 \mathbf{{z}_{R}}^{2})\mathbf{\hat{k}_{R}}$$

Notice that the full gradient has been taken (i.e., all partial derivatives have been taken). The components are clearly specified including the unit vectors ($\hat{i},\hat{j},\hat{k}$). To see how the whole calculation hangs together, we can perform all this work in two lines for the function $g(x,y,z) = e^x\sin(y)\log(z)$ above.

```python
g = exp(R.x)*sin(R.y)*log(R.z)   # Construct the function g(x,y,z)
R.delop(g).doit()                # Take the gradient of g(x,y,z)
```

$$(e^{\mathbf{{x}_{R}}} \log{\left (\mathbf{{z}_{R}} \right )} \sin{\left (\mathbf{{y}_{R}} \right )})\mathbf{\hat{i}_{R}} + (e^{\mathbf{{x}_{R}}} \log{\left (\mathbf{{z}_{R}} \right )} \cos{\left (\mathbf{{y}_{R}} \right )})\mathbf{\hat{j}_{R}} + (\frac{e^{\mathbf{{x}_{R}}}}{\mathbf{{z}_{R}}} \sin{\left (\mathbf{{y}_{R}} \right )})\mathbf{\hat{k}_{R}}$$

# Problems to solve

Using what you've learned about either the `diff` function or the methods of `sympy.vector`, compute the gradients of:

1. the magnitude of the separation vector, $|\vec{\mathfrak{r}}|$, and
2. the inverse of the magntiude of the separation vector, $\dfrac{1}{|\vec{\mathfrak{r}}|}$.

Check these answers against what you obtained by hand. (*Hint: you will need to create three additional symoblic variables for the source location.*)

```python

```


In this notebook, you will implement the forward longitudinal vehicle model. The model accepts throttle inputs and steps through the longitudinal dynamic equations. Once implemented, you will be given a set of inputs that drives over a small road slope to test your model.

The input to the model is a throttle percentage $x_\theta \in [0,1]$ which provides torque to the engine and subsequently accelerates the vehicle for forward motion.

The dynamic equations consist of many stages to convert throttle inputs to wheel speed (engine -> torque converter -> transmission -> wheel). These stages are bundled together in a single inertia term $J_e$ which is used in the following combined engine dynamic equations.

\begin{align}
    J_e \dot{\omega}_e &= T_e - (GR)(r_{eff} F_{load}) \\ m\ddot{x} &= F_x - F_{load}
\end{align}

Where $T_e$ is the engine torque, $GR$ is the gear ratio, $r_{eff}$ is the effective radius, $m$ is the vehicle mass, $x$ is the vehicle position, $F_x$ is the tire force, and $F_{load}$ is the total load force.

The engine torque is computed from the throttle input and the engine angular velocity $\omega_e$ using a simplified quadratic model.

\begin{align}
    T_e = x_{\theta}(a_0 + a_1 \omega_e + a_2 \omega_e^2)
\end{align}

The load forces consist of aerodynamic drag $F_{aero}$, rolling friction $R_x$, and gravitational force $F_g$ from an incline at angle $\alpha$. The aerodynamic drag is a quadratic model and the friction is a linear model.

\begin{align}
    F_{load} &= F_{aero} + R_x + F_g \\
    F_{aero} &= \frac{1}{2} C_a \rho A \dot{x}^2 = c_a \dot{x}^2\\
    R_x &= N(\hat{c}_{r,0} + \hat{c}_{r,1}|\dot{x}| + \hat{c}_{r,2}\dot{x}^2) \approx c_{r,1} \dot{x}\\
    F_g &= mg\sin{\alpha}
\end{align}

Note that the absolute value is ignored for friction since the model is used for only forward motion ($\dot{x} \ge 0$).

The tire force is computed using the engine speed and wheel slip equations.

\begin{align}
    \omega_w &= (GR)\omega_e \\
    s &= \frac{\omega_w r_e - \dot{x}}{\dot{x}}\\
    F_x &= \left\{\begin{array}{lr}
        cs, &  |s| < 1\\
        F_{max}, & \text{otherwise}
        \end{array}\right\}
\end{align}

Where $\omega_w$ is the wheel angular velocity and $s$ is the slip ratio.

We setup the longitudinal model inside a Python class below. The vehicle begins with an initial velocity of 5 m/s and engine speed of 100 rad/s. All the relevant parameters are defined and like the bicycle model, a sampling time of 10ms is used for numerical integration.

```python
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

class Vehicle():
    def __init__(self):

        # ==================================
        #  Parameters
        # ==================================

        #Throttle to engine torque
        self.a_0 = 400
        self.a_1 = 0.1
        self.a_2 = -0.0002

        # Gear ratio, effective radius, mass + inertia
        self.GR = 0.35
        self.r_e = 0.3
        self.J_e = 10
        self.m = 2000
        self.g = 9.81

        # Aerodynamic and friction coefficients
        self.c_a = 1.36
        self.c_r1 = 0.01

        # Tire force
        self.c = 10000
        self.F_max = 10000

        # State variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0

        self.sample_time = 0.01

    def reset(self):
        # reset state variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0
```

Implement the combined engine dynamic equations along with the force equations in the cell below. The function $\textit{step}$ takes the throttle $x_\theta$ and incline angle $\alpha$ as inputs and performs numerical integration over one timestep to update the state variables. Hint: Integrate to find the current position, velocity, and engine speed first, then propagate those values into the set of equations.

```python
class Vehicle(Vehicle):
    def step(self, throttle, alpha):
        # ==================================
        #  Implement vehicle model here
        # ==================================
        T_e = throttle * (self.a_0 + self.a_1*self.w_e + self.a_2*self.w_e**2)
        F_areo = self.c_a * self.v**2
        R_x = self.c_r1 * self.v
        F_g = self.m * self.g * np.sin(alpha)
        F_load = F_areo + R_x + F_g
        #torque equation (angular acceleration)
        self.w_e_dot = (T_e - self.GR*self.r_e*F_load) / self.J_e

        w_w = self.GR * self.w_e
        s = (w_w*self.r_e - self.v) / self.v
        if abs(s) < 1:
            F_x = self.c * s
        else:
            F_x = self.F_max
        #force equation (acceleration)
        self.a = (F_x - F_load) / self.m

        #update equations
        self.w_e += self.w_e_dot * self.sample_time
        #since v = a*t
        self.v += self.a * self.sample_time
        #since x = v*t - (1/2)*a*t^2
        self.x += (self.v * self.sample_time) - (0.5 * self.a * self.sample_time**2)

```

Using the model, you can send constant throttle inputs to the vehicle in the cell below. You will observe that the velocity converges to a fixed value based on the throttle input due to the aerodynamic drag and tire force limit. A similar velocity profile can be seen by setting a negative incline angle $\alpha$. In this case, gravity accelerates the vehicle to a terminal velocity where it is balanced by the drag force.

```python
sample_time = 0.01
time_end = 100
model = Vehicle()

t_data = np.arange(0,time_end,sample_time)
v_data = np.zeros_like(t_data)

# throttle percentage between 0 and 1
throttle = 0.2

# incline angle (in radians)
alpha = 0

for i in range(t_data.shape[0]):
    v_data[i] = model.v
    model.step(throttle, alpha)

plt.plot(t_data, v_data)
plt.show()
```

We will now drive the vehicle over a slope as shown in the diagram below.

To climb the slope, a trapezoidal throttle input is provided for the next 20 seconds as shown in the figure below.

The vehicle begins at 20% throttle and gradually increases to 50% throttle. This is maintained for 10 seconds as the vehicle climbs the steeper slope. Afterwards, the vehicle reduces the throttle to 0.

In the cell below, implement the ramp angle profile $\alpha (x)$ and throttle profile $x_\theta (t)$ and step them through the vehicle dynamics. The vehicle position $x(t)$ is saved in the array $\textit{x_data}$. This will be used to grade your solution.

```python
time_end = 20
t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)
v_data = np.zeros_like(t_data)
w_e_data = np.zeros_like(t_data)

# reset the states
model.reset()

# ==================================
#  Learner solution begins here
# ==================================
def angle(i, alpha, x):
    if x < 60:
        alpha[i] = np.arctan(3/60)
    elif x < 150:
        alpha[i] = np.arctan(9/90)
    else:
        alpha[i] = 0

throttle = np.zeros_like(t_data)
alpha = np.zeros_like(t_data)

#throttle depends on time and alpha depends on distance travelled (model.x)
for i in range(t_data.shape[0]):
    if t_data[i] < 5:
        throttle[i] = 0.2 + ((0.5 - 0.2)/5)*t_data[i]
        angle(i, alpha, model.x)
    elif t_data[i] < 15:
        throttle[i] = 0.5
        angle(i, alpha, model.x)
    else:
        throttle[i] = ((0 - 0.5)/(20 - 15))*(t_data[i] - 20)
        angle(i, alpha, model.x)

    #call the step function and update x_data array
    model.step(throttle[i], alpha[i])
    x_data[i] = model.x
    v_data[i] = model.v
    w_e_data[i] = model.w_e

# ==================================
#  Learner solution ends here
# ==================================

# Plot x vs t for visualization
plt.title('Distance')
plt.plot(t_data, x_data)
plt.show()
```

```python
plt.title('Velocity')
plt.plot(t_data, v_data)
plt.show()
```

```python
plt.title('w_e')
plt.plot(t_data, w_e_data)
plt.show()
```

```python
plt.title('throttle')
plt.plot(t_data, throttle)
plt.show()
```

```python
plt.title('alpha')
plt.plot(t_data, alpha)
plt.show()
```

If you have implemented the vehicle model and inputs correctly, you should see that the vehicle crosses the ramp at ~15s where the throttle input begins to decrease.

The cell below will save the time and vehicle inputs as text file named $\textit{xdata.txt}$. To locate the file, change the end of your web directory to $\textit{/notebooks/Course_1_Module_4/xdata.txt}$

Once you are there, you can download the file and submit to the Coursera grader to complete this assessment.

```python
data = np.vstack([t_data, x_data]).T
np.savetxt('xdata.txt', data, delimiter=', ')
```

Congratulations! You have now completed the assessment! Feel free to test the vehicle model with different inputs in the cell below, and see what trajectories they form. In the next module, you will see the longitudinal model being used for speed control. See you there!

```python
sample_time = 0.01
time_end = 30
model.reset()

t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)

# ==================================
#  Test various inputs here
# ==================================
for i in range(t_data.shape[0]):

    model.step(0,0)

plt.axis('equal')
plt.plot(x_data, y_data)
plt.show()
```


```python
import sympy
```

$$u(t) = \mathcal{1}(t)$$

$$g(t) = e^{-t}\mathcal{1}(t)$$

$$y(t) = u(t) \bigotimes g(t) = \mathcal{L}^{-1}(U(s) G(s)) = \mathcal{L}^{-1}\left(\frac{1}{s} \cdot \frac{1}{s+1}\right)
= \mathcal{L}^{-1}\left(\frac{1}{s(s+1)}\right)= \mathcal{L}^{-1}\left(\frac{1}{s} - \frac{1}{s+1}\right) =
\mathcal{1}(t) - e^{-t}\mathcal{1}(t)$$

```python
t = sympy.symbols('t', real=True)
u = sympy.Heaviside(t)  # unit step
g = sympy.exp(-t)*sympy.Heaviside(t)
y = sympy.Heaviside(t) - sympy.exp(-t)*sympy.Heaviside(t)
g
```

$\displaystyle e^{- t} \theta\left(t\right)$

```python
sympy.plot(y)
```

```python
sympy.plot(g)
```

```python
y_approx = g + g.subs(t, t - 1) + g.subs(t, t - 2) + g.subs(t, t - 3) + g.subs(t, t - 4)
y_approx
```

$\displaystyle e^{1 - t} \theta\left(t - 1\right) + e^{2 - t} \theta\left(t - 2\right) + e^{3 - t} \theta\left(t - 3\right) + e^{4 - t} \theta\left(t - 4\right) + e^{- t} \theta\left(t\right)$

```python
sympy.plot(y_approx, y, (t, 0, 4))
```

```python
y_approx2 = 0.5*g + 0.5*g.subs(t, t - 0.5) + 0.5*g.subs(t, t - 1) + 0.5*g.subs(t, t - 1.5) + 0.5*g.subs(t, t - 2) \
    + 0.5*g.subs(t, t - 2.5) + 0.5*g.subs(t, t - 3) + 0.5*g.subs(t, t - 3.5) + 0.5*g.subs(t, t - 4)
y_approx2
```

$\displaystyle 0.5 e^{1 - t} \theta\left(t - 1\right) + 0.5 e^{2 - t} \theta\left(t - 2\right) + 0.5 e^{3 - t} \theta\left(t - 3\right) + 0.5 e^{4 - t} \theta\left(t - 4\right) + 0.5 e^{- t} \theta\left(t\right) + 16.5577259793462 e^{- t} \theta\left(t - 3.5\right) + 6.09124698035174 e^{- t} \theta\left(t - 2.5\right) + 2.24084453516903 e^{- t} \theta\left(t - 1.5\right) + 0.824360635350064 e^{- t} \theta\left(t - 0.5\right)$

```python
y_approx3 = 0
tf = 4
dt = 0.1
for i in range(int(tf/dt)):
    y_approx3 += dt*g.subs(t, t-i*dt)
```

```python
y4 = sympy.Heaviside(t) - sympy.exp(-t) + t*sympy.exp(t)
sympy.plot(y4, (t, -3, 3))
```

```python
y4 = (1 - sympy.exp(-t) + t*sympy.exp(t))*sympy.Heaviside(t)
sympy.plot(y4, (t, -3, 3))
```

```python
sympy.plot(y, y_approx3, (t, 0, 4))
```

```python
sympy.plot(y_approx, y, y_approx2, (t, 0, 4))
```

```python

```


```python
import sympy as sp
import numpy as np

sp.var('q0, q1, q2, q3, m0, m1, m2, m3, g, l0, l1, l2, l3, l4, l34, t')

x0 = l0*sp.cos(q0)
y0 = l0*sp.sin(q0)
x1 = l0*sp.cos(q0) + l1*sp.cos(q0 + q1)
y1 = l0*sp.sin(q0) + l1*sp.sin(q0 + q1)

x2 = l2*sp.cos(q2)
y2 = l2*sp.sin(q2)

# foot forward kinematics
x3 = l2*sp.cos(q2) + l34*sp.cos(q2 + q3)
y3 = l2*sp.sin(q2) + l34*sp.sin(q2 + q3)

# constraint forward kinematics
xc = l2*sp.cos(q2) + l3*sp.cos(q2 + q3)
yc = l2*sp.sin(q2) + l3*sp.sin(q2 + q3)
```

```python
# Compute Constraint
# enforces endpoints to have same position and velocity at all times
c  = sp.Matrix((2, 1))
c[0, 0] = x1 - xc
c[1, 0] = y1 - yc
print(c)
```

    Matrix([[l0*cos(q0) + l1*cos(q0 + q1) - l2*cos(q2) - l3*cos(q2 + q3)], [l0*sin(q0) + l1*sin(q0 + q1) - l2*sin(q2) - l3*sin(q2 + q3)]])

```python
# Constraint Jacobian
D = c.jacobian([q0, q1, q2, q3])
print(D)
```

    Matrix([[-l0*sin(q0) - l1*sin(q0 + q1), -l1*sin(q0 + q1), l2*sin(q2) + l3*sin(q2 + q3), l3*sin(q2 + q3)], [l0*cos(q0) + l1*cos(q0 + q1), l1*cos(q0 + q1), -l2*cos(q2) - l3*cos(q2 + q3), -l3*cos(q2 + q3)]])

```python
# Compute del/delq(D(q)q_dot)q_dot
'''
q0 = Function('q0')
q0_t = q0(t)
q1 = Function('q1')
q1_t = q1(t)
q1 = Function('q2')
q2_t = q2(t)
q2 = Function('q3')
q3_t = q3(t)
q_t = sp.array([q0_t, q1_t, q2_t, q3_t])
q_dot = sp.diff(q_t, t)
'''
sp.var('q0d q1d q2d q3d')
q_dot = sp.Matrix([q0d, q1d, q2d, q3d])
# dqdot = sp.Matrix((2, 4))
dqdot = D.multiply(q_dot)
d = dqdot.jacobian([q0, q1, q2, q3])*q_dot
print(d)
```

    Matrix([[q0d*(-l1*q1d*cos(q0 + q1) + q0d*(-l0*cos(q0) - l1*cos(q0 + q1))) + q1d*(-l1*q0d*cos(q0 + q1) - l1*q1d*cos(q0 + q1)) + q2d*(l3*q3d*cos(q2 + q3) + q2d*(l2*cos(q2) + l3*cos(q2 + q3))) + q3d*(l3*q2d*cos(q2 + q3) + l3*q3d*cos(q2 + q3))], [q0d*(-l1*q1d*sin(q0 + q1) + q0d*(-l0*sin(q0) - l1*sin(q0 + q1))) + q1d*(-l1*q0d*sin(q0 + q1) - l1*q1d*sin(q0 + q1)) + q2d*(l3*q3d*sin(q2 + q3) + q2d*(l2*sin(q2) + l3*sin(q2 + q3))) + q3d*(l3*q2d*sin(q2 + q3) + l3*q3d*sin(q2 + q3))]])

```python
#Compute cdot (first derivative of constraint function)
cdot = sp.transpose(q_dot.T * D.T)
# cdot = # sp.transpose(sp.ones(np.shape(D)[1], 1).T * D.T)
print(cdot)
```

    Matrix([[-l1*q1d*sin(q0 + q1) + l3*q3d*sin(q2 + q3) + q0d*(-l0*sin(q0) - l1*sin(q0 + q1)) + q2d*(l2*sin(q2) + l3*sin(q2 + q3))], [l1*q1d*cos(q0 + q1) - l3*q3d*cos(q2 + q3) + q0d*(l0*cos(q0) + l1*cos(q0 + q1)) + q2d*(-l2*cos(q2) - l3*cos(q2 + q3))]])

```python
# Compute Foot Jacobian
Jx = x2.jacobian([q0, q1, q2, q3])
Jy = y2.jacobian([q0, q1, q2, q3])
Jf = np.array([Jx, Jy])
print(Jf)
```


##  The Empirical Risk Minimization Framework

Let $\mathcal{Q}$ and $\mathcal{D}$ be the query space and the document space, respectively, we use $\Phi:\mathcal{Q}\times\mathcal{D}\rightarrow\mathcal{Z}=\mathbb{R}^{d}$ to denote the mapping function for generating a feature vector for a query-document pair, where $\mathcal{Z}$ represents the $d$-dimensional feature space. We use $\mathcal{R}=\mathbb{R}_{+}$ to denote the space of the ground-truth relevance scores each document receives. Thus for each query, we have a list of document feature vectors $X=(X_{1},...,X_{m})\in\mathcal{X}=\mathcal{Z}^{m}$ and a corresponding list $Y=(Y_{1},...,Y_{m})\in\mathcal{Y}=\mathcal{R}^{m}$ of ground-truth relevance scores. In practice, we get independently and identically distributed (i.i.d) samples $\mathcal{S}=\{(X_{i},Y_{i})\}_{i=1}^{n}$
from an unknown joint distribution $P(\cdot,\cdot)$ over $\mathcal{X}\times\mathcal{Y}$. We use $f_{\theta}:X\rightarrow\mathbb{R}_{+}^{m}$ parameterized by $\theta\in\Theta$ to denote the real-valued ranking function, which assigns each document a score. The scores of the documents associated with the same query are used to rank the documents. We measure the loss of ranking documents for a query using $f_{\theta}$ with the loss function $\ell(f_{\theta}(X),Y)$. The goal is to learn the optimal ranking function over a hypothesis space $\mathcal{F}$ of ranking functions that can \emph{minimize the expected risk} as defined below:

\begin{equation}
	\min_{f_{\theta}\in\mathcal{F}}\Re(f_{\theta})=\min_{f_{\theta}\in\mathcal{F}}\int_{\mathcal{X}\times\mathcal{Y}}\ell(f_{\theta}(X),Y)dP(X,Y)
\end{equation}

Typically, $\Re(f_{\theta})$ is intractable to optimize directly and the joint distribution is unknown, we appeal to the \emph{empirical risk minimization} to approximate the expected risk, which is defined as follows:

\begin{equation}
	\min_{f_{\theta}\in\mathcal{F}}\hat{\Re}(f_{\theta};\mathcal{S})=\min_{f_{\theta}\in\mathcal{F}}\frac{1}{n}\sum_{i=1}^{n}\ell(f_{\theta}(X),Y)
\end{equation}

Given the above optimization framework, one can design various ranking methods by deploying different loss functions to learn the parameters $\theta$ based on the training documents. In the testing phase, the predicted ranking can be obtained efficiently by sorting the testing documents in descending order of their individual relevance scores $f_{\theta}(X_{i})$.