# Robinson Crusoe or 10 minutes to respy

This is a short introduction to respy for new users. As economists love Robinsonades [1](#fn1), we will showcase the implementation of a Robinson Crusoe economy as a discrete choice dynamic programming model. Throughout the notebook you find italic text which make you familiar with Robinson's story. We will first set the scene with a broad introduction and then turn to the precise model specification. We continue by simulating the model and analyze its comparative statics. We then extend the model and showcase the estimation of the model parameters.

Just to be clear, don't misinterpret the fact that we explain **respy** using such a simplistic model. **respy** is not a toy and can just as well solve state-of-the-art structural models. It's just easier to explain **respy** in a situation where we don't have to explain a complicated model at the same time.

```python
%matplotlib inline

import io
import matplotlib.pyplot as plt
import pandas as pd
import respy as rp
import yaml
import seaborn as sns
import numpy as np

from pathlib import Path
from time import time

plt.style.use("../_static/respy.mplstyle")
```

## Introduction

---
*After setting sail against his parents' wishes, being captured by pirates, escaping from them, building a plantation, and setting sail again to capture slaves in Africa, [Robinson Crusoe](https://en.wikipedia.org/wiki/Robinson_Crusoe) stranded on a small island. He is alone with one dog, two cats, and only some supplies. He goes fishing to make ends meet and if he is too tired he will relax in his hammock. But, he cannot relax to often as storing food is a difficult task on a tropical island.*

---

In the discrete choice dynamic programming model, Robinson chooses every period $t = 0, \dots, T - 1$ to either go fishing, $a = 0$, or spend the day in the hammock, $a = 1$, to maximize his expected sum of discounted lifetime utility. The utility of a choice, $U(s_t, a_t)$, depends on the state $s_t$, which contains information on the individual's characteristics, and the chosen alternative $a_t$. For working alternatives like fishing utility consists of two components, a wage and a non-pecuniary component.

$$
    U(s_t, a_t) = W(s_t, a_t) + N(s_t, a_t)
$$

For non-working alternatives like the hammock, $W(s_t, a_t) = 0$. The wage is defined as

$$\begin{align}
        W(s_t, a_t)  &= r_a \exp\{x^w_{at} \beta^w_a + \epsilon_{at}\}\\
    \ln(W(s_t, a_t)) &= \ln(r_a) + x^w_{at} \beta^w_a + \epsilon_{at}
\end{align}$$

where $r_a$ is normally a market rental price for the skill units generated in the exponential expression. Another interpretation is that $ln(r_a)$ is simply the constant in the skill units. The skill units are generated by two components. $x^w_{at}$ and $\beta^w_a$ are the choice- and time-dependent covariates and parameters related to the wage signaled by superscript $w$. $\epsilon_{at}$ is a choice-specific random shock from the shock vector $\epsilon_t \sim \mathcal{N}(0, \Sigma)$ for all choices. Shocks are usually correlated across choices in one period, but are independent across periods.

The non-pecuniary rewards for working alternatives are a vector dot product of covariates $x_t^w$ and parameters $\beta^w$. The superscript $w$ signals that the components belong to working alternatives.

$$
    N^w(s_t, a_t) = x_t^w\beta^w
$$

The non-pecuniary reward for non-working alternatives is very similar except that the shocks enter the equation additively. Superscript $n$ stands for non-pecuniary.

$$
    N^n(s_t, a_t) = x_t^n\beta^n + \epsilon_{at}
$$

Along with the lower triangular elements of the shock variance-covariance matrix of $\epsilon_t$, the utility parameters $\beta_a^w$, $\beta_a^n$ and $r_a$ form the main parameters of the model.

If Robinson chooses to go fishing, he gains one additional unit of experience in the next period. Experience starts at zero and goes over 1, 2, 3 up to $T - 1$.

The general assumption imposed on Robinson is that he is forward-looking and maximizes the expected present value of utility over the remaining lifetime. W.l.o.g. $t = 0$ and let $V(s_0)$ define the value of the maximization which is achieved by a sequence of choices, $\{a_t\}^T_{t = 0}$, such that every action is in the choice set, $a_t \in C(s_t)$ and $s_{t + 1}$ adheres to the law of motion. Then, the expected present value of lifetime utility in state $s_0$ is

$$
    V(s_0) = \text{E} \max_{\{a_t\}^T_{t = 0}} \left[
        \sum^T_{t = 0} \delta^t U(s_t, a_t) \, \Big|
        \, a_t \in C(s_t), s_{t+1} = m(s_t, a_t)
    \right]
$$

Note that the shocks in period $t = 0$ are not stochastic. Thus, one can extract the utility in the current period $U(s_0, a_0)$, also called the flow utility, and the discount factor $\delta$ from the expectation. Then, the formula can be rewritten so that the second term becomes the maximum over alternative-specific value functions at time $t = 1$ which are also called continuation values.

$$\begin{align}
    V(s_0) &= \max_{a_0} \, U(s_0, a_0) + \delta \text{E} \max_{\{a_t\}^T_{t = 1}}
              \left[
                  \sum^T_{t = 1} \delta^{t - 1} U(s_t, a_t) \, \Big|
                  \, a_t \in C(s_t), s_{t + 1} = m(s_t, a_t)
              \right] \\
           &= \max_{a_0} \, U(s_0, a_0)
              + \delta \text{E} \max_{a_1} V_{a_1}(s_1)
\end{align}$$

The maximum over alternative-specific value functions can be rewritten as the value function of state $s_1$ or $V(s_1) = \max_{a_1} V_{a_1}(s_1)$ which yields the famous Bellman equation. Due to the recursive nature of the problem, the alternative-specific value functions are defined as

$$\begin{equation}
    V_a(s_t) = \begin{cases}
        U(s_t, a_t) + \delta \text{E} V(s_{t+1}) & \text{if } t < T \\
        U(s_t, a_t) & \text{if } t = T
    \end{cases}
\end{equation}$$

The former equation shows that the shocks in period $t + 1$ are unknown to the individual in period $t$. Thus, utility must be maximized given the joint distribution of shocks in period $t + 1$ which is a maximization problem over a two-dimensional integral. Denote the non-stochastic part of a state as $s^-$. Then, Robinson maximizes

$$\begin{equation}
    V(s_t) = \max_{a_t}\{
        U(s_t, a_t) + \delta \int_{\epsilon_{0, t + 1}} \dots \int_{\epsilon_{2, t + 1}}
        \max_{a_{t + 1}} V_{a_{t + 1}}(s^-_{t + 1}, \epsilon_{t + 1})
        f_\epsilon(\epsilon_{t + 1})
        d_{\epsilon_{0, t + 1}} \dots d_{\epsilon_{2, t + 1}}
    \}
\end{equation}$$

## Specification

How can we express the equations and parameters with **respy**? The following cell contains the code to write a `.csv` file which is the cornerstone of a model as it contains all parameters and some other specifications. With `io.StringIO` we can pretend it is an actual file on the filesystem and easily loaded with `pandas`.

```python
params = """category,name,value
delta,delta,0.95
wage_fishing,exp_fishing,0.1
nonpec_fishing,constant,-1
nonpec_hammock,constant,2.5
nonpec_hammock,not_fishing_last_period,-1
shocks_sdcorr,sd_fishing,1
shocks_sdcorr,sd_hammock,1
shocks_sdcorr,corr_hammock_fishing,-0.2
lagged_choice_1_hammock,constant,1
"""
```

```python
params = pd.read_csv(io.StringIO(params), index_col=["category", "name"])
params
```

      value

      category
      name

      delta
      delta
      0.95

      wage_fishing
      exp_fishing
      0.10

      nonpec_fishing
      constant
      -1.00

      nonpec_hammock
      constant
      2.50

      not_fishing_last_period
      -1.00

      shocks_sdcorr
      sd_fishing
      1.00

      sd_hammock
      1.00

      corr_hammock_fishing
      -0.20

      lagged_choice_1_hammock
      constant
      1.00

The DataFrame for the parameters contains a two-level MultiIndex to group parameters in categories. `name` should be uniquely assigned in each category or otherwise only the sum of identically named parameters is identified. `value` contains the value of the parameter. Note that we named Robinson's alternatives `"fishing"` and `"hammock"` and we have to use the names consistently. As long as you stick to lowercase letters separated by underscores, you can choose any name you want.

The parameter specification contains following entries:

- The first entry contains the discount factor of individuals.
- The second category `"wage_fishing"` contains the parameters of the log wage equation for fishing. The group contains only one name called `"exp_fishing"` where `"exp_*"` is an identifier in the model for experience accumulated in a certain alternative. **respy** requires that you respect those identifiers of which there are not many and reference your alternatives consistently with the same name. If you stick to lowercase letters possibly separated by underscores, you are fine.
- The third and fourth categories concern the non-pecuniary reward of fishing and relaxing in the hammock.
- `"shocks_sdcorr"` groups the lower triangular of the variance-covariance matrix of shocks.
- `"lagged_choice_1_hammock"` governs the distribution of previous choices at the begin of the model horizon.

`params` is complemented with `options` which contains additional information. Here is short description:

- `"n_periods"` defines the number of periods for which decision rules are computed.
- `"_seed"`: Seeds are used in every model component to ensure reproducibility. You can use any seed you would like or even repeat the same seed number. Internally, we ensure that randomness is completely uncorrelated.
- `"estimation_draws"` defines the number of draws used to simulate the choice probabilities with Monte Carlo simulation in the maximum likelihood estimation.
- `"estimation_tau"` controls the temperature of the softmax function to avoid zero-valued choice probabilities.
- `"interpolation_points"` controls how many states are used to approximate the value functions of others states in each period. `-1` turns the approximation off. The approximation is detailed in Keane and Wolpin (1994).
- ``"simulation_agents"`` defines how many individuals are simulated.
- ``"solution_draws"`` defines the number of draws used to simulate the expected value functions in the solution.
- `"covariates"` is another dictionary where the key determines the covariate's name and the value is its definition. Here, we have to define what `"constant"` means.

```python
options = """n_periods: 10
estimation_draws: 200
estimation_seed: 500
estimation_tau: 0.001
interpolation_points: -1
simulation_agents: 1_000
simulation_seed: 132
solution_draws: 500
solution_seed: 456
covariates:
    constant: "1"
    not_fishing_last_period: "lagged_choice_1 != 'fishing'"
"""
```

```python
options = yaml.safe_load(options)
options
```

    {'n_periods': 10,
     'estimation_draws': 200,
     'estimation_seed': 500,
     'estimation_tau': 0.001,
     'interpolation_points': -1,
     'simulation_agents': 1000,
     'simulation_seed': 132,
     'solution_draws': 500,
     'solution_seed': 456,
     'covariates': {'constant': '1',
      'not_fishing_last_period': "lagged_choice_1 != 'fishing'"}}

## Simulation

We are now ready to simulate the model.

```python
simulate = rp.get_simulate_func(params, options)
df = simulate(params)
```

```python
df.head(15)
```

      Experience_Fishing
      Lagged_Choice_1
      Shock_Reward_Fishing
      Meas_Error_Wage_Fishing
      Shock_Reward_Hammock
      Meas_Error_Wage_Hammock
      Dense_Key
      Core_Index
      Choice
      Wage
      ...
      Nonpecuniary_Reward_Fishing
      Wage_Fishing
      Flow_Utility_Fishing
      Value_Function_Fishing
      Continuation_Value_Fishing
      Nonpecuniary_Reward_Hammock
      Wage_Hammock
      Flow_Utility_Hammock
      Value_Function_Hammock
      Continuation_Value_Hammock

      Identifier
      Period

      0
      0
      0
      hammock
      -0.035035
      1
      0.040965
      1
      0
      1
      fishing
      0.965571
      ...
      -1
      0.965571
      -0.034429
      18.430145
      19.436393
      1.5
      NaN
      1.547145
      18.414638
      17.755256

      1
      1
      fishing
      0.074254
      1
      1.506491
      1
      1
      0
      hammock
      NaN
      ...
      -1
      1.190358
      0.190358
      17.891912
      18.633215
      2.5
      NaN
      3.961203
      20.072216
      16.958962

      2
      1
      hammock
      -0.354560
      1
      1.185316
      1
      2
      0
      hammock
      NaN
      ...
      -1
      0.775258
      -0.224742
      15.433115
      16.481955
      1.5
      NaN
      2.732280
      16.936717
      14.952039

      3
      1
      hammock
      -0.109397
      1
      -0.785877
      1
      3
      1
      fishing
      0.990647
      ...
      -1
      0.990647
      -0.009353
      13.645001
      14.373005
      1.5
      NaN
      0.751880
      13.073149
      12.969757

      4
      2
      fishing
      -1.063705
      1
      1.245234
      1
      4
      6
      hammock
      NaN
      ...
      -1
      0.421597
      -0.578403
      11.723370
      12.949235
      2.5
      NaN
      3.932816
      14.967629
      11.615592

      5
      2
      hammock
      -0.106235
      1
      -1.854834
      1
      5
      1
      fishing
      1.098301
      ...
      -1
      1.098301
      0.098301
      10.090155
      10.517741
      1.5
      NaN
      -0.296111
      8.560856
      9.323124

      6
      3
      fishing
      -0.692603
      1
      -1.238533
      1
      6
      10
      hammock
      NaN
      ...
      -1
      0.675297
      -0.324703
      7.806439
      8.559098
      2.5
      NaN
      1.425011
      8.519145
      7.467509

      7
      3
      hammock
      -0.555217
      1
      -0.542048
      1
      7
      13
      hammock
      NaN
      ...
      -1
      0.774749
      -0.225251
      5.408186
      5.929934
      1.5
      NaN
      1.079947
      5.832661
      5.002857

      8
      3
      hammock
      -0.943424
      1
      -0.581919
      1
      8
      1
      hammock
      NaN
      ...
      -1
      0.525490
      -0.474510
      2.664255
      3.303963
      1.5
      NaN
      1.118523
      3.427303
      2.430295

      9
      3
      hammock
      -1.541738
      1
      0.487632
      1
      9
      3
      hammock
      NaN
      ...
      -1
      0.288882
      -0.711118
      -0.711118
      0.000000
      1.5
      NaN
      2.286128
      2.286128
      0.000000

      1
      0
      0
      hammock
      -0.713137
      1
      1.418950
      1
      0
      1
      hammock
      NaN
      ...
      -1
      0.490104
      -0.509896
      17.954678
      19.436393
      1.5
      NaN
      3.032909
      19.900402
      17.755256

      1
      0
      hammock
      -0.464134
      1
      -0.384774
      1
      1
      3
      hammock
      NaN
      ...
      -1
      0.628679
      -0.371321
      16.268399
      17.515494
      1.5
      NaN
      1.215827
      16.377564
      15.959723

      2
      0
      hammock
      0.066755
      1
      -0.844060
      1
      2
      1
      fishing
      1.069034
      ...
      -1
      1.069034
      0.069034
      14.828094
      15.535854
      1.5
      NaN
      0.659643
      14.053384
      14.098675

      3
      1
      fishing
      -0.748565
      1
      -1.348821
      1
      3
      5
      hammock
      NaN
      ...
      -1
      0.522795
      -0.477205
      13.177150
      14.373005
      2.5
      NaN
      1.328144
      13.649412
      12.969757

      4
      1
      hammock
      -0.130574
      1
      -0.539693
      1
      4
      9
      fishing
      0.969889
      ...
      -1
      0.969889
      -0.030111
      11.562961
      12.203234
      1.5
      NaN
      0.997326
      11.386079
      10.935529

15 rows × 22 columns

We can inspect Robinson's decisions period by period.

```python
fig, ax = plt.subplots()

df.groupby("Period").Choice.value_counts(normalize=True).unstack().plot.bar(
    stacked=True, ax=ax
)

plt.xticks(rotation="horizontal")

plt.legend(loc="lower center", bbox_to_anchor=(0.5,-0.275), ncol=2)

plt.show()
plt.close()
```

We can also analyze the persistence in decisions.

```python
data = pd.crosstab(df.Lagged_Choice_1, df.Choice, normalize=True)
sns.heatmap(data, cmap="Blues", annot=True)
```

## Analysis

We now study how Robinson's behavior changes as we increase the returns to experience. We do so by plotting the average level of final experience in the sample under the different parameterizations.

This analysis of the comparative statics of the model is straightforward to implement. In models of educational choice, this type of analysis is often applied to evaluate the effect of alternative tuition policies on average educational attainment. See Keane & Wolpin (1997, 2001) for example. The basic structure of the analysis remains the same.

```python
# Specification of grid for evaluation
num_points = 15
grid_start = 0.0
grid_stop = 0.3

grid_points = np.linspace(grid_start, grid_stop, num_points)

rslts = list()
for value in grid_points:

    params.loc["wage_fishing", "exp_fishing"] = value

    df = simulate(params)

    stat = df.groupby("Identifier")["Experience_Fishing"].max().mean()
    rslts.append(stat)
```

We collected all results in `rslts` and are ready to create a basic visualization.

```python
fig, ax = plt.subplots()

ax.plot(grid_points, rslts)

ax.set_ylim([0, 10])
ax.set_xlabel("Return to experience")
ax.set_ylabel("Average final level of exerience")

plt.show()
plt.close()
```

In the absence of any returns to experience, Robinson still spends more than two periods fishing. This share then increases with the return. Starting at around 0.2, Robinson spends all his time fishing.

## Extension

Let us make the model more interesting!

---
*At some point Crusoe notices that a group of cannibals occasionally visits the island and celebrate one of their dark rituals. But then, a prisoner can escape and becomes Crusoe's new friend Friday whom he teaches English. In return Friday can share his knowledge once to help Robinson improve his fishing skills, but that is only possible after Robinson tried at least once to go fishing.*

---

A common extension to structural models is to increase the choice set. Here, we want to add another choice called `"friday"` which affects the utility of fishing. The choice should be available once, starting with the third period, and only after Robinson has been fishing before.

Note that, we load the example models with the function, `rp.get_example_model`. The command for the former model is `params, options, df = rp.get_example_model("robinson_crusoe_basic")`. You can use `with_data=False` to suppress the automatic simulation of a sample with this parameterization.

```python
params, options = rp.get_example_model("robinson_crusoe_extended", with_data=False)
```

At first, take a look at the parameterization. There is a new positive parameter called `"contemplation_with_friday"` which enters the wage equation of fishing. The choice `"friday"` itself has a negative constant utility term which models the effort costs of learning and the food penalty. The variance-covariance matrix is also adjusted.

```python
params
```

      value

      category
      name

      delta
      delta
      0.95

      wage_fishing
      exp_fishing
      0.10

      contemplation_with_friday
      0.40

      nonpec_fishing
      constant
      -1.00

      nonpec_friday
      constant
      -1.00

      not_fishing_last_period
      -1.00

      nonpec_hammock
      constant
      2.50

      not_fishing_last_period
      -1.00

      shocks_sdcorr
      sd_fishing
      1.00

      sd_friday
      1.00

      sd_hammock
      1.00

      corr_friday_fishing
      0.00

      corr_hammock_fishing
      0.00

      corr_hammock_friday
      0.00

      lagged_choice_1_hammock
      constant
      1.00

Turning to the `options`, we can see that the new covariate `"contemplation_with_friday"` is only affecting utility if Robinson is experienced in fishing and only for one interaction with friday. This naturally limits the interaction with Friday. The key `"negative_choice_set"` can be used to restrict the choice Friday to the third and following periods. The first key matches a choice. The value of the key can be a list of strings. If the string evaluates to `True`, a utility penalty ensures that individuals will never choose the corresponding states. There exist some states in the state space which will never be reached because choices are mutually exclusive or are affected by other restrictions. Filters under `"core_state_space_filters"` can be used to purge those states from the state space, reducing runtime and memory consumption.

```python
options
```

    {'n_periods': 10,
     'estimation_draws': 200,
     'estimation_seed': 500,
     'estimation_tau': 0.001,
     'interpolation_points': -1,
     'simulation_agents': 1000,
     'simulation_seed': 132,
     'solution_draws': 500,
     'solution_seed': 456,
     'covariates': {'constant': '1',
      'contemplation_with_friday': 'exp_friday == 1 and exp_fishing >= 1',
      'not_fishing_last_period': "lagged_choice_1 != 'fishing'"},
     'negative_choice_set': {'friday': ['period < 2', 'exp_fishing == 0']},
     'core_state_space_filters': ["period > 0 and exp_fishing + exp_friday == period and lagged_choice_1 == 'hammock'",
      'period <= 2 and exp_friday != 0',
      'period >= 3 and period - exp_friday < 2',
      'exp_friday > 0 and exp_fishing == 0',
      "exp_friday > 0 and exp_fishing == 1 and lagged_choice_1 == 'fishing'",
      "period - exp_friday == 2 and lagged_choice_1 != 'friday' and period > 2",
      "exp_{choices_w_exp} == 0 and lagged_choice_1 == '{choices_w_exp}'"]}

Now, let us simulate a sample of the new model.

```python
simulate = rp.get_simulate_func(params, options)
```

```python
df = simulate(params)
```

```python
fig, ax = plt.subplots()

df.groupby("Period").Choice.value_counts(normalize=True).unstack().plot.bar(
    stacked=True, ax=ax, color=["C0", "C2", "C1"],
)

plt.xticks(rotation="horizontal")

plt.legend(loc="lower center", bbox_to_anchor=(0.5,-0.275), ncol=3)

plt.show()
plt.close()
```

## Estimation

For model calibration, **respy** supports estimation via maximum likelihood and the method of simulated moments. An appropriate criterion function function for both methods can be constructed in just a few steps.

For optimization of the criterion, the use of external optimization libraries is requried. We recommend [estimagic](https://github.com/OpenSourceEconomics/estimagic), an open-source tool to estimate structural models and more. **estimagic** can be used for the optimization and standard error calculation of a criterion function produced by **respy**.

Unlike other optimization libraries, ``estimagic`` does not optimize over a simple vector of parameters, but instead stores parameters in a ``pd.DataFrame``, which makes it easier to parse them into the quantities we need, store lower and upper bounds together with parameters and express constraints on the parameters.

For ``estimagic``, we need to pass constraints on the parameters in a list containing dictionaries. Each dictionary is a constraint. A constraint includes two components: First, we need to tell ``estimagic`` which parameters we want to constrain. This is achieved by specifying an index location which will be passed to `df.loc`. Then, define the type of the constraint. Here, we only impose the constraint that the shock parameters have to be valid variances and correlations.

*Note*: It is possible to utilize other optimization libraries but we recommend **estimagic** due to the reasons stated above.

```python
from estimagic import maximize
```

```python
crit_func = rp.get_log_like_func(params, options, df)
crit_func(params)

constr = rp.get_parameter_constraints("robinson_crusoe")
```

```python
results = maximize(
    criterion=crit_func,
    params=params,
    algorithm="scipy_lbfgsb",
    algo_options={"stopping_max_criterion_evaluations": 3},
    constraints=constr,
)
```

Running the minimal working example shown above will start an optimization that is limited to three criterion evaluations (for the sake of this tutorial). **estimagic** will also produce a logging database called `logging.db` that stores information about the optimization. The package offers many useful options to set up a tractable estimation for your model.

More information can be found in the **estimagic** documentation: https://estimagic.readthedocs.io/.

## Footnotes

1
    One of the earliest references of Robinsonades in economics can be found in Marx (1867). In the 37th footnote, he mentions that even Ricardo used the theme before him.

## References

> Bellman, R. (1957). Dynamic Programming. *Princeton University Press*, Princeton, NJ.

> Keane, M. P. and Wolpin, K. I. (1997). [The Career Decisions of Young Men](https://doi.org/10.1086/262080). *Journal of Political Economy*, 105(3): 473-522.

> Marx, K. (1867). Das Kapital, Bd. 1. *MEW*, Bd, 23, 405


# Research question: When to Accept or Reject an Offer?

References: J J McCall. Economics of Information and Job Search. The Quarterly Journal of Economics, 84(1):113–126, 1970.

### install the QuantEcon Library

```
!pip install quantecon ###if you use anaconda, type in the command instead: !conda install -y quantecon
```

    Requirement already satisfied: quantecon in /usr/local/lib/python3.6/dist-packages (0.4.8)
    Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from quantecon) (2.23.0)
    Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from quantecon) (1.19.5)
    Requirement already satisfied: sympy in /usr/local/lib/python3.6/dist-packages (from quantecon) (1.1.1)
    Requirement already satisfied: scipy>=1.0.0 in /usr/local/lib/python3.6/dist-packages (from quantecon) (1.4.1)
    Requirement already satisfied: numba>=0.38 in /usr/local/lib/python3.6/dist-packages (from quantecon) (0.48.0)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests->quantecon) (2020.12.5)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests->quantecon) (3.0.4)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests->quantecon) (1.24.3)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests->quantecon) (2.10)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.6/dist-packages (from sympy->quantecon) (1.1.0)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from numba>=0.38->quantecon) (51.3.3)
    Requirement already satisfied: llvmlite<0.32.0,>=0.31.0dev0 in /usr/local/lib/python3.6/dist-packages (from numba>=0.38->quantecon) (0.31.0)

```
!pip install numba
```

    Requirement already satisfied: numba in /usr/local/lib/python3.6/dist-packages (0.48.0)
    Requirement already satisfied: llvmlite<0.32.0,>=0.31.0dev0 in /usr/local/lib/python3.6/dist-packages (from numba) (0.31.0)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from numba) (51.3.3)
    Requirement already satisfied: numpy>=1.15 in /usr/local/lib/python3.6/dist-packages (from numba) (1.19.5)

```
import numpy as np
from numba import jit, float64, jitclass
import matplotlib.pyplot as plt
%matplotlib inline
import quantecon as qe
from quantecon.distributions import BetaBinomial
```

# The Model

### The Objective function

The agent is infinitely lived and aims to maximize the expected discounted sum of earnings $E\sum_{t=0}^{\infty}\beta^{t}y_t$

*   $\beta$ is the discount factor
*   $y_{t}$ is income, equal to the wage $w_{t}$ when employed, and compensation $c$ when unemployed

### The State Value Function

$s_t$ is an i.i.d. random variable called "the State", it is distributed over a set $S$ with probability $p(s)$ for $s\in S$

*   $w_t =w(s_t)$
*   **State value function**: $v^{*}(s)$ denotes the value of the objective function given state s when an agent in this situation makes an optimal decision

*   **The bellman equation**: the state value function must satisfy the following condition
$v^{*}=max[w(s)/(1-\beta),c+\beta\sum_{s'\in S}v*(s')q(s') ]$

### The optimal policy
Choose the strategy that (accept or reject an offer) gives a higher value

$\sigma(s) =1[w(s)/(1-\beta)>c+\beta\sum_{s'\in S}v*(s')q(s') ]$

here $1[P]$ =1 if statement P is true and 0 otherwise

we can also write this as $\sigma(s) =1[w(s)>\overline{w}]$, where $\overline{w}$ is the reservation wage which is a constant depending on β,c,and the wage distribution. From the above optimal strategy, we can see that actually $\overline{w}=(1-\beta)(c+\beta\sum_{s'\in S}v*(s')q(s') )$.

The agent should accept if and only if the current wage offer exceeds the reservation wage.

# Simulation

## Simulate wage distributions

###BetaBinominal distribution: https://en.wikipedia.org/wiki/Beta-binomial_distribution

BetaBinominal(n,a,b) is a distribution on finite support {0, 1, 2, ..,n} with parameter a and b.

Special case:

*  It reduces to the Bernoulli distribution as a special case when n = 1.
*  For a = b = 1, it is the discrete uniform distribution from 0 to n.

Change following two boxes only to simulate the model for your own parameter of choices.

```
n, a, b = 50, 1, 1                        # default parameters
q_default = BetaBinomial(n, a, b).pdf()       # default choice of q
```

```
w_min, w_max = 50, 100
w_default = np.linspace(w_min, w_max, n+1)
```

```
fig, ax = plt.subplots()
ax.plot(w_default, q_default, '-o', label='$q(w(i))$')
ax.set_xlabel('wages')
ax.set_ylabel('probabilities')

plt.show()
```

## Solve for the optimal policy and compute reservation wage

references:  compiling python code with @jit https://numba.pydata.org/numba-doc/latest/user/jit.html

```
mccall_data = [
    ('c', float64),      # unemployment compensation
    ('β', float64),      # discount factor
    ('w', float64[:]),   # array of wage values, w[i] = wage at state i
    ('q', float64[:])    # array of probabilities
]
```

```
@jitclass(mccall_data)
class McCallModel:

    def __init__(self, c=25, β=0.99, w=w_default, q=q_default):

        self.c, self.β = c, β
        self.w, self.q = w_default, q_default

    def state_action_values(self, i, v):
        """
        The values of state-action pairs.
        """
        # Simplify names
        c, β, w, q = self.c, self.β, self.w, self.q
        # Evaluate value for each state-action pair
        # Consider action = accept or reject the current offer
        accept = w[i] / (1 - β)
        reject = c + β * np.sum(v * q)

        return np.array([accept, reject])
```

```
def plot_value_function_seq(mcm, ax, num_plots=6):
    """
    Plot a sequence of value functions.

        * mcm is an instance of McCallModel
        * ax is an axes object that implements a plot method.

    """

    n = len(mcm.w)
    v = mcm.w / (1 - mcm.β)
    v_next = np.empty_like(v)
    for i in range(num_plots):
        ax.plot(mcm.w, v, '-', alpha=0.4, label=f"iterate {i}")
        # Update guess
        for i in range(n):
            v_next[i] = np.max(mcm.state_action_values(i, v))
        v[:] = v_next  # copy contents into v

    ax.legend(loc='lower right')
```

```
mcm = McCallModel()

fig, ax = plt.subplots()
ax.set_xlabel('wage')
ax.set_ylabel('value')
plot_value_function_seq(mcm, ax)
plt.show()
```

```
@jit(nopython=True)
def compute_reservation_wage(mcm,
                             max_iter=500,
                             tol=1e-6):

    # Simplify names
    c, β, w, q = mcm.c, mcm.β, mcm.w, mcm.q

    # == First compute the value function == #

    n = len(w)
    v = w / (1 - β)          # initial guess
    v_next = np.empty_like(v)
    i = 0
    error = tol + 1
    while i < max_iter and error > tol:

        for i in range(n):
            v_next[i] = np.max(mcm.state_action_values(i, v))

        error = np.max(np.abs(v_next - v))
        i += 1

        v[:] = v_next  # copy contents into v

    # == Now compute the reservation wage == #

    return (1 - β) * (c + β * np.sum(v * q))
```

```
compute_reservation_wage(mcm)
```

    92.17437718608396

### change the parameter in the following box to calculate new reservation wage for parameters of your choice.

```
mcm = McCallModel(c=20, β=0.999)
compute_reservation_wage(mcm)
```

    97.67814959177011

## Comparative Statics

### the resevation wage

```
grid_size = 25
R = np.empty((grid_size, grid_size))

c_vals = np.linspace(10.0, 30.0, grid_size) ### change parameter of the unemployment conpensation
β_vals = np.linspace(0.9, 0.99, grid_size) ### change parameter of the discount factors

for i, c in enumerate(c_vals):
    for j, β in enumerate(β_vals):
        mcm = McCallModel(c=c, β=β)
        R[i, j] = compute_reservation_wage(mcm)
```

contour plot :

https://problemsolvingwithpython.com/06-Plotting-with-Matplotlib/06.14-Contour-Plots/

```
fig, ax = plt.subplots()

cs1 = ax.contourf(c_vals, β_vals, R.T, alpha=0.75)
ctr1 = ax.contour(c_vals, β_vals, R.T)

plt.clabel(ctr1, inline=1, fontsize=13)
plt.colorbar(cs1, ax=ax)

ax.set_title("reservation wage")
ax.set_xlabel("$c$", fontsize=16)
ax.set_ylabel("$β$", fontsize=16)

ax.ticklabel_format(useOffset=False)

plt.show()
```

### in the following box, we keep unemployment compensation c fixed, and we discuss how the reservation wage change when the discount factor $\beta$ changes

```
line_lens =100 ### change to value of your choice
c=60 # please change c to value of your choice
β_vals = np.linspace(0, 0.9999, line_lens) # please change the range of beta to value of your choice
R = np.empty((line_lens,1))
for j, β in enumerate(β_vals):
        mcm = McCallModel(c=c, β=β)
        R[j] = compute_reservation_wage(mcm)
```

```
fig, ax = plt.subplots()

cs2 = ax.scatter(β_vals, R)

ax.set_xlabel("$β$", fontsize=16)
ax.set_ylabel("$reservation wage$", fontsize=16)

ax.ticklabel_format(useOffset=False)

plt.show()
```

### in the following box, we keep the discount factor $\beta$ constant, we discuss how the reservation wage change when the unemployment compensation changes

```
line_lens =25 ## change line_lens to value of your choice
β=0.5 ### change beta to value of your choice
c_vals = np.linspace(10, 80, line_lens) ### change c to the range of your choice
R = np.empty((line_lens,1))
for i, c in enumerate(c_vals):
        mcm = McCallModel(c=c, β=β)
        R[i] = compute_reservation_wage(mcm)
```

```
fig, ax = plt.subplots()

cs3 = ax.scatter(c_vals, R)

ax.set_xlabel("$c$", fontsize=16)
ax.set_ylabel("$reservation wage$", fontsize=16)

ax.ticklabel_format(useOffset=False)

plt.show()
```

### the duration of unemploymnet

comparative statics for the unemployment compensation

```
cdf = np.cumsum(q_default)

@jit(nopython=True)
def compute_stopping_time(w_bar, seed=1234):

    np.random.seed(seed)
    t = 1
    while True:
        # Generate a wage draw
        w = w_default[qe.random.draw(cdf)]
        # Stop when the draw is above the reservation wage
        if w >= w_bar:
            stopping_time = t
            break
        else:
            t += 1
    return stopping_time

@jit(nopython=True)
def compute_mean_stopping_time(w_bar, num_reps=100000):
    obs = np.empty(num_reps)
    for i in range(num_reps):
        obs[i] = compute_stopping_time(w_bar, seed=i)
    return obs.mean()

β =0.6 ### change the discount factor beta to value of your choice
c_vals = np.linspace(10, 80, 25) ### change the unemployment compensation to value of your choice

stop_times = np.empty_like(c_vals)
for i, c in enumerate(c_vals):
    mcm = McCallModel(c=c, β=β)
    w_bar = compute_reservation_wage(mcm)
    stop_times[i] = compute_mean_stopping_time(w_bar)

fig, ax = plt.subplots()

ax.plot(c_vals, stop_times, label="mean unemployment duration")
ax.set(xlabel="unemployment compensation", ylabel="months")
ax.legend()

plt.show()
```

comparative statics for the discount factor

```
cdf = np.cumsum(q_default)

@jit(nopython=True)
def compute_stopping_time(w_bar, seed=1234):

    np.random.seed(seed)
    t = 1
    while True:
        # Generate a wage draw
        w = w_default[qe.random.draw(cdf)]
        # Stop when the draw is above the reservation wage
        if w >= w_bar:
            stopping_time = t
            break
        else:
            t += 1
    return stopping_time

@jit(nopython=True)
def compute_mean_stopping_time(w_bar, num_reps=100000):
    obs = np.empty(num_reps)
    for i in range(num_reps):
        obs[i] = compute_stopping_time(w_bar, seed=i)
    return obs.mean()

c=60 ### change the unemployment compensation c to values of your choice
β_vals = np.linspace(0, 0.99, 100) ### change discount factor beta to range of your choice.
stop_times = np.empty_like(β_vals)
for j, β in enumerate(β_vals):
    mcm = McCallModel(c=c, β=β)
    w_bar = compute_reservation_wage(mcm)
    stop_times[j] = compute_mean_stopping_time(w_bar)

fig, ax = plt.subplots()

ax.plot(β_vals, stop_times, label="discount factor")
ax.set(xlabel="discount factor", ylabel="months")
ax.legend()

plt.show()
```

```

```


```python
import os
import sys
jupyter_module_path = os.path.join( os.getcwd(), '..', '..' )
sys.path.append( jupyter_module_path )
jupyter_module_path = os.path.join( os.getcwd(), '..', '..', 'EfJupyter' )
sys.path.append( jupyter_module_path )

from EfJupyter import EfConf
```

In this example, exact analytical trajectory of a charged particle moving in a uniform electric field is compared with results of numerical simulation.

In uniform electric field charged particle moves with uniform acceleration, directed along the field.

\begin{align}
& \mathbf{v} = \mathbf{v}_0 + \frac{q}{m} \mathbf{E}_0 t
\\
& \mathbf{r} = \mathbf{r}_0 + \mathbf{v}_0 t + \frac{1}{2} \frac{q}{m} \mathbf{E}_0 t^2
\end{align}

Part of a config file, regarding single particle, is similar to
['singe particle in free space'](https://github.com/epicf/ef/wiki/Ex1:-Single-Particle-In-Free-Space).

_поменять на равномерное поле_

As of uniform electric field, currently ef can not use any analytical expressions for fields, nor load precomputed numerical fields. However, a good approximation for uniform field is field between two parallel charged plates.
The simplest way to model this in ef is to specify appropriate boundary conditions. We need nonzero potential difference on two opposing sides of the computational volume.

    [Boundary conditions]
    boundary_phi_left = 0.0
    boundary_phi_right = 0.0
    boundary_phi_bottom = 0.0
    boundary_phi_top = 0.0
    boundary_phi_near = 3.0
    boundary_phi_far = -3.0

To ensure a good degree of field uniformity, we need distance between two "plates" (i.e. sides of the computational volume with potential difference) to be much less than size of the plates

    [Spatial mesh]
    grid_x_size = 10.0
    grid_x_step = 0.25
    grid_y_size = 10.0
    grid_y_step = 0.25
    grid_z_size = 1.0
    grid_z_step = 0.01

Initial position of the particle had to be adjusted:

    [Particle_source_box.emit_single_particle]
    ...
    box_x_left = 5.00
    box_x_right = 5.01
    box_y_bottom = 5.00
    box_y_top = 5.01
    box_z_near = 0.10
    box_z_far = 0.11
    ...

After the simulation, it is useful to glance over potential distribution in the volume.
3d figure is not very informative.
Use 2d colormaps at XY and XZ planes at nodes near particle initial position.
Relevant plotting script is [link].

The results are

Electric field is directed perpendicular to equipotential surfaces. It can be seen, that it
the middle of computational region electric field is directed along Z.

```python

```

Trajectories are plotted similarly to previous examples.
Magnitude of electric field equals potential difference divided by distance between plates:
[E_0 = - delta phi / d ][eqn]

Figures of trajectories: (todo: something wrong with kin. energy.)

Degree of field uniformity can be estimated by evaluating difference
of field components near initial and final points of particle trajectory:
[code]

For present choice of parameters:

    ( Exf - Exi ) / Ez0 = ,
    ( Eyf - Eyi ) / Ez0 = ,
    ( Ezf - Ezi ) / Ez0 =

It should not be very significant.

```python

```


# Description:

* calculations for modeling fragments in a CsCl gradient under non-equilibrium conditions

# Notes

* Good chapter on determining G+C content from CsCl gradient analysis
  * http://www.academia.edu/428160/Using_Analytical_Ultracentrifugation_of_DNA_in_CsCl_Gradients_to_Explore_Large-Scale_Properties_of_Genomes

* http://www.analyticalultracentrifugation.com/dynamic_density_gradients.htm

* Meselson et al. - 1957 - Equilibrium Sedimentation of Macromolecules in Den

* Vinograd et al. - 1963 - Band-Centrifugation of Macromolecules and Viruses

* http://onlinelibrary.wiley.com.proxy.library.cornell.edu/doi/10.1002/bip.360101011/pdf

## Ultracentrigation book
http://books.google.com/books?hl=en&lr=&id=vxcSBQAAQBAJ&oi=fnd&pg=PA143&dq=Measurement+of+Density+Heterogeneity+by+Sedimentation+in&ots=l8ObYN-zVv&sig=Vcldf9_aqrJ-u7nQ1lBRKbknHps#v=onepage&q&f=false

## Forum info
* http://stackoverflow.com/questions/18624005/how-do-i-perform-a-convolution-in-python-with-a-variable-width-gaussian
* http://timstaley.co.uk/posts/convolving-pdfs-in-python/

## Possible workflows:

### KDE convolution
* KDE of fragment GC values
* bandwidth cross validation: https://jakevdp.github.io/blog/2013/12/01/kernel-density-estimation/
* convolution of KDE with diffusion function:
	* gaussian w/ mean of 0 and scale param = 44.5 (kb) / (mean fragment length)
		* http://www.academia.edu/428160/Using_Analytical_Ultracentrifugation_of_DNA_in_CsCl_Gradients_to_Explore_Large-Scale_Properties_of_Genomes
	* http://nbviewer.ipython.org/github/timstaley/ipython-notebooks/blob/compiled/probabilistic_programming/convolving_distributions_illustration.ipynb

## variable KDE
* variable KDE of fragment GC values where kernel sigma is determined by mean fragment length
	* gaussian w/ scale param = 44.5 (kb) / fragment length

# Standard deviation of homogeneous DNA fragments

Vinograd et al., 1963; (band-centrifugation):

\begin{align}
\sigma^2 = \frac{r_0}{r_0^0} \left\{ \frac{r_0}{r_0^0} + 2D \left( t - t^0 \right) \right\}
\end{align}

## Standard deviation of Gaussian band (assuming equilibrium), Meselson et al., 1957:

\begin{align}
\sigma^2 = -\sqrt{w} \\
w = \textrm{molecular weight}
\end{align}

## Standard deviation of Gaussian band at a given time, Meselson et al., 1957:

\begin{equation}
t^* = \frac{\sigma^2}{D} \left(ln \frac{L}{\sigma} + 1.26 \right), \quad L\gg\sigma \\
\sigma^2 = \textrm{stdev at equilibrium} \\
L = \textrm{length of column}
\end{equation}

* Gaussian within 1% of equillibrium value from center.
* ! assumes density gradient established at t = 0

### Alternative form (from Birne and Rickwood 1978; eq 6.22):

\begin{align}
t = \frac{\beta^{\circ}(p_p - p_m)}{w^4 r_p^2 s} \left(1.26 + ln \frac{r_b - r_t}{\sigma}\right)
\end{align}

\begin{equation}
t = \textrm{time in seconds} \\
\beta^{\circ} = \beta^{\circ} \textrm{ of salt forming the density gradient (CsCl = ?)} \\
p_p = \textrm{buoyant density of the the particle at equilibrium} \\
p_m = \textrm{average density of the medium} \\
w = \textrm{angular velocity} \\
r_p = \textrm{distance (cm) of particle from from the axis of rotation (at equilibrium)} \\
s = \textrm{sedimentation rate} (S_{20,w} * 10^{-13}) \\
r_b = \textrm{distance to top of gradient (cm)} \\
r_t = \textrm{distance to bottom of gradient (cm)} \\
r_b - r_t = \textrm{length of gradient (L)}
\end{equation}

### Solving for sigma:

\begin{align}
\sigma = \frac{L}{e^{\left(\frac{t w^4 r_p^2 s}{\beta^{\circ}(p_p - p_m)} - 1.26\right)}}
\end{align}

### sigma (alternative; but assuming sedimentation equilibrium reached; no time component)

\begin{align}
{\sigma} = \frac{\theta}{M_{app}} \frac{RT}{ \frac{w^2r_c}{\beta} * w^2r_o }
\end{align}

\begin{equation}
{\theta} = \textrm{buoyant dnesity of the macromolecules} \\
M_{app} = \textrm{apparent molecular weight oif the solvated macromolecules} \\
R = \textrm{universal gas constant} \\
T = \textrm{Temperate in K} \\
w = \textrm{angular velocity} \\
\beta^{\circ} = \beta^{\circ} \textrm{ coef. of salt forming the density gradient} \\
r_c = \textrm{isoconcentration point} \\
r_o = \textrm{distance (cm) of particle from from the axis of rotation (at equilibrium)} \\
\end{equation}

## Clay et al., 2003 method (assumes sedimentation equilibrium)

\begin{align}
\sigma = \sqrt{\frac{\rho R T}{B^2 G M_C l}}
\end{align}

\begin{equation}
{\rho} = \textrm{buoyant dnesity of the macromolecules} \\
R = \textrm{universal gas constant} \\
T = \textrm{Temperate in K} \\
\beta = \beta^{\circ} \textrm{ coef. of salt forming the density gradient} \\
M_C =  \textrm{molecular weight per base pair of dry cesium DNA} \\
G = \textrm{Constant from Clay et al., 2003 (7.87x10^-10) } \\
l = \textrm{fragment length (bp)} \\
\end{equation}

# Variables specific to the Buckley lab setup

\begin{equation}
\omega = (2\pi \times \textrm{RPM}) /60, \quad \textrm{RPM} = 55000 \\
\beta^{\circ} = 1.14 \times 10^9 \\
r_b = 4.85 \\
r_t = 2.6 \\
L = r_b - r_t \\
s = S_{20,w} * 10^{-13} \\
S_{20,w} = 2.8 + 0.00834 * (l*666)^{0.479}, \quad \textrm{where l = length of fragment; S in Svedberg units} \\
p_m = 1.7 \\
p_p = \textrm{buoyant density of the particle in CsCl} \\
r_p = ? \\
t = \textrm{independent variable}
\end{equation}

__isoconcentration point__

\begin{equation}
r_c = \sqrt{(r_t^2 + r_t * r_b + r_b^2)/3}
\end{equation}

__rp in relation to the particle's buoyant density__

\begin{equation}
r_p = \sqrt{ ((p_p-p_m)\frac{2\beta^{\circ}}{w}) + r_c^2 } \\
p_p = \textrm{buoyant density}
\end{equation}

__buoyant density of a DNA fragment in CsCl__

\begin{equation}
p_p = 0.098F + 1.66, \quad \textrm{where F = G+C molar fraction}
\end{equation}

__info needed on a DNA fragment to determine it's sigma of the Guassian distribution__

* fragment length
* fragment G+C

# Coding equations

```python
%load_ext rpy2.ipython
```

    The rpy2.ipython extension is already loaded. To reload it, use:
      %reload_ext rpy2.ipython

```r
%%R
library(dplyr)
library(tidyr)
library(ggplot2)
library(gridExtra)
```

```r
%%R

GC2MW = function(x){
    A = 313.2
    T = 304.2
    C = 289.2
    G = 329.2
    GC = G + C
    AT = A + T
    x = x / 100
    x*GC + (1-x)*AT
}

GC2BD = function(GC){
    # GC = percentage
    BD = GC / 100 * 0.098 + 1.66
    return(BD)
}

calc_BD_macro = function(p_i, w, B, r)

rpm2w2 = function(rpm){
    x = 2 * pi * rpm / 60
    return(x**2)
}

calc_R_c = function(r_t, r_b){
    x = r_t**2 + r_t * r_b + r_b**2
    return(sqrt(x/3))
}

calc_R_p = function(p_p, p_m, B, w, r_c){
    # distance of the particle from the axis of rotation (at equilibrium)
    x = ((p_p - p_m) * (2 * B / w)) + r_c**2
    return(sqrt(x))
}

calc_S = function(l, GC){
    # l = dsDNA length (bp)
    MW = GC2MW(GC)
    S = 0.00834 * (l * MW)**0.479 + 2.8
    S = S * 1e-13
    return(S)
}

calc_dif_sigma_OLD = function(L, w, r_p, S, t, B, p_p, p_m){
    nom = w**2 * r_p**2 * S
    denom = B * (p_p - p_m)
    x = nom / denom * t - 1.26
    sigma = L / exp(x)
    return(sigma)
}

calc_dif_sigma = function(L, w, r_c, S, t, B, p_p, p_m){
    nom = w**2 * r_c**2 * S
    denom = B * (p_p - p_m)
    x = nom / denom * t - 1.26
    sigma = L / exp(x)
    return(sigma)
}

R_p2BD = function(r_p, p_m, B, w, r_c){
    # converting a distance from center of rotation of a particle to buoyant density
    ## inverse of `calc_R_p`
    nom = (r_p**2 - r_c**2) * w
    return(nom / (2 * B) + p_m)
}

sigma2BD = function(r_p, sigma, p_m, B, w, r_c){
    BD_low = R_p2BD(r_p - sigma, p_m, B, w, r_c)
    BD_high = R_p2BD(r_p + sigma, p_m, B, w, r_c)
    return(BD_high - BD_low)
}

time2eq = function(B, p_p, p_m, w, r_c, s, L, sigma){
    x = (B * (p_p - p_m)) / (w**2 * r_c**2 * s)
    y = 1.26 + log(L / sigma)
    return(x * y)
}

```

# Time to equilibrium

```r
%%R -w 450 -h 300
# time to eq

calc_time2eq = function(x, B, L, rpm, r_t, r_b, sigma, p_m){
    l = x[1]
    GC = x[2]
    s = calc_S(l, GC)
    w = rpm2w2(rpm)
    p_p = GC2BD(GC)
    r_c = calc_R_c(r_t, r_b)
    #r_p = calc_R_p(p_p, p_m, B, w, r_c)
    t = time2eq(B, p_p, p_m, w, r_c, s, L, sigma)
    t = t / 360
    return(t)
}

rpm = 55000
B = 1.14e9
r_b = 4.85
r_t = 2.6
L = r_b - r_t
p_m = 1.7

l = seq(100,20000,100)          # bp
GC = 1:100               # percent
sigma = 0.01

df = expand.grid(l, GC)
df$t = apply(df, 1, calc_time2eq, B=B, L=L, rpm=rpm, r_t=r_t, r_b=r_b, sigma=sigma, p_m=p_m)
colnames(df) = c('length', 'GC', 'time')
df %>% head

cols = rev(rainbow(12))
p1 = ggplot(df, aes(GC, length, fill=time)) +
    geom_tile() +
    scale_x_continuous(expand=c(0,0)) +
    scale_y_continuous(expand=c(0,0)) +
    scale_fill_gradientn(colors=cols) +
    geom_hline(yintercept=4000, linetype='dashed', color='black') +
    #geom_vline(xintercept=60*60*66, linetype='dashed', color='black') +
    labs(x='GC (%)', y='dsDNA length (bp)') +
    theme_bw() +
    theme(
        text = element_text(size=16)
    )

p1
```

# sigma as a function of time & fragment length

```r
%%R
rpm = 55000
B = 1.14e9
r_b = 4.85
r_t = 2.6
L = r_b - r_t
p_m = 1.7

l = 500          # bp
GC = 50           # pebrcent
t = 60 * 60 * 66     # sec

S = calc_S(l, GC)
w2 = rpm2w2(rpm)
p_p = GC2BD(GC)
r_c = calc_R_c(r_t, r_b)
r_p = calc_R_p(p_p, p_m, B, w2, r_c)
sigma = calc_dif_sigma(L, w2, r_p, S, t, B, p_p, p_m)
print(sigma)
#sigma_BD = sigma2BD(r_p, sigma, p_m, B, w2, r_c)
#print(sigma_BD)
```

    [1] 9.800228e-105

```r
%%R

#-- alternative calculation
p_p = 1.7

M = l * 882
R = 8.3144598   #J mol^-1 K^-1
T = 293.15

calc_stdev(p_p, M, R, T, w2, r_c, B, r_p)
```

    [1] 6.665251e-10

# Graphing sigma as a function of time & fragment length

```r
%%R -h 300 -w 850
calc_sigma_BD = function(x, rpm, GC, r_t, r_b, p_m, B, L){
    l = x[1]
    t = x[2]
    S = calc_S(l, GC)
    w2 = rpm2w2(rpm)
    p_p = GC2BD(GC)
    r_c = calc_R_c(r_t, r_b)
    r_p = calc_R_p(p_p, p_m, B, w2, r_c)
    sigma = calc_dif_sigma(L, w2, r_p, S, t, B, p_p, p_m)
    if (sigma > L){
        return(NA)
    } else {
        return(sigma)
    }
}

# params
GC = 50
rpm = 55000
B = 1.14e9
r_b = 4.85
r_t = 2.6
L = r_b - r_t
p_m = 1.66

# pairwise calculations of all parameters
l = 50**seq(1,3, by=0.05)
t = 6**seq(3,8, by=0.05)
df = expand.grid(l, t)
df$sigma = apply(df, 1, calc_sigma_BD, rpm=rpm, GC=GC, r_t=r_t, r_b=r_b, p_m=p_m, B=B, L=L)
colnames(df) = c('length', 'time', 'sigma')
df= df %>%
    mutate(sigma = ifelse((sigma < 1e-20 | sigma > 1e20), NA, sigma))

# plotting
cols = rev(rainbow(12))
p1 = ggplot(df, aes(time, length, fill=sigma)) +
    geom_tile() +
    scale_x_log10(expand=c(0,0)) +
    scale_y_log10(expand=c(0,0)) +
    scale_fill_gradientn(colors=cols) +
    #geom_hline(yintercept=4000, linetype='dashed', color='black') +
    geom_vline(xintercept=60*60*66, linetype='dashed', color='black') +
    labs(x='Time', y='Length') +
    theme_bw() +
    theme(
        text = element_text(size=16)
    )

p2 = p1 + scale_fill_gradientn(colors=cols, trans='log10')
grid.arrange(p1, p2, ncol=2)
```

### Low GC

```r
%%R -h 300 -w 850

# params
GC = 20
rpm = 55000
B = 1.14e9
r_b = 4.85
r_t = 2.6
L = r_b - r_t
p_m = 1.66

# pairwise calculations of all parameters
l = 50**seq(1,3, by=0.05)
t = 6**seq(3,8, by=0.05)
df = expand.grid(l, t)
df$sigma = apply(df, 1, calc_sigma_BD, rpm=rpm, GC=GC, r_t=r_t, r_b=r_b, p_m=p_m, B=B, L=L)
colnames(df) = c('length', 'time', 'sigma')
df= df %>%
    mutate(sigma = ifelse((sigma < 1e-20 | sigma > 1e20), NA, sigma))

# plotting
cols = rev(rainbow(12))
p1 = ggplot(df, aes(time, length, fill=sigma)) +
    geom_tile() +
    scale_x_log10(expand=c(0,0)) +
    scale_y_log10(expand=c(0,0)) +
    scale_fill_gradientn(colors=cols) +
    #geom_hline(yintercept=4000, linetype='dashed', color='black') +
    geom_vline(xintercept=60*60*66, linetype='dashed', color='black') +
    labs(x='Time', y='Length') +
    theme_bw() +
    theme(
        text = element_text(size=16)
    )

p2 = p1 + scale_fill_gradientn(colors=cols, trans='log10')
grid.arrange(p1, p2, ncol=2)
```

### High GC

```r
%%R -h 300 -w 850

# params
GC = 80
rpm = 55000
B = 1.14e9
r_b = 4.85
r_t = 2.6
L = r_b - r_t
p_m = 1.66

# pairwise calculations of all parameters
l = 50**seq(1,3, by=0.05)
t = 6**seq(3,8, by=0.05)
df = expand.grid(l, t)
df$sigma = apply(df, 1, calc_sigma_BD, rpm=rpm, GC=GC, r_t=r_t, r_b=r_b, p_m=p_m, B=B, L=L)
colnames(df) = c('length', 'time', 'sigma')
df= df %>%
    mutate(sigma = ifelse((sigma < 1e-20 | sigma > 1e20), NA, sigma))

# plotting
cols = rev(rainbow(12))
p1 = ggplot(df, aes(time, length, fill=sigma)) +
    geom_tile() +
    scale_x_log10(expand=c(0,0)) +
    scale_y_log10(expand=c(0,0)) +
    scale_fill_gradientn(colors=cols) +
    #geom_hline(yintercept=4000, linetype='dashed', color='black') +
    geom_vline(xintercept=60*60*66, linetype='dashed', color='black') +
    labs(x='Time', y='Length') +
    theme_bw() +
    theme(
        text = element_text(size=16)
    )

p2 = p1 + scale_fill_gradientn(colors=cols, trans='log10')
grid.arrange(p1, p2, ncol=2)
```

## Plotting Clay et al,. method

```r
%%R
calc_dif_sigma_Clay = function(rho, R, T, B, G, M, l){
    sigma = sqrt((rho*R*T)/(B**2*G*M*l))
    return(sigma)
}
```

```r
%%R -w 850 -h 300

wrap_calc_sigma_Clay = function(x, R, T, B, G, m){
    l= x[1]
    GC = x[2]
    rho = GC2BD(GC)
    sigma = calc_dif_sigma_Clay(rho, R, T, B, G, m, l)
    return(sigma)
}

# params
R = 8.3145e7
T = 293.15
G = 7.87e-10
M = 882
B = 1.14e9

l = 50**seq(1,3, by=0.05)
GC = 1:100

# pairwise calculations of all parameters

df = expand.grid(l, GC)

df$sigma = apply(df, 1, wrap_calc_sigma_Clay, R=R, T=T, B=B, G=G, m=M)
colnames(df) = c('length', 'GC', 'sigma')

# plotting
cols = rev(rainbow(12))
p1 = ggplot(df, aes(GC, length, fill=sigma)) +
    geom_tile() +
    scale_y_log10(expand=c(0,0)) +
    scale_x_continuous(expand=c(0,0)) +
    scale_fill_gradientn(colors=cols) +
    labs(y='length (bp)', x='G+C') +
    theme_bw() +
    theme(
        text = element_text(size=16)
    )

p2 = p1 + scale_fill_gradientn(colors=cols, trans='log10')
grid.arrange(p1, p2, ncol=2)
```

```python

```

```python

```

# --Sandbox--

# Graphing the equations above

```python
%pylab inline
```

```python
import scipy as sp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mixture
#import sklearn.mixture as mixture
```

## Generating fragments

```python
n_frags = 10000
frag_GC = np.random.normal(0.5,0.1,n_frags)
frag_GC[frag_GC < 0] = 0
frag_GC[frag_GC > 1] = 1
frag_len = np.random.normal(10000,1000,n_frags)
```

```python
ret = plt.hist2d(frag_GC, frag_len, bins=100)
```

## Setting variables

```python
RPM = 55000
omega = (2 * np.pi * RPM) / 60

beta_o = 1.14 * 10**9

radius_bottom = 4.85
radius_top = 2.6
col_len = radius_bottom - radius_top

density_medium = 1.7
```

## Calculation functions

```python
# BD from GC
frag_BD = 0.098 * frag_GC + 1.66

ret = plt.hist(frag_BD, bins=100)
```

```python
sedimentation = (frag_len*666)**0.479 * 0.00834 + 2.8   # l = length of fragment

ret = plt.hist(sedimentation, bins=100)
```

```python
# sedimentation as a function of fragment length
len_range = np.arange(1,10000, 100)

ret = plt.scatter(len_range, 2.8 + 0.00834 * (len_range*666)**0.479 )
```

```python
# isoconcentration point
iso_point = sqrt((radius_top**2 + radius_top * radius_bottom + radius_bottom**2)/3)
iso_point
```

```python
# radius of particle

#radius_particle = np.sqrt( (frag_BD - density_medium)*2*(beta_o/omega) + iso_point**2 )

#ret = plt.hist(radius_particle)
```

```python

```

# Testing out speed of mixture models

```python
n_dists = 10
n_samp = 10000
```

```python
def make_mm(n_dists):
    dist_loc = np.random.uniform(0,1,n_dists)
    dist_scale = np.random.uniform(0,0.1, n_dists)
    dists = [mixture.NormalDistribution(x,y) for x,y in zip(dist_loc, dist_scale)]
    eq_weights = np.array([1.0 / n_dists] * n_dists)
    eq_weights[0] += 1.0 - np.sum(eq_weights)
    return mixture.MixtureModel(n_dists, eq_weights, dists)
```

```python
mm = make_mm(n_dists)
```

```python
%%timeit
smp = mm.sampleDataSet(n_samp).getInternalFeature(0).flatten()
```

```python
%%timeit
smp = np.array([mm.sample() for i in arange(n_samp)])
```

```python
n_dists = 1000
mm = make_mm(n_dists)
```

```python
%%timeit
smp = mm.sampleDataSet(n_samp).getInternalFeature(0).flatten()
```

```python
%%timeit
smp = np.array([mm.sample() for i in arange(n_samp)])
```

```python
n_dists = 10000
mm = make_mm(n_dists)
```

```python
%%timeit
smp = mm.sampleDataSet(n_samp).getInternalFeature(0).flatten()
```

```python
%%timeit
smp = np.array([mm.sample() for i in arange(n_samp)])
```

```python
n_samp = 100000
```

```python
%%timeit
smp = mm.sampleDataSet(n_samp).getInternalFeature(0).flatten()
```

```python
%%timeit
smp = np.array([mm.sample() for i in arange(n_samp)])
```

__Notes:__

* a mixture model with many distributions (>1000) is very slow for sampling

```python
x = np.random.normal(3, 1, 100)
y = np.random.normal(1, 1, 100)
H, xedges, yedges = np.histogram2d(y, x, bins=100)
```

```python
H
```

***
***

# Workflow for modeling DNA fragment locations in a gradient

For each genome in mock community, simulate N fragments and calculate their Guassian distributions in the gradient.
Create a mixture model of those Guassian distributions to sample Aa fragments,
where Aa = the absolute abundance of the taxon in the mock community.
One mixture model per genome.

## User defined:

* Rotor specs
* cfg parameters (RPM, time)

## Generate fragment density distributions

* For each genome in the mock community:
  * Simulate fragments
  * Calculate sigma of Gaussian density distribution
  * Create mixture model from all Gaussians of the fragments

## Simulate fraction communities

* For each genome in mock community:
  * sample fragments from mixture model based on total abundance of taxon in mock community
  * bin fragments into gradient fractions

```python

```