|
| 1 | +''' |
| 2 | +This file defines all of the necessary functions for DynaMETE, including the transition functions and |
| 3 | +the structure function R. This function does NOT include sums over n, since it is designed to be a |
| 4 | +more flexible version incorporating different transition functions. This will be very slow for large N or E. |
| 5 | +It also defines the METE constraint for beta, which is needed, and a function to obtain mete_lambdas. |
| 6 | +This file uses the toy model with only n and no e dependence. |
| 7 | +''' |
| 8 | + |
| 9 | +# Import |
| 10 | +import numpy as np |
| 11 | +import pandas as pd |
| 12 | +from scipy.optimize import fsolve |
| 13 | +from scipy import integrate |
| 14 | + |
| 15 | +# Get initial lambda |
| 16 | +def lambda_i(s): |
| 17 | + '''Initial lambda 1 given lambda 2 = 0 and state variables''' |
| 18 | + l1_guess = np.log(s['N']/(s['N']-1)) # log(no of individuals/no of individuals - 1) ? why we do that |
| 19 | + nrange = np.arange(s['N'])+1 #array of each index of the individuals |
| 20 | + l1_true = fsolve(lambda l1 : np.sum(nrange*np.exp(-l1*nrange))/np.sum(np.exp(-l1*nrange))-s['N']/s['S'],l1_guess)[0] #finding the first root of the equation using the guess |
| 21 | + li = np.array([l1_true,0]) |
| 22 | + return li |
| 23 | + |
| 24 | +# Transition functions |
| 25 | +# The idea here is to make everything easy to change by changing only these functions. |
| 26 | +# For f |
| 27 | +def fb0(s,p): |
| 28 | + return p['b0'] |
| 29 | +def fd0(s,p): |
| 30 | + return -p['d0']*s['N']/p['Nc'] |
| 31 | +def f(n,s,p): |
| 32 | + '''Transition function for dN/dt. n is the microscopic variables. |
| 33 | + s are state variables, call S, N |
| 34 | + p are parameters, call b0, d0, Nc ''' |
| 35 | + return (fb0(s,p)+fd0(s,p)*n)*n |
| 36 | + |
| 37 | +# Also need derivatives for lambda dynamics. Note that these have to be manually editted for alternate f,h,q |
| 38 | +def dfdt(n,s,p,ds): |
| 39 | + return fd0(s,p)/s['N']*ds['dN']*n*n |
| 40 | + |
| 41 | +# R itself |
| 42 | +def R(n,l,s,p): |
| 43 | + '''Unnormalized struture function for toy model. |
| 44 | + n is microscopic variables. |
| 45 | + l are lambdas |
| 46 | + s are state variables, call S, N |
| 47 | + p are parameters, call b0, d0, Nc ''' |
| 48 | + return np.exp(-l[0]*n-l[1]*f(n,s,p)) |
| 49 | + |
| 50 | +# For calculating a single mean with specific powers of n and e |
| 51 | +def mean_pow(npow,l,s,p,z=1): |
| 52 | + ''' |
| 53 | + This function returns the mean of n^npow over the R function. |
| 54 | + It is NOT normalized, but it does take in z as an optional argument to normalize. |
| 55 | + This function sums numerically over n. |
| 56 | + Note that npow=0 corresponds to Z, so by default these are not normalized. |
| 57 | + l are lambdas |
| 58 | + s are state variables, call S, N |
| 59 | + p are parameters, call b0, d0, Nc |
| 60 | + ''' |
| 61 | + nrange = np.arange(s['N'])+1 |
| 62 | + return np.sum(nrange**npow*R(nrange,l,s,p))/z |
| 63 | + |
| 64 | +# For calculating a covariance with specific powers of n and e for each function |
| 65 | +def cov_pow(npow,l,s,p,z): |
| 66 | + ''' |
| 67 | + This function returns the covariance of two functions with the form n^npow over the R function. |
| 68 | + You have to pass in the normalization so that things are faster than calculating normalization each time. |
| 69 | + npow should be a 2d array |
| 70 | + This function sums numerically over n. |
| 71 | + z is the normalization |
| 72 | + l are lambdas |
| 73 | + s are state variables, call S, N |
| 74 | + p are parameters, call b0, d0, Nc |
| 75 | + ''' |
| 76 | + nrange = np.arange(s['N'])+1 |
| 77 | + # Get integral over both functions |
| 78 | + ff = np.sum(nrange**np.sum(npow)*R(nrange,l,s,p))/z |
| 79 | + # Get integral over each function |
| 80 | + f1f2 = 1 |
| 81 | + for nn in npow: |
| 82 | + f1f2 *= np.sum(nrange**nn*R(nrange,l,s,p))/z |
| 83 | + return ff-f1f2 |
| 84 | + |
| 85 | +# For calculating a single mean over an arbitrary function |
| 86 | +# For these next two I could get clever and use *args etc to make it easier to pass in n and e |
| 87 | +# But I just don't want to bother right now. |
| 88 | +# Also, if you really want to do that, just use mean_pow since that's WAY easier |
| 89 | +def mean(func,l,s,p,*args,z=1): |
| 90 | + ''' |
| 91 | + This function returns the mean of an arbitrary function over the R function. |
| 92 | + It is NOT normalized, but it does take in z as an optional argument to normalize. |
| 93 | + Because I put *args first, you have to use z=z0 if you want to put in a normalization. |
| 94 | + The arbitrary function must take arguments of the form (n,s,p) for this to work. |
| 95 | + This is the form of the f function above. |
| 96 | + You can pass additional arguments as required for the function (ie. pass ds for df/dt) |
| 97 | + To pass in n, use lambda n,e,s,p: n or similar |
| 98 | + Alternatively, use mean_pow |
| 99 | + This function sums numerically over n. |
| 100 | + z is the normalization |
| 101 | + l are lambdas |
| 102 | + s are state variables, call S, N |
| 103 | + p are parameters, call b0, d0, Nc |
| 104 | + ''' |
| 105 | + nrange = np.arange(s['N'])+1 |
| 106 | + # Below is to make this easier for lambda functions, but it isn't worth it. Just require s and p passed, |
| 107 | + # and let other things be passed as args if needed. |
| 108 | + # Check if we need args by looking at function passed in |
| 109 | +# funcargs = func.__code__.co_argcount |
| 110 | +# if funcargs >= 4: |
| 111 | +# args = s,p,args |
| 112 | + return np.sum(R(nrange,l,s,p)*func(nrange,s,p,*args))/z |
| 113 | + |
| 114 | +# For calculating a covariance |
| 115 | +# Note if you want to do this with non-functions, use cov_pow |
| 116 | +def cov(func1,func2,l,s,p,z,*args): |
| 117 | + ''' |
| 118 | + This function returns the covariance of two arbitrary functions over the R function. |
| 119 | + You have to pass in the normalization so that things are faster than calculating normalization each time. |
| 120 | + The arbitrary functions must take arguments of the form (n,s,p) for this to work. |
| 121 | + This is the form of the f function above. |
| 122 | + You can pass additional arguments as required for the function (ie. pass ds for df/dt) |
| 123 | + To pass in n use lambda n,s,p: n |
| 124 | + This function sums numerically over n. |
| 125 | + z is the normalization |
| 126 | + l are lambdas |
| 127 | + s are state variables, call S, N |
| 128 | + p are parameters, call b0, d0, Nc |
| 129 | + ''' |
| 130 | + nrange = np.arange(s['N'])+1 |
| 131 | + # Get integral over both functions |
| 132 | + ffeint = R(nrange,l,s,p)*func1(nrange,s,p,*args)*func2(nrange,s,p,*args) |
| 133 | + ff = np.sum(ffeint)/z |
| 134 | + # Get integral over each function |
| 135 | + f1f2 = 1 |
| 136 | + for func in [func1,func2]: |
| 137 | + feint = R(nrange,l,s,p)*func(nrange,s,p,*args) |
| 138 | + f1f2 *= np.sum(feint)/z |
| 139 | + return ff-f1f2 |
| 140 | + |
| 141 | +def get_dXdt(l,s,p): |
| 142 | + ''' |
| 143 | + Returns the time derivatives of the state variables. |
| 144 | + Inputs lambdas, state variables, and parameters. |
| 145 | + Outputs a pandas series of ds. |
| 146 | + ''' |
| 147 | + # Create storage |
| 148 | + ds = pd.Series(np.zeros(2),index=['dS','dN']) |
| 149 | + # To normalize |
| 150 | + z = mean_pow(0,l,s,p) |
| 151 | + ds['dN'] = s['S']*mean(f,l,s,p,z=z) |
| 152 | + return ds |
0 commit comments