/* File rxnparam.c, written by Steven Andrews, 2003. This code is in the public domain. It is not copyrighted and may not be copyrighted. This file calculates reaction rates for a stochastic spatial numerical method, as described in the research paper "Stochastic Simulation of Chemical Reactions with Spatial Resolution and Single Molecule Detail" written by Steven S. Andrews and Dennis Bray, which was submitted to J. Phys. Chem. B in 2003. The only documentation at present are that paper and the comments in this file. The code here is written entirely in ANSII C. History: Started 3/02, converted to a separate file 5/31/03. */ /**************** rxnparam.h - header file for rest of code ******************* These comments can be cut and pasted to create an independent header file. #ifndef __rxnparam_h #define __rxnparam_h float numrxnrate(float step,float a,float b); float actrxnrate(float step,float a); float bindingradius(float rate,float dt,float difc,float b,int rel); float unbindingradius(float rate,float dt,float difc,float a); double rdfabsorb(double *r,double *rdf,int n); void rdfdiffuse(double *r,double *rdfa,double *rdfd,int n,double step); void rdfreverserxn(double *r,double *rdf,int n,double step,double b,double flux); double rdfsteadystate(double *r,double *rdfa,double *rdfd,int n,double step,double b, double eps); void rdfmaketable(); #endif ***************************** End of header file *****************************/ #include #include #include #include "rxnparam.h" #define PI 3.1415926535897932 #define SQRT2PI 2.50662827462 float erfcc(float x); /* numrxnrate calculates the bimolecular reaction rate for the simulation algorithm, based on the rms step length in step, the binding radius in a, and the unbinding radius in b. It uses cubic polynomial interpolation on previously computed data, with interpolation both on the reduced step length (step/a) and on the reduced unbinding radius (b/a). Enter a negative value of b for irreversible reactions. If the input parameters result in reduced values that are off the edge of the tabulated data, analytical results are used where possible. If there are no analytical results, the data are extrapolated. Variable components: s is step, i is irreversible, r is reversible with b>a, b is reversible with b=0&&b<1) return -1; if(step==0) return 0; step=log(step/a); b/=a; sindx=(step-slo)/sinc; for(i=0;i<4;i++) x[i]=slo+(sindx-1+i)*sinc; z[0]=(step-x[1])*(step-x[2])*(step-x[3])/(-6.0*sinc*sinc*sinc); z[1]=(step-x[0])*(step-x[2])*(step-x[3])/(2.0*sinc*sinc*sinc); z[2]=(step-x[0])*(step-x[1])*(step-x[3])/(-2.0*sinc*sinc*sinc); z[3]=(step-x[0])*(step-x[1])*(step-x[2])/(6.0*sinc*sinc*sinc); if(b<0) for(ans=i=0;i<4;i++) { if(sindx-1+i>=snum) ans+=z[i]*2.0*PI*exp(2.0*x[i]); else if(sindx-1+i<0) ans+=z[i]*4.0*PI/3.0; else ans+=z[i]*yi[sindx-1+i]; } else if(b<1.0) { bindx=(b-blob)/bincb; if(bindx<1) bindx=1; else if(bindx+2>=bnumb) bindx=bnumb-3; while(sindx+3>=snum||sindx>0&&yb[(bindx-1)*snum+sindx+3]<0) sindx--; for(i=0;i<4;i++) x[i]=slo+(sindx-1+i)*sinc; z[0]=(step-x[1])*(step-x[2])*(step-x[3])/(-6.0*sinc*sinc*sinc); z[1]=(step-x[0])*(step-x[2])*(step-x[3])/(2.0*sinc*sinc*sinc); z[2]=(step-x[0])*(step-x[1])*(step-x[3])/(-2.0*sinc*sinc*sinc); z[3]=(step-x[0])*(step-x[1])*(step-x[2])/(6.0*sinc*sinc*sinc); for(j=0;j<4;j++) for(y[j]=i=0;i<4;i++) { if(sindx-1+i>=snum) y[j]+=z[i]*yb[(bindx-1+j)*snum]; else if(sindx-1+i<0) y[j]+=z[i]*4.0*PI/3.0; else y[j]+=z[i]*yb[(bindx-1+j)*snum+sindx-1+i]; } for(j=0;j<4;j++) x[j]=blob+(bindx-1+j)*bincb; z[0]=(b-x[1])*(b-x[2])*(b-x[3])/(-6.0*bincb*bincb*bincb); z[1]=(b-x[0])*(b-x[2])*(b-x[3])/(2.0*bincb*bincb*bincb); z[2]=(b-x[0])*(b-x[1])*(b-x[3])/(-2.0*bincb*bincb*bincb); z[3]=(b-x[0])*(b-x[1])*(b-x[2])/(6.0*bincb*bincb*bincb); ans=z[0]*y[0]+z[1]*y[1]+z[2]*y[2]+z[3]*y[3]; } else { b=log(b); bindx=(b-blor)/bincr; if(bindx<1) bindx=1; else if(bindx+2>=bnumr) bindx=bnumr-3; for(j=0;j<4;j++) for(y[j]=i=0;i<4;i++) { if(sindx-1+i>=snum&&b==0) y[j]+=z[i]*2.0*PI*exp(x[i])*(1.0+exp(x[i])); else if(sindx-1+i>=snum) y[j]+=z[i]*2.0*PI*exp(2.0*x[i])*exp(b)/(exp(b)-1.0); else if(sindx-1+i<0) y[j]+=z[i]*4.0*PI/3.0; else y[j]+=z[i]*yr[(bindx-1+j)*snum+sindx-1+i]; } for(j=0;j<4;j++) x[j]=blor+(bindx-1+j)*bincr; z[0]=(b-x[1])*(b-x[2])*(b-x[3])/(-6.0*bincr*bincr*bincr); z[1]=(b-x[0])*(b-x[2])*(b-x[3])/(2.0*bincr*bincr*bincr); z[2]=(b-x[0])*(b-x[1])*(b-x[3])/(-2.0*bincr*bincr*bincr); z[3]=(b-x[0])*(b-x[1])*(b-x[2])/(6.0*bincr*bincr*bincr); ans=z[0]*y[0]+z[1]*y[1]+z[2]*y[2]+z[3]*y[3]; } return ans*a*a*a; } /* actrxnrate calculates the effective activation limited reaction rate for the simulation, which is the reaction rate if the radial correlation function is 1 for all r>a. The returned value needs to be divided by delta_t. The equation is ka=4¹/3[erfc(Ã2/s)+sÃ(2/¹)]+2Ã(2¹)/3*s(s^2-1)[exp(-2/s^2)-1]. It was calculated analytically and verified numerically. */ float actrxnrate(float step,float a) { float ka; if(step<0||a<=0) return -1; if(step==0) return 0; step/=a; ka=4.0*PI/3.0*(erfcc(sqrt(2.0)/step)+step*sqrt(2.0/PI)); ka+=2.0*SQRT2PI/3.0*step*(step*step-1.0)*(exp(-2.0/step/step)-1.0); return ka*a*a*a; } /* bindingradius returns the binding radius that corresponds to some given information. rate is the actual rate constant (not reduced), dt is the time step, and difc is the mutual diffusion constant (sum of reactant diffusion constants). If b is -1, the reaction is assumed to be irreversible; if b>=0 and rel=0, then the b value is used as the unbinding radius; and it b>=0 and rel=1, then the b value is used as the ratio of the unbinding to binding radius, b/a. This algorithm executes a simple search from numrxnrate, based on the fact that reaction rates monotonically increase with increasing a, for all the b value possibilities. The return value is usually the binding radius. However, a value of -1 signifies illegal input parameters. */ float bindingradius(float rate,float dt,float difc,float b,int rel) { float a,lo,dif,step; int n; if(rate<0||dt<=0||difc<=0) return -1; if(rate==0) return 0; step=sqrt(2.0*difc*dt); lo=0; a=step; while(numrxnrate(step,a,rel?b*a:b)=1||difc<=0||a<=0||dt<=0) return -2; step=sqrt(2.0*difc*dt); ki=numrxnrate(step,a,-1); kmax=numrxnrate(step,a,0); if(1.0-ki/kmaxpgem) { lo=b; b*=2.0; } dif=b-lo; for(n=0;n<15;n++) { dif*=0.5; b=lo+dif; if(1.0-ki/numrxnrate(step,a,b)>pgem) lo=b; } b=lo+0.5*dif; return b; } /******************************************************************************/ /* FOLLOWING ROUTINES ARE FOR CALCULATING DIFFUSION CURRENTS */ /******************************************************************************/ /* erfcc returns the complementary error function, calculated with a method copied verbatim from Numerical Recipies in C, by Press et al., Cambridge University Press, Cambridge, 1988. It works for all x and has fractional error everywhere less than 1.2e-7 */ float erfcc(float x) { float t,z,ans; z=fabs(x); t=1.0/(1.0+0.5*z); ans=t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806 +t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)) ))))))); return x>=0.0?ans:2.0-ans; } /* rdfabsorb integrates the radial diffusion function (rdf) for 0<=r<=1, sets those values to 0, and returns the integral. r is a vector of radii; r[0] may equal zero but that is not required; if not, then it is assumed that the rdf has zero slope at the origin. Integration uses a spherical version of the trapezoid rule: at positions r0 and r1, the function f has values f0 and f1, leading to the linear interpolation f=[(r-r0)f1+(r1-r)f0]/(r1-r0). Its integral is A=º4¹r^2f(r)dr A=4¹/(r1-r0)*[(f1-f0)/4*(r1^4-r0^4)+(r1f0-r0f1)/3*(r1^3-r0^3)] A=¹(f1-f0)(r1+r0)(r1^2+r0^2)+4¹/3*(r1f0-r0f1)(r1^2+r1r0+r0^2) The left end of the integral assumes zero slope for the rdf. The right end does not terminate exactly at 1, but includes the upper left triangle of the final trapezoid. That way, if there are two absorptions in a row, the second one will return an integral of 0, and area is properly conserved. The problem is that it does not terminate exactly at 1. Furthermore, the correct relative location of 1 between two r[j] points depends on the function. The best solution is to use an unevenly spaced r[j] vector, with a very narrow separation about 1 and no r[j] equal to 1. */ double rdfabsorb(double *r,double *rdf,int n) { int j; double sum,r0,r1,f0,f1; r1=0; f1=rdf[0]; sum=0; for(j=(r[0]==0?1:0);r1<1&&j=0;j--) rdf[j]=0; return sum; } /* rdfdiffuse integrates the radial distribution function with the Green's function for radially symmetric diffusion to implement diffusion over a fixed time step. r is a vector of radii, rdfa is the input rdf, rdfd is the output rdf, n is the number of points, and step is the rms step length, equal to (2Dt)^1/2. r[0] may equal 0 but it is not required. It is assumed that rdfa has zero slope at r=0. The boundary condition on the large r side is that the function tends to 1 with a functional form 1+a2/r, for large r. This is accomplished by fitting the 10% largest r portion of the rdf with the function 1+a2/r. After the integral over the tabulated data is complete, the rdf is integrated on to infinity using the previous fit information and an analytical result for that integral. The numerical portion of the integral is carried out exactly like the one in absorb but with a different integrand, which is c(r)=º4¹r'^2*rdfa(r')*grn(r,r')dr'. grn(r,r') is the Green's function, equal to grn(r,r')=1/(4¹rr')[G_step(r-r')-G_step(r+r')] and G_s(x) is a normalized Gaussian with mean 0 and standard deviation s. */ void rdfdiffuse(double *r,double *rdfa,double *rdfd,int n,double step) { int i,j; double grn,sum,f0,f1,rr,r0,r1,alpha,beta,a2,erfcdif,erfcsum; alpha=beta=0; for(i=0.9*n;ieps; it++) { fluxold=flux; rdfdiffuse(r,rdfa,rdfd,n,step); if(b>=0) rdfreverserxn(r,rdfd,n,step,b,flux); for(i=0;i=maxit||flux>=maxflux) flux=-1; return flux; } /* rxnratetable is used to create data tables of reaction rates, including those used above in rxnratenum. All input is requested from the user using the standard input and all output is sent to standard output. Runtime is about 1 minute with mode i, 200 pts, eps=1e-4 */ void rdfmaketable() { double slo=exp(-3),shi=exp(3),sinc=exp(0.2); // step size low, high, increment const double blor=exp(0),bhir=exp(3),bincr=exp(0.2); // b low, high, inc for b>a const double blob=0,bhib=1.0,bincb=0.1; // b low, high, inc for bshi*sqrt(sinc);s*=sinc) { if(flux>=0) flux=rdfsteadystate(r,rdfa,rdfd,n,s,b,eps); if(mode=='i') printf("%lf %lf\n",s,flux); else if(mode=='r'||mode=='b') printf("%lf %lf %lf\n",b,s,flux); else printf("%lf,",fabs(flux)); } printf("\n"); if(mode=='i'||mode=='I') done=1; else if(mode=='r'||mode=='R') { b*=bincr; if(b>bhir*sqrt(bincr)) done=1; } else { b+=bincb; if(b>bhib+bincb/2.0) done=1; }} free(r); free(rdfa); free(rdfd); return; }