Highly Charged Ion (HCI) Lines from NIST ASD
Motivation
We often find ourselves hard coding line center positions into mass,
which is prone to errors and can be tedious when there are many lines of interest to insert.
In addition, the line positions would need to be manually updated for any changes in established results.
In the case of highly charged ions, such as those produced in an electron beam ion trap (EBIT),
there is a vast number of potential lines coming from almost any charge state of almost any element.
Luckily, these lines are well documented through the NIST Atomic Spectral Database (ASD).
Here, we have parsed a NIST ASD SQL dump and converted it into an easily Python readable pickle file.
The hci_lines.py
module implements the NIST_ASD
class,
which loads that pickle file and contains useful functions for working with the ASD data.
It also automatically adds in some of the more common HCI lines that we commonly use in our EBIT data analyses.
Exploring the methods of class NIST_ASD
The class NIST_ASD
can be initialized without arguments if the user wants to use the default ASD pickle file.
This file is located at mass/calibration/nist_asd.pickle.
A custom pickle file can be used by passing in the pickleFilename
argument during initialization.
The methods of the NIST_ASD
class are described below:
Usage examples
Next, we will demonstrate usage of these methods with the example of Ne,
a commonly injected gas at the NIST EBIT.
# mkdocs: render
import mass2
import mass2.calibration.hci_lines
import numpy as numpy
import pylab as plt
test_asd = mass2.calibration.hci_lines.NIST_ASD()
availableElements = test_asd.getAvailableElements()
assert 'Ne' in availableElements
availableNeCharges = test_asd.getAvailableSpectralCharges(element='Ne')
assert 10 in availableNeCharges
subsetNe10Levels = test_asd.getAvailableLevels(element='Ne', spectralCharge=10, maxLevels=6, getUncertainty=False)
assert '2p 2P* J=1/2' in list(subsetNe10Levels.keys())
exampleNeLevel = test_asd.getSingleLevel(element='Ne', spectralCharge=10, conf='2p', term='2P*', JVal='1/2', getUncertainty=False)
print(availableElements[:10])
print(availableNeCharges)
for k, v in subsetNe10Levels.items():
subsetNe10Levels[k] = round(v, 1)
print(subsetNe10Levels)
print(f'{exampleNeLevel:.1f}')
[np.str_('Sn'), np.str_('Cu'), np.str_('Na'), np.str_('As'), np.str_('Zn'), np.str_('Ne'), np.str_('Ge'), np.str_('Ga'), np.str_('Rb'), np.str_('Se')]
[9, 1, 2, 3, 4, 5, 6, 7, 8, 10]
{'1s 2S J=1/2': 0.0, '2p 2P* J=1/2': 1021.5, '2s 2S J=1/2': 1021.5, '2p 2P* J=3/2': 1022.0, '3p 2P* J=1/2': 1210.8, '3s 2S J=1/2': 1210.8}
1021.5
Functions for generating SpectralLine
objects from ASD data
The module also contains some functions outside of the NIST_ASD
class that are useful for integration with MASS.
First, the add_hci_line
function which, takes arguments that are relevant in HCI work, including as
element
, spectr_ch
, energies
, widths
, and ratios
.
The function calls mass2.calibration.fluorescence_lines.addline
, generates a line name with the given parameters,
and populates the various fields.
As an example, let us create a H-like Be line. Here, we assume a lorentzian width of 0.1 eV.
# mkdocs: render
test_element = 'Be'
test_charge = 4
test_conf = '2p'
test_term = '2P*'
test_JVal = '3/2'
test_level = f'{test_conf} {test_term} J={test_JVal}'
test_energy = test_asd.getSingleLevel(
element=test_element, spectralCharge=test_charge,
conf=test_conf, term=test_term, JVal=test_JVal, getUncertainty=False)
test_line = mass2.calibration.hci_lines.add_hci_line(element=test_element, spectr_ch=test_charge,
line_identifier=test_level, energies=[test_energy], widths=[0.1], ratios=[1.0])
assert test_line.peak_energy == test_energy
print(mass2.spectra[f'{test_element}{test_charge} {test_conf} {test_term} J={test_JVal}'])
print(f'{test_line.peak_energy:.1f}')
SpectralLine: Be4 2p 2P* J=3/2
163.3
The name format for grabbing the line from mass2.spectra
is shown above.
The transition is uniquely specified by the element, charge, configuration, term, and J value.
Below, we show what this line looks like assuming a zero-width Gaussian component.
# mkdocs: render
test_line.plot()
The module contains two other functions which are used to easily generate some lines from levels that are commonly observed at the NIST EBIT.
These functions are add_H_like_lines_from_asd
and add_He_like_lines_from_asd
.
As the names imply, these functions add H- and He-like lines to mass using the data in the ASD pickle.
These functions require the asd
and element
arguments and also contain the optional maxLevels argument,
which works similarly as the argument in the class methods.
The module also automatically adds H- and He-like lines for the most commonly used elements,
which includes 'N', 'O', 'Ne', and 'Ar'.
Below, we check that common elements are being added as spectralLine objects
and then add some of the lower order H- and He-like Ga lines.
# mkdocs: render
print([mass2.spectra['O7 1s.2p 1P* J=1'], round(mass2.spectra['O7 1s.2p 1P* J=1'].peak_energy,1)])
test_element = 'Ga'
HLikeGaLines = mass2.calibration.hci_lines.add_H_like_lines_from_asd(asd=test_asd, element=test_element, maxLevels=6)
HeLikeGaLines = mass2.calibration.hci_lines.add_He_like_lines_from_asd(asd=test_asd, element=test_element, maxLevels=7)
print([[iLine, round(iLine.peak_energy, 1)] for iLine in HLikeGaLines])
print([[iLine, round(iLine.peak_energy, 1)] for iLine in HeLikeGaLines])
[SpectralLine: Ne10 2p 2P* J=3/2, np.float64(1022.0)]
[SpectralLine: O7 1s.2p 1P* J=1, np.float64(574.0)]
[[SpectralLine: Ga31 2p 2P* J=1/2, np.float64(9917.0)], [SpectralLine: Ga31 2s 2S J=1/2, np.float64(9918.0)], [SpectralLine: Ga31 2p 2P* J=3/2, np.float64(9960.3)], [SpectralLine: Ga31 3p 2P* J=1/2, np.float64(11767.7)], [SpectralLine: Ga31 3s 2S J=1/2, np.float64(11768.0)], [SpectralLine: Ga31 3d 2D J=3/2, np.float64(11780.5)]]
[[SpectralLine: Ga30 1s.2s 3S J=1, np.float64(9535.6)], [SpectralLine: Ga30 1s.2p 3P* J=0, np.float64(9571.8)], [SpectralLine: Ga30 1s.2p 3P* J=1, np.float64(9574.4)], [SpectralLine: Ga30 1s.2s 1S J=0, np.float64(9574.6)], [SpectralLine: Ga30 1s.2p 3P* J=2, np.float64(9607.4)], [SpectralLine: Ga30 1s.2p 1P* J=1, np.float64(9628.2)], [SpectralLine: Ga30 1s.3s 3S J=1, np.float64(11304.6)]]
HCI lines and models docstring info
hci_lines.py
Uses pickle file containing NIST ASD levels data to generate some commonly used HCI lines in mass.
Meant to be a replacement for _highly_charged_ion_lines.py, which hard codes in line parameters.
The pickle file can be gzip-compressed, provided the compressed filename ends with ".gz".
February 2020
Paul Szypryt
NIST_ASD
Class for working with a pickled atomic spectra database
Source code in mass2/calibration/hci_lines.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 | class NIST_ASD:
"""Class for working with a pickled atomic spectra database"""
def __init__(self, pickleFilename: str | None = None):
"""Loads ASD pickle file (optionally gzipped)
Parameters
----------
pickleFilename : str | None, optional
ASD pickle file name, as str, or if none then `mass2.calibration.hci_lines.DEFAULT_PICKLE_PATH` (default None)
"""
if pickleFilename is None:
pickleFilename = os.path.join(os.path.split(__file__)[0], str(DEFAULT_PICKLE_PATH))
if pickleFilename.endswith(".gz"):
with gzip.GzipFile(pickleFilename, "rb") as handle:
self.NIST_ASD_Dict = pickle.load(handle)
else:
with open(pickleFilename, "rb") as handle:
self.NIST_ASD_Dict = pickle.load(handle)
def getAvailableElements(self):
"""Returns a list of all available elements from the ASD pickle file"""
return list(self.NIST_ASD_Dict.keys())
def getAvailableSpectralCharges(self, element: str) -> list[int]:
"""For a given element, returns a list of all available charge states from the ASD pickle file
Parameters
----------
element : str
atomic symbol of element, e.g. 'Ne'
Returns
-------
list[int]
Available charge states
"""
return list(self.NIST_ASD_Dict[element].keys())
def getAvailableLevels(
self,
element: str,
spectralCharge: int,
requiredConf: str | None = None,
requiredTerm: str | None = None,
requiredJVal: str | None = None,
maxLevels: int | None = None,
units: str = "eV",
getUncertainty: bool = True,
) -> dict:
"""For a given element and spectral charge state, return a dict of all known levels from the ASD pickle file
Parameters
----------
element : str
Elemental atomic symbol, e.g. 'Ne'
spectralCharge : int
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
requiredConf : str | None, optional
if not None, limits results to those with `conf == requiredConf`, by default None
requiredTerm : str | None, optional
if not None, limits results to those with `term == requiredTerm`, by default None
requiredJVal : str | None, optional
if not None, limits results to those with `a == requiredJVal`, by default None
maxLevels : int | None, optional
the maximum number of levels (sorted by energy) to return, by default None
units : str, optional
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
getUncertainty : bool, optional
whether to return uncertain values, by default True
Returns
-------
dict
A dictionary of energy level strings to energy levels.
"""
if units not in {"eV", "cm-1"}:
raise ValueError("Unit type not supported, please use eV or cm-1")
spectralCharge = int(spectralCharge)
levelsDict: dict = {}
numLevels = 0
for iLevel in list(self.NIST_ASD_Dict[element][spectralCharge].keys()):
try:
# Check to see if we reached maximum number of levels to return
if maxLevels is not None:
if numLevels == maxLevels:
return levelsDict
# If required, check to see if level matches search conf, term, JVal
includeTerm = False
includeJVal = False
conf, term, j_str = iLevel.split()
JVal = j_str.split("=")[1]
includeConf = (requiredConf is None) or conf == requiredConf
includeTerm = (requiredTerm is None) or term == requiredTerm
includeJVal = (requiredJVal is None) or JVal == requiredJVal
# Include levels that match, in either cm-1 or eV
if includeConf and includeTerm and includeJVal:
numLevels += 1
if units == "cm-1":
if getUncertainty:
levelsDict[iLevel] = self.NIST_ASD_Dict[element][spectralCharge][iLevel]
else:
levelsDict[iLevel] = self.NIST_ASD_Dict[element][spectralCharge][iLevel][0]
elif units == "eV":
if getUncertainty:
levelsDict[iLevel] = [
iValue * INVCM_TO_EV for iValue in self.NIST_ASD_Dict[element][spectralCharge][iLevel]
]
else:
levelsDict[iLevel] = INVCM_TO_EV * self.NIST_ASD_Dict[element][spectralCharge][iLevel][0]
except ValueError:
f"Warning: cannot parse level: {iLevel}"
return levelsDict
def getSingleLevel(
self, element: str, spectralCharge: int, conf: str, term: str, JVal: str, units: str = "eV", getUncertainty: bool = True
) -> float:
"""Return the level data for a fully defined element, charge state, conf, term, and JVal.
Parameters
----------
element : str
atomic symbol of element, e.g. 'Ne'
spectralCharge : int
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
conf : str
nuclear configuration, e.g. '2p'
term : str
nuclear term, e.g. '2P*'
JVal : str
total angular momentum J, e.g. '3/2'
units : str, optional
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
getUncertainty : bool, optional
includes uncertainties in list of levels, by default True
Returns
-------
float
_description_
"""
levelString = f"{conf} {term} J={JVal}"
if units == "cm-1":
if getUncertainty:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString]
else:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString][0]
elif units == "eV":
if getUncertainty:
levelEnergy = [iValue * INVCM_TO_EV for iValue in self.NIST_ASD_Dict[element][spectralCharge][levelString]]
else:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString][0] * INVCM_TO_EV
else:
raise ValueError("Unit type not supported, please use eV or cm-1")
return levelEnergy
|
__init__(pickleFilename=None)
Loads ASD pickle file (optionally gzipped)
Parameters: |
-
pickleFilename
(str | None , default:
None
)
–
ASD pickle file name, as str, or if none then mass2.calibration.hci_lines.DEFAULT_PICKLE_PATH (default None)
|
Source code in mass2/calibration/hci_lines.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | def __init__(self, pickleFilename: str | None = None):
"""Loads ASD pickle file (optionally gzipped)
Parameters
----------
pickleFilename : str | None, optional
ASD pickle file name, as str, or if none then `mass2.calibration.hci_lines.DEFAULT_PICKLE_PATH` (default None)
"""
if pickleFilename is None:
pickleFilename = os.path.join(os.path.split(__file__)[0], str(DEFAULT_PICKLE_PATH))
if pickleFilename.endswith(".gz"):
with gzip.GzipFile(pickleFilename, "rb") as handle:
self.NIST_ASD_Dict = pickle.load(handle)
else:
with open(pickleFilename, "rb") as handle:
self.NIST_ASD_Dict = pickle.load(handle)
|
getAvailableElements()
Returns a list of all available elements from the ASD pickle file
Source code in mass2/calibration/hci_lines.py
| def getAvailableElements(self):
"""Returns a list of all available elements from the ASD pickle file"""
return list(self.NIST_ASD_Dict.keys())
|
getAvailableLevels(element, spectralCharge, requiredConf=None, requiredTerm=None, requiredJVal=None, maxLevels=None, units='eV', getUncertainty=True)
For a given element and spectral charge state, return a dict of all known levels from the ASD pickle file
Parameters: |
-
element
(str )
–
Elemental atomic symbol, e.g. 'Ne'
-
spectralCharge
(int )
–
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
-
requiredConf
(str | None , default:
None
)
–
if not None, limits results to those with conf == requiredConf , by default None
-
requiredTerm
(str | None , default:
None
)
–
if not None, limits results to those with term == requiredTerm , by default None
-
requiredJVal
(str | None , default:
None
)
–
if not None, limits results to those with a == requiredJVal , by default None
-
maxLevels
(int | None , default:
None
)
–
the maximum number of levels (sorted by energy) to return, by default None
-
units
(str , default:
'eV'
)
–
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
-
getUncertainty
(bool , default:
True
)
–
whether to return uncertain values, by default True
|
Returns: |
-
dict
–
A dictionary of energy level strings to energy levels.
|
Source code in mass2/calibration/hci_lines.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 | def getAvailableLevels(
self,
element: str,
spectralCharge: int,
requiredConf: str | None = None,
requiredTerm: str | None = None,
requiredJVal: str | None = None,
maxLevels: int | None = None,
units: str = "eV",
getUncertainty: bool = True,
) -> dict:
"""For a given element and spectral charge state, return a dict of all known levels from the ASD pickle file
Parameters
----------
element : str
Elemental atomic symbol, e.g. 'Ne'
spectralCharge : int
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
requiredConf : str | None, optional
if not None, limits results to those with `conf == requiredConf`, by default None
requiredTerm : str | None, optional
if not None, limits results to those with `term == requiredTerm`, by default None
requiredJVal : str | None, optional
if not None, limits results to those with `a == requiredJVal`, by default None
maxLevels : int | None, optional
the maximum number of levels (sorted by energy) to return, by default None
units : str, optional
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
getUncertainty : bool, optional
whether to return uncertain values, by default True
Returns
-------
dict
A dictionary of energy level strings to energy levels.
"""
if units not in {"eV", "cm-1"}:
raise ValueError("Unit type not supported, please use eV or cm-1")
spectralCharge = int(spectralCharge)
levelsDict: dict = {}
numLevels = 0
for iLevel in list(self.NIST_ASD_Dict[element][spectralCharge].keys()):
try:
# Check to see if we reached maximum number of levels to return
if maxLevels is not None:
if numLevels == maxLevels:
return levelsDict
# If required, check to see if level matches search conf, term, JVal
includeTerm = False
includeJVal = False
conf, term, j_str = iLevel.split()
JVal = j_str.split("=")[1]
includeConf = (requiredConf is None) or conf == requiredConf
includeTerm = (requiredTerm is None) or term == requiredTerm
includeJVal = (requiredJVal is None) or JVal == requiredJVal
# Include levels that match, in either cm-1 or eV
if includeConf and includeTerm and includeJVal:
numLevels += 1
if units == "cm-1":
if getUncertainty:
levelsDict[iLevel] = self.NIST_ASD_Dict[element][spectralCharge][iLevel]
else:
levelsDict[iLevel] = self.NIST_ASD_Dict[element][spectralCharge][iLevel][0]
elif units == "eV":
if getUncertainty:
levelsDict[iLevel] = [
iValue * INVCM_TO_EV for iValue in self.NIST_ASD_Dict[element][spectralCharge][iLevel]
]
else:
levelsDict[iLevel] = INVCM_TO_EV * self.NIST_ASD_Dict[element][spectralCharge][iLevel][0]
except ValueError:
f"Warning: cannot parse level: {iLevel}"
return levelsDict
|
getAvailableSpectralCharges(element)
For a given element, returns a list of all available charge states from the ASD pickle file
Parameters: |
-
element
(str )
–
atomic symbol of element, e.g. 'Ne'
|
Source code in mass2/calibration/hci_lines.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 | def getAvailableSpectralCharges(self, element: str) -> list[int]:
"""For a given element, returns a list of all available charge states from the ASD pickle file
Parameters
----------
element : str
atomic symbol of element, e.g. 'Ne'
Returns
-------
list[int]
Available charge states
"""
return list(self.NIST_ASD_Dict[element].keys())
|
getSingleLevel(element, spectralCharge, conf, term, JVal, units='eV', getUncertainty=True)
Return the level data for a fully defined element, charge state, conf, term, and JVal.
Parameters: |
-
element
(str )
–
atomic symbol of element, e.g. 'Ne'
-
spectralCharge
(int )
–
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
-
conf
(str )
–
nuclear configuration, e.g. '2p'
-
term
(str )
–
-
JVal
(str )
–
total angular momentum J, e.g. '3/2'
-
units
(str , default:
'eV'
)
–
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
-
getUncertainty
(bool , default:
True
)
–
includes uncertainties in list of levels, by default True
|
Source code in mass2/calibration/hci_lines.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 | def getSingleLevel(
self, element: str, spectralCharge: int, conf: str, term: str, JVal: str, units: str = "eV", getUncertainty: bool = True
) -> float:
"""Return the level data for a fully defined element, charge state, conf, term, and JVal.
Parameters
----------
element : str
atomic symbol of element, e.g. 'Ne'
spectralCharge : int
spectral charge state, e.g. 1 for neutral atoms, 10 for H-like Ne
conf : str
nuclear configuration, e.g. '2p'
term : str
nuclear term, e.g. '2P*'
JVal : str
total angular momentum J, e.g. '3/2'
units : str, optional
'cm-1' or 'eV' for returned line position. If 'eV', converts from database 'cm-1' values, by default "eV"
getUncertainty : bool, optional
includes uncertainties in list of levels, by default True
Returns
-------
float
_description_
"""
levelString = f"{conf} {term} J={JVal}"
if units == "cm-1":
if getUncertainty:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString]
else:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString][0]
elif units == "eV":
if getUncertainty:
levelEnergy = [iValue * INVCM_TO_EV for iValue in self.NIST_ASD_Dict[element][spectralCharge][levelString]]
else:
levelEnergy = self.NIST_ASD_Dict[element][spectralCharge][levelString][0] * INVCM_TO_EV
else:
raise ValueError("Unit type not supported, please use eV or cm-1")
return levelEnergy
|
hci_models.py
Some useful methods for initializing GenericLineModel and CompositeMLEModel objects applied to HCI lines.
June 2020
Paul Szypryt
add_bg_model(generic_model, vary_slope=False)
Adds a LinearBackgroundModel to a generic lmfit model
Parameters: |
-
generic_model
(GenericLineModel )
–
object to which to add a linear background model
-
vary_slope
(bool , default:
False
)
–
allows a varying linear slope rather than just constant value, by default False
|
Returns: |
-
GenericLineModel
–
The input model, with background componets added
|
Source code in mass2/calibration/hci_models.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 | def add_bg_model(generic_model: GenericLineModel, vary_slope: bool = False) -> GenericLineModel:
"""Adds a LinearBackgroundModel to a generic lmfit model
Parameters
----------
generic_model : GenericLineModel
object to which to add a linear background model
vary_slope : bool, optional
allows a varying linear slope rather than just constant value, by default False
Returns
-------
GenericLineModel
The input model, with background componets added
"""
# composite_name = generic_model._name
# bg_prefix = f"{composite_name}_".replace(" ", "_").replace("J=", "").replace("/", "_").replace("*", "").replace(".", "")
raise NotImplementedError("No LinearBackgroundModel still exists in mass2")
|
initialize_HLike_2P_model(element, conf, has_linear_background=False, has_tails=False, vary_amp_ratio=False)
Initializes H-like 2P models consisting of J=1/2 and J=3/2 lines
Parameters: |
-
element
(str )
–
atomic symbol as str, e.g. 'Ne' or 'Ar'
-
conf
(str )
–
nuclear configuration as str, e.g. '2p' or '3p'
-
has_linear_background
(bool , default:
False
)
–
include a single linear background on top of the 2 Lorentzians, by default False
-
has_tails
(bool , default:
False
)
–
include low energy tail in the model, by default False
-
vary_amp_ratio
(bool , default:
False
)
–
allow the ratio of the J=3/2 to J=1/2 states to vary away from 2, by default False
|
Source code in mass2/calibration/hci_models.py
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 | def initialize_HLike_2P_model(
element: str, conf: str, has_linear_background: bool = False, has_tails: bool = False, vary_amp_ratio: bool = False
) -> GenericLineModel:
"""Initializes H-like 2P models consisting of J=1/2 and J=3/2 lines
Parameters
----------
element : str
atomic symbol as str, e.g. 'Ne' or 'Ar'
conf : str
nuclear configuration as str, e.g. '2p' or '3p'
has_linear_background : bool, optional
include a single linear background on top of the 2 Lorentzians, by default False
has_tails : bool, optional
include low energy tail in the model, by default False
vary_amp_ratio : bool, optional
allow the ratio of the J=3/2 to J=1/2 states to vary away from 2, by default False
Returns
-------
GenericLineModel
The new composite line
"""
# Set up line names and lmfit prefixes
charge = int(xraydb.atomic_number(element))
line_name_1_2 = f"{element}{charge} {conf} 2P* J=1/2"
line_name_3_2 = f"{element}{charge} {conf} 2P* J=3/2"
prefix_1_2 = f"{line_name_1_2}_".replace(" ", "_").replace("J=", "").replace("/", "_").replace("*", "").replace(".", "")
prefix_3_2 = f"{line_name_3_2}_".replace(" ", "_").replace("J=", "").replace("/", "_").replace("*", "").replace(".", "")
# Initialize individual lines and models
line_1_2 = spectra[line_name_1_2]
line_3_2 = spectra[line_name_3_2]
model_1_2 = line_1_2.model(has_linear_background=False, has_tails=has_tails, prefix=prefix_1_2)
model_3_2 = line_3_2.model(has_linear_background=False, has_tails=has_tails, prefix=prefix_3_2)
# Initialize composite model and set addition H-like constraints
composite_name = f"{element}{charge} {conf}"
composite_model = initialize_hci_composite_model(
composite_name=composite_name,
individual_models=[model_1_2, model_3_2],
has_linear_background=has_linear_background,
peak_component_name=line_name_3_2,
)
amp_ratio_param_name = f"{element}{charge}_{conf}_amp_ratio"
composite_model.set_param_hint(name=amp_ratio_param_name, value=0.5, min=0.0, vary=vary_amp_ratio)
composite_model.set_param_hint(f"{prefix_1_2}integral", expr=f"{prefix_3_2}integral * {amp_ratio_param_name}")
return composite_model
|
initialize_HeLike_complex_model(element, has_linear_background=False, has_tails=False, additional_line_names=[])
Initializes 1s2s,2p He-like complexes for a given element.
By default, uses only the 1s.2s 3S J=1, 1s.2p 3P J=1, and 1s.2p 1P J=1 lines.
Parameters: |
-
element
(str )
–
atomic symbol as str, e.g. 'Ne' or 'Ar'
-
has_linear_background
(bool , default:
False
)
–
include a single linear background on top of the Lorentzian models, by default False
-
has_tails
(bool , default:
False
)
–
include low energy tail in the model, by default False
-
additional_line_names
(list , default:
[]
)
–
additional line names to include in model, e.g. low level Li/Be-like features, by default []
|
Returns: |
-
GenericLineModel
–
A model of the given HCI complex.
|
Source code in mass2/calibration/hci_models.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 | def initialize_HeLike_complex_model(
element: str, has_linear_background: bool = False, has_tails: bool = False, additional_line_names: list = []
) -> GenericLineModel:
"""Initializes 1s2s,2p He-like complexes for a given element.
By default, uses only the 1s.2s 3S J=1, 1s.2p 3P* J=1, and 1s.2p 1P* J=1 lines.
Parameters
----------
element : str
atomic symbol as str, e.g. 'Ne' or 'Ar'
has_linear_background : bool, optional
include a single linear background on top of the Lorentzian models, by default False
has_tails : bool, optional
include low energy tail in the model, by default False
additional_line_names : list, optional
additional line names to include in model, e.g. low level Li/Be-like features, by default []
Returns
-------
GenericLineModel
A model of the given HCI complex.
"""
# Set up line names
charge = int(xraydb.atomic_number(element) - 1)
line_name_1s2s_3S = f"{element}{charge} 1s.2s 3S J=1"
line_name_1s2p_3P = f"{element}{charge} 1s.2p 3P* J=1"
line_name_1s2p_1P = f"{element}{charge} 1s.2p 1P* J=1"
line_names = np.hstack([[line_name_1s2s_3S, line_name_1s2p_3P, line_name_1s2p_1P], additional_line_names])
# Set up lines and models based on line_names
# individual_lines = [spectra[i_line_name]() for i_line_name in line_names]
individual_models = [
initialize_hci_line_model(i_line_name, has_linear_background=False, has_tails=has_tails) for i_line_name in line_names
]
# Set up composite model
composite_name = f"{element}{charge} 1s2s_2p Complex"
composite_model = initialize_hci_composite_model(
composite_name=composite_name,
individual_models=individual_models,
has_linear_background=has_linear_background,
peak_component_name=line_name_1s2p_1P,
)
return composite_model
|
initialize_hci_composite_model(composite_name, individual_models, has_linear_background=False, peak_component_name=None)
Initializes composite lmfit model from the sum of input models
Parameters: |
-
composite_name
(str )
–
name given to composite line model
-
individual_models
(list[GenericLineModel] )
–
Models to sum into a composite
-
has_linear_background
(bool , default:
False
)
–
include a single linear background on top of group of lorentzians, by default False
-
peak_component_name
(str | None , default:
None
)
–
designate a component to be a peak for energy, all expressions are referenced to this component, by default None
|
Source code in mass2/calibration/hci_models.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 | def initialize_hci_composite_model(
composite_name: str,
individual_models: list[GenericLineModel],
has_linear_background: bool = False,
peak_component_name: str | None = None,
) -> GenericLineModel:
"""Initializes composite lmfit model from the sum of input models
Parameters
----------
composite_name : str
name given to composite line model
individual_models : list[GenericLineModel]
Models to sum into a composite
has_linear_background : bool, optional
include a single linear background on top of group of lorentzians, by default False
peak_component_name : str | None, optional
designate a component to be a peak for energy, all expressions are referenced to this component, by default None
Returns
-------
GenericLineModel
The new composite line
"""
composite_model: GenericLineModel = np.sum(individual_models)
composite_model.name = composite_name
if has_linear_background:
composite_model = add_bg_model(composite_model)
# Workaround for energy calibration using composite models, pick 1st GenericLineModel component
line_model_components = [
i_comp for i_comp in composite_model.components if isinstance(i_comp, mass2.calibration.line_models.GenericLineModel)
]
if peak_component_name is None:
peak_component_name = line_model_components[0]._name
peak_component_index = [i_comp._name for i_comp in line_model_components].index(peak_component_name)
peak_component = line_model_components[peak_component_index]
composite_model.peak_prefix = peak_component.prefix
composite_model.peak_energy = peak_component.spect.peak_energy
# Set up some constraints relative to peak_component
num_line_components = len(line_model_components)
line_component_prefixes = [iComp.prefix for iComp in line_model_components]
line_component_energies = [iComp.spect.peak_energy for iComp in line_model_components]
for i in np.arange(num_line_components):
if i != peak_component_index:
# Single fwhm across model
composite_model.set_param_hint(f"{line_component_prefixes[i]}fwhm", expr=f"{composite_model.peak_prefix}fwhm")
# Single dph_de across model
composite_model.set_param_hint(f"{line_component_prefixes[i]}dph_de", expr=f"{composite_model.peak_prefix}dph_de")
# Fixed energy separation based on database values
separation = line_component_energies[i] - composite_model.peak_energy
hint = f"({separation} * {composite_model.peak_prefix}dph_de) + {composite_model.peak_prefix}peak_ph"
composite_model.set_param_hint(f"{line_component_prefixes[i]}peak_ph", expr=hint)
composite_model.shortname = composite_name
return composite_model
|
initialize_hci_line_model(line_name, has_linear_background=False, has_tails=False)
Initializes a single lorentzian HCI line model. Reformats line_name to create a lmfit valid prefix.
Parameters: |
-
line_name
(str )
–
name of line to use in mass2.spectra
-
has_linear_background
(bool , default:
False
)
–
include linear background in the model, by default False
-
has_tails
(bool , default:
False
)
–
include low-energy tail in the model, by default False
|
Source code in mass2/calibration/hci_models.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 | def initialize_hci_line_model(line_name: str, has_linear_background: bool = False, has_tails: bool = False) -> GenericLineModel:
"""Initializes a single lorentzian HCI line model. Reformats line_name to create a lmfit valid prefix.
Parameters
----------
line_name : str
name of line to use in mass2.spectra
has_linear_background : bool, optional
include linear background in the model, by default False
has_tails : bool, optional
include low-energy tail in the model, by default False
Returns
-------
GenericLineModel
New HCI line.
"""
line = spectra[line_name]
prefix = f"{line_name}_".replace(" ", "_").replace("J=", "").replace("/", "_").replace("*", "").replace(".", "")
line_model = line.model(has_linear_background=has_linear_background, has_tails=has_tails, prefix=prefix)
line_model.shortname = line_name
return line_model
|
models(has_linear_background=False, has_tails=False, vary_Hlike_amp_ratio=False, additional_Helike_complex_lines=[])
Generates some commonly used HCI line models that can be used for energy calibration, etc.
Parameters: |
-
has_linear_background
(bool , default:
False
)
–
include a single linear background on top of the 2 Lorentzians, by default False
-
has_tails
(bool , default:
False
)
–
nclude low-energy tail in the model, by default False
-
vary_Hlike_amp_ratio
(bool , default:
False
)
–
allow the ratio of the J=3/2 to J=1/2 H-like states to vary, by default False
-
additional_Helike_complex_lines
(list , default:
[]
)
–
additional line names to include inHe-like complex model, e.g. low level Li/Be-like features, by default []
|
Returns: |
-
_type_
–
Dictionary of mass2.calibration.fluorescence_lines.SpectralLine objects, containing commonly used HCI lines.
|
Source code in mass2/calibration/hci_models.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 | def models(
has_linear_background: bool = False,
has_tails: bool = False,
vary_Hlike_amp_ratio: bool = False,
additional_Helike_complex_lines=[],
) -> dict:
"""Generates some commonly used HCI line models that can be used for energy calibration, etc.
Parameters
----------
has_linear_background : bool, optional
include a single linear background on top of the 2 Lorentzians, by default False
has_tails : bool, optional
nclude low-energy tail in the model, by default False
vary_Hlike_amp_ratio : bool, optional
allow the ratio of the J=3/2 to J=1/2 H-like states to vary, by default False
additional_Helike_complex_lines : list, optional
additional line names to include inHe-like complex model, e.g. low level Li/Be-like features, by default []
Returns
-------
_type_
Dictionary of mass2.calibration.fluorescence_lines.SpectralLine objects, containing commonly used HCI lines.
"""
models_dict = {}
# Make some common H-like 2P* models
conf_Hlike_2P_dict = {}
conf_Hlike_2P_dict["N"] = ["3p", "4p", "5p"]
conf_Hlike_2P_dict["O"] = ["3p", "4p", "5p"]
conf_Hlike_2P_dict["Ne"] = ["2p", "3p", "4p", "5p"]
conf_Hlike_2P_dict["Ar"] = ["2p", "3p", "4p", "5p"]
for i_element in list(conf_Hlike_2P_dict.keys()):
for i_conf in conf_Hlike_2P_dict[i_element]:
Hlike_model = initialize_HLike_2P_model(
i_element,
i_conf,
has_linear_background=has_linear_background,
has_tails=has_tails,
vary_amp_ratio=vary_Hlike_amp_ratio,
)
models_dict[Hlike_model._name] = Hlike_model
# Make some common He-like 1s2s,2p complex and higher order 1p* models
# He-like lines
Helike_complex_elements = ["N", "O", "Ne", "Ar"]
for i_element in Helike_complex_elements:
Helike_model = initialize_HeLike_complex_model(
i_element,
has_linear_background=has_linear_background,
has_tails=has_tails,
additional_line_names=additional_Helike_complex_lines,
)
models_dict[Helike_model._name] = Helike_model
# 1s.np 1P* lines for n>=3
conf_Helike_1P_dict = {}
conf_Helike_1P_dict["N"] = ["1s.4p", "1s.5p"]
conf_Helike_1P_dict["O"] = ["1s.4p", "1s.5p"]
conf_Helike_1P_dict["Ne"] = ["1s.3p", "1s.4p", "1s.5p"]
conf_Helike_1P_dict["Ar"] = ["1s.3p", "1s.4p", "1s.5p"]
for i_element in list(conf_Helike_1P_dict.keys()):
i_charge = int(xraydb.atomic_number(i_element) - 1)
for i_conf in conf_Helike_1P_dict[i_element]:
Helike_line_name = f"{i_element}{i_charge} {i_conf} 1P* J=1"
Helike_model = initialize_hci_line_model(
Helike_line_name, has_linear_background=has_linear_background, has_tails=has_tails
)
models_dict[Helike_model._name] = Helike_model
# Some more complicated cases
# 500 eV region of H-/He-like N
N6_1s3p_model = initialize_hci_line_model("N6 1s.3p 1P* J=1", has_linear_background=False, has_tails=has_tails)
N7_2p_model = initialize_HLike_2P_model(
"N", "2p", has_linear_background=False, has_tails=has_tails, vary_amp_ratio=vary_Hlike_amp_ratio
)
N_500eV_model = initialize_hci_composite_model(
"N 500eV Region",
[N6_1s3p_model, N7_2p_model],
has_linear_background=has_linear_background,
peak_component_name="N7 2p 2P* J=3/2",
)
models_dict[N_500eV_model._name] = N_500eV_model
# 660 eV region of H-/He-like O
O8_2p_model = initialize_HLike_2P_model(
"O", "2p", has_linear_background=False, has_tails=has_tails, vary_amp_ratio=vary_Hlike_amp_ratio
)
O7_1s3p_model = initialize_hci_line_model("O7 1s.3p 1P* J=1", has_linear_background=False, has_tails=has_tails)
O_660eV_model = initialize_hci_composite_model(
"O 660eV Region",
[O8_2p_model, O7_1s3p_model],
has_linear_background=has_linear_background,
peak_component_name="O8 2p 2P* J=3/2",
)
models_dict[O_660eV_model._name] = O_660eV_model
return models_dict
|