owlspec 0.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.
- owlspec-0.1/PKG-INFO +53 -0
- owlspec-0.1/README.md +38 -0
- owlspec-0.1/owlspec/__init__.py +11 -0
- owlspec-0.1/owlspec/emission_line.py +158 -0
- owlspec-0.1/owlspec/emitter/__init__.py +3 -0
- owlspec-0.1/owlspec/emitter/level.py +139 -0
- owlspec-0.1/owlspec/emitter/transition.py +108 -0
- owlspec-0.1/owlspec/nist_levels/__init__.py +26 -0
- owlspec-0.1/owlspec/nist_levels/core.py +151 -0
- owlspec-0.1/owlspec/nist_levels/tests/__init__.py +0 -0
- owlspec-0.1/owlspec/nist_levels/tests/setup_package.py +9 -0
- owlspec-0.1/owlspec/nist_levels/tests/test_nist.py +57 -0
- owlspec-0.1/owlspec/nist_levels/tests/test_nist_remote.py +32 -0
- owlspec-0.1/owlspec/spectrometer/__init__.py +6 -0
- owlspec-0.1/owlspec/spectrometer/avantes.py +31 -0
- owlspec-0.1/owlspec/spectrometer/basic.py +46 -0
- owlspec-0.1/owlspec/spectrometer/newport.py +92 -0
- owlspec-0.1/owlspec/spectrometer/pgs.py +113 -0
- owlspec-0.1/owlspec/spectrometer/triax.py +60 -0
- owlspec-0.1/owlspec/spectrum.py +270 -0
- owlspec-0.1/owlspec/stark/__init__.py +2 -0
- owlspec-0.1/owlspec/stark/gigosos_he_loader.py +227 -0
- owlspec-0.1/owlspec/stark/gigosos_loader.py +202 -0
- owlspec-0.1/owlspec/stark/griem.py +100 -0
- owlspec-0.1/owlspec/stark/stark.py +88 -0
- owlspec-0.1/owlspec/util.py +179 -0
- owlspec-0.1/owlspec/vdW/__init__.py +2 -0
- owlspec-0.1/owlspec/vdW/vdW.py +134 -0
- owlspec-0.1/owlspec.egg-info/PKG-INFO +53 -0
- owlspec-0.1/owlspec.egg-info/SOURCES.txt +33 -0
- owlspec-0.1/owlspec.egg-info/dependency_links.txt +1 -0
- owlspec-0.1/owlspec.egg-info/requires.txt +5 -0
- owlspec-0.1/owlspec.egg-info/top_level.txt +1 -0
- owlspec-0.1/setup.cfg +4 -0
- owlspec-0.1/setup.py +27 -0
owlspec-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: owlspec
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Library for optical emission spectroscopy of low-temperature plasmas.
|
|
5
|
+
Home-page: https://github.com/mimurrayy/owl
|
|
6
|
+
Author: Julian Held
|
|
7
|
+
Author-email: julian.held@umn.edu
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# owl
|
|
17
|
+
|
|
18
|
+
Library to calculate optical emission line shapes.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
The library has currently three main classes:
|
|
22
|
+
|
|
23
|
+
- spectrometer
|
|
24
|
+
- transition
|
|
25
|
+
- emission_line
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
```python
|
|
29
|
+
#!/bin/python3
|
|
30
|
+
import matplotlib.pyplot as plt
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
import sys
|
|
34
|
+
import os.path
|
|
35
|
+
home = os.path.expanduser('~')
|
|
36
|
+
sys.path.append(home + '/.local/lib/')
|
|
37
|
+
import owl
|
|
38
|
+
|
|
39
|
+
cw = central_wavelength = 453.37
|
|
40
|
+
x = np.linspace(cw-0.1, cw+0.1, 1000)
|
|
41
|
+
|
|
42
|
+
transition1 = owl.emitter.transition("Ti I", 453.324)
|
|
43
|
+
transition2 = owl.emitter.transition("Ti II", 453.396)
|
|
44
|
+
|
|
45
|
+
line1 = owl.emission_line(transition1,453.337,B=0.5)
|
|
46
|
+
line2 = owl.emission_line(transition2,453.410,B=0.5)
|
|
47
|
+
|
|
48
|
+
y1 = line1.get_profile(x, A=1, T=5000)
|
|
49
|
+
y2 = line2.get_profile(x, A=1, T=5000)
|
|
50
|
+
|
|
51
|
+
plt.plot(x,y1)
|
|
52
|
+
plt.plot(x,y2)
|
|
53
|
+
```
|
owlspec-0.1/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# owl
|
|
2
|
+
|
|
3
|
+
Library to calculate optical emission line shapes.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
The library has currently three main classes:
|
|
7
|
+
|
|
8
|
+
- spectrometer
|
|
9
|
+
- transition
|
|
10
|
+
- emission_line
|
|
11
|
+
|
|
12
|
+
## Example
|
|
13
|
+
```python
|
|
14
|
+
#!/bin/python3
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import os.path
|
|
20
|
+
home = os.path.expanduser('~')
|
|
21
|
+
sys.path.append(home + '/.local/lib/')
|
|
22
|
+
import owl
|
|
23
|
+
|
|
24
|
+
cw = central_wavelength = 453.37
|
|
25
|
+
x = np.linspace(cw-0.1, cw+0.1, 1000)
|
|
26
|
+
|
|
27
|
+
transition1 = owl.emitter.transition("Ti I", 453.324)
|
|
28
|
+
transition2 = owl.emitter.transition("Ti II", 453.396)
|
|
29
|
+
|
|
30
|
+
line1 = owl.emission_line(transition1,453.337,B=0.5)
|
|
31
|
+
line2 = owl.emission_line(transition2,453.410,B=0.5)
|
|
32
|
+
|
|
33
|
+
y1 = line1.get_profile(x, A=1, T=5000)
|
|
34
|
+
y2 = line2.get_profile(x, A=1, T=5000)
|
|
35
|
+
|
|
36
|
+
plt.plot(x,y1)
|
|
37
|
+
plt.plot(x,y2)
|
|
38
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
from . import emitter
|
|
3
|
+
from . import spectrometer
|
|
4
|
+
from .util import *
|
|
5
|
+
from .emission_line import *
|
|
6
|
+
from .spectrum import *
|
|
7
|
+
from .stark import gigosos_loader, gigosos_he_loader
|
|
8
|
+
|
|
9
|
+
def download_stark_tables(redownload=False):
|
|
10
|
+
gigosos_loader.download_profiles(redownload)
|
|
11
|
+
gigosos_he_loader.download_profiles(redownload)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/bin/python3
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.signal import fftconvolve as convolve
|
|
4
|
+
from scipy import constants as const
|
|
5
|
+
from scipy import interpolate
|
|
6
|
+
#from . import emitter
|
|
7
|
+
from .util import zeeman, parse_spectroscopic_name, doppler_thompson,\
|
|
8
|
+
doppler_maxwell, interpol, gauss_function
|
|
9
|
+
from . import stark
|
|
10
|
+
from . import vdW
|
|
11
|
+
import mendeleev
|
|
12
|
+
|
|
13
|
+
class emission_line():
|
|
14
|
+
def __init__(self, transition, wl, spectrometer = None,
|
|
15
|
+
T = None, Eb = None, gamma = None, B = None, ne = None, Te = None,
|
|
16
|
+
side=False, N = None, pert = None):
|
|
17
|
+
""" T in K, Eb,Te in eV, ne in m^-3, N in m^-3"""
|
|
18
|
+
self.wl = wl
|
|
19
|
+
self.transition = transition
|
|
20
|
+
particle = transition.particle
|
|
21
|
+
self.m = particle.m
|
|
22
|
+
self.Ei = particle.Ei
|
|
23
|
+
self.T = T
|
|
24
|
+
self.Eb = Eb
|
|
25
|
+
self.gamma = gamma
|
|
26
|
+
self.B = B
|
|
27
|
+
self.ne = ne
|
|
28
|
+
self.Te = Te
|
|
29
|
+
self.N = N
|
|
30
|
+
self.pert = pert
|
|
31
|
+
self.spectrometer = spectrometer
|
|
32
|
+
self.side = side # symmetric version of Thompson?
|
|
33
|
+
self.last_profiles = []
|
|
34
|
+
|
|
35
|
+
if pert:
|
|
36
|
+
self.pert_name, self.pert_charge = parse_spectroscopic_name(pert)
|
|
37
|
+
self.pert = mendeleev.element(self.pert_name)
|
|
38
|
+
self.pert.charge = self.pert_charge
|
|
39
|
+
self.pert.m = self.pert.mass
|
|
40
|
+
self.pert.Ei = self.pert.ionenergies[1]
|
|
41
|
+
if T:
|
|
42
|
+
self.pert.T = T
|
|
43
|
+
elif Te:
|
|
44
|
+
self.pert.T = Te*const.eV/const.k
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_profile(self, x, A = 1,
|
|
49
|
+
shift = False, wl=None, T = None, Eb = None, gamma = None, B = None,
|
|
50
|
+
N = None, ne = None, Te = None, pert = None):
|
|
51
|
+
|
|
52
|
+
if wl == None and self.wl:
|
|
53
|
+
wl = self.wl
|
|
54
|
+
if T == None and self.T:
|
|
55
|
+
T = self.T
|
|
56
|
+
if Eb == None and self.Eb:
|
|
57
|
+
Eb = self.Eb
|
|
58
|
+
if gamma == None and self.gamma:
|
|
59
|
+
gamma = self.gamma
|
|
60
|
+
if B == None and self.B:
|
|
61
|
+
B = self.B
|
|
62
|
+
if ne == None and self.ne:
|
|
63
|
+
ne = self.ne
|
|
64
|
+
if Te == None and self.Te:
|
|
65
|
+
Te = self.Te
|
|
66
|
+
if N == None and self.N:
|
|
67
|
+
N = self.N
|
|
68
|
+
if pert == None and self.pert:
|
|
69
|
+
pert = self.pert
|
|
70
|
+
|
|
71
|
+
elif pert:
|
|
72
|
+
self.pert_name, self.pert_charge = parse_spectroscopic_name(pert)
|
|
73
|
+
self.pert = mendeleev.element(self.pert_name)
|
|
74
|
+
self.pert.charge = self.pert_charge
|
|
75
|
+
self.pert.m = self.pert.mass
|
|
76
|
+
self.pert.Ei = self.pert.ionenergies[1]
|
|
77
|
+
if T:
|
|
78
|
+
self.pert.T = T
|
|
79
|
+
elif Te:
|
|
80
|
+
self.pert.T = Te*const.eV/const.k
|
|
81
|
+
pert = self.pert
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
self.last_profiles = []
|
|
85
|
+
orig_x = x
|
|
86
|
+
s = 0 # lineshift in nm
|
|
87
|
+
try:
|
|
88
|
+
x = self.spectrometer.fine_x
|
|
89
|
+
if len(orig_x)<1024:
|
|
90
|
+
x = x[x>orig_x[0]]
|
|
91
|
+
x = x[x<orig_x[-1]]
|
|
92
|
+
except:
|
|
93
|
+
x = x
|
|
94
|
+
|
|
95
|
+
resolution = abs((x[-1]-x[0])/(len(x)-1))
|
|
96
|
+
|
|
97
|
+
middle_wl = x[int(len(x)/2)-1]
|
|
98
|
+
components = []
|
|
99
|
+
|
|
100
|
+
if T:
|
|
101
|
+
if Eb and gamma:
|
|
102
|
+
thompson = doppler_thompson(x, middle_wl, Eb, self.m, self.side)
|
|
103
|
+
maxwell = doppler_maxwell(x, middle_wl, T, self.m)
|
|
104
|
+
doppler = gamma*maxwell + (1-gamma)*thompson
|
|
105
|
+
self.last_profiles.append(gamma*maxwell)
|
|
106
|
+
self.last_profiles.append((1-gamma)*thompson)
|
|
107
|
+
self.last_profiles.append(doppler)
|
|
108
|
+
components.append(doppler)
|
|
109
|
+
else:
|
|
110
|
+
maxwell = doppler_maxwell(x, middle_wl, T, self.m)
|
|
111
|
+
self.last_profiles.append(maxwell)
|
|
112
|
+
components.append(maxwell)
|
|
113
|
+
if Eb and not T:
|
|
114
|
+
thompson = doppler_thompson(x, middle_wl, Eb, self.m, self.side)
|
|
115
|
+
self.last_profiles.append(thompson)
|
|
116
|
+
components.append(thompson)
|
|
117
|
+
if B:
|
|
118
|
+
t = self.transition
|
|
119
|
+
zeeman_pattern = zeeman(x, middle_wl, B, t.upperJ, t.lowerJ, t.upperG, t.lowerG)/resolution
|
|
120
|
+
self.last_profiles.append(zeeman_pattern)
|
|
121
|
+
components.append(zeeman_pattern)
|
|
122
|
+
if ne:
|
|
123
|
+
this_stark = stark.stark(self.transition)
|
|
124
|
+
stark_profile = this_stark.get_profile(x, ne, Te, pert)
|
|
125
|
+
self.last_profiles.append(stark_profile)
|
|
126
|
+
components.append(stark_profile)
|
|
127
|
+
if N:
|
|
128
|
+
this_vdW = vdW.vdW(self.transition, pert=pert)
|
|
129
|
+
vdW_profile = this_vdW.get_profile(x, T, N)
|
|
130
|
+
self.last_profiles.append(vdW_profile)
|
|
131
|
+
components.append(vdW_profile)
|
|
132
|
+
if shift:
|
|
133
|
+
s = s + this_vdW.get_shift(x, N, T)
|
|
134
|
+
|
|
135
|
+
# We let the instrumental profile determine center position.
|
|
136
|
+
# ALL other components are shifted to the middle!
|
|
137
|
+
if self.spectrometer:
|
|
138
|
+
instrumental_profile = self.spectrometer.instrument_function(x, wl+s, self.transition)
|
|
139
|
+
instrumental_profile = instrumental_profile/resolution/1024
|
|
140
|
+
else:
|
|
141
|
+
instrumental_profile = gauss_function(x, wl+s, x[0]-x[1])
|
|
142
|
+
|
|
143
|
+
profile = instrumental_profile
|
|
144
|
+
for component in components:
|
|
145
|
+
profile = convolve(profile, component, mode='same') * resolution
|
|
146
|
+
y = A*profile
|
|
147
|
+
self.last_profiles.append(y)
|
|
148
|
+
lowres_y = interpol(x, y, orig_x)
|
|
149
|
+
self.last_profiles.append(lowres_y)
|
|
150
|
+
# normalize intensity to A
|
|
151
|
+
y = A*lowres_y/np.sum(lowres_y)/resolution
|
|
152
|
+
return y
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_instrumental_profile(self, x, wl=None):
|
|
156
|
+
if wl == None and self.wl:
|
|
157
|
+
wl = self.wl
|
|
158
|
+
return self.spectrometer.instrument_function(x, wl, self.transition)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from ..util import parse_spectroscopic_name
|
|
6
|
+
import numpy as np
|
|
7
|
+
import mendeleev
|
|
8
|
+
from astroquery.nist import Nist
|
|
9
|
+
from ..nist_levels import NistLevels
|
|
10
|
+
import astropy.units as u
|
|
11
|
+
|
|
12
|
+
class level():
|
|
13
|
+
def __init__(self, emitter_name, energy, debug=False):
|
|
14
|
+
self.name, self.charge = parse_spectroscopic_name(emitter_name)
|
|
15
|
+
self.spec_name = emitter_name
|
|
16
|
+
self.emitter = self.particle = mendeleev.element(self.name)
|
|
17
|
+
self.emitter.charge = self.charge
|
|
18
|
+
self.emitter.m = self.emitter.mass
|
|
19
|
+
self.emitter.Ei = self.emitter.ionenergies[1]
|
|
20
|
+
self.charge = self.emitter.charge
|
|
21
|
+
|
|
22
|
+
nist_levels = NistLevels.query(linename=emitter_name)
|
|
23
|
+
|
|
24
|
+
# clean up the energy column from NIST levels (e.g. remove '[')
|
|
25
|
+
non_decimal = re.compile(r'[^\d.]+')
|
|
26
|
+
level_energies = []
|
|
27
|
+
|
|
28
|
+
for entry in nist_levels['Level (eV)']:
|
|
29
|
+
this_entry = non_decimal.sub('', str(entry))
|
|
30
|
+
try:
|
|
31
|
+
this_entry = float(this_entry)
|
|
32
|
+
except:
|
|
33
|
+
this_entry = np.nan
|
|
34
|
+
level_energies.append(this_entry)
|
|
35
|
+
level_energies = np.array(level_energies, dtype=float)
|
|
36
|
+
|
|
37
|
+
# select upper and lower energy levels
|
|
38
|
+
level_idx = np.argmin((np.nan_to_num(level_energies)-energy)**2)
|
|
39
|
+
row = nist_levels[level_idx]
|
|
40
|
+
|
|
41
|
+
self.E = level_energies[level_idx] # Energy in eV
|
|
42
|
+
try:
|
|
43
|
+
self.J = float(eval(row['J'])) # Angular momentum, can be 5/2 or so...
|
|
44
|
+
except:
|
|
45
|
+
self.J = np.nan
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
conf_end = row['Configuration'].split('.')
|
|
49
|
+
if "(" in conf_end[-1]:
|
|
50
|
+
conf_end.pop()
|
|
51
|
+
self.l = int(self.l_name_to_num(conf_end[-1][1]))
|
|
52
|
+
except:
|
|
53
|
+
self.l = np.nan
|
|
54
|
+
|
|
55
|
+
self.g = float(nist_levels[level_idx]['g']) # statistical weight
|
|
56
|
+
self.conf = row['Configuration'] # configuration and term string
|
|
57
|
+
|
|
58
|
+
if 'Landé' in nist_levels.keys():
|
|
59
|
+
self.G = nist_levels[level_idx]['Landé'] #Lande g factor
|
|
60
|
+
else:
|
|
61
|
+
self.G = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def l_name_to_num(self, name):
|
|
66
|
+
chars = ["s","p","d","f","g","h","i","j"]
|
|
67
|
+
num = chars.index(name)
|
|
68
|
+
return num
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_transitions(self, kind="all", debug=False):
|
|
72
|
+
"""Return the transition objects that belong to the energy level
|
|
73
|
+
Type is a string and may be "from", "to" or "all", to select transitions
|
|
74
|
+
from the level, to the level or both."""
|
|
75
|
+
from .transition import transition # needed to avoid circular import
|
|
76
|
+
|
|
77
|
+
# clean up the wl column from NIST levels (e.g. remove '[') below
|
|
78
|
+
non_decimal = re.compile(r'[^\d.]+')
|
|
79
|
+
|
|
80
|
+
# Load data tables from NIST (+- 1 nm around requested wl)
|
|
81
|
+
nist_lines = Nist.query(1*u.nm, 99999*u.nm,
|
|
82
|
+
linename=self.spec_name, wavelength_type='vac+air')
|
|
83
|
+
|
|
84
|
+
transitions_to = []
|
|
85
|
+
transitions_from = []
|
|
86
|
+
transitions_all = []
|
|
87
|
+
|
|
88
|
+
for entry in nist_lines:
|
|
89
|
+
if not np.ma.is_masked(entry['Ei Ek']) \
|
|
90
|
+
and not (np.ma.is_masked(entry['Observed']) and np.ma.is_masked(entry['Ritz']) ):
|
|
91
|
+
|
|
92
|
+
if '-' in entry['Ei Ek']:
|
|
93
|
+
upperE = float(entry['Ei Ek'].split('-')[1]) # Energy in eV
|
|
94
|
+
lowerE = float(entry['Ei Ek'].split('-')[0])
|
|
95
|
+
if upperE == self.E:
|
|
96
|
+
try:
|
|
97
|
+
wl = float(entry['Observed'])
|
|
98
|
+
except:
|
|
99
|
+
try:
|
|
100
|
+
wl = float(non_decimal.sub('', str(entry['Ritz'])))
|
|
101
|
+
except:
|
|
102
|
+
break
|
|
103
|
+
if not np.isnan(wl):
|
|
104
|
+
this_transition = transition(self.spec_name, wl, wl_type="either")
|
|
105
|
+
transitions_from.append(this_transition)
|
|
106
|
+
transitions_all.append(this_transition)
|
|
107
|
+
|
|
108
|
+
if lowerE == self.E:
|
|
109
|
+
try:
|
|
110
|
+
wl = float(entry['Observed'])
|
|
111
|
+
if np.isnan(wl):
|
|
112
|
+
wl = float(non_decimal.sub('', str(entry['Ritz'])))
|
|
113
|
+
except:
|
|
114
|
+
try:
|
|
115
|
+
wl = float(non_decimal.sub('', str(entry['Ritz'])))
|
|
116
|
+
except:
|
|
117
|
+
break
|
|
118
|
+
if not np.isnan(wl):
|
|
119
|
+
this_transition = transition(self.spec_name, wl, wl_type="either")
|
|
120
|
+
transitions_to.append(this_transition)
|
|
121
|
+
transitions_all.append(this_transition)
|
|
122
|
+
|
|
123
|
+
if kind=="all":
|
|
124
|
+
return transitions_all
|
|
125
|
+
if kind=="from":
|
|
126
|
+
return transitions_from
|
|
127
|
+
if kind=="to":
|
|
128
|
+
return transitions_to
|
|
129
|
+
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_lifetime(self, debug=False):
|
|
134
|
+
Aiks = []
|
|
135
|
+
for transition in self.get_transitions(kind="from", debug=debug):
|
|
136
|
+
if transition.Aik:
|
|
137
|
+
Aiks.append(transition.Aik)
|
|
138
|
+
sum_Aik = np.sum(Aiks)
|
|
139
|
+
return 1/sum_Aik
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import numpy as np
|
|
5
|
+
import re
|
|
6
|
+
from ..util import parse_spectroscopic_name, get_spectroscopic_name
|
|
7
|
+
from .level import level
|
|
8
|
+
import mendeleev
|
|
9
|
+
from astroquery.nist import Nist
|
|
10
|
+
import astropy.units as u
|
|
11
|
+
|
|
12
|
+
class transition():
|
|
13
|
+
def __init__(self, emitter_name, wavelength, wl_type="Observed", debug=False):
|
|
14
|
+
"""Return the closest transition for the specified emitter to the
|
|
15
|
+
specified wavelength.
|
|
16
|
+
emitter_name in spectroscopic notation, e.g. O I or Ar II
|
|
17
|
+
wl_type can be "Observed" or "Ritz" or "either"
|
|
18
|
+
|
|
19
|
+
Returns a transition object with entries: upperE, lowerE, upperl,
|
|
20
|
+
lowerl, upperg, lowerg, Aik, wl and the upper
|
|
21
|
+
and lower levels of the transition: upper_level/lower_level."""
|
|
22
|
+
|
|
23
|
+
self.name, self.charge = parse_spectroscopic_name(emitter_name)
|
|
24
|
+
self.spec_name = get_spectroscopic_name(self.name, self.charge)
|
|
25
|
+
self.emitter = self.particle = mendeleev.element(self.name)
|
|
26
|
+
self.emitter.charge = self.charge
|
|
27
|
+
self.emitter.m = self.emitter.mass
|
|
28
|
+
self.emitter.Ei = self.emitter.ionenergies[1]
|
|
29
|
+
self.charge = self.emitter.charge
|
|
30
|
+
# clean up the wl column from NIST levels (e.g. remove '[') below
|
|
31
|
+
non_decimal = re.compile(r'[^\d.]+')
|
|
32
|
+
|
|
33
|
+
# Load data tables from NIST (+- 1 nm around requested wl)
|
|
34
|
+
nist_lines = Nist.query((wavelength-1)*u.nm, (wavelength+1)*u.nm,
|
|
35
|
+
linename=self.spec_name, wavelength_type='vac+air')
|
|
36
|
+
|
|
37
|
+
# select closest line
|
|
38
|
+
if wl_type == "Observed":
|
|
39
|
+
line_idx = np.argmin((nist_lines['Observed']-wavelength)**2)
|
|
40
|
+
self.wl = nist_lines['Observed'][line_idx]
|
|
41
|
+
|
|
42
|
+
if wl_type == "Ritz":
|
|
43
|
+
wls = []
|
|
44
|
+
for this_wl in nist_lines['Ritz']:
|
|
45
|
+
try:
|
|
46
|
+
wls.append(float(non_decimal.sub('', str(this_wl))))
|
|
47
|
+
except:
|
|
48
|
+
wls.append(0.0)
|
|
49
|
+
line_idx = np.argmin((wls-wavelength)**2)
|
|
50
|
+
self.wl = wls[line_idx]
|
|
51
|
+
|
|
52
|
+
if wl_type == "either":
|
|
53
|
+
wls = []
|
|
54
|
+
for this_wl in nist_lines['Ritz']:
|
|
55
|
+
try:
|
|
56
|
+
wls.append(float(non_decimal.sub('', str(this_wl))))
|
|
57
|
+
except:
|
|
58
|
+
wls.append(0.0)
|
|
59
|
+
this_wl = np.max((np.array(nist_lines['Observed'], dtype=float), wls), axis=0)
|
|
60
|
+
line_idx = np.argmin((this_wl-wavelength)**2)
|
|
61
|
+
self.wl = this_wl[line_idx]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
row = nist_lines[line_idx]
|
|
65
|
+
|
|
66
|
+
self.upperE = row['Ei Ek'].split('-')[1]
|
|
67
|
+
self.upperE = float(non_decimal.sub('', str(self.upperE)))
|
|
68
|
+
self.lowerE = row['Ei Ek'].split('-')[0]
|
|
69
|
+
self.lowerE = float(non_decimal.sub('', str(self.lowerE)))
|
|
70
|
+
# self.upperJ = row['Upper level'].split('|')[-1] # Angular momentum
|
|
71
|
+
# self.lowerJ = row['Lower level'].split('|')[-1]
|
|
72
|
+
|
|
73
|
+
upper_conf_end = row['Upper level'].split('|')[0].split('.')
|
|
74
|
+
if "(" in upper_conf_end[-1]:
|
|
75
|
+
upper_conf_end.pop()
|
|
76
|
+
lower_conf_end = row['Lower level'].split('|')[0].split('.')
|
|
77
|
+
if "(" in lower_conf_end[-1]:
|
|
78
|
+
lower_conf_end.pop()
|
|
79
|
+
try:
|
|
80
|
+
self.upperl = self.l_name_to_num(upper_conf_end[-1][1])
|
|
81
|
+
self.lowerl = self.l_name_to_num(lower_conf_end[-1][1])
|
|
82
|
+
except:
|
|
83
|
+
self.upperl = None
|
|
84
|
+
self.lowerl = None
|
|
85
|
+
|
|
86
|
+
self.Aik = row['Aki']
|
|
87
|
+
self.upperg = float(row['gi gk'].split('-')[1])
|
|
88
|
+
self.lowerg = float(row['gi gk'].split('-')[0])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
self.upper_level, self.lower_level = self.levels()
|
|
92
|
+
self.upper, self.lower = self.upper_level, self.lower_level
|
|
93
|
+
|
|
94
|
+
def l_name_to_num(self, name):
|
|
95
|
+
chars = ["s","p","d","f","g","h","i","j"]
|
|
96
|
+
num = chars.index(name)
|
|
97
|
+
return num
|
|
98
|
+
|
|
99
|
+
def levels(self):
|
|
100
|
+
upper_level = level(self.spec_name, self.upperE)
|
|
101
|
+
lower_level = level(self.spec_name, self.lowerE)
|
|
102
|
+
self.upperJ = upper_level.J
|
|
103
|
+
self.upperG = upper_level.G
|
|
104
|
+
self.lowerJ = lower_level.J
|
|
105
|
+
self.lowerG = lower_level.G
|
|
106
|
+
return upper_level, lower_level
|
|
107
|
+
|
|
108
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
|
|
2
|
+
"""
|
|
3
|
+
Fetches level information from the NIST Atomic Spectra Database.
|
|
4
|
+
"""
|
|
5
|
+
from astropy import config as _config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Conf(_config.ConfigNamespace):
|
|
9
|
+
"""
|
|
10
|
+
Configuration parameters for `astroquery.nist_levels`.
|
|
11
|
+
"""
|
|
12
|
+
server = _config.ConfigItem(
|
|
13
|
+
['https://physics.nist.gov/cgi-bin/ASD/energy1.pl'],
|
|
14
|
+
'Name of the NIST URL to query.')
|
|
15
|
+
timeout = _config.ConfigItem(
|
|
16
|
+
30,
|
|
17
|
+
'Time limit for connecting to NIST server.')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
conf = Conf()
|
|
21
|
+
|
|
22
|
+
from .core import NistLevels, NistLevelsClass
|
|
23
|
+
|
|
24
|
+
__all__ = ['NistLevels', 'NistLevelsClass',
|
|
25
|
+
'Conf', 'conf',
|
|
26
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import html
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import astropy.io.ascii as asciitable
|
|
8
|
+
|
|
9
|
+
from astroquery.query import BaseQuery
|
|
10
|
+
from astroquery.utils import async_to_sync, prepend_docstr_nosections
|
|
11
|
+
from . import conf
|
|
12
|
+
from astroquery.exceptions import TableParseError
|
|
13
|
+
|
|
14
|
+
__all__ = ['NistLevels', 'NistLevelsClass']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _strip_blanks(table):
|
|
18
|
+
"""
|
|
19
|
+
Remove blank lines from table (included for "human readability" but
|
|
20
|
+
useless to us...
|
|
21
|
+
returns a single string joined by \n newlines
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
table : str
|
|
26
|
+
table to strip as a string
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
single string joined by newlines.
|
|
31
|
+
"""
|
|
32
|
+
numbersletters = re.compile("[0-9A-Za-z]")
|
|
33
|
+
if isinstance(table, str):
|
|
34
|
+
table = table.split('\n')
|
|
35
|
+
table = [line for line in table if numbersletters.search(line)]
|
|
36
|
+
return "\n".join(table)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@async_to_sync
|
|
40
|
+
class NistLevelsClass(BaseQuery):
|
|
41
|
+
URL = conf.server
|
|
42
|
+
TIMEOUT = conf.timeout
|
|
43
|
+
unit_code = {'Angstrom': 0,
|
|
44
|
+
'nm': 1,
|
|
45
|
+
'um': 2}
|
|
46
|
+
energy_level_code = {'cm-1': 0, 'invcm': 0, 'cm': 0,
|
|
47
|
+
'ev': 1, 'eV': 1, 'EV': 1, 'electronvolt': 1,
|
|
48
|
+
'R': 2, 'Rydberg': 2, 'rydberg': 2}
|
|
49
|
+
|
|
50
|
+
def _args_to_payload(self, *args, **kwargs):
|
|
51
|
+
"""
|
|
52
|
+
Serves the same purpose as `~NistClass.query` but returns
|
|
53
|
+
the raw HTTP response rather than a `~astropy.table.Table` object.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
linename : str, optional
|
|
58
|
+
The spectrum to fetch. Defaults to "H I"
|
|
59
|
+
energy_level_unit : str, optional
|
|
60
|
+
The energy level units must be one of the following:
|
|
61
|
+
'R', 'Rydberg', 'rydberg', 'cm', 'cm-1', 'EV', 'eV',
|
|
62
|
+
'electronvolt', 'ev', 'invcm' Defaults to 'eV'.
|
|
63
|
+
get_query_payload : bool, optional
|
|
64
|
+
If true then returns the dictionary of query parameters, posted to
|
|
65
|
+
remote server. Defaults to `False`.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
request_payload : dict
|
|
70
|
+
The dictionary of parameters sent with the HTTP request
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
request_payload = {}
|
|
74
|
+
linename = kwargs["linename"]
|
|
75
|
+
request_payload["de"] = 0
|
|
76
|
+
request_payload["spectrum"] = linename
|
|
77
|
+
request_payload["submit"] = "Retrieve Data"
|
|
78
|
+
request_payload["units"] = NistLevels.energy_level_code[
|
|
79
|
+
kwargs["energy_level_unit"]]
|
|
80
|
+
request_payload["format"] = 1 # ascii
|
|
81
|
+
request_payload["output"] = 0 # entirely rather than pagewise
|
|
82
|
+
request_payload["page_size"] = 15
|
|
83
|
+
request_payload["multiplet_ordered"] = 0
|
|
84
|
+
request_payload["conf_out"] = "on"
|
|
85
|
+
request_payload["term_out"] = "on"
|
|
86
|
+
request_payload["level_out"] = "on"
|
|
87
|
+
request_payload["unc_out"] = "on"
|
|
88
|
+
request_payload["j_out"] = "on"
|
|
89
|
+
request_payload["g_out"] = "on"
|
|
90
|
+
request_payload["lande_out"] = "on"
|
|
91
|
+
request_payload["perc_out"] = "on"
|
|
92
|
+
request_payload["biblio"] = "on"
|
|
93
|
+
request_payload["splitting"] = 1
|
|
94
|
+
return request_payload
|
|
95
|
+
|
|
96
|
+
@prepend_docstr_nosections("\n" + _args_to_payload.__doc__)
|
|
97
|
+
def query_async(self, linename="H I", energy_level_unit='eV', get_query_payload=False):
|
|
98
|
+
"""
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
response : `requests.Response` object
|
|
102
|
+
The response of the HTTP request.
|
|
103
|
+
"""
|
|
104
|
+
request_payload = self._args_to_payload(
|
|
105
|
+
linename=linename,
|
|
106
|
+
energy_level_unit=energy_level_unit)
|
|
107
|
+
if get_query_payload:
|
|
108
|
+
return request_payload
|
|
109
|
+
|
|
110
|
+
response = self._request("GET", url=NistLevels.URL, params=request_payload,
|
|
111
|
+
timeout=NistLevels.TIMEOUT)
|
|
112
|
+
return response
|
|
113
|
+
|
|
114
|
+
def _parse_result(self, response, *, verbose=False):
|
|
115
|
+
"""
|
|
116
|
+
Parses the results from the HTTP response to `astropy.table.Table`.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
response : `requests.Response`
|
|
121
|
+
The HTTP response object
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
table : `astropy.table.Table`
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
pre_re = re.compile("<PRE>(.*)</PRE>", flags=re.DOTALL)
|
|
129
|
+
links_re = re.compile(r"<\/?a(.|\n)*?>")
|
|
130
|
+
content = str(response.text)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
pre = pre_re.findall(content)[0]
|
|
134
|
+
except IndexError:
|
|
135
|
+
raise Exception("Result did not contain a table")
|
|
136
|
+
try:
|
|
137
|
+
table = _strip_blanks(pre)
|
|
138
|
+
table = links_re.sub(r'\1', table)
|
|
139
|
+
table = html.unescape(table)
|
|
140
|
+
table = asciitable.read(table, Reader=asciitable.FixedWidth,
|
|
141
|
+
data_start=1, delimiter='|')
|
|
142
|
+
return table
|
|
143
|
+
except Exception as ex:
|
|
144
|
+
self.response = response
|
|
145
|
+
self.table_parse_error = ex
|
|
146
|
+
raise TableParseError("Failed to parse asciitable! The raw "
|
|
147
|
+
"response can be found in self.response, "
|
|
148
|
+
"and the error in self.table_parse_error.")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
NistLevels = NistLevelsClass()
|
|
File without changes
|