# Converted from hbv_common.mimportnumpyasnpfromscipy.integrateimportsolve_ivpdefhbv_deriv(t,x,p):k=p['k']return[k[1]*x[1]-k[3]*x[0],k[0]*x[0]-k[1]*x[1]-k[5]*x[1]*x[2],k[2]*x[0]-k[4]*x[2]-k[5]*x[1]*x[2]]def_load_stochsim_lib():"""Try to load the compiled C stochsim shared library via ctypes."""importctypes,osso=os.path.join(os.path.dirname(__file__),'stochsim_py.so')ifnotos.path.exists(so):returnNonetry:lib=ctypes.CDLL(so)lib.stochsim.restype=Nonelib.stochsim.argtypes=[ctypes.POINTER(ctypes.c_double),ctypes.c_int,# x0, nxctypes.POINTER(ctypes.c_double),# kctypes.POINTER(ctypes.c_double),ctypes.c_int,# stoiT, nrxnsctypes.POINTER(ctypes.c_double),ctypes.c_int,# tout, ntsctypes.POINTER(ctypes.c_double),# xoutctypes.c_ulong,# seed]returnlibexceptException:returnNone_stochsim_lib=_load_stochsim_lib()defhbv_stochsim(x0,k,stoiT,tout,rng):"""Gillespie algorithm for HBV model. Uses compiled C extension if available."""importctypesx0=np.array(x0,dtype=float)nts=len(tout)nx=len(x0)if_stochsim_libisnotNone:# Fast path: call compiled C libraryxout=np.zeros((nx,nts),dtype=np.float64,order='C')stoiT_cm=np.ascontiguousarray(stoiT,dtype=np.float64)# nx x nrxnstout_c=np.ascontiguousarray(tout,dtype=np.float64)k_c=np.ascontiguousarray(k,dtype=np.float64)x0_c=np.ascontiguousarray(x0,dtype=np.float64)seed=rng.randint(0,2**31)nrxns=stoiT.shape[1]_stochsim_lib.stochsim(x0_c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),nx,k_c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),stoiT_cm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),nrxns,tout_c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),nts,xout.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),ctypes.c_ulong(seed),)returnxout# Fallback: pure Python Gillespiex=x0.copy()t=tout[0]n_rxns=stoiT.shape[1]tout_idx=0results=np.zeros((nx,nts))results[:,0]=x0whiletout_idx<nts-1:r=np.zeros(n_rxns)r[0]=k[0]*x[0];r[1]=k[1]*x[1];r[2]=k[2]*x[0]r[3]=k[3]*x[0];r[4]=k[4]*x[2];r[5]=k[5]*x[1]*x[2]rtot=np.sum(r)ifrtot==0:foridxinrange(tout_idx+1,nts):results[:,idx]=xbreakp2=rng.rand(2)timenew=t-np.log(p2[0])/rtotwhiletout_idx<nts-1andtout[tout_idx+1]<=timenew:tout_idx+=1results[:,tout_idx]=xiftout_idx>=nts-1:breakcumr=np.cumsum(r)m=np.searchsorted(cumr,p2[1]*rtot)ifm<n_rxns:x=np.maximum(x+stoiT[:,m],0)t=timenewreturnresultsdefhbv_common():k=np.array([1.,0.025,1000.,0.25,2.,7.5e-6])p={'k':k}stoi=np.array([[0,1,0],[1,-1,0],[0,0,1],[-1,0,0],[0,0,-1],[0,-1,-1]],dtype=float)x0=np.array([1.,0.,0.])stoiT=stoi.Ttfin=200.0nts=200tout=np.linspace(0,tfin,nts)p['x0']=x0;p['stoiT']=stoiT;p['tout']=tout;p['tfin']=tfin;p['nts']=ntssqeps=np.sqrt(np.finfo(float).eps)sol=solve_ivp(lambdat,x:hbv_deriv(t,x,p),[0,tfin],x0,method='Radau',t_eval=tout,rtol=sqeps,atol=sqeps)tsolver=sol.tx=sol.y.Treturnp,tsolver,xif__name__=='__main__':p,tsolver,x=hbv_common()print(f'hbv_common ran ok, x shape = {x.shape}')
# Converted from hbv_binary.m# [depends] hbv_common.py stochsim_py.c# [makes] hbv_binary.npz# [rule] copymat# Runs 500 stochastic simulations and saves results as hbv_binary.npz.importnumpyasnpfromhbv_commonimporthbv_common,hbv_stochsimp,tsolver,xdet=hbv_common()rng=np.random.RandomState(0)nsim=10# change to 500 for figure in textnts=p['nts']x0=p['x0']nspec=len(x0)xavg=np.zeros((nspec,nts))foriinrange(nsim):xsim=hbv_stochsim(x0,p['k'],p['stoiT'],p['tout'],rng)xavg+=xsim/nsimxnull=hbv_stochsim(x0,p['k'],p['stoiT'],p['tout'],np.random.RandomState(121))np.savez('hbv_binary.npz',xavg=xavg,xout=xsim,tout=p['tout'],x_det=xdet.T,xnull=xnull)print('hbv_binary.npz saved')
/* * stochsim_py.c -- Gillespie stochastic simulator for HBV model. * Called from Python via ctypes. * * Interface: * void stochsim(const double *x0, int nx, * const double *k, * const double *stoiT, // shape (nx, nrxns), column-major * int nrxns, * const double *tout, int nts, * double *xout, // shape (nx, nts), column-major (output) * unsigned long seed) */#include<math.h>#include<stdlib.h>#include<string.h>/* Simple LCG random number generator (thread-safe, seedable per call) */staticunsignedlongrng_state;staticvoidrng_seed(unsignedlongseed){rng_state=seed;}staticdoublerng_rand(void){rng_state=rng_state*6364136223846793005ULL+1442695040888963407ULL;return(double)((rng_state>>33)&0x7FFFFFFF)/(double)0x7FFFFFFF;}voidstochsim(constdouble*x0,intnx,constdouble*k,constdouble*stoiT,/* nx * nrxns, column-major */intnrxns,constdouble*tout,intnts,double*xout,/* nx * nts, column-major (output) */unsignedlongseed){rng_seed(seed);double*x=(double*)malloc(nx*sizeof(double));memcpy(x,x0,nx*sizeof(double));doubletime=tout[0];intiout=0;/* HBV-specific rates (6 reactions, 3 species) */doubler[6];while(iout<nts){doublex0v=x[0],x1v=x[1],x2v=x[2];r[0]=k[0]*x0v;r[1]=k[1]*x1v;r[2]=k[2]*x0v;r[3]=k[3]*x0v;r[4]=k[4]*x2v;r[5]=k[5]*x1v*x2v;doublertot=r[0]+r[1]+r[2]+r[3]+r[4]+r[5];if(rtot==0.0){while(iout<nts){for(ints=0;s<nx;s++)xout[s*nts+iout]=x[s];iout++;}break;}doublep1=rng_rand();doublep2=rng_rand();doubletimenew=time-log(p1)/rtot;/* record output times that fall before timenew */while(iout<nts&&tout[iout]<=timenew){for(ints=0;s<nx;s++)xout[s*nts+iout]=x[s];iout++;}if(iout>=nts)break;/* choose reaction */doublercum=0.0;doubletarget=p2*rtot;intm=0;for(m=0;m<nrxns-1;m++){rcum+=r[m];if(rcum>target)break;}/* apply stoichiometry */for(ints=0;s<nx;s++){doublexnew=x[s]+stoiT[s*nrxns+m];x[s]=xnew<0.0?0.0:xnew;}time=timenew;}free(x);}
main.py
1 2 3 4 5 6 7 8 9101112131415
# Converted from hbv_stoch_cccdna.m# [depends] hbv_common.py hbv_binary.npz hbv_binary.py stochsim_py.c# This py-file loads data file 'hbv_binary.npz'.importnumpyasnpfrommiscimportsave_asciidata=np.load('hbv_binary.npz')tout=data['tout']x_det=data['x_det'].Txavg=data['xavg']xout=data['xout']xnull=data['xnull']table=np.column_stack([tout,x_det,xavg.T,xout.T,xnull.T])save_ascii('hbv_stoch_cccdna.dat',table)