lightweaver 0.16.1__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.
- lightweaver/Data/AbundancesAsplund09.pickle +0 -0
- lightweaver/Data/AtomicMassesNames.pickle +0 -0
- lightweaver/Data/Barklem_dfdata.dat +41 -0
- lightweaver/Data/Barklem_pddata.dat +40 -0
- lightweaver/Data/Barklem_spdata.dat +46 -0
- lightweaver/Data/DefaultMolecules/C2.molecule +27 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-A.asc +46409 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-A_12.asc +28322 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-B.asc +4272 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-B_12.asc +2583 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-C.asc +20916 -0
- lightweaver/Data/DefaultMolecules/CH/CH_X-C_12.asc +13106 -0
- lightweaver/Data/DefaultMolecules/CH.molecule +35 -0
- lightweaver/Data/DefaultMolecules/CN.molecule +30 -0
- lightweaver/Data/DefaultMolecules/CO/vmax=3_Jmax=49_dv=1_26 +296 -0
- lightweaver/Data/DefaultMolecules/CO/vmax=9_Jmax=120_dv=1_26 +2162 -0
- lightweaver/Data/DefaultMolecules/CO.molecule +30 -0
- lightweaver/Data/DefaultMolecules/CO_NLTE.molecule +29 -0
- lightweaver/Data/DefaultMolecules/CaH.molecule +29 -0
- lightweaver/Data/DefaultMolecules/H2+.molecule +27 -0
- lightweaver/Data/DefaultMolecules/H2.molecule +27 -0
- lightweaver/Data/DefaultMolecules/H2O.molecule +27 -0
- lightweaver/Data/DefaultMolecules/HF.molecule +29 -0
- lightweaver/Data/DefaultMolecules/LiH.molecule +27 -0
- lightweaver/Data/DefaultMolecules/MgH.molecule +34 -0
- lightweaver/Data/DefaultMolecules/N2.molecule +28 -0
- lightweaver/Data/DefaultMolecules/NH.molecule +27 -0
- lightweaver/Data/DefaultMolecules/NO.molecule +27 -0
- lightweaver/Data/DefaultMolecules/O2.molecule +27 -0
- lightweaver/Data/DefaultMolecules/OH.molecule +27 -0
- lightweaver/Data/DefaultMolecules/SiO.molecule +26 -0
- lightweaver/Data/DefaultMolecules/TiO.molecule +30 -0
- lightweaver/Data/Quadratures.pickle +0 -0
- lightweaver/Data/pf_Kurucz.input +0 -0
- lightweaver/DefaultIterSchemes/.placeholder +0 -0
- lightweaver/DefaultIterSchemes/SimdImpl_AVX2FMA.cpython-312-x86_64-linux-gnu.so +0 -0
- lightweaver/DefaultIterSchemes/SimdImpl_AVX512.cpython-312-x86_64-linux-gnu.so +0 -0
- lightweaver/DefaultIterSchemes/SimdImpl_SSE2.cpython-312-x86_64-linux-gnu.so +0 -0
- lightweaver/LwCompiled.cpython-312-x86_64-linux-gnu.so +0 -0
- lightweaver/__init__.py +33 -0
- lightweaver/atmosphere.py +1767 -0
- lightweaver/atomic_model.py +852 -0
- lightweaver/atomic_set.py +1286 -0
- lightweaver/atomic_table.py +653 -0
- lightweaver/barklem.py +151 -0
- lightweaver/benchmark.py +113 -0
- lightweaver/broadening.py +605 -0
- lightweaver/collisional_rates.py +337 -0
- lightweaver/config.py +106 -0
- lightweaver/constants.py +22 -0
- lightweaver/crtaf.py +197 -0
- lightweaver/fal.py +440 -0
- lightweaver/iterate_ctx.py +241 -0
- lightweaver/iteration_update.py +134 -0
- lightweaver/libenkiTS.so +0 -0
- lightweaver/molecule.py +225 -0
- lightweaver/multi.py +113 -0
- lightweaver/nr_update.py +106 -0
- lightweaver/rh_atoms.py +19743 -0
- lightweaver/simd_management.py +42 -0
- lightweaver/utils.py +509 -0
- lightweaver/version.py +34 -0
- lightweaver/wittmann.py +1375 -0
- lightweaver/zeeman.py +157 -0
- lightweaver-0.16.1.dist-info/METADATA +81 -0
- lightweaver-0.16.1.dist-info/RECORD +69 -0
- lightweaver-0.16.1.dist-info/WHEEL +6 -0
- lightweaver-0.16.1.dist-info/licenses/LICENSE +21 -0
- lightweaver-0.16.1.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())
|
lightweaver/constants.py
ADDED
|
@@ -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
|
+
|