---
# System prepended metadata

title: import numpy as npimport matpl

---

```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
```

