lightweaver 0.15.0__cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

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.

Potentially problematic release.


This version of lightweaver might be problematic. Click here for more details.

Files changed (69) hide show
  1. lightweaver/Data/AbundancesAsplund09.pickle +0 -0
  2. lightweaver/Data/AtomicMassesNames.pickle +0 -0
  3. lightweaver/Data/Barklem_dfdata.dat +41 -0
  4. lightweaver/Data/Barklem_pddata.dat +40 -0
  5. lightweaver/Data/Barklem_spdata.dat +46 -0
  6. lightweaver/Data/DefaultMolecules/C2.molecule +27 -0
  7. lightweaver/Data/DefaultMolecules/CH/CH_X-A.asc +46409 -0
  8. lightweaver/Data/DefaultMolecules/CH/CH_X-A_12.asc +28322 -0
  9. lightweaver/Data/DefaultMolecules/CH/CH_X-B.asc +4272 -0
  10. lightweaver/Data/DefaultMolecules/CH/CH_X-B_12.asc +2583 -0
  11. lightweaver/Data/DefaultMolecules/CH/CH_X-C.asc +20916 -0
  12. lightweaver/Data/DefaultMolecules/CH/CH_X-C_12.asc +13106 -0
  13. lightweaver/Data/DefaultMolecules/CH.molecule +35 -0
  14. lightweaver/Data/DefaultMolecules/CN.molecule +30 -0
  15. lightweaver/Data/DefaultMolecules/CO/vmax=3_Jmax=49_dv=1_26 +296 -0
  16. lightweaver/Data/DefaultMolecules/CO/vmax=9_Jmax=120_dv=1_26 +2162 -0
  17. lightweaver/Data/DefaultMolecules/CO.molecule +30 -0
  18. lightweaver/Data/DefaultMolecules/CO_NLTE.molecule +29 -0
  19. lightweaver/Data/DefaultMolecules/CaH.molecule +29 -0
  20. lightweaver/Data/DefaultMolecules/H2+.molecule +27 -0
  21. lightweaver/Data/DefaultMolecules/H2.molecule +27 -0
  22. lightweaver/Data/DefaultMolecules/H2O.molecule +27 -0
  23. lightweaver/Data/DefaultMolecules/HF.molecule +29 -0
  24. lightweaver/Data/DefaultMolecules/LiH.molecule +27 -0
  25. lightweaver/Data/DefaultMolecules/MgH.molecule +34 -0
  26. lightweaver/Data/DefaultMolecules/N2.molecule +28 -0
  27. lightweaver/Data/DefaultMolecules/NH.molecule +27 -0
  28. lightweaver/Data/DefaultMolecules/NO.molecule +27 -0
  29. lightweaver/Data/DefaultMolecules/O2.molecule +27 -0
  30. lightweaver/Data/DefaultMolecules/OH.molecule +27 -0
  31. lightweaver/Data/DefaultMolecules/SiO.molecule +26 -0
  32. lightweaver/Data/DefaultMolecules/TiO.molecule +30 -0
  33. lightweaver/Data/Quadratures.pickle +0 -0
  34. lightweaver/Data/pf_Kurucz.input +0 -0
  35. lightweaver/DefaultIterSchemes/.placeholder +0 -0
  36. lightweaver/DefaultIterSchemes/SimdImpl_AVX2FMA.cpython-312-x86_64-linux-gnu.so +0 -0
  37. lightweaver/DefaultIterSchemes/SimdImpl_AVX512.cpython-312-x86_64-linux-gnu.so +0 -0
  38. lightweaver/DefaultIterSchemes/SimdImpl_SSE2.cpython-312-x86_64-linux-gnu.so +0 -0
  39. lightweaver/LwCompiled.cpython-312-x86_64-linux-gnu.so +0 -0
  40. lightweaver/__init__.py +33 -0
  41. lightweaver/atmosphere.py +1640 -0
  42. lightweaver/atomic_model.py +852 -0
  43. lightweaver/atomic_set.py +1286 -0
  44. lightweaver/atomic_table.py +653 -0
  45. lightweaver/barklem.py +151 -0
  46. lightweaver/benchmark.py +113 -0
  47. lightweaver/broadening.py +605 -0
  48. lightweaver/collisional_rates.py +337 -0
  49. lightweaver/config.py +106 -0
  50. lightweaver/constants.py +22 -0
  51. lightweaver/crtaf.py +197 -0
  52. lightweaver/fal.py +440 -0
  53. lightweaver/iterate_ctx.py +241 -0
  54. lightweaver/iteration_update.py +134 -0
  55. lightweaver/libenkiTS.so +0 -0
  56. lightweaver/molecule.py +225 -0
  57. lightweaver/multi.py +113 -0
  58. lightweaver/nr_update.py +106 -0
  59. lightweaver/rh_atoms.py +19743 -0
  60. lightweaver/simd_management.py +42 -0
  61. lightweaver/utils.py +504 -0
  62. lightweaver/version.py +34 -0
  63. lightweaver/wittmann.py +1375 -0
  64. lightweaver/zeeman.py +157 -0
  65. lightweaver-0.15.0.dist-info/METADATA +81 -0
  66. lightweaver-0.15.0.dist-info/RECORD +69 -0
  67. lightweaver-0.15.0.dist-info/WHEEL +6 -0
  68. lightweaver-0.15.0.dist-info/licenses/LICENSE +21 -0
  69. lightweaver-0.15.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,337 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import TYPE_CHECKING, Sequence, cast
3
+
4
+ import astropy.units as u
5
+ import numpy as np
6
+ from numba import njit
7
+ from scipy.special import exp1
8
+ from weno4 import weno4
9
+
10
+ import lightweaver.constants as Const
11
+ from .utils import sequence_repr
12
+
13
+ if TYPE_CHECKING:
14
+ from .atmosphere import Atmosphere
15
+ from .atomic_model import AtomicModel
16
+ from .atomic_set import SpeciesStateTable
17
+
18
+ @dataclass
19
+ class CollisionalRates:
20
+ '''
21
+ Base class for all CollisionalRates.
22
+ '''
23
+ j: int
24
+ i: int
25
+ atom: 'AtomicModel' = field(init=False)
26
+
27
+ def __repr__(self):
28
+ s = 'CollisionalRates(j=%d, i=%d)' % (self.j, self.i)
29
+ return s
30
+
31
+ def setup(self, atom):
32
+ pass
33
+
34
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
35
+ Cmat: np.ndarray):
36
+ raise NotImplementedError
37
+
38
+ def __eq__(self, other: object) -> bool:
39
+ if other is self:
40
+ return True
41
+
42
+ return repr(self) == repr(other)
43
+
44
+
45
+ @dataclass(eq=False)
46
+ class TemperatureInterpolationRates(CollisionalRates):
47
+ '''
48
+ Base class for rates defined by interpolating a coefficient on a
49
+ temperature grid.
50
+ '''
51
+ temperature: Sequence[float]
52
+ rates: Sequence[float]
53
+ def __repr__(self):
54
+ s = '%s(j=%d, i=%d, temperature=%s, rates=%s)' % (type(self).__name__,
55
+ self.j, self.i,
56
+ sequence_repr(self.temperature),
57
+ sequence_repr(self.rates))
58
+ return s
59
+
60
+ def setup(self, atom):
61
+ i, j = self.i, self.j
62
+ self.i = min(i, j)
63
+ self.j = max(i, j)
64
+ self.atom = atom
65
+ self.jLevel = atom.levels[self.j]
66
+ self.iLevel = atom.levels[self.i]
67
+ self.temperature = np.asarray(self.temperature)
68
+ self.rates = np.asarray(self.rates)
69
+
70
+ @dataclass(eq=False, repr=False)
71
+ class Omega(TemperatureInterpolationRates):
72
+ '''
73
+ Collisional (de-)excitation of ions by electrons (dimensionless).
74
+ Omega as in Seaton's collision strength.
75
+ Rate scales as 1/(sqrt(T)) exp(DeltaE).
76
+ '''
77
+ def setup(self, atom):
78
+ super().setup(atom)
79
+ self.C0 = (Const.ERydberg / np.sqrt(Const.MElectron) * np.pi *
80
+ Const.RBohr**2 * np.sqrt(8.0 / (np.pi * Const.KBoltzmann)))
81
+
82
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
83
+ Cmat: np.ndarray):
84
+ C = weno4(atmos.temperature, self.temperature, self.rates)
85
+ C[C < 0.0] = 0.0
86
+ nstar = eqPops.atomicPops[self.atom.element].nStar
87
+ Cdown = self.C0 * atmos.ne * C / (self.jLevel.g * np.sqrt(atmos.temperature))
88
+ Cmat[self.i, self.j, :] += Cdown
89
+ Cmat[self.j, self.i, :] += Cdown * nstar[self.j] / nstar[self.i]
90
+
91
+ @dataclass(eq=False, repr=False)
92
+ class CI(TemperatureInterpolationRates):
93
+ '''
94
+ Collisional ionisation by electrons.
95
+ Units: s^-1 K^-1/2 m^3
96
+ Rate scales as sqrt(T) exp(DeltaE)
97
+ '''
98
+ def setup(self, atom):
99
+ super().setup(atom)
100
+ self.dE = self.jLevel.E_SI - self.iLevel.E_SI
101
+
102
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
103
+ Cmat: np.ndarray):
104
+ C = weno4(atmos.temperature, self.temperature, self.rates)
105
+ C[C < 0.0] = 0.0
106
+ nstar = eqPops.atomicPops[self.atom.element].nStar
107
+ Cup = (C * atmos.ne * np.exp(-self.dE / (Const.KBoltzmann * atmos.temperature))
108
+ * np.sqrt(atmos.temperature))
109
+ Cmat[self.j, self.i, :] += Cup
110
+ Cmat[self.i, self.j, :] += Cup * nstar[self.i] / nstar[self.j]
111
+
112
+
113
+ @dataclass(eq=False, repr=False)
114
+ class CE(TemperatureInterpolationRates):
115
+ '''
116
+ Collisional (de-)excitation of neutrals by electrons.
117
+ Units: s^-1 K^-1/2 m^3
118
+ Rate scales as sqrt(T) exp(DeltaE)
119
+ '''
120
+ def setup(self, atom):
121
+ super().setup(atom)
122
+ self.gij = self.iLevel.g / self.jLevel.g
123
+
124
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
125
+ Cmat: np.ndarray):
126
+ C = weno4(atmos.temperature, self.temperature, self.rates)
127
+ C[C < 0.0] = 0.0
128
+ nstar = eqPops.atomicPops[self.atom.element].nStar
129
+ Cdown = C * atmos.ne * self.gij * np.sqrt(atmos.temperature)
130
+ Cmat[self.i, self.j, :] += Cdown
131
+ Cmat[self.j, self.i, :] += Cdown * nstar[self.j] / nstar[self.i]
132
+
133
+ @dataclass(eq=False, repr=False)
134
+ class CP(TemperatureInterpolationRates):
135
+ '''
136
+ Collisional (de-)excitation by protons.
137
+ Units: s^-1 m^3
138
+ '''
139
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
140
+ Cmat: np.ndarray):
141
+ C = weno4(atmos.temperature, self.temperature, self.rates)
142
+ C[C < 0.0] = 0.0
143
+ nProton = eqPops['H'][-1, :]
144
+ Cdown = C * nProton
145
+ nstar = eqPops.atomicPops[self.atom.element].nStar
146
+ Cmat[self.i, self.j, :] += Cdown
147
+ Cmat[self.j, self.i, :] += Cdown * nstar[self.j] / nstar[self.i]
148
+
149
+ @dataclass(eq=False, repr=False)
150
+ class CH(TemperatureInterpolationRates):
151
+ '''
152
+ Collisions with neutral hydrogen.
153
+ Units: s^-1 m^3
154
+ '''
155
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
156
+ Cmat: np.ndarray):
157
+ C = weno4(atmos.temperature, self.temperature, self.rates)
158
+ C[C < 0.0] = 0.0
159
+ nh0 = eqPops['H'][0, :]
160
+ Cup = C * nh0
161
+ nstar = eqPops.atomicPops[self.atom.element].nStar
162
+ Cmat[self.j, self.i, :] += Cup
163
+ Cmat[self.i, self.j, :] += Cup * nstar[self.i] / nstar[self.j]
164
+
165
+ @dataclass(eq=False, repr=False)
166
+ class ChargeExchangeNeutralH(TemperatureInterpolationRates):
167
+ '''
168
+ Charge exchange with neutral hydrogen.
169
+ Units: s^-1 m^3
170
+ Note: downward rate only.
171
+ '''
172
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
173
+ Cmat: np.ndarray):
174
+ C = weno4(atmos.temperature, self.temperature, self.rates)
175
+ C[C < 0.0] = 0.0
176
+ nh0 = eqPops['H'][0, :]
177
+ Cdown = C * nh0
178
+ nstar = eqPops.atomicPops[self.atom.element].nStar
179
+ Cmat[self.i, self.j, :] += Cdown
180
+
181
+ @dataclass(eq=False, repr=False)
182
+ class ChargeExchangeProton(TemperatureInterpolationRates):
183
+ '''
184
+ Charge exchange with protons.
185
+ Units: s^-1 m^3
186
+ Note: upward rate only.
187
+ '''
188
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
189
+ Cmat: np.ndarray):
190
+ C = weno4(atmos.temperature, self.temperature, self.rates)
191
+ C[C < 0.0] = 0.0
192
+ nProton = eqPops['H'][-1, :]
193
+ Cup = C * nProton
194
+ Cmat[self.j, self.i, :] += Cup
195
+
196
+ def fone(x):
197
+ # return np.where(x <= 50.0, np.exp(x) * exp1(x), 1.0/x)
198
+ return np.where(x <= 50.0, np.exp(x) * exp1(x), (1.0 - 1.0 / x + 2.0 / x**2) / x)
199
+
200
+ @njit(cache=True)
201
+ def ftwo(x):
202
+ p = np.array((1.0000e+00, 2.1658e+02, 2.0336e+04, 1.0911e+06, 3.7114e+07,
203
+ 8.3963e+08, 1.2889e+10, 1.3449e+11, 9.4002e+11, 4.2571e+12,
204
+ 1.1743e+13, 1.7549e+13, 1.0806e+13, 4.9776e+11, 0.0000))
205
+ q = np.array((1.0000e+00, 2.1958e+02, 2.0984e+04, 1.1517e+06, 4.0349e+07,
206
+ 9.4900e+08, 1.5345e+10, 1.7182e+11, 1.3249e+12, 6.9071e+12,
207
+ 2.3531e+13, 4.9432e+13, 5.7760e+13, 3.0225e+13, 3.3641e+12))
208
+
209
+ def ftwo_impl(x):
210
+ if x > 4.0:
211
+ px = p[0]
212
+ xFact = 1.0
213
+ for i in range(1, 15):
214
+ xFact /= x
215
+ px += p[i] * xFact
216
+
217
+ qx = q[0]
218
+ xFact = 1.0
219
+ for i in range(1, 15):
220
+ xFact /= x
221
+ qx += q[i] * xFact
222
+
223
+ return px / (qx * x**2)
224
+
225
+ else:
226
+ gamma = 0.5772156649
227
+ f0x = np.pi**2 / 12.0
228
+ term = 1.0
229
+ count = 0.0
230
+ fact = 1.0
231
+ xFact = 1.0
232
+
233
+ while abs(term / f0x) > 1e-8:
234
+ count += 1.0
235
+ fact *= count
236
+ xFact *= -x
237
+ term = xFact / (count**2 * fact)
238
+ f0x += term
239
+
240
+ if count > 100.0:
241
+ raise ValueError('ftwo too slow to converge')
242
+
243
+ y = np.exp(x) * ((np.log(x) + gamma)**2 * 0.5 + f0x)
244
+ return y
245
+
246
+ y = np.empty_like(x)
247
+ for i in range(x.shape[0]):
248
+ y[i] = ftwo_impl(x[i])
249
+
250
+ return y
251
+
252
+ @dataclass
253
+ class Ar85Cdi(CollisionalRates):
254
+ '''
255
+ Collisional ionisation rates based on Arnaud & Rothenflug (1985, ApJS 60).
256
+ Units remain in CGS as per paper.
257
+ '''
258
+ cdi: Sequence[Sequence[float]]
259
+
260
+ def __repr__(self):
261
+ s = 'Ar85Cdi(j=%d, i=%d, cdi=%s)' % (self.j, self.i, sequence_repr(self.cdi))
262
+ return s
263
+
264
+ def setup(self, atom):
265
+ i, j = self.i, self.j
266
+ self.i = min(i, j)
267
+ self.j = max(i, j)
268
+ self.atom = atom
269
+ self.iLevel = atom.levels[self.i]
270
+ self.jLevel = atom.levels[self.j]
271
+ self.cdi = np.array(self.cdi)
272
+
273
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
274
+ Cmat: np.ndarray):
275
+ nstar = eqPops.atomicPops[self.atom.element].nStar
276
+ Cup = np.zeros(atmos.Nspace)
277
+ cdi = cast(np.ndarray, self.cdi)
278
+ for m in range(cdi.shape[0]):
279
+ xj = cdi[m, 0] * Const.EV / (Const.KBoltzmann * atmos.temperature)
280
+ fac = np.exp(-xj) * np.sqrt(xj)
281
+ fxj = (cdi[m, 1] + cdi[m, 2] * (1.0 + xj)
282
+ + (cdi[m, 3] - xj * (cdi[m, 1] + cdi[m, 2] * (2.0 + xj)))
283
+ * fone(xj) + cdi[m, 4] * xj * ftwo(xj))
284
+
285
+ fxj *= fac
286
+ fac = 6.69e-7 / cdi[m, 0]**1.5
287
+ Cup += fac * (fxj << u.Unit('cm3')).to('m3').value
288
+ Cup[Cup < 0] = 0.0
289
+
290
+ Cup *= atmos.ne
291
+ Cdown = Cup * nstar[self.i] / nstar[self.j]
292
+ Cmat[self.i, self.j, :] += Cdown
293
+ Cmat[self.j, self.i, :] += Cup
294
+
295
+
296
+ @dataclass
297
+ class Burgess(CollisionalRates):
298
+ '''
299
+ Collisional ionisation from excited states from Burgess & Chidichimo
300
+ (1983, MNRAS 203, 1269).
301
+ Fudge parameter is dimensionless.
302
+ '''
303
+ fudge: float = 1.0
304
+
305
+ def __repr__(self):
306
+ s = 'Burgess(j=%d, i=%d, fudge=%g)' % (self.j, self.i, self.fudge)
307
+ return s
308
+
309
+ def setup(self, atom):
310
+ i, j = self.i, self.j
311
+ self.i = min(i, j)
312
+ self.j = max(i, j)
313
+ self.atom = atom
314
+ self.iLevel = atom.levels[self.i]
315
+ self.jLevel = atom.levels[self.j]
316
+
317
+ def compute_rates(self, atmos: 'Atmosphere', eqPops: 'SpeciesStateTable',
318
+ Cmat: np.ndarray):
319
+ nstar = eqPops.atomicPops[self.atom.element].nStar
320
+ dE = (self.jLevel.E_SI - self.iLevel.E_SI) / Const.EV
321
+ zz = self.iLevel.stage
322
+ betaB = 0.25 * (np.sqrt((100.0 * zz + 91.0) / (4.0 * zz + 3.0)) - 5.0)
323
+ cbar = 2.3
324
+
325
+ dEkT = dE * Const.EV / (Const.KBoltzmann * atmos.temperature)
326
+ dEkT = np.minimum(dEkT, 500)
327
+ invdEkT = 1.0 / dEkT
328
+ wlog = np.log(1.0 + invdEkT)
329
+ wb = wlog**(betaB / (1.0 + invdEkT))
330
+ Cup = (2.1715e-8 * cbar * (13.6/dE)**1.5 * np.sqrt(dEkT)
331
+ * exp1(dEkT) * wb * (atmos.ne << u.Unit('m-3')).to('cm-3').value)
332
+
333
+ Cup *= self.fudge
334
+ Cdown = Cup * nstar[self.i, :] / nstar[self.j, :]
335
+
336
+ Cmat[self.j, self.i, :] += Cup
337
+ Cmat[self.i, self.j, :] += Cdown
lightweaver/config.py ADDED
@@ -0,0 +1,106 @@
1
+ import warnings
2
+ from copy import copy
3
+ from os import path
4
+ from typing import List, Optional
5
+
6
+ import astropy.config as conf
7
+ import yaml
8
+ from lightweaver.simd_management import (LwSimdImplsAndFlags,
9
+ get_available_simd_suffixes)
10
+
11
+ Defaults = {
12
+ 'FormalSolver1d': 'piecewise_bezier3_1d',
13
+ 'FormalSolver2d': 'piecewise_besser_2d',
14
+ 'IterationScheme': 'mali_full_precond',
15
+ 'SimdImpl': 'scalar',
16
+ }
17
+
18
+ params = copy(Defaults)
19
+
20
+ def get_home_config_path() -> str:
21
+ '''
22
+ Return the location where the user's configuration data *should* be stored,
23
+ whether it is currently present or not.
24
+ '''
25
+ confDir = conf.get_config_dir('lightweaver')
26
+ homePath = path.join(confDir, 'lightweaverrc')
27
+ return homePath
28
+
29
+
30
+ def get_config_path() -> Optional[str]:
31
+ '''
32
+ Returns the path to the `lightweaverrc` configuration file, or None if one
33
+ cannot be found.
34
+ '''
35
+ localPath = 'lightweaverrc'
36
+ if path.isfile(localPath):
37
+ return localPath
38
+ homePath = get_home_config_path()
39
+ if path.isfile(homePath):
40
+ return homePath
41
+
42
+ return None
43
+
44
+
45
+ def set_most_advanced_simd_impl():
46
+ '''
47
+ Picks the most advanced SIMD extensions (as detected by NumPy), that can be
48
+ used on this system. This does not guarantee fastest (see the note in
49
+ `update_config_dict`).
50
+ '''
51
+ availableImpls = get_available_simd_suffixes()
52
+
53
+ def check_add_impl(simdType):
54
+ if simdType in availableImpls:
55
+ params['SimdImpl'] = simdType
56
+
57
+ for impl in LwSimdImplsAndFlags:
58
+ check_add_impl(impl)
59
+
60
+
61
+ def update_config_dict(configPath: Optional[str]):
62
+ '''
63
+ Updates the configuration dict (`lightweaver.ConfigDict`), from the config
64
+ file. If there is no config file, the defaults are used, and the most
65
+ advanced instruction set is chosen for the SimdImpl. If the SimdImpl in the
66
+ config file is too advanced for the current CPU, the maximum available is
67
+ chosen.
68
+
69
+ Parameters
70
+ ----------
71
+ configPath : str, optional
72
+ The path to the config file, or None.
73
+ '''
74
+ if configPath is None:
75
+ warnings.warn('No config file found, using defaults. For optimised vectorised code,'
76
+ ' please run `lightweaver.benchmark()`, otherwise the most advanced'
77
+ ' instruction set supported by your machine will be picked, which may'
78
+ ' not be the fastest (due to e.g. aggressive AVX offsets).')
79
+ set_most_advanced_simd_impl()
80
+ return
81
+
82
+ with open(configPath, 'r') as f:
83
+ confDict = yaml.safe_load(f)
84
+ params.update(confDict)
85
+
86
+ availableSimd : List[str] = get_available_simd_suffixes()
87
+ if params['SimdImpl'] not in ['scalar'] + availableSimd:
88
+ set_most_advanced_simd_impl()
89
+ warnings.warn('SimdImpl was set to an overly advanced instruction set for the '
90
+ 'current CPU, setting to the maximum supported by your CPU.')
91
+
92
+
93
+ def update_config_file(configPath: str):
94
+ '''
95
+ Updates the config file to the current values of the config dict.
96
+
97
+ Parameters
98
+ ----------
99
+ configPath : str
100
+ The path to the config file.
101
+ '''
102
+ with open(configPath, 'w') as f:
103
+ yaml.safe_dump(params, f)
104
+
105
+
106
+ update_config_dict(get_config_path())
@@ -0,0 +1,22 @@
1
+
2
+ CLight = 2.99792458E+08 # Speed of light [m/s] */
3
+ HPlanck = 6.6260755E-34 # Planck's constant [Js] */
4
+ HC = HPlanck * CLight
5
+ HC_CM = HC * 1e2 # HC with c in cm/s, for wavenumbers as cm-1
6
+ KBoltzmann = 1.380658E-23 # Boltzman's constant [J/K] */
7
+ Amu = 1.6605402E-27 # Atomic mass unit [kg] */
8
+ MElectron = 9.1093897E-31 # Electron mass [kg] */
9
+ QElectron = 1.60217733E-19 # Electron charge [C] */
10
+ Epsilon0 = 8.854187817E-12 # Vacuum permittivity [F/m] */
11
+ Mu0 = 1.2566370614E-06 # Magnetic induct. of vac. */
12
+ RBohr = 5.29177349E-11 # Bohr radius [m] */
13
+ ERydberg = 2.1798741E-18 # Ion. pot. Hydrogen [J] */
14
+ EV = 1.60217733E-19 # One electronVolt [J] */
15
+ Theta0 = 5.03974756E+03 # log10(e) * eV/k [K^-1] */
16
+ ABarH = 7.42E-41 # polarizability of Hydrogen [Fm^2]
17
+ E_ION_HMIN = 0.754*EV
18
+
19
+ NM_TO_M = 1.0E-09
20
+
21
+ VMICRO_CHAR=3.0e3
22
+ B_CHAR=0.0 #TESLA
lightweaver/crtaf.py ADDED
@@ -0,0 +1,197 @@
1
+ import warnings
2
+
3
+ import astropy.units as u
4
+ import crtaf
5
+ from fractions import Fraction
6
+
7
+ from lightweaver.atomic_model import AtomicModel, AtomicLevel, LineType, LinearQuadrature, TabulatedQuadrature, LinearCoreExpWings, VoigtLine, HydrogenicContinuum, ExplicitContinuum
8
+ from lightweaver.broadening import LineBroadening, RadiativeBroadening, HydrogenLinearStarkBroadening, MultiplicativeStarkBroadening, QuadraticStarkBroadening, VdwUnsold, ScaledExponentBroadening
9
+ from lightweaver.collisional_rates import Omega, CE, CI, CH, CP, ChargeExchangeProton, ChargeExchangeNeutralH
10
+ from lightweaver.atomic_table import PeriodicTable
11
+
12
+ def from_crtaf(model: crtaf.Atom) -> AtomicModel:
13
+ crtaf_labels = []
14
+ levels = {}
15
+ for label, level in model.levels.items():
16
+ crtaf_labels.append(label)
17
+ J = None
18
+ if level.J is not None:
19
+ J = Fraction(level.J.numerator, level.J.denominator)
20
+ L = None
21
+ if level.L is not None:
22
+ L = level.L
23
+ S = None
24
+ if level.S is not None:
25
+ S = Fraction(level.S.numerator, level.S.denominator)
26
+ lw_level = AtomicLevel(
27
+ E=level.energy.to("cm-1", equivalencies=u.spectral()).value.item(),
28
+ g=level.g,
29
+ label=level.label if level.label is not None else "",
30
+ stage=level.stage-1,
31
+ J=J,
32
+ L=L,
33
+ S=S,
34
+ )
35
+ levels[label] = lw_level
36
+
37
+ crtaf_labels = sorted(crtaf_labels, key=lambda x: levels[x].E)
38
+ levels = [levels[l] for l in crtaf_labels]
39
+ level_conversion_dict = {label: idx for idx, label in enumerate(crtaf_labels)}
40
+
41
+ lines = []
42
+ for line in model.lines:
43
+ if not isinstance(line, crtaf.VoigtBoundBound):
44
+ raise ValueError(f"Unexpected line type encountered {line!r}, can only handle Voigt/PRD-Voigt.")
45
+
46
+ ty = LineType.CRD
47
+ if isinstance(line, crtaf.PrdVoigtBoundBound):
48
+ ty = LineType.PRD
49
+
50
+ natural_broadening = []
51
+ elastic_broadening = []
52
+ for b in line.broadening:
53
+ if isinstance(b, crtaf.NaturalBroadening):
54
+ natural_broadening.append(RadiativeBroadening(b.value.to("s-1").value))
55
+ elif isinstance(b, crtaf.StarkLinearSutton):
56
+ elastic_broadening.append(HydrogenLinearStarkBroadening())
57
+ elif isinstance(b, crtaf.StarkMultiplicative):
58
+ elastic_broadening.append(MultiplicativeStarkBroadening(b.C_4.value))
59
+ elif isinstance(b, crtaf.StarkQuadratic):
60
+ elastic_broadening.append(QuadraticStarkBroadening(b.scaling))
61
+ elif isinstance(b, crtaf.VdWUnsold):
62
+ elastic_broadening.append(VdwUnsold(vals=[b.H_scaling, b.He_scaling]))
63
+ elif isinstance(b, crtaf.ScaledExponents):
64
+ lw_b = ScaledExponentBroadening(
65
+ scaling=b.scaling,
66
+ temperatureExp=b.temperature_exponent,
67
+ hydrogenExp=b.hydrogen_exponent,
68
+ electronExp=b.electron_exponent,
69
+ )
70
+ if b.elastic:
71
+ elastic_broadening.append(lw_b)
72
+ else:
73
+ natural_broadening.append(lw_b)
74
+ else:
75
+ raise ValueError(f"Unexpected broadening ({b}), can only handle core CRTAF types.")
76
+ broadening = LineBroadening(natural=natural_broadening, elastic=elastic_broadening)
77
+
78
+ q = line.wavelength_grid
79
+ if isinstance(q, crtaf.LinearGrid):
80
+ grid = LinearQuadrature(
81
+ Nlambda=q.n_lambda,
82
+ deltaLambda=q.delta_lambda.to(u.nm).value.item(),
83
+ )
84
+ elif isinstance(q, crtaf.TabulatedGrid):
85
+ grid = TabulatedQuadrature(
86
+ wavelengthGrid=q.wavelengths.to(u.nm).value.tolist(),
87
+ )
88
+ elif isinstance(q, crtaf.LinearCoreExpWings):
89
+ grid = LinearCoreExpWings(
90
+ qCore=q.q_core,
91
+ qWing=q.q_wing,
92
+ Nlambda=q.n_lambda,
93
+ )
94
+ else:
95
+ raise ValueError(f"Unexpected WavelengthGrid ({q}), can only handle core CRTAF types and LinearCoreExpWings.")
96
+
97
+ lw_line = VoigtLine(
98
+ j=level_conversion_dict[line.transition[0]],
99
+ i=level_conversion_dict[line.transition[1]],
100
+ f=line.f_value,
101
+ type=ty,
102
+ quadrature=grid,
103
+ broadening=broadening,
104
+ )
105
+ lines.append(lw_line)
106
+
107
+
108
+ continua = []
109
+ for cont in model.continua:
110
+ if isinstance(cont, crtaf.HydrogenicBoundFree):
111
+ lw_cont = HydrogenicContinuum(
112
+ j=level_conversion_dict[cont.transition[0]],
113
+ i=level_conversion_dict[cont.transition[1]],
114
+ NlambdaGen=cont.n_lambda,
115
+ alpha0=cont.sigma_peak.to("m2").value,
116
+ minWavelength=cont.lambda_min.to(u.nm).value,
117
+ )
118
+ elif isinstance(cont, crtaf.TabulatedBoundFree):
119
+ lw_cont = ExplicitContinuum(
120
+ j=level_conversion_dict[cont.transition[0]],
121
+ i=level_conversion_dict[cont.transition[1]],
122
+ wavelengthGrid=cont.wavelengths.to(u.nm).value,
123
+ alphaGrid=cont.sigma.to("m2").value,
124
+ )
125
+ else:
126
+ raise ValueError(f"Unexpected continuum ({cont}), can only handle Hydrogenic and Tabulated.")
127
+ continua.append(lw_cont)
128
+
129
+ collisions = []
130
+ for coll in model.collisions:
131
+ j = level_conversion_dict[coll.transition[0]]
132
+ i = level_conversion_dict[coll.transition[1]]
133
+ for process in coll.data:
134
+ if isinstance(process, crtaf.OmegaRate):
135
+ lw_coll = Omega(
136
+ j=j,
137
+ i=i,
138
+ temperature=process.temperature.to(u.K).value.tolist(),
139
+ rates=process.data.value.tolist(),
140
+ )
141
+ elif isinstance(process, crtaf.CIRate):
142
+ lw_coll = CI(
143
+ j=j,
144
+ i=i,
145
+ temperature=process.temperature.to(u.K).value.tolist(),
146
+ rates=process.data.to("m3 s-1 K(-1/2)").value.tolist(),
147
+ )
148
+ elif isinstance(process, crtaf.CERate):
149
+ lw_coll = CE(
150
+ j=j,
151
+ i=i,
152
+ temperature=process.temperature.to(u.K).value.tolist(),
153
+ rates=process.data.to("m3 s-1 K(-1/2)").value.tolist(),
154
+ )
155
+ elif isinstance(process, crtaf.CHRate):
156
+ lw_coll = CH(
157
+ j=j,
158
+ i=i,
159
+ temperature=process.temperature.to(u.K).value.tolist(),
160
+ rates=process.data.to("m3 s-1").value.tolist(),
161
+ )
162
+ elif isinstance(process, crtaf.CPRate):
163
+ lw_coll = CP(
164
+ j=j,
165
+ i=i,
166
+ temperature=process.temperature.to(u.K).value.tolist(),
167
+ rates=process.data.to("m3 s-1").value.tolist(),
168
+ )
169
+ elif isinstance(process, crtaf.ChargeExcHRate):
170
+ lw_coll = ChargeExchangeNeutralH(
171
+ j=j,
172
+ i=i,
173
+ temperature=process.temperature.to(u.K).value.tolist(),
174
+ rates=process.data.to("m3 s-1").value.tolist(),
175
+ )
176
+ elif isinstance(process, crtaf.ChargeExcPRate):
177
+ lw_coll = ChargeExchangeProton(
178
+ j=j,
179
+ i=i,
180
+ temperature=process.temperature.to(u.K).value.tolist(),
181
+ rates=process.data.to("m3 s-1").value.tolist(),
182
+ )
183
+ else:
184
+ raise ValueError(f"Unexpected collisional rate encountered ({coll}), expected one of [Omega, CI, CE, CH, CP, ChargeExcH, ChargeExcP].")
185
+ collisions.append(lw_coll)
186
+
187
+ if model.element.N is not None:
188
+ warnings.warn("N provided. Whilst Lightweaver has the ability to handle isotopes, the CRTAF parser currently does not.")
189
+ lw_model = AtomicModel(
190
+ element=PeriodicTable[model.element.symbol],
191
+ levels=levels,
192
+ lines=lines,
193
+ continua=continua,
194
+ collisions=collisions,
195
+ )
196
+ return lw_model
197
+