dipolmol 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: dipolmol
3
+ Version: 1.1.1
4
+ Summary: DiPolMol: tools for calculating molecular dipole moments, polarisabilities, and Hamiltonians
5
+ License: MIT
6
+ Keywords: quantum physics,molecular physics,dipole moment,polarizability,AMO physics,CaF
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy>=1.23
19
+ Requires-Dist: scipy>=1.9
20
+ Requires-Dist: sympy>=1.11
21
+ Requires-Dist: matplotlib>=3.6
22
+
23
+ DiPolMol-Py
24
+ ===========
25
+ A Python package to calculate the rotational and hyperfine structure of doublet-Sigma molecules (e.g., CaF, BaF, SrF) in the presence of external fields.
26
+
27
+ DiPolMol-Py is licensed under a BSD 3 clause license, a copy can be found `See the [LICENSE](LICENSE) file'.
28
+ If you use our work for academic purposes you can cite us using:
29
+
30
+ B.Humphreys *et al.* DiPolMol-Py: A Python package for calculations for $^{2}{\Sigma}$ ground-state molecules (https://arxiv.org/pdf/2503.21663).
31
+
32
+ Installation
33
+ ----------
34
+
35
+ run: pip install dipolmol
36
+
37
+ This installs the latest stable release and all required dependencies.
38
+
39
+ Package structure
40
+ -------------
41
+ **Programme Files:**
42
+
43
+ *Hamiltonian* – used to build the required Hamiltonian using matrix representation, including the field-free Hamiltonian and in the presence of magnetic, dc electric and off-resonant light fields.
44
+
45
+ *Calculate* – uses the eigenstates and eigenenergies found from diagonalising the Hamiltonian to run various calculations. Can be used to identify quantum numbers of eigenstates, calculate transition dipole moments and polarisabilities.
46
+
47
+ *Constants* – includes all known constants for CaF, BaF and SrF.
48
+
49
+ **Examples:**
50
+
51
+ There are three example files for calculating the energy structure in the presence of a magnetic, off-resonant light and electric field (*example_Bfield, example_ac_Efield, example_dc_Efield*).
52
+
53
+ There are two files to calculate the electric and magnetic moments of states (*example_electric_moment, example_magnetic_moment*).
54
+
55
+ We provide *example_polarisability* to calculate the polarisability for a given wavelength and polarisation of light.
56
+
57
+ Finally, *example_tdm* can be used to calculate the transition dipole moment.
58
+
59
+ Example
60
+ -------
61
+ .. code-block:: python
62
+
63
+ import numpy as np
64
+ import dipolmol.hamiltonian as hamiltonian
65
+ import dipolmol.calculate as calc
66
+ from dipolmol.constants import SrF
67
+
68
+ Nmax=4 #Identify the maximum N
69
+ H0,H_B,H_dc,H_ac
70
+ = hamiltonian.build
71
+ (Nmax,SrF,zeeman=True,Edc=False
72
+ ,Eac=False)
73
+
74
+ B = np.linspace(0,100,5000)*1e-4 #Tesla
75
+
76
+ H = H_0[..., None] + H_B[..., None]*B
77
+ H = H.transpose(2,0,1)
78
+
79
+ energies, states, label_list =
80
+ calc.solve(H, Nmax, SrF,label=True, B)
81
+
82
+ Resulting plot of above code
83
+
84
+ .. image:: Images/zeeman_SrF_plot.png
85
+ :width: 400
86
+ :alt: Resulting plot of above example
87
+
88
+ For more examples of usage, see the ``./Examples`` module.
@@ -0,0 +1,66 @@
1
+ DiPolMol-Py
2
+ ===========
3
+ A Python package to calculate the rotational and hyperfine structure of doublet-Sigma molecules (e.g., CaF, BaF, SrF) in the presence of external fields.
4
+
5
+ DiPolMol-Py is licensed under a BSD 3 clause license, a copy can be found `See the [LICENSE](LICENSE) file'.
6
+ If you use our work for academic purposes you can cite us using:
7
+
8
+ B.Humphreys *et al.* DiPolMol-Py: A Python package for calculations for $^{2}{\Sigma}$ ground-state molecules (https://arxiv.org/pdf/2503.21663).
9
+
10
+ Installation
11
+ ----------
12
+
13
+ run: pip install dipolmol
14
+
15
+ This installs the latest stable release and all required dependencies.
16
+
17
+ Package structure
18
+ -------------
19
+ **Programme Files:**
20
+
21
+ *Hamiltonian* – used to build the required Hamiltonian using matrix representation, including the field-free Hamiltonian and in the presence of magnetic, dc electric and off-resonant light fields.
22
+
23
+ *Calculate* – uses the eigenstates and eigenenergies found from diagonalising the Hamiltonian to run various calculations. Can be used to identify quantum numbers of eigenstates, calculate transition dipole moments and polarisabilities.
24
+
25
+ *Constants* – includes all known constants for CaF, BaF and SrF.
26
+
27
+ **Examples:**
28
+
29
+ There are three example files for calculating the energy structure in the presence of a magnetic, off-resonant light and electric field (*example_Bfield, example_ac_Efield, example_dc_Efield*).
30
+
31
+ There are two files to calculate the electric and magnetic moments of states (*example_electric_moment, example_magnetic_moment*).
32
+
33
+ We provide *example_polarisability* to calculate the polarisability for a given wavelength and polarisation of light.
34
+
35
+ Finally, *example_tdm* can be used to calculate the transition dipole moment.
36
+
37
+ Example
38
+ -------
39
+ .. code-block:: python
40
+
41
+ import numpy as np
42
+ import dipolmol.hamiltonian as hamiltonian
43
+ import dipolmol.calculate as calc
44
+ from dipolmol.constants import SrF
45
+
46
+ Nmax=4 #Identify the maximum N
47
+ H0,H_B,H_dc,H_ac
48
+ = hamiltonian.build
49
+ (Nmax,SrF,zeeman=True,Edc=False
50
+ ,Eac=False)
51
+
52
+ B = np.linspace(0,100,5000)*1e-4 #Tesla
53
+
54
+ H = H_0[..., None] + H_B[..., None]*B
55
+ H = H.transpose(2,0,1)
56
+
57
+ energies, states, label_list =
58
+ calc.solve(H, Nmax, SrF,label=True, B)
59
+
60
+ Resulting plot of above code
61
+
62
+ .. image:: Images/zeeman_SrF_plot.png
63
+ :width: 400
64
+ :alt: Resulting plot of above example
65
+
66
+ For more examples of usage, see the ``./Examples`` module.
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dipolmol"
7
+ version = "1.1.1"
8
+ description = "DiPolMol: tools for calculating molecular dipole moments, polarisabilities, and Hamiltonians"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+
13
+
14
+ keywords = [
15
+ "quantum physics",
16
+ "molecular physics",
17
+ "dipole moment",
18
+ "polarizability",
19
+ "AMO physics",
20
+ "CaF"
21
+ ]
22
+
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Science/Research",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Topic :: Scientific/Engineering :: Physics",
33
+ ]
34
+
35
+ dependencies = [
36
+ "numpy>=1.23",
37
+ "scipy>=1.9",
38
+ "sympy>=1.11",
39
+ "matplotlib>=3.6",
40
+ ]
41
+
42
+ # ---------------- setuptools config ---------------- #
43
+
44
+ [tool.setuptools]
45
+ package-dir = {"" = "src"}
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,405 @@
1
+ from . import hamiltonian as hamiltonian
2
+ from .constants import CaF
3
+
4
+ import numpy as np
5
+ import scipy.constants
6
+ from sympy.physics.wigner import wigner_3j as sympy_wig_3j
7
+ from sympy.physics.wigner import wigner_6j as sympy_wig_6j
8
+ import matplotlib.pyplot as plt
9
+ ###############################################################################
10
+ # Start by definining a bunch of constants that are needed for the code #
11
+ ###############################################################################
12
+
13
+ '''
14
+ Important note!
15
+
16
+ All units in this code are SI i.e. elements in the Hamiltonian have units
17
+ of Joules. Outputs will be on the order of 1e-30
18
+
19
+ '''
20
+
21
+ h = scipy.constants.h
22
+ muN = scipy.constants.physical_constants['nuclear magneton'][0]
23
+ bohr = scipy.constants.physical_constants['Bohr radius'][0]
24
+ epsilon_0 = scipy.constants.epsilon_0
25
+ c = scipy.constants.c
26
+
27
+ #Defining Wigner symbols to ensure on float errors
28
+ def wigner_3j(a, b, c, d, e, f):
29
+ a = float(a)
30
+ b = float(b)
31
+ c = float(c)
32
+ d = float(d)
33
+ e = float(e)
34
+ f = float(f)
35
+ return sympy_wig_3j(a,b,c,d,e,f)
36
+
37
+ def wigner_6j(a, b, c, d, e, f):
38
+ a = float(a)
39
+ b = float(b)
40
+ c = float(c)
41
+ d = float(d)
42
+ e = float(e)
43
+ f = float(f)
44
+ return sympy_wig_6j(a,b,c,d,e,f)
45
+
46
+ #Frequencies of each vibrational transition needed for polarisability calculation
47
+ def XXfreq(v1,v2,consts):
48
+ f = 100*c*(consts['XT'][v2]+2*consts['XB'][v2] - consts['XT'][v1])
49
+ return f
50
+ def XAfreq(Xv,Av,Omega,consts):
51
+ f = 100*c*(consts['AT'][Av]+consts['AA'][Av]*(Omega-1) - consts['XT'][Xv])
52
+ return f
53
+ def XBfreq(Xv,Bv,consts):
54
+ f = 100*c*consts['BT'][Bv] - consts['XT'][Xv]
55
+ return f
56
+
57
+
58
+
59
+
60
+ def alpha_012(l, consts):
61
+ '''Calculates the polarisability of the X(v=0) state for a given wavelength l.
62
+
63
+ Args:
64
+ l (float): Wavelength at which to calculate the polarisability at. (m)
65
+ consts (dict): Dictionary of constants for the molecule to be calculated.
66
+
67
+ Returns:
68
+ alpha (list): List containing alpha_0, alpha_1 and alpha_2, the scalar, vector
69
+ and tensor components of molecular polarisability respectively.'''
70
+
71
+ f=c/(l*10**-9)
72
+ XXmu = consts['d0']
73
+ XXvibmu = consts['XXvibmu']
74
+ XAmu = consts['XAmu']
75
+ XBmu = consts['XBmu']
76
+ XAfc = consts['XAfc']
77
+ XBfc = consts['XBfc']
78
+ #There are three alpha terms: alpha parallel (Sigma overlap) and alpha perpendicular (Pi overlap for both Omega values)
79
+ parallel_terms = [[XXfreq(0,0,consts),XXmu],[XXfreq(0,1,consts),XXvibmu],[XBfreq(0, 0,consts),XBmu*(XBfc[0][0]**0.5)]]
80
+ perp_terms1 = [[XAfreq(0,0,0.5,consts), XAmu*(XAfc[0][0]**0.5)],[XAfreq(0,1,0.5,consts), XAmu*(XAfc[0][1]**0.5)]]
81
+ perp_terms3 = [[XAfreq(0,0,1.5,consts), XAmu*(XAfc[0][0]**0.5)],[XAfreq(0,1,1.5,consts), XAmu*(XAfc[0][1]**0.5)]]
82
+ a_par = 0
83
+ a_perp1 = 0
84
+ a_perp3 = 0
85
+ for i in range(len(parallel_terms)):
86
+ a_par += 1/h*(1/(parallel_terms[i][0]+f)+1/(parallel_terms[i][0]-f))*parallel_terms[i][1]**2
87
+ for i in range(len(perp_terms1)):
88
+ a_perp1 += 1/h*(1/(perp_terms1[i][0]+f)+1/(perp_terms1[i][0]-f))*perp_terms1[i][1]**2
89
+ a_perp3 += 1/h*(1/(perp_terms3[i][0]+f)+1/(perp_terms3[i][0]-f))*perp_terms3[i][1]**2
90
+ alpha_0 = 1/3*(a_par+a_perp1+a_perp3)*10**4/(2*c*epsilon_0)*10**-4
91
+ alpha_1 = 1/2*(f/XAfreq(0,0,0.5,consts)*a_perp1-f/XAfreq(0,0,1.5,consts)*a_perp3)*10**4/(2*c*epsilon_0)*10**-4
92
+ alpha_2 = 1/3*(2*a_par - a_perp1 - a_perp3)*10**4/(2*c*epsilon_0)*10**-4
93
+ return [alpha_0,alpha_1, alpha_2]
94
+
95
+
96
+ def dipole(Nmax,Consts,M):
97
+ ''' Generates the induced dipole moment operator for a Rigid rotor.
98
+ Expanded to cover state vectors in the uncoupled hyperfine basis.
99
+
100
+ Args:
101
+ Nmax (int): maximum rotational states.
102
+ Consts (dict): Dictionary of constants for the molecule to be calculated.
103
+ M (float): index indicating the helicity of the dipole field. -1 = S+, 0 = Pi, +1 = S-
104
+
105
+ Returns:
106
+ Dmat (np.ndarray): - dipole matrix
107
+ '''
108
+
109
+
110
+ I = Consts['I']
111
+ S = Consts['S']
112
+ Ishape = int(2*I+1)
113
+ Sshape = int(2*S+1)
114
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
115
+ dmat = np.zeros((shape,shape),dtype= complex)
116
+ dmat = (np.kron(dmat,np.kron(np.identity(Ishape),
117
+ np.identity(Sshape))))
118
+ i=0
119
+ j=0
120
+
121
+ for N1 in range(0,Nmax+1):
122
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
123
+ for F1 in np.arange(J1-I,(J1+I+1),1):
124
+ for mF1 in np.arange(-F1,(F1+1),1):
125
+ for N2 in range(0,Nmax+1):
126
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
127
+ for F2 in np.arange(J2-I,(J2+I+1),1):
128
+ for mF2 in np.arange(-F2,+(F2+1),1):
129
+ dmat[i,j] = np.round((-1+0j)**(F2-mF2+J1+J2+F1+N2+1))*\
130
+ ((2*F1+1)*(2*F2+1)*(2*J1+1)*(2*J2+1))**0.5*((2*N1+1)*(2*N2+1))**0.5*(-1)**N2*wigner_3j(N2,1,N1,0,0,0)*\
131
+ wigner_3j(F2,1,F1,-mF2,M,mF1)*wigner_6j(J1,F1,I,F2,J2,1)*wigner_6j(N1,J1,0.5,J2,N2,1)
132
+
133
+ i+=1
134
+ i=0
135
+ j+=1
136
+
137
+
138
+ return dmat
139
+
140
+
141
+ def transition_dipole_moment(Nmax,Consts,M,states,gs,locs=None):
142
+ ''' Function to calculate the Transition Dipole Moment between a state gs
143
+ and a range of states. Returns the TDM in units of the permanent dipole
144
+ moment (d0).
145
+
146
+ Args:
147
+ Nmax (int): Maximum rotational quantum number in original calculations
148
+ Consts (dict): Dictionary of constants for the molecule to be calculated
149
+ M (float): Helicity of Transition, -1 = S+, 0 = Pi, +1 = S-
150
+ States (np.ndarray): matrix for eigenstates of problem output from np.linalg.eig
151
+ gs (int): index of ground state.
152
+
153
+ kwargs:
154
+ locs (list of ints): optional argument to calculate for subset of States, should be an
155
+ array-like.
156
+
157
+ Returns:
158
+ TDM (list of floats): transition dipole moment between gs and States.
159
+
160
+ '''
161
+ states = states[0]
162
+ dipole_op = dipole(Nmax,Consts,M)
163
+
164
+ gs = np.conj(states[:,gs])
165
+ if locs != None :
166
+ states = states[:,locs]
167
+
168
+ tdm = np.einsum('i,ij,jk->k',gs,dipole_op,states).real
169
+
170
+ return tdm
171
+
172
+
173
+ def magnetic_moment(States, Nmax, Consts):
174
+ '''Returns the magnetic moments of each eigenstate.
175
+
176
+ Args:
177
+ States (np.ndarray): matrix for eigenstates of problem output from np.linalg.eig
178
+ Nmax (int): Maximum rotational quantum number in original calculations
179
+ Consts (dict): Dictionary of constants for the molecule to be calculated
180
+
181
+ Returns:
182
+ mu (list of floats): magnetic moment for each eigenstate in States.
183
+
184
+ '''
185
+
186
+ muz = -1*hamiltonian.H_zee(Nmax,Consts,Consts['I'],Consts['S'])
187
+
188
+ mu =np.einsum('ijk,jl,ilk->ik',
189
+ np.conjugate(States),muz,
190
+ States)
191
+ return mu
192
+
193
+
194
+ def electric_moment(States, Nmax, Consts):
195
+ '''Returns the electric dipole moments of each eigenstate
196
+
197
+ Args:
198
+ States (np.ndarray): matrix for eigenstates of problem output from np.linalg.eig
199
+ Nmax (int): Maximum rotational quantum number in original calculations
200
+ Consts (dict): Dictionary of constants for the molecule to be calculated
201
+
202
+ Returns:
203
+ d (list of floats): electric dipole moment for each eigenstate in States
204
+ '''
205
+
206
+ dz = -1*hamiltonian.H_dc(Nmax,Consts,Consts['I'],Consts['S'])
207
+
208
+ d =np.einsum('ijk,jl,ilk->ik',
209
+ np.conjugate(States),dz,
210
+ States)
211
+ return d
212
+
213
+
214
+
215
+ def sort_smooth(energy, states):
216
+ ''' Sort states to remove false avoided crossings.
217
+ This is a function to ensure that all eigenstates plotted change
218
+ adiabatically, it does this by assuming that step to step the eigenstates
219
+ should vary by only a small amount (i.e. that the step size is fine) and
220
+ arranging states to maximise the overlap one step to the next.
221
+
222
+ Args:
223
+ Energy (np.ndarray) : np.ndarray containing the eigenergies, as from np.linalg.eig
224
+ States (np.ndarray): np.ndarray containing the states, in the same order as Energy
225
+ Returns:
226
+ Energy (np.ndarray) : np.ndarray containing the eigenergies, as from np.linalg.eig
227
+ States (np.ndarray): np.ndarray containing the states, in the same order as Energy E[x,i] -> States[x,:,i]
228
+ '''
229
+ ls = np.arange(states.shape[2],dtype="int") #from 0 to number of states
230
+
231
+ number_iterations = len(energy[:,0]) #ie the number of B field values
232
+
233
+ for i in range(2, number_iterations):
234
+ '''
235
+ This loop sorts the eigenstates such that they maintain some
236
+ continuity. Each eigenstate should be chosen to maximise the overlap
237
+ with the previous.
238
+ '''
239
+ #calculate the overlap of the ith and jth eigenstates
240
+ overlaps = np.einsum('ij,ik->jk',
241
+ np.conjugate(states[i-1,:,:]),states[i,:,:])
242
+ orig2 = states[i,:,:].copy()
243
+ orig1 = energy[i,:].copy()
244
+ #insert location of maximums into array ls
245
+ np.argmax(np.abs(overlaps),axis=1,out=ls)
246
+
247
+ for k in range(states.shape[2]):
248
+ l = ls[k]
249
+ if l!=k:
250
+ energy[i,k] = orig1[l].copy()
251
+ states[i,:,k] = orig2[:,l].copy()
252
+ return energy, states
253
+
254
+
255
+
256
+
257
+
258
+ def label_FmF_states(States, Nmax, consts, B):
259
+ """
260
+ Function that takes the array of eigenstates as generated from the hamiltonian
261
+ (and processed with sort_smooth to avoid false avoided crossings) and assigns
262
+ each an (N, F, mF) label.
263
+
264
+ Inputs:
265
+ States (np.ndarray) : np.ndarray containing the eigenstates, as from np.linalg.eig
266
+ Nmax (int) : Maximum N state to consider in the calculations
267
+ B (int/float/list/array) : Magnetic field values used to calculate the eigenstates
268
+ consts (dict): Dictionary of constants for the molecule to be calculated
269
+
270
+ Returns:
271
+ FmF_labels or F_labels (list): list of [N, F, mF] or [N, F] labels, one for each of the eigenstates in the input
272
+ """
273
+
274
+ S = consts['S']
275
+ I= consts['I']
276
+
277
+ #initialise error counters to 0
278
+ zeromagfielderror = 0
279
+ F_error = 0
280
+ mF_error = 0
281
+
282
+ startingstateindex = 0
283
+
284
+ #check the B fields input by the user, select a non-zero B field to label at
285
+ if type(B) == float or type(B) == int:
286
+ States = States[0]
287
+ if B == 0:
288
+ zeromagfielderror += 1
289
+ else:
290
+ if B[0] != 0:
291
+ States = States[0]
292
+ else:
293
+ if len(States) > 1:
294
+ States = States[1]
295
+ startingstateindex += 1
296
+ else:
297
+ States = States[0]
298
+ zeromagfielderror += 1
299
+
300
+
301
+
302
+ #generate a list of the possible (N, J, F, mF) states
303
+ statelist = []
304
+ for N1 in range(0,Nmax+1):
305
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
306
+ for F1 in np.arange(J1-I,(J1+I+1),1):
307
+ for mF1 in np.arange(-F1,(F1+1),1):
308
+ state = [N1, J1, F1, mF1]
309
+ statelist.append(state)
310
+
311
+ FmF_labels = []
312
+ F_labels = []
313
+
314
+ #now match the (N,J,F,mF) labels with the eigenstates
315
+ for i in range(len(States)): #For each of the eigenstates
316
+ statebreakdown = []
317
+ for n in range(len(States)): #loop through each value in the eigenstate
318
+ if np.round(States[:,i][n],1) != 0: #consider the non-zero coeff
319
+ statebreakdown.append(statelist[n])
320
+ #following can be useful to check states
321
+ #print(f'{States[:,i][n]} coeff of {statelist[n]}' )
322
+
323
+ #Confirming consistency in the F,mF labelling of the states
324
+ if len(statebreakdown) > 1: #only do this if there are multiple (F,mF) states to consider
325
+ avgF = np.mean(statebreakdown, axis=0)[2]
326
+ avgmF = np.mean(statebreakdown, axis=0)[3]
327
+
328
+ if avgF != statebreakdown[0][2] or statebreakdown[0][2] != statebreakdown[1][2]:
329
+ F_error += 1
330
+
331
+ if avgmF != statebreakdown[0][3] or statebreakdown[0][3] != statebreakdown[1][3]:
332
+ mF_error += 1
333
+ F_labels.append([int(statebreakdown[0][0]), int(statebreakdown[0][2])])
334
+
335
+ if avgF == statebreakdown[0][2] and statebreakdown[0][2] == statebreakdown[1][2] and avgmF == statebreakdown[0][3] and statebreakdown[0][3] == statebreakdown[1][3]:
336
+ FmF_labels.append([int(statebreakdown[0][0]), int(statebreakdown[0][2]), int(statebreakdown[0][3])])
337
+ F_labels.append([int(statebreakdown[0][0]), int(statebreakdown[0][2])])
338
+
339
+ else: #if we are only considering one state
340
+ FmF_labels.append([int(statebreakdown[0][0]), int(statebreakdown[0][2]), int(statebreakdown[0][3])])
341
+ F_labels.append([int(statebreakdown[0][0]), int(statebreakdown[0][2])])
342
+
343
+
344
+ #Various error messages
345
+ if zeromagfielderror != 0:
346
+ if mF_error != 0 and F_error == 0:
347
+ print('mF labelling failed as attempted to label at zero magnetic field: please input a non-zero magnetic field. Returning N,F labels instead.')
348
+ if F_error != 0:
349
+ print('mF, F labelling failed as attempted to label at too high a magnetic field: please input a smaller starting magnetic field value.')
350
+ else:
351
+ if F_error != 0:
352
+ if startingstateindex == 0:
353
+ print('mF, F labelling failed as attempted to label at too high a magnetic field: please input a smaller starting magnetic field value.')
354
+ else:
355
+ print('mF, F labelling failed as attempted to label at too high a magnetic field: please reduce the spacing between your input magnetic field values. Alternatively, start plotting at a small, non-zero magnetic field.')
356
+ if mF_error != 0 and F_error ==0:
357
+ if startingstateindex == 0:
358
+ print('mF labelling failed as attempted to label at too high a magnetic field: please input a smaller starting magnetic field value. Returning N,F labels instead.')
359
+ else:
360
+ print('mF labelling failed as attempted to label at too high a magnetic field: please reduce the spacing between your input magnetic field values. Alternatively, start plotting at a small, non-zero magnetic field. Returning N,F labels instead.')
361
+
362
+
363
+ if F_error == 0 and mF_error == 0:
364
+ return FmF_labels
365
+ if F_error == 0 and mF_error != 0:
366
+ return F_labels
367
+ if F_error != 0:
368
+ return None
369
+
370
+
371
+
372
+
373
+ def solve(H, Nmax, consts, label, B=None):
374
+ """Function that combines the diagonalisation of the Hamiltonian generated by the
375
+ hamiltonian.build method, and the sorting of the states performed by the
376
+ calculate.sort_smooth function. Generates the sorted set of eigenstates and
377
+ eigenenergies for the given Hamiltonian.
378
+
379
+ Inputs:
380
+ H (np.ndarray): Hamiltonian to generate the eigenstates and eigenenergies for,
381
+ as generated by hamiltonian.build method.
382
+ Nmax (int): Maximum rotational state to consider in the calculations.
383
+ consts (dict): Dictionary of constants for the molecule to be calculated
384
+ B (list/array): Magnetic field values used to calculate the eigenstates
385
+ label (boolean): If True, return the F, mF state labels for the eigenstates
386
+ """
387
+ #generate the eigenenergies and eigenstates
388
+ energies, states = np.linalg.eigh(H)
389
+ #apply sort_smooth to the eigenstates to maintain consistency with the labelling
390
+ energies, states = sort_smooth(energies, states)
391
+
392
+ if label:
393
+ if type(B) == float or type(B) == int or type(B) == list or type(B) == np.ndarray:
394
+ #ie if a suitable type of B value is provided
395
+ label_list = label_FmF_states(states, Nmax, consts, B)
396
+
397
+ return energies, states, label_list
398
+
399
+ else:
400
+ print('Please provide magnetic field value(s) to label at.')
401
+ return energies, states, None
402
+
403
+
404
+ else:
405
+ return energies, states
@@ -0,0 +1,106 @@
1
+ import scipy.constants
2
+ import numpy as np
3
+
4
+ ###############################################################################
5
+ # Molecular Constants
6
+ ###############################################################################
7
+ #Here are some starting dictionaries for various molecules.
8
+ #References given, but check up to date if precision needed!
9
+ e = scipy.constants.e
10
+ h = scipy.constants.h
11
+ muN = scipy.constants.physical_constants['nuclear magneton'][0]
12
+ muB = scipy.constants.physical_constants['Bohr magneton'][0]
13
+ bohr = scipy.constants.physical_constants['Bohr radius'][0]
14
+ eps0 = scipy.constants.epsilon_0
15
+ a0 = scipy.constants.physical_constants['atomic unit of length'][0]
16
+ c = scipy.constants.c
17
+ DebyeSI = 3.33564e-30
18
+ inv_cm_to_Hz = 100*c
19
+
20
+ CaF = {"I":0.5,
21
+ "S":0.5,
22
+ "d0": 1.02041*10**-29,#3.07*DebyeSI,
23
+ "Brot":10267.5387*10**6*h,
24
+ "Drot":0.01406*10**6*h,
25
+ "gamma": 39.65891*10**6*h,
26
+ "b" : 109.1839*10**6*h,
27
+ "c": 40.119*10**6*h,
28
+ "CC": 28.76*10**3*h,
29
+ "MuS": 2.002*muB,
30
+ "MuL": -1.86*10**-3*muB, #10.1103/PhysRevLett.124.063001
31
+ "MuN": 5.585*muN,
32
+ "MuR": -5.13*10**-5*muB, #10.1103/PhysRevLett.124.063001
33
+ "alpha_0":1.4*10**-3*h, #h*Hz/(W/m^2) at 780 nm calculated in Caldwell2020 https://arxiv.org/abs/1910.10689 NB these are alpha/2ce_0
34
+ "alpha_1":3*10**-5*h,
35
+ "alpha_2":-8*10**-4*h,
36
+ "Beta":0,#angle of polarisation for polarisability function
37
+ #Transition dipole moments
38
+ "XXvibmu" : 0.0999*e*a0, #
39
+ "XAmu" : 2.03*1e-29,#2.3439*e*a0, #P. J. Dagdigian, H. W. Cruse, and R. N. Zare, J. Chem. Phys. 60, 2330 (1974).
40
+ "XBmu" : 1.71*e*a0, # P. J. Dagdigian, H. W. Cruse, and R. N. Zare, J. Chem. Phys. 60, 2330 (1974).
41
+ #FC factors from M. Pelegrini, C. S. Vivacqua, O. Roberto-Neto, F. R. Ornellas, and F. B. Machado. Braz. J. Phys. 35.4 A (2005), pp. 950–956. X-A
42
+ # X-B M. Dulick, P. F. Bernath, and R. W. Field. Can. J. Phys. 58.5 (1980), pp. 703–712.
43
+ "XAfc" : [[0.964,0.036,0],[0.035,0.895,0.07],[0.001,0.065,0.83]],
44
+ "XBfc" : [[9.992*10**-1,7.27*10**-4,3.809*10**-5],[7.396*10**-4,9.973*10**-1,1.814*10**-3],[2.473*10**-5,1.873*10**-3,9.945*10**-1]],
45
+ #Terms for vibrational transitions [v=0,1,2] Journal of Molecular Spectroscopy 197, 289–296 (1999)
46
+ #Only need for v=0 in X and B, and then V=0,1 in A
47
+ #In cm^-1
48
+ "XT" : np.array([0,582.8478,1159.9473]), #electronic splitting
49
+ "XB" : np.array([0.34248818,0.34005359, 0.33762897]),#rotational constants
50
+ "AT" : np.array([16529.653,17118.103,17700.49]), #electronic splitting
51
+ "AA" : np.array([71.491, 71.614, 71.737]), #Lambda doubling
52
+ "BT" : np.array([18832.031, 19398.254, 19958.276]), #electronic splitting
53
+ "BB" : np.array([0.341058, 0.338469, 0.335903]), # rotational constants
54
+ }
55
+
56
+ BaF = {"I":0.5,
57
+ "S":0.5,
58
+ "d0": 1.05739e-29,#3.17*DebyeSI,
59
+ "Brot":6473.9586572e6*h,# from PRA 94, 063415 (2016) https://journals.aps.org/pra/pdf/10.1103/PhysRevA.94.063415
60
+ "Drot":5.5296816e3*h,#1085*10**7*29979.2458*h,
61
+ "gamma": 80.95547199999999e6*h,
62
+ "b" : 63.509e6*h,
63
+ "c": 8.224e6*h,
64
+ "CC": 0,
65
+ "MuS": 2.002*muB,
66
+ "MuL": -0.028*muB,#Where did I get this?
67
+ "MuN": 5.585*muN,
68
+ "MuR" : 0,
69
+ #"alpha_0":,
70
+ #"alpha_1":,
71
+ #"alpha_2":,
72
+ "Beta":0 #angle of polarisation for polarisability function
73
+ }
74
+
75
+
76
+ SrF = {"I": 0.5,
77
+ "S": 0.5,
78
+ "d0": 3.4963*DebyeSI,
79
+ "Brot": 7487.6*10**6*h,
80
+ "Drot":0.0075*10**6*h,
81
+ "gamma": 74.795*10**6*h,
82
+ "b" : 97.0827*10**6*h,
83
+ "c":30.2675*10**6*h,
84
+ "CC": 0.0023*10**6*h,
85
+ "MuS": 2.002*muB, # g_s * muB
86
+ "MuN": 5.585*muN,
87
+ "MuL": -4.97*10**-3*muB, #10.1103/PhysRevLett.124.063001
88
+ "MuR": -4.77*10**-5*muB, #10.1103/PhysRevLett.124.063001
89
+ "Beta":0, #angle of polarisation for polarisability function
90
+ #Transition dipole moments
91
+ "XXvibmu" : 0.0999*e*a0, #MISSING
92
+ "XAmu" : 2.45*e*a0, #P. J. Dagdigian, H. W. Cruse, and R. N. Zare, J. Chem. Phys. 60, 2330 (1974).
93
+ "XBmu" : 1.94*e*a0, # Berg, L. E. et al. Chem. Phys. Lett. 248, (1996)
94
+ #FC factors from Hao et al. J. Chem. Phys. 151, 034302 (2019)
95
+ "XAfc" : [[0.9789,0.02054,4*10**-4],[0.02102,0.9377,0.03969],[2.72*10**-5,4.158*10**-2,8.978*10**-1]],
96
+ "XBfc" : [[9.961*10**-1,3.866*10**-3,3.604*10**-6],[3.856*10**-3,9.881*10**-1,8*10**-3],[1.343*10**-5,7.959*10**-3,9.796*10**-1]],
97
+ #Terms for vibrational transitions [v=0,1,2] - for v=0 find in J. Barry thesis table 2.6
98
+ "XT" : np.array([0,0,0])*inv_cm_to_Hz, #electronic splitting
99
+ "XB" : np.array([7.4876,7.44123,7.395])*10**9,#rotational constants J. Barry thesis table 2.8
100
+
101
+ "AT" : np.array([15072.09,0,0])*inv_cm_to_Hz, #electronic splitting
102
+ "AA" : np.array([281.46138,0,0])*inv_cm_to_Hz, #Lambda doubling
103
+
104
+ "BT" : np.array([17267.41,0,0])*inv_cm_to_Hz, #electronic splitting
105
+ "BB" : np.array([0.249396,0,0])*inv_cm_to_Hz, # rotational constants
106
+ }
@@ -0,0 +1,422 @@
1
+ import numpy as np
2
+ from sympy.physics.wigner import wigner_3j as sympy_wig_3j
3
+ from sympy.physics.wigner import wigner_6j as sympy_wig_6j
4
+
5
+ from sympy import KroneckerDelta
6
+ from scipy.linalg import block_diag
7
+ import scipy.constants
8
+ from scipy.special import sph_harm
9
+ import matplotlib.pyplot as plt
10
+ '''
11
+ This module contains the main code to calculate the hyperfine structure of
12
+ singlet -sigma molecules. In usual circumstances most of the functions within
13
+ are not user-oriented.
14
+
15
+ Example:
16
+ Basic usage of this module is for accessing the eigenstates and
17
+ eigenvalues of the molecule in question. This is most easily done
18
+ by combining this module with the user's favourite linear algebra module.
19
+ For instance to find the zero-field hyperfine states of Molecule::
20
+
21
+ $ from diatom import Hamiltonian
22
+ $ from np import linalg as la
23
+ $ H0,Hz,HDC,HAC = Hamiltonian.Build_Hamiltonians(5,Molecule)
24
+ $ ev,es = la.eigh(H0)
25
+ '''
26
+
27
+
28
+ ###############################################################################
29
+ # Start by definining constants that are needed for the code #
30
+ ###############################################################################
31
+
32
+ '''
33
+ Important note!
34
+
35
+ All units in this code are SI i.e. elements in the Hamiltonian have units
36
+ of Joules. Outputs will be on the order of 1e-30
37
+
38
+ '''
39
+
40
+ h = scipy.constants.h
41
+ muN = scipy.constants.physical_constants['nuclear magneton'][0]
42
+ bohr = scipy.constants.physical_constants['Bohr radius'][0]
43
+ eps0 = scipy.constants.epsilon_0
44
+ c = scipy.constants.c
45
+ pi = np.pi
46
+
47
+ DebyeSI = 3.33564e-30
48
+ """ Conversion factor from debyes to J/V/m """
49
+
50
+ ###############################################################################
51
+ # Functions for the calculations to use #
52
+ ###############################################################################
53
+
54
+ def wigner_3j(a, b, c, d, e, f):
55
+ a = float(a)
56
+ b = float(b)
57
+ c = float(c)
58
+ d = float(d)
59
+ e = float(e)
60
+ f = float(f)
61
+ return sympy_wig_3j(a,b,c,d,e,f)
62
+
63
+ def wigner_6j(a, b, c, d, e, f):
64
+ a = float(a)
65
+ b = float(b)
66
+ c = float(c)
67
+ d = float(d)
68
+ e = float(e)
69
+ f = float(f)
70
+ return sympy_wig_6j(a,b,c,d,e,f)
71
+
72
+ def wigner_D(l, m, alpha, beta, gamma):
73
+ ''' The Wigner D matrix with labels l and m.
74
+
75
+ Calculates the Wigner D Matrix for the given Alpha,beta,gamma in radians.
76
+ The wigner-D matrices represent rotations of angular momentum operators.
77
+ The indices l and m determine the value of the matrix.
78
+ The second index (m') is always zero.
79
+
80
+ The input angles are the x-z-x euler angles
81
+
82
+ Args:
83
+ l (int) : order of wigner Matrix
84
+ m (float): first index of Wigner Matrix
85
+ alpha,beta,gamma (float) : x,z,x Euler angles in radians
86
+ Returns:
87
+ D (float) : Value of the wigner-D matrix
88
+ '''
89
+ prefactor = np.sqrt((4*np.pi)/(2*l+1))
90
+ function = np.conj(sph_harm(m,l,alpha,beta))
91
+ return prefactor*function
92
+
93
+
94
+ def H_rot(Nmax,consts,I,S):
95
+ ''' Calculates <N,J,F,mF| H_rot |N',J',F', mF'>. The rotational hamiltonian H_rot
96
+ is diagonal in this basis, and this function iterates over N, J, F, mF, N',
97
+ J', F', mF' to build a matrix and evaluate the diagonal elements.
98
+ This function is based off Jesus Aldegunde's FORTRAN 77 code.
99
+
100
+ Args:
101
+ Nmax (int): Maximum rotational quantum number to calculate.
102
+ I (float): Nuclear spin. We only consider molecules where one consistuent
103
+ atom has no nuclear spin, so this is the non zero I of the other.
104
+ S (float): Electronic spin.
105
+ consts (dict): Dictionary of constants for the molecule to be calculated.
106
+
107
+ Returns:
108
+ H (np.ndarray): Hamiltonian in joules
109
+ '''
110
+ Ishape = int(2*I+1)
111
+ Sshape = int(2*S+1)
112
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
113
+ H_ROT = np.zeros((shape,shape), dtype= complex)
114
+ H_ROT = (np.kron(H_ROT,np.kron(np.identity(Ishape),
115
+ np.identity(Sshape))))
116
+
117
+ i=0
118
+ j=0
119
+ for N1 in range(0,Nmax+1):
120
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
121
+ for F1 in np.arange(J1-I,(J1+I+1),1):
122
+ for mF1 in np.arange(-F1,(F1+1),1):
123
+ for N2 in range(0,Nmax+1):
124
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
125
+ for F2 in np.arange(J2-I,(J2+I+1),1):
126
+ for mF2 in np.arange(-F2,+(F2+1),1):
127
+
128
+
129
+ H_ROT[i,j]= consts['Brot']*(N1*(N1+1))*KroneckerDelta(N1,N2)*\
130
+ KroneckerDelta(J1,J2)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)+\
131
+ consts['Drot']*(N1*(N1+1))**2*KroneckerDelta(N1,N2)*\
132
+ KroneckerDelta(J1,J2)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)
133
+
134
+
135
+ i+=1
136
+ i=0
137
+ j+=1
138
+
139
+ #final check for NaN errors, mostly this is due to division by zero or
140
+ # multiplication by a small prefactor. it is safe to set these terms to 0
141
+ H_ROT[np.isnan(H_ROT)] =0
142
+
143
+ #return the matrix, in the full coupled basis.
144
+
145
+ return H_ROT
146
+
147
+
148
+ def H_hf(Nmax,consts,I,S):
149
+ ''' Calculates <N,J,F,mF| H_hf |N',J',F', mF'> by iterating over N, J, F, mF, N',
150
+ J', F', mF' to build a matrix and evaluate the elements.
151
+ This function is based off Jesus Aldegunde's FORTRAN 77 code.
152
+
153
+ Args:
154
+
155
+ Nmax (int) - maximum rotational quantum number to calculate
156
+ I (float): Nuclear spin. We only consider molecules where one consistuent
157
+ atom has no nuclear spin, so this is the non zero I of the other.
158
+ S (float): Electronic spin.
159
+ consts (dict): Dictionary of constants for the molecule to be calculated.
160
+
161
+ Returns:
162
+ H (np.ndarray): Hamiltonian in joules
163
+ '''
164
+
165
+ Ishape = int(2*I+1)
166
+ Sshape = int(2*S+1)
167
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
168
+ H_HF = np.zeros((shape,shape),dtype= complex)
169
+ H_HF = (np.kron(H_HF,np.kron(np.identity(Ishape),
170
+ np.identity(Sshape))))
171
+
172
+ i=0
173
+ j=0
174
+ for N1 in range(0,Nmax+1):
175
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
176
+ for F1 in np.arange(J1-I,(J1+I+1),1):
177
+ for mF1 in np.arange(-F1,(F1+1),1):
178
+ for N2 in range(0,Nmax+1):
179
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
180
+ for F2 in np.arange(J2-I,(J2+I+1),1):
181
+ for mF2 in np.arange(-F2,+(F2+1),1):
182
+ H_HF[i,j]= consts['gamma']/2*(J1*(J1+1)-N1*(N1+1)-S*(S+1))*KroneckerDelta(N1,N2)*\
183
+ KroneckerDelta(J1,J2)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)+\
184
+ (consts['b']+consts['c']/3)*(np.round((-1+0j)**(J1+J2+F1+N1),0)*\
185
+ KroneckerDelta(N1,N2)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)*\
186
+ 3/2*((2*J2+1)*(2*J1+1))**0.5*wigner_6j(F1,0.5,J2,1,J1,0.5)*\
187
+ wigner_6j(0.5, J2, N1, J1, 0.5, 1)) +\
188
+ 10**0.5*consts['c']*np.round((-1+0j)**(J1+F1+N2+0.5),0)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)*\
189
+ ((2*N2+1)*(2*N1+1)*(2*J2+1)*(2*J1+1))**0.5*wigner_6j(F1,0.5,J2,1,J1,0.5)*\
190
+ wigner_6j(J2,J1,1,0.5,1.5,N1)*wigner_6j(N1,N2,2,0.5,1.5,J2)*wigner_3j(N2,2,N1,0,0,0)+\
191
+ consts['CC']*KroneckerDelta(N1,N2)*KroneckerDelta(F1,F2)*KroneckerDelta(mF1,mF2)*\
192
+ np.round((-1+0j)**(2*J1+N2+F2),0)*(3/2)**0.5*((2*J2+1)*(2*J1+1)*N1*(N1+1)*(2*N1+1))**0.5*\
193
+ wigner_6j(F1,0.5,J2,1,J1,0.5)*wigner_6j(N2,J2,0.5,J1,N1,1)
194
+
195
+
196
+ i+=1
197
+ i=0
198
+ j+=1
199
+
200
+ #final check for NaN errors, mostly this is due to division by zero or
201
+ # multiplication by a small prefactor. it is safe to set these terms to 0
202
+ H_HF[np.isnan(H_HF)] =0
203
+
204
+ #return the matrix, in the full coupled basis.
205
+ return H_HF
206
+
207
+ def H_dc(Nmax,consts,I,S):
208
+ ''' Calculates the effect of the anisotropic DC light shift for a rigid-rotor
209
+ like molecule, , <N,J,F,mF| H_dc |N',J',F', mF'>.
210
+
211
+ This function based on Jesus Aldegunde's FORTRAN 77 code and iterates
212
+ over N, J, F, mF, N', J', F', mF' to build a matrix and evaluate the elements.
213
+
214
+ Args:
215
+ Nmax (int) - maximum rotational quantum number to calculate
216
+ I (float): Nuclear spin. We only consider molecules where one consistuent
217
+ atom has no nuclear spin, so this is the non zero I of the other.
218
+ S (float): Electronic spin.
219
+ constst (dict): Dictionary of constants for the molecule to be calculated.
220
+
221
+ Returns:
222
+ H (np.ndarray): Hamiltonian in joules
223
+ '''
224
+
225
+ Ishape = int(2*I+1)
226
+ Sshape = int(2*S+1)
227
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
228
+ HDC = np.zeros((shape,shape),dtype= complex)
229
+ HDC = (np.kron(HDC,np.kron(np.identity(Ishape),
230
+ np.identity(Sshape))))
231
+ i=0
232
+ j=0
233
+
234
+ for N1 in range(0,Nmax+1):
235
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
236
+ for F1 in np.arange(J1-I,(J1+I+1),1):
237
+ for mF1 in np.arange(-F1,(F1+1),1):
238
+ for N2 in range(0,Nmax+1):
239
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
240
+ for F2 in np.arange(J2-I,(J2+I+1),1):
241
+ for mF2 in np.arange(-F2,+(F2+1),1):
242
+ HDC[i,j] = -1*consts['d0']*np.round((-1+0j)**(F2-mF2+J1+J2+F1+N2+1))*\
243
+ ((2*F1+1)*(2*F2+1)*(2*J1+1)*(2*J2+1))**0.5*((2*N1+1)*(2*N2+1))**0.5*(-1)**N2*wigner_3j(N2,1,N1,0,0,0)*\
244
+ wigner_3j(F2,1,F1,-mF2,0,mF1)*wigner_6j(J1,F1,I,F2,J2,1)*wigner_6j(N1,J1,0.5,J2,N2,1)
245
+
246
+ i+=1
247
+ i=0
248
+ j+=1
249
+ #final check for NaN errors, mostly this is due to division by zero or
250
+ # multiplication by a small prefactor. it is safe to set these terms to 0
251
+ HDC[np.isnan(HDC)] =0
252
+
253
+ #return the matrix, in the full coupled basis.
254
+ return HDC
255
+
256
+ def H_ac(Nmax,consts,I,S):
257
+ ''' Calculates the effect of the anisotropic light shift for a rigid-rotor
258
+ like molecule, , <N,J,F,mF| H_ac |N',J',F', mF'>.
259
+
260
+ This function based on Jesus Aldegunde's FORTRAN 77 code and iterates
261
+ over N, J, F, mF, N', J', F', mF' to build a matrix and evaluate the elements.
262
+
263
+ Args:
264
+
265
+ Nmax (int) - maximum rotational quantum number to calculate
266
+ I (float): Nuclear spin. We only consider molecules where one consistuent
267
+ atom has no nuclear spin, so this is the non zero I of the other.
268
+ S (float): Electronic spin.
269
+ constst (dict): Dictionary of constants for the molecule to be calculated.
270
+
271
+ Returns:
272
+ H (np.ndarray): Hamiltonian in joules
273
+ '''
274
+ Ishape = int(2*I+1)
275
+ Sshape = int(2*S+1)
276
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
277
+ HAC = np.zeros((shape,shape),dtype= complex)
278
+ HAC = (np.kron(HAC,np.kron(np.identity(Ishape),
279
+ np.identity(Sshape))))
280
+ i=0
281
+ j=0
282
+ for N1 in range(0,Nmax+1):
283
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
284
+ for F1 in np.arange(J1-I,(J1+I+1),1):
285
+ for mF1 in np.arange(-F1,(F1+1),1):
286
+ for N2 in range(0,Nmax+1):
287
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
288
+ for F2 in np.arange(J2-I,(J2+I+1),1):
289
+ for mF2 in np.arange(-F2,+(F2+1),1):
290
+ M=mF2-mF1
291
+ HAC[i,j]= -1*consts['alpha_2']*\
292
+ (wigner_D(2,M,0,consts['Beta'],0)*\
293
+ ((-1)**(N1+N2)+1)*np.round((-1+0j)**(F2-mF2+F1-J2+J1+I+0.5),0)*((2*F1+1)*(2*F2+1))**0.5*\
294
+ ((2*N1+1)*(2*N2+1)*(2*J1+1)*(2*J2+1))**0.5*wigner_6j(J2,F2,I,F1,J1,2)*\
295
+ wigner_3j(F2,2,F1,-mF2,M,mF1)*wigner_3j(J1,0.5,N1,-0.5,0.5,0)*\
296
+ wigner_3j(J2,0.5,N2,-0.5,0.5,0)*wigner_3j(J2,2,J1,-0.5,0,0.5)) -\
297
+ consts['alpha_1']*(wigner_D(2,M,0,consts['Beta'],0)*((-1)**(N1+N2)+1)*np.round((-1+0j)**(F2-mF2+F1-J2+J1+I+0.5),0)*\
298
+ ((2*F1+1)*(2*F2+1))**0.5*((2*N1+1)*(2*N2+1)*(2*J1+1)*(2*J2+1))**0.5*\
299
+ wigner_6j(J2,F2,I,F1,J1,1)*wigner_3j(F2,1,F1,-mF2,M,mF1)*wigner_3j(J1,0.5,N1,-0.5,0.5,0)*\
300
+ wigner_3j(J2,0.5,N2,-0.5,0.5,0)*wigner_3j(J2,1,J1,-0.5,0,0.5)) -\
301
+ consts['alpha_0']*(((-1)**(N1+N2)+1)*np.round((-1+0j)**(F2-mF2+F1-J2+J1+I+0.5),0)*\
302
+ ((2*F1+1)*(2*F2+1))**0.5*((2*N1+1)*(2*N2+1)*(2*J1+1)*(2*J2+1))**0.5*\
303
+ wigner_6j(J2,F2,I,F1,J1,0)*wigner_3j(F2,0,F1,-mF2,M,mF1)*wigner_3j(J1,0.5,N1,-0.5,0.5,0)*\
304
+ wigner_3j(J2,0.5,N2,-0.5,0.5,0)*wigner_3j(J2,0,J1,-0.5,0,0.5))
305
+ i+=1
306
+ i=0
307
+ j+=1
308
+
309
+ #final check for NaN errors, mostly this is due to division by zero or
310
+ # multiplication by a small prefactor. it is safe to set these terms to 0
311
+ HAC[np.isnan(HAC)] =0
312
+
313
+ #return the matrix, in the full coupled basis.
314
+ return HAC
315
+
316
+ def H_B(Nmax,consts,I,S):
317
+ ''' Calculates the Zeeman shift for a molecule in a magnetic field, <N,J,F,mF| H_zee |N',J',F', mF'>
318
+
319
+ This function based on Jesus Aldegunde's FORTRAN 77 code and iterates
320
+ over N, J, F, mF, N', J', F', mF' to build a matrix and evaluate the elements.
321
+
322
+ Args:
323
+
324
+ Nmax (int) - maximum rotational quantum number to calculate
325
+ I (float): Nuclear spin. We only consider molecules where one consistuent
326
+ atom has no nuclear spin, so this is the non zero I of the other.
327
+ S (float): Electronic spin.
328
+ constst (dict): Dictionary of constants for the molecule to be calculated.
329
+
330
+ Returns:
331
+ H (np.ndarray): Hamiltonian in joules
332
+ '''
333
+ Ishape = int(2*I+1)
334
+ Sshape = int(2*S+1)
335
+ shape = np.sum(np.array([2*x+1 for x in range(0,Nmax+1)]))
336
+ HB = np.zeros((shape,shape),dtype= complex)
337
+ HB = (np.kron(HB,np.kron(np.identity(Ishape),
338
+ np.identity(Sshape))))
339
+
340
+ i=0
341
+ j=0
342
+ for N1 in range(0,Nmax+1):
343
+ for J1 in np.arange(np.abs(N1-S),(N1+S+1),1):
344
+ for F1 in np.arange(J1-I,(J1+I+1),1):
345
+ for mF1 in np.arange(-F1,(F1+1),1):
346
+ for N2 in range(0,Nmax+1):
347
+ for J2 in np.arange(np.abs(N2-S),(N2+S+1),1):
348
+ for F2 in np.arange(J2-I,(J2+I+1),1):
349
+ for mF2 in np.arange(-F2,+(F2+1),1):
350
+
351
+
352
+ HB[i,j]= (consts['MuS']+consts['MuL'])*((-1+0j)**(F2-mF2+2*J2+F1+N1+1)*KroneckerDelta(N1,N2)*\
353
+ (3/2*(2*F1+1)*(2*F2+1)*(2*J1+1)*(2*J2+1))**0.5*\
354
+ wigner_6j(J1,F1,0.5,F2,J2,1)*wigner_6j(0.5,J2,N1,J1,0.5,1)*\
355
+ wigner_3j(F2,1,F1,-mF2,0,mF1)) +\
356
+ consts['MuL']*(np.round((-1+0j)**(F1+F2-mF2+2*J2+N1+N2+0.5),0)*\
357
+ ((2*F1+1)*(2*F2+1)*(2*J1+1)*(2*J2+1)*(2*N1+1)*(2*N2+1))**0.5*\
358
+ wigner_3j(F2,1,F1,-mF2,0,mF1)*wigner_6j(J1,F1,I,F2,J2,1)*\
359
+ np.sum((-1)**(O)*\
360
+ wigner_3j(J2,1,J1,-O,0,O)*wigner_3j(J2,S,N2,O,-O,0)*\
361
+ wigner_3j(J1,S,N1,O,-O,0)*O for O in [-0.5,0.5]))+\
362
+ consts['MuR']*((-1+0j)**(F1-mF1+J1+J2+F1+N1+3)*\
363
+ ((2*F1+1)*(2*F2+1)*(2*J1+1)*(2*J2+1))**0.5*\
364
+ wigner_6j(J1,F1,0.5,F2,J2,1)*wigner_6j(N1,J1,0.5,J2,N2,1)*\
365
+ wigner_3j(F1,1,F2,-mF1,0,mF2)) +\
366
+ consts['MuN']*((-1+0j)**(2*F1-mF1+J1+3/2)*\
367
+ (3/2*(2*F1+1)*(2*F2+1))**0.5*KroneckerDelta(J1,J2)\
368
+ *wigner_6j(0.5,F2,J2,F1,0.5,1)*\
369
+ wigner_3j(F1,1,F2,-mF1,0,mF2))
370
+
371
+ i+=1
372
+ i=0
373
+ j+=1
374
+ #final check for NaN errors, mostly this is due to division by zero or
375
+ # multiplication by a small prefactor. it is safe to set these terms to 0
376
+ HB[np.isnan(HB)] =0
377
+
378
+ #return the matrix, in the full coupled basis.
379
+
380
+ return HB
381
+
382
+
383
+
384
+ # This is the main build function and one that the user will actually have to use.
385
+ def build(Nmax,constants,zeeman=False,Edc=False,ac=False):
386
+ ''' Return the hyperfine hamiltonian.
387
+
388
+ This function builds the hamiltonian matrices for evaluation so that
389
+ the user doesn't have to rebuild them every time and we can benefit from
390
+ np's ability to do distributed multiplication.
391
+
392
+ Args:
393
+ Nmax (int) - Maximum rotational level to include
394
+ Constants (Dictionary) - Dictionary of constants for the molecule to be calculated.
395
+ B,EDC,AC (Boolean) - Switches for turning off parts of the total Hamiltonian.
396
+ This can save significant time on calculations where DC
397
+ and AC fields are not required due to nested for loops
398
+
399
+ Returns:
400
+ H0,HB,HDC,HAC (np.ndarray): Each of the terms in the Hamiltonian.
401
+ '''
402
+
403
+ I = constants['I']
404
+ S = constants['S']
405
+
406
+
407
+ H0 = H_rot(Nmax, constants, I, S) + H_hf(Nmax,constants,I,S)
408
+ if zeeman:
409
+ HB = H_B(Nmax,constants,I,S)
410
+ else:
411
+ HB =0.
412
+ if Edc:
413
+ Hdc = H_dc(Nmax,constants,I,S)
414
+ else:
415
+ Hdc =0.
416
+ if ac:
417
+ Hac = H_ac(Nmax,constants,I,S)
418
+ else:
419
+ Hac =0.
420
+ return H0,HB,Hdc,Hac
421
+
422
+
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: dipolmol
3
+ Version: 1.1.1
4
+ Summary: DiPolMol: tools for calculating molecular dipole moments, polarisabilities, and Hamiltonians
5
+ License: MIT
6
+ Keywords: quantum physics,molecular physics,dipole moment,polarizability,AMO physics,CaF
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy>=1.23
19
+ Requires-Dist: scipy>=1.9
20
+ Requires-Dist: sympy>=1.11
21
+ Requires-Dist: matplotlib>=3.6
22
+
23
+ DiPolMol-Py
24
+ ===========
25
+ A Python package to calculate the rotational and hyperfine structure of doublet-Sigma molecules (e.g., CaF, BaF, SrF) in the presence of external fields.
26
+
27
+ DiPolMol-Py is licensed under a BSD 3 clause license, a copy can be found `See the [LICENSE](LICENSE) file'.
28
+ If you use our work for academic purposes you can cite us using:
29
+
30
+ B.Humphreys *et al.* DiPolMol-Py: A Python package for calculations for $^{2}{\Sigma}$ ground-state molecules (https://arxiv.org/pdf/2503.21663).
31
+
32
+ Installation
33
+ ----------
34
+
35
+ run: pip install dipolmol
36
+
37
+ This installs the latest stable release and all required dependencies.
38
+
39
+ Package structure
40
+ -------------
41
+ **Programme Files:**
42
+
43
+ *Hamiltonian* – used to build the required Hamiltonian using matrix representation, including the field-free Hamiltonian and in the presence of magnetic, dc electric and off-resonant light fields.
44
+
45
+ *Calculate* – uses the eigenstates and eigenenergies found from diagonalising the Hamiltonian to run various calculations. Can be used to identify quantum numbers of eigenstates, calculate transition dipole moments and polarisabilities.
46
+
47
+ *Constants* – includes all known constants for CaF, BaF and SrF.
48
+
49
+ **Examples:**
50
+
51
+ There are three example files for calculating the energy structure in the presence of a magnetic, off-resonant light and electric field (*example_Bfield, example_ac_Efield, example_dc_Efield*).
52
+
53
+ There are two files to calculate the electric and magnetic moments of states (*example_electric_moment, example_magnetic_moment*).
54
+
55
+ We provide *example_polarisability* to calculate the polarisability for a given wavelength and polarisation of light.
56
+
57
+ Finally, *example_tdm* can be used to calculate the transition dipole moment.
58
+
59
+ Example
60
+ -------
61
+ .. code-block:: python
62
+
63
+ import numpy as np
64
+ import dipolmol.hamiltonian as hamiltonian
65
+ import dipolmol.calculate as calc
66
+ from dipolmol.constants import SrF
67
+
68
+ Nmax=4 #Identify the maximum N
69
+ H0,H_B,H_dc,H_ac
70
+ = hamiltonian.build
71
+ (Nmax,SrF,zeeman=True,Edc=False
72
+ ,Eac=False)
73
+
74
+ B = np.linspace(0,100,5000)*1e-4 #Tesla
75
+
76
+ H = H_0[..., None] + H_B[..., None]*B
77
+ H = H.transpose(2,0,1)
78
+
79
+ energies, states, label_list =
80
+ calc.solve(H, Nmax, SrF,label=True, B)
81
+
82
+ Resulting plot of above code
83
+
84
+ .. image:: Images/zeeman_SrF_plot.png
85
+ :width: 400
86
+ :alt: Resulting plot of above example
87
+
88
+ For more examples of usage, see the ``./Examples`` module.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/dipolmol/__init__.py
4
+ src/dipolmol/calculate.py
5
+ src/dipolmol/constants.py
6
+ src/dipolmol/hamiltonian.py
7
+ src/dipolmol.egg-info/PKG-INFO
8
+ src/dipolmol.egg-info/SOURCES.txt
9
+ src/dipolmol.egg-info/dependency_links.txt
10
+ src/dipolmol.egg-info/requires.txt
11
+ src/dipolmol.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ numpy>=1.23
2
+ scipy>=1.9
3
+ sympy>=1.11
4
+ matplotlib>=3.6
@@ -0,0 +1 @@
1
+ dipolmol