lightweaver 0.15.0__cp310-cp310-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.
- 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-310-x86_64-linux-gnu.so +0 -0
- lightweaver/DefaultIterSchemes/SimdImpl_AVX512.cpython-310-x86_64-linux-gnu.so +0 -0
- lightweaver/DefaultIterSchemes/SimdImpl_SSE2.cpython-310-x86_64-linux-gnu.so +0 -0
- lightweaver/LwCompiled.cpython-310-x86_64-linux-gnu.so +0 -0
- lightweaver/__init__.py +33 -0
- lightweaver/atmosphere.py +1640 -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 +504 -0
- lightweaver/version.py +34 -0
- lightweaver/wittmann.py +1375 -0
- lightweaver/zeeman.py +157 -0
- lightweaver-0.15.0.dist-info/METADATA +81 -0
- lightweaver-0.15.0.dist-info/RECORD +69 -0
- lightweaver-0.15.0.dist-info/WHEEL +6 -0
- lightweaver-0.15.0.dist-info/licenses/LICENSE +21 -0
- lightweaver-0.15.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from numpy.core._multiarray_umath import __cpu_features__
|
|
5
|
+
|
|
6
|
+
# NOTE(cmo): These are added in reverse order of preference (due to width), i.e.
|
|
7
|
+
# try to use the key furthest down the list.
|
|
8
|
+
LwSimdImplsAndFlags = {
|
|
9
|
+
'SSE2': ['SSE2'],
|
|
10
|
+
'AVX2FMA': ['AVX2', 'FMA3'],
|
|
11
|
+
'AVX512': ['AVX512F', 'AVX512DQ']
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def get_available_simd_suffixes() -> List[str]:
|
|
15
|
+
'''
|
|
16
|
+
Verifies the necessary flags against the features NumPy indicates are
|
|
17
|
+
available, and returns a list of available LightweaverSimdImpls
|
|
18
|
+
'''
|
|
19
|
+
validExts = []
|
|
20
|
+
for impl, flags in LwSimdImplsAndFlags.items():
|
|
21
|
+
if all(__cpu_features__[flag] for flag in flags):
|
|
22
|
+
validExts.append(impl)
|
|
23
|
+
return validExts
|
|
24
|
+
|
|
25
|
+
def filter_usable_simd_impls(implLibs: List[str]) -> List[str]:
|
|
26
|
+
'''
|
|
27
|
+
Filter a list of SimdImpl library names, returning those that are usable on
|
|
28
|
+
the current machine.
|
|
29
|
+
'''
|
|
30
|
+
usableImpls = get_available_simd_suffixes()
|
|
31
|
+
result = []
|
|
32
|
+
for lib in implLibs:
|
|
33
|
+
_, libName = path.split(lib)
|
|
34
|
+
# NOTE(cmo): A lib name is expected to be of the form
|
|
35
|
+
# SimdImpl_{SimdType}.{pep3149}.so. So we split at the underscore and
|
|
36
|
+
# check what the name starts with.
|
|
37
|
+
nameEnd = libName.split('_')[1]
|
|
38
|
+
for simdType in usableImpls:
|
|
39
|
+
if nameEnd.startswith(simdType):
|
|
40
|
+
result.append(lib)
|
|
41
|
+
break
|
|
42
|
+
return result
|
lightweaver/utils.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
from os import path
|
|
6
|
+
from typing import TYPE_CHECKING, Optional, Sequence, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from astropy import units
|
|
10
|
+
from numba import njit
|
|
11
|
+
from scipy import special
|
|
12
|
+
from scipy.integrate import trapezoid
|
|
13
|
+
from weno4 import weno4
|
|
14
|
+
|
|
15
|
+
import lightweaver.constants as C
|
|
16
|
+
from .simd_management import filter_usable_simd_impls
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .atomic_model import AtomicLine, AtomicModel
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class NgOptions:
|
|
23
|
+
'''
|
|
24
|
+
Container for the options related to Ng acceleration.
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
Norder : int, optional
|
|
28
|
+
The order of the acceleration scheme to use (default: 0, i.e. none).
|
|
29
|
+
Nperiod : int, optional
|
|
30
|
+
The number of iterations to run between accelerations.
|
|
31
|
+
Ndelay : int, optional
|
|
32
|
+
The number of iterations to run before starting acceleration.
|
|
33
|
+
threshold : float, optional
|
|
34
|
+
The threshold which all historic iterations (Norder+2) must be below for
|
|
35
|
+
acceleration. Default, 5e-2
|
|
36
|
+
lowerThreshold : float, optional
|
|
37
|
+
The threshold below which to disable Ng acceleration. Default, 2e-4
|
|
38
|
+
'''
|
|
39
|
+
Norder: int = 0
|
|
40
|
+
Nperiod: int = 0
|
|
41
|
+
Ndelay: int = 0
|
|
42
|
+
threshold: float = 5e-2
|
|
43
|
+
lowerThreshold: float = 2e-4
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InitialSolution(Enum):
|
|
47
|
+
'''
|
|
48
|
+
Initial solutions to use for atomic populations, either LTE, Zero
|
|
49
|
+
radiation (not yet supported), or second order escape probability.
|
|
50
|
+
'''
|
|
51
|
+
Lte = auto()
|
|
52
|
+
Zero = auto()
|
|
53
|
+
EscapeProbability = auto()
|
|
54
|
+
|
|
55
|
+
def voigt_H(a, v):
|
|
56
|
+
'''
|
|
57
|
+
Scalar Voigt profile.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
a : float or array-like
|
|
62
|
+
The a damping parameter to be used in the Voigt profile.
|
|
63
|
+
v : float or array-like
|
|
64
|
+
The position in the line profile in Doppler units.
|
|
65
|
+
'''
|
|
66
|
+
z = (v + 1j * a)
|
|
67
|
+
return special.wofz(z).real
|
|
68
|
+
|
|
69
|
+
@njit
|
|
70
|
+
def planck(temp, wav):
|
|
71
|
+
'''
|
|
72
|
+
Planck black-body function B_nu(T) from wavelength.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
temp : float or array-like
|
|
77
|
+
Temperature [K]
|
|
78
|
+
wav : float or array-like
|
|
79
|
+
The wavelength at which to compute B_nu [nm].
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
result : float or array-like
|
|
84
|
+
B_nu(T)
|
|
85
|
+
'''
|
|
86
|
+
hc_Tkla = C.HC / (C.KBoltzmann * C.NM_TO_M * wav) / temp
|
|
87
|
+
twohnu3_c2 = (2.0 * C.HC) / (C.NM_TO_M * wav)**3
|
|
88
|
+
|
|
89
|
+
return twohnu3_c2 / (np.exp(hc_Tkla) - 1.0)
|
|
90
|
+
|
|
91
|
+
def gaunt_bf(wvl, nEff, charge) -> float:
|
|
92
|
+
'''
|
|
93
|
+
Gaunt factor for bound-free transitions, from Seaton (1960), Rep. Prog.
|
|
94
|
+
Phys. 23, 313, as used in RH.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
wvl : float or array-like
|
|
99
|
+
The wavelength at which to compute the Gaunt factor [nm].
|
|
100
|
+
nEff : float
|
|
101
|
+
Principal quantum number.
|
|
102
|
+
charge : float
|
|
103
|
+
Charge of free state.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
result : float or array-like
|
|
108
|
+
Gaunt factor for bound-free transitions.
|
|
109
|
+
'''
|
|
110
|
+
# /* --- M. J. Seaton (1960), Rep. Prog. Phys. 23, 313 -- ----------- */
|
|
111
|
+
# Copied from RH, ensuring vectorisation support
|
|
112
|
+
x = C.HC / (wvl * C.NM_TO_M) / (C.ERydberg * charge**2)
|
|
113
|
+
x3 = x**(1.0/3.0)
|
|
114
|
+
nsqx = 1.0 / (nEff**2 *x)
|
|
115
|
+
|
|
116
|
+
return 1.0 + 0.1728 * x3 * (1.0 - 2.0 * nsqx) - 0.0496 * x3**2 \
|
|
117
|
+
* (1.0 - (1.0 - nsqx) * (2.0 / 3.0) * nsqx)
|
|
118
|
+
|
|
119
|
+
class ConvergenceError(Exception):
|
|
120
|
+
'''
|
|
121
|
+
Raised by some iteration schemes, can also be used in user code.
|
|
122
|
+
'''
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
class ExplodingMatrixError(Exception):
|
|
126
|
+
'''
|
|
127
|
+
Raised by the linear system matrix solver in the case of unsolvable
|
|
128
|
+
systems.
|
|
129
|
+
'''
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def get_code_location():
|
|
133
|
+
'''
|
|
134
|
+
Returns the directory containing the Lightweaver Python source.
|
|
135
|
+
'''
|
|
136
|
+
directory, _ = path.split(path.realpath(__file__))
|
|
137
|
+
return directory
|
|
138
|
+
|
|
139
|
+
def get_data_path():
|
|
140
|
+
'''
|
|
141
|
+
Returns the location of the Lightweaver support data.
|
|
142
|
+
'''
|
|
143
|
+
return path.join(get_code_location(), 'Data') + path.sep
|
|
144
|
+
|
|
145
|
+
def get_default_molecule_path():
|
|
146
|
+
'''
|
|
147
|
+
Returns the location of the default molecules taken from RH.
|
|
148
|
+
'''
|
|
149
|
+
return path.join(get_code_location(), 'Data', 'DefaultMolecules') + path.sep
|
|
150
|
+
|
|
151
|
+
def filter_fs_iter_libs(libs: Sequence[str], exts: Sequence[str]) -> Sequence[str]:
|
|
152
|
+
'''
|
|
153
|
+
Filter a list of libraries (e.g. SimdImpl_{SimdType}.{pep3149}.so) with a
|
|
154
|
+
valid collection of extensions. (As .so is a valid extension, we can't just
|
|
155
|
+
check the end of the file name).
|
|
156
|
+
'''
|
|
157
|
+
result = []
|
|
158
|
+
for libName in libs:
|
|
159
|
+
libPrefix = libName.split('.')[0]
|
|
160
|
+
for ext in exts:
|
|
161
|
+
if libPrefix + ext == libName:
|
|
162
|
+
result.append(libName)
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
def get_fs_iter_libs() -> Sequence[str]:
|
|
166
|
+
'''
|
|
167
|
+
Returns the paths of the default FsIterationScheme libraries usable on the
|
|
168
|
+
current machine (due to available SIMD optimisations -- these are detected by NumPy).
|
|
169
|
+
'''
|
|
170
|
+
validExts = importlib.machinery.EXTENSION_SUFFIXES
|
|
171
|
+
iterSchemesDir = path.join(get_code_location(), 'DefaultIterSchemes')
|
|
172
|
+
schemes = [path.join(iterSchemesDir, x) for x in
|
|
173
|
+
filter_usable_simd_impls(
|
|
174
|
+
filter_fs_iter_libs(os.listdir(iterSchemesDir), validExts)
|
|
175
|
+
)]
|
|
176
|
+
return schemes
|
|
177
|
+
|
|
178
|
+
def vac_to_air(wavelength: np.ndarray) -> np.ndarray:
|
|
179
|
+
'''
|
|
180
|
+
Convert vacuum wavelength to air.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
wavelength : float or array-like or astropy.Quantity
|
|
185
|
+
If no units then the wavelength is assumed to be in [nm], otherwise the
|
|
186
|
+
provided units are used.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
result : float or array-like or astropy.Quantity
|
|
191
|
+
The converted wavelength in [nm].
|
|
192
|
+
'''
|
|
193
|
+
# NOTE(cmo): Moved this import here as it's very slow
|
|
194
|
+
### HACK
|
|
195
|
+
from specutils.utils.wcs_utils import vac_to_air as spec_vac_to_air
|
|
196
|
+
return spec_vac_to_air(wavelength << units.nm, method='edlen1966').value
|
|
197
|
+
|
|
198
|
+
def air_to_vac(wavelength: np.ndarray) -> np.ndarray:
|
|
199
|
+
'''
|
|
200
|
+
Convert air wavelength to vacuum.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
wavelength : float or array-like or astropy.Quantity
|
|
205
|
+
If no units then the wavelength is assumed to be in [nm], otherwise the
|
|
206
|
+
provided units are used.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
result : float or array-like or astropy.Quantity
|
|
211
|
+
The converted wavelength in [nm].
|
|
212
|
+
'''
|
|
213
|
+
# NOTE(cmo): Moved this import here as it's very slow
|
|
214
|
+
### HACK
|
|
215
|
+
from specutils.utils.wcs_utils import air_to_vac as spec_air_to_vac
|
|
216
|
+
return spec_air_to_vac(wavelength << units.nm, scheme='iteration',
|
|
217
|
+
method='edlen1966').value
|
|
218
|
+
|
|
219
|
+
def convert_specific_intensity(wavelength: np.ndarray,
|
|
220
|
+
specInt: np.ndarray,
|
|
221
|
+
outUnits) -> units.quantity.Quantity:
|
|
222
|
+
'''
|
|
223
|
+
Convert a specific intensity between different units.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
wavelength : np.ndarray or astropy.Quantity
|
|
228
|
+
If no units are provided then this is assumed to be in nm.
|
|
229
|
+
specInt : np.ndarray or astropy.Quantity
|
|
230
|
+
If no units are provided then this is assumed to be in J/s/m2/sr/Hz,
|
|
231
|
+
the default for Lightweaver.
|
|
232
|
+
outUnits : str or astropy.Unit
|
|
233
|
+
The units to convert specInt to e.g. 'erg/s/cm2/sr/Angstrom'
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
result : astropy.Quantity
|
|
238
|
+
specInt converted to the desired units.
|
|
239
|
+
'''
|
|
240
|
+
if not isinstance(wavelength, units.Quantity):
|
|
241
|
+
wavelength = wavelength << units.nm
|
|
242
|
+
|
|
243
|
+
if not isinstance(specInt, units.Quantity):
|
|
244
|
+
specInt = specInt << units.J / units.s / units.m**2 / units.sr / units.Hz
|
|
245
|
+
|
|
246
|
+
return specInt.to(outUnits, equivalencies=units.spectral_density(wavelength))
|
|
247
|
+
|
|
248
|
+
class CrswIterator:
|
|
249
|
+
'''
|
|
250
|
+
Basic iterator to be used for controlling the scale of the collisional
|
|
251
|
+
radiative switching (of Hummer & Voels) multiplicative paramter. Can be
|
|
252
|
+
inherited to provide different behaviour. By default starts from a factor
|
|
253
|
+
of 1e3 and scales this factor by 0.1**(1.0/value) each iteration, as is
|
|
254
|
+
the default behaviour in RH.
|
|
255
|
+
'''
|
|
256
|
+
def __init__(self, initVal=1e3):
|
|
257
|
+
self.val = initVal
|
|
258
|
+
|
|
259
|
+
def __call__(self):
|
|
260
|
+
self.val = max(1.0, self.val * 0.1**(1.0/self.val))
|
|
261
|
+
return self.val
|
|
262
|
+
|
|
263
|
+
class UnityCrswIterator(CrswIterator):
|
|
264
|
+
'''
|
|
265
|
+
A specific case representing no collisional radiative switching (i.e.
|
|
266
|
+
parameter always 1).
|
|
267
|
+
'''
|
|
268
|
+
def __init__(self):
|
|
269
|
+
super().__init__(1.0)
|
|
270
|
+
|
|
271
|
+
def __call__(self):
|
|
272
|
+
return self.val
|
|
273
|
+
|
|
274
|
+
def sequence_repr(x: Sequence) -> str:
|
|
275
|
+
'''
|
|
276
|
+
Uniform representation of arrays and lists as lists for use in
|
|
277
|
+
round-tripping AtomicModels.
|
|
278
|
+
'''
|
|
279
|
+
if isinstance(x, np.ndarray):
|
|
280
|
+
return repr(x.tolist())
|
|
281
|
+
|
|
282
|
+
return repr(x)
|
|
283
|
+
|
|
284
|
+
def view_flatten(x: np.ndarray) -> np.ndarray:
|
|
285
|
+
'''
|
|
286
|
+
Return a flattened view over an array, will raise an Exception if it
|
|
287
|
+
cannot be represented as a flat array without copy.
|
|
288
|
+
'''
|
|
289
|
+
y = x.view()
|
|
290
|
+
y.shape = (x.size,)
|
|
291
|
+
return y
|
|
292
|
+
|
|
293
|
+
def check_shape_exception(a: np.ndarray, shape: Union[int, Tuple[int]],
|
|
294
|
+
ndim: Optional[int]=1, name: Optional[str]='array'):
|
|
295
|
+
'''
|
|
296
|
+
Ensure that an array matches the expected number of dimensions and shape.
|
|
297
|
+
Raise a ValueError if not, quoting the array's name (if provided)
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
a : np.ndarray
|
|
302
|
+
The array to verify.
|
|
303
|
+
shape : int or Tuple[int]
|
|
304
|
+
The length (for a 1D array), or shape for a multi-dimensional array.
|
|
305
|
+
ndim : int, optional
|
|
306
|
+
The expected number of dimensions (default: 1)
|
|
307
|
+
name : str, optional
|
|
308
|
+
The name to in any exception (default: array)
|
|
309
|
+
|
|
310
|
+
'''
|
|
311
|
+
if isinstance(shape, int):
|
|
312
|
+
shape = (shape,)
|
|
313
|
+
|
|
314
|
+
if a.ndim != ndim:
|
|
315
|
+
raise ValueError(f'Array ({name}) does not have the expected number '
|
|
316
|
+
f'of dimensions: {ndim} (got: {a.ndim}).')
|
|
317
|
+
|
|
318
|
+
if a.shape != shape:
|
|
319
|
+
raise ValueError(f'Array ({name}) does not have the expected shape: '
|
|
320
|
+
f'{shape} (got: {a.shape}).')
|
|
321
|
+
|
|
322
|
+
def compute_radiative_losses(ctx) -> np.ndarray:
|
|
323
|
+
'''
|
|
324
|
+
Compute the radiative gains and losses for each wavelength in the grid
|
|
325
|
+
used by the context. Units of J/s/m3/Hz. Includes
|
|
326
|
+
background/contributions from overlapping lines. Convention of positive
|
|
327
|
+
=> radiative gain, negative => radiative loss.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
ctx : Context
|
|
332
|
+
A context with full depth-dependent data (i.e. ctx.depthData.fill =
|
|
333
|
+
True set before the most recent formal solution).
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
loss : np.ndarray
|
|
338
|
+
The radiative gains losses for each depth and wavelength in the
|
|
339
|
+
simulation.
|
|
340
|
+
'''
|
|
341
|
+
atmos = ctx.kwargs['atmos']
|
|
342
|
+
|
|
343
|
+
chiTot = ctx.depthData.chi
|
|
344
|
+
S = (ctx.depthData.eta + (ctx.background.sca * ctx.spect.J)[:, None, None, :]) / chiTot
|
|
345
|
+
Idepth = ctx.depthData.I
|
|
346
|
+
loss = ((chiTot * (S - Idepth)) * 0.5).sum(axis=2).transpose(0, 2, 1) @ atmos.wmu
|
|
347
|
+
|
|
348
|
+
return loss
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def integrate_line_losses(ctx, loss : np.ndarray,
|
|
352
|
+
lines : Union['AtomicLine', Sequence['AtomicLine']],
|
|
353
|
+
extendGridNm: float=0.0) -> Union[Sequence[np.ndarray], np.ndarray]:
|
|
354
|
+
'''
|
|
355
|
+
Integrate the radiative gains and losses over the band associated with a
|
|
356
|
+
line or list of lines. Units of J/s/m3. Includes background/contributions
|
|
357
|
+
from overlapping lines. Convention of positive => radiative gain,
|
|
358
|
+
negative => radiative loss.
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
ctx : Context
|
|
363
|
+
A context with the full depth-dependent data (i.e. ctx.depthData.fill
|
|
364
|
+
= True set before the most recent formal solution).
|
|
365
|
+
loss : np.ndarray
|
|
366
|
+
The radiative gains/losses for each wavelength and depth computed by
|
|
367
|
+
`compute_radiative_losses`.
|
|
368
|
+
lines : AtomicLine or list of AtomicLine
|
|
369
|
+
The lines for which to compute losses.
|
|
370
|
+
extendGridNm : float, optional
|
|
371
|
+
Set this to a positive value to add an additional point at each end
|
|
372
|
+
of the integration range to include a wider continuum/far-wing
|
|
373
|
+
contribution. Units: nm, default: 0.0.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
linesLosses : array or list of array
|
|
378
|
+
The radiative gain/losses per line at each depth.
|
|
379
|
+
'''
|
|
380
|
+
from .atomic_model import AtomicLine
|
|
381
|
+
|
|
382
|
+
if isinstance(lines, AtomicLine):
|
|
383
|
+
lines = [lines]
|
|
384
|
+
|
|
385
|
+
spect = ctx.kwargs['spect']
|
|
386
|
+
|
|
387
|
+
lineLosses = []
|
|
388
|
+
for line in lines:
|
|
389
|
+
transId = line.transId
|
|
390
|
+
grid = spect.transWavelengths[transId]
|
|
391
|
+
blueIdx = spect.blueIdx[transId]
|
|
392
|
+
blue = ctx.spect.wavelength[blueIdx]
|
|
393
|
+
redIdx = blueIdx + grid.shape[0]
|
|
394
|
+
red = ctx.spect.wavelength[redIdx-1]
|
|
395
|
+
|
|
396
|
+
if extendGridNm != 0.0:
|
|
397
|
+
wav = np.concatenate(((blue-extendGridNm,),
|
|
398
|
+
ctx.spect.wavelength[blueIdx:redIdx],
|
|
399
|
+
(red+extendGridNm,)))
|
|
400
|
+
else:
|
|
401
|
+
wav = ctx.spect.wavelength[blueIdx:redIdx]
|
|
402
|
+
|
|
403
|
+
# NOTE(cmo): There's a sneaky transpose going on here for the integration
|
|
404
|
+
lineLoss = np.zeros((loss.shape[1], wav.shape[0]))
|
|
405
|
+
for k in range(loss.shape[1]):
|
|
406
|
+
lineLoss[k, :] = weno4(wav, ctx.spect.wavelength, loss[:, k])
|
|
407
|
+
lineLosses.append(trapezoid(lineLoss,
|
|
408
|
+
(wav << units.nm).to(units.Hz,
|
|
409
|
+
equivalencies=units.spectral()).value)
|
|
410
|
+
)
|
|
411
|
+
return lineLosses[0] if len(lineLosses) == 1 else lineLosses
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def compute_contribution_fn(ctx, mu : int=-1, outgoing : bool=True) -> np.ndarray:
|
|
415
|
+
'''
|
|
416
|
+
Computes the contribution function for all wavelengths in the simulation,
|
|
417
|
+
for a chosen angular index.
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
ctx : Context
|
|
422
|
+
A context with the full depth-dependent data (i.e. ctx.depthData.fill
|
|
423
|
+
= True set before the most recent formal solution).
|
|
424
|
+
mu : Optional[int]
|
|
425
|
+
The angular index to use (corresponding to the order of the angular
|
|
426
|
+
quadratures in atmosphere), default: -1.
|
|
427
|
+
outgoing : Optional[bool]
|
|
428
|
+
Whether to compute the contribution for outgoing or incoming
|
|
429
|
+
radiation (wrt to the atmosphere). Default: outgoing==True, i.e. to
|
|
430
|
+
observer.
|
|
431
|
+
|
|
432
|
+
Returns
|
|
433
|
+
-------
|
|
434
|
+
cfn : np.ndarray
|
|
435
|
+
The contribution function in terms of depth and wavelength.
|
|
436
|
+
'''
|
|
437
|
+
upDown = 1 if outgoing else 0
|
|
438
|
+
tau = np.zeros_like(ctx.depthData.chi[:, mu, upDown, :])
|
|
439
|
+
chi = ctx.depthData.chi
|
|
440
|
+
atmos = ctx.kwargs['atmos']
|
|
441
|
+
|
|
442
|
+
# NOTE(cmo): Compute tau for all wavelengths
|
|
443
|
+
tau[:, 0] = 1e-20
|
|
444
|
+
for k in range(1, tau.shape[1]):
|
|
445
|
+
tau[:, k] = tau[:, k-1] + 0.5 * (chi[:, mu, upDown, k] + chi[:, mu, upDown, k-1]) \
|
|
446
|
+
* (atmos.height[k-1] - atmos.height[k])
|
|
447
|
+
|
|
448
|
+
# NOTE(cmo): Source function.
|
|
449
|
+
Sfn = ((ctx.depthData.eta
|
|
450
|
+
+ (ctx.background.sca * ctx.spect.J)[:, None, None, :])
|
|
451
|
+
/ chi)
|
|
452
|
+
|
|
453
|
+
# NOTE(cmo): Contribution function for all wavelengths.
|
|
454
|
+
cfn = ctx.depthData.chi[:, mu, upDown, :] / atmos.muz[mu] \
|
|
455
|
+
* np.exp(-tau / atmos.muz[mu]) * Sfn[:, mu, upDown, :]
|
|
456
|
+
|
|
457
|
+
return cfn
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def compute_wavelength_edges(ctx) -> np.ndarray:
|
|
461
|
+
'''
|
|
462
|
+
Compute the edges of the wavelength bins associated with the wavelength
|
|
463
|
+
array used in a simulation, typically used in conjunction with a plot
|
|
464
|
+
using pcolormesh.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
ctx : Context
|
|
469
|
+
The context from which to construct the wavelength edges.
|
|
470
|
+
|
|
471
|
+
Returns
|
|
472
|
+
-------
|
|
473
|
+
wlEdges : np.ndarray
|
|
474
|
+
The edges of the wavelength bins.
|
|
475
|
+
'''
|
|
476
|
+
wav = ctx.spect.wavelength
|
|
477
|
+
wlEdges = np.concatenate(((wav[0] - 0.5 * (wav[1] - wav[0]),),
|
|
478
|
+
0.5 * (wav[1:] + wav[:-1]),
|
|
479
|
+
(wav[-1] + 0.5 * (wav[-1] - wav[-2]),)
|
|
480
|
+
))
|
|
481
|
+
return wlEdges
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def compute_height_edges(ctx) -> np.ndarray:
|
|
485
|
+
'''
|
|
486
|
+
Compute the edges of the height bins associated with the stratified
|
|
487
|
+
altitude array used in a simulation, typically used in conjunction with a
|
|
488
|
+
plot using pcolormesh.
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
ctx : Context
|
|
493
|
+
The context from which to construct the height edges.
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
heightEdges : np.ndarray
|
|
498
|
+
The edges of the height bins.
|
|
499
|
+
'''
|
|
500
|
+
atmos = ctx.kwargs['atmos']
|
|
501
|
+
heightEdges = np.concatenate(((atmos.height[0] + 0.5 * (atmos.height[0] - atmos.height[1]),),
|
|
502
|
+
0.5 * (atmos.height[1:] + atmos.height[:-1]),
|
|
503
|
+
(atmos.height[-1] - 0.5 * (atmos.height[-2] - atmos.height[-1]),)))
|
|
504
|
+
return heightEdges
|
lightweaver/version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.15.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 15, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'gd4bacfad7'
|