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.
Files changed (35) hide show
  1. owlspec-0.1/PKG-INFO +53 -0
  2. owlspec-0.1/README.md +38 -0
  3. owlspec-0.1/owlspec/__init__.py +11 -0
  4. owlspec-0.1/owlspec/emission_line.py +158 -0
  5. owlspec-0.1/owlspec/emitter/__init__.py +3 -0
  6. owlspec-0.1/owlspec/emitter/level.py +139 -0
  7. owlspec-0.1/owlspec/emitter/transition.py +108 -0
  8. owlspec-0.1/owlspec/nist_levels/__init__.py +26 -0
  9. owlspec-0.1/owlspec/nist_levels/core.py +151 -0
  10. owlspec-0.1/owlspec/nist_levels/tests/__init__.py +0 -0
  11. owlspec-0.1/owlspec/nist_levels/tests/setup_package.py +9 -0
  12. owlspec-0.1/owlspec/nist_levels/tests/test_nist.py +57 -0
  13. owlspec-0.1/owlspec/nist_levels/tests/test_nist_remote.py +32 -0
  14. owlspec-0.1/owlspec/spectrometer/__init__.py +6 -0
  15. owlspec-0.1/owlspec/spectrometer/avantes.py +31 -0
  16. owlspec-0.1/owlspec/spectrometer/basic.py +46 -0
  17. owlspec-0.1/owlspec/spectrometer/newport.py +92 -0
  18. owlspec-0.1/owlspec/spectrometer/pgs.py +113 -0
  19. owlspec-0.1/owlspec/spectrometer/triax.py +60 -0
  20. owlspec-0.1/owlspec/spectrum.py +270 -0
  21. owlspec-0.1/owlspec/stark/__init__.py +2 -0
  22. owlspec-0.1/owlspec/stark/gigosos_he_loader.py +227 -0
  23. owlspec-0.1/owlspec/stark/gigosos_loader.py +202 -0
  24. owlspec-0.1/owlspec/stark/griem.py +100 -0
  25. owlspec-0.1/owlspec/stark/stark.py +88 -0
  26. owlspec-0.1/owlspec/util.py +179 -0
  27. owlspec-0.1/owlspec/vdW/__init__.py +2 -0
  28. owlspec-0.1/owlspec/vdW/vdW.py +134 -0
  29. owlspec-0.1/owlspec.egg-info/PKG-INFO +53 -0
  30. owlspec-0.1/owlspec.egg-info/SOURCES.txt +33 -0
  31. owlspec-0.1/owlspec.egg-info/dependency_links.txt +1 -0
  32. owlspec-0.1/owlspec.egg-info/requires.txt +5 -0
  33. owlspec-0.1/owlspec.egg-info/top_level.txt +1 -0
  34. owlspec-0.1/setup.cfg +4 -0
  35. 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,3 @@
1
+ #!/usr/bin/python
2
+ from .transition import *
3
+ from .level import *
@@ -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