# Notebook from sbenthall/privacy-abm
Path: contact-tracing/description/Compartmental epidemiological model.ipynb

# https://scipython.com/book/chapter-8-scipy/additional-examples/the-sir-epidemic-model/

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

import seaborn as sns_____no_output_____

# Compartmental model

We are using a compartmental epidemiological that is a variation of the SEIR model.

We have introduced variations into order to model population heterogeneity._____no_output_____## SEIR model

In this model, the individuals are either **Susceptible**, **Exposed**, **Infectious**, or **Recovered** (or Removed). We use $S$, $E$, $I$, and $R$ to denote the number of individuals in each compartment and $N$ to denote the total population.

There are several model parameters:

 * $\beta$, the average number of contacts by an individual per time step multiplied by the probability of disease transmission between an susceptible and infectious person.
 * $\alpha$, the inverse of the mean of the (exponentially distributed) incubation period for the disease
 * $\gamma$, the inverse of the mean of the (exponentially distributed) infectious period for the disease

Then the dynamic equations are:

[//]: # "Use better latex to line up equations here"

$\frac{dS}{dt} = - \beta \frac{I}{N} S$

$\frac{dE}{dt} = \beta \frac{I}{N} S - \alpha E$

$\frac{dI}{dt} = \alpha E - \gamma I$

$\frac{dR}{dt} = \gamma I$_____no_output_____

# The SEIR model differential equations.
def SEIR_deriv(y, t, N, beta, alpha, gamma):
    S, E, I, R = y
    dSdt = -beta * S * I / N
    dEdt = beta * S * I / N - alpha * E
    dIdt = alpha * E - gamma * I
    dRdt = gamma * I
    return dSdt, dEdt, dIdt, dRdt_____no_output_____

We will set some parameters to use throughout this notebook_____no_output_____

# Total population, N.
N = 1000
# Initial number of infected and recovered individuals, I0 and R0.
E0, I0, R0 = 0, 1, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - E0 - I0 - R0
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
beta, alpha, gamma = 0.2, 2./10, 1./10
# A grid of time points (in days)
t = np.linspace(0, 160, 160)_____no_output_____

Using these equations we can simulate the model._____no_output_____

# Initial conditions vector
y0 = S0, E0, I0, R0
# Integrate the SEIR equations over the time grid, t.
ret = odeint(SEIR_deriv, y0, t, args=(N, beta, alpha, gamma))
S, E, I, R = ret.T

S_benchmark = S_____no_output_____# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w')
ax = fig.add_subplot(111, facecolor='#dddddd', axisbelow=True)
ax.plot(t, S/1000, 'b', alpha=0.5, lw=2, label='Susceptible')
ax.plot(t, E/1000, 'y', alpha=0.5, lw=2, label='Exposed')
ax.plot(t, I/1000, 'r', alpha=0.5, lw=2, label='Infected')
ax.plot(t, R/1000, 'g', alpha=0.5, lw=2, label='Recovered with immunity')
ax.set_xlabel('Time /days')
ax.set_ylabel('Number (1000s)')
ax.set_ylim(0,1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.title('SEIR model')
plt.show()_____no_output_____# Shortcut to computing the former
def SEIR_test(beta,alpha,gamma):
    ret = odeint(SEIR_deriv, y0, t, args=(N, beta, alpha, gamma))
    return ret.T_____no_output_____

## SkEIR model with S, $\beta$ heterogeneity (Dolbeault & Turinici, 2020)

A challenge for compartmental epidemiological models is that while modeling population heterogeneity is of increasing relevance for modeling diseases like COVID-19, this is not "native" to this modeling technique. Like other fields, compartmental epidemiology tends to use a 'representative agent' model.

Here, we consider heterogeneity in contacts by an individual per time step, which is a factor in the definition of $\beta$:

 * $\beta$, the average number of contacts by an individual per time step multiplied by the probability of disease transmission between an susceptible and infectious person.

The Dolbeault & Turinici (2020) has shown that, contrary to the assumption within this definition, the "average number of contacts by an individual per time step" is perhaps not straightforwardly calculated, or accurate? It considers a "factor of reduction in contacts" $q$, which is differentiated for $n$ classes, $q_0, q_1, ..., q_n$. They then define:

$$\beta_k = \beta / q_k$$

They then use a model where several different classes of _susceptibles_ : $ S = \sum_k p_k S_k$, with $1 = \sum_k p_k$

Then the dynamic equations are:

[//]: # "Use better latex to line up equations here"

$\frac{dS_k}{dt} = - \beta_k \frac{I}{N} S$

$\frac{dE}{dt} = \sum^{n}_{k=1} \beta_k \frac{I}{N} S_k - \alpha E$

$\frac{dI}{dt} = \alpha E - \gamma I$

$\frac{dR}{dt} = \gamma I$

The authors compare models of this kind to the original SEIR models that have the same $\mathcal{R}_0$.

Following those authors, we will try to replicate their results with two classed $n = 2$._____no_output_____

type(np.zeros(1))_____no_output_____# The SEIR model differential equations.
def SkEIR_deriv(y, t, N, beta : np.ndarray, alpha, gamma):
    S_0, S_1, E, I, R = y

    dS0dt = - beta[0] * S_0 * I / N
    dS1dt = - beta[1] * S_1 * I / N

    dEdt = (beta[0] * S_0 + beta[1] * S_1) * I / N - alpha * E

    dIdt = alpha * E - gamma * I
    dRdt = gamma * I
    return dS0dt, dS1dt, dEdt, dIdt, dRdt_____no_output_____p = np.array([.5, .5])

Sk0 = p * S0
beta = np.array([0.1,.3])

# Initial conditions vector
yk0 = Sk0[0], Sk0[1], E0, I0, R0
# Integrate the SIR equations over the time grid, t.
ret = odeint(SkEIR_deriv, yk0, t, args=(N, beta, alpha, gamma))
S_0, S_1 , E, I, R = ret.T_____no_output_____# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w',figsize=(8,6))
ax = fig.add_subplot(111, facecolor='#dddddd', axisbelow=True)
ax.plot(t, (S_0 + S_1)/1000, 'b', alpha=0.5, lw=2, label='Susceptible')
ax.plot(t, (S_0)/1000, 'b', alpha=0.5, lw=.5, label='S0')
ax.plot(t, (S_1)/1000, 'b--', alpha=0.5, lw=.5, label='S1')
ax.plot(t, E/1000, 'y', alpha=0.5, lw=2, label='Exposed')
ax.plot(t, I/1000, 'r', alpha=0.5, lw=2, label='Infected')
ax.plot(t, R/1000, 'g', alpha=0.5, lw=2, label='Recovered with immunity')
ax.plot(t, (S)/1000, 'm', alpha=0.5, lw=2, label='S, SEIR model')
ax.set_xlabel('Time /days')
ax.set_ylabel('Number (1000s)')
ax.set_ylim(0,1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.title('SkEIR model')
plt.show()_____no_output_____

Though $\sum_k p_k \beta_k = \beta$, the outcomes of this model and the previous one diverge greatly. This is a bit of a mystery, as the $\mathcal{R}_0$ are the same. But the breakdown of the `S0` and `S1` groups shows what's happening: the total susceptible count is the sum of two differently effected subsystems._____no_output_____### Hypothesis: Wrong mean formula_____no_output_____One idea: maybe the discrepancy here is due to the use of the wrong mean.

This can be tested quickly._____no_output_____

import math
import statistics_____no_output_____S_harmonic = SEIR_test(statistics.harmonic_mean(1 + beta) - 1,
                       alpha,
                       gamma)[0]
S_geometric = SEIR_test(math.sqrt((beta[0] + 1) * (beta[1] + 1)) - 1,
                        alpha,
                        gamma)[0]_____no_output_____

(I did some work and disproved this hypothesis.)_____no_output_____

# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w')
ax = fig.add_subplot(111, facecolor='#dddddd', axisbelow=True)
ax.plot(t, (S_benchmark)/1000, 'b--', alpha=0.5, lw=1, label='SEIR (mean)')
ax.plot(t, (S_0 + S_1)/1000, 'b', alpha=0.5, lw=2, label='SkEIR')
ax.plot(t, S_geometric/1000, 'm', alpha=0.5, lw=2, label='SEIR, geometric mean beta')
ax.plot(t, S_harmonic/1000, 'c', alpha=0.5, lw=2, label='SEIR, harmonic  mean beta')
ax.set_xlabel('Time /days')
ax.set_ylabel('Number (1000s)')
ax.set_ylim(0,1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.title('SkEIR model')
plt.show()_____no_output_____

This is a false path._____no_output_____## SEIkR model with I, $\beta$ heterogeneity

Our (Benthall, Epstein, Hatna, and Strandburg) model (in NetLogo) thus far is a bit different.

We have been assigning different transmission probabilities to different _infectors_ in order to model the phenomenon of _superspreaders_.

Rendered as a compartmental model, this has different equations from D&T 2020.

Here, $\sum_k p_k = 1$ and represents the proportion of different categories of infectors.

$\frac{dS}{dt} = - \sum_k \beta_k \frac{I_k}{N} S$

$\frac{dE}{dt} = \sum_k \beta_k \frac{I_k}{N} S - \alpha E$

$\frac{dI_k}{dt} = p_k \alpha E - \gamma I_k$

$\frac{dR}{dt} = \sum_k \gamma I_k$_____no_output_____

# The SEIR model differential equations.
def SEIkR_deriv(y, t, N, beta : np.ndarray, alpha, gamma, p : np.ndarray):
    S_0, S_1, E_0, E_1, I_0, I_1, R = y

    dSdt = - (beta[0] * I_0 + beta[1] * I_1) * S / N

    dEdt = (beta[0] * I_0 + beta[1] * I_1) * S / N - alpha * E

    dI0dt = p[0] * alpha * E - gamma * I_0
    dI1dt = p[1] * alpha * E - gamma * I_1
    dRdt = gamma * I_0 + gamma * I_1
    return dSdt, dEdt, dI0dt, dI1dt, dRdt_____no_output_____### I messed this up!!!

p = np.array([.5, .5])

beta = 0.2

I0k = I0 * p

# Initial conditions vector
yk0 = S0, E0, I0k[0], I0k[1], R0
# Integrate the SIR equations over the time grid, t.
ret = odeint(SEIkR_deriv, yk0, t, args=(N, beta, alpha, gamma, p))
S, E, I_0, I_1, R = ret.T_____no_output_____E_____no_output_____# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w',figsize=(8,6))
ax = fig.add_subplot(111, facecolor='#dddddd', axisbelow=True)
ax.plot(t, S/1000, 'b', alpha=0.5, lw=2, label='Susceptible')
ax.plot(t, E/1000, 'y', alpha=0.5, lw=2, label='Exposed')
ax.plot(t, (I_0 + I_1)/1000, 'r', alpha=0.5, lw=2, label='Infected')
ax.plot(t, (I_0)/1000, 'r', alpha=0.5, lw=.5, label='I_0')
ax.plot(t, (I_1)/1000, 'r--', alpha=0.5, lw=.5, label='I_1')
ax.plot(t, R/1000, 'g', alpha=0.5, lw=2, label='Recovered with immunity')
ax.plot(t, (S_benchmark)/1000, 'm--', alpha=0.5, lw=2, label='S, SEIR model')
ax.set_xlabel('Time /days')
ax.set_ylabel('Number (1000s)')
ax.set_ylim(0,1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.title('SEIkR model')
plt.show()_____no_output_____

Note hear that with the SEIkR model, we get identical performance of the model when $\beta = \sum_k p_k \beta_k$ as originally proposed by Hatna. This suggests that our model is better than D & T 2020.

But why were we not able to replicate this in the ABM?_____no_output_____## SkEkIkR model with I, $\gamma$ heterogeneity and $p$ blending

Our (Benthall, Hatna, Epstein and Strandburg) Watts-Strogatz model (in Python) is different still.

Assume that the population is divided into $k = 2$ compartments.
When somebody infects another, it is a local infection with probability $(1 - p) + p / 2$.
If it is a local infection, then the same compartment is infected.
If it is a distant infection, which happens with probability $p / 2$, then the other compartment is infected.

Here, $p$ is the rewiring rate of the notional Watts-Strogatz model.

Let $\hat{p} = p / 2$.

The two compartments vary in their recovery rate $\gamma$. Here, we include quarantine due to contact tracing and testing as a form of recovery. So varying $\gamma$ reflects the difference in adoption of smartphone based contact tracing in the two compartments.

$\frac{dS_k}{dt} = - \beta \frac{(1 - \hat{p}) I_k + \hat{p} I_{\neg k}}{(N / 2)} S_k$

$\frac{dE_k}{dt} = \beta \frac{(1 - \hat{p}) I_k + \hat{p} I_{\neg k}}{(N / 2)} S_k - \alpha E_k$

$\frac{dI_k}{dt} = \alpha E_k - \gamma_k I_k$

$\frac{dR}{dt} = \sum_k \gamma_k I_k$_____no_output_____

# The SEIR model differential equations.
def SkEkIkR_deriv(y, t, N, beta, alpha, gamma : np.ndarray, p_hat):
    S_0, S_1, E_0, E_1, I_0, I_1, R = y

    dS0dt = - beta * ((1 - p_hat) * I_0 + (p_hat) * I_1) * S_0 / Nk[0]
    dS1dt = - beta * ((p_hat) * I_0 + (1 - p_hat) * I_1) * S_1 / Nk[1]

    dE0dt = beta * ((1 - p_hat) * I_0 + (p_hat) * I_1) * S_0 / Nk[0] - alpha * E_0
    dE1dt = beta * ((p_hat) * I_0 + (1 - p_hat) * I_1) * S_1 / Nk[1] - alpha * E_1

    dI0dt = alpha * E_0 - gamma[0] * I_0
    dI1dt = alpha * E_1 - gamma[1] * I_1

    dRdt = gamma[0] * I_0 + gamma[1] * I_1

    return dS0dt, dS1dt, dE0dt, dE1dt, dI0dt, dI1dt, dRdt_____no_output_____# A grid of time points (in days)
t = np.linspace(0, 160, 160)

p_hat = .125
p = np.array([.5, .5])

# compartmentalized population
Nk = N * p

Ek0 = 0 * p
Ik0 = 1 * p
R0 = 0

Sk0 = Nk - Ek0 - Ik0 - R0

beta = 0.2
alpha = 0.2
x = .02
gamma = [beta / 2 - x, beta / 2 + x]

# Initial conditions vector
yk0 = Sk0[0], Sk0[1], Ek0[0], Ek0[1], Ik0[0], Ik0[1], R0

# Integrate the SIR equations over the time grid, t.
ret = odeint(SkEkIkR_deriv, yk0, t, args=(Nk, beta, alpha, gamma, p_hat))
S_0, S_1, E_0, E_1, I_0, I_1, R = ret.T_____no_output_____# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w',figsize=(8,6))
ax = fig.add_subplot(111, facecolor='#dddddd', axisbelow=True)
ax.plot(t, (S_0 + S_1) / N, 'b', alpha=0.5, lw=2, label='Susceptible')
ax.plot(t, S_0/Nk[0], 'b-.', alpha=0.5, lw=2, label='Susceptible_0')
ax.plot(t, S_1/Nk[1], 'b--', alpha=0.5, lw=2, label='Susceptible_1')
ax.plot(t, (E_0 + E_1)/N, 'y-', alpha=0.5, lw=2, label='Exposed_0')
ax.plot(t, E_0/Nk[0], 'y-.', alpha=0.5, lw=2, label='Exposed_0')
ax.plot(t, E_1/Nk[1], 'y--', alpha=0.5, lw=2, label='Exposed_1')
ax.plot(t, (I_0 + I_1)/N, 'r', alpha=0.5, lw=2, label='Infected')
ax.plot(t, (I_0)/Nk[0], 'r-.', alpha=0.5, lw=.5, label='I_0')
ax.plot(t, (I_1)/Nk[1], 'r--', alpha=0.5, lw=.5, label='I_1')
ax.plot(t, R/N, 'g', alpha=0.5, lw=2, label='Recovered with immunity')
ax.plot(t, (S_benchmark)/ N, 'm--', alpha=0.5, lw=2, label='S, SEIR model')
ax.set_xlabel('Time /days')
ax.set_ylabel('% of population size')
ax.set_ylim(0,1.1)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.title('SkEkIkR model')
plt.show()_____no_output_____def results(p_hat, gamma):

    # A grid of time points (in days)
    t = np.linspace(0, 160, 160)

    p = np.array([.5, .5])

    # compartmentalized population
    Nk = N * p

    Ek0 = 0 * p
    Ik0 = 1 * p
    R0 = 0

    Sk0 = Nk - Ek0 - Ik0 - R0

    beta = 0.2
    alpha = 0.25
    gamma = gamma

    # Initial conditions vector
    yk0 = Sk0[0], Sk0[1], Ek0[0], Ek0[1], Ik0[0], Ik0[1], R0

    # Integrate the SIR equations over the time grid, t.
    ret = odeint(SkEkIkR_deriv, yk0, t, args=(Nk, beta, alpha, gamma, p_hat))

    # S_0, S_1, E_0, E_1, I_0, I_1, R
    return ret.T_____no_output_____np.linspace(0,.1,20)_____no_output_____p_hat = .125

infections = {}

for x in np.linspace(0,.1,20):
    res = results(p_hat, [.1 - x, .1 + x])
    infections[x] = res[4][-1] + res[5][-1]_____no_output_____sns.lineplot(data = infections)_____no_output_____

## Next steps?

This is veering towards a more complete generalization of the model, in which the transitions between different classes of agents can be parameterized out through $\beta$ and also $\alpha, \gamma$. Quite possibly an approximation of the spatial characteristics of the model can be built in this way.

Consider:
 - a factor for classes of susceptibles that controls the rate of exposure is reflective of that agent's "in-degree" of transmitting contacts.
 - a factor for classes of infectors that controls the rate of infection is reflective of that agent's "out-degree" of transmitting contacts.

In principle, each agent could be a member of both a class of susceptibles and a class of infectors, and the parameterized distribution across those classes would enable a wide range of network topologies.

It would also be possible to control how much the groups intermingle by having weighted parameters for each pairwise group interaction. I.e., use matrices for the $\beta, \alpha$ and $\gamma$ parameters, not vectors.

The SEIkR model and the SkEIR model are slightly different in that while the former uses the probabilistic differentiation into classes as weights in the differential equations, the latter uses them to preset the populations as fractions of N._____no_output_____## References

Dolbeault, J., & Turinici, G. (2020). Social heterogeneity and the COVID-19 lockdown in a multi-group SEIR model. medRxiv._____no_output_____


# Notebook from morganjwilliams/11-complex-data
Path: notebooks/1_Evolutionary computing.ipynb

# Evolutionary Algorithms

In this notebook we will develop an Evolutionary Algorithm (EA) from scratch to minimise an objective function. As we saw during class, EAs are stochastic search methods that mimic the natural evolution, inspired in Darwin's theory of evolution. These techniques use evolutionary operators such as reproduction, mutation and selection to minimise a given objective function (known as **fitness function**).

**Summary**
 - [Fitness Function](#objfun)
 - [Evolutionary Operators](#operators)
   - [Selection](#selection)
   - [Reproduction](#reproduction)
   - [Mutation](#mutation)
 - [Evolutionary Search](#algorithm)_____no_output_____

%matplotlib notebook

# Standard libraries
import numpy as np  # written in C, is a faster and robust library for numerical and matrix operations
import math as mt #provides access to the mathematical functions defined by the C standard.
import pandas as pd # data manipulation library, it is widely used for data analysis and relies on numpy library.
import matplotlib.pyplot as plt # for plotting
import seaborn as sns # plot nicely =)_____no_output_____

## Fitness Function

Let's first define the fitness function to be minimised by the evolutionary algorithm:

$$f(x) = \frac{\cos(\frac{1}{x})}{x}$$_____no_output_____

def objfun(x):
    f=np.cos(1/x)/x
    return f_____no_output_____

### Plotting the Fitness Function_____no_output_____

t=np.linspace(-1,1,1000)
ft=objfun(t)

plt.plot(t,ft)
plt.title("Fitness function")
plt.ylabel('$f(x)$')
plt.xlabel('x')_____no_output_____

## Evolutionary Operators

Here we define the evolutionary operators, which are the components that perform the evolution of a population.

**Covered Operators**
 - [Selection](#selection)
 - [Reproduction](#reproduction)
 - [Mutation](#mutation)_____no_output_____### Selection

This operator determines which individuals in the population are chosen for reproduction._____no_output_____

def selection(fitness):
    """
    Selects mum and dad for the reproduction step
    parameters:
        fitness: an array with the fitness value for each individual in the population
    return:
        mum, dad
    """
    pdf=fitness/np.sum(fitness)  #probability of each being the parent
    # fitness.shape[0] is the number of individuals in the population (aka, population size)
    (mum,dad)=np.random.choice(fitness.shape[0], 2,p=pdf)  #choose mum and dad

    return (mum, dad)_____no_output_____

Sanity check_____no_output_____

n = 20
parents = np.random.rand(n)
fitness = objfun(parents)

fitness = abs(fitness*(fitness>0)) #objective function fitness must be non-negative

print('parents')
print(fitness)
mum,dad = selection(fitness)
print('mum: {} (fitness: {:.5}) -  dad: {} (fitness: {:.5})'.format(mum,fitness[mum],dad,fitness[dad]))parents
[ 0.20675683  0.36996561  0.47651545  0.          0.40654703  0.
  0.          0.31947314  0.11090721  0.         12.28169875  1.39695145
  0.          0.35926868  0.42540162  0.          0.42430857  0.
  0.          0.44352987]
mum: 4 (fitness: 0.40655) -  dad: 10 (fitness: 12.282)

### Reproduction

Produces new individuals combining the information contained in the parents
(mum and dad)._____no_output_____

def reproduction(parents, mum, dad, ngene):
    """
    Produces new individuals
    parameters:
        parents: inviduals in the population
        mum, dad: index of the parents
        ngene: coding dependent
    return:
        new individual
        chosen crossover
    """
    ggene=np.random.randint(0,ngene)    #choose crossover
    shift=2**ggene
    kid= ( mt.floor(parents[mum]*shift) + (parents[dad]*shift)%1 ) #mum+dad

    return kid, ggene_____no_output_____

Sanity check_____no_output_____

ngene = 64
kid, ggene = reproduction(parents, mum, dad, ngene)
print("New spring: {:.5} - chosen crossover: {}".format(kid, ggene))New spring: 51.154 - chosen crossover: 6

### Mutation

After reproduction every individual undergoes mutation. Mutation occurs during evolution according to a mutation probability, such probability should be set low. This step is analogous to biological mutation, and the main target is to maintain population diversity._____no_output_____

def mutation(kid, ggene, ngene, prob_mutation):
    """
    Produces new individuals
    parameters:
        kid: new individual create by reproduction step
        ggene: chosen crossover
        ngene: coding dependent
        prob_mutation: mutation probability, this should be set low.
    return:
        mutated individual
    """
    kid= mt.floor(kid* (2**(ngene-ggene))) #big kid (long-int)
    imut= int(np.sum(2**np.argwhere((np.random.rand(ngene) < prob_mutation))))
    kid= kid^imut # mutation
    kid=kid/(2**ngene)

    return kid_____no_output_____

Sanity check_____no_output_____

prob_mutation=0.01
mut_kid = mutation(kid, ggene, ngene, prob_mutation)
print("NOT Mutated spring: {:.5}".format(kid))
print("Mutated spring: {:.5}".format(mut_kid))NOT Mutated spring: 51.154
Mutated spring: 0.79928

## Evolutionary Search

This process iteratively applies the evolutionary operators to minimise the fitness function. At each generation, new solutions are created according to their fitness, allowing the evolution of the solutions (individuals) that minimise the fitness function. The figure below illustrates the entire process of the evolutionary algorithm.

_____no_output_____

np.random.seed(42)

n=1000 #population 1000
parents=np.random.rand(n)
ngen=50 #number of generations 50
ngene=64 #coding dependent - we're in a 64-bit world
gen=0
pmut=0.01 #bit-wise/gene-wise mutation rate

while gen<ngen:

    fit=objfun(parents)
    children=np.array([])

    for i in range(0,n): # make n children, parents are polyganous and reproduction is asexual

        fit=abs(fit*(fit>0)) #objective function fitness must be non-negative

        #Selection
        (mum,dad)=selection(fit)
        #Reproduction
        kid, ggene = reproduction(parents, mum, dad, ngene)
        #Mutation
        kid = mutation(kid, ggene, ngene, pmut)

        #add the new kid to the list of children
        children=np.append(children,kid)

    parents=children
    #report
    print("Generation: ",gen," maximum fitness =",max(fit),' at ',parents[np.argmax(fit)])

    #and repeat
    gen+=1Generation:  0  maximum fitness = 112.52504958505489  at  0.005514747282993171
Generation:  1  maximum fitness = 1307.4729899340828  at  0.26135364476671497
Generation:  2  maximum fitness = 6753.581913638761  at  0.000125083362413958
Generation:  3  maximum fitness = 8052.368451293858  at  0.0005199026667607745
Generation:  4  maximum fitness = 892328.867247451  at  1.0870196204548765e-06
Generation:  5  maximum fitness = 6464395.704643294  at  1.120485949912542e-06
Generation:  6  maximum fitness = 7204007.733383496  at  1.0832943319327715e-06
Generation:  7  maximum fitness = 18664529.11817452  at  4.7602837338839646e-08
Generation:  8  maximum fitness = 57642756.28785365  at  3.066774244459003e-05
Generation:  9  maximum fitness = 133444110.24796624  at  4.760965877093071e-08
Generation:  10  maximum fitness = 145537593.9876388  at  2.903717569169295e-08
Generation:  11  maximum fitness = 148411519.32431263  at  6.915195694279924e-09
Generation:  12  maximum fitness = 150573803.54968297  at  6.9151992467767626e-09
Generation:  13  maximum fitness = 198049438.5365915  at  6.980679258597466e-09
Generation:  14  maximum fitness = 422810644.97920376  at  6.915195640286656e-09
Generation:  15  maximum fitness = 402667719.8939948  at  6.685433901355148e-09
Generation:  16  maximum fitness = 402667719.8939948  at  6.682368617217521e-09
Generation:  17  maximum fitness = 538788202.4813595  at  6.915199247427284e-09
Generation:  18  maximum fitness = 1342991228.3324137  at  0.03125000691520011
Generation:  19  maximum fitness = 3469876754.351594  at  3.959411969296778e-10
Generation:  20  maximum fitness = 8963051649.372953  at  1.3254341652002566e-09
Generation:  21  maximum fitness = 8963051649.372953  at  0.0019531251103598606
Generation:  22  maximum fitness = 12112750231.626116  at  4.5415003881036156e-10
Generation:  23  maximum fitness = 20774722320.459496  at  0.2578125003959412
Generation:  24  maximum fitness = 21295316541.541077  at  5.397122332384541e-11
Generation:  25  maximum fitness = 21576260348.836044  at  1.1035630764245907e-10
Generation:  26  maximum fitness = 54308632217.08635  at  9.580442868065489e-11
Generation:  27  maximum fitness = 52992034287.66806  at  1.0399345279224828e-10
Generation:  28  maximum fitness = 56842659067.70096  at  1.7701535766703747e-11
Generation:  29  maximum fitness = 67167702647.258415  at  4.6810090634404056e-11
Generation:  30  maximum fitness = 311370853570.2171  at  4.589678647987788e-11
Generation:  31  maximum fitness = 725212420372.6528  at  1.7694429255143973e-11
Generation:  32  maximum fitness = 815336125888.1913  at  9.344768578736784e-10
Generation:  33  maximum fitness = 2351620006330.4175  at  3.154338227250031e-12
Generation:  34  maximum fitness = 2860795002193.83  at  1.3626391681675898e-12
Generation:  35  maximum fitness = 2860795002193.83  at  2.244844934939927e-12
Generation:  36  maximum fitness = 12916171068434.385  at  4.258537966705944e-13
Generation:  37  maximum fitness = 12916171068434.385  at  7.65846262275327e-14
Generation:  38  maximum fitness = 13339822418145.43  at  3.1261820389316686e-13
Generation:  39  maximum fitness = 13339822418145.43  at  7.659850401534052e-14
Generation:  40  maximum fitness = 47829588820854.24  at  4.2585563981428765e-13
Generation:  41  maximum fitness = 47829588820854.24  at  7.65846262275327e-14
Generation:  42  maximum fitness = 47829588820854.24  at  1.9630185064262173e-14
Generation:  43  maximum fitness = 48964930744690.086  at  1.974120736672469e-14
Generation:  44  maximum fitness = 169009174845262.97  at  0.007812500116435063
Generation:  45  maximum fitness = 180757081914717.44  at  1.974120736672469e-14
Generation:  46  maximum fitness = 180757081914717.44  at  1.996325197164972e-14
Generation:  47  maximum fitness = 235839625516623.2  at  1.974120736672469e-14
Generation:  48  maximum fitness = 235839625516623.2  at  1.9359513991901167e-15
Generation:  49  maximum fitness = 499733357677997.3  at  5.4748415002914275e-15

### Ploting the Solutions_____no_output_____

plt.figure()
plt.plot(parents, fit, 'r*')
plt.plot(t,ft,'k')
plt.show()_____no_output_____

**Your turn**

In this exercise, we propose that you try to minimise a 2D function - this is the ["Eggholder" function](https://en.wikipedia.org/wiki/Test_functions_for_optimization) (according to Wikipedia), and it is notoriously bumpy. _____no_output_____**Defining the function**_____no_output_____

def Eggholder(x,y):
    f=-(y+47)*np.sin(np.sqrt(np.abs(x/2+y+47)))-x*np.sin(np.sqrt(np.abs(x-y-47)))
    return f_____no_output_____

**Visualising the "Eggholder" function **_____no_output_____

from mpl_toolkits.mplot3d import Axes3D # This import has side effects required for the kwarg projection='3d' in the call to fig.add_subplot

fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
x = y = np.arange(-512, 512, 1)
X, Y = np.meshgrid(x, y)
zs = np.array([Eggholder(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)

ax.plot_surface(X, Y, Z)_____no_output_____


# Notebook from nithin-seenivasan/social_media_analytics
Path: HW3.ipynb

# Homework 3
This notebook provides some skeleton code to get you started on the homework. Add in your own code and markdown cells to answer the homework questions. If you want to submit the notebook as a PDF, make sure your code and markdowns are clear and concise to make grading easy for the TAs.

This notebook can be opened in Colab
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zlisto/social_media_analytics/blob/main/HW3.ipynb)

Before starting, select "Runtime->Factory reset runtime" to start with your directories and environment in the base state.

If you want to save changes to the notebook, select "File->Save a copy in Drive" from the top menu in Colab. This will save the notebook in your Google Drive.

_____no_output_____# Clones, installs, imports, and **GPU**_____no_output_____## Use GPU

You can finish this assignment faster if you use a GPU._____no_output_____

import tensorflow as tf
tf.test.gpu_device_name()_____no_output_____

## Clone GitHub Repository
This will clone the repository to your machine. This includes the code and data files. Then change into the directory of the repository._____no_output_____

!git clone https://github.com/zlisto/social_media_analytics

import os
os.chdir("social_media_analytics")_____no_output_____

## Install Packages_____no_output_____

!pip install -r requirements.txt
_____no_output_____

## Import Packages_____no_output_____

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

import umap
import scripts.TextAnalysis as ta
from scripts.api import *
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import codecs  #this let's us display tweets properly (emojis, etc.)

import sklearn.cluster as cluster
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from scipy import stats

from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
pd.set_option("display.max_colwidth", None)
_____no_output_____

# Problem 1. Tweet Sentiment (35 points)

_____no_output_____#### 1. (4 points) Load data

Load the tweets in the file `"data/Oreo.db"` into a dataframe (remember to not write the `.db`). Then remove all columns except `"screen_name"`, `"text"`, and `"retweet_count"`.  Print out the head of this dataframe so we know you did it right._____no_output_____#### 2. (15 points) Calculate sentiment

Calculate the sentiment of tweets using the BERT model `"nlptown/bert-base-multilingual-uncased-sentiment"` that we used in class.  Make sure you use the GPU provided in Colab to make things faster.

Save the sentiments to a list `Sentiment`. Then add this list as a column to your tweet dataframe called `"sentiment"`.

_____no_output_____#### 3. (2 points)  What is the mean opinion of the tweets?  Make sure you print out your answer._____no_output_____#### 4. (4 points) Sentiment histogram

Plot a histogram of the tweet sentiment using this `histplot` function.   Make sure to add axes labels and a title to the figure._____no_output_____#### 5. (5 points) Most postive sentiment tweets

Print out the text and sentiment of the five highest sentiment tweets.
_____no_output_____#### 6. (5 points)  Most negative sentiment tweets

Print out the text and sentiment of the three lowest sentiment tweets._____no_output_____# Problem 2:  Clustering Tweets (32 points)_____no_output_____#### 1. (5 points)  Clean text

Use the `text_clean` function to clean the tweet text in your dataframe.  Add a column to the tweet dataframe with the cleaned text.  Print the head of the dataframe so we know you did it right.

Note: to avoid issues later in this assignment, don't remove cleaned tweets of length zero._____no_output_____#### 2. (5 points) TF-IDF Embedding

Calculate the term frequency inverse document frequency (tf-idf) embedding of the tweets. How big is the vocabulary?  Print your answer._____no_output_____#### 3. (8 points) UMAP TF-IDF Embedding

Calculate the UMAP embedding of the tf-idf embedding.  Add columns to your dataframe called `"umap_tfidf_x"` and `"umap_tfidf_y"` for the x and y embedding coordinates._____no_output_____#### 4. (14 points) K-means clusters

Calculate the k-means clusters of the umap embedding using three clusters.  Add a column to the dataframe with the tweets called `"kmeans_label_umap"` that contains the k-means cluster labels.

Make a histogram showing the number of tweets in each cluster with the `countplot` function.  Make sure to add axes labels and a title to the figure._____no_output_____# Problem 3:  Clusters, Sentiment, and Retweet Count (27 points)

Now we will look at the relationship between the sentiment, retweet count, and text in the clusters you found.
_____no_output_____#### 1. (6 points) Plot retweet count versus sentiment

Create a column called `"star"` which is the sentiment column rounded to the nearest integer.  Make a barplot of the retweet count versus star value.  Make sure to add axes labels and a title to the figure._____no_output_____#### 3. (4 points) Plot sentiment per cluster

Make a barplot of the mean sentiment of tweets in each k-means cluster.  Make sure to add axes labels and a title to the figure._____no_output_____#### 4. (4 points) Plot retweet count per cluster

Make a barplot of the mean rewteet count of tweets in each k-means cluster.   Make sure to add axes labels and a title to the figure._____no_output_____#### 5. (8 points) Cluster word clouds

Plot the word cloud for each cluster.  Don't forget to remove stopwords.  A `for` loop might be useful here._____no_output_____#### 6. (5 points) Cluster analyis

Print the text for some tweets in the cluster with the lowest sentiment (the `sample` function might be useful here).  What is the common theme of these tweets?_____no_output_____


# Notebook from Jere-Lyn12/jls_murray_2020SURF
Path: Produce_2N/Produce 2N_2.ipynb

# Imports
from biocrnpyler import *
from genelet import *
import pylab as plt
import numpy as np
from bokeh.layouts import row
from bokeh.io import export_png

import warnings
import bokeh.io
import bokeh.plotting_____no_output_____

# Produce 2N pt 2 Simulations #_____no_output_____

###### Create CRN of Produce 2N
Prod2_off = Species("Prod2")
#rR1 = Species("rR1")
Broccoli_Aptamer = Species("BrocApt")
#dA1 = Species("dA1" )
DFHBI = Species("DFHBI")

Prod2 = Genelet(Prod2_off, transcript= Broccoli_Aptamer , activator= "dA1"  , inhibitor= "rR1",
                rnap="RNAP", rnaseH="RNAseH")
M_Prod2 = Mixture(name = "Produce2_test", components = [Prod2], parameter_file = "default_parameters.txt")
repr(M_Prod2)
CRN_Prod2 = M_Prod2.compile_crn()

#rxn1 = ([Broccoli_Aptamer, DFHBI_dye], [ComplexSpecies([Broccoli_Aptamer, DFHBI_dye], name = "Broc_flu"), k = 9.96e-2)

rxn1 = Reaction([Broccoli_Aptamer,DFHBI], [ComplexSpecies([Broccoli_Aptamer, DFHBI], name="Flu1")], k = 9.96e-5)
#rxn2 = Reaction([ComplexSpecies([Broccoli_Aptamer, DFHBI], name="Flu1")], [Broccoli_Aptamer, DFHBI], k = 9.96e-10)
CRN_Prod2.add_reactions([rxn1])

# Write SBML file
#CRN_Prod2.write_sbml_file('Prod2_CRN.xml')

print(CRN_Prod2.pretty_print())Species (11) = {0. Prod2_OFF, 1. complex[Prod2_OFF:dna[dA1]], 2. BrocApt, 3. dna[dA1], 4. rna[rR1], 5. protein[RNAP], 6. protein[RNAseH], 7. complex[rna[rR1]:dna[dA1]], 8. complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]], 9. complex[Prod2_OFF:protein[RNAP]], 10. complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]]}
Reactions (10) = [
0. Prod2_OFF + dna[dA1] --> complex[Prod2_OFF:dna[dA1]]
        massaction: k_f(Prod2_OFF,dna[dA1])=0.00394*Prod2_OFF*dna[dA1]
1. complex[Prod2_OFF:dna[dA1]] + rna[rR1] --> Prod2_OFF + complex[rna[rR1]:dna[dA1]]
        massaction: k_f(complex[Prod2_OFF:dna[dA1]],rna[rR1])=0.0696*complex[Prod2_OFF:dna[dA1]]*rna[rR1]
2. dna[dA1] + rna[rR1] --> complex[rna[rR1]:dna[dA1]]
        massaction: k_f(dna[dA1],rna[rR1])=0.0696*dna[dA1]*rna[rR1]
3. complex[Prod2_OFF:dna[dA1]] + protein[RNAP] <--> complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]]
        massaction: k_f(complex[Prod2_OFF:dna[dA1]],protein[RNAP])=38.857142857142854*complex[Prod2_OFF:dna[dA1]]*protein[RNAP]
        k_r(complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]])=10.0*complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]]
4. complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]] --> complex[Prod2_OFF:dna[dA1]] + BrocApt + protein[RNAP]
        massaction: k_f(complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]])=0.064*complex[complex[Prod2_OFF:dna[dA1]]:protein[RNAP]]
5. Prod2_OFF + protein[RNAP] <--> complex[Prod2_OFF:protein[RNAP]]
        massaction: k_f(Prod2_OFF,protein[RNAP])=9.53047619047619*Prod2_OFF*protein[RNAP]
        k_r(complex[Prod2_OFF:protein[RNAP]])=10.0*complex[Prod2_OFF:protein[RNAP]]
6. complex[Prod2_OFF:protein[RNAP]] --> Prod2_OFF + BrocApt + protein[RNAP]
        massaction: k_f(complex[Prod2_OFF:protein[RNAP]])=0.007*complex[Prod2_OFF:protein[RNAP]]
7. complex[rna[rR1]:dna[dA1]] + protein[RNAseH] <--> complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]]
        massaction: k_f(complex[rna[rR1]:dna[dA1]],protein[RNAseH])=111.82417582417582*complex[rna[rR1]:dna[dA1]]*protein[RNAseH]
        k_r(complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]])=10.0*complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]]
8. complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]] --> dna[dA1] + protein[RNAseH]
        massaction: k_f(complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]])=0.176*complex[complex[rna[rR1]:dna[dA1]]:protein[RNAseH]]
9. BrocApt + DFHBI --> complex[BrocApt:DFHBI]
        massaction: k_f(BrocApt,DFHBI)=9.96e-05*BrocApt*DFHBI
]
# BioSCRAPE simulation of Produce2N with No repressors#
#Close to Schaffter and Schulman's paper#

io = {"Prod2_OFF": 0.035, "dna_dA1": 1, "rna_rR1": 0, "DFHBI": 15,
      "protein_RNAseH":0, "protein_RNAP":15}
# For label convenience
x = 'Time (hours)'
y = 'Concentration (uM)'
timepoints = np.linspace(0, 28800, 1000)
R = CRN_Prod2.simulate_with_bioscrape(timepoints, initial_condition_dict = io)
timepoints = timepoints/3600

bokeh.io.output_notebook()
p = bokeh.plotting.figure(
    plot_width = 300,
    plot_height = 300,
    x_axis_label = x,
    y_axis_label = y,
)
p.circle(timepoints, R["Prod2_OFF"], legend_label = "OFF Produce 2", color = "orange")
p.circle(timepoints, R["complex_Prod2_ON"], legend_label = "ON Produce 2", color = "red")
p.legend.click_policy="hide"

#v = bokeh.plotting.figure(
#    plot_width = 400,
#    plot_height = 300,
#    x_axis_label = x,
#    y_axis_label = y,)
#v.circle(timepoints, R["complex_Prod2_ON"], legend_label = "ON Produce 2" , color = "red")
#v.legend.click_policy="hide"
#v.legend.location = "center_right"

r = bokeh.plotting.figure(
     plot_width = 300,
     plot_height = 300,
     x_axis_label = x,
     y_axis_label = y,)
r.circle(timepoints, R["complex_Flu1"], legend_label = "Fluoresence", color = "darkgreen")
r.legend.location = "bottom_right"

s = bokeh.plotting.figure(
    plot_width = 300,
    plot_height = 300,
    x_axis_label = x,
    y_axis_label = y,)
s.circle(timepoints, R["BrocApt"], legend_label = "Broc Apt", color = "green")
s.legend.click_policy="hide"
s.legend.location = "top_left"

# t = bokeh.plotting.figure(
#     plot_width = 300,
#     plot_height = 300,
#     x_axis_label = x,
#     y_axis_label = y,)
# t.circle(timepoints, R["DFHBI"], legend_label = "DFHBI molecules", color = "lightgreen")
# t.legend.click_policy="hide"
# t.legend.location = "top_right"

bokeh.io.show(row (p, s, r))
warnings.filterwarnings("ignore")C:\Users\Jeremiah\anaconda3\lib\site-packages\html5lib\_trie\_base.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
  from collections import Mapping
C:\Users\Jeremiah\anaconda3\lib\importlib\_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject
  return f(*args, **kwds)
C:\Users\Jeremiah\anaconda3\lib\site-packages\biocrnpyler\chemical_reaction_network.py:1089: UserWarning: The following species are uninitialized and their value has been defaulted to 0: DFHBI, complex_Flu1,
  initial_condition_dict = initial_condition_dict)
a = bokeh.plotting.figure(
    plot_width = 300,
    plot_height = 300,
    x_axis_label = x,
    y_axis_label = y,)
a.circle(timepoints, R["dna_dA1"], legend_label = "dA1", color = "blue")
a.legend.click_policy="hide"
a.legend.location = "top_right"

b = bokeh.plotting.figure(
    plot_width = 300,
    plot_height = 300,
    x_axis_label = x,
    y_axis_label = y,)
b.circle(timepoints, R["protein_RNAP"], legend_label = "RNAP", color = "green")

b.legend.click_policy="hide"
b.legend.location = "top_right"

bokeh.io.show(row (a, b))
warnings.filterwarnings("ignore")_____no_output_____c = bokeh.plotting.figure(
    plot_width = 500,
    plot_height = 300,
    x_axis_label = x,
    y_axis_label = y,)
c.circle(timepoints, R["complex_Prod2_ON_protein_RNAP"], legend_label = "Produce 2 ON w/ RNAP", color = "purple")
c.circle(timepoints, R["complex_Prod2_ON"], legend_label = "ON Produce 2" , color = "red")
c.circle(timepoints, R["complex_Prod2_OFF_protein_RNAP"], legend_label = "ON Produce 2 OFF RNAP" , color = "blue")
c.legend.click_policy="hide"
c.legend.location = "center_right"

bokeh.io.show(row(c))_____no_output_____