summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugeniy Mikhailov <evgmik@gmail.com>2013-03-14 13:26:58 -0400
committerEugeniy Mikhailov <evgmik@gmail.com>2013-03-14 13:26:58 -0400
commit13103836b6fd82c47f493860105a0693de5d59f8 (patch)
tree414a1d1dff5cfe683fd4ffa1032cd2cad4506f23
parenta03d3af7881044685e6f1daa8981df3b190517e8 (diff)
downloadwgmr-13103836b6fd82c47f493860105a0693de5d59f8.tar.gz
wgmr-13103836b6fd82c47f493860105a0693de5d59f8.zip
Corrections from Dmitry Strekalov.
Fixed angular and radial overlap calculations.
-rw-r--r--phasematching/WGM_lib.py824
1 files changed, 444 insertions, 380 deletions
diff --git a/phasematching/WGM_lib.py b/phasematching/WGM_lib.py
index 3e19fee..ee03b3d 100644
--- a/phasematching/WGM_lib.py
+++ b/phasematching/WGM_lib.py
@@ -1,380 +1,444 @@
-#!/usr/bin/env mpython_q
-def ntest(lda,T,pol=0,MgO=0,MgO_th=5.0,E = 0): return 1.5
-
-def nLNO1(lda,T,pol,MgO=0,MgO_th=5.0,E = 0):
- """
- This is Sellmeyer eq. for Lithium Niobate from "Influence of the defect structure on the refractive indices
- of undoped and Mg-doped lithium niobate", U.Schlarb and K.Betzler, Phys. Rev. B 50, 751 (1994).
- Temperature in Centigrades, wavelength in microns. pol=0 "ordinary", pol=1 "extraordinary" MgO
- =0 MgO concentration in % MgO_th the threshold concentration. External electric field E in V/cm
- """
- import math
- if pol: # extraordinary
- w0 = 218.203
- mu = 6.4047E-6
- A0 = 3.9466E-5
- A_NbLi = 23.727E-8
- A_Mg = 7.6243E-8
- A_IR = 3.0998E-8
- A_UV = 2.6613
- rr = 33E-10 # r_33 cm/V
- else: # ordinary
- w0 = 223.219
- mu = 1.1082E-6
- A0 = 4.5312E-5
- A_NbLi = -1.4464E-8
- A_Mg = -7.3548E-8
- A_IR = 3.6340E-8
- A_UV = 2.6613
- rr = 11E-10 # r_13 cm/V
- B = A0+A_Mg*MgO
- if MgO < MgO_th: B+=(MgO_th-MgO)*A_NbLi
- def f(T):
- return (T + 273)**2 + 4.0238E5*(1/math.tanh(261.6/(T + 273)) - 1)
- def n(lda,T):
- return math.sqrt(B/((w0+(f(T)-f(24.5))*mu)**(-2)-lda**(-2))-A_IR*lda**2+A_UV)
- return n(lda,T)+0.5*rr*E*n(lda,T)**3
-
-def nBBO(lda,T,pol=0,MgO=0,MgO_th=5.0,E=0):
- """
- This is Sellmeyer eq. for BBO from http://refractiveindex.info/
- (Handbook of Optics, 3rd edition, Vol. 4. McGraw-Hill 2009);
- T is the angle (in degrees) between the optical axis and polarization:
- T = 0 extraordinary, T = 90 ordinary.
- """
- import math
- a = 2.7405,2.3730 # ordinary, extraordinary
- b = 0.0184,0.0128
- c = 0.0179,0.0156
- d = 0.0155,0.0044
- lda = lda/1000.0 # nm -> microns
- no = math.sqrt(a[0]-d[0]*lda*lda+b[0]/(lda*lda-c[0]))
- ne = math.sqrt(a[1]-d[1]*lda*lda+b[1]/(lda*lda-c[1]))
- o = math.sin(math.pi*T/180)/no
- e = math.cos(math.pi*T/180)/ne
- return (o*o+e*e)**(-0.5)
-
-def nBBO1(lda,T,pol=0,MgO=0,MgO_th=5.0,E=0):
- """
- This is Sellmeyer eq. for BBO from Optics Communications 184 (2000) 485-491;
- T is the angle (in degrees) between the optical axis and polarization:
- T = 0 extraordinary, T = 90 ordinary.
- """
- import math
- a = 2.7359,2.3753 # ordinary, extraordinary
- b = 0.01878,0.01224
- c = 0.01822,0.01667
- d = 0.01471,0.01627
- x = 0.0006081,0.0005716
- y = 0.00006740,0.00006305
- lda = lda/1000.0 # nm -> microns
- no = math.sqrt(a[0]-d[0]*lda*lda+b[0]/(lda*lda-c[0])+x[0]*lda**4-y[0]*lda**6)
- ne = math.sqrt(a[1]-d[1]*lda*lda+b[1]/(lda*lda-c[1])+x[1]*lda**4-y[1]*lda**6)
- o = math.sin(math.pi*T/180)/no
- e = math.cos(math.pi*T/180)/ne
- return (o*o+e*e)**(-0.5)
-
-def FSR_simple(R,lda,T,pol,L,q = 1,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
- import math
- #from WGM_lib import nLNO1
- from scipy.special import ai_zeros
- q -= 1
- AiRoots = -ai_zeros(40)[0]
- a = 29.9792/(2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E))
- b = 1+0.5*AiRoots[q]*((L/2.0+0.5)**(1.0/3.0)-(L/2.0-0.5)**(1.0/3.0))
- return a*b
-
-def Get_frac_L(R,r,lda,T,pol,q = 1,p = 0,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
- """
- This iteratively solves the WGM dispersion equation for a spheroid at the target wavelength lda
- and returs the fractional orbital number L
- pol=0 "ordinary", pol=1 "extraordinary" MgO =0 MgO concentration in %
- MgO_th the threshold concentration. External electric field E in V/cm
- R, r are big and small radia in mm
- """
- import math
- #from WGM_lib import nLNO1
- from scipy.special import ai_zeros
- q -= 1
- r = math.sqrt(r*R) # redefine the rim radius to spheroid semi-axis
- AiRoots = -ai_zeros(40)[0]
- freq_term = 2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E)*1E7/lda
- geom_term = (2*p*(R-r)+R)/2.0/r
- pol_term = n(lda,T,pol,MgO,MgO_th,E)**(1-2*pol)/math.sqrt(n(lda,T,pol,MgO,MgO_th,E)**2-1)
- L = freq_term
- dL = 1
- while math.fabs(dL) >= 1E-9:
- L1 = freq_term - AiRoots[q]*(L/2.0)**(1.0/3.0) - geom_term + pol_term\
- - 3*AiRoots[q]**2*(L/2.0)**(-1.0/3.0)/20
- dL = L1-L
- L = L1
- return L
-
-def WGM_freq(R,r,L,T,pol,q = 1,p = 0,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
- """
- This iteratively solves the WGM dispersion equation for a spheroid at the target
- orbital number L and returs the wavelength (nm) and frequency (GHz)
- pol=0 "ordinary", pol=1 "extraordinary" MgO =0 MgO concentration in %
- MgO_th the threshold concentration. External electric field E in V/cm
- R, r are big and small radia in mm
- """
- import math
- #from WGM_lib import nLNO1
- from scipy.special import ai_zeros
- q -= 1
- r = math.sqrt(r*R) # redefine the rim radius to spheroid semi-axis
- AiRoots = -ai_zeros(40)[0] # >0
- geom_term = (2*p*(R-r)+R)/2.0/r
- def nm2GHz(lda): return 2.99792E8/lda
- def wl1(lda):
- freq_term = 2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E)*1E7
- pol_term = n(lda,T,pol,MgO,MgO_th,E)**(1-2*pol)/math.sqrt(n(lda,T,pol,MgO,MgO_th,E)**2-1)
- return freq_term/(L+AiRoots[q]*(L/2.0)**(1.0/3.0)\
- +geom_term-pol_term+3*AiRoots[q]**2*(L/2.0)**(-1.0/3.0)/20)
- lda0 = 2*math.pi*R*n(1000,T,pol,MgO,MgO_th,E)*1E7/L # initial wavelength guess based on n(1000 nm)
- lda = wl1(lda0)
- #print lda0, lda
- df = 1
- while math.fabs(df) > 1E-6: #1 kHz accuracy
- lda1 = wl1(lda)
- df = nm2GHz(lda1)-nm2GHz(lda)
- lda = lda1
- #print df,lda1,n(lda,T,pol,MgO,MgO_th,E)
- return lda, nm2GHz(lda)
-
-def Gaunt(L1,m1,L2,m2,L3,m3):
- """
- Gaunt formula for Legendre polynomials overlap. Large factorials are treated in Stirling's approximation.
- """
- import math
- def fa(n):
- if n<=10: fac = float(math.factorial(n))
- else: fac = math.sqrt(2*math.pi*n)*(n/math.e)**n
- return fac
- params = [(L1,m1),(L2,m2),(L3,m3)]
- params = sorted(params,key=lambda list: list[1])
- l,u = params[-1]
- prms = params[:-1]
- prms = sorted(prms,key=lambda list: list[0])
- n,w = prms[0]
- m,v = prms[1]
- if not v+w == u: return 0
- else:
- s = (l+m+n)/2.0
- if not s-round(s) == 0 : return 0
- elif m+n <l : return 0
- elif m-n >l : return 0
- else:
- p = int(max(0, n-m-u))
- q = int(min(n+m-u,l-u,n-w))
- a = (-1)**(s-m-w)*math.sqrt((2*l+1)*(2*m+1)*(2*n+1)*fa(l-u)*fa(n-w)/2.0/fa(m-v))/4/math.pi/math.pi
- b = a*math.sqrt(math.sqrt(2*math.pi*(m+v)*(n+w)/(l+u))/(s-m)/(s-n))/2.0/math.pi
- c = b*math.exp(0.5*(m+n-l+u-v-w))*(s-l)**(l-s)
- d = c*((n+w)/float(l+n-m))**(s-m)*((n+w)/2.0)**((w+m-l)/2.0)
- t0 = d*2**((w-n)/2.0)*((m+v)/float(l+m-n))**((l+m-n)/2.0)*(m+v)**((v-l+n)/2.0)*(l+m-n)**(l-u)
- sum = 0
- for t in range (p,q+1):
- f1 = (-1)**t*fa(m+n-u-t)/fa(t)/fa(l-u-t)/fa(n-w-t)
- f2 = f1*((l+u+t)/float(m-n+u+t))**(0.5+t)
- f3 = f2*((l+u+t)/float(l+u))**(0.5*l+0.5*u)
- f4 = f3*((l+u+t)/float(l+m+n))**((l+m+n)/2.0)*(l+u+t)**((u-m-n)/2.0)
- sum+= f4*((l+m-n)/float(u+m-n+t))**(u+m-n)
- return t0*sum
-
-def Gaunt_low(L1,m1,L2,m2,L3,m3):
- """
- Gaunt formula for Legendre polynomials overlap computed exactly. Good only for low orders.
- """
- import math
- def fa(n):
- if n<=10: fac = float(math.factorial(n))
- else: fac = math.sqrt(2*math.pi*n)*(n/math.e)**n
- return fac
- def fa_ratio(n1, n2): #calculates n1!/n2!
- p = 1.0
- for j in range (min(n1, n2)+1, max(n1, n2)+1):
- p = p*j
- if n1 >= n2: return p
- else: return 1/p
- params = [(L1,m1),(L2,m2),(L3,m3)]
- params = sorted(params,key=lambda list: list[1])
- l,u = params[-1]
- prms = params[:-1]
- prms = sorted(prms,key=lambda list: list[0])
- n,w = prms[0]
- m,v = prms[1]
- if not v+w == u: return 0
- else:
- s = (l+m+n)/2.0
- if not s-round(s) == 0 : return 0
- elif m+n <l : return 0
- elif m-n >l : return 0
- else:
- s = int(s)
- p = int(max(0, n-m-u))
- q = int(min(n+m-u,l-u,n-w))
- #print l,m,n,s
- #print fa(l-u),fa(n-w), fa(m-v),fa(s-l)
- a1 = (-1)**(s-m-w)*math.sqrt((2*l+1)*(2*m+1)*(2*n+1)*fa(l-u)*fa(n-w)/fa(m-v)/math.pi)/fa(s-l)/float(2*s+1)/4.0
- a2 = math.sqrt(fa(m+v)*fa(n+w)/fa(l+u))*fa(s)/fa(s-m)/fa(s-n)
- sum = 0
- for t in range (p,q+1):
- b1 = (-1)**t*fa(m+n-u-t)/fa(t)/fa(l-u-t)/fa(n-w-t)
- b2 = fa_ratio(l+u+t,2*s)*fa_ratio(2*s-2*n,m-n+u+t)
- sum+= b1*b2
- #print a1, a2, sum
- return a1*a2*sum
-
-def RadOvlp(R,L1,q1,L2,q2,L3,q3):
- """
- Calculates radial overlap of three Airy functions
- """
- import math
- from scipy.special import airy, ai_zeros
- from scipy.integrate import quad
- AiRoots = -ai_zeros(40)[0]
- q1, q2, q3 = q1-1, q2-1, q3-1
- def nk(L,q): return L*(1+AiRoots[q]*(2*L**2)**(-1.0/3.0)+0.5/L)
- def AiArg(x,L,q): return -AiRoots[q]*(L+0.5-nk(L,q)*x)/(L+0.5-nk(L,q))
- def RadFunc(x,L,q): return airy(AiArg(x,L,q))[0]/math.sqrt(x)
- L0 = min(L1,L2,L3)
- q0 = max(q1,q2,q3)
- Rmin = (L0-3*(L0/2.0)**(1.0/3.0))/nk(L0,q0)
- N1 = R**3*quad(lambda x: RadFunc(x,L1,q1)**2, Rmin, 1)[0]
- N2 = R**3*quad(lambda x: RadFunc(x,L2,q2)**2, Rmin, 1)[0]
- N3 = R**3*quad(lambda x: RadFunc(x,L3,q3)**2, Rmin, 1)[0]
- def Core(x): return RadFunc(x,L1,q1)*RadFunc(x,L2,q2)*RadFunc(x,L3,q3)*x*x
- return R**3*quad(lambda x: Core(x), Rmin, 1)[0]/math.sqrt(N1*N2*N3)
-
-def AngOvlp(L1,p1,L2,p2,L3,p3,limit=0.05):
- """
- Calculates angular overlap of three spherical functions. Default limit is sufficient for up to p = 10
- """
- import math
- from scipy.special import hermite
- from scipy.integrate import quad
- def HG(p,L,x): return (2**L*math.factorial(p))**-0.5*hermite(p)(x*math.sqrt(L))*(L/math.pi)**0.25*math.exp(-L*x**2/2.0)
- def HGnorm(p,L,x): return HG(p,L,x)*(4*math.pi*quad(lambda x: HG(p,L,x)**2, 0, limit*(14000.0/L)**0.5)[0])**(-0.5)
- return 4*math.pi*quad(lambda x: HGnorm(p1,L1,x)*HGnorm(p2,L2,x)*HGnorm(p3,L3,x), 0, limit*(14000.0/min(L1,L2,L3))**0.5)[0]
-
-def T_phasematch(L1,p1,q1,L2,p2,q2,L3,p3,q3,R,r,T0=70,MgO=0,MgO_th=5.0,E = 0,Tstep=0.1, n = nLNO1):
- '''
- Finds 1/wl2 + 1/wl2 = 1/wl3, L1+L2 -> L3 phase matching temperature down to the accuracy deltaf (GHz)
- based on the initial guess T0 with initial search step Tstep
- Requires importing Sellmeyer equations. This program is unaware of the orbital selection rules!
- '''
- import math
- from WGM_lib import WGM_freq
- def Df(T): # freqency detuning in GHz
- lda_1, freq_1 = WGM_freq(R,r,L1,T,0,q1,p1,MgO,MgO_th,E,n)
- lda_2, freq_2 = WGM_freq(R,r,L2,T,0,q2,p2,MgO,MgO_th,E,n)
- lda_3, freq_3 = WGM_freq(R,r,L3,T,1,q3,p3,MgO,MgO_th,E,n)
- return freq_3-freq_1-freq_2
- T = T0
- df = Df(T)
- while math.fabs(df)>1E-6: #1 kHz accuracy
- #print "T = ",T," df = ", df,
- df1 = Df(T+Tstep)
- ddfdt = (df1-df)/Tstep
- dT = df/ddfdt
- T -= dT
- Tstep = math.fabs(dT/2)
- df = Df(T)
- #print " dT = ", dT, " T -> ",T, " df -> ", df
- if T<-200 or T> 500: break
- return T
-
-def T_phasematch_blk(wl1,wl2, T0=70,MgO=0,MgO_th=5.0,E = 0,Tstep=0.1, n = nLNO1):
- '''
- Finds 1/wl2 + 1/wl2 = 1/wl3 (wl in nanometers), collinear bulk phase matching temperature
- based on the initial guess T0 with initial search step Tstep
- Requires importing Sellmeyer equations.
- '''
- import math
- wl3 = 1/(1.0/wl1+1.0/wl2)
- def Dk(T):
- k1 = 2*math.pi*n(wl1,T,0,MgO,MgO_th,E)*1e7/wl1 # cm^-1
- k2 = 2*math.pi*n(wl2,T,0,MgO,MgO_th,E)*1e7/wl2
- k3 = 2*math.pi*n(wl3,T,1,MgO,MgO_th,E)*1e7/wl3
- return k3-k2-k1
- T = T0
- dk = Dk(T)
- while math.fabs(dk)>0.1: #10 cm beat length
- dk1 = Dk(T+Tstep)
- ddkdt = (dk1-dk)/Tstep
- dT = dk/ddkdt
- T -= dT
- Tstep = math.fabs(dT/2)
- dk = Dk(T)
- #print " dT = ", dT, " T -> ",T, " dk -> ", dk
- if T<-200 or T> 500:
- print "No phase matching!"
- break
- return T
-
-def SH_phasematch_blk(angle, wl0=1000,step=1.01, n = nBBO):
- '''
- Finds the fundamental wavelength (in nm) for frequency doubling o+o->e at a given angle between the optical axis and the e.
- Requires importing Sellmeyer equations.
- '''
- import math
- def Dk(wl):
- k1 = 2*math.pi*n(wl,90)*1e7/wl # ordinary cm^-1
- k2 = 4*math.pi*n(wl/2.0,angle)*1e7/wl # extraordinary with angle
- return k2-2*k1
- wl = wl0
- dk = Dk(wl)
- while math.fabs(dk)>0.1: #10 cm beat length
- dk1 = Dk(wl*step)
- ddkdt = (dk1-dk)/step
- dwl = dk/ddkdt
- wl = wl/dwl
- #step = min(step,math.fabs(dwl/2) )
- dk = Dk(wl)
- print " dwl = ", dwl, " wl -> ",wl, " dk -> ", dk
- return wl
-
-
-def Phasematch(wlP,p1,q1,p2,q2,p3,q3,R,r,T,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
- '''
-
- NOT FULLY FUNCTIONAL!!
-
- Finds wl1 and wl2 such that 1/wl2 + 1/wl2 = 1/wlP and L1-p1+L2-p2 = L3-p3 at a given temperature T.
- Requires importing Sellmeyer equations. R is evaluated at T. This program is unaware of the other orbital
- selection rules! L1, L2, L3 are allowed to be non-integer.
- '''
- import math
- from WGM_lib import Get_frac_L
- def Dm(wl): # m3-m1-m2, orbital detuning
- L3 = Get_frac_L(R,r,wlP,T,1,q3,p3,MgO,MgO_th)
- L1 = Get_frac_L(R,r,wl,T,0,q1,p1,MgO,MgO_th)
- wl2 = 1.0/(1.0/wlP-1.0/wl)
- L2 = Get_frac_L(R,r,wl2,T,0,q2,p2,MgO,MgO_th)
- return L2-L3+p3+L1-p1-p2
- wl = 2*wlP #try at degeneracy
- dm = Dm(wl)
- print "dm = ", dm
- ddm = 0
- wlstep = 1.0 # chosing an appropriate wavelength step so the m variation is not too large or too small
- while math.fabs(ddm)<1:
- wlstep = 2*wlstep
- ddm = Dm(wl+wlstep)-dm
- print "wlstep = ", wlstep, " initial slope = ", ddm/wlstep
- while math.fabs(dm)>1E-6: # equiv *FSR accuracy
- dm1 = Dm(wl+wlstep)
- print "dm1 = ", dm1
- slope = (dm1-dm)/wlstep
- print "slope = ", slope, "nm^-1"
- dwl = dm/slope
- wl -= dwl
- print "new wl = ", wl
- wlstep = min(math.fabs(dwl/2), wlstep)
- dm = Dm(wl)
- print " d wl = ", dwl, " wl -> ",wl, " dm -> ", dm
- if math.fabs(dm)> Get_frac_L(R,r,2*wlP,T,0,q1,p1,MgO,MgO_th): break
- L1 = Get_frac_L(R,r,wl,T,0,q1,p1,MgO,MgO_th)
- wl2 = 1.0/(1.0/wlP-1.0/wl)
- L2 = Get_frac_L(R,r,wl2,T,0,q2,p2,MgO,MgO_th)
- return L1, L2, wl, wl2
-
+#!/usr/bin/env mpython_q
+def ntest(lda,T,pol=0,MgO=0,MgO_th=5.0,E = 0): return 1.5
+
+def nLNO1(lda,T,pol,MgO=0,MgO_th=5.0,E = 0):
+ """
+ This is Sellmeyer eq. for Lithium Niobate from "Influence of the defect structure on the refractive indices
+ of undoped and Mg-doped lithium niobate", U.Schlarb and K.Betzler, Phys. Rev. B 50, 751 (1994).
+ Temperature in Centigrades, wavelength in microns. pol=0 "ordinary", pol=1 "extraordinary" MgO
+ =0 MgO concentration in % MgO_th the threshold concentration. External electric field E in V/cm
+ """
+ import math
+ if pol: # extraordinary
+ w0 = 218.203
+ mu = 6.4047E-6
+ A0 = 3.9466E-5
+ A_NbLi = 23.727E-8
+ A_Mg = 7.6243E-8
+ A_IR = 3.0998E-8
+ A_UV = 2.6613
+ rr = 33E-10 # r_33 cm/V
+ else: # ordinary
+ w0 = 223.219
+ mu = 1.1082E-6
+ A0 = 4.5312E-5
+ A_NbLi = -1.4464E-8
+ A_Mg = -7.3548E-8
+ A_IR = 3.6340E-8
+ A_UV = 2.6613
+ rr = 11E-10 # r_13 cm/V
+ B = A0+A_Mg*MgO
+ if MgO < MgO_th: B+=(MgO_th-MgO)*A_NbLi
+ def f(T):
+ return (T + 273)**2 + 4.0238E5*(1/math.tanh(261.6/(T + 273)) - 1)
+ def n(lda,T):
+ return math.sqrt(B/((w0+(f(T)-f(24.5))*mu)**(-2)-lda**(-2))-A_IR*lda**2+A_UV)
+ return n(lda,T)+0.5*rr*E*n(lda,T)**3
+
+def nLNO1_disp(lda,T,pol,MgO=0,MgO_th=5.0):
+ """
+ dispersion dn/dlda (in nm) for Lithium Niobate dispersion nLNO1
+ """
+ import math
+ if pol: # extraordinary
+ w0 = 218.203
+ mu = 6.4047E-6
+ A0 = 3.9466E-5
+ A_NbLi = 23.727E-8
+ A_Mg = 7.6243E-8
+ A_IR = 3.0998E-8
+ A_UV = 2.6613
+ else: # ordinary
+ w0 = 223.219
+ mu = 1.1082E-6
+ A0 = 4.5312E-5
+ A_NbLi = -1.4464E-8
+ A_Mg = -7.3548E-8
+ A_IR = 3.6340E-8
+ A_UV = 2.6613
+ B = A0+A_Mg*MgO
+ if MgO < MgO_th: B+=(MgO_th-MgO)*A_NbLi
+ def f(T):
+ return (T + 273)**2 + 4.0238E5*(1/math.tanh(261.6/(T + 273)) - 1)
+ def n(lda,T):
+ return math.sqrt(B/((w0+(f(T)-f(24.5))*mu)**(-2)-lda**(-2))-A_IR*lda**2+A_UV)
+ return -(B*lda**(-3)*((w0+(f(T)-f(24.5))*mu)**(-2)-lda**(-2))**(-2)+A_IR*lda)/n(lda,T)
+
+def nBBO(lda,T,pol=0,MgO=0,MgO_th=5.0,E=0):
+ """
+ This is Sellmeyer eq. for BBO from http://refractiveindex.info/
+ (Handbook of Optics, 3rd edition, Vol. 4. McGraw-Hill 2009);
+ T is the angle (in degrees) between the optical axis and polarization:
+ T = 0 extraordinary, T = 90 ordinary.
+ """
+ import math
+ a = 2.7405,2.3730 # ordinary, extraordinary
+ b = 0.0184,0.0128
+ c = 0.0179,0.0156
+ d = 0.0155,0.0044
+ lda = lda/1000.0 # nm -> microns
+ no = math.sqrt(a[0]-d[0]*lda*lda+b[0]/(lda*lda-c[0]))
+ ne = math.sqrt(a[1]-d[1]*lda*lda+b[1]/(lda*lda-c[1]))
+ o = math.sin(math.pi*T/180)/no
+ e = math.cos(math.pi*T/180)/ne
+ return (o*o+e*e)**(-0.5)
+
+def nBBO1(lda,T,pol=0,MgO=0,MgO_th=5.0,E=0):
+ """
+ This is Sellmeyer eq. for BBO from Optics Communications 184 (2000) 485-491;
+ T is the angle (in degrees) between the optical axis and polarization:
+ T = 0 extraordinary, T = 90 ordinary.
+ """
+ import math
+ a = 2.7359,2.3753 # ordinary, extraordinary
+ b = 0.01878,0.01224
+ c = 0.01822,0.01667
+ d = 0.01471,0.01627
+ x = 0.0006081,0.0005716
+ y = 0.00006740,0.00006305
+ lda = lda/1000.0 # nm -> microns
+ no = math.sqrt(a[0]-d[0]*lda*lda+b[0]/(lda*lda-c[0])+x[0]*lda**4-y[0]*lda**6)
+ ne = math.sqrt(a[1]-d[1]*lda*lda+b[1]/(lda*lda-c[1])+x[1]*lda**4-y[1]*lda**6)
+ o = math.sin(math.pi*T/180)/no
+ e = math.cos(math.pi*T/180)/ne
+ return (o*o+e*e)**(-0.5)
+
+def FSR_simple(R,lda,T,pol,L,q = 1,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
+ import math
+ #from WGM_lib import nLNO1
+ from scipy.special import ai_zeros
+ q -= 1
+ AiRoots = -ai_zeros(40)[0]
+ a = 29.9792/(2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E))
+ b = 1+0.5*AiRoots[q]*((L/2.0+0.5)**(1.0/3.0)-(L/2.0-0.5)**(1.0/3.0))
+ return a*b
+
+def Get_frac_L(R,r,lda,T,pol,q = 1,p = 0,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
+ """
+ This iteratively solves the WGM dispersion equation for a spheroid at the target wavelength lda
+ and returs the fractional orbital number L
+ pol=0 "ordinary", pol=1 "extraordinary" MgO =0 MgO concentration in %
+ MgO_th the threshold concentration. External electric field E in V/cm
+ R, r are big and small radia in mm
+ """
+ import math
+ #from WGM_lib import nLNO1
+ from scipy.special import ai_zeros
+ q -= 1
+ r = math.sqrt(r*R) # redefine the rim radius to spheroid semi-axis
+ AiRoots = -ai_zeros(40)[0]
+ freq_term = 2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E)*1E7/lda
+ geom_term = (2*p*(R-r)+R)/2.0/r
+ pol_term = n(lda,T,pol,MgO,MgO_th,E)**(1-2*pol)/math.sqrt(n(lda,T,pol,MgO,MgO_th,E)**2-1)
+ L = freq_term
+ dL = 1
+ while math.fabs(dL) >= 1E-9:
+ L1 = freq_term - AiRoots[q]*(L/2.0)**(1.0/3.0) - geom_term + pol_term\
+ - 3*AiRoots[q]**2*(L/2.0)**(-1.0/3.0)/20
+ dL = L1-L
+ L = L1
+ return L
+
+def WGM_freq(R,r,L,T,pol,q = 1,p = 0,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
+ """
+ This iteratively solves the WGM dispersion equation for a spheroid at the target
+ orbital number L and returs the wavelength (nm) and frequency (GHz)
+ pol=0 "ordinary", pol=1 "extraordinary" MgO =0 MgO concentration in %
+ MgO_th the threshold concentration. External electric field E in V/cm
+ R, r are big and small radia in mm
+ """
+ import math
+ #from WGM_lib import nLNO1
+ from scipy.special import ai_zeros
+ q -= 1
+ r = math.sqrt(r*R) # redefine the rim radius to spheroid semi-axis
+ AiRoots = -ai_zeros(40)[0] # >0
+ geom_term = (2*p*(R-r)+R)/2.0/r
+ def nm2GHz(lda): return 2.99792E8/lda
+ def wl1(lda):
+ freq_term = 2*math.pi*R*n(lda,T,pol,MgO,MgO_th,E)*1E7
+ pol_term = n(lda,T,pol,MgO,MgO_th,E)**(1-2*pol)/math.sqrt(n(lda,T,pol,MgO,MgO_th,E)**2-1)
+ return freq_term/(L+AiRoots[q]*(L/2.0)**(1.0/3.0)\
+ +geom_term-pol_term+3*AiRoots[q]**2*(L/2.0)**(-1.0/3.0)/20)
+ lda0 = 2*math.pi*R*n(1000,T,pol,MgO,MgO_th,E)*1E7/L # initial wavelength guess based on n(1000 nm)
+ lda = wl1(lda0)
+ #print lda0, lda
+ df = 1
+ while math.fabs(df) > 1E-6: #1 kHz accuracy
+ lda1 = wl1(lda)
+ df = nm2GHz(lda1)-nm2GHz(lda)
+ lda = lda1
+ #print df,lda1,n(lda,T,pol,MgO,MgO_th,E)
+ return lda, nm2GHz(lda)
+
+def Gaunt(L1,m1,L2,m2,L3,m3):
+ """
+ Gaunt formula for Legendre polynomials overlap. Large factorials are treated in Stirling's approximation.
+ """
+ import math
+ def fa(n):
+ if n<=10: fac = float(math.factorial(n))
+ else: fac = math.sqrt(2*math.pi*n)*(n/math.e)**n
+ return fac
+ params = [(L1,m1),(L2,m2),(L3,m3)]
+ params = sorted(params,key=lambda list: list[1])
+ l,u = params[-1]
+ prms = params[:-1]
+ prms = sorted(prms,key=lambda list: list[0])
+ n,w = prms[0]
+ m,v = prms[1]
+ if not v+w == u: return 0
+ else:
+ s = (l+m+n)/2.0
+ if not s-round(s) == 0 : return 0
+ elif m+n <l : return 0
+ elif m-n >l : return 0
+ else:
+ p = int(max(0, n-m-u))
+ q = int(min(n+m-u,l-u,n-w))
+ a = (-1)**(s-m-w)*math.sqrt((2*l+1)*(2*m+1)*(2*n+1)*fa(l-u)*fa(n-w)/2.0/fa(m-v))/4/math.pi/math.pi
+ b = a*math.sqrt(math.sqrt(2*math.pi*(m+v)*(n+w)/(l+u))/(s-m)/(s-n))/2.0/math.pi
+ c = b*math.exp(0.5*(m+n-l+u-v-w))*(s-l)**(l-s)
+ d = c*((n+w)/float(l+n-m))**(s-m)*((n+w)/2.0)**((w+m-l)/2.0)
+ t0 = d*2**((w-n)/2.0)*((m+v)/float(l+m-n))**((l+m-n)/2.0)*(m+v)**((v-l+n)/2.0)*(l+m-n)**(l-u)
+ sum = 0
+ for t in range (p,q+1):
+ f1 = (-1)**t*fa(m+n-u-t)/fa(t)/fa(l-u-t)/fa(n-w-t)
+ f2 = f1*((l+u+t)/float(m-n+u+t))**(0.5+t)
+ f3 = f2*((l+u+t)/float(l+u))**(0.5*l+0.5*u)
+ f4 = f3*((l+u+t)/float(l+m+n))**((l+m+n)/2.0)*(l+u+t)**((u-m-n)/2.0)
+ sum+= f4*((l+m-n)/float(u+m-n+t))**(u+m-n)
+ return t0*sum
+
+def Gaunt_low(L1,m1,L2,m2,L3,m3):
+ """
+ Gaunt formula for Legendre polynomials overlap computed exactly. Good only for low orders.
+ """
+ import math
+ def fa(n):
+ if n<=10: fac = float(math.factorial(n))
+ else: fac = math.sqrt(2*math.pi*n)*(n/math.e)**n
+ return fac
+ def fa_ratio(n1, n2): #calculates n1!/n2!
+ p = 1.0
+ for j in range (min(n1, n2)+1, max(n1, n2)+1):
+ p = p*j
+ if n1 >= n2: return p
+ else: return 1/p
+ params = [(L1,m1),(L2,m2),(L3,m3)]
+ params = sorted(params,key=lambda list: list[1])
+ l,u = params[-1]
+ prms = params[:-1]
+ prms = sorted(prms,key=lambda list: list[0])
+ n,w = prms[0]
+ m,v = prms[1]
+ if not v+w == u: return 0
+ else:
+ s = (l+m+n)/2.0
+ if not s-round(s) == 0 : return 0
+ elif m+n <l : return 0
+ elif m-n >l : return 0
+ else:
+ s = int(s)
+ p = int(max(0, n-m-u))
+ q = int(min(n+m-u,l-u,n-w))
+ #print l,m,n,s
+ #print fa(l-u),fa(n-w), fa(m-v),fa(s-l)
+ a1 = (-1)**(s-m-w)*math.sqrt((2*l+1)*(2*m+1)*(2*n+1)*fa(l-u)*fa(n-w)/fa(m-v)/math.pi)/fa(s-l)/float(2*s+1)/4.0
+ a2 = math.sqrt(fa(m+v)*fa(n+w)/fa(l+u))*fa(s)/fa(s-m)/fa(s-n)
+ sum = 0
+ for t in range (p,q+1):
+ b1 = (-1)**t*fa(m+n-u-t)/fa(t)/fa(l-u-t)/fa(n-w-t)
+ b2 = fa_ratio(l+u+t,2*s)*fa_ratio(2*s-2*n,m-n+u+t)
+ sum+= b1*b2
+ #print a1, a2, sum
+ return a1*a2*sum
+
+def RadOvlp(R,L1,q1,L2,q2,L3,q3):
+ """
+ Calculates radial overlap of three Airy functions
+ """
+ import math
+ from scipy.special import airy, ai_zeros
+ from scipy.integrate import quad
+ AiRoots = -ai_zeros(40)[0]
+ q1, q2, q3 = q1-1, q2-1, q3-1
+ def nk(L,q): return L*(1+AiRoots[q]*(2*L**2)**(-1.0/3.0))
+ def AiArg(x,L,q): return -AiRoots[q]*(L-nk(L+0.5,q)*x)/(L-nk(L+0.5,q))
+ def RadFunc(x,L,q): return airy(AiArg(x,L,q))[0]/math.sqrt(x)
+ L0 = min(L1,L2,L3)
+ q0 = max(q1,q2,q3)+1
+ Rmin = 1-5*math.sqrt(q0)*L0**(-2.0/3.0)
+ N1 = R**3*quad(lambda x: x*x*RadFunc(x,L1,q1)**2, Rmin, 1)[0]
+ N2 = R**3*quad(lambda x: x*x*RadFunc(x,L2,q2)**2, Rmin, 1)[0]
+ N3 = R**3*quad(lambda x: x*x*RadFunc(x,L3,q3)**2, Rmin, 1)[0]
+ #print N1, N2, N3
+ def Core(x): return RadFunc(x,L1,q1)*RadFunc(x,L2,q2)*RadFunc(x,L3,q3)*x*x
+ return R**3*quad(lambda x: Core(x), Rmin, 1)[0]/math.sqrt(N1*N2*N3)
+
+def AngOvlp(L1,p1,L2,p2,L3,p3,limit=3):
+ """
+ Calculates angular overlap of three spherical functions. Default limit is sufficient for up to p = 10
+ """
+ import math
+ from scipy.special import hermite
+ from scipy.integrate import quad
+ #def HG(p,L,x): return (2**L*math.factorial(p))**-0.5*hermite(p)(x*math.sqrt(L))*(L/math.pi)**0.25*math.exp(-L*x**2/2.0)
+ def HG(p,L,x): return hermite(p)(x*math.sqrt(L))*math.exp(-L*x**2/2.0) # removed a large factor that would drop out in normalization anyway
+ def HGnorm(p,L,x): return HG(p,L,x)*(4*math.pi*quad(lambda x: HG(p,L,x)**2, 0, limit*((p+1)*math.pi/L/2)**0.5)[0])**(-0.5)
+ return 2*quad(lambda x: HGnorm(p1,L1,x)*HGnorm(p2,L2,x)*HGnorm(p3,L3,x), 0, limit*((max(p1,p2,p3)+1)*math.pi/min(L1,L2,L3)/2)**0.5)[0]
+
+def T_phasematch(L1,p1,q1,L2,p2,q2,L3,p3,q3,R,r,T0=70,MgO=0,MgO_th=5.0,E = 0,Tstep=0.1, n = nLNO1):
+ '''
+ Finds 1/wl2 + 1/wl2 = 1/wl3, L1+L2 -> L3 phase matching temperature down to the accuracy deltaf (GHz)
+ based on the initial guess T0 with initial search step Tstep
+ Requires importing Sellmeyer equations. This program is unaware of the orbital selection rules!
+ '''
+ import math
+ from WGM_lib import WGM_freq
+ def Df(T): # freqency detuning in GHz
+ lda_1, freq_1 = WGM_freq(R,r,L1,T,0,q1,p1,MgO,MgO_th,E,n)
+ lda_2, freq_2 = WGM_freq(R,r,L2,T,0,q2,p2,MgO,MgO_th,E,n)
+ lda_3, freq_3 = WGM_freq(R,r,L3,T,1,q3,p3,MgO,MgO_th,E,n)
+ return freq_3-freq_1-freq_2
+ T = T0
+ df = Df(T)
+ while math.fabs(df)>1E-6: #1 kHz accuracy
+ #print "T = ",T," df = ", df,
+ df1 = Df(T+Tstep)
+ ddfdt = (df1-df)/Tstep
+ dT = df/ddfdt
+ T -= dT
+ Tstep = math.fabs(dT/2.0)
+ df = Df(T)
+ #print " dT = ", dT, " T -> ",T, " df -> ", df
+ if T<-200 or T> 500: break
+ return T
+
+def T_phasematch_blk(wl1,wl2, T0=70,MgO=0,MgO_th=5.0,E = 0,Tstep=0.1, n = nLNO1):
+ '''
+ Finds 1/wl2 + 1/wl2 = 1/wl3 (wl in nanometers), collinear bulk phase matching temperature
+ based on the initial guess T0 with initial search step Tstep
+ Requires importing Sellmeyer equations.
+ '''
+ import math
+ wl3 = 1/(1.0/wl1+1.0/wl2)
+ def Dk(T):
+ k1 = 2*math.pi*n(wl1,T,0,MgO,MgO_th,E)*1e7/wl1 # cm^-1
+ k2 = 2*math.pi*n(wl2,T,0,MgO,MgO_th,E)*1e7/wl2
+ k3 = 2*math.pi*n(wl3,T,1,MgO,MgO_th,E)*1e7/wl3
+ return k3-k2-k1
+ T = T0
+ dk = Dk(T)
+ while math.fabs(dk)>0.1: #10 cm beat length
+ dk1 = Dk(T+Tstep)
+ ddkdt = (dk1-dk)/Tstep
+ dT = dk/ddkdt
+ T -= dT
+ Tstep = math.fabs(dT/2)
+ dk = Dk(T)
+ #print " dT = ", dT, " T -> ",T, " dk -> ", dk
+ if T<-200 or T> 500:
+ print "No phase matching!"
+ break
+ return T
+
+def SH_phasematch_blk(angle, wl0=1000,step=1.01, n = nBBO):
+ '''
+ Finds the fundamental wavelength (in nm) for frequency doubling o+o->e at a given angle between the optical axis and the e.
+ Requires importing Sellmeyer equations.
+ '''
+ import math
+ def Dk(wl):
+ k1 = 2*math.pi*n(wl,90)*1e7/wl # ordinary cm^-1
+ k2 = 4*math.pi*n(wl/2.0,angle)*1e7/wl # extraordinary with angle
+ return k2-2*k1
+ wl = wl0
+ dk = Dk(wl)
+ while math.fabs(dk)>0.1: #10 cm beat length
+ dk1 = Dk(wl*step)
+ ddkdt = (dk1-dk)/step
+ dwl = dk/ddkdt
+ wl = wl/dwl
+ #step = min(step,math.fabs(dwl/2) )
+ dk = Dk(wl)
+ print " dwl = ", dwl, " wl -> ",wl, " dk -> ", dk
+ return wl
+
+
+def Phasematch(wlP,p1,q1,p2,q2,p3,q3,R,r,T,MgO=0,MgO_th=5.0,E = 0, n = nLNO1):
+ '''
+
+ NOT FULLY FUNCTIONAL!!
+
+ Finds wl1 and wl2 such that 1/wl2 + 1/wl2 = 1/wlP and L1-p1+L2-p2 = L3-p3 at a given temperature T.
+ Requires importing Sellmeyer equations. R is evaluated at T. This program is unaware of the other orbital
+ selection rules! L1, L2, L3 are allowed to be non-integer.
+ '''
+ import math
+ from WGM_lib import Get_frac_L
+ def Dm(wl): # m3-m1-m2, orbital detuning
+ L3 = Get_frac_L(R,r,wlP,T,1,q3,p3,MgO,MgO_th)
+ L1 = Get_frac_L(R,r,wl,T,0,q1,p1,MgO,MgO_th)
+ wl2 = 1.0/(1.0/wlP-1.0/wl)
+ L2 = Get_frac_L(R,r,wl2,T,0,q2,p2,MgO,MgO_th)
+ return L2-L3+p3+L1-p1-p2
+ wl = 2*wlP #try at degeneracy
+ dm = Dm(wl)
+ print "dm = ", dm
+ ddm = 0
+ wlstep = 1.0 # chosing an appropriate wavelength step so the m variation is not too large or too small
+ while math.fabs(ddm)<1:
+ wlstep = 2*wlstep
+ ddm = Dm(wl+wlstep)-dm
+ print "wlstep = ", wlstep, " initial slope = ", ddm/wlstep
+ while math.fabs(dm)>1E-6: # equiv *FSR accuracy
+ dm1 = Dm(wl+wlstep)
+ print "dm1 = ", dm1
+ slope = (dm1-dm)/wlstep
+ print "slope = ", slope, "nm^-1"
+ dwl = dm/slope
+ wl -= dwl
+ print "new wl = ", wl
+ wlstep = min(math.fabs(dwl/2), wlstep)
+ dm = Dm(wl)
+ print " d wl = ", dwl, " wl -> ",wl, " dm -> ", dm
+ if math.fabs(dm)> Get_frac_L(R,r,2*wlP,T,0,q1,p1,MgO,MgO_th): break
+ L1 = Get_frac_L(R,r,wl,T,0,q1,p1,MgO,MgO_th)
+ wl2 = 1.0/(1.0/wlP-1.0/wl)
+ L2 = Get_frac_L(R,r,wl2,T,0,q2,p2,MgO,MgO_th)
+ return L1, L2, wl, wl2
+
+def n_eff_FSR(wl,R,T,FSR,pol,MgO=0,MgO_th=5.0,E = 0,n = nLNO1,n_disp=nLNO1_disp):
+ '''
+ Finds effective refraction index (material+geometrical) based on the FSR measuremert (in GHz).
+ R in cm, wl in nm. Also tries to guess the appropriate q and L. The first order approximation.
+ '''
+ import math
+ from scipy.special import ai_zeros
+ import bisect
+ nr = n(wl,T,pol,MgO,MgO_th,E)
+ wl_deriv = n_disp(wl,T,pol,MgO,MgO_th)
+ denom = 2*math.pi*3*R*FSR*(nr-wl_deriv*wl)/29.9792-2
+ neff = nr/denom
+ AiRoots = -ai_zeros(40)[0]
+ L = 2*math.pi*R*neff*1e7/wl
+ a_q = (nr/neff-1)*(2*L*L)**(1.0/3.0)
+ j = bisect.bisect(AiRoots, a_q)
+ #q = 0
+ #if a_q>AiRoots[0]/2.0: q = 1
+ if j: q = j+(a_q-AiRoots[j-1])/(AiRoots[j]-AiRoots[j-1])
+ else: q = 1+(a_q-AiRoots[0])/(AiRoots[1]-AiRoots[0])
+ return neff,q,L
+
+def n_eff(wl,R,r,T,pol,q,p,MgO=0,MgO_th=5.0,E = 0,n = nLNO1):
+ '''
+ Finds effective refraction index (material+geometrical) for a given WGM.
+ R,r in cm, wl in nm.
+ '''
+ import math
+ from WGM_lib import Get_frac_L
+ L = Get_frac_L(R,r,wl,T,pol,q,p,MgO,MgO_th,E,n)
+ neff = L*wl*1e-7/(2*math.pi*R)
+ return neff,L
+ \ No newline at end of file