```python
import numpy as np
import matplotlib.pyplot as plt
from numba import jit
import scipy.special
@jit(nopython=True)
def OneStateTranscription(alpha, beta, gamma, num_reactions):
'''
Parameters
----------
alpha : transcription rate u -> u+1
beta : Stochastic rate constant u -> s
gamma : Stochastic rate constant for s -> null
num_reactions: total reactions allowed
Returns
-------
Timestamps and trjectories of U and S
'''
#create trajectories storage
U = np.zeros(num_reactions+1, dtype=np.int16)
S = np.zeros(num_reactions+1, dtype=np.int16)
T = np.zeros(num_reactions+1, dtype=np.float32)
dt = np.zeros(num_reactions+1, dtype=np.float32)
#generate random numbers
r1_array = np.random.random(size=num_reactions)
r2_array = np.random.random(size=num_reactions)
#loop till termination time
for i in range(num_reactions):
#current population
Ucur = U[i]
Scur = S[i]
#propensity for reactions
#splicing
a1 = Ucur*beta
#degradation
a2 = Scur*gamma
#total propensity
a0 = alpha+a1+a2
#find and update arrival time
r1 = r1_array[i]
tau = np.log(1/r1)/a0
dt[i] = tau
T[i+1] = T[i]+tau
# Threshold for selecting a reaction
r2a0 = r2_array[i]*a0
#choose a reaction
if r2a0 < alpha:
U[i+1] = Ucur+1
S[i+1] = Scur
elif alpha <= r2a0 <= (alpha+a1):
U[i+1] = Ucur-1
S[i+1] = Scur+1
else:
U[i+1] = Ucur
S[i+1] = Scur-1
return U, S, dt
@jit(nopython=True)
def TwoStateTranscription(kon, koff, alpha, beta, gamma, num_reactions):
'''
Parameters
----------
kon: g=o -> g=1
koff: g=1 -> g=0
alpha : transcription rate u -> u+1
beta : Stochastic rate constant u -> s
gamma : Stochastic rate constant for s -> null
num_reactions: total reactions allowed
Returns
-------
Timestamps and trjectories of U and S
'''
#create trajectories storage
G = np.zeros(num_reactions+1, dtype=np.int16)
U = np.zeros(num_reactions+1, dtype=np.int16)
S = np.zeros(num_reactions+1, dtype=np.int16)
T = np.zeros(num_reactions+1, dtype=np.float32)
dt = np.zeros(num_reactions+1, dtype=np.float32)
#generate random numbers
r1_array = np.random.random(size=num_reactions)
r2_array = np.random.random(size=num_reactions)
#loop till termination time
for i in range(num_reactions):
#current population
Ucur = U[i]
Scur = S[i]
Gcur = G[i]
#propensity for reactions
if Gcur == 0:
a_sw = kon
else:
a_sw = koff
#splicing
a1 = Ucur*beta
#degradation
a2 = Scur*gamma
#total propensity
a0 = alpha+a1+a2+a_sw
#find and update arrival time
r1 = r1_array[i]
tau = np.log(1/r1)/a0
dt[i] = tau
T[i+1] = T[i]+tau
# Threshold for selecting a reaction
r2a0 = r2_array[i]*a0
#choose a reaction
if r2a0 < a_sw:
G[i+1] = 1-Gcur
elif a_sw<= r2a0 < (alpha+a_sw):
U[i+1] = Ucur+1
S[i+1] = Scur
elif (alpha+a_sw) <= r2a0 <= (alpha+a1+a_sw):
U[i+1] = Ucur-1
S[i+1] = Scur+1
else:
U[i+1] = Ucur
S[i+1] = Scur-1
return U, S, dt
@jit(nopython=True)
def GeometricBurstTranscription(kon, b, beta, gamma, num_reactions):
'''
Parameters
----------
kon: rate of firing
b: mean of the geometric burst
beta : Stochastic rate constant u -> s
gamma : Stochastic rate constant for s -> null
num_reactions: total reactions allowed
Returns
-------
Timestamps and trjectories of U and S
'''
#parameter for geometric distribution
p = 1/(b+1)
#create trajectories storage
U = np.zeros(num_reactions+1, dtype=np.int16)
S = np.zeros(num_reactions+1, dtype=np.int16)
dt = np.zeros(num_reactions+1, dtype=np.float32)
#generate random numbers
r1_array = np.random.random(size=num_reactions)
r2_array = np.random.random(size=num_reactions)
for i in range(num_reactions):
#current population
Ucur = U[i]
Scur = S[i]
#propensity for reactions
a1 = Ucur*beta #splicing
a2 = Scur*gamma #degradation
a0 = kon+a1+a2 #total propensity
#find and update arrival time
r1 = r1_array[i]
tau = np.log(1/r1)/a0
dt[i] = tau
# Threshold for selecting a reaction
r2a0 = r2_array[i]*a0
#choose a reaction
if r2a0 < kon:
U[i+1] = Ucur + np.random.geometric(p) - 1
S[i+1] = Scur
#splicing
elif kon <= r2a0 <= kon+a1:
U[i+1] = Ucur-1
S[i+1] = Scur+1
#degradation
else:
U[i+1] = Ucur
S[i+1] = Scur-1
return U, S, dt
@jit(nopython=True)
def MomentConvergence(x, dt):
'''
Check the time convergence of x as a function of time (T)
'''
return np.cumsum(np.multiply(x, dt))/np.cumsum(dt)
@jit(nopython=True)
def JointDistributionAnalysis(U, S, dt):
'''
Construct the joint distribution from a list of (U, S) generated from simulation
'''
N = len(U)
A = np.zeros( ( int(max(U))+1,int(max(S))+1) )
for i in range(N):
A[U[i], S[i]] += dt[i]
return A/np.sum(dt)
@jit(nopython=True)
def JointDistributionAnalysis_exp(U, S):
'''
Construct the joint distribution from a list of (U, S)
'''
N = len(U)
A = np.zeros( (max(U)+1,max(S)+1) )
for i in range(N):
A[U[i], S[i]] += 1
return A/N
def MarginalDistributionFromJD(JD, margin = 'S'):
'''
Given a joint distribution (row, column) = (unspliced, spliced)
compute the marginal distribution
'''
if margin == 'S':
return np.sum(JD, axis=0)
else:
return np.sum(JD, axis=1)
def FirstMomentsFromJD(p):
'''
p should be U (row) by S (column)
'''
nrow, ncol = p.shape
EU = 0
ES = 0
for i in range(nrow):
for j in range(ncol):
pij = p[i,j]
EU += pij*i
ES += pij*j
return EU, ES
def OneState_SteadyState_JD_us(alpha, beta, gamma, u, s):
a = alpha/beta
b = alpha/gamma
Pus = np.float_power(a, u)*np.float_power(b, s)*np.exp(-a-b)/ scipy.special.factorial(u) / scipy.special.factorial(s)
return Pus
def OneState_SteadyState_JD(alpha, beta, gamma, umax, smax):
ana_p = np.zeros((umax, smax))
for u in range(umax):
for s in range(smax):
ana_p[u,s] = OneState_SteadyState_JD_us(alpha, beta, gamma, u, s)
return ana_p
```