00001 """Compute Schrodinger equation eigenfunctions very quickly and efficiently using Numerov's method to solve the underlying differential equations"""
00002 _rcsid="$Id: numerov_solve.py,v 1.3 2003/10/03 14:37:38 mendenhall Exp $"
00003
00004 import math
00005 import Numeric
00006 import sys
00007
00008 try:
00009 from _numerov import _numerov
00010 def numerov_iter(Y, coefs):
00011 _numerov(Y,coefs)
00012 except:
00013 def numerov_iter(Y, coefs):
00014 npoints=len(Y)
00015 for i in range(1,npoints-1):
00016 yy=Y[i+1]=Y[i]*coefs[i]-Y[i-1]
00017 if abs(yy) > 1e20:
00018 Y*=1e-20
00019
00020
00021 def bare_numerov(V, dx):
00022 """generate a solution to Schrodinger's eqn for a potential V=(2m/hbar**2)(V-E) using the Numerov method.
00023 Always integrate in from forbidden region to center of potential from both ends, and solve to match boundary conditions. The initial conditions will fail if started in an allowed (V-E < 0) region"""
00024 npoints=len(V)
00025 Y=Numeric.zeros(npoints,Numeric.Float)
00026 dx2=dx*dx
00027 ypsifact=1.0/(1.0-(dx2/12.0)*V)
00028 coef=2.0+dx2*V*ypsifact
00029
00030 Y[0]=1.0/ypsifact[0]
00031 Y[1]=math.exp(math.sqrt(V[0])*dx)/ypsifact[1]
00032
00033 numerov_iter(Y,coef)
00034
00035 return Y*ypsifact
00036
00037
00038 def zbrent(func, x1, x2, tol, itmax=100, trace=0):
00039 "find zeros using the van Wijngaarden-Dekker-Brent method a la Numeric Recipes"
00040 a=x1; b=x2
00041 fa=func(a); fb=func(b)
00042
00043 assert fb*fa < 0.0, "Root must be bracketed in ZBRENT"
00044 fc=fb
00045 for iter in range(itmax):
00046 if trace: print iter, a, b, fa, fb
00047 if (fb*fc > 0.0):
00048 c=a
00049 fc=fa
00050 e=d=b-a
00051 if abs(fc) < abs(fb):
00052 a=b
00053 b=c
00054 c=a
00055 fa=fb
00056 fb=fc
00057 fc=fa
00058 tol1=2.0*1e-14*abs(b)+0.5*tol
00059 xm=0.5*(c-b)
00060 if abs(xm) <= tol1 or fb == 0.0: return b
00061 if abs(e) >= tol1 and abs(fa) > abs(fb):
00062 s=fb/fa
00063 if a==c:
00064 p=2.0*xm*s
00065 q=1.0-s
00066 else:
00067 q=fa/fc
00068 r=fb/fc
00069 p=s*(2.0*xm*q*(q-r)-(b-a)*(r-1.0))
00070 q=(q-1.0)*(r-1.0)*(s-1.0)
00071 if p>0.0: q = -q
00072 p=abs(p)
00073 min1=3.0*xm*q-abs(tol1*q)
00074 min2=abs(e*q)
00075 if 2.0*p < min(min1,min2):
00076 e=d
00077 d=p/q
00078 else:
00079 d=xm
00080 e=d
00081 else:
00082 d=xm; e=d
00083 a=b
00084 fa=fb
00085 if abs(d) > tol1:
00086 b += d
00087 elif xm > 0.0:
00088 b+=tol1
00089 else:
00090 b-=tol1
00091 fb = func(b);
00092
00093 assert 0, "Maximum number of iterations exceeded in ZBRENT"
00094
00095 def single_well_numerov_match(V0, dx, parity=1, overlapsize=5):
00096 "generate a candidate eigenfunction with given potential, assuming the potential well has a single allowed region bounded on both sides"
00097 assert len(V0) & 1, "Must have a grid with an odd number of points"
00098 vminchan=Numeric.argmin(V0)
00099 right_turn=Numeric.searchsorted(V0[vminchan:], 0.0)+vminchan
00100
00101
00102 leftpsi=bare_numerov(V0[:right_turn], dx)
00103
00104 rightpsi= Numeric.array(bare_numerov(V0[right_turn-overlapsize:][::-1], dx)[::-1])
00105
00106
00107 slopeweight=Numeric.array(range(overlapsize),Numeric.Float)-overlapsize/2.0
00108 leftend=leftpsi[-overlapsize:]
00109 rightend=rightpsi[:overlapsize]
00110
00111 scale=abs(Numeric.sum(leftend*rightend)/Numeric.sum(leftend**2))
00112
00113 rightpsi*=float(parity)/scale
00114 error=Numeric.sum((leftend-rightend)*slopeweight)
00115 psi0=Numeric.concatenate((leftpsi, rightpsi[overlapsize:]))
00116
00117 psi2=psi0*psi0
00118 integral=psi2[0]+psi2[-1]+4.0*Numeric.sum(psi2[1:-1:2])+2.0*Numeric.sum(psi2[2:-2:2])
00119 psimag=1.0/math.sqrt(integral*dx/3.0)
00120
00121 return psi0*psimag, error*psimag
00122
00123 def single_well_numerov_solve(V0, dx, parity, emin, emax, overlapsize=5):
00124 "generate an eigenfunction with given potential with emin<(2m/hbar**2)E<emax, assuming the potential well has a single allowed region bounded on both sides"
00125 e=zbrent(lambda x: single_well_numerov_match(V0-x, dx, parity, overlapsize=overlapsize)[1], emin, emax, 1e-15, trace=0)
00126 psi, slope_error=single_well_numerov_match(V0-e, dx, parity, overlapsize)
00127 return e, psi
00128
00129 def find_spectrum(V0, dx, levels, ground_state_estimate, state_separation_estimate=None, trace=0, subdivide=4):
00130 results=[]
00131 if state_separation_estimate is None:
00132 state_separation_estimate=ground_state_estimate
00133
00134 e0, e1= ground_state_estimate-2*state_separation_estimate, ground_state_estimate-state_separation_estimate
00135 for i in range(levels):
00136 if trace: print '***', i, e0, e1
00137 delta_e=(e1-e0)/subdivide
00138 if i & 1:
00139 parity=-1
00140 else:
00141 parity=1
00142 try:
00143 firstguess=e1+(e1-e0)*0.5
00144 psi, slope=single_well_numerov_match(V0-firstguess, dx, parity)
00145 for s in range(1,10*subdivide):
00146
00147 e_test=firstguess+delta_e
00148 psi, test_slope=single_well_numerov_match(V0-e_test, dx, parity)
00149 if trace:
00150 print "%4d %4d %8.2e %8.2e %8.2e %8.2e" % (i, s, firstguess, slope, e_test, test_slope)
00151 if test_slope==0.0 or test_slope*slope < 0.0:
00152 break
00153 if s==(10*subdivide-1):
00154 assert 0,"No root found"
00155 firstguess=e_test
00156 slope=test_slope
00157 e_n, psi_n=single_well_numerov_solve(V0, dx, parity, e1+(s-1)*delta_e, e_test)
00158 results.append((i,e_n, psi_n))
00159 e0, e1=e1, e_n
00160 except:
00161 print sys.exc_value
00162 break
00163 return results
00164
00165
00166 if __name__=='__main__':
00167 potlen=101
00168 xmax=7.0
00169 dx=2.0*xmax/(potlen-1)
00170 xar=(Numeric.array(range(potlen),Numeric.Float)-(potlen-1)/2.0)*(xmax*2.0/(potlen-1))
00171 testpot=xar**2
00172
00173 results=find_spectrum(testpot, dx, 10, 1., trace=0)
00174 for n, e_n, psi_n in results:
00175 print n, e_n