# Numerical exploration of the distribution moments for mRNA and protein.

(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 glob
import cloudpickle
import os

# Our numerical workhorses
import numpy as np
import scipy as sp
import pandas as pd

# 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.
%config InlineBackend.figure_formats = {'png', 'retina'}

figdir = '../../fig/chemical_master_moments/'
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{rp}{r _p}$
$\newcommand{gm}{\gamma _m}$
$\newcommand{gp}{\gamma _p}$
$\newcommand{ee}[1]{\left\langle #1 \right\rangle}$
$\newcommand{foldchange}{\text{fold-change}}$
$\newcommand{fano}{\text{fano-factor}}$
$\newcommand{Var}{\text{Var}}$

## Exploring the mRNA and protein distribution moments

Now that we have computed the mRNA and protein distribution moments for the two- and the three-state promoter let's numerically explore the analytical results.

### Import `lambdify` functions using `cloudpickle`.

When the analytical results were derived we generated `lambdify` functions to explore these results numerically and export them into a file using the `cloudpickle` library. Let's now import these functions into this notebook to make use of them.

```python
# Unregulated two-state promoter
# Parameters are feed in the following order:
# (kp_on, kp_off, rm, gm, rp, gp)
with open('./two_state_moments_lambdify.dill', 'rb') as file:
    mom_unreg_dict = cloudpickle.load(file)

# Regulated three-state promoter
# Parameters are feed in the following order:
# (kr_on, kr_off, kp_on, kp_off, rm, gm, rp, gp)
with open('./three_state_moments_lambdify.dill', 'rb') as file:
    mom_reg_dict = cloudpickle.load(file)
```

## Defining fano-factor and $\eta^2$ function.

Two interesting quantities is worth exploring is the fano-factor (variance / mean) and the noise-square function $\eta^2$ (variance / mean$^2$). Each of them have their experimental and theoretical advantages.

Specifically the fano-factor is a good metric that can tell us how "non-Poissonian" a distribution is as we expect this quantity to be 1 for the case of a Poisson distribution. If one measures absolute number of molecules the fano-factor is dimensionless because both the variance and the mean have no units (counts of *something* is not a unit). This is useful for example if one is able to count mRNA per cell using single molecule mRNA FISH or some protein counting technique. But if one measures fluorescence in a microscope without a conversion between arbitrary units of fluorescence (a.u.) and absolute molecule count the fano-factor presents theoretical complications.

To see this let $N$ be the number of molecules counted, then if the relationship between arbitrary units and these count is linear we have that the intensity $I$ of a cell with $N$ molecules is given by
$$
I = \alpha N,
\label{calib_factor}
$$
where $\alpha = \left[{\text{a.u.} \over \text{molecule count}} \right]$ is the conversion factor between units of fluorescence and molecule count.
The fano factor is then of the form
$$
\fano = {\text{Var}(N) \over \ee{N}}.
$$
Using Eq. \ref{calib_factor} we have
$$
\fano = {\Var(I \alpha^{-1}) \over \ee{I \alpha^{-1}}}.
$$
Since $\Var(a x) = a^2 \Var(x)$ for $a =$ constant we have that
$$
\fano = {\alpha^{-2}\Var(I) \over \alpha^{-1}\ee{I}} =
{\Var(I) \over \alpha \ee{I}},
$$
which is not a dimensionless parameter since it depends on $\alpha$.

On the other hand the noise-squared $\eta^2$ is a dimensionless quantity that does not depend on the calibration factor $\alpha$. This is easy to see if we write
$$
\eta^2 \equiv {\Var(N) \over \ee{N}^2} =
{\Var(I \alpha^{-1}) \over \ee{I \alpha^{-1}}^2},
$$
which can be simplified to
$$
\eta^2 = {\alpha^{-2}\Var(I) \over (\alpha^{-1}\ee{I})^2} =
\Var{I} \over \ee{I}^2.
$$
Since the units in the numerator and the denominator cancel this is a dimensionless quantity.

## Two-state unregulated promoter.

Let's define a function that computes these quantities for the two-state unregulated promoter.

```python
def fano_unreg(kp_on, kp_off, rm, gm, rp, gp, mRNA=True):
    '''
    Computes the fano-factor (variance / mean) for a two-state unregulated
    promoter at the mRNA level
    Parameters
    ----------
    kp_on : float.
        RNAP on rate. [time**-1]
    kp_off : float.
        RNAP off rate. [time**-1]
    rm : float.
        transcription initiation rate. [time**-1]
    gm : float.
        mRNA degradation rate. [time**-1]
    mRNA : bool. Default = True
        Boolean indicating if it should be computed for the mRNA level.
        If false it is computed for the protein level
    Returns
    -------
    mRNA fano-factor for an unregulated promoter.
    '''
    par = [kp_on, kp_off, rm, gm, rp, gp]

    if mRNA:
        return (mom_unreg_dict[(2, 0)](*par) -
                mom_unreg_dict[(1, 0)](*par)**2) / mom_unreg_dict[(1, 0)](*par)
    else:
        return (mom_unreg_dict[(0, 2)](*par) -
                mom_unreg_dict[(0, 1)](*par)**2) / mom_unreg_dict[(1, 0)](*par)

def noise_square_unreg(kp_on, kp_off, rm, gm, rp, gp, mRNA=True):
    '''
    Computes the fano-factor (variance / mean**2) for a two-state unregulated
    promoter at the mRNA level
    Parameters
    ----------
    kp_on : float.
        RNAP on rate. [time**-1]
    kp_off : float.
        RNAP off rate. [time**-1]
    rm : float.
        transcription initiation rate. [time**-1]
    gm : float.
        mRNA degradation rate. [time**-1]
    mRNA : bool. Default = True
        Boolean indicating if it should be computed for the mRNA level.
        If false it is computed for the protein level
    Returns
    -------
    mRNA fano-factor for an unregulated promoter.
    '''
    par = [kp_on, kp_off, rm, gm, rp, gp]

    if mRNA:
        return (mom_unreg_dict[(2, 0)](*par) -
                mom_unreg_dict[(1, 0)](*par)**2) / mom_unreg_dict[(0, 1)](*par)**2
    else:
        return (mom_unreg_dict[(0, 2)](*par) -
                mom_unreg_dict[(0, 1)](*par)**2) / mom_unreg_dict[(0, 1)](*par)**2
```

Let's now list the parameters fit for the *lacUV5* unregulated promoter.

```python
# List the parameters fit for the lacUV5 promoter
par_UV5 = dict(kp_on=5.5, kp_off=28.9, rm=87.6, gm=1)

# define protein degradation rate in units of mRNA degradation rate
gp = 0.000277 / 0.00284
par_UV5['gp'] = gp

# define rp based on the mean protein copy number per mRNA
par_UV5['rp'] = 1000 * par_UV5['gp']
```

An interesting quantity to explore is how the noise would vary as the RNAP binding energy, encoded in the $\kpoff$ rate in the kinetic language is varied. But rather than plotting it as a function of this parameter we will plot it as a function of the mean.

```python
# List array of kpoff values to plot
kp_off_array = np.logspace(-3, 3, 100)
# Copy the dictionary for the lacUV5 promoter
par = par_UV5.copy()
# Change the kp_off value to an array
par['kp_off'] = kp_off_array

# Initialize figure
fig, ax = plt.subplots(2, 2, figsize=(10, 8))
ax = ax.ravel()

# Plot the fano factor as a function of the mean mRNA
ax[0].plot(mom_unreg_dict[(1, 0)](*par.values()), fano_unreg(**par))
ax[0].set_xlabel(r'$\left\langle m \right\rangle$')
ax[0].set_ylabel('fano-factor')

ax[1].plot(mom_unreg_dict[(1, 0)](*par.values()), noise_square_unreg(**par))
ax[1].set_xlabel(r'$\left\langle m \right\rangle$')
ax[1].set_ylabel('noise $\eta^2$')

# Plot the fano factor as a function of the mean mRNA
ax[2].plot(mom_unreg_dict[(0, 1)](*par.values()), fano_unreg(mRNA=False, **par))
ax[2].set_xlabel(r'$\left\langle p \right\rangle$')
ax[2].set_ylabel('fano-factor')

ax[3].plot(mom_unreg_dict[(0, 1)](*par.values()),
           noise_square_unreg(mRNA=False, **par))
ax[3].set_xlabel(r'$\left\langle p \right\rangle$')
ax[3].set_ylabel('noise $\eta^2$')

fig.suptitle('unregulated promoter', **{'y': 1.05, 'fontsize':18})
plt.tight_layout()
```

### Compare two-state promoter protein noise with Shahrezaei & Swain

The noise-squared $\eta^2$ in gene expression defined as
$$
\eta^2 \equiv {Var(\text{gene expression}) \over \ee{\text{gene expression}}^2},
$$
has the advantage of being an adimensional quantity. The fano-factor is adimensional only if the mean and standard deviation are measured in "counts" of molecules. But for the case of microscopy for example there is an arbitrary unit (a.u.) involved that is not canceled when using the fano factor.

Shahrezaei & Swain in their 2008 paper derived an expression for the noise in protein copy numbers of the form
\begin{equation}
\eta^2 = \frac{1}{\langle p \rangle} + \frac{\gp}{\gm} \frac{1}{\langle m \rangle} + \frac{\gp}{\gp + \kpon + \kpoff} \frac{\kpoff}{\kpon}.
\end{equation}

This is derived under the assumption that $\gm \gg \gp$. Let's compare this with the complete analytical result as obtained by computing the moments directly from the master equation.

Let's define both functions to compute the S & S approximation and the full form of the noise.

```python
def noise_square_unreg_swain(kp_on, kp_off, rm, gm, rp, gp):
    '''
    Computes the noise square (variance / mean**2) for a two-state unregulated
    promoter at the protein level as derived by Shahrezaei & Swain PNAS 2008.
    This approximation is valid for gm >> gp.
    Parameters
    ----------
    kp_on : float.
        RNAP on rate. [time**-1]
    kp_off : float.
        RNAP off rate. [time**-1]
    rm : float.
        transcription initiation rate. [time**-1]
    gm : float.
        mRNA degradation rate. [time**-1]
    rp : float.
        translation initiation rate. [time**-1]
    gp : float.
        protein degradation rate. [time**-1]
    Returns
    -------
    protein noise for an unregulated promoter.
    '''
    # Compute the mean mRNA
    mean_m = (rm / gm) * kp_on / (kp_on + kp_off)
    # Compute the mean protein
    mean_p = (rp / gp) * mean_m

    return 1 / mean_p + (gp / gm) * (1 / mean_m) +\
           gp / (gp + kp_on + kp_off) * (kp_off / kp_on)
```

Having defined the functions let's compare them for different values of $\gp$ using the most likely parameters for the *lacUV5* promoter.

```python
# Define a range of gm / gp ratios
gamma = np.logspace(0, 4) # gm / gp

# Copy the UV5 parameters and modify gp
par = par_UV5.copy()
par['gp'] = par['gp'] / gamma
par['rp'] = 1000 * par['gp']

# Plot the ratio of noises
plt.plot(gamma, noise_square_unreg(mRNA=False, **par) /
         noise_square_unreg_swain(**par))

# Indicate with vertical line the most likely value of the gm / gp ratio
plt.vlines(par_UV5['gm'] / par_UV5['gp'], 0.92, 1, linestyle='--')

# Label plot
plt.xscale('log')
plt.xlabel(r'$\gamma_m \; / \; \gamma_p$')
_ = plt.ylabel(r'$\eta^2_{full} \; / \; \eta^2_{S&S}$')
```

From this plot we can see that Shahrezaei & Swain's approximation works the best as the assumption of $\gm \gg \gp$ is better satisfied. From the vertical line we can see that for our assumption the approximation is already very accurate.

### Varying the $\gm / \gp$ ratio

An interesting question is now how much the ratio $\gm / \gp$ affects this noise. Let's plot the noise as we scale this ratio.

```python
# Define array of gp values to explore
gamma_array = np.logspace(-3, 3)

# Copy parameters dictionary
par = par_UV5.copy()

# Initialize array to save fano factor values
noise_square_var_gamma = np.empty_like(gp_array)
# Loop through gp values
for i, gamma in enumerate(gamma_array):
    # Update parameters
    par['gm'] = par_UV5['gp'] * gamma
    par['kp_on'] = par_UV5['kp_on'] * par['gm']
    par['kp_off'] = par_UV5['kp_off'] * par['gm']
    par['rm'] = par_UV5['rm'] * par['gm']
    # rp/gp is the average number of protein/mRNA
    par['rp'] = 1000 * par_UV5['gp']
    # Compute and save noise
    noise_square_var_gamma[i] = noise_square_unreg_p(**par)

plt.plot(gamma_array, noise_square_var_gamma)
plt.xlabel(r'$\gamma_m / \gamma_p$')
plt.ylabel(r'noise square $\eta^2$')
plt.xscale('log')
```

This ratio of degradation rates does have an effect in the noise at the protein level.

Since we do not know with certainty the value of these degradation rates, let's try to obtain a good estimate for this ratio given our experimental data.

### Estimating the $\gm / \gp$ ratio from experimental data.

Given the data that we have on unregulated promoters we can try to estimate the ratio of the degradation rates $\gm / \gp$. For this we will import the single-cell microscopy data for the $\Delta$*lacI* strains and compute the noise-squaare $\eta^2$.

```python
# Define directory where data is stored
expdir = '../../data/microscopy/'

# Define directory where the bootstrap data was stored
bootsdir = '../channcap_exp/'

# List files of data taken exclusively for this experiment
bootsfiles = [x for x in os.listdir(bootsdir)
              if ('channel_capacity_experiment' in x) &
              ('ipynb' not in x)]

# Extract dates for these experiments
project_dates = [x.split('_')[0] for x in bootsfiles]

# List files with the bootstrap sampling of the
files = glob.glob(expdir + '*csv')

files = glob.glob('../../data/csv_microscopy/*csv')# + mwc_files

# Extract dates from these files
file_dates = [file.split('/')[-1] for file in files]
file_dates = [file.split('_')[0] for file in file_dates]

# Filter for files taken exclusively for this experiment.
files = [file for i, file in enumerate(files) if file_dates[i] in project_dates]

df_micro = pd.concat(pd.read_csv(f, comment='#') for f in files)

# Separate the auto and delta strains
df_delta = df_micro[(df_micro['rbs'] == 'auto') |
                    (df_micro['rbs'] == 'delta')]

df_delta[['date', 'operator', 'rbs', 'mean_intensity']].head()
```

      date
      operator
      rbs
      mean_intensity

      0
      20161204
      O3
      auto
      52.562979

      1
      20161204
      O3
      auto
      53.070565

      2
      20161204
      O3
      auto
      52.323744

      3
      20161204
      O3
      auto
      51.013142

      4
      20161204
      O3
      auto
      44.325267

Let's now compute the noise square $\eta^2$

```python
# group by date and by IPTG concentration
df_group = df_delta.groupby(['date', 'IPTG_uM'])

# Initialize data frame to save the noise
df_delta_noise = pd.DataFrame(columns=['date', 'operator', 'binding_energy',
                                      'mean_intensity', 'variance',
                                       'noise_square'])
for (date, c), data in df_group:
    # Extract the mean autofluorescence
    I_auto = data[data.rbs == 'auto'].mean_intensity.mean()

    # Extract the strain fluorescence measurements
    delta_data = data[data.rbs == 'delta']

    # Append the require info
    delta_info = [date, delta_data.operator.unique()[0],
                   delta_data.binding_energy.unique()[0],
                  (delta_data.mean_intensity - I_auto).mean(),
                  (delta_data.mean_intensity - I_auto).var(ddof=1)]
    # Compute the noise
    delta_info.append(delta_info[-1] / delta_info[-2]**2)
    # Convert to a pandas series to attach to the dataframe
    delta_info = pd.Series(delta_info, index=['date', 'operator',
                                              'binding_energy',
                                              'mean_intensity',
                                              'variance', 'noise_square'])
    # Append to the info to the data frame
    df_delta_noise = df_delta_noise.append(delta_info, ignore_index=True)

df_delta_noise.tail()
```

      date
      operator
      binding_energy
      mean_intensity
      variance
      noise_square

      7
      20180321
      O2
      -13.9
      2824.722626
      1.304615e+06
      0.163505

      8
      20180326
      O2
      -13.9
      2419.937410
      5.919979e+05
      0.101091

      9
      20180326
      O2
      -13.9
      2446.035749
      5.125456e+05
      0.085666

      10
      20180327
      O3
      -9.7
      2803.400709
      1.663008e+06
      0.211604

      11
      20180327
      O3
      -9.7
      2185.627027
      5.592565e+05
      0.117074

Let's now define a function that computes the residuals between the theoretical and the experimental value in order to find the ratio of degradation rates that better fits the available data.

```python
def resid(gamma, noise_square_exp, unreg_par):
    '''
    Computes the residuals of the theoretical and the experimental noise-square
    for a two-state unregulated promoter
    '''
    par = unreg_par.copy()

    # Update parameters
    par['gm'] = unreg_par['gp'] * gamma
    par['kp_on'] = unreg_par['kp_on'] * par['gm']
    par['kp_off'] = unreg_par['kp_off'] * par['gm']
    par['rm'] = unreg_par['rm'] * par['gm']
    par['rp'] = 1000 * unreg_par['gp']
    # Compute and save noise
    return  noise_square_exp - noise_square_unreg_p(**par)
```

```python
# Copy the parameters from the lacUV5 promoter
par = par_UV5.copy()

# Initial guess for the gm / gp ratio
p0 = 100

popt = sp.optimize.least_squares(resid, p0,
       args=(df_delta_noise.noise_square.values, par))
popt.x
```

    array([ 0.224307])

This number that we are obtaining does not make sense. We know that the protein has a much longer life-time than mRNA so we expected the ratio of degradation rates to be much larger than 1.

If we were to consider that this ratio $\gm / \gp$ is of order 10, then assumed that the microscopy reading added a constant noise such that these measurements were of the form
$$
\eta_{\text{data}}^2 = \eta_{\text{measurement}}^2 +
                       \eta_{\text{gene expression}}^2,
$$
where $\eta_{\text{measurement}}^2$ is the constant noise added by the microscope and $\eta_{\text{gene expression}}^2$ is the intrinsic noise given by the physics of gene expression, we could have that $\eta_{\text{measurement}}^2$ from this data.

```python
# Copy the parameters
par = par_UV5.copy()
# Define the ratio gm / gp
gamma = 10

# Update parameters
par['gm'] = par_UV5['gp'] * gamma
par['kp_on'] = par_UV5['kp_on'] * par['gm']
par['kp_off'] = par_UV5['kp_off'] * par['gm']
par['rm'] = par_UV5['rm'] * par['gm']
par['rp'] = 1000 * par_UV5['gp']

df_delta_noise.noise_square.mean() -  noise_square_unreg_p(**par)
```

    0.16230074477003661

### The effect of the protein production rate $\rm$ on the noise

Another interesting explanation for this deviation...

```python
# Define array of gp values to explore
rp_array = np.logspace(0, 6)

# Define different rp / gp ratios
rp_gp_ratio = np.logspace(1, 5, 5).astype(int)

# Define colors
colors = sns.color_palette('Purples', n_colors=len(rp_gp_ratio))

# Initialize array to save noise values
noise_square_var_rp = np.empty([len(rp_array), len(rp_gp_ratio)])
# Initialize array to save gm/gp values
gm_gp = np.empty_like(noise_square_var_rp)

# Initialize plot
fig, ax = plt.subplots(2, 1, figsize=(8, 8), sharex=True)

# Loop through rp / gp ratios
for j, rp_gp in enumerate(rp_gp_ratio):
    # Copy parameters dictionary
    par = par_UV5.copy()

    # Loop through rp values
    for i, rp in enumerate(rp_array):

        # Update parameters
        par['rp'] = rp
        par['gp'] = rp / rp_gp

        # Compute and save noise
        noise_square_var_rp[i, j] = noise_square_unreg_p(**par)
        # Comput and save gm / gp
        gm_gp[i, j] = par['gm'] / par['gp']

    ax[0].plot(rp_array, noise_square_var_rp[:, j], label=str(rp_gp),
             color=colors[j])
    ax[1].plot(rp_array, gm_gp[:, j], label=str(rp_gp),
             color=colors[j])

ax[0].set_ylabel(r'noise square $\eta^2$')
ax[0].legend(title=r'$r_p / \gamma_p$')
ax[0].set_xscale('log')

ax[1].set_xlabel(r'$r_p$')
ax[1].set_ylabel(r'$\gamma_m\; /\; \gamma_p$')
ax[1].legend(title=r'$r_p / \gamma_p$')
ax[1].set_xscale('log')
ax[1].set_yscale('log')

plt.tight_layout()
```

```python
# Define array of gp values to explore
rp_array = np.logspace(-1, 1)

# Copy parameters dictionary
par = par_UV5.copy()

# Initialize array to save fano factor values
noise_square_var_rp = np.empty_like(rp_array)

# Loop through gp values
for i, rp in enumerate(rp_array):
    # Update parameters
    par['rp'] = rp
    # Compute and save noise
    noise_square_var_rp[i] = noise_square_unreg_p(**par)

plt.plot(rp_array / par['gp'], noise_square_var_rp)
plt.xlabel(r'$r_p\; /\; \gamma_p$')
plt.ylabel(r'noise square $\eta^2$')
plt.xscale('log')
```

## Explore the noise for the regulated three-state promoter.

Let's now explore how the noise scales as the repressor copy number. The way that the repressor copy number enters the kinetic scheme is by assuming that the $\kron$ rate is a diffusion limited rate of the form
$$
\kron = k_o \left[ R \right].
$$
The constant $k_o$ was indirectly inferred by the Sunney Xie lab and quoted by Jones and Brewster as $k_o = 2.7 \times 10^{-3} (\text{s nM})^{-1}$.

The value of $\kroff$ is then constrained by the value of the binding energy as inferred using equilibrium statistical mechanics. For the stat. mech. framework we have that the fold-change in gene expression is given by
$$
\foldchange = \frac{1}{1 + \frac{R \cdot p_{act}}{N_{NS}} e^{-\beta \Delta \epsilon_r}},
$$
where $R$ is the absolute copy number of repressors per cell, $N_{NS}$ is the number of non-specific binding sites for the repressors (in our case the entire *E. coli genome* $4.6 \times 10^6$), $\beta$ is the inverse temperature times the Boltzmann constant and $\Delta \epsilon_r$ is the binding energy of the repressor to the operator binding site.

In the case of the three-state promoter the fold-change is given by
$$
\foldchange = {1 \over 1 + {\kron \over \kroff}
               \left( {\kpoff \over \kpoff + \kpon} \right)}
            = {1 \over 1 + {k_o [R] p_{act} \over \kroff}
               \left( {\kpoff \over \kpoff + \kpon} \right)}.
$$

Given that these two equation have the same functional form we can conclude that
$$
\frac{R \cdot p_{act}}{N_{NS}} e^{-\beta \Delta \epsilon_r} =
{k_o [R] p_{act} \over \kroff}
               \left( {\kpoff \over \kpoff + \kpon} \right).
$$
If we solve for $\kroff$ and convert the absolute number or repressors $R$ into a concentration $[R]$ we find that
$$
\kroff = 1.66 \cdot k_0 \cdot N_{NS} e^{\beta \Delta \epsilon_r}
          \left( {\kpoff \over \kpoff + \kpon} \right).
$$

Given this result let's define a function that computes $\kroff$ as a function of the binding energies and the RNAP rates.

```python
def kr_off_fun(eRA, k0=2.7E-3, kp_on=0.017, kp_off=0.132, Nns=4.6E6):
    '''
    Returns the off rate of the repressor as a function of the stat. mech.
    binding energy and the RNAP on and off rates
    Parameters
    ----------
    eRA : float.
        Repressor binding energies [kbT]
    k0 : float.
        Diffusion limited constant [s**-1 nM**-1]
    kp_on : float.
        RNAP on rate. [time**-1]
    kp_off : float.
        RNAP off rate. [time**-1]
    Nns : float.
        Number of non-specific binding sites
    Returns
    -------
    Repressor off rate
    '''
    return 1.66 * k0 * Nns * np.exp(eRA) * kp_off / (kp_off + kp_on)
```

Let's now define a function that computes the noise square $\eta^2$ for the regulated promoter as a function of repressor copy number and inducer concentration. For this we will assume that the number of active repressors is given by the equilibrium value as determiend by $R_{act} = R \cdot p_{act}$, where $p_{act}$ is given by the MWC model.

```python
def noise_square_reg_p(C, rep, eRA, rm, gm, rp, gp,
                k0=2.7E-3, kp_on=0.017, kp_off=0.132, Nns=4.6E6,
                ka=139, ki=0.53, epsilon=4.5):
    '''
    Computes the noise square in protein copy number (variance / mean**2)
    as a function  of all the parameters that go into the chemical master
    equation.

    Parameters
    ----------
    C : array-like.
        Concentration at which evaluate the probability.
    rep: float.
        repressor copy number per cell.
    eRA : float.
        Repressor binding energy [kBT]
    rm : float.
        transcription initiation rate. [time**-1]
    gm : float.
        mRNA degradation rate. [time**-1]
    rp : float.
        translation initiation rate. [time**-1]
    gp : float.
        protein degradation rate. [time**-1]
    k0 : float.
        diffusion limited rate of a repressor binding the promoter
    kp_on : float.
        RNAP on rate. [time**-1]
    kp_off : float.
        RNAP off rate. [time**-1]
    Nns : float.
        Number of non-specific binding sites
    ki, ka : float.
        dissociation constants for the inactive and active states respectively
        in the MWC model of the lac repressor.
    epsilon : float.
        energetic barrier between the inactive and the active state.

    Returns
    -------
    eta the noise in protein distribution
    '''
    # Convert C into np.array
    C = np.array(C)

    # Calculate the repressor on rate including the MWC model
    kr_on = k0 * rep * chann_cap.p_act(C, ka, ki, epsilon)
    # Compute the repressor off-rate based on the on-rate and the binding energy
    kr_off = kr_off_fun(eRA, k0, kp_on, kp_off, Nns)

    return second_reg_p(kr_on, kr_off, kp_on, kp_off, rm, gm, rp, gp) /\
           first_reg_p(kr_on, kr_off, kp_on, kp_off, rm, gm, rp, gp)**2 - 1
```

### Noise for varying repressor copy number

Now that we have this function let's compute the noise for varying number of repressors at fix binding energy.

```python
# Copy the parameters fit for the lacUV5 promoter
par = par_UV5.copy()
# Define the k0 parameters in units of the mRNA degradation time
k0_norm = 2.7E-3 / 0.00284
par['k0'] = k0_norm

# Define the IPTG concentrations to evaluate the theoretical noise
IPTG = np.logspace(-1, 4, 100)

# Define the repressor copy numbers to use
repressors = [22, 60, 124, 260, 1220, 1740]
# Define the binding energy
eRA = -15.3

# Update it according to the gm/gp ratio
# (useful when exploring different ratios)
gamma = par_UV5['gm'] / par_UV5['gp']
# Update parameters
par['gm'] = par_UV5['gp'] * gamma
par['kp_on'] = par_UV5['kp_on'] * par['gm']
par['kp_off'] = par_UV5['kp_off'] * par['gm']
par['rm'] = par_UV5['rm'] * par['gm']
par['rp'] = 1000 * par_UV5['gp']
par['eRA'] = eRA

for rep in repressors:
    par['rep'] = rep
    noise_square = noise_square_reg_p(IPTG, **par)
    plt.plot(IPTG, np.sqrt(noise_square), label='{:d}'.format(rep))

plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.legend(title='rep/cell')
plt.title('operator O1')

```

    Text(0.5,1,'operator O1')

### Noise for varying binding energy

Let's now compute the noise for varying binding energy at fix repressor copy number

```python
# Define the IPTG concentrations to evaluate the theoretical noise
IPTG = np.logspace(-1, 4, 100)
# Define the repressor copy numbers to use
energies = dict(zip(['O1', 'O2', 'O3'], [-15.3, -13.9, -9.7]))

# Copy parameter set and update it according to the gm/gp ratio
par = par_UV5.copy()
# Define the k0 parameters in units of the mRNA degradation time
k0_norm = 2.7E-3 / 0.00284
par['k0'] = k0_norm

# Update it according to the gm/gp ratio
# (useful when exploring different ratios)
gamma = par_UV5['gm'] / par_UV5['gp']
# Update parameters
par['gm'] = par_UV5['gp'] * gamma
par['kp_on'] = par_UV5['kp_on'] * par['gm']
par['kp_off'] = par_UV5['kp_off'] * par['gm']
par['rm'] = par_UV5['rm'] * par['gm']
par['rp'] = 1000 * par_UV5['gp']
par['rep'] = 260

for energy, eRA in energies.items():
    par['eRA'] = eRA
    noise_square = noise_square_reg_p(IPTG, **par)
    plt.plot(IPTG, np.sqrt(noise_square), label='{:s}'.format(energy))

plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.ylim(bottom=0)
plt.legend(title='operator')
plt.title(r'$R = 260$ rep/cell')
```

    Text(0.5,1,'$R = 260$ rep/cell')

# Testing three-state model with experimental data.

Let's now see how the three-state model performs when trying to predict the experimental noise measured with microscopy for different repressor copy numbers, binding energies and inducer concentrations.

First let's load the data.

```python
# Read the tidy-data frame
files = glob.glob('../../data/csv_microscopy/*csv')# + mwc_files
df_micro = pd.concat(pd.read_csv(f, comment='#') for f in files if 'Oid' not in f)
df_micro[['date', 'operator', 'rbs', 'mean_intensity']].head()
```

      date
      operator
      rbs
      mean_intensity

      0
      20161204
      O3
      auto
      52.562979

      1
      20161204
      O3
      auto
      53.070565

      2
      20161204
      O3
      auto
      52.323744

      3
      20161204
      O3
      auto
      51.013142

      4
      20161204
      O3
      auto
      44.325267

Now let's compute the noise square $\eta^2$.

```python
# group by date and by IPTG concentration
df_group = df_micro.groupby(['date'])

# Initialize data frame to save the noise
var = ['date', 'IPTG_uM','operator', 'binding_energy', 'rbs', 'repressors',
       'mean', 'std', 'noise_square']
df_noise = pd.DataFrame(columns=var)
for date, data in df_group:
    # Extract the mean autofluorescence
    I_auto = data[data.rbs == 'auto'].mean_intensity.mean()

    # Extract the strain fluorescence measurements
    strain_data = data[(data.rbs != 'auto') & (data.rbs != 'delta')]

    # Group data by IPTG measurement
    data_group = strain_data.groupby('IPTG_uM')
    for inducer, data_inducer in data_group:
        # Append the require info
        strain_info = [date, inducer, data_inducer.operator.unique()[0],
                       data_inducer.binding_energy.unique()[0],
                       data_inducer.rbs.unique()[0],
                       data_inducer.repressors.unique()[0] * 2,
                      (data_inducer.mean_intensity - I_auto).mean(),
                      (data_inducer.mean_intensity - I_auto).var(ddof=1)]
        # Compute the noise
        strain_info.append(strain_info[-1] / strain_info[-2]**2)
        # Convert to a pandas series to attach to the dataframe
        strain_info = pd.Series(strain_info, index=var)
        # Append to the info to the data frame
        df_noise = df_noise.append(strain_info, ignore_index=True)

df_noise.head()
```

      date
      IPTG_uM
      operator
      binding_energy
      rbs
      repressors
      mean
      std
      noise_square

      0
      20161118
      0.0
      O2
      -13.9
      RBS1027
      260
      28.660991
      1175.976490
      1.431582

      1
      20161118
      0.1
      O2
      -13.9
      RBS1027
      260
      29.234495
      1210.486104
      1.416344

      2
      20161118
      5.0
      O2
      -13.9
      RBS1027
      260
      71.514148
      2829.580422
      0.553271

      3
      20161118
      10.0
      O2
      -13.9
      RBS1027
      260
      129.849144
      7627.019220
      0.452352

      4
      20161118
      25.0
      O2
      -13.9
      RBS1027
      260
      391.288683
      57668.980651
      0.376658

Let's now plot the noise as a function of inducer concentration for each of the measured strains.

```python
# Group data frame by operator and rbs
df_group = df_noise.groupby(['operator', 'rbs'])

# Loop through groops plotting the noise as a function of IPTG concentration
for group, data in df_group:
    plt.plot(data.IPTG_uM, np.sqrt(data.noise_square), lw=0, marker='o',
            label='{0:s}-{1:s}'.format(group[0], group[1]))
# Label the plot
plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.legend(title='strain')
plt.xlim(left=-5E-1)
```

    (-0.5, 8402.7716270925066)

### Testing the parameters

Let's now test the parameters that we think are the best guess given all of the combined experimental evidence so far.

```python
# Define the IPTG concentrations to evaluate the theoretical noise
IPTG = np.logspace(-1, 4, 100)

# Copy the parameters fit for the lacUV5 promoter
par = par_UV5.copy()
# Define the k0 parameters in units of the mRNA degradation time
k0_norm = 2.7E-3 / 0.00284
par['k0'] = k0_norm

# Group by operator and rbs
df_group = df_noise.groupby(['operator', 'rbs'])

colors = sns.color_palette('colorblind', n_colors=len(df_group))

i = 0
for group, data in df_group:
    # Extract energy and number of repressors
    par['eRA'] = data.binding_energy.unique()[0]
    # Indicate the repressor copy number
    par['rep'] = data.repressors.unique()[0] * 1.66

    # Compute the theoretical noise for different IPTG concentrations
    noise_thry = np.sqrt(noise_square_reg_p(IPTG, **par))
    # Plot the theoretical noise
    plt.plot(IPTG, noise_thry, color=colors[i])
    # Polot the experimental noise
    plt.plot(data.IPTG_uM, np.sqrt(data.noise_square.values),
             lw=0, marker='o', label='{0:s}-{1:s}'.format(group[0], group[1]),
             color=colors[i])
    i+=1
plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.legend(title='strain')
plt.xlim(left=-5E-1)
# plt.ylim(bottom=0)
```

    (-0.5, 17398.189457156805)

IT seems that the theoretical predictions are systematically off the experimental data. But if we assume that the inferred discrepancy between our theoretical prediction and our experimental data for the unregulated promoter can be extended to the regulated case, we could add this extra noise to our theoretical predictions to see if that could account partially for this discrepancy.

```python
# Define the IPTG concentrations to evaluate the theoretical noise
IPTG = np.logspace(-1, 4, 100)

# Copy the parameters fit for the lacUV5 promoter
par = par_UV5.copy()
# Define the k0 parameters in units of the mRNA degradation time
k0_norm = 2.7E-3 / 0.00284
par['k0'] = k0_norm

# Group by operator and rbs
df_group = df_noise.groupby(['operator', 'rbs'])

colors = sns.color_palette('colorblind', n_colors=len(df_group))

i = 0
for group, data in df_group:
    # Extract energy and number of repressors
    par['eRA'] = data.binding_energy.unique()[0]
    # Indicate the repressor copy number
    par['rep'] = data.repressors.unique()[0] * 1.66

    # Compute the theoretical noise for different IPTG concentrations
    noise_thry = np.sqrt(noise_square_reg_p(IPTG, **par) + .177)
    # Plot the theoretical noise
    plt.plot(IPTG, noise_thry, color=colors[i])
    # Polot the experimental noise
    plt.plot(data.IPTG_uM, np.sqrt(data.noise_square.values),
             lw=0, marker='o', label='{0:s}-{1:s}'.format(group[0], group[1]),
             color=colors[i])
    i+=1
plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.legend(title='strain')
plt.xlim(left=-5E-1)
plt.ylim(bottom=0)
```

    (0, 1.5750402571984381)

The predictions are still off, but we need to obtain more data to properly asses this.

```python
def resid(k0, gamma, df, reg_par, correction=.177):
    '''
    Computes the residuals of the theoretical and the experimental noise-square
    for a two-state unregulated promoter
    '''
    par = reg_par.copy()
    # Update parameters
    par['gm'] = reg_par['gp'] * gamma
    par['kp_on'] = reg_par['kp_on'] * par['gm']
    par['kp_off'] = reg_par['kp_off'] * par['gm']
    par['rm'] = reg_par['rm'] * par['gm']
    par['rp'] = 1000 * reg_par['gp']
    par['k0'] = k0 * par['gp']

    # Group by date and concentration
    noise_group = df.groupby(['date','IPTG_uM'])

    residuals = []
    # Loop through groups returning the residuals
    for group, data in noise_group:
        # Specify parameters for specific data set
        binding_energy = data.binding_energy.unique()[0]
        repressors = data.repressors.unique()[0]
        par['rep'] = 2 * 1.66 * repressors
        par['eRA'] = binding_energy
        par['C'] = group[1]
        # Compute experimental noise
        avg_noise = data.noise_square.values - correction
        # Append linear residuals
        residuals.append(noise_square_reg_p(**par) - avg_noise)

    return np.array(residuals).ravel()
```

```python
# List the parameters fit for the lacUV5 promoter
par_UV5 = dict(kp_on=5.858, kp_off=46.554, rm=124.706,
               gm=1, gp=1)

gamma = 10
# Initial guess for the k0 value
p0 = 1
df = df_noise[(df_noise.operator == 'O2') & (df_noise.rbs == 'RBS1027')]
popt = scipy.optimize.least_squares(resid, p0,
       args=(gamma, df, par_UV5, .177))
popt.x
```

    array([ 4437.33294678])

```python
# List the parameters fit for the lacUV5 promoter
par_UV5 = dict(kp_on=5.5, kp_off=28.9, rm=87.6,
               gm=1, gp=1, k0=2.7E-3)

# Copy parameter set and update it according to the gm/gp ratio
par = par_UV5.copy()
gamma = 10
k0 = 10
# Update parameters
par['gm'] = par_UV5['gp'] * gamma
par['kp_on'] = par_UV5['kp_on'] * par['gm']
par['kp_off'] = par_UV5['kp_off'] * par['gm']
par['rm'] = par_UV5['rm'] * par['gm']
par['rp'] = 1000 * par_UV5['gp']
par['k0'] = k0 * par['gp']

# Group by operator and rbs
df_group = df_noise.groupby(['operator', 'rbs'])

colors = sns.color_palette('colorblind', n_colors=len(df_group))

i = 0
for group, data in df_group:
    # Extract energy and number of repressors
    par['eRA'] = data.binding_energy.unique()[0]
    # Indicate the repressor copy number
    par['rep'] = data.repressors.unique()[0] * 1.66 * 2

    # Compute the theoretical noise for different IPTG concentrations
    noise_thry = np.sqrt(noise_square_reg_p(IPTG, **par))
    # Plot the theoretical noise
    plt.plot(IPTG, noise_thry, color=colors[i])
    # Polot the experimental noise
    plt.plot(data.IPTG_uM, np.sqrt(data.noise_square.values - .177),
             lw=0, marker='o', label='{0:s}-{1:s}'.format(group[0], group[1]),
             color=colors[i])
    i+=1
plt.xscale('symlog')
plt.xlabel('IPTG ($\mu$M)')
plt.ylabel('noise $\eta$')
plt.legend(title='strain')
plt.xlim(left=-5E-1)
# plt.ylim(bottom=0)
```

    /Users/razo/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:34: RuntimeWarning: invalid value encountered in sqrt

    (-0.5, 17398.189457156805)


First composition derivatives from SRK
======================================

$$\frac{\partial{} \alpha^r}{{}{\partial \delta}} = \frac{1}{R T_{r}} \left(- R T_{r}\frac{b}{b\rho_r\delta - 1} - \rho_{r} \tau\frac{ a{\left (\tau \right )}}{b \delta \rho_{r} + 1}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{} \alpha^r}{{}{\partial \delta}}\right) = \frac{1}{R T_{r}} \left(-RT_r \frac{(b\delta\rho_r-1)\frac{db}{dx_i}-b\rho_r\delta\frac{db}{dx_i}}{(b \rho_{r}\delta - 1)^2} - \rho_{r} \tau \frac{(b \delta \rho_{r} + 1)\frac{\partial a}{\partial x_i}-a\delta\rho_r\frac{\partial b}{\partial x_i}}{(b \delta \rho_{r} + 1)^2}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{} \alpha^r}{{}{\partial \delta}}\right) = \frac{1}{R T_{r}} \left( \frac{db}{dx_i}\frac{RT_r}{(b \rho_{r}\delta - 1)^2} - \rho_{r} \tau \frac{(b \delta \rho_{r} + 1)\frac{\partial a}{\partial x_i}-a\delta\rho_r\frac{\partial b}{\partial x_i}}{(b \delta \rho_{r} + 1)^2}\right)$$

```python

```

$$\frac{\partial{} \alpha^r}{{\partial \tau}{}} = \frac{1}{R T_{r}} \left(\left(- \tau\frac{\frac{d}{d \tau} a{\left (\tau \right )}}{b} - \frac{a{\left (\tau \right )}}{b} \right) \log{\left (b \delta \rho_{r} + 1 \right )}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{} \alpha^r}{{\partial \tau}{}}\right) = \frac{1}{R T_{r}} \left(\left(- \tau \frac{b\frac{d^2a}{\partial\tau\partial x_i} -\frac{\partial a}{\partial \tau}\frac{\partial b}{\partial x_i}}{b^2} - \frac{b\frac{\partial a}{\partial x_i}-a\frac{\partial b}{\partial x_i}}{b^2} \right) \log{\left (b \delta \rho_{r} + 1 \right )} + \left(- \tau\frac{\frac{d}{d \tau} a{\left (\tau \right )}}{b} - \frac{a{\left (\tau \right )}}{b} \right) \frac{\delta\rho_r}{\left (b \delta \rho_{r} + 1 \right )}\frac{\partial b}{\partial x_i} \right)$$

```python

```

$$\frac{\partial{^{2}} \alpha^r}{{}{\partial \delta^{2}}} = \frac{1}{R T_{r}} \left(R T_{r}\frac{1}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{2}} + \rho_{r}^{2} \tau \frac{b a{\left (\tau \right )}}{\left(b \delta \rho_{r} + 1\right)^{2}}\right)$$

$$\frac{\partial}{\partial x_i} \left( \frac{\partial{^{2}} \alpha^r}{{}{\partial \delta^{2}}} \right) = \frac{1}{R T_{r}} \left(R T_{r}\frac{-2\left(\delta - \frac{1}{b \rho_{r}}\right)\left(\frac{1}{b\rho_r^2}\right)}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{4}} + \rho_{r}^{2} \tau \frac{\left(b \delta \rho_{r} + 1\right)^{2}(b\frac{\partial a}{\partial x_i}-a\frac{\partial b}{\partial x_i})-2ba\left(b \delta \rho_{r} + 1\right)\delta\rho_r\frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)^{2}}\right) $$

$$\frac{\partial}{\partial x_i} \left( \frac{\partial{^{2}} \alpha^r}{{}{\partial \delta^{2}}} \right) = \frac{1}{R T_{r}} \left(-\frac{2R T_{r}}{b^2\rho_r\left(\delta - \frac{1}{b \rho_{r}}\right)^{3}} + \rho_{r}^{2} \tau\left( \left(b\frac{\partial a}{\partial x_i}-a\frac{\partial b}{\partial x_i}\right) -\frac{2ba\delta\rho_r\frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)}\right)\right) $$

```python

```

$$\frac{\partial{^{2}} \alpha^r}{{\partial \tau}{\partial \delta}} = -\frac{\rho_r}{R T_{r} } \left(\frac{\tau \frac{da}{d \tau}+a}{b \delta \rho_{r} + 1} \right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{2}} \alpha^r}{{\partial \tau}{\partial \delta}}\right) = -\frac{\rho_r}{R T_{r} } \left( \frac{(b \delta \rho_{r} + 1)(\tau \frac{\partial^2a}{\partial \tau \partial x_i}+\frac{\partial a}{\partial x_i})-(\tau \frac{da}{d \tau}+a)\delta\rho_r\frac{\partial b}{\partial x_i}}{(b \delta \rho_{r} + 1)^2} \right)$$

```python

```

$$\frac{\partial{^{2}} \alpha^r}{{\partial \tau^{2}}{}} = - \frac{1}{R T_{r} } \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b}$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{2}} \alpha^r}{{\partial \tau^{2}}{}}\right) = - \frac{1}{R T_{r} } \left[\left(\tau \frac{\partial^{3}a}{\partial \tau^{2}\partial x_i}   + 2 \frac{\partial^2a}{\partial \tau\partial x_i} \right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b} + \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right) \frac{\partial b}{\partial x_i}\frac{\frac{b\delta\rho_r}{\left (b \delta \rho_{r} + 1 \right )} - \log{\left (b \delta \rho_{r} + 1 \right )}}{b^2} \right]$$

```python

```

$$\frac{\partial{^{3}} \alpha^r}{{}{\partial \delta^{3}}} = - \frac{1}{R T_{r}} \left(2 R T_{r}\frac{1}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{3}} + 2 \rho_{r}^{3} \tau \frac{b^{2} a{\left (\tau \right )}}{\left(b \delta \rho_{r} + 1\right)^{3}}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{3}} \alpha^r}{{}{\partial \delta^{3}}}\right) = - \frac{1}{R T_{r}} \left(2 R T_{r}\frac{-3\frac{1}{b^2\rho_r}\frac{\partial b}{\partial x_i}}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{4}} + 2 \rho_{r}^{3} \tau \frac{\left(b \delta \rho_{r} + 1\right)^{3}(2ba\frac{\partial b}{\partial x_i}+b^2\frac{\partial a}{\partial x_i})-(b^2a)3\left(b \delta \rho_{r} + 1\right)^{2}\delta\rho_r\frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)^{6}}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{3}} \alpha^r}{{}{\partial \delta^{3}}}\right) = - \frac{1}{R T_{r}} \left(\frac{-6 R T_{r}}{b^2\rho_r}\frac{\frac{\partial b}{\partial x_i}}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{4}} + 2 \rho_{r}^{3} \tau \frac{\left(b \delta \rho_{r} + 1\right)(2ba\frac{\partial b}{\partial x_i}+b^2\frac{\partial a}{\partial x_i})-3b^2a\delta\rho_r\frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)^{4}}\right)$$

```python

```

$$\frac{\partial{^{3}} \alpha^r}{{\partial \tau}{\partial \delta^{2}}} = \frac{\rho_{r}^{2}}{R T_{r} }\frac{b  \left(\tau \frac{d}{d \tau} a{\left (\tau \right )} + a{\left (\tau \right )}\right)}{\left(b \delta \rho_{r} + 1\right)^{2}}$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{3}} \alpha^r}{{\partial \tau}{\partial \delta^{2}}}\right) = \frac{\rho_{r}^{2}}{R T_{r} }  \frac{\left(b \delta \rho_{r} + 1\right)^2\left[\frac{\partial b}{\partial x_i}\left(\tau \frac{da}{d \tau}  + a\right) + b\left(\tau \frac{d^2a}{\partial \tau\partial x_i}  + \frac{\partial a}{\partial x_i}\right)\right] -2b\delta \rho_{r} \left(b \delta \rho_{r} + 1\right) \left(\tau \frac{da}{d \tau}  + a\right) \frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)^{4}}$$

```python

```

$$\frac{\partial{^{3}} \alpha^r}{{\partial \tau^{2}}{\partial \delta}} = - \frac{\rho_{r}}{R T_{r}}\frac{1}{ \left(b \delta \rho_{r} + 1\right)} \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{3}} \alpha^r}{{\partial \tau^{2}}{\partial \delta}}\right) = - \frac{\rho_{r}}{R T_{r}}\left[\frac{1}{ \left(b \delta \rho_{r} + 1\right)} \left(\tau \frac{d^{3}a}{d \tau^{2}\partial x_i} + 2 \frac{\partial^2a}{\partial\tau\partial x_i} \right) + \frac{-\delta \rho_{r}}{ \left(b \delta \rho_{r} + 1\right)^2}\frac{\partial b}{\partial x_i} \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right)\right]$$

```python

```

$$\frac{\partial{^{3}} \alpha^r}{{\partial \tau^{3}}{}} = - \frac{1}{R T_{r}} \left(\tau \frac{d^{3}}{d \tau^{3}}  a{\left (\tau \right )} + 3 \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )}\right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b} $$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{3}} \alpha^r}{{\partial \tau^{3}}{}}\right) = - \frac{1}{R T_{r} } \left[\left(\tau \frac{\partial^{4}a}{\partial \tau^{3}\partial x_i}   + 3 \frac{\partial^3a}{\partial \tau^2\partial x_i} \right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b} + \left(\tau \frac{d^{3}}{d \tau^{3}}  a{\left (\tau \right )} + 3 \frac{d^2}{d \tau^2} a{\left (\tau \right )}\right) \frac{\partial b}{\partial x_i}\frac{\frac{b\delta\rho_r}{\left (b \delta \rho_{r} + 1 \right )} - \log{\left (b \delta \rho_{r} + 1 \right )}}{b^2} \right]$$

```python

```

$$\frac{\partial{^{4}} \alpha^r}{{}{\partial \delta^{4}}} = - \frac{1}{R T_{r}} \left(6 R T_{r}\frac{1}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{4}} + 6 \rho_{r}^{4} \tau \frac{b^{3} a{\left (\tau \right )}}{\left(b \delta \rho_{r} + 1\right)^{4}}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{}{\partial \delta^{4}}}\right) = - \frac{1}{R T_{r}} \left(6 R T_{r}\frac{-4\frac{1}{b^2\rho_r}\frac{\partial b}{\partial x_i}}{\left(\delta - \frac{1}{b \rho_{r}}\right)^{5}} + 6 \rho_{r}^{4} \tau \frac{\left(b \delta \rho_{r} + 1\right)^{4}(3b^2a\frac{\partial b}{\partial x_i}+b^3\frac{\partial a}{\partial x_i})-(b^3a)4\left(b \delta \rho_{r} + 1\right)^{3}\delta\rho_r\frac{\partial b}{\partial x_i}}{\left(b \delta \rho_{r} + 1\right)^{8}}\right)$$

```python

```

$$\frac{\partial{^{4}} \alpha^r}{{\partial \tau}{\partial \delta^{3}}} = - \frac{2 \rho_{r}^{3}}{R T_{r} }\frac{ b^{2}}{\left(b \delta \rho_{r} + 1\right)^{3}} \left(\tau \frac{d}{d \tau} a{\left (\tau \right )} + a{\left (\tau \right )}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau}{\partial \delta^{3}}}\right) = - \frac{2 \rho_{r}^{3}}{R T_{r} }\left[\frac{b^{2}}{\left(b \delta \rho_{r} + 1\right)^{3}} \left(\tau \frac{\partial^2a}{\partial\tau\partial x_i}  + \frac{\partial a}{\partial x_i}\right) + \frac{\left(b \delta \rho_{r} + 1\right)^{3}2b\frac{db}{dx_i}-b^23\left(b \delta \rho_{r} + 1\right)^2\delta\rho_r\frac{db}{dx_i}}{\left(b \delta \rho_{r} + 1\right)^{6}} \left(\tau \frac{d}{d \tau} a{\left (\tau \right )} + a{\left (\tau \right )}\right)\right]$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau}{\partial \delta^{3}}}\right) = - \frac{2 \rho_{r}^{3}}{R T_{r} }\left[\frac{b^{2}}{\left(b \delta \rho_{r} + 1\right)^{3}} \left(\tau \frac{\partial^2a}{\partial\tau\partial x_i}  + \frac{\partial a}{\partial x_i}\right) + b\frac{\partial b}{\partial x_i}\frac{2\left(\delta \rho_{r} + 1\right)-3b\delta\rho_r}{\left(b \delta \rho_{r} + 1\right)^{4}} \left(\tau \frac{d}{d \tau} a{\left (\tau \right )} + a{\left (\tau \right )}\right)\right]$$

```python

```

$$\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{2}}{\partial \delta^{2}}} = \frac{\rho_{r}^{2}}{R T_{r}}\frac{b}{ \left(b \delta \rho_{r} + 1\right)^{2}} \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right)$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{2}}{\partial \delta^{2}}}\right) = \frac{\rho_{r}^{2}}{R T_{r}}\left[\frac{b}{\left(b \delta \rho_{r} + 1\right)^{2}} \left(\tau \frac{\partial^{3}a}{\partial \tau^{2}\partial x_i} + 2 \frac{d^2a}{d \tau\partial x_i}\right) + \frac{\left(b \delta \rho_{r} + 1\right)^{2}\frac{\partial b}{\partial x_i} -b2\left(b \delta \rho_{r} + 1\right)\delta\rho_r\frac{\partial b}{\partial x_i}}{ \left(b \delta \rho_{r} + 1\right)^{4}} \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right)\right]$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{2}}{\partial \delta^{2}}}\right) = \frac{\rho_{r}^{2}}{R T_{r}}\left[\frac{b}{\left(b \delta \rho_{r} + 1\right)^{2}} \left(\tau \frac{\partial^{3}a}{\partial \tau^{2}\partial x_i} + 2 \frac{d^2a}{d \tau\partial x_i}\right) + \frac{\left(b \delta \rho_{r} + 1\right)\frac{\partial b}{\partial x_i} -2b\delta\rho_r\frac{\partial b}{\partial x_i}}{ \left(b \delta \rho_{r} + 1\right)} \left(\tau \frac{d^{2}}{d \tau^{2}}  a{\left (\tau \right )} + 2 \frac{d}{d \tau} a{\left (\tau \right )}\right)\right]$$

```python

```

$$\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{3}}{\partial \delta}} = - \frac{\rho_{r}}{R T_{r}}\frac{\tau \frac{d^{3}a}{d \tau^{3}} + 3 \frac{d^{2}a}{d \tau^{2}}}{ \left(b \delta \rho_{r} + 1\right)}$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{3}}{\partial \delta}}\right) = - \frac{\rho_{r}}{R T_{r}}\frac{\left(b \delta \rho_{r} + 1\right)\left(\tau \frac{d^{4}a}{d \tau^{3}\partial x_i} + 3 \frac{\partial^{3}a}{\partial \tau^{2}\partial x_i}\right) - \left(\tau \frac{d^{3}a}{d \tau^{3}} + 3 \frac{d^{2}a}{d \tau^{2}}\right)\delta\rho_r\frac{\partial b}{\partial x_i}}{ \left(b \delta \rho_{r} + 1\right)^2}$$

```python

```

$$\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{4}}{}} = - \frac{1}{R T_{r}} \left(\tau \frac{d^{4}}{d \tau^{4}}  a{\left (\tau \right )} + 4 \frac{d^{3}}{d \tau^{3}}  a{\left (\tau \right )}\right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b}$$

$$\frac{\partial}{\partial x_i}\left(\frac{\partial{^{4}} \alpha^r}{{\partial \tau^{4}}{}}\right) = - \frac{1}{R T_{r} } \left[\left(\tau \frac{\partial^{5}a}{\partial \tau^{4}\partial x_i}   + 4 \frac{\partial^4a}{\partial \tau^3\partial x_i} \right) \frac{\log{\left (b \delta \rho_{r} + 1 \right )}}{b} + \left(\tau \frac{d^{4}}{d \tau^{4}}  a{\left (\tau \right )} + 4 \frac{d^3}{d \tau^3} a{\left (\tau \right )}\right) \frac{\partial b}{\partial x_i}\frac{\frac{b\delta\rho_r}{\left (b \delta \rho_{r} + 1 \right )} - \log{\left (b \delta \rho_{r} + 1 \right )}}{b^2} \right]$$

Derivatives of $a$
==================

$$a = \sum_i\sum_jx_ix_j\sqrt{a_ia_j}(1-k_{ij}) $$

$$\frac{\partial a}{\partial \tau} = \sum_i\sum_jx_ix_j\frac{1}{2\sqrt{a_ia_j}}\left(a_i\frac{\partial a_j}{\partial \tau}+a_j\frac{\partial a_i}{\partial \tau}\right)(1-k_{ij}) $$

$$\frac{\partial^2 a}{\partial \tau\partial x_i} = \sum_jx_j\frac{1}{2\sqrt{a_ia_j}}\left(a_i\frac{\partial a_j}{\partial \tau}+a_j\frac{\partial a_i}{\partial \tau}\right)(1-k_{ij}) $$

$$\frac{\partial^3 a}{\partial \tau\partial x_i\partial x_j} = \frac{1}{2\sqrt{a_ia_j}}\left(a_i\frac{\partial a_j}{\partial \tau}+a_j\frac{\partial a_i}{\partial \tau}\right)(1-k_{ij}) $$

```python
from __future__ import division
from sympy import *
from IPython.display import display
init_printing()
tau = symbols('tau', positive = True, real = True)

a_i = symbols('a_i', cls = Function)(tau)
a_j = symbols('a_j', cls = Function)(tau)

for idiff in range(0,5):
    display(simplify(diff(sqrt(a_i*a_j),tau,idiff)))
```

```python

```

doc/notebooks/SRK first composition derivatives.ipynb


# **Combinatorial Promoter Example**

written by Andrey Shur, 5/4/2020

### CombinatorialPromoter Overview

The CombinatorialPromoter is supposed to represent the general case where a promoter is transcribable when bound by a specific combination of regulators.

In general, consider a set of $M$ regulators $R_1...R_M$ which bind to the promoter $P$ with cooperativities $n_1...n_M$ ie $n_i$ copies of $R_i$ bind to the promoter at once. The CombinatorialPromoter enumerates all possible bound Promoter-Regulator complexes which are returned as a list of ComplexSpecies using the Combinatorial_Cooperative_Binding Mechanism. These complexes will be default take the following form with the regulators in alphabetical order:

\begin{align}
\textrm{Species}=\{&P, P:n_1\textrm{x}R_1, &&..., P:n_M\textrm{x}R_M, \\
&P:n_1\textrm{x}R_1:n_2\textrm{x}R_2, &&..., P:n_{M-1}\textrm{x}R_{M-1}:n_M\textrm{x}R_M \\
&&. \\
&&. \\
&&. \\
&P:n_1\textrm{x}R_1:n_2\textrm{x}R_2: &&...:n_{M-1}\textrm{x}R_{M-1}:n_M\textrm{x}R_M \}
\end{align}

The Combinatorial_Cooperative_Binding Mechanism in Combinatorial Promoter returns all reactions where a Complex consisting of a promoter and a set of regulators bind to one additional regulator.
\begin{align}
\textrm{Reactions} =
\{&P + n_1 R_1 \leftrightarrow P:n_1\textrm{x}R_1, ...\\
 &..., P + n_M R_M \leftrightarrow P:n_M\textrm{x}R_M, ... \\
  &..., P:n_1\textrm{x}R_1 + n_2 R_2 \leftrightarrow P:n_1\textrm{x}R_1:n_2\textrm{x}R_2,... \\
  &..., P:n_{M-1}\textrm{x}R_{M-1} + n_M R_M \leftrightarrow P:n_{M-1}\textrm{x}R_{M-1}:n_M\textrm{x}R_M, ... \\
  &. \\
  &. \\
  &. \\
  &..., P:n_1\textrm{x}R_1:n_2\textrm{x}R_2:...:n_{M-1}\textrm{x}R_{M-1} + n_M R_M
  \leftrightarrow
  P:n_1\textrm{x}R_1:n_2\textrm{x}R_2:...:n_{M-1}\textrm{x}R_{M-1}:n_M\textrm{x}R_M
\end{align}

Arbitrary combinations of these regulators can be defined as transcribable using:

    CombinatorialPromoter(name, [Regulators], tx_capable_list = [[Transcribable Combination fo regulators 1], [Combination 2], ...])

Additionally, there is the option for leak reactions in which case the free promoter is transcribed at some leak rate.

    CombinatorialPromoter(name, [Regulators], leak = True/False )

### Below are imports required for these examples and plotting.

```python
#Using Bioscrape: Basic Imports
#from bioscrape.types import Model
#from bioscrape.simulator import py_simulate_model
#For arrays and plotting
import numpy as np
import pylab as plt
try:
    make_heatmap = True
    import seaborn as sns
    import pandas as pd
except ModuleNotFoundError:
    make_heatmap = False
#this is for the network representation
try:
    import bokeh.plotting
    import bokeh.io
    bokeh.io.output_notebook()
    plotCRN = True
    from biocrnpyler import plotting
except (ModuleNotFoundError,ImportError) as e:
    plotCRN = False
#biocrnpyler
from biocrnpyler import *
```

    Loading BokehJS ...

## Example 1: Default Behavior to Produce AND logic with leak
In the following example, we consider a promoter phrpL which can be bound by the proteins hrpR and hrpS. When both proteins are present, transcription is activated, but when one or the other (or none) is present, transcription is not activated.

```python
#these are the additional parameters that you need:
parameters = {
    ('phrpL_hrpR', 'cooperativity'): 1.0, #default is 2, but these proteins bind at cooperativity = 1
    ('phrpL_hrpS', 'cooperativity'): 1.0,
    ('phrpL_hrpR', 'ku'):50, #these are the regulator unbinding rates
    ('phrpL_hrpS', 'ku'):50,
    ('phrpL_leak', 'ktx'): 0.01,  #this is the leak rate
    ('phrpL_leak', 'ku'): 100, #this is the leak polymerase unbinding rate

    ('translation_mm', 'B0030', 'ku'): 10.0, #Unbinding
    ('translation_mm', 'B0030', 'ktl'): 1.5, #Translation Rate

    #('transcription_mm',  'ktx'): .01,    #These are the parameters for transcription leak
    #('transcription_mm', 'ku'): 100,
    "kb":100, "ku":10, "ktx":.05, "ktl":.2, "kdeg":2 #some default parameters
}
#above you can see the desired part_id is very simple. The general syntax is <promoter.name>_<regulator.name>.
#using this syntax you can define the strength of binding of each regulator to the promoter.
#cooperativity between different regulators is not supported.

#you can also define the transcription rate and binding and unbinding (kb or ku) of polymerase from a transcribable
#complex. A transcribable complex is a promoter with regulators bound that is capable of transcription.

#By default, all combinations of regulators bound will be capable of transcription. They will use the "ktx" parameter
#promoter with nothing bound will use the phrpL_leak parameter

#now we define the proteins. Their names should match what you put in the promoter, below
hrpR = Protein("hrpR")
hrpS = Protein("hrpS")

#Define the promoter

#this is an "AND gate" promoter. That means that it only leads to transcript if both regulators are bound
#binding of one or the other regulator should not lead to any transcription
#this is the network:
# hrpR + mydna <--> mydna:hrpR
# hrpS + mydna <--> mydna:hrpS
# hrpR + mydna:hrpS <--> mydna:hrpR:hrpS
# hrpS + mydna:hrpR <--> mydna:hrpR:hrpS

phrpL = CombinatorialPromoter("phrpL",["hrpR","hrpS"], leak=True)
#this defines a promoter which binds hrpR and hrpS, and it leaks.
#By default, leak only happens when there is nothing bound to the DNA.
#this also means that in this promoter, individually bound species (with only one regulator) will not lead to
#any transcription! This means they will not leak also.

#this is our DNA
dna = DNAassembly("mydna",promoter=phrpL,rbs="B0030",protein="GFP")

#TxTl Extract is a Mixture with more complex internal models
extract_1_TXTL = TxTlExtract(name = "e coli extract 1", components = [dna,hrpR,hrpS], \
                                     parameters=parameters, parameter_file = "default_parameters.txt")
CRN_extract_1 = extract_1_TXTL.compile_crn()

print("The species and reactions in the CRN:\n", CRN_extract_1.pretty_print())

#below is CRN plotting code. set plotCRN to False to disable
if(plotCRN):
    DG, DGspec, DGrxn = generate_networkx_graph(CRN_extract_1)
    plot = Plot(plot_width=500, plot_height=500, x_range=Range1d(-500, 500), y_range=Range1d(-500, 500))
    graphPlot(DG,DGspec,DGrxn,plot)
    bokeh.io.show(plot)

#this is just a normal time trace of the model, with both proteins present
try:
    timepoints = np.linspace(0, 200, 1000)
    x0 = {"dna_mydna":5.0, "protein_hrpS":10, "protein_hrpR":7, "protein_RNAP":10., "protein_Ribo":50.,}
    Re1 = CRN_extract_1.simulate_with_bioscrape(timepoints, initial_condition_dict = x0)
    plt.plot(timepoints,Re1["protein_GFP"], label = "protein_GFP")
    plt.plot(timepoints,Re1["protein_hrpR"], label = "protein_hrpR")
    plt.plot(timepoints,Re1["protein_hrpS"], label = "protein_hrpS")
    plt.plot(timepoints,Re1["rna_mydna"], label = "rna_mydna")
    plt.gca().set_yscale("log")
    plt.title("Time trace of AND gate promoter phrpL")
    plt.xlabel("time")
    plt.ylabel("protein")
    plt.legend()
    print(parameters)
except ModuleNotFoundError:
    pass
```

# Vary hrpR and hrpS
To really test the combinatorial promoter we will vary the concentrations of hrpR and hrpS.

```python
if(make_heatmap):
    #aggregator data frame
    GFP_max = pd.DataFrame(columns = ['hrpS_conc', 'hrpR_conc', 'GFP_max'])
    #Different initial values of R and S
    conc_hrpR = np.linspace(0, 30, 9)
    conc_hrpS = np.linspace(0, 30, 9)

    try:
        for conc_R in conc_hrpR:
            x0["protein_hrpR"] = conc_R
            for conc_S in conc_hrpS:
                x0["protein_hrpS"] = conc_S #Change my initial condition dictionary
                Re1 = CRN_extract_1.simulate_with_bioscrape(timepoints, initial_condition_dict = x0)
                #now we are simulating over and over again, but only taking the final protein_GFP value
                GFP_max = GFP_max.append({'hrpS_conc':conc_S,
                                'hrpR_conc':conc_R,
                                'GFP_max': Re1["protein_GFP"].values[-1]}, ignore_index=True)
        #now, you make a 2d plot with all the data
        data = pd.pivot_table(data = GFP_max, index = 'hrpS_conc',
                    columns = 'hrpR_conc',
                    values = 'GFP_max')

        ax = sns.heatmap(data,
                        cmap = 'viridis',
                        linewidths=.2)
        ax.set_ylim(9,-0.5) #not sure why this is needed

        ax.set(xlabel='hrpS', ylabel='hrpR')
        ax.set_title("hrpR, hrpS Combinatorial Promoter Heatmap")
    except ModuleNotFoundError:
        pass
```

### Improving the AND Logic Model using the tx_capable_list keyword and more detailed parameters

After careful inspection of the above heatmap, you might notice two important features:

1) The promoter is on at all times except when no regulators are present

2) the promoter is on very strongly for most possible concentrations

We will adjust this model by changing the parameters and adding additional transcribable states

1) increase the unbinding rate

2) define single regulator bound promoters as being "leak"

To do this, we will use the tx_capable_list keyword and modify the list of parameters

    phrpL = CombinatorialPromoter(... tx_capable_list = [["hrpR","hrpS"]] ...)

Notice that there are 3 transcribable states, each regulator bound individually and both regulators bound together. Each of these combination of regulators requires their own parameters which are seen below in the newparam dictionary.

```python
newparam = {
    ('phrpL_hrpR', 'cooperativity'): 1.0,
    ('phrpL_hrpS', 'cooperativity'): 1.0,
    ('phrpL_hrpR','ku'):5, #unbinding rate of each regulator
    ('phrpL_hrpS','ku'):5,
    ('phrpL_hrpR_hrpS_RNAP', 'ktx'): 0.17025, #these are the rates for the correctly bound promoter
    ('phrpL_hrpR_hrpS_RNAP', 'ku'): 11.01321586,
    ('phrpL_leak','ktx'):0.01, #this is the leak transcription rate
    ('phrpL_leak', 'ku'): 100.,#this unbinding rate is now needed because we changed the tx_capable_list. Transcription from singly bound promoter uses the default "ktx"

}

#WARNING!!!
#out of order regulators will not work! For example, phrpL_hrpR_hrpS_RNAP is correct, but phrpL_hrpS_hrpR_RNAP is not.

parameters.update(newparam)
#you can also define the ktx for the leak reaction. I don't think this is necessary
#    ('phrpL_hrpR_RNAP', 'ktx'): 0.17,
#    ('phrpL_hrpS_RNAP', 'ktx'): 0.17,

phrpL = CombinatorialPromoter("phrpL",["hrpR","hrpS"],
                              tx_capable_list = [[Species("hrpR",material_type="protein"),"hrpS"]], #species can also be used
                              leak=True)
#now we are saying that there are more species that are capable of transcribing. Specifically, that we
#can still transcribe when both are bound, but also when one or the other is bound.

#this is our DNA
dna = DNAassembly("mydna",promoter=phrpL,rbs="B0030",protein="GFP")

extract_1_TXTL = TxTlExtract(name = "e coli extract 1", components = [dna,hrpR,hrpS], \
                                     parameters=parameters, parameter_file = "default_parameters.txt")
CRN_extract_1 = extract_1_TXTL.compile_crn()
try:
    if(make_heatmap):
        #aggregator data frame
        GFP_max = pd.DataFrame(columns = ['hrpS_conc', 'hrpR_conc', 'GFP_max'])
        #Different initial values of R and S
        conc_hrpR = np.linspace(0, 30, 9)
        conc_hrpS = np.linspace(0, 30, 9)

        for conc_R in conc_hrpR:
            x0["protein_hrpR"] = conc_R
            for conc_S in conc_hrpS:
                x0["protein_hrpS"] = conc_S #Change my initial condition dictionary
                Re1 = CRN_extract_1.simulate_with_bioscrape(timepoints, initial_condition_dict = x0)
                #now we are simulating over and over again, but only taking the final protein_GFP value
                GFP_max = GFP_max.append({'hrpS_conc':conc_S,
                                'hrpR_conc':conc_R,
                                'GFP_max': Re1["protein_GFP"].values[-1]}, ignore_index=True)

        #now, you make a 2d plot with all the data
        data = pd.pivot_table(data = GFP_max, index = 'hrpS_conc',
                    columns = 'hrpR_conc',
                    values = 'GFP_max')

        ax = sns.heatmap(data,
                        cmap = 'viridis',
                        linewidths=.2)
        ax.set_ylim(9,-0.5) #not sure why this is needed

        ax.set(xlabel='hrpS', ylabel='hrpR')
        ax.set_title("hrpR, hrpS Combinatorial Promoter Heatmap")
except ModuleNotFoundError:
    pass
```

## Example 2: Using the tx_capable_list keyword to implement XOR logic
In the following example, we consider a promoter phrpL which can be bound by the proteins hrpR and hrpS. When either one  proteins or the other is present, but not both, transcription is activated. To do this, we pass in the following combinations to transcribable list:

    phrpL = CombinatorialPromoter(... tx_capable_list = [["hrpR"],["hrpS"]] ...)

This ensures that only when hrpR or hrpS is individually bound to the promoter can it be transcribed.

```python
newparam = {
    ('phrpL_hrpR', 'cooperativity'): 1.0,
    ('phrpL_hrpS', 'cooperativity'): 1.0,
    ('phrpL_hrpR','ku'):3, #we have to massively decrease the unbinding rate to make this xor gate work
    ('phrpL_hrpS','ku'):3,
    #('phrpL_hrpR_hrpS_RNAP', 'ku'): 100, #this time, this one is leak
    ('phrpL_hrpR_RNAP', 'ktx'): 0.17, #these are now transcribing at high rates!
    ('phrpL_hrpS_RNAP', 'ktx'): 0.17,
    ('phrpL_hrpS_RNAP', 'ku'): 11.,
    ('phrpL_hrpR_RNAP', 'ku'): 11,
    ('phrpL','ku'):100,

}

parameters.update(newparam)
phrpL = CombinatorialPromoter("phrpL",["hrpR","hrpS"],
                              tx_capable_list = [["hrpR"],["hrpS"]],
                              leak=True)
#this is our DNA
dna = DNAassembly("mydna",promoter=phrpL,rbs="B0030",protein="GFP")

extract_1_TXTL = TxTlExtract(name = "e coli extract 1", components = [dna,hrpR,hrpS], \
                                     parameters=parameters, parameter_file = "default_parameters.txt")
CRN_extract_1 = extract_1_TXTL.compile_crn()

try:
    if(make_heatmap):
        #aggregator data frame
        GFP_max = pd.DataFrame(columns = ['hrpS_conc', 'hrpR_conc', 'GFP_max'])
        #Different initial values of R and S
        conc_hrpR = np.linspace(0,30, 9)
        conc_hrpS = np.linspace(0, 30, 9)
        for conc_R in conc_hrpR:
            x0["protein_hrpR"] = conc_R
            for conc_S in conc_hrpS:
                x0["protein_hrpS"] = conc_S #Change my initial condition dictionary
                Re1 = CRN_extract_1.simulate_with_bioscrape(timepoints, initial_condition_dict = x0)
                #now we are simulating over and over again, but only taking the final protein_GFP value
                GFP_max = GFP_max.append({'hrpS_conc':conc_S,
                                'hrpR_conc':conc_R,
                                'GFP_max': Re1["protein_GFP"].values[-1]}, ignore_index=True)

        #now, you make a 2d plot with all the data
        data = pd.pivot_table(data = GFP_max, index = 'hrpS_conc',
                    columns = 'hrpR_conc',
                    values = 'GFP_max')

        ax = sns.heatmap(data,
                        cmap = 'viridis',
                        linewidths=.2)
        ax.set_ylim(9,-0.5) #not sure why this is needed

        ax.set(xlabel='hrpS', ylabel='hrpR')
        ax.set_title("hrpR, hrpS Combinatorial Promoter Heatmap")
except ModuleNotFoundError:
    pass
```

```python

```

examples/Specialized Component Tutorials/CombinatorialPromoter Details.ipynb


# Chapter 3

***Ver como criar uma tabela de conteúdo TOC**

## Strings

```python
a = "My dog's name is"
b = "Bingo"
```

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

```python
c
```

    "My dog's name is Bingo"

```python
#trying to add string and integer
d = "927"
e = 927
```

```python
d + e
```

## Lists

```python
a = [0, 1, 1, 2, 3, 5, 8, 13]
b = [5., "girl", 2+0j, "horse", 21]
```

```python
b[0]
```

    5.0

```python
b[1]
```

    'girl'

<big>Lists are  *zero-indexed* </big>

$$
\begin{align}
list = &[a, b, c, d, e]\\
&\color{red}\Downarrow
\hspace{2.2pc}\color{red}\Downarrow\\
&\color{purple}{list[0]}
\hspace{1.2pc}
\color{purple}{list[4]}\\
&\color{brown}{list[-5]}
\hspace{0.7pc}
\color{brown}{list[-1]}
\end{align}
$$

```python
b[-1]
```

    21

```python
b[-5]
```

    5.0

```python
b[4]
```

    21

```python
b = [5., "girl", 2+0j, "horse", 21]
b[0] = b[0]+2
import numpy as np
b[3] = np.pi
```

```python
b
```

    [7.0, 'girl', (2+0j), 3.141592653589793, 21]

```python
a
```

    [0, 1, 1, 2, 3, 5, 8, 13]

<big>Adding lists  *concatenates* them, just as the **+** operator concatenates strings. </big>

```python
a+a
```

    [0, 1, 1, 2, 3, 5, 8, 13, 0, 1, 1, 2, 3, 5, 8, 13]

### Slicing lists

<big>Reparar que  *não* se inclui o último elemento.</big>

```python
b
```

    [7.0, 'girl', (2+0j), 3.141592653589793, 21]

```python
b[1:4]
```

    ['girl', (2+0j), 3.141592653589793]

```python
b[3:5]
```

    [3.141592653589793, 21]

```python
b[2:]
```

    [(2+0j), 3.141592653589793, 21]

```python
b[:3]
```

    [7.0, 'girl', (2+0j)]

```python
b[:]
```

    [7.0, 'girl', (2+0j), 3.141592653589793, 21]

```python
b[1:-1]
```

    ['girl', (2+0j), 3.141592653589793]

```python
len(b) #len --> length
```

    5

```python
? range
```

### Creating and modifying lists

range(stop) -> range object

range(start, stop[, step]) -> range object

Útil para criar *PAs (progressões aritméticas)*

```python
range(10) #começa de zero por padrão, armazena apenas início, fim e step. Útil para economizar memória
```

    range(0, 10)

```python
print(range(10))
```

    range(0, 10)

```python
list(range(10)) #para explicitar todos os integrantes
```

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

```python
list(range(3,10))
```

    [3, 4, 5, 6, 7, 8, 9]

```python
list(range(0,10,2))
```

    [0, 2, 4, 6, 8]

```python
a = range(1,10,3)
```

```python
a
```

    range(1, 10, 3)

```python
list(a)
```

    [1, 4, 7]

```python
a += [16, 31, 64, 127]
```

```python
a = a + [16, 31, 64,127]
```

```python
a = list(a) + [16, 31, 64,127]
```

```python
a
```

    [1, 4, 7, 16, 31, 64, 127]

```python
a = [0, 0] + a
```

```python
a
```

    [0, 0, 1, 4, 7, 16, 31, 64, 127]

```python
b = a[:5] + [101, 102] + a[5:]
```

```python
b
```

    [0, 0, 1, 4, 7, 101, 102, 16, 31, 64, 127]

### Tuples

<big>**Tuples** are lists that are  *immutable*</big>

Logo, pode ser usado para armazenar constantes, por exemplo.

```python
c = (1, 1, 2, 3, 5, 8, 13)
```

```python
c[4]
```

    5

```python
c[4] = 7
```

### Multidimensional lists and tuples

Useful in making tables and other structures.

```python
a = [[3,9], [8,5], [11,1]] #list
```

```python
a
```

    [[3, 9], [8, 5], [11, 1]]

```python
a[0]
```

    [3, 9]

```python
a[1][0]
```

    8

```python
a[1][0] = 10
```

```python
a
```

    [[3, 9], [10, 5], [11, 1]]

```python
a = ([3,9], [8,5], [11,1]) #? Não forma tuple assim... tudo deve ser parêntese. Ver abaixo
```

```python
a[1][0]
```

    8

```python
a[1][0] = 10
```

```python
a
```

    ([3, 9], [10, 5], [11, 1])

```python
a = ((3,9), (8,5), (11,1)) #tuple
```

```python
a
```

    ((3, 9), (8, 5), (11, 1))

```python
a[1][0]
```

    8

```python
a[1][0] = 10
```

## NumPy arrays

- all the elements are of the same type.

```python
import numpy as np

a = [0, 0, 1, 4, 7, 16, 31, 64,127]
```

```python
a
```

    [0, 0, 1, 4, 7, 16, 31, 64, 127]

```python
b = np.array(a)    #converts a list to an array
```

```python
b
```

    array([  0,   0,   1,   4,   7,  16,  31,  64, 127])

- the `array` function promotes all of the numbers to the type of the most general entry in the list.

```python
c = np.array([1, 4., -2,7]) #todos se tornarão float
```

```python
c
```

    array([ 1.,  4., -2.,  7.])

```python
? np.linspace
```

np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)

Return evenly spaced numbers over a specified interval.

Returns `num` evenly spaced samples, calculated over the interval [`start`, `stop`].

The endpoint of the interval can optionally be excluded.

```python
np.linspace(0, 10, 5)
```

    array([  0. ,   2.5,   5. ,   7.5,  10. ])

```python
np.linspace(0, 10, 5, endpoint=False)
```

    array([ 0.,  2.,  4.,  6.,  8.])

```python
np.linspace(0, 10, 5, retstep=True)
```

    (array([  0. ,   2.5,   5. ,   7.5,  10. ]), 2.5)

```python
? np.logspace
```

np.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None)

Return numbers spaced evenly on a log scale.

In linear space, the sequence starts at ``base**start`` (`base` to the power of `start`) and ends with ``base**stop``.

```python
np.logspace(1,3,5)
```

    array([   10.        ,    31.6227766 ,   100.        ,   316.22776602,
            1000.        ])

```python
%precision 1
np.logspace(1,3,5)
```

    array([   10. ,    31.6,   100. ,   316.2,  1000. ])

```python
? np.arange
```

arange([start,] stop[, step,], dtype=None)

Return evenly spaced values within a given interval.

Values are generated within the half-open interval ``[start, stop)`` (in other words, the interval including `start` but excluding `stop`). For integer arguments the function is equivalent to the Python built-in
`range <http://docs.python.org/lib/built-in-funcs.html>`_ function, but returns an ndarray rather than a list.

```python
np.arange(0, 10, 2)
```

    array([0, 2, 4, 6, 8])

```python
np.arange(0., 10, 2)   #todos serão float
```

    array([ 0.,  2.,  4.,  6.,  8.])

```python
np.arange(0, 10, 1.5)
```

    array([ 0. ,  1.5,  3. ,  4.5,  6. ,  7.5,  9. ])

### Criação de arrays de zeros e uns.

```python
np.zeros(6)
```

    array([ 0.,  0.,  0.,  0.,  0.,  0.])

```python
np.ones(8)
```

    array([ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.])

```python
np.ones(8, dtype=int)
```

    array([1, 1, 1, 1, 1, 1, 1, 1])

### Mathematical operations with arrays

```python
import numpy as np

a = np.linspace(-1, 5, 7)
```

```python
a
```

    array([-1.,  0.,  1.,  2.,  3.,  4.,  5.])

```python
a*6
```

    array([ -6.,   0.,   6.,  12.,  18.,  24.,  30.])

```python
np.sin(a)
```

    array([-0.84147098,  0.        ,  0.84147098,  0.90929743,  0.14112001,
           -0.7568025 , -0.95892427])

```python
x = np.linspace(-3.14, 3.14, 21)
```

```python
y = np.cos(x)
```

```python
x
```

    array([-3.14 , -2.826, -2.512, -2.198, -1.884, -1.57 , -1.256, -0.942,
           -0.628, -0.314,  0.   ,  0.314,  0.628,  0.942,  1.256,  1.57 ,
            1.884,  2.198,  2.512,  2.826,  3.14 ])

```python
y #fazer o plot disto futuramente
```

    array([ -9.99998732e-01,  -9.50612598e-01,  -8.08267427e-01,
            -5.86882949e-01,  -3.08108031e-01,   7.96326711e-04,
             3.09622813e-01,   5.88171730e-01,   8.09204181e-01,
             9.51105720e-01,   1.00000000e+00,   9.51105720e-01,
             8.09204181e-01,   5.88171730e-01,   3.09622813e-01,
             7.96326711e-04,  -3.08108031e-01,  -5.86882949e-01,
            -8.08267427e-01,  -9.50612598e-01,  -9.99998732e-01])

```python
a
```

    array([-1.,  0.,  1.,  2.,  3.,  4.,  5.])

```python
np.log(a)
```

    /home/chico/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:1: RuntimeWarning: divide by zero encountered in log
      if __name__ == '__main__':
    /home/chico/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:1: RuntimeWarning: invalid value encountered in log
      if __name__ == '__main__':

    array([        nan,        -inf,  0.        ,  0.69314718,  1.09861229,
            1.38629436,  1.60943791])

```python
a = np.array([34., -12, 5.])
```

```python
b = np.array([68., 5., 20.])
```

```python
a+b #vectorized operations
```

    array([ 102.,   -7.,   25.])

### Slicing and addressing arrays

Fórmula para a velocidade média em um intervalo de tempo *i*:

$$
v_i = \frac{y_i - y_{i-1}}{t_i - t_{i-1}}
$$

```python
y = np.array([0., 1.3, 5., 10.9, 18.9, 28.7, 40.])
t = np.array([0., 0.49, 1., 1.5, 2.08, 2.55, 3.2])
```

```python
y[:-1]
```

    array([  0. ,   1.3,   5. ,  10.9,  18.9,  28.7])

```python
y[1:]
```

    array([  1.3,   5. ,  10.9,  18.9,  28.7,  40. ])

```python
v = (y[1:]-y[:-1])/(t[1:]-t[:-1])
```

```python
v
```

    array([  2.65306122,   7.25490196,  11.8       ,  13.79310345,
            20.85106383,  17.38461538])

### Multi-dimensional arrays and matrices

```python
b = np.array([[1., 4, 5], [9, 7, 4]])
```

```python
b
```

    array([[ 1.,  4.,  5.],
           [ 9.,  7.,  4.]])

```python
#all elements of a MunPy array must be of the same data type: floats, integers, complex numbers, etc.
```

```python
a = np.ones((3,4), dtype=float)
```

```python
a
```

    array([[ 1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.],
           [ 1.,  1.,  1.,  1.]])

```python
np.eye(4)
```

    array([[ 1.,  0.,  0.,  0.],
           [ 0.,  1.,  0.,  0.],
           [ 0.,  0.,  1.,  0.],
           [ 0.,  0.,  0.,  1.]])

```python
c = np.arange(6)
```

```python
c
```

    array([0, 1, 2, 3, 4, 5])

```python
c = np.reshape(c, (2,3))
```

```python
c
```

    array([[0, 1, 2],
           [3, 4, 5]])

```python
b
```

    array([[ 1.,  4.,  5.],
           [ 9.,  7.,  4.]])

```python
b[0][2]
```

    5.0

```python
b[0,2] #0 indexed
```

    5.0

```python
b[1,2]
```

    4.0

```python
2*b
```

    array([[  2.,   8.,  10.],
           [ 18.,  14.,   8.]])

*Beware*: array multiplication, done on an element-by-element basis, *is not the same as **matrix** multiplication* as defined in linear algebra. Therefore, we distinguish between *array* multiplication and *matrix* multiplication in Python.

```python
b*c
```

    array([[  0.,   4.,  10.],
           [ 27.,  28.,  20.]])

```python
d = c.T #cria matriz transposta
```

```python
d
```

    array([[0, 3],
           [1, 4],
           [2, 5]])

```python
np.dot(b,d) #faz multiplicação matricial
```

    array([[ 14.,  44.],
           [ 15.,  75.]])

## Dictionaries

\* Também chamados de *hashmaps* ou *associative arrays* em outras linguagens de programação.

$$
\begin{align}
room =&\text{{"Emma":309, "Jacob":582, "Olivia":764}}\\
&\hspace{1.0pc}\color{red}\Downarrow
\hspace{1.5pc}\color{red}\Downarrow\\
&\hspace{0.7pc}\color{purple}{key} \hspace{1.5pc}\color{purple}{value}
\end{align}
$$

```python
room = {"Emma":309, "Jacob":582, "Olivia":764}
```

```python
room["Olivia"]
```

    764

```python
weird = {"tank":52, 846:"horse", "bones":[23, "fox", "grass"], "phrase":"I am here"}
```

```python
weird["tank"]
```

    52

```python
weird[846]
```

    'horse'

```python
weird["bones"]
```

    [23, 'fox', 'grass']

```python
weird["phrase"]
```

    'I am here'

```python
d = {}
```

```python
d["last name"] = "Alberts"
d["first name"] = "Marie"
d["birthday"] = "January 27"
```

```python
d
```

    {'birthday': 'January 27', 'first name': 'Marie', 'last name': 'Alberts'}

```python
d.keys()
```

    dict_keys(['last name', 'first name', 'birthday'])

```python
d.values()
```

    dict_values(['Alberts', 'Marie', 'January 27'])

## Random numbers

`np.random.rand(num)` creates an array of `num` floats **uniformly** distributed on the interval from 0 to 1.

`np.random.randn(num)` produces a **normal (Gaussian)** distribution of `num` random numbers with a mean of 0 and a standard deviation of 1. They are distributed according to
$$
P(x)=\frac{1}{\sqrt{2\pi}}e^{-\frac{1}{2}x²}
$$

`np.random.randint(low, high, num)` produces a **uniform** random distribution of `num` integers between `low` (inclusive) and `high` (exclusive).

```python
np.random.rand()
```

    0.6128257951854355

```python
np.random.rand(5)
```

    array([ 0.39011104,  0.94966976,  0.82918667,  0.82582458,  0.5470922 ])

```python
a, b = 10, 20
(b-a)*np.random.rand(20) + a #setting interval
```

    array([ 18.77336643,  11.61410374,  14.88429681,  15.51534267,
            13.17639605,  13.1415327 ,  19.31115566,  13.72687979,
            10.54121763,  11.1926624 ,  13.0620921 ,  13.70948059,
            16.47806379,  18.84756575,  17.21223502,  15.50349539,
            12.67808023,  15.71166147,  16.56673275,  19.71620007])

```python
x0, sigma = 15, 10
sigma*np.random.randn(20) + x0 #setting width and center of normal distribution
```

    array([ 16.0341322 ,  24.85431056,  15.82390913,   3.11407242,
             6.59829903,  18.96369418,  18.25224176,   9.49064922,
             1.56663384,   6.72413305,  15.84363561,  21.53336751,
            19.2771351 ,   9.42367517,  -0.57121141,  30.73648204,
            -3.88731961,   8.33712126,   9.68661964,  16.82579145])

```python
np.random.randint(1, 7, 12) #simutaling a dozen rolls of a single die
```

    array([3, 2, 3, 6, 6, 3, 4, 5, 2, 2, 6, 1])

```python

```

```python

```