Table of Contents

1  Two-state promoter (protein)

1.1  Import mRNA moments

1.2  Zeroth moment two-state promoter

1.3  First moment two-state promoter (protein)

1.4  mRNA-protein correlation two-state promoter

1.5  Second moment two-state promoter

1.6  Third moment two-state promoter $\ee{\bb{p}^3}$

1.6.1  $\ee{\bb{m}^2\bb{p}}$

1.6.2  $\ee{\bb{mp}^2}$

1.6.3  $\ee{\bb{p}^3}$

1.7  Export lambdify functions to dill objects

2  Three-state promoter (protein)

2.1  Zeroth moment three-state promoter

2.2  First moment two-state promoter (protein)

2.2.1  fold-change.

2.3  mRNA-protein correlation three-state promoter

2.4  Second moment three-state promoter

2.5  Third moment three-state promoter $\ee{\bb{p}^3}$

2.5.1  $\ee{\bb{m}^2\bb{p}}$

2.5.2  $\ee{\bb{mp}^2}$

2.5.3  $\ee{\bb{p}^3}$

2.6  Export lambdify functions to dill objects

# The moments of the distribution for protein in a two- and three-state promoter.

(c) 2017 Manuel Razo. This work is licensed under a [Creative Commons Attribution License CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). All code contained herein is licensed under an [MIT license](https://opensource.org/licenses/MIT)

```python
import pickle
import dill # Library that we will use to export lambdify functions

# To compute symbolic expressions
import sympy
sympy.init_printing(use_unicode=True, use_latex=True) # print outputs in LaTeX

# Import matplotlib stuff for plotting
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib as mpl

# Seaborn, useful for graphics
import seaborn as sns

# Import the utils for this project
import chann_cap_utils as chann_cap

# Set PBoC plotting style
# chann_cap.set_plotting_style()

# Magic function to make matplotlib inline; other style specs must come AFTER
%matplotlib inline

# This enables SVG graphics inline (only use with static plots (non-Bokeh))
%config InlineBackend.figure_format = 'svg'

# figdir = '../../fig/chemical_master_analytic_mRNA/'
tmpdir = '../../tmp/'
```

# $\LaTeX$ macros
$\newcommand{kpon}{k^p_{\text{on}}}$
$\newcommand{kpoff}{k^p_{\text{off}}}$
$\newcommand{kron}{k^r_{\text{on}}}$
$\newcommand{kroff}{k^r_{\text{off}}}$
$\newcommand{rm}{r _m}$
$\newcommand{gm}{\gamma _m}$
$\newcommand{mm}{\left\langle m \right\rangle}$
$\newcommand{ee}[1]{\left\langle #1 \right\rangle}$
$\newcommand{bb}[1]{\mathbf{#1}}$

## Two-state promoter (protein)

In this section we will perform the linear algebra to solve for the steady-state moments of the protein distribution.

Up to this stage we wrote the chemical master equation in matrix notation and did some tricks when it came to compute the $n^{\text{th}}$ moment. Now we will use the equations we obtained for the time derivative of the protein distribution moments and set that to steady-state to solve for the moments themselves.

Let's first define the `sympy` variables that we will need for the two- and three-state promoter.

```python
# Define rate constant variables
kp_off, kp_on = sympy.symbols('{k_{off}^{(p)}} {k_{on}^{(p)}}')
kr_off, kr_on = sympy.symbols('{k_{off}^{(r)}} {k_{on}^{(r)}}')

# Define degradation rate and production rate
rm, gm = sympy.symbols('r_m gamma_m')
rp, gp = sympy.symbols('r_p gamma_p')

kp_off, kp_on, kr_off, kr_on, rm, gm, rp, gp
```

Let's now define a function that generates a `sympy` variable of the form $\left\langle x^n \right\rangle$ to easily define the variables to save the moments.

```python
def moment_sympy(var, moment):
    '''
    Shortcut to generate variables of the form <var**n> in sympy to facilitate the
    definition of the moment variables
    Parameters
    ----------
    var : string.
        variable name string that should be put inside the <> parenthesis.
    moment : int.
        exponent of the moment to be defined.
    '''
    if moment == None:
        return sympy.Symbol('{\\langle ' + var + '\\rangle}')
    else:
        return sympy.Symbol('{\\langle ' + var + '^{:d}'.format(moment) +\
                            '\\rangle}')
```

Now let's define the moment variables for the unregulated promoter. We will do it for both the mRNA and the protein since the protein moments depend also on mRNA moments.

```python
# Define the string for the states
m_unreg = ['m_{P_{unreg}}', 'm_{E_{unreg}}']

# Define the zeroth moments
m0_unreg = [moment_sympy(x, 0) for x in m_unreg]
# Define the first moments
m1_unreg = [moment_sympy(x, 1) for x in m_unreg]
# Define the second moments
m2_unreg = [moment_sympy(x, 2) for x in m_unreg]
# Define the second moments
m3_unreg = [moment_sympy(x, 3) for x in m_unreg]

m0_unreg, m1_unreg, m2_unreg, m3_unreg
```

```python
# Define the string for the states
p_unreg = ['p_{P_{unreg}}', 'p_{E_{unreg}}']

# Define the zeroth moments
p0_unreg = [moment_sympy(x, 0) for x in p_unreg]
# Define the first moments
p1_unreg = [moment_sympy(x, 1) for x in p_unreg]
# Define the second moments
p2_unreg = [moment_sympy(x, 2) for x in p_unreg]
# Define the second moments
p3_unreg = [moment_sympy(x, 3) for x in p_unreg]

p0_unreg, p1_unreg, p2_unreg, p3_unreg
```

```python
# define "global" moments and cross correlations
mm0_unreg = moment_sympy('\mathbf{m}_u', 0)
mm1_unreg = moment_sympy('\mathbf{m}_u', None)
mm2_unreg = moment_sympy('\mathbf{m}_u', 2)
mm3_unreg = moment_sympy('\mathbf{m}_u', 3)
pp0_unreg = moment_sympy('\mathbf{p}_u', 0)
pp1_unreg = moment_sympy('\mathbf{p}_u', None)
pp2_unreg = moment_sympy('\mathbf{p}_u', 2)
pp3_unreg = moment_sympy('\mathbf{p}_u', 3)

# Cross corelations
mmpp_unreg = moment_sympy('\mathbf{mp}_u', None)
mm2pp_unreg = moment_sympy('\mathbf{m}^2\mathbf{p}_u', None)
mmpp2_unreg = moment_sympy('\mathbf{m}\mathbf{p}^2_u', None)

mm0_unreg, mm1_unreg, mm2_unreg, mm3_unreg,\
pp0_unreg, pp1_unreg, pp2_unreg, pp3_unreg,\
mmpp_unreg, mm2pp_unreg, mmpp2_unreg
```

Now we will define the state transition matrix $\mathbf{K}^u$, the mRNA production matrix $\mathbf{R}^u_m$ and the mRNA degradation matrix $\mathbf{\Gamma}^u_m$.

```python
# Define the rate constant matrix
Km_unreg = sympy.Matrix([[-kp_off, kp_on],
                           [kp_off, -kp_on]])
# Define the mRNA production matrix
Rm_unreg = sympy.Matrix([[rm, 0],
                         [0, 0]])
# Define the mRNA degradation matrix
Gm_unreg = sympy.Matrix([[gm, 0],
                         [0, gm]])

# Define the protein production matrix
Rp_unreg = sympy.Matrix([[rp, 0],
                         [0, rp]])
# Define the protein degradation matrix
Gp_unreg = sympy.Matrix([[gp, 0],
                         [0, gp]])

Km_unreg, Rm_unreg, Gm_unreg, Rp_unreg, Gp_unreg
```

### Import mRNA moments
Let's import the solutions we obtained for the mRNA moments that are saved in a `pickle` object.

```python
with open(tmpdir + 'two_state_mRNA_solutions.pkl', 'rb') as file:
    zero_sol_unreg_m = pickle.load(file)
    first_sol_unreg_m = pickle.load(file)
    second_sol_unreg_m = pickle.load(file)
    third_sol_unreg_m = pickle.load(file)
```

We now import the expressions of the moments in termos of the global moments

```python
with open(tmpdir + 'two_state_mRNA_moments.pkl', 'rb') as file:
    first_unreg_moments_m = pickle.load(file)
    second_unreg_moments_m = pickle.load(file)
    third_unreg_moments_m = pickle.load(file)
```

### Zeroth moment two-state promoter
In order to solve for the two-state promoter we have two equations to use
$$
1 = \mathbf{u} \cdot \left\langle \mathbf{m}^0 \right\rangle,
$$
where $u = (1, 1)$ and $\left\langle \mathbf{m}^0 \right\rangle$ is the vector containing all the states, and
$$
\mathbf{0} = \mathbf{K}^u_m \left\langle \mathbf{m}^0 \right\rangle.
$$

We will use the linear algebra solver where we write an extended matrix with the last column being the right hand side of these equations.

Let's now define this extended matrix.

```python
# Add the extra row for the normalization constraint
K_sys_unreg = Km_unreg.row_insert(0, sympy.Matrix([1, 1]).transpose())

# Define right-side array
b = sympy.Matrix([1, 0, 0])

# Define augmented matrix
zero_aug_unreg = K_sys_unreg.col_insert(2, b)
zero_aug_unreg
```

Now we solve the equation for the two state moments.

```python
# Solve linear system for the three zeroth moments
zero_sol_unreg = sympy.solve_linear_system(zero_aug_unreg, *m0_unreg)
zero_sol_unreg
```

### First moment two-state promoter (protein)

Now that we have the zeroth moment that indicates the probability of being on either state let's now compute the first moment. For this we have the matrix equation
$$
\mathbf{0} = \left( \mathbf{K}^u - \mathbf{\Gamma}^u_p  \right)
             + \mathbf{R}^u_p \left\langle \mathbf{m} \right\rangle.
$$

Let's write this system in the form of the augmented matrix.

```python
# Define augmented matrix for first moment
first_aug_unreg = (Km_unreg - Gp_unreg).col_insert(2,
                                            -Rp_unreg * sympy.Matrix(m1_unreg))
first_aug_unreg
```

And again we use the `sympy` solver to solve for the first moments

```python
# Solve linear system for the first protein moments
first_sol_unreg_p = sympy.solve_linear_system(first_aug_unreg, *p1_unreg)
```

We now sum both elements and substitute the solution for the zeroth moments

```python
# Sum the results for the first moments
first_unreg_p = sum(first_sol_unreg_p.values()).simplify()
# Substitute the solutions for the mRNA first and zeroth moment
first_unreg_p_s = first_unreg_p.subs(first_sol_unreg_m).\
                            subs(zero_sol_unreg).simplify()
# Display solution in terms of the mRNA global first moment
first_unreg_p,\
first_unreg_p_s # Disiplay substituted solution.
```

Let's display the solution for the first moment in terms of the global moments

```python
first_unreg_moments_p = first_unreg_p.subs(sum(m1_unreg), mm1_unreg)
sympy.Eq(pp1_unreg, first_unreg_moments_p)
```

### mRNA-protein correlation two-state promoter

The computation of the second moment $\left\langle \mathbf{p}^2 \right\rangle$ depends on the correlation $\left\langle \mathbf{mp} \right\rangle$. To compute this correlation we have the equation
$$
\mathbf{0} = \left( \mathbf{K}^u - \mathbf{\Gamma}^u_m -  \mathbf{\Gamma}^u_p  \right)
             \left\langle \mathbf{mp} \right\rangle
             + \mathbf{R}^u_m \left\langle \mathbf{p} \right\rangle
             + \mathbf{R}^u_p \left\langle \mathbf{m}^2 \right\rangle
$$
Let's define this augmented matrix.

```python
# Define the string for the states
corr_mp_unreg = ['mp_{P_{unreg}}', 'mp_{E_{unreg}}']

# Define the zeroth moments
mp_unreg = [moment_sympy(x, None) for x in corr_mp_unreg]
mp_unreg
```

```python
# define augmented matrix for correlation
corr_aug_unreg = (Km_unreg - Gm_unreg - Gp_unreg).col_insert(2,
                  - Rm_unreg * sympy.Matrix(p1_unreg)\
                  - Rp_unreg * sympy.Matrix(m2_unreg))
corr_aug_unreg
```

Now let's solve the system.

```python
# Solve linear system for the three moments
corr_mp_sol_unreg = sympy.solve_linear_system(corr_aug_unreg, *mp_unreg)
```

The sum of these terms gives the correlation we are looking for.

```python
# Sum the terms of the correlation
corr_mp_unreg = sum(corr_mp_sol_unreg.values()).simplify()
# Substitutte relevant solutions
corr_mp_unreg_s = corr_mp_unreg.subs(first_sol_unreg_p).\
                                subs(second_sol_unreg_m).\
                                subs(first_sol_unreg_m).\
                                subs(zero_sol_unreg).simplify()

# Display solution without substitutions
corr_mp_unreg
```

We now display the solution in terms of the global moments.

```python
# Display correlation as a functiono f the second mRNA moment
corr_moments = corr_mp_unreg.collect(rp).subs(sum(m2_unreg), mm2_unreg)
sympy.Eq(mmpp_unreg, corr_moments)
```

LEt's implement the approximation of $\gm \gg \gamma_p$. This would give us

```python
corr_moments_approx = corr_moments.subs(gm + gp, gm)
corr_moments_approx
```

### Second moment two-state promoter

For the second moment we have an equation of the form
$$
\mathbf{0} = \left( \mathbf{K}^u - 2 \mathbf{\Gamma}^u_p \right)
             \left\langle \mathbf{p}^2 \right\rangle
             + \mathbf{\Gamma}^u_p \left\langle \mathbf{p} \right\rangle
             + \mathbf{R}^u_p \left\langle \mathbf{m} \right\rangle
             + 2 \mathbf{R}^u_p \left\langle \mathbf{mp} \right\rangle
$$

Let's again define this augmented matrix.

```python
# Define augmented matrix for second moment
second_aug_unreg = (Km_unreg - 2 * Gp_unreg).col_insert(2,
                    - Gp_unreg * sympy.Matrix(p1_unreg)\
                    - Rp_unreg * sympy.Matrix(m1_unreg)\
                    - 2 * Rp_unreg * sympy.Matrix(mp_unreg))
second_aug_unreg
```

Having defined the matrix let's solve the system.

```python
# Solve linear system for the three moments
second_sol_unreg_p = sympy.solve_linear_system(second_aug_unreg, *p2_unreg)
```

Again let's add both of these moments to obtain the global second moment.

```python
# Sum the soltuon of second moments
second_unreg_p = sum(second_sol_unreg_p.values()).simplify()
# Substitute relevant solutions
second_unreg_p_s = second_unreg_p.subs(corr_mp_sol_unreg).\
                                    subs(first_sol_unreg_p).\
                                    subs(second_sol_unreg_m).\
                                    subs(first_sol_unreg_m).\
                                    subs(zero_sol_unreg).simplify()

# Display solution without substitutions
second_unreg_p
```

Again let's show the solution using the global moments.

The complexity of the substitutions comes from step-by-step manually recognizing patterns.

```python
# Display solution
second_unreg_moments_p = second_unreg_p.\
            collect(gp).collect(rp).\
            subs(sum(p1_unreg), pp1_unreg).\
            subs(sum(m1_unreg), mm1_unreg).\
            subs(sum(2 * mp_unreg), 2 * mmpp_unreg).\
            expand().\
            subs(rp / (2 * gp) * mm1_unreg, pp1_unreg / 2)
sympy.Eq(pp2_unreg,  second_unreg_moments_p)
```

Using the solution for $\left\langle \mathbf{mp} \right\rangle$ with the $\gm \gg \gamma_p$ approximation gives

```python
second_unreg_moments_approx = second_unreg_moments_p.\
                              subs(mmpp_unreg, corr_moments_approx).\
                              collect(rp / (gm * gp))

sympy.Eq(pp2_unreg, second_unreg_moments_approx)
```

### Third moment two-state promoter $\ee{\bb{p}^3}$

####  $\ee{\bb{m}^2\bb{p}}$

To compute the third moment $\ee{\bb{p}^3}$ we need to compute the correlation term $\ee{\bb{mp}^2}$. This correlation needs at the same time the correlation $\ee{\bb{m}^2\bb{p}}$. So we will solve for these two terms.

For the correlation $\ee{\bb{m}^2\bb{p}}$ we have the equation
$$
\bb{0} = \left( \bb{K}^u - 2 \bb{\Gamma}_p^u - \bb{\Gamma}_p^u \right)
\ee{\bb{m}^2\bb{p}} +
\bb{R}_m^u \ee{\bb{p}} +
\left( 2 \bb{R}_m^u + \bb{\Gamma}_m^u \right) \ee{\bb{mp}} +
\bb{R}_p^u \ee{\bb{m}^3}.
$$

Let's define this augmented matrix.

```python
# Define the string for the states
corr_mp_unreg = ['m^2p_{P_{unreg}}', 'm^2p_{E_{unreg}}']

# Define the zeroth moments
m2p_unreg = [moment_sympy(x, None) for x in corr_mp_unreg]
m2p_unreg
```

```python
# define augmented matrix for correlation
m2p_aug_unreg = (Km_unreg - 2 * Gm_unreg - Gp_unreg).col_insert(2,
                  - Rm_unreg * sympy.Matrix(p1_unreg)\
                  - (2 * Rm_unreg + Gm_unreg) * sympy.Matrix(mp_unreg)\
                  - Rp_unreg * sympy.Matrix(m3_unreg))
m2p_aug_unreg
```

Let's now solve the system.

```python
# Solve linear system for the three moments
m2p_sol_unreg = sympy.solve_linear_system(m2p_aug_unreg, *m2p_unreg)
```

By adding the terms we can find the correlation we are looking for

```python
# Sum the terms of the m2pelation
corr_m2p_unreg = sum(m2p_sol_unreg.values()).simplify()
# Substitutte relevant solutions
corr_m2p_unreg_s = corr_m2p_unreg.subs(corr_mp_sol_unreg).\
                                    subs(first_sol_unreg_p).\
                                    subs(third_sol_unreg_m).\
                                    subs(second_sol_unreg_m).\
                                    subs(first_sol_unreg_m).\
                                    subs(zero_sol_unreg)

# Display solution without substitutions
corr_m2p_unreg
```

Let's now display this solution as a function of the global moments.

```python
# Display m2pelation as a functiono f the second mRNA moment
m2p_moments = corr_m2p_unreg.collect(gm).subs(sum(mp_unreg), mmpp_unreg)\
                       .collect(rp).subs(sum(m3_unreg), mm3_unreg)\
                       .collect(rm)
sympy.Eq(mm2pp_unreg, m2p_moments)
```

#### $\ee{\bb{mp}^2}$
We have a solution for all these moments, so we can procced to compute the correlation $\ee{\bb{mp}^2}$. For that we have the equation
$$
\bb{0} = \left( \bb{K}^u - \bb{\Gamma}_m^u - 2 \bb{\Gamma}_p^u \right)
\ee{\bb{mp}^2} +
\bb{R}_m^u \ee{\bb{p}^2} +
2 \bb{R}_p^u \ee{\bb{m}^2\bb{p}} +
\bb{R}_p^u \ee{\bb{m}^2} +
\bb{\Gamma}_p^u \ee{\bb{mp}}.
$$

Let's define the agumented matrix for this correlation term.

```python
# Define the string for the states
corr_mp_unreg = ['mp^2_{P_{unreg}}', 'mp^2_{E_{unreg}}']

# Define the zeroth moments
mp2_unreg = [moment_sympy(x, None) for x in corr_mp_unreg]
mp2_unreg
```

```python
# define augmented matrix for correlation
mp2_aug_unreg = (Km_unreg - Gm_unreg - 2 * Gp_unreg).col_insert(2,
                  - Rm_unreg * sympy.Matrix(p2_unreg)\
                  - 2 * Rp_unreg * sympy.Matrix(m2p_unreg)\
                  - Rp_unreg * sympy.Matrix(m2_unreg)\
                  - Gp_unreg * sympy.Matrix(mp_unreg))
mp2_aug_unreg
```

Let's solve the system.

```python
# Solve linear system for the three moments
mp2_sol_unreg = sympy.solve_linear_system(mp2_aug_unreg, *mp2_unreg)
```

By adding the terms we can find the correlation we are looking for

```python
# Sum the terms of the mp2elation
corr_mp2_unreg = sum(mp2_sol_unreg.values()).simplify()
# Substitutte relevant solutions
corr_mp2_unreg_s = corr_mp2_unreg.subs(m2p_sol_unreg).\
                                    subs(second_sol_unreg_p).\
                                    subs(corr_mp_sol_unreg).\
                                    subs(first_sol_unreg_p).\
                                    subs(third_sol_unreg_m).\
                                    subs(second_sol_unreg_m).\
                                    subs(first_sol_unreg_m).\
                                    subs(zero_sol_unreg).simplify()

# Display solution without substitutions
corr_mp2_unreg
```

Let's now display this solution as a function of the global moments.

```python
# Display correlation as a function of the second mRNA moment
mp2_moments = corr_mp2_unreg.collect(gp).subs(sum(mp_unreg), mmpp_unreg)\
                       .collect(rp).subs(sum(m2_unreg), mm2_unreg)\
                       .subs(sum(2 * m2p_unreg), 2 * mm2pp_unreg)
sympy.Eq(mmpp2_unreg, mp2_moments)
```

#### $\ee{\bb{p}^3}$

Finally having these two correlations in hand we can compute the third moment of the protein distribution $\ee{\mm{p}^3}$. For that we have the equation
$$
\bb{0} = \left( \bb{K}^u - 3 \bb{\Gamma}_p^u \right) \ee{\bb{p}^3} +
3 \bb{\Gamma}_p^u \ee{\bb{p}^2} -
\bb{\Gamma}_p^u \ee{\bb{p}} +
3 \bb{R}_p^u \ee{\bb{mp}^2} +
3 \bb{R}_p^u \ee{\bb{mp}} +
\bb{R}_p^u \ee{\bb{m}}.
$$

Let's define this agumented matrix.

```python
# define augmented matrix for correlation
p3_aug_unreg = (Km_unreg - 3 * Gp_unreg).col_insert(2,
                  - 3 * Gp_unreg * sympy.Matrix(p2_unreg)\
                  - Gp_unreg * sympy.Matrix(p1_unreg)\
                  - 3 * Rp_unreg * sympy.Matrix(mp2_unreg)\
                  - 3 * Rp_unreg * sympy.Matrix(mp_unreg)\
                  - Rp_unreg * sympy.Matrix(m1_unreg))
p3_aug_unreg
```

Let's solve the linear system

```python
third_sol_unreg_p = sympy.solve_linear_system(p3_aug_unreg, *p3_unreg)
```

Now let's add the solutions to obtain the desired moment.

```python
# Sum the soltuon of third moments
third_unreg_p = sum(third_sol_unreg_p.values()).simplify()
# Substitute relevant solutions
third_unreg_p_s = third_unreg_p.subs(mp2_sol_unreg).\
                    subs(m2p_sol_unreg).\
                    subs(second_sol_unreg_p).\
                    subs(corr_mp_sol_unreg).\
                    subs(first_sol_unreg_p).\
                    subs(third_sol_unreg_m).\
                    subs(second_sol_unreg_m).\
                    subs(first_sol_unreg_m).\
                    subs(zero_sol_unreg)

# Display solution without substitutions
third_unreg_p
```

Let's write down this in terms of the global moments.

```python
# Display solution
third_unreg_moments_p = third_unreg_p.\
            collect(gp).\
            subs(sum(p1_unreg) / 3, pp1_unreg / 3).\
            subs(sum(p2_unreg), pp2_unreg).\
            collect(rp).\
            subs(sum(m1_unreg) / 3, mm1_unreg / 3).\
            subs(sum(mp2_unreg), mmpp2_unreg).\
            subs(sum(mp_unreg), mmpp_unreg)
sympy.Eq(pp2_unreg,  third_unreg_moments_p)
```

---
### Export `lambdify` functions to `dill` objects

Since we want to keep the notebooks in an organized fashion, the numerical exploration of these functions will be done in a different notebook. Therefore here we will generate `lambdify` functions that will be saved into an object using the `dill` library.

Let' first generate lambdify functions for the moments.

```python
# Define array containing variables
var = [kp_on, kp_off, rm, gm, rp, gp]
# Define lambdify functions for the first three moments
first_unreg_num = sympy.lambdify(var, first_unreg_p_s)
second_unreg_num = sympy.lambdify(var, second_unreg_p_s)
third_unreg_num = sympy.lambdify(var, third_unreg_p_s)

# Define lambdify functions for the cross correlation terms
corr_mp_unreg_num = sympy.lambdify(var, corr_mp_unreg_s)
corr_m2p_unreg_num = sympy.lambdify(var, corr_m2p_unreg_s)
corr_mp2_unreg_num = sympy.lambdify(var, corr_mp2_unreg_s)
```

```python
# Change dill setting to allow the export of the functions
dill.settings['recurse'] = True

# Open file to save functions
with open(tmpdir + 'two_state_protein_lambdify.dill', 'wb') as file:
    dill.dump(first_unreg_num, file)
    dill.dump(second_unreg_num, file)
    dill.dump(third_unreg_num, file)
    dill.dump(corr_mp_unreg_num, file)
    dill.dump(corr_m2p_unreg_num, file)
    dill.dump(corr_mp2_unreg_num, file)
```

---
## Three-state promoter (protein)
---
Let's now derive the moments for the three-state promoter. The matrix equations remain the same between the two- and the three-state promoter. The only difference is the definition of the state transition, production and degradation matrix.

Let's first define the moment variables

```python
# Define the string for the states
m_reg = ['m_{P_{reg}}', 'm_{E_{reg}}', 'm_{R_{reg}}']

# Define the zeroth moments
m0_reg = [moment_sympy(x, 0) for x in m_reg]
# Define the first moments
m1_reg = [moment_sympy(x, 1) for x in m_reg]
# Define the second moments
m2_reg = [moment_sympy(x, 2) for x in m_reg]
# Define the second moments
m3_reg = [moment_sympy(x, 3) for x in m_reg]

m0_reg, m1_reg, m2_reg, m3_reg
```

```python
# Define the string for the states
p_reg = ['p_{P_{reg}}', 'p_{E_{reg}}', 'p_{R_{reg}}']

# Define the zeroth moments
p0_reg = [moment_sympy(x, 0) for x in p_reg]
# Define the first moments
p1_reg = [moment_sympy(x, 1) for x in p_reg]
# Define the second moments
p2_reg = [moment_sympy(x, 2) for x in p_reg]
# Define the third moments
p3_reg = [moment_sympy(x, 3) for x in p_reg]

p0_reg, p1_reg, p2_reg, p3_reg
```

```python
# define "global" moments
mm0_reg = moment_sympy('\mathbf{m}_r', 0)
mm1_reg = moment_sympy('\mathbf{m}_r', None)
mm2_reg = moment_sympy('\mathbf{m}_r', 2)
mm3_reg = moment_sympy('\mathbf{m}_r', 3)
pp0_reg = moment_sympy('\mathbf{p}_r', 0)
pp1_reg = moment_sympy('\mathbf{p}_r', None)
pp2_reg = moment_sympy('\mathbf{p}_r', 2)
pp3_reg = moment_sympy('\mathbf{p}_r', 3)

# Cross corelations
mmpp_reg = moment_sympy('\mathbf{mp}_r', None)
mm2pp_reg = moment_sympy('\mathbf{m}^2\mathbf{p}_r', None)
mmpp2_reg = moment_sympy('\mathbf{m}\mathbf{p}^2_r', None)

mm0_reg, mm1_reg, mm2_reg, mm3_reg,\
pp0_reg, pp1_reg, pp2_reg, pp3_reg,\
mmpp_reg, mm2pp_reg, mmpp2_reg
```

```python
with open(tmpdir + 'three_state_mRNA_solutions.pkl', 'rb') as file:
    first_sol_reg_m = pickle.load(file)
    second_sol_reg_m = pickle.load(file)
    third_sol_reg_m = pickle.load(file)
```

```python
# Define the rate constant matrix
Km_reg = sympy.Matrix([[-kp_off, kp_on, 0],
                         [kp_off, -(kp_on + kr_on), kr_off],
                         [0, kr_on, -kr_off]])
# Define the production matrix
Rm_reg = sympy.Matrix([[rm, 0, 0],
                       [0, 0, 0],
                       [0, 0, 0]])
# Define the degradation matrix
Gm_reg = sympy.Matrix([[gm, 0, 0],
                       [0, gm, 0],
                       [0, 0, gm]])

# Define the production matrix
Rp_reg = sympy.Matrix([[rp, 0, 0],
                       [0, rp, 0],
                       [0, 0, rp]])

# Define the production matrix
Gp_reg = sympy.Matrix([[gp, 0, 0],
                       [0, gp, 0],
                       [0, 0, gp]])

Km_reg, Rm_reg, Gm_reg, Rp_reg, Gp_reg
```

### Zeroth moment three-state promoter
To solve the zeroth moment for the three-state promoter we use the same set of equations as for the two-state promoter, this is
$$
1 = \mathbf{u} \cdot \left\langle \mathbf{m}^0 \right\rangle,
$$
where this time $u = (1, 1, 1)$ and $\left\langle \mathbf{m}^0 \right\rangle$ is the vector containing all *three* states. We use as well
$$
\mathbf{0} = \mathbf{K}^r \left\langle \mathbf{m}^0 \right\rangle.
$$

We will again use the linear algebra solver where we write an extended matrix with the last column being the right hand side of these equations.

Let's now define this extended matrix.

```python
# Add the extra row for the normalization constraint
K_sys_reg = Km_reg.row_insert(0, sympy.Matrix([1, 1, 1]).transpose())

# Define right-side array
b = sympy.Matrix([1, 0, 0, 0])

# Define augmented matrix
zero_aug_reg = K_sys_reg.col_insert(3, b)
zero_aug_reg
```

Let's solve the system for our three zeroth moments.

```python
# Solve linear system for the three zeroth moments
zero_sol_reg = sympy.solve_linear_system(zero_aug_reg, *m0_reg)
zero_sol_reg
```

Let's quickly confirm that these moments add up to one since each represents the probability of being on each of the promoter states.

```python
sum(zero_sol_reg.values()).simplify()
```

### First moment two-state promoter (protein)

Now that we have the zeroth moment that indicates the probability of being on either state let's now compute the first moment. For this we have the matrix equation
$$
\mathbf{0} = \left( \mathbf{K}^r - \mathbf{\Gamma}^r_p  \right)
             + \mathbf{R}^r_p \left\langle \mathbf{m} \right\rangle.
$$

Let's write this system in the form of the augmented matrix.

```python
# Define augmented matrix for first moment
first_aug_reg = (Km_reg - Gp_reg).col_insert(3,
                -Rp_reg * sympy.Matrix(m1_reg))
first_aug_reg
```

Now let's solve the system.

```python
# Solve linear system for the first protein moments
first_sol_reg_p = sympy.solve_linear_system(first_aug_reg, *p1_reg)
```

Let's sum the elements of this solution to compute the global moment.

```python
# Sum the results for the first moments
first_reg_p = sum(first_sol_reg_p.values()).simplify()
# Substitute the solutions for the mRNA first and zeroth moment
first_reg_p_s = first_reg_p.subs(first_sol_reg_m).\
                            subs(zero_sol_reg).simplify()
# Display solution in terms of the mRNA global first moment
first_reg_p,\
first_reg_p_s # Disiplay substituted solution.
```

#### fold-change.

A very relevant quantity to compute is the fold-change in gene expression. Since this quantity must be consistent between the thermodynamic framework and the master equation framework this quantity will constraint the value of the parameters that we obtain at the end of the day.

```python
# Compute fold-change
fold_change = first_reg_p_s / first_unreg_p_s
fold_change.factor(kr_off)
```

This is as expected the same expression that we obtained at the mRNA level.

### mRNA-protein correlation three-state promoter

The computation of the second moment $\left\langle \mathbf{p}^2 \right\rangle$ depends on the correlation $\left\langle \mathbf{mp} \right\rangle$. To compute this correlation we have the equation
$$
\mathbf{0} = \left( \mathbf{K}^r - \mathbf{\Gamma}^r_m -  \mathbf{\Gamma}^r_p  \right)
             \left\langle \mathbf{mp} \right\rangle
             + \mathbf{R}^r_m \left\langle \mathbf{p} \right\rangle
             + \mathbf{R}^r_p \left\langle \mathbf{m}^2 \right\rangle
$$
Let's define this augmented matrix.

```python
# Define the string for the states
corr_mp_reg = ['mp_{P_{reg}}', 'mp_{E_{reg}}', 'mp_{R_{reg}}']

# Define the zeroth moments
mp_reg = [moment_sympy(x, None) for x in corr_mp_reg]
mp_reg
```

```python
# define augmented matrix for correlation
corr_aug_reg = (Km_reg - Gm_reg - Gp_reg).col_insert(3,
                - Rm_reg * sympy.Matrix(p1_reg)\
                - Rp_reg * sympy.Matrix(m2_reg))
corr_aug_reg
```

```python
# Solve linear system for the three moments
corr_mp_sol_reg = sympy.solve_linear_system(corr_aug_reg, *mp_reg)
```

Now let's add each of these solutions to obtain the total correlation.

```python
# Sum the terms of the correlation
corr_mp_reg = sum(corr_mp_sol_reg.values()).simplify()
# Substitutte relevant solutions
corr_mp_reg_s = corr_mp_reg.subs(first_sol_reg_p).subs(second_sol_reg_m).\
               subs(first_sol_reg_m).subs(zero_sol_reg)

# Display solution without substitutions
corr_mp_reg
```

Let's now write this in terms of the global moments

```python
# Display correlation as a functiono f the second mRNA moment
corr_moments = corr_mp_reg.collect(rp).subs(sum(m2_reg), mm2_reg)
sympy.Eq(mmpp_reg, corr_moments)
```

### Second moment three-state promoter

For the second moment we have an equation of the form
$$
\mathbf{0} = \left( \mathbf{K}^r - 2 \mathbf{\Gamma}^r_p \right)
             \left\langle \mathbf{p}^2 \right\rangle
             + \mathbf{\Gamma}^r_p \left\langle \mathbf{p} \right\rangle
             + \mathbf{R}^r_p \mm
             + 2 \mathbf{R}^r_p \left\langle \mathbf{mp} \right\rangle
$$

Let's again define this augmented matrix.

```python
# Define augmented matrix for second moment
second_aug_reg = (Km_reg - 2 * Gp_reg).col_insert(3,
                    - Gp_reg * sympy.Matrix(p1_reg)\
                    - Rp_reg * sympy.Matrix(m1_reg)\
                    - 2 * Rp_reg * sympy.Matrix(mp_reg))
second_aug_reg
```

Having defined the augmented matrix let's solve the linear system.

```python
# Solve linear system for the three moments
second_sol_reg_p = sympy.solve_linear_system(second_aug_reg, *p2_reg)
```

Now we add all these elements to obtain the global second moment

```python
# Sum the soltuon of second moments
second_reg_p = sum(second_sol_reg_p.values()).simplify()
# Substitute relevant solutions
second_reg_p_s = second_reg_p.subs(corr_mp_sol_reg).subs(first_sol_reg_p).\
                 subs(second_sol_reg_m).subs(first_sol_reg_m).\
                 subs(zero_sol_reg)

# Display solution without substitutions
second_reg_p
```

Let's now express this moment as a funciton of the global moments.

```python
# Display solution
second_reg_moments_p = second_reg_p.\
            collect(gp).collect(rp).\
            subs(sum(p1_reg), pp1_reg).\
            subs(sum(m1_reg), mm1_reg).\
            subs(sum(2 * mp_reg), 2 * mmpp_reg).\
            expand().\
            subs(rp / (2 * gp) * mm1_reg, pp1_reg / 2)
sympy.Eq(pp2_reg,  second_reg_moments_p)
```

It has the exact same funcitonal form as the two-state promoter. The only difference is obviously the definition of each of the moments.

### Third moment three-state promoter $\ee{\bb{p}^3}$

#### $\ee{\bb{m}^2\bb{p}}$

To compute the third moment $\ee{\bb{p}^3}$ we need to compute the correlation term $\ee{\bb{mp}^2}$. This correlation needs at the same time the correlation $\ee{\bb{m}^2\bb{p}}$. So we will solve for these two terms.

For the correlation $\ee{\bb{m}^2\bb{p}}$ we have the equation
$$
\bb{0} = \left( \bb{K}^r - 2 \bb{\Gamma}_p^r - \bb{\Gamma}_p^r \right)
\ee{\bb{m}^2\bb{p}} +
\bb{R}_m^r \ee{\bb{p}} +
\left( 2 \bb{R}_m^r + \bb{\Gamma}_m^r \right) \ee{\bb{mp}} +
\bb{R}_p^r \ee{\bb{m}^3}.
$$

Let's define this augmented matrix.

```python
# Define the string for the states
corr_mp_reg = ['m^2p_{P_{reg}}', 'm^2p_{E_{reg}}', 'm^2p_{R_{reg}}']

# Define the zeroth moments
m2p_reg = [moment_sympy(x, None) for x in corr_mp_reg]
m2p_reg
```

```python
# define augmented matrix for correlation
m2p_aug_reg = (Km_reg - 2 * Gm_reg - Gp_reg).col_insert(3,
                  - Rm_reg * sympy.Matrix(p1_reg)\
                  - (2 * Rm_reg + Gm_reg) * sympy.Matrix(mp_reg)\
                  - Rp_reg * sympy.Matrix(m3_reg))
m2p_aug_reg
```

Let's now solve the system.

```python
# Solve linear system for the three moments
m2p_sol_reg = sympy.solve_linear_system(m2p_aug_reg, *m2p_reg)
```

Let's add these terms to see the total correlation.

```python
# Sum the terms of the m2pelation
corr_m2p_reg = sum(m2p_sol_reg.values()).simplify()
# Substitutte relevant solutions
corr_m2p_reg_s = corr_m2p_reg.subs(second_sol_reg_p).\
                    subs(corr_mp_sol_reg).\
                    subs(first_sol_reg_p).\
                    subs(third_sol_reg_m).\
                    subs(second_sol_reg_m).\
                    subs(first_sol_reg_m).\
                    subs(zero_sol_reg)

# Display solution without substitutions
corr_m2p_reg
```

Now let's express this in terms of the global moments

```python
# Display m2pelation as a functiono f the second mRNA moment
m2p_moments_reg = corr_m2p_reg.collect(gm).subs(sum(mp_reg), mmpp_reg)\
                       .collect(rp).subs(sum(m3_reg), mm3_reg)\
                       .collect(rm)
sympy.Eq(mm2pp_reg, m2p_moments_reg)
```

#### $\ee{\bb{mp}^2}$
We have a solution for all these moments, so we can procced to compute the correlation $\ee{\bb{mp}^2}$. For that we have the equation
$$
\bb{0} = \left( \bb{K}^r - \bb{\Gamma}_m^r - 2 \bb{\Gamma}_p^r \right)
\ee{\bb{mp}^2} +
\bb{R}_m^r \ee{\bb{p}^2} +
2 \bb{R}_p^r \ee{\bb{m}^2\bb{p}} +
\bb{R}_p^r \ee{\bb{m}^2} +
\bb{\Gamma}_p^r \ee{\bb{mp}}.
$$

Let's define the agumented matrix for this correlation term.

```python
# Define the string for the states
corr_mp_reg = ['mp^2_{P_{reg}}', 'mp^2_{E_{reg}}', 'mp^2_{R_{reg}}']

# Define the zeroth moments
mp2_reg = [moment_sympy(x, None) for x in corr_mp_reg]
mp2_reg
```

```python
# define augmented matrix for correlation
mp2_aug_reg = (Km_reg - Gm_reg - 2 * Gp_reg).col_insert(3,
                  - Rm_reg * sympy.Matrix(p2_reg)\
                  - 2 * Rp_reg * sympy.Matrix(m2p_reg)\
                  - Rp_reg * sympy.Matrix(m2_reg)\
                  - Gp_reg * sympy.Matrix(mp_reg))
mp2_aug_reg
```

Now we can solve the system

```python
# Solve linear system for the three moments
mp2_sol_reg = sympy.solve_linear_system(mp2_aug_reg, *mp2_reg)
```

Again we add the individual terms to compute the total correlation term.

```python
# Sum the terms of the mp2elation
corr_mp2_reg = sum(mp2_sol_reg.values()).simplify()
# Substitutte relevant solutions
corr_mp2_reg_s = corr_mp2_reg.subs(m2p_sol_reg).\
                    subs(second_sol_reg_p).\
                    subs(corr_mp_sol_reg).\
                    subs(first_sol_reg_p).\
                    subs(third_sol_reg_m).\
                    subs(second_sol_reg_m).\
                    subs(first_sol_reg_m).\
                    subs(zero_sol_reg)

# Display solution without substitutions
corr_mp2_reg
```

In terms of the total correlation terms this gives

```python
# Display correlation as a function of the second mRNA moment
mp2_moments_reg = corr_mp2_reg.collect(gp).subs(sum(mp_reg), mmpp_reg)\
                       .collect(rp).subs(sum(m2_reg), mm2_reg)\
                       .subs(sum(2 * m2p_reg), 2 * mm2pp_reg)
sympy.Eq(mmpp2_reg, mp2_moments_reg)
```

#### $\ee{\bb{p}^3}$

Finally having these two correlations in hand we can compute the third moment of the protein distribution $\ee{\mm{p}^3}$. For that we have the equation
$$
\bb{0} = \left( \bb{K}^r - 3 \bb{\Gamma}_p^r \right) \ee{\bb{p}^3} +
3 \bb{\Gamma}_p^r \ee{\bb{p}^2} -
\bb{\Gamma}_p^r \ee{\bb{p}} +
3 \bb{R}_p^r \ee{\bb{mp}^2} +
3 \bb{R}_p^r \ee{\bb{mp}} +
\bb{R}_p^r \ee{\bb{m}}.
$$

Let's define this agumented matrix.

```python
# define augmented matrix for correlation
p3_aug_reg = (Km_reg - 3 * Gp_reg).col_insert(3,
                  - 3 * Gp_reg * sympy.Matrix(p2_reg)\
                  - Gp_reg * sympy.Matrix(p1_reg)\
                  - 3 * Rp_reg * sympy.Matrix(mp2_reg)\
                  - 3 * Rp_reg * sympy.Matrix(mp_reg)\
                  - Rp_reg * sympy.Matrix(m1_reg))
p3_aug_reg
```

We now solve the system.

```python
third_sol_reg_p = sympy.solve_linear_system(p3_aug_reg, *p3_reg)
```

And compute the total third moment again by adding the individual terms.

```python
# Sum the soltuon of third moments
third_reg_p = sum(third_sol_reg_p.values()).simplify()
# Substitute relevant solutions
third_reg_p_s = third_reg_p.subs(mp2_sol_reg).\
                    subs(m2p_sol_reg).\
                    subs(second_sol_reg_p).\
                    subs(corr_mp_sol_reg).\
                    subs(first_sol_reg_p).\
                    subs(third_sol_reg_m).\
                    subs(second_sol_reg_m).\
                    subs(first_sol_reg_m).\
                    subs(zero_sol_reg)

# Display solution without substitutions
third_reg_p
```

In term of the total moments this is given by

```python
# Display solution
third_reg_moments_p = third_reg_p.\
            collect(gp).\
            subs(sum(p1_reg) / 3, pp1_reg / 3).\
            subs(sum(p2_reg), pp2_reg).\
            collect(rp).\
            subs(sum(m1_reg) / 3, mm1_reg / 3).\
            subs(sum(mp2_reg), mmpp2_reg).\
            subs(sum(mp_reg), mmpp_reg)
sympy.Eq(pp2_reg,  third_reg_moments_p)
```

### Export `lambdify` functions to `dill` objects

Since we want to keep the notebooks in an organized fashion, the numerical exploration of these functions will be done in a different notebook. Therefore here we will generate `lambdify` functions that will be saved into an object using the `dill` library.

Let' first generate lambdify functions for the moments.

```python
# Define array containing variables
var = [kr_on, kr_off, kp_on, kp_off, rm, gm, rp, gp]
# Define lambdify functions for the first three moments
first_reg_num = sympy.lambdify(var, first_reg_p_s)
second_reg_num = sympy.lambdify(var, second_reg_p_s)
third_reg_num = sympy.lambdify(var, third_reg_p_s)

# Define lambdify functions for the cross correlation terms
corr_mp_reg_num = sympy.lambdify(var, corr_mp_reg_s)
corr_m2p_reg_num = sympy.lambdify(var, corr_m2p_reg_s)
corr_mp2_reg_num = sympy.lambdify(var, corr_mp2_reg_s)
```

```python
# Change dill setting to allow the export of the functions
dill.settings['recurse'] = True

# Open file to save functions
with open(tmpdir + 'three_state_protein_lambdify.dill', 'wb') as file:
    dill.dump(first_reg_num, file)
    dill.dump(second_reg_num, file)
    dill.dump(third_reg_num, file)
    dill.dump(corr_mp_reg_num, file)
    dill.dump(corr_m2p_reg_num, file)
    dill.dump(corr_mp2_reg_num, file)
```


# Latent Factor DCMM Analysis

This example highlights several advanced modeling strategies.

**Dynamic Count Mixture Model**

A Dynamic Count Mixture Model (DCMM) is the combination of Bernoulli and Poisson DGLMs. The Bernoulli DGLM models the probability of a zero outcome, while the Poisson models the outcome, conditional on it being non-zero (Berry and West, 2019):

\begin{equation} \label{eqn-dcmm}
z_t \sim Ber(\pi_t) \text{ and } y_t \mid z_t =
\begin{cases}
0, & \text{if } z_t = 0,\\
1 + x_t, \quad x_t \sim Po(\mu_t), & \text{if }z_t = 1
\end{cases}
\end{equation}

where $\pi_t$ and $\mu_t$ vary according to the dynamics of independent Bernoulli and Poisson DGLMs respectively:

\begin{equation*}
\text{logit}(\pi_t) = \mathbf{F}_{ber, t}^{'}\boldsymbol{\theta}_{ber, t} \qquad \text{and} \qquad \text{log}(\mu_t) = \mathbf{F}_{Po, t}^{'} \boldsymbol{\theta}_{Po, t}
\end{equation*}

A DCMM is useful to capture a higher prevalence of $0$ outcomes than in a standard Poisson DGLM.

**Latent Factors**

The second concept is the use of *latent factors* for multiscale modeling. This example features simulated data from a retail sales setting. There is data for an item, and also for total sales at the store. The total sales are smoother and more predictable than the item level sales. A model is fit to the total sales which includes a day-of-week seasonal effect. This seasonal effect is extracted from the model on total sales, and used as a predictor in the item level model.

Modeling effects at different levels of a hierarchy - aka multiscale modeling - can improve forecast accuracy, because the sales of an individual can be very noisy, making it difficult to learn accurate patterns.

**Copula Forecasting**

The final concept is Copula forecasting. In this example we are focusing on forecasting sales *1* through *14* days into the future. To do this, we will simulate from the joint forecast distribution *1:14* days into the future. This is accomplished in an efficient manner through the use of a Copula model, which accounts for dependence in the forecasts across days.

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
```

```python
from pybats.analysis import analysis, analysis_dcmm
from pybats.latent_factor import seas_weekly_lf
from pybats.shared import load_dcmm_latent_factor_example
from pybats.plot import plot_data_forecast
from pybats.point_forecast import median
from pybats.loss_functions import MAD
```

We start by loading the simulated retail sales data.

The dataframe 'totaldata' has the total daily sales in a store, along with a predictor, which represents a measure of average price.

The dataframe 'data' has the sales of a single item, and along with a predictor which represents the daily change in price.

```python
data = load_dcmm_latent_factor_example()
totaldata, data = data.values()
totaldata['Y'] = np.log(totaldata['Y'] + 1)
```

```python
totaldata.head()
```

      Y
      X

      2014-06-01
      5.613128
      -0.133124

      2014-06-02
      5.602119
      -0.070102

      2014-06-03
      5.831882
      1.020901

      2014-06-04
      5.393628
      2.219605

      2014-06-05
      5.267858
      -0.970556

```python
data.head()
```

      Y
      X

      2014-06-01
      14.0
      2.413448

      2014-06-02
      13.0
      0.989871

      2014-06-03
      7.0
      -0.489734

      2014-06-04
      34.0
      -1.544868

      2014-06-05
      15.0
      1.639670

Here we define hyper parameters for the analysis:
-  *rho* is a discount factor which calibrates the forecast distribution. A value of *rho* less than $1$ increases the forecast variance.

- *k* is the number of days ahead to forecast.

- *nsamps* is the number of forecast samples to draw.

- *prior_length* is the number of days of data to use when defining the priors for model coefficients.

```python
#Define hyper parameters
rho = .2
k = 14 # Number of days ahead that we will forecast
nsamps = 100
prior_length = 21
```

Next, we define the window of time that we want to forecast over. For each day within this time period, the model will sample from the path (joint) forecast distribution *1:k* days into the future.

```python
# Define forecast range for final year of data
T = len(totaldata)
forecast_end_date = totaldata.index[-k]
forecast_start_date = forecast_end_date - pd.DateOffset(days=365)
```

The first analysis is run on the 'totaldata', which is used to learn a latent factor. In this case, we fit a normal DLM to the log of total sales in order to learn the weekly season effect, which is stored in the latent factor.

```python
# Get multiscale signal (a latent factor) from higher level log-normal model
latent_factor = analysis(totaldata['Y'].values, totaldata['X'].values, k,
                         forecast_start_date, forecast_end_date, dates=totaldata.index,
                         seasPeriods=[7], seasHarmComponents=[[1,2,3]],
                         family="normal", ret=['new_latent_factors'], new_latent_factors= [seas_weekly_lf],
                         prior_length=prior_length)
```

    beginning forecasting

The second analysis fits a DCMM to 'data', which is the simulated sales of a single item. The latent factor we derived above is used as a predictive signal for the sales of this individual item. On each day within the forecasting period, a copula is used to draw joint forecast samples *1:k* days ahead.

```python
# Update and forecast the model
mod, forecast_samples = analysis_dcmm(data['Y'].values, data['X'].values.reshape(-1,1), k,
                                      forecast_start_date, forecast_end_date,
                                      prior_length=prior_length, nsamps=nsamps, rho=rho,
                                      latent_factor=latent_factor, dates=totaldata.index)

forecast = median(forecast_samples)
```

    beginning forecasting

Finally we can examine the results, first by plotting both the *1-* and *14-* step ahead forecasts. The point forecast is the median (blue line), and the forecast samples provide easy access to 95% credible intervals.

```python
# Plotting 1-step ahead forecasts

horizon = 1
plot_length = 50
fig, ax = plt.subplots(figsize=(8,4))
start_date = forecast_end_date + pd.DateOffset(horizon - plot_length)
end_date = forecast_end_date + pd.DateOffset(horizon - 1)
ax = plot_data_forecast(fig, ax, data.loc[start_date:end_date].Y,
                        forecast[-plot_length:,horizon - 1],
                        forecast_samples[:,-plot_length:,horizon - 1],
                        data.loc[start_date:end_date].index,
                        linewidth = 2)
```

```python
# Plotting 14-step ahead forecasts

horizon = 14
plot_length = 50
fig, ax = plt.subplots(figsize=(8,4))
start_date = forecast_end_date + pd.DateOffset(horizon - plot_length)
end_date = forecast_end_date + pd.DateOffset(horizon - 1)
ax = plot_data_forecast(fig, ax, data.loc[start_date:end_date].Y,
                        forecast[-plot_length:,horizon - 1],
                        forecast_samples[:,-plot_length:,horizon - 1],
                        data.loc[start_date:end_date].index,
                        linewidth = 2)
```

To evaluate the point forecasts, we can look at the mean absolute deviation (MAD) between the forecast median and the observations across forecast horizons. Interestingly, there is only a small increase in the MAD for longer forecast horizons.

```python
# Mean absolute deviation at increasing forecast horizons
horizons = list(range(1, k+1))
list(map(lambda k: MAD(data.loc[forecast_start_date + pd.DateOffset(k-1):forecast_end_date + pd.DateOffset(k-1)].Y,
                      forecast[:,k-1]),
         horizons))
```

    [2.8278688524590163,
     2.8647540983606556,
     2.837431693989071,
     2.8565573770491803,
     2.878415300546448,
     2.83879781420765,
     2.8647540983606556,
     2.860655737704918,
     2.866120218579235,
     2.918032786885246,
     2.8456284153005464,
     2.851092896174863,
     2.831967213114754,
     2.8961748633879782]

```python

```

examples/DCMM Latent Factor Example.ipynb


# Learn Latex using Jupyter notebook

```
$$
\begin{align}
\sum_{k=1}^{\infty} \frac{1}{k^2} = \frac{\pi^2}{6}
\end{align}
$$
```
$$
\begin{align}
\sum_{k=1}^{\infty} \frac{1}{k^2} = \frac{\pi^2}{6}
\end{align}
$$

`$$`で囲むと行替えしてセンタリングされるみたい。
`$`で囲むと行替えされずに行の中に書かれる。

```
$$
\newcommand{\rot}[1]{\nabla\times #1}
\newcommand{\pdfrac}[2]{\frac{\partial #1}{\partial #2}}
\begin{align}
  \mathbf{D} &= 0 \\\
  \mathbf{B} &= 0 \\\
  \rot{\mathbf{E}} &= - \pdfrac{\mathbf{B}}{t} \\\
  \rot{\mathbf{H}} &= \pdfrac{\mathbf{D}}{t}
\end{align}
$$
```
$$
\newcommand{\rot}[1]{\nabla\times #1}
\newcommand{\pdfrac}[2]{\frac{\partial #1}{\partial #2}}
\begin{align}
  \mathbf{D} &= 0 \\\
  \mathbf{B} &= 0 \\\
  \rot{\mathbf{E}} &= - \pdfrac{\mathbf{B}}{t} \\\
  \rot{\mathbf{H}} &= \pdfrac{\mathbf{D}}{t}
\end{align}
$$

`$$`で囲んだ中の改行は`\\\`みたい。

```
\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}
```
\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}

`& =`というのは縦に`=`を揃える、ってことかな。
ここでは改行は`\\`になっている。

おっとそれより、`$$`で囲まなくてもいいのか！

```
\begin{equation*}
\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
\end{equation*}
```
\begin{equation*}
\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
\end{equation*}

`\displaystyle`とか書かなくてもシグマの添字が上下についているがこれを横につけるにはどうしたらよいのか。

```python
%matplotlib inline
from sympy import *
init_printing()
x = Symbol('x')
y = Symbol('y')
expr = (x + y)**5
expr
```

```python
expand (expr)
```

```python
solve(expr, x, 0)
```

```python

```

```python

```

```python

```

```python

```

```python

```


.. meta::
   :description: Multi-criteria Decision Making addresses the selection of a solution set with multiple conflicting objectives.

.. meta::
   :keywords: Multi-criteria Decision Making, MCDM, Multi-objective Optimization, Python

## Multi-Criteria Decision Making (MCDM)

The focus of pymoo is on optimization methods itself. However, some basic multi-criteria decision making methods are available:

### Compromise Programming

We can use any scalarization method and use it for post-processing. Let us assume our algorithm has converged to the Pareto-front:

```python
from pymoo.factory import get_problem

F = get_problem("zdt1").pareto_front()
```

Then, we initialize weights and our decomposition function:

```python
from pymoo.factory import get_problem, get_visualization, get_decomposition
import numpy as np

weights = np.array([0.5, 0.5])
decomp = get_decomposition("asf")
```

We apply the decomposition and retrieve the best value (here minimum):

```python
I = decomp.do(F, weights).argmin()
print("Best regarding decomposition: Point %s - %s" % (I, F[I]))
```

    Best regarding decomposition: Point 38 - [0.38383838 0.38045308]

Visualize it:

```python
from pymoo.factory import get_visualization

plot = get_visualization("scatter")
plot.add(F, color="blue", alpha=0.2, s=10)
plot.add(F[I], color="red", s=30)
plot.do()
plot.apply(lambda ax: ax.arrow(0, 0, 0.5, 0.5, color='black',
                               head_width=0.01, head_length=0.01, alpha=0.4))
plot.show()
```

### Pseudo-Weights

A simple way to chose a solution out of a solution set in the context of multi-objective optimization is the pseudo-weight vector approach proposed in . Respectively, the pseudo weight $w_i$ for the i-ith objective function can be calculated by:

\begin{equation}
w_i = \frac{(f_i^{max} - f_i {(x)}) \, /\,  (f_i^{max} - f_i^{min})}{\sum_{m=1}^M (f_m^{max} - f_m (x)) \, /\,  (f_m^{max} - f_m^{min})}
\end{equation}

This equation calculates the normalized distance to the worst solution regarding each objective $i$. Please note that for non-convex Pareto fronts the pseudo weight does not correspond to the result of an optimization using the weighted sum. However, for convex Pareto-fronts the pseudo weights are an indicator of the location in the objective space.

```python
from pymoo.factory import get_reference_directions
from pymoo.mcdm.pseudo_weights import PseudoWeights

ref_dirs = get_reference_directions("das-dennis", 4, n_partitions=12)
F = get_problem("dtlz1").pareto_front(ref_dirs)

weights = np.array([0.25, 0.25, 0.25, 0.25])
a, pseudo_weights = PseudoWeights(weights).do(F, return_pseudo_weights=True)

weights = np.array([0.4, 0.20, 0.15, 0.25])
b, pseudo_weights = PseudoWeights(weights).do(F, return_pseudo_weights=True)

plot = get_visualization("petal", bounds=(0, 0.5), reverse=True)
plot.add(F[[a, b]])
plot.show()
```

### High Trade-off Points

Furthermore, high trade-off points are usually of interest. We have implemented the trade-off metric proposed in . An example for 2 and 3 dimensional solution is given below:

```python
import os

import numpy as np

from pymoo.factory import get_decision_making
from pymoo.visualization.scatter import Scatter
from pymoo.mcdm.high_tradeoff import HighTradeoffPoints

pf = np.loadtxt("knee-2d.out")
dm = HighTradeoffPoints()

I = dm.do(pf)

plot = Scatter()
plot.add(pf, alpha=0.2)
plot.add(pf[I], color="red", s=100)
plot.show()
```

```python
pf = np.loadtxt("knee-3d.out")
dm = get_decision_making("high-tradeoff")

I = dm.do(pf)

plot = Scatter(angle=(10, 140))
plot.add(pf, alpha=0.2)
plot.add(pf[I], color="red", s=100)
plot.show()
```


# 鍛鍊你的資料分析力 | Python 程式設計

> 條件判斷

郭耀仁 <yaojenkuo@datainpoint.com> from [DATAINPOINT](https://www.datainpoint.com/)

> Simple is better than complex.
>
> The Zen of Python

## 大綱

- `if` 判斷
- `else` 判斷
- `elif` 判斷

## `if` 判斷

## 我們需要布林來進行：

- **條件判斷**
- `while` 迴圈
- 資料篩選

## `if` 判斷的程式碼結構

```python
if EXPR:
    # EXPR 被評估為 True 時候才執行縮排的程式碼
```

## 評分超過門檻值 `threshold` 就推薦是好看的電影

```python
movie_title = "Avengers: Endgame"
movie_rating = 8.4
threshold = 6.5

if movie_rating > threshold:
    print("{} 的 IMDB 評分為 {} 分，是一部好看的電影！".format(movie_title, movie_rating))
```

    Avengers: Endgame 的 IMDB 評分為 8.4 分，是一部好看的電影！

```python
movie_title = "The Meg"
movie_rating = 5.6
threshold = 6.5

if movie_rating > threshold:
    print("{} 的 IMDB 評分為 {} 分，是一部好看的電影！".format(movie_title, movie_rating))
```

## 評分超過門檻值 `threshold` 就推薦是好看的電影，小於等於門檻值則警告是不好看的電影

使用兩個 `if` 判斷。

```python
movie_title = "The Meg"
movie_rating = 5.6
threshold = 6.5

if movie_rating > threshold:
    print("{} 的 IMDB 評分為 {} 分，是一部好看的電影！".format(movie_title, movie_rating))
if movie_rating <= threshold:
    print("{} 的 IMDB 評分為 {} 分，是一部不好看的電影！".format(movie_title, movie_rating))
```

    The Meg 的 IMDB 評分為 5.6 分，是一部不好看的電影！

## `else` 判斷

## `else` 判斷的程式碼結構

```python
if EXPR:
    # EXPR 被評估為 True 時候才執行縮排的程式碼
else:
    # EXPR 被評估為 False 時候才執行縮排的程式碼
```

## 評分超過門檻值 `threshold` 就推薦是好看的電影，小於等於門檻值則警告是不好看的電影

使用一組 `if...else...` 判斷。

```python
movie_title = "The Meg"
movie_rating = 5.6
threshold = 6.5

if movie_rating > threshold:
    print("{} 的 IMDB 評分為 {} 分，是一部好看的電影！".format(movie_title, movie_rating))
else:
    print("{} 的 IMDB 評分為 {} 分，是一部不好看的電影！".format(movie_title, movie_rating))
```

    The Meg 的 IMDB 評分為 5.6 分，是一部不好看的電影！

## `elif` 判斷

## `elif` 判斷的程式碼結構

```python
if EXPR_1:
    # EXPR_1 被評估為 True 時候才執行縮排的程式碼
elif EXPR_2:
    # EXPR_1 被評估為 False 而且 EXPR_2 被評估為 True 時候才執行縮排的程式碼
elif EXPR_3:
    # EXPR_1 被評估為 False、EXPR_2 被評估為 False 並且 EXPR_3 被評估為 True 時候才執行縮排的程式碼
# ...
# 可以插入多個 elif
# ...
else:
    # EXPR_1、EXPR_2 與 EXPR_3 均被評估為 False 時候才執行縮排的程式碼
```

## 將 `BMI` 對應為敘述

<https://en.wikipedia.org/wiki/Body_mass_index#/media/File:BMI_chart.svg>

使用四個 `if` 判斷。

```python
bmi = 31
if bmi > 30:
    desc = 'Obese'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
if bmi > 25 and bmi <=30:
    desc = 'Overweight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
if bmi >= 18.5 and bmi <= 25:
    desc = 'Normal weight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
if bmi < 18.5:
    desc = 'Underweight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
```

    BMI 31 的敘述為 Obese

## 將 `BMI` 對應為敘述

<https://en.wikipedia.org/wiki/Body_mass_index#/media/File:BMI_chart.svg>

使用一組 `if...elif...else` 判斷。

```python
bmi = 31
if bmi > 30:
    desc = 'Obese'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
elif bmi > 25:
    desc = 'Overweight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
elif bmi >= 18.5:
    desc = 'Normal weight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
else:
    desc = 'Underweight'
    print("BMI {} 的敘述為 {}".format(bmi, desc))
```

    BMI 31 的敘述為 Obese

## 當一組 `if...elif...else` 判斷中有重疊的條件，執行先寫的分支程式碼

## 隨堂練習

## 隨堂練習：定義一個函式 `odd_or_even(x)` 如果 `x` 是奇數回傳 `'odd'` 是偶數則回傳 `'even'`

- 預期輸入：一個整數 `x`
- 預期輸出：一個文字

```python
def odd_or_even(x):
    """
    >>> odd_or_even(0)
    'even'
    >>> odd_or_even(1)
    'odd'
    >>> odd_or_even(2)
    'even'
    >>> odd_or_even(3)
    'odd'
    """
```

## 隨堂練習：定義一個函式 `absolute(x)` 如果 `x` 小於 0 回傳 `-x`，否則回傳 `x`

- 預期輸入：一個整數 `x`
- 預期輸出：一個整數

```python
def absolute(x):
    """
    >>> absolute(-5)
    5
    >>> absolute(-6)
    6
    >>> absolute(0)
    0
    >>> absolute(56)
    56
    """
```

## 隨堂練習：定義一個函式 `step(z)` 如果 `z` 大於等於 0.5 回傳 `1`，否則回傳 `0`

- 預期輸入：一個浮點數 `z`
- 預期輸出：一個整數

```python
def step(z):
    """
    >>> step(0.1)
    0
    >>> step(0.4)
    0
    >>> step(0.5)
    1
    >>> step(0.9)
    1
    """
```

## 隨堂練習：定義一個函式 `bmi_condition(height, weight)` 依據身高（公分）與體重（公斤）判斷 BMI 的分類敘述

\begin{equation}
BMI = \frac{weight_{kg}}{height_{m}^2}
\end{equation}

<https://en.wikipedia.org/wiki/Body_mass_index#/media/File:BMI_chart.svg>

- 預期輸入：兩個數值 `height` 與 `weight`
- 預期輸出：一個文字

```python
def bmi_condition(height, weight):
    """
    >>> bmi_condition(198, 129) # Zion Williamson
    '身高 198 公分體重 129 公斤是 Obese'
    >>> bmi_condition(206, 113) # LeBron James
    '身高 206 公分體重 113 公斤是 Overweight'
    >>> bmi_condition(198, 98)  # Michael Jordan
    '身高 198 公分體重 98 公斤是 Normal weight'
    """
```

## 隨堂練習：定義一個函式 `fizz_buzz(x)`，當 `x` 是 3 的倍數時回傳 `Fizz`、是 5 的倍數時回傳 `Buzz`、是 15 的倍數時回傳 `Fizz Buzz`，否則回傳 `x`，條件重疊時以 15 的倍數優先判斷

- 預期輸入：一個正整數 `x`
- 預期輸出：一個文字或一個整數

```python
def fizz_buzz(x):
    """
    >>> fizz_buzz(3)
    'Fizz'
    >>> fizz_buzz(5)
    'Buzz'
    >>> fizz_buzz(15)
    'Fizz Buzz'
    >>> fizz_buzz(16)
    16
    """
```

## 執行測試

```python
%load ../test_cases/test_cases_04.py
```


# Modelo del rendimiento de una cuenta de ahorro

> **¿Tiene el dinero el mismo valor a lo largo del tiempo?** La respuesta es *no*. Todos lo hemos vivido.

> Dos situaciones básicas:
1. Inflación: ¿Cuánto dinero necesitabas para comprar unas papas y un refresco hace 10 años? ¿Cuánto necesitas hoy?
2. Interés: no es lo mismo tener \$10000 MXN disponibles hoy a recibir \$10000 MXN en un año, pues los primeros pueden ser invertidos en un negocio o una cuenta bancaria para generar *interés*. Por lo tanto los \$10000 MXN disponibles hoy valen más que los \$10000 MXN que se recibirán en un año.

Referencia:
- Vidaurri Aguirre, Héctor Manuel. *Ingeniería económica básica*, ISBN: 978-607-519-017-4. (Disponible en biblioteca)

Referencias:
- http://www.sympy.org
- http://matplotlib.org
- http://www.numpy.org
- http://ipywidgets.readthedocs.io/en/latest/index.html

___
## Interés
Nos centraremos en como cambia el valor del dinero en el tiempo debido al **interés**. Existen dos tipos:

### Capitalización por interés simple
Este tipo de interés se calcula única y exclusivamente sobre la cantidad original que se invirtió. Como consecuencia, el interés generado no forma parte del dinero que se invierte, es decir, los intereses no ganan intereses.

Suponga que se tiene un capital inicial $C_0$ y se invierte a un plazo de $k$ periodos (pueden ser meses, trimestres, semestres, años...) a una tasa de **interés simple** por periodo $i$. Al final del primer periodo, el capital $C_1$ que se obtiene es:

$$C_1=C_0+iC_0=C_0(1+i).$$

De la misma manera, como el interés solo se calcula sobre el capital inicial, al final del segundo periodo, el capital $C_2$ que se obtiene es:

$$C_2=C_1+iC_0=C_0+iC_0+iC_0=C_0(1+2i).$$

Así, al final del $k-$ésimo periodo, el capital $C_k$ que se obtiene es:

$$C_k=C_{k-1}+iC_0=C_0+kiC_0=C_0(1+ki).$$

> **Ejemplo.** Suponga que se tiene un capital de \$10000 MXN, el cual se pone en un fondo de inversión que paga una tasa de interés simple del 0.8% mensual.

> Si se tiene una meta de ahorro de \$11000 MXN sin inversiones adicionales, ¿cuántos meses se debería dejar invertido el dinero?

```python
# Librería para cálculo numérico
import numpy as np

# Valores dados en el enunciado

# Despejamos k tal que C_k=meta
k =    # Notar el uso de la función ceil (no se puede tener un número no entero de periodos)
k =    # Conversión a entero (para visualización)
C_k =                # Cálculo del capital al final del periodo k
C_k =                # Redondeo con dos cifras decimales

# Imprimimos respuesta en pantalla
print("El número de periodos que se debe dejar invertido el dinero es ", k,". Al final del periodo ", k,
      ", el capital es ", C_k, ".", sep="")
```

> **Actividad.**
> - ¿Qué pasa si el interés no es del 0.8% mensual sino del 1% mensual?
> - ¿Qué pasa si la meta no son \$11000 MXN si no \$12000 MXN?

```python
# Solución
```

> Una gráfica que nos permite ilustrar la situación anterior se puede realizar de la siguiente manera.

```python
# Librerías para gráficos
import matplotlib.pyplot as plt
# Para que se muestren las gráficas en la misma ventana
%matplotlib inline
# Librería para widgets de jupyter
from ipywidgets import *

def interes_simple(C_0, meta, i):
    # Despejamos k
    k = np.ceil((meta/C_0 - 1)/i)           # Notar el uso de la función ceil
    k = k.astype(int)                       # Conversión a entero
    C_k = C_0*(1+k*i)                       # Cálculo del capital al final del periodo k
    C_k = round(C_k, 2)                     # Redondeo con dos cifras decimales

    # Vector de periodos
    kk =
    # Vector de capitales por periodo
    CC =
    # Gráfico
    plt.            # Figura 1, borrar lo que contenga
    plt.      # Se grafica la evolución de los capitales
    plt.    # Se grafica la meta
    plt.xlabel('k')                         # Etiqueta eje x
    plt.ylabel('C_k')                       # Etiqueta eje y
    plt.grid(True)                          # Malla en la gráfica
    plt.show()                              # Mostrar la figura
    print("El número de periodos que se debe dejar invertido el dinero para llegar a la meta de ", meta," es ", k,
          ". Al final del periodo ", k,", el capital es ", C_k, ".", sep="")

interact_manual(interes_simple, C_0=fixed(10000), meta=(10000,12000,100), i=fixed(0.008));
```

Como se esperaba, el capital en el $k-$ésimo periodo $C_k=C_0(1+ki)$ crece linealmente con $k$.

### Capitalización por interés compuesto
El capital que genera el interés simple permanece constante todo el tiempo de duración de la inversión. En cambio, el que produce el interés compuesto en un periodo se convierte en capital en el siguiente periodo. Esto es, el interés generado al final de un periodo se reinvierte para el siguiente periodo para también producir interés.

Suponga que se tiene un capital inicial $C_0$, y se va a ceder el uso de este capital por un periodo de tiempo determinado a una tasa de interés $i$. El capital que se obtiene al final del primer periodo $C_1$ se puede calcular por

$$C_1=C_0(1+i).$$

Si la anterior suma se vuelve a ceder a la misma tasa de interés, al final del periodo dos el capital $C_2$ es

$$C_2=C_1(1+i)=C_0(1+i)^2.$$

Si se repite el anterior proceso $k$ veces, el capital al final del $k-$ésimo periodo $C_k$ es

$$C_k=C_{k-1}(1+i)=C_0(1+i)^k.$$

**Referencia**:
- https://es.wikipedia.org/wiki/Inter%C3%A9s_compuesto.

> **Ejemplo.** Suponga que se tiene un capital de \$10000 MXN, el cual se pone en un fondo de inversión que paga una tasa de interés del 0.8% mensual.

> Si se tiene una meta de ahorro de \$11000 MXN sin inversiones adicionales, ¿cuántos meses se debería dejar invertido el dinero?

> Muestre una gráfica que ilustre la situación.

```python
def interes_compuesto(C_0, meta, i):
    # Despejamos k
    k = np.ceil(np.log(meta/C_0)/np.log(1+i))
    k = k.astype(int)
    C_k = C_0*(1+i)**k                      # Cálculo del capital al final del periodo k
    C_k = round(C_k,2)                      # Redondeo con dos cifras decimales
    # Vector de periodos
    kk = np.linspace(0,k,k+1)
    # Vector de capitales por periodo
    CC = C_0*(1+i)**kk
    # Gráfico
    plt.figure(num=1); plt.clf()            # Figura 1, borrar lo que contenga
    plt.plot(kk, CC,'*',linewidth=3.0)      # Se grafica la evolución de los capitales
    plt.plot(kk,meta*np.ones(k+1),'--k')    # Se grafica la meta
    plt.xlabel('k')                         # Etiqueta eje x
    plt.ylabel('C_k')                       # Etiqueta eje y
    plt.grid(True)                          # Malla en la gráfica
    plt.show()                              # Mostrar la figura
    print("El número de periodos que se debe dejar invertido el dinero para llegar a la meta de ", meta," es ", k,
          ". Al final del periodo ", k,", el capital es ", C_k, ".", sep="")

interact_manual(

```

El capital en el $k-$ésimo periodo $C_k=C_0(1+i)^k$ crece de manera exponencial con $k$.

> **Actividad.**
> - Modificar el código anterior para dejar fija la meta de ahorro y variar la tasa de interés compuesta.

### Capitalización continua de intereses
La capitalización continua se considera un tipo de capitalización compuesta, en la que a cada instante de tiempo $t$ se se capitalizan los intereses. Es decir, la frecuencia de capitalización es infinita (o, equivalentemente, el periodo de capitalización tiende a cero).

Suponga que se tiene un capital inicial $C_0$, y que el capital acumulado en el tiempo $t$ es $C(t)$. Queremos saber cuanto será el capital pasado un periodo de tiempo $\Delta t$, dado que la tasa de interés efectiva para este periodo de tiempo es $i$. De acuerdo a lo anterior tenemos

$$C(t+\Delta t)=C(t)(1+i)=C(t)(1+r\Delta t),$$

donde $r=\frac{i}{\Delta t}$ es la tasa de interés instantánea. Manipulando la anterior expresión, obtenemos

$$\frac{\log(C(t+\Delta t))-\log(C(t))}{\Delta t}=\frac{\log((1+r\Delta t))}{\Delta t}.$$

Haciendo $\Delta t\to 0$, obtenemos la siguiente ecuación diferencial

$$\frac{d C(t)}{dt}=r\; C(t),$$

sujeta a la condición inicial (monto o capital inicial) $C(0)=C_0$.

La anterior, es una ecuación diferencial lineal de primer orden, para la cual se puede calcular la *solución analítica*.

```python
# Librería de cálculo simbólico
import sympy as sym

# Para imprimir en formato TeX
from sympy import init_printing; init_printing(use_latex='mathjax')
from IPython.display import display

# Símbolos t(para el tiempo) y r(para el interés instantáneo)
#t, r = sym.symbols('t r')
# Otra forma de hacer lo anterior

```

```python
 # Ecuación diferencial
 # Mostrar ecuación
 # Resolver
```

con $C_1=C_0$.

La equivalencia entre la tasa de interés compuesta $i$ y la tasa de interés instantánea $r$ viene dada por

$$e^r=1+i.$$

___
¿Cómo podemos calcular la *solución numérica*?

```python
# Librerías para integración numérica
from scipy.integrate import odeint

# Modelo de capitalización continua

```

```python
def interes_continuo(C_0, meta, r):
    # Despejamos t
    t = np.log(meta/C_0)/r
    # Vector de periodos
    tt = np.linspace(0,t,100)
    # Vector de capitales por periodo
---->

    # Gráfico
    plt.figure(num=1); plt.clf()                # Figura 1, borrar lo que contenga
    plt.plot(tt, CC,'-',linewidth=3.0)          # Se grafica la evolución de los capitales
    plt.plot(tt,meta*np.ones(len(tt)),'--k')    # Se grafica la meta
    plt.xlabel('t')                             # Etiqueta eje x
    plt.ylabel('C(t)')                          # Etiqueta eje y
    plt.grid(True)                              # Malla en la gráfica
    plt.show()                                  # Mostrar la figura

```

Ver que lo anterior es una aproximación continua del modelo discreto de interés continuo cuando la frecuencia de capitalización tiende a infinito

> **Actividad.**
> - Averiguar tasas de interés reales en algún banco y proyectar un ahorro mensual para que al terminar su carrera tengan \$50000 MXN en su cuenta.

```python
!open .
```

```python

```