#! /bin/sh
""":"
exec ipython -i $0 ${1+"$@"}
"""

####################################################################
####        Ring Dynamical System analysis   ########################
####
####                                   Ruben Perez-Carrasco
####                                   ##### Created: Apr 2016
####################################################################


from PyDSTool import *
import matplotlib.pyplot  as plt
import matplotlib as mpl
from matplotlib import rc,rcParams,cm
import sys
from mpl_toolkits.mplot3d import Axes3D
from scipy.signal import spectral, argrelextrema
from scipy.optimize import brentq
from matplotlib.collections import LineCollection, PatchCollection
from matplotlib.ticker import MaxNLocator
import matplotlib.patches as mpatches
import collections
from itertools import cycle
import os,sys
import json
import seaborn as sns
from mpl_toolkits.axes_grid1 import make_axes_locatable


colorcycle=["#e41a1c", #RED
  "#377eb8","#4daf4a", #BLUE, GREEN
  "#ff7f00","#984ea3", #ORANGE, PURPLE
  "#999999","#f781bf","#a65628","#ffff33"] #GREY, PINK, BROWN

sbcolorcycle=sns.color_palette("deep")
sbcolorcycledark=sns.color_palette("dark")

inferno_cmap = cm.get_cmap('inferno')


def info():
    print('''
------------------------------------------------------------------------------
Module for ring DS Analysis
------------------------------------------------------------------------------
*********** Functions to define the dynamical system:
* define(mod,N,lr,rm):  Create a new network topology that will be use in the exploration
* settime(t): Set the time range of the integration
* setpars(pard): Set the parameters of the system
* integrate(): Integrate the ODEs

*********** Parameter sampling:
* exploreoscillations(N): returns a list of a set of parameters with successful oscillations

***** Functions to manage parameter lists:
* parstats(parsetlist): return a statistics summary for a list of parameter sets
* saved(parsetlist): save a list of parameter sets to an external file 
* loadd(parsetlist): load a list of parameter sets from an external file 

************* Functions to plot results
* timeintplot(): Plotting the trajectory for a given set of parameters
* freqplot(): Computing and plotting the frequency for a list of set of parameters
* meshmaphistorep(): Plotting a successful set of parameters on top of a surface plot of $A$ for a protein network
* meshmaphistotrans(A): Same than meshmaphistorep but for a network with mRNA

''')

###### Example  of program that generates a 3-gene repressilator without mRNA, 
# explores the parameter space , plots a similar plot to Fig1c 
# and  also plots the trajectory of the first finding
#
#  define(mod = "repressilator", N = 3, lr = [-1,-1,-1])
#  parlist = exploreoscillations(1000)
#  meshmaphistorep(parlist[0])
#  setpars(parlist[0][0])
#  timeintplot()

###### Example  of program that generates a 3-gene repressilator with mRNA), 
# explores the parameter space, and plots a similar plot to Fig3b (different parameter exploration condition than the default one)
# and a plot similar to Fig3d 
#
#  define(mod = "transtrans", N = 3, lr = [-1,-1,-1])
#  parlist = exploreoscillations(1000)
#  freqplot(parlist,Ndim=6)
#  meshmaphistotrans(parlist[0])




def define(mod = "repressilator", N = 3, lr = [-1,-1,-1], rm = True):
    # Creation of a new C integrator for a particular network topology
    # it also creates a pyDSTool dynamical system DS
    # The properties of the model are:
    #
    # mod = "repressilator", N repressive units, if a list is passed it sets the nature of each interaction
    #   [-1] for thermo repression and [+1] for thermo activation [+2] linear activation
    # mode = "transtrans", N  units composed of mrNA and protein with a list of repression between units
    #
    # rm is a flag to remove previous compilations of the C code before creating the new one

    global model # type of model (protein network "repressilator", or mRNA + protein "transtrans")
    global Ndim # number of nodes
    global listrep # list with the interactions defining the network
    global pardict # dictionary with the parameters used in the model, it can be changed after the C code is compilated

    model = mod
    Ndim = N

    if (rm): # try to erase old compilations to create the new C code
        try:
            os.remove("_radau5_ring_vf.so")
            print "Old compilation removed"
        except:
            print "Old complation not removed (probably it didn't exist?)"

    if (lr is False): # if lr is not given, then and all-repressor ring is created
        listrep = [-1]*Ndim
        if Ndim%2 == 0:
            listrep[-1] = 1 # if even number, add one activation
    elif type(lr) == list :
        listrep = lr
    else :
        sys.exit("Error: wrong argument lr")        
    if np.prod(listrep) == 1:
        sys.exit("Error: Even numer of repressions")

    icdict = {} # dictionary with the initial conditions, it can be redefined later
    # mRNAs are caled M1, M2, ...
    for i in range(1,N+1):
        icdict["P"+str(i)] =  0.02 # proteins will be called P1, P2, ....
        if model == "transtrans":
            icdict["M"+str(i)] = 0.002 # mRNAs will be called M1, M2, ....

    # custom initial parameters, they should be redifined later for each specific case
    parrepress = {"k":0.01,"rho":10.0,'n':3.0,'a':1.0,'d':10, 'f':10}
    partrans = {"a":1.1,"d":1.0}

    pardict = {"timedir": 1.0} # use -1.0 to perform backwards integrations
    for key in icdict: # creation of the parameter dictionary for the whole network pardict
        if model == "repressilator":
                for par,value in parrepress.iteritems():
                    pardict[par+key] = value # names will be kP1, rhoP1, kP2, etc.
        if model == "transtrans":
            if key[0]=="M":
                for par,value in parrepress.iteritems():
                    pardict[par+key] = value
            elif key[0]=="P":
                for par,value in partrans.iteritems():
                    pardict[par+key] = value

    # definition of a dictionary of auxiliar functions with the partition functions of the thermodynamic model
    auxfndict ={} 
    for i in range(1,N+1):
            iprev = str(linkbefore(i,N))
            if model == "repressilator":
                if listrep[i-1] == -1:
                    auxfndict["gP"+str(i)] = (["P"+iprev],
                        "1.0/(1+rhoP"+str(i)+"*pow(1+P"+iprev+"/kP"+str(i)+",nP"+str(i)+"))")
                    auxfndict["gPd"+str(i)] = (["P"+iprev],
                        "-gP"+str(i)+"(P"+iprev+")*gP"+str(i)+"(P"+iprev+")*nP"+str(i)+"*rhoP"+str(i)+"/kP"+str(i)+"*pow(1+P"+iprev+"/kP"+str(i)+",nP"+str(i)+"-1)")
                elif listrep[i-1] == 1:
                    auxfndict["gP"+str(i)] = (["P"+iprev],
                        "1.0/(1+rhoP"+str(i)+"*pow((1+P"+iprev+"/kP"+str(i)+")/(1+fP"+str(i)+"*P"+iprev+"/kP"+str(i)+"),nP"+str(i)+"))")
                    auxfndict["gPd"+str(i)] = (["P"+iprev],
                        ("-gP"+str(i)+"(P"+iprev+")*gP"+str(i)+"(P"+iprev+")*nP"+str(i)+"/kP"+str(i)+
                        "*(1.0/gP"+str(i)+"(P"+iprev+")-1)*(1.0/(1+P"+iprev+"/kP"+str(i)+")-fP"+str(i)+"/(1+fP"+str(i)+"*P"+iprev+"/kP"+str(i)+"))"))
                elif listrep[i-1] == 2:
                    auxfndict["gP"+str(i)] = (["P"+iprev],
                        "P"+iprev)
                    auxfndict["gPd"+str(i)] = (["P"+iprev],
                        "1")
                else:
                    sys.exit("Error: repression list can only contain -1, +1 or +2")
            elif model == "transtrans":
                # mRNA regulation works like repressilator but with constant names ending in "M"
                if listrep[i-1] == -1:
                    auxfndict["gM"+str(i)] = (["P"+iprev],
                        "1.0/(1+rhoM"+str(i)+"*pow(1+P"+iprev+"/kM"+str(i)+",nM"+str(i)+"))")
                    auxfndict["gMd"+str(i)] = (["P"+iprev],
                        "-gM"+str(i)+"(P"+iprev+")*gM"+str(i)+"(P"+iprev+")*nM"+str(i)+"*rhoM"+str(i)+"/kM"+str(i)+"*pow(1+P"+iprev+"/kM"+str(i)+",nM"+str(i)+"-1)")
                elif listrep[i-1] == 1:
                    auxfndict["gM"+str(i)] = (["P"+iprev],
                        "1.0/(1+rhoM"+str(i)+"*pow((1+P"+iprev+"/kM"+str(i)+")/(1+fM"+str(i)+"*P"+iprev+"/kM"+str(i)+"),nM"+str(i)+"))")
                    auxfndict["gMd"+str(i)] = (["P"+iprev],
                        ("-gM"+str(i)+"(P"+iprev+")*gM"+str(i)+"(P"+iprev+")*nM"+str(i)+"/kM"+str(i)+
                        "*(1.0/gM"+str(i)+"(P"+iprev+")-1)*(1.0/(1+P"+iprev+"/kM"+str(i)+")-fM"+str(i)+"/(1+fM"+str(i)+"*P"+iprev+"/kM"+str(i)+"))"))
                # Protein dynamics are just lineal
                auxfndict["gP"+str(i)] = (["M"+str(i)],
                    "M"+str(i))
                auxfndict["gPd"+str(i)] = (["M"+str(i)],
                    "1")

    # definition of the r.h.s of the ODEs defining the dynamical system
    vardict = {}
    for i in range(1,N+1):
        iprev = str(linkbefore(i,N))
        if ((model == "repressilator")):
            vardict["P"+str(i)] = "timedir*dP"+str(i)+"*(aP"+str(i)+"*gP"+str(i)+"(P"+iprev+")-P"+str(i)+")"
        if (model == "transtrans"):
            vardict["M"+str(i)] = "timedir*dM"+str(i)+"*(aM"+str(i)+"*gM"+str(i)+"(P"+iprev+")-M"+str(i)+")"
            vardict["P"+str(i)] = "timedir*dP"+str(i)+"*(aP"+str(i)+"*M"+str(i)+"-P"+str(i)+")"
        
    # definition of the pyDSTool system given the objects defined above
    
    print "Creating PyDSTool dynamical system with the following properties\n"
    print icdict
    print pardict
    print vardict
    for el,sel in auxfndict.items(): 
        print el, sel

    DSargs = args() # create an instance of the arg class
    DSargs.name = "ring" # name of the object
    DSargs.ics = icdict # initial conditions
    DSargs.pars = pardict # parameters of the system
    DSargs.tdata = [0,1000] # time range for the integration
    DSargs.varspecs = vardict # definition of the rhs of the ODEs
    DSargs.fnspecs = auxfndict # definition of the functions used in the ODEs
    global DS # making it accessible to other methods
    DS = Generator.Radau_ODEsystem(DSargs) # Dynamical system created through Radau integrator, 
    # all changes to dicts do not have any effect in DS, to change properties of DS it must be done through the command DS.set()
#   DS = Generator.Vode_ODEsystem(DSargs) # alternative integrator

def settime(t): # set time range of the integration
    DS.set(tdata=[0,t])

def setpars(pard): # set parameters for the integration
    DS.set(pars=pard)

def integrate(): # integrate for the given parameters and obtain the trajectories for each protein/mRNA
    global traj
    traj=DS.compute('line').sample()


def timeintplot(direction="f", savefig = False):
# funcion to integrate and plot a trajectory for a given parameter set 
# direction sets the integration forwards "f" or backwards "b" in time

    global trajs

    if direction == "f": # forward integration
        DS.pars["timedir"] = 1.0
    elif direction == "b": # backward integration, the initial conditions are set inside the potentially invariant attraction hypercube
        for el in DS.initialconditions:
            DS.initialconditions[el] =  DS.pars["a"+el]/2.0
        DS.pars["timedir"]= -1.0
    else:
        print "Wrong time direction keyword: ",direction

    trajs=[] # trajs will contain the integrated trajectory
    fig, ax = plt.subplots(1,1, sharex=True, figsize=(6,6))
    trajs.append(DS.compute('line').sample()) # integration
    for i in range(Ndim): # plot for every species
        ax.plot(trajs[-1]['t'],trajs[-1]['P'+str(i+1)],c=colorcycle[i],lw=1.5)#,label=lab
        if model == "transtrans":
            ax.plot(trajs[-1]['t'],trajs[-1]['M'+str(i+1)],":",c=colorcycle[i],lw=1.5)#,label=lab

    # setting labels        
    plt.xlabel('$t$' ,fontsize=24)
    ax.set_ylabel('Gene Expression',fontsize=24)
    ax.set_yscale('log')
    
    yticks = ax.yaxis.get_major_ticks()
    yticks[-1].label1.set_visible(False)

    plt.ylim([1E-6,100])

    if savefig is True:
        plt.tight_layout()
        plt.savefig("timeint.pdf")
    plt.show()


################################################################
# PARAMETER SAMPLING
#################################################################

rngrnd = {} # this dictionary will contain the ranges for the exploration
def exploreoscillations(N=10000, usetraj = True, checktraj = True, ratio = 10):
    # function to explore a parameter set.
    # N is the number of random sets explored
    # usetraj=True uses the integrated trajectory to check oscillations, otherwise the Jacobian is used
    # checktraj computes trajectory AND Jacobian to check that they coincide
    # ratio is a dummy variable just in case it is wanted to run several exploreoscillations with different properties
    # it returns to lists: oscdislist containing the successful sets of parameters and nonchecktrajlist that contains missmatches
    # between networks that oscillate and have a stable node, or networks that do not oscillate but have an unstable node
    # nonchecktrajlist should in general be empty.
    oscdictlist = [] # sets of parameters that show oscillations
    logAlist = [] # sets of values of network parameter $A$
    skippedlist = [] # sets of parameters that give integration problems (should stay empty)
    nonchecktrajlist = [] # trajectories with non-matching results between oscilatory behaviour and unstable steady state from Jacobian 
    nwrite = 100 # parameter to prompt the progress of the sampling in the console
    DS.algparams['max_pts'] = 100000 # max points obtained in each trajectory, the bigger the more accurate (and slower)


    ## Setting parameter range

    global rngrnd
    if model == "repressilator":
    # Parameters used for sampling networks without mRNA
        rngrnd = {"kP": [-5.0,-1.0],# ranges indicate logarithmic limits
                  "aP": [-4.0,4.0],
                  "rhoP": [-4.0,4.0],
                  "fP" : [1.0,5.0], # this is $l$ in the text
                  "dP": [-2,3],
    #          "nM": [np.log10(5.0),np.log10(5.0)]
             }
    elif model == "transtrans":
        # Parameters used in the transcription translation
        rngrnd = {"kM": [-5.0,-1.0],
                   "aP": [-4.0,4.0],
                   "aM": [0.0,8.0],
                   "rhoM": [-4.0,4.0],
                   "fM" : [1,5],
                   "dP": [-3,0.0],
                   "dM": [0,3.0],
                   #"nM": [np.log10(5.0),np.log10(5.0)]
                   }

    else:
        print "Wrong model name for parameter range"

    for i in range(N): # for each parameter set
        if i%nwrite==0: # prompt for sampling progress
            print "("+str(i)+"/"+str(N)+") osc:"+str(len(oscdictlist))

        for rngpar,rngrange in rngrnd.items(): # stochastic sampling of parameters
            rnglist = np.power(10,(random.uniform(rngrange[0], rngrange[1], size=Ndim)))
            for i in range(1,Ndim+1):
                DS.pars[rngpar+str(i)] = rnglist[i-1]

        ### The following commented blocks show different choices for the random samplings
        ### and should be activated accordingly

        ### fixing of the first protein degradation to 1.0 or protein and mRNA to 0.1/10.0 (as in Fig. 3)
        if model=="repressilator":
            DS.pars["dP1"] = 1.0 # this option is used when the first proteins is fixed to 1.0
        if model=="transtrans":    
            DS.pars["dP1"] = 0.1 # this option is used when one the first protein is fixed to 1.0 
            DS.pars["dM1"] = 10.0 # this option is used when the first mRNA is fixed to 1.0 

        #### differences used in the parameter rho when there is an activator
        # DS.pars["rhoP3"]=DS.pars["rhoP3"]*1E7
        # DS.pars["rhoP5"]=DS.pars["rhoP5"]*1E7  
        # DS.pars["rhoP4"]=DS.pars["rhoP4"]*1E7
        # DS.pars["aP4"]=1.0

#       #### Fixing a random protein species degradation rate
#        dPlist = ["dP1","dP2","dP3"]
#        fixeddP = dPlist[random.randint(3)]
#        DS.pars[fixeddP] = 1E3

#       ###  Fixing a random element degradation rate to 1.0 and dp5 to ratio
#       maxd = int(np.random.randint(3))+1 # random number in the set (1,2,3)
#        DS.pars["dP"+str(maxd)] = 1.0
#        DS.pars["dP4"]= ratio

#       ###  Fixing some the degradation rates to one fixed value
        #DS.pars["dP1"] = 1.0 # in general dP1 is always set to one since there is this freedom in the time scaling of the whole DS
        # DS.pars["dP2"]=DS.pars["dP1"]
        # DS.pars["dP3"]=DS.pars["dP1"]
        # DS.pars["dP4"]=DS.pars["dP1"]
        # DS.pars["dP5"]=DS.pars["dP1"]

        try: # Looking for the steady state and its stability the subroutines for this are defined at the end of the code
            zstar = findnode() # find node position
            stab = findstability(zstar) # compute its stability
            if usetraj or checktraj:
                integrate() # resulting trajectory is recorded in the global variable "traj"
                lent=len(traj["t"])
                # finding the period T from a trajectory, T=-1 means a non-oscillatory regime
                T = findperiod2(traj["t"][lent/2:],traj["P1"][lent/2:]) 
                T = T["period"]
                if checktraj:
                    Ttraj = T
            if not(usetraj): # if trajectories are not computed, use variable T simply to record the stability 
                if np.max(stab).real>0.0:
                    T = 0 #unstable
                else:
                    T = -1 # stable
            if checktraj: # checking that the integration and the stability analysis coincide in predicting oscillations
                #print Ttraj, T 
                if ((Ttraj>0 and T==-1) or (Ttraj<0 and T==0)): # is stability not coinciding
                    print "non-matching equilibrium found!" # this could happen if the Hopf bifurcation is supercritical.
                    nonchecktrajlist.append(dict(DS.pars))

            A = findA (zstar) # value of the network parameter $A$
            logAlist.append(np.log(abs(A)))
            
            if T>-1: # if the system is oscillating
                oscdictlist.append(dict(DS.pars))
        except : # if there was some error during the integration/Jacobian/Period computation raise warning
            print "Skipped set of parameters"
            skippedlist.append(dict(DS.pars))

    print "From "+ str(N) +" points, found " + str(len(oscdictlist)) + " oscillations and "+  str(len(skippedlist))+" were skipped. logGeoaverage(A) = "+str(np.mean(logAlist)) 
    if checktraj:
        print "There were "+str(len(nonchecktrajlist))+" bad matches between trajectories and eigenvalues"
    return oscdictlist, nonchecktrajlist


########## PLOTTING FUNCTIONS  ###############

def freqplot(Mlist,integ = False, Ndim = 3):
    # Takes a list of sets of parameters Mlist, computes magnitudes such as frequency or amplitude and plots them (as in Fig. 2b)
    # The option integ=True uses values of period, amplitude, etc... already computed in a previous running of freqplot that are stored in Mlist, to save time 
    # note that the plot does not identify automatically the number of nodes i.e. it has been given manually through the parameter Ndim
    # this is used to set the expected value for A
    sns.set_style("ticks")
    sns.set_context("talk")
    fig, ax = plt.subplots(2,figsize=(8, 6),sharex=True)
    sns.despine()
    outlist = []

    mincoloridx = 100.0 # coloridx is used when setting a color from the properties of the network
    maxcoloridx = -100.0
    maxA = 0 # range of the plot
    minA = 100

    for iM, M in enumerate(Mlist):
        for idx,el in enumerate(M):
            print "analyzing "+str(idx)+"/"+str(len(M))
            if integ is True: # use the already computed values of amplitude and frequency
                T = {}
                T["period"] = el["period"]
                T["amplitude"] = el["amplitude"]
                A = el["A"]
                T["degradationrates"] = el["degradationrates"]
            else: # integrate a trajectory and compute the period and amplitude for the given parameters
                DS.pars.update(el)
                T = 0
                goodint = False
                minradau = 100000
                maxradau = 10000000
                DS.algparams['max_pts'] = minradau # change in computation time the number of points required for radau convergence
                while (not(goodint) and DS.algparams['max_pts']<maxradau): # if simulation accuracy is not good, repeat with more precision
                    try:
                        integrate()
                        goodint = True
                    except:
                        DS.algparams['max_pts'] = 2 * DS.algparams['max_pts'] 
                        print DS.algparams['max_pts']
                if goodint is False:
                    T = -2
                else: # if a good trajectory is found, compute parameter $A$, period and amplitude
                    lent=len(traj["t"])
                    T = findperiod2(traj["t"][lent/2:],traj["P1"][lent/2:]) 
                    if (T["period"]!= -1 and T["period"]!=0):        
                        zstar = findnode()
                        stab = findstability(zstar)
                        A = findA(zstar)
                        Ndim = len(DS.variables)
                        if A>maxA:
                            maxA = A
                            maxi = idx
                        if A<minA:
                            minA = A
                            mini = idx
                        T["degradationrates"] = {"dP2":el["dP2"],"dP3":el["dP3"]}
                        outlist.append({"A": A, "period": T["period"], "amplitude":T["amplitude"],
                                        "degradationrates" : T["degradationrates"],
                                        })

            dd2 = T["degradationrates"]["dP2"] # lists with the degradation rates
            dd3 = T["degradationrates"]["dP3"]

            # plot of the frequency and the amplitude
            alpha= 1.0 # 0.3 transparency value used in the paper
            ax[0].loglog(A,2*np.pi/T["period"],'o',alpha=alpha, markersize = 3, color = colorcycle[iM]) #color = inferno_cmap(coloridx)) 
            ax[1].loglog(A,T["amplitude"],'o',alpha=alpha, markersize = 3, color = colorcycle[iM]) # color = inferno_cmap(coloridx))

    wrange = np.logspace(-2,1,10)
    Hrange = np.logspace(-8,14,10)

    # plot of the analytical prediction
    miny = 0.2
    maxy = 0.5
    lrange = np.linspace(miny,maxy)
    ax[0].scatter(np.power(1.0/np.cos(np.pi/Ndim),Ndim),np.tan(np.pi/Ndim),
        marker='o',edgecolor=colorcycle[0], facecolor="w", s = 50, lw=2.0, clip_on = False, zorder=100)
    ax[0].semilogx(np.power(1.0/np.cos(np.pi/Ndim),Ndim)*np.ones(len(wrange)),wrange,'--',color=colorcycle[0],zorder=-10,clip_on=False)
    ax[1].semilogx(np.power(1.0/np.cos(np.pi/Ndim),Ndim)*np.ones(len(wrange)),Hrange,'--',color=colorcycle[0],zorder=-10,clip_on=False)

    # set axes properties
    ax[0].set_xlim([1,400])
    ax[1].set_xlim([1,400])
    ax[0].set_ylim([0.05,10.0])
    ax[1].set_ylim([1E-8,1E10])    
    ax[0].set_xlabel('$A$',fontsize=24)
    ax[0].set_ylabel('$\omega$',fontsize=24, rotation = 0)
    ax[1].set_ylabel('Amplitude',fontsize=24)
    plt.tight_layout()
    plt.savefig("freqampplot.pdf")
    plt.show()
    return outlist


def meshmaphistorep(parlist,parlistB=False):
    # takes the result of a parameter exploration list parlist and plots it agains the topological value $A$.
    # parlistB is an alternative list to parlist that is also plotted to compare to explorations (e.g. Fig 1c)

     
    minx = -2.0
    maxx = 3.0
    miny = -2.0
    maxy = 3.0

    sns.set_style("ticks")
    sns.set_context("talk")
    fig, ax = plt.subplots(figsize=(8, 6))
    sns.despine()

    # evaluating the analytical expression of A as a function of two degradation rates
    delta = 0.05 # mesh size for the evaluation
    x = np.power(10,(arange(minx,maxx,delta)))
    y = np.power(10,(arange(minx,maxx,delta)))
    X,Y = meshgrid(x, y) # grid of point
    Z = np.log10((X+Y+X*Y)*(X+Y+1)/(X*Y)) # evaluation of A 

    # plot of the analytical expression of A
    cmapA = sns.cubehelix_palette(light=1, start = 0.42, as_cmap=True) # colormap for the analytical expression of A 
    colorA = ax.imshow(Z, cmap=cmapA,  
            extent=[minx, maxx, miny, maxy],
            vmin= np.log10(8.0), vmax=3.0,
            origin = 'lower', interpolation='sinc')
    cbarmA = fig.colorbar(colorA, shrink=0.8) # colorbar

    # list of degradation rates obtained in the exploration
    P2 = [el["dP2"] for el in parlist] 
    P2 = np.log10(np.array(P2).flatten())
    P3 = [el["dP3"] for el in parlist] 
    P3 = np.log10(np.array(P3).flatten())

    if parlistB:
        P2b = [el["dP2"] for el in parlistB] 
        P2b = np.log10(np.array(P2b).flatten())
        P3b = [el["dP3"] for el in parlistB] 
        P3b = np.log10(np.array(P3b).flatten())

    # colors and properties of the scatter plot
    clrs = sns.color_palette()
    clrsalpha = sns.color_palette()
    clrsdark = sns.color_palette("dark")
    alphac = (0.7,)
    # plot of the exploration
    plt.scatter(P2,P3,color = clrs[0]+(alphac),marker ='o' ,edgecolor = clrsdark[0], s = 30)
    if parlistB:
        plt.scatter(P2b,P3b,color = clrs[1]+(alphac), marker = 's' ,edgecolor = clrsdark[1], s = 30)

    ax.set_xlim([minx,maxx])
    ax.set_ylim([miny,maxy])
    ax.set_xlabel("$\log_{10}\,\delta_2/\delta_1$",fontsize=24)
    ax.set_ylabel("$\log_{10}\,\delta_3/\delta_1$",fontsize=24)
    cbarmA.set_ticks([np.log10(10),np.log10(30),np.log10(100),np.log10(300),np.log10(1000)])
    cbarmA.set_ticklabels(["10","30","100","300","1000"])

    ax.text(3.5,-2,"$A$",fontsize=24)

    plt.tight_layout()
    plt.savefig("ring3.pdf")
    plt.show()


def meshmaphistotrans(parlist):

    # limits of the plot        
    minx = -3.0
    maxx = 3.0
    miny = -3.0
    maxy = 3.0

    # properties of the axes
    sns.set_style("ticks")
    sns.set_context("talk")
    fig, ax = plt.subplots(figsize=(8, 6))
    sns.despine()

    # creating list of values to plot, this will change depending on the size of the network
    evenmRNA = [el["dM2"] for el in parlist] 
    evenmRNA = np.log10(np.array(evenmRNA).flatten())
    oddmRNA = [el["dM3"] for el in parlist] 
    oddmRNA = np.log10(np.array(oddmRNA).flatten())
    evenprot = [el["dP2"] for el in parlist] 
    evenprot = np.log10(np.array(evenprot).flatten())
    oddprot = [el["dP3"] for el in parlist] 
    oddprot = np.log10(np.array(oddprot).flatten())

    # histograms showing the probability density
    zmRNA,xmRNA,ymRNA = np.histogram2d(oddmRNA, evenmRNA, bins=20, range=None, normed=True, weights=None)
    zprot,xprot,yprot = np.histogram2d(oddprot, evenprot, bins=20, range=None, normed=True, weights=None)

    print "sumhisto ", sum(zmRNA), sum(zprot)

    # bin sizes
    dxhmRNA = xmRNA[1]-xmRNA[0]
    dyhmRNA = ymRNA[1]-ymRNA[0]
    dxhprot = xprot[1]-xprot[0]
    dyhprot = yprot[1]-yprot[0]

    # creating the color palelttes for mrNA and protein
    cmapmRNA = sns.cubehelix_palette(light=1, start = 0, as_cmap=True)
    cmapprot = sns.cubehelix_palette(light=1, start = 0, rot = -0.75, as_cmap=True)

    # density plot for mRNA
    cmRNA = ax.imshow(zmRNA, cmap=cmapmRNA,  
            extent=[xmRNA[0], xmRNA[-1], ymRNA[0], ymRNA[-1]],
            vmin=0, vmax=np.max(zmRNA)*0.9, origin = 'lower',
            interpolation='sinc')

    # density plot for the protein
    cprot = ax.imshow(zprot, cmap=cmapprot,  
           extent=[xprot[0], xprot[-1], yprot[0], yprot[-1]],
           vmin=0, vmax=np.max(zprot), origin = 'lower',
           interpolation='sinc')

    # colorbars 
    cbarmRNA = fig.colorbar(cmRNA, shrink=0.8)
    cbarprot = fig.colorbar(cprot, shrink=0.8)

    # plot lines separating mRNA and protein
    ax.plot([rngrnd["dP"][0],rngrnd["dP"][1],rngrnd["dP"][1]], [rngrnd["dP"][1],rngrnd["dP"][1],rngrnd["dP"][0]],
        "--",color=sns.xkcd_rgb["charcoal"])
    ax.plot([rngrnd["dM"][0],rngrnd["dM"][0],rngrnd["dM"][1]], [rngrnd["dM"][1],rngrnd["dM"][0],rngrnd["dM"][0]],
        "--",color=sns.xkcd_rgb["charcoal"])
    # plot points indicating the coordinates of degradation of protein 1 and mrNA 1 (remove for 2 genes. e.g. Fig. 3c)
    ax.scatter(np.log10(el["dP1"]),np.log10(el["dP1"]),
        marker='o',edgecolor=sns.xkcd_rgb["charcoal"], facecolor="w", s = 50, lw=2.0, clip_on = False, zorder=100)
    ax.scatter(np.log10(el["dM1"]),np.log10(el["dM1"]),marker='o',edgecolor=sns.xkcd_rgb["charcoal"], facecolor="w", s = 50, lw=2.0)

    # setting plot axes, creating and saving plot
    ax.set_xlim([minx,maxx])
    ax.set_ylim([miny,maxy])
    ax.set_xlabel("$\log_{10}\,\delta_i$",fontsize=24)
    ax.set_ylabel("$\log_{10}\,\delta_{i\!+\!1}$",fontsize=24)
    ax.text(1.0,-0.4,"mRNA")
    ax.text(0.2,-1.2,"protein")
    ax.text(3.3,-3.5,"$\\rho(\delta^{protein})$")
    ax.text(5.0,-3.5,"$\\rho(\delta^{mRNA})$")
    fig.savefig("ringtrans.pdf")
    plt.show()


####################################################################################################
########### SUBROUTINES FOR COMPUTING DYNAMICAL PROPERTIES


def findperiod(t,y): 
# returns the period of a oscillating signal y in a time course t by using Fourier Transform
    y=(y-y.mean()/y.std())
    freqs=np.linspace(0.1,3,1000)
    pg=spectral.lombscargle(t,y,freqs)
    return 2.0*pi/freqs[argmax(pg)]


def findperiod2(t,y):
# alternative to findperiod returns the period of a oscillating signal y in a time course t by checking maxima and minima of signal
    eps1 = 0.01 # threshold in change of amplitude to consider an oscillator
    eps2 = 1E-10 # lower limit of concentration considered numerically accurate (related to parameter n_points of the integration scheme) 
    maxarg=argrelextrema(y,np.greater)[0]
    if len(maxarg)>4: # if more than 4 maxima are found:
        minarg=argrelextrema(y,np.less)[0]
        if abs(len(minarg)-len(maxarg))<3: # Check that is indeed oscillating by comparing its amplitude
            Amp1 = y[maxarg[-1]]-y[minarg[-1]]
            Amp2 = y[maxarg[-2]]-y[minarg[-2]]
            if (y[maxarg[-1]]/y[minarg[-1]]>(1+eps1) and y[maxarg[-1]]>eps2): # check for numerical artifacts
                if abs((Amp1-Amp2))/Amp1 < 0.1: # if the difference in amplitude between oscillations is small enough
#                    print "Oscillations found!"
                     return {"period": t[maxarg[-1]]-t[maxarg[-2]], "amplitude":  Amp1}
    return {"period": -1, "amplitude":  0}


def findnode():
# find the position of the node by solving the steady state equations
# returns a dictionary with the values for the proteins and the mRNA in the node

    # first is necessary to set the range [0,maxz] where looking for the solution:
    if model == "transtrans":
        maxz = DS.pars["aM"+str(Ndim)]*DS.pars["aP"+str(Ndim)]
    elif (model == "repressilator"):
        idxl = Ndim-1
        while (listrep[idxl]==2): # the case ==2 menans that the regulatory function is simply f(x)=x so that element cannot be used to check ranges
            idxl -= 1
        maxz = DS.pars["aP"+str(idxl+1)]

    zstar = brentq(dynamicFz,0,maxz) # Brent method for finding a solution
    dictzstar = {}
    fz=zstar
    if model == "transtrans":
        for i in range(1,Ndim+1):
            fz = DS.pars["aM"+str(i)]*DS.auxfns["gM"+str(i)](fz)
            dictzstar["M"+str(i)] = fz
            fz = DS.pars["aP"+str(i)]*fz
            dictzstar["P"+str(i)] = fz
    elif model == "repressilator":
        for i in range(1,Ndim+1):
            fz = DS.pars["aP"+str(i)]*DS.auxfns["gP"+str(i)](fz)
            dictzstar["P"+str(i)] = fz

    return dictzstar 


def dynamicFz(z): # function returning F(z)-z that should be zero in the steady state

    fz = z # variable to store the F(z)
    if model == "transtrans": 
        for i in range(1,Ndim+1):
                fz = DS.pars["aM"+str(i)]*DS.auxfns["gM"+str(i)](fz)
                fz = DS.pars["aP"+str(i)]*fz
    elif (model == "repressilator"):
        for i in range(1,Ndim+1):
                fz = DS.pars["aP"+str(i)]*DS.auxfns["gP"+str(i)](fz)
    return fz - z


def findstability(z): # returns the eigenvalues of the Jacobian

    Ndimext = len(DS.variables)
    J = np.zeros([Ndimext,Ndimext])
    for i in range(1,Ndim+1):
        iprev = str(linkbefore(i,Ndim))
        if (model == "repressilator"):
            J[i-1,i-1] = -DS.pars["dP"+str(i)]
            J[i-2,i-1] = DS.pars["dP"+str(i)]*DS.pars["aP"+str(i)]*DS.auxfns["gPd"+str(i)](z["P"+iprev])
        elif model == "transtrans":  
            # mRNA
 #       print i,linkbefore
            J[2*i-2,2*i-2] = -DS.pars["dM"+str(i)] 
            J[2*i-2,2*i-3] = DS.pars["dM"+str(i)]*DS.pars["aM"+str(i)]*DS.auxfns["gMd"+str(i)](z["P"+iprev])
        # protein
            J[2*i-1,2*i-1] = -DS.pars["dP"+str(i)]
            J[2*i-1,2*i-2] = DS.pars["dP"+str(i)]*DS.pars["aP"+str(i)]
    #print J
    return np.linalg.eigvals(J)    


def findA(z): # find value of $A$ defined in the manuscript
    A = 1.0
    if (model == "repressilator"):
        for i in range(1,Ndim+1):
            A = A * DS.auxfns["gPd"+str(i)](z["P"+str(linkbefore(i,Ndim))])
            A = A * DS.pars["aP"+str(i)]
    elif model == "transtrans":
        for i in range(1,Ndim+1):
            A = A * DS.auxfns["gMd"+str(i)](z["P"+str(linkbefore(i,Ndim))])
            A = A * DS.pars["aM"+str(i)]
            A = A * DS.pars["aP"+str(i)]
    return -A

def linkbefore(i,Ndim): # returns the element of the ring that is pointed by element i
    return (i+(Ndim-2))%(Ndim)+1

def parstats(A,rngrnd=rngrnd): # makes a stat summary of the statistics of the parameter list A, usually obtained from the parameter exploration exploreosscilations()
    amlist = np.array([[np.log10(el["am1"]), np.log10(el["am2"]),
              np.log10(el["am3"]), np.log10(el["am4"]), np.log10(el["am5"])] for el in A]).flatten()
    aplist = np.array([[np.log10(el["ap1"]), np.log10(el["ap2"]),
              np.log10(el["ap3"]), np.log10(el["ap4"]), np.log10(el["ap5"])] for el in A]).flatten()
    dplist = np.array([[np.log10(el["dp1"]), np.log10(el["dp2"]),
              np.log10(el["dp3"]), np.log10(el["dp4"]), np.log10(el["dp5"])] for el in A]).flatten()
    dmlist = np.array([[np.log10(el["dm1"]), np.log10(el["dm2"]),
              np.log10(el["dm3"]), np.log10(el["dm4"]), np.log10(el["dm5"])] for el in A]).flatten()
    kmlist = np.array([[np.log10(el["km1"]), np.log10(el["km2"]),
              np.log10(el["km3"]), np.log10(el["km4"]), np.log10(el["km5"])] for el in A]).flatten() 
    rhomlist = np.array([[np.log10(el["rhom1"]), np.log10(el["rhom2"]),
              np.log10(el["rhom3"]), np.log10(el["rhom4"]), np.log10(el["rhom5"])] for el in A]).flatten() 


    print "km:  ", np.mean(kmlist), "(", np.std(kmlist), ") Mm[,",min(kmlist),max(kmlist),"]  from ", rngrnd["km"]
    print "rhom:  ", np.mean(rhomlist), "(", np.std(rhomlist), ") Mm[,",min(rhomlist),max(rhomlist),"]  from ", rngrnd["rhom"]
    print "ap:  ", np.mean(aplist), "(", np.std(aplist), ") Mm[,",min(aplist),max(aplist),"]  from ", rngrnd["ap"]
    print "am:  ", np.mean(amlist), "(", np.std(amlist), ") Mm[,",min(amlist),max(amlist),"]  from ", rngrnd["am"]
    print "dp:  ", np.mean(dplist), "(", np.std(dplist), ") Mm[,",min(dplist),max(dplist),"]  from ", rngrnd["dp"]
    print "dm:  ", np.mean(dmlist), "(", np.std(dmlist), ") Mm[,",min(dmlist),max(dmlist),"]  from ", rngrnd["dm"]


        
# save a sets of values, usually ontained from the parameter exploration exploreoscillations()
def saved(A,name):
    with open(name,'w') as fileopen:
        json.dump(A,fileopen)

# loads a sets of values, usually ontained from the parameter exploration exploreoscillations()
def loadd(name):
    with open(name) as fileopen:
        return json.load(fileopen)



########################################################################################
#################   INITIAL ACTIONS
#########################################################################################

info()
#define()





