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,134 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import TYPE_CHECKING, List
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from . import Context
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class IterationUpdate:
|
|
9
|
+
'''
|
|
10
|
+
Stores the results of an iteration of one of the backend functions, and
|
|
11
|
+
determines how to format this for printing. All changes refer to relative
|
|
12
|
+
change.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
ctx : Context
|
|
17
|
+
The context with which this update is associated.
|
|
18
|
+
crsw : float
|
|
19
|
+
The current value of the collisional radiative switching parameter.
|
|
20
|
+
updatedJ : bool
|
|
21
|
+
Whether the iteration affected the global J grid.
|
|
22
|
+
dJMax : float
|
|
23
|
+
The maximum change in J.
|
|
24
|
+
dJMaxIdx : int
|
|
25
|
+
The index of the maximum change of J in a flattened array of J.
|
|
26
|
+
updatedPops : bool
|
|
27
|
+
Whether the active atomic populations were modified by the iteration.
|
|
28
|
+
dPops : List[float]
|
|
29
|
+
The maximum change in each active population.
|
|
30
|
+
dPopsMaxIdx : List[int]
|
|
31
|
+
The location of the maximum change in each population in the flattened
|
|
32
|
+
population array.
|
|
33
|
+
ngAccelerated : List[bool]
|
|
34
|
+
Whether the atomic populations were modified by Ng Acceleration (per species, due to thresholding).
|
|
35
|
+
updatedNe : bool
|
|
36
|
+
Whether the electron density in the atmosphere was affected by the iteration.
|
|
37
|
+
dNeMax : float
|
|
38
|
+
The maximum change in the electron density.
|
|
39
|
+
dNeMaxIdx : int
|
|
40
|
+
The location of the maximum change in the electron density array.
|
|
41
|
+
updatedRho : bool
|
|
42
|
+
Whether the iteration affected the value of rhoPrd on PRD lines.
|
|
43
|
+
NprdSubIter : int
|
|
44
|
+
The number of PRD sub-iterations taken (if multiple),
|
|
45
|
+
dRho : List[float]
|
|
46
|
+
The maximum change in rho for each spectral line treated with PRD, in
|
|
47
|
+
the order of the lines on each activeAtom. These values are repeated for
|
|
48
|
+
each sub-iteration < NprdSubIter.
|
|
49
|
+
dRhoMaxIdx : List[int]
|
|
50
|
+
The location of the maximum change in rho for each PRD line.
|
|
51
|
+
updatedJPrd : bool
|
|
52
|
+
Whether the PRD iteration affected J.
|
|
53
|
+
dJPrdMax : float
|
|
54
|
+
The maximum change in J during each PRD sub-iteration.
|
|
55
|
+
dJPrdMaxIdx : int
|
|
56
|
+
The location of the maximum change in J for each PRD sub-iteration.
|
|
57
|
+
dPopsMax : float
|
|
58
|
+
The maximum population change (including ne) over the iteration
|
|
59
|
+
(read-only property).
|
|
60
|
+
dRhoMax : float
|
|
61
|
+
The maximum change in the PRD rho value for any line in the final
|
|
62
|
+
subiteration (read-only property).
|
|
63
|
+
'''
|
|
64
|
+
ctx: 'Context'
|
|
65
|
+
crsw: float = 1.0
|
|
66
|
+
updatedJ: bool = False
|
|
67
|
+
dJMax: float = 0.0
|
|
68
|
+
dJMaxIdx: int = 0
|
|
69
|
+
|
|
70
|
+
updatedPops: bool = False
|
|
71
|
+
dPops: List[float] = field(default_factory=list)
|
|
72
|
+
dPopsMaxIdx: List[int] = field(default_factory=list)
|
|
73
|
+
ngAccelerated: List[bool] = field(default_factory=list)
|
|
74
|
+
|
|
75
|
+
updatedNe: bool = False
|
|
76
|
+
dNeMax: float = 0.0
|
|
77
|
+
dNeMaxIdx: int = 0
|
|
78
|
+
|
|
79
|
+
updatedRho: bool = False
|
|
80
|
+
NprdSubIter: int = 0
|
|
81
|
+
dRho: List[float] = field(default_factory=list)
|
|
82
|
+
dRhoMaxIdx: List[int] = field(default_factory=list)
|
|
83
|
+
updatedJPrd: bool = False
|
|
84
|
+
dJPrdMax: List[float] = field(default_factory=list)
|
|
85
|
+
dJPrdMaxIdx: List[int] = field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def dPopsMax(self) -> float:
|
|
89
|
+
if len(self.dPops) == 0:
|
|
90
|
+
if self.updatedNe:
|
|
91
|
+
return self.dNeMax
|
|
92
|
+
else:
|
|
93
|
+
return 0.0
|
|
94
|
+
|
|
95
|
+
result = max(self.dPops)
|
|
96
|
+
if self.updatedNe:
|
|
97
|
+
result = max(result, self.dNeMax)
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def dRhoMax(self) -> float:
|
|
102
|
+
if self.NprdSubIter == 0:
|
|
103
|
+
return 0.0
|
|
104
|
+
finalSubIterStart = (self.NprdSubIter - 1) * self.ctx.kwargs['spect'].NprdTrans
|
|
105
|
+
return max(self.dRho[finalSubIterStart:])
|
|
106
|
+
|
|
107
|
+
def compact_representation(self):
|
|
108
|
+
'''
|
|
109
|
+
Produce a compact string representation of the object (similar to
|
|
110
|
+
Lightweaver < v0.8).
|
|
111
|
+
'''
|
|
112
|
+
chunks = []
|
|
113
|
+
if self.crsw != 1.0:
|
|
114
|
+
chunks.append(f'CRSW: {self.crsw:.2e}')
|
|
115
|
+
|
|
116
|
+
if self.updatedJ:
|
|
117
|
+
chunks.append(f'dJ = {self.dJMax:.2e}')
|
|
118
|
+
|
|
119
|
+
if self.updatedPops:
|
|
120
|
+
for idx, delta in enumerate(self.dPops):
|
|
121
|
+
atomName = self.ctx.activeAtoms[idx].atomicModel.element.name
|
|
122
|
+
accel = ' (accelerated)' if self.ngAccelerated[idx] else ''
|
|
123
|
+
chunks.append(f' {atomName} delta = {delta:6.4e}{accel}')
|
|
124
|
+
|
|
125
|
+
if self.updatedNe:
|
|
126
|
+
delta = self.dNeMax
|
|
127
|
+
chunks.append(f' ne delta = {delta:6.4e}')
|
|
128
|
+
|
|
129
|
+
if self.updatedRho:
|
|
130
|
+
iterCount = self.NprdSubIter
|
|
131
|
+
dRhoMax = self.dRhoMax
|
|
132
|
+
chunks.append(f' PRD dRho = {dRhoMax:.2e}, (sub-iterations: {iterCount})')
|
|
133
|
+
|
|
134
|
+
return '\n'.join(chunks)
|
lightweaver/libenkiTS.so
ADDED
|
Binary file
|
lightweaver/molecule.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from typing import List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
import astropy.units as u
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numba import njit
|
|
7
|
+
from parse import parse
|
|
8
|
+
|
|
9
|
+
import lightweaver.constants as Const
|
|
10
|
+
from .atomic_table import Element, PeriodicTable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# TODO(cmo): This should really be done with a generator/coroutine
|
|
14
|
+
def get_next_line(data: List[str]) -> Optional[str]:
|
|
15
|
+
if len(data) == 0:
|
|
16
|
+
return None
|
|
17
|
+
for i, d in enumerate(data):
|
|
18
|
+
if d.strip().startswith('#') or d.strip() == '':
|
|
19
|
+
continue
|
|
20
|
+
break
|
|
21
|
+
d = data[i]
|
|
22
|
+
if i == len(data) - 1:
|
|
23
|
+
data[:] = []
|
|
24
|
+
return d.strip()
|
|
25
|
+
data[:] = data[i+1:]
|
|
26
|
+
return d.strip()
|
|
27
|
+
|
|
28
|
+
def get_constituent(name: str) -> Tuple[int, str]:
|
|
29
|
+
res = parse('{:d}{!s}', name)
|
|
30
|
+
if res is None:
|
|
31
|
+
constituent = (1, name)
|
|
32
|
+
else:
|
|
33
|
+
constituent = (res[0], res[1])
|
|
34
|
+
return constituent
|
|
35
|
+
|
|
36
|
+
def equilibrium_constant_kurucz_70(tempRange, mk, Ediss, eqc):
|
|
37
|
+
minTemp = tempRange[0]
|
|
38
|
+
maxTemp = tempRange[1]
|
|
39
|
+
kB = Const.KBoltzmann
|
|
40
|
+
CM_TO_M = u.Unit('cm').to('m')
|
|
41
|
+
|
|
42
|
+
@njit('float64(float64)')
|
|
43
|
+
def kurucz_70(T):
|
|
44
|
+
if T < minTemp or T > maxTemp:
|
|
45
|
+
return 0.0
|
|
46
|
+
|
|
47
|
+
kT = kB * T
|
|
48
|
+
eq = eqc[0]
|
|
49
|
+
for i in range(1, eqc.shape[0]):
|
|
50
|
+
eq = eq * T + eqc[i]
|
|
51
|
+
arg = Ediss / kT + eq - 1.5 * mk * np.log(T)
|
|
52
|
+
eq = np.exp(arg)
|
|
53
|
+
return eq * (CM_TO_M**3)**mk
|
|
54
|
+
return kurucz_70
|
|
55
|
+
|
|
56
|
+
def equilibrium_constant_kurucz_85(tempRange, mk, Ediss, eqc):
|
|
57
|
+
minTemp = tempRange[0]
|
|
58
|
+
maxTemp = tempRange[1]
|
|
59
|
+
kB = Const.KBoltzmann
|
|
60
|
+
CM_TO_M = u.Unit('cm').to('m')
|
|
61
|
+
|
|
62
|
+
@njit('float64(float64)')
|
|
63
|
+
def kurucz_85(T):
|
|
64
|
+
if T < minTemp or T > maxTemp:
|
|
65
|
+
return 0.0
|
|
66
|
+
|
|
67
|
+
t = T * 1e-4
|
|
68
|
+
kT = kB * T
|
|
69
|
+
eq = eqc[0]
|
|
70
|
+
for i in range(1, eqc.shape[0]):
|
|
71
|
+
eq = eq * t + eqc[i]
|
|
72
|
+
eq = np.exp(Ediss / kT + eq - 1.5 * mk * np.log(T))
|
|
73
|
+
return eq * (CM_TO_M**3)**mk
|
|
74
|
+
return kurucz_85
|
|
75
|
+
|
|
76
|
+
def equilibrium_constant_sauval_tatum(tempRange, Ediss, eqc):
|
|
77
|
+
minTemp = tempRange[0]
|
|
78
|
+
maxTemp = tempRange[1]
|
|
79
|
+
kB = Const.KBoltzmann
|
|
80
|
+
THETA0 = Const.Theta0
|
|
81
|
+
Ediss = Ediss / Const.EV
|
|
82
|
+
|
|
83
|
+
@njit('float64(float64)')
|
|
84
|
+
def sauval_tatum(T):
|
|
85
|
+
if T < minTemp or T > maxTemp:
|
|
86
|
+
return 0.0
|
|
87
|
+
|
|
88
|
+
theta = THETA0 / T
|
|
89
|
+
t = np.log10(theta)
|
|
90
|
+
kT = kB * T
|
|
91
|
+
|
|
92
|
+
eq = eqc[0]
|
|
93
|
+
for i in range(1, eqc.shape[0]):
|
|
94
|
+
eq = eq * t + eqc[i]
|
|
95
|
+
eq = 10**(Ediss * theta - eq) * kT
|
|
96
|
+
return eq
|
|
97
|
+
return sauval_tatum
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Molecule:
|
|
101
|
+
'''
|
|
102
|
+
Simple class for working with RH molecule definitions.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
filePath : str
|
|
107
|
+
Path from which to load molecular data. Use
|
|
108
|
+
`get_default_molecule_path` for the path to the default RH
|
|
109
|
+
distribution of molecule files (C2, CH, CN, CO, CaH, H2+, H2, H2O,
|
|
110
|
+
HF, LiH, MgH, N2, NH, NO, O2, OH, SiO, TiO) all of which have the
|
|
111
|
+
'.molecule' extension.
|
|
112
|
+
'''
|
|
113
|
+
def __init__(self, filePath: str):
|
|
114
|
+
with open(filePath, 'r') as f:
|
|
115
|
+
lines = f.readlines()
|
|
116
|
+
|
|
117
|
+
l = get_next_line(lines)
|
|
118
|
+
self.name = l
|
|
119
|
+
l = get_next_line(lines)
|
|
120
|
+
self.charge = int(l)
|
|
121
|
+
if self.charge < 0 or self.charge > 1:
|
|
122
|
+
raise ValueError(('Only neutral or singly charged positive molecules '
|
|
123
|
+
'are allowed (%s)') % self.name)
|
|
124
|
+
|
|
125
|
+
structure = get_next_line(lines)
|
|
126
|
+
constituents = [get_constituent(s.strip()) for s in structure.split(',')]
|
|
127
|
+
self.elements = [PeriodicTable[c[1]] for c in constituents]
|
|
128
|
+
self.elementCount = [c[0] for c in constituents]
|
|
129
|
+
self.Nnuclei = sum(self.elementCount)
|
|
130
|
+
|
|
131
|
+
l = get_next_line(lines)
|
|
132
|
+
self.Ediss = float(l) * Const.EV
|
|
133
|
+
|
|
134
|
+
fitStr = get_next_line(lines)
|
|
135
|
+
self.formationTempRange = [float(f) for f in get_next_line(lines).split()]
|
|
136
|
+
if len(self.formationTempRange) != 2:
|
|
137
|
+
raise ValueError("Expected two entries for formation temperature range (%s)"
|
|
138
|
+
% self.name)
|
|
139
|
+
|
|
140
|
+
pfCoeffs = get_next_line(lines).split()
|
|
141
|
+
Npf = int(pfCoeffs[0].strip())
|
|
142
|
+
if len(pfCoeffs) != Npf+1:
|
|
143
|
+
raise ValueError("Unexpected number of partition function fit parameters (%s)"
|
|
144
|
+
% self.name)
|
|
145
|
+
self.pfCoeffs = np.array([float(f.strip()) for f in pfCoeffs[1:]][::-1])
|
|
146
|
+
|
|
147
|
+
eqcCoeffs = get_next_line(lines).split()
|
|
148
|
+
Neqc = int(eqcCoeffs[0].strip())
|
|
149
|
+
if len(eqcCoeffs) != Neqc+1:
|
|
150
|
+
raise ValueError(("Unexpected number of equilibrium coefficient "
|
|
151
|
+
"fit parameters (%s)") % self.name)
|
|
152
|
+
self.eqcCoeffs = np.array([float(f.strip()) for f in eqcCoeffs[1:]][::-1])
|
|
153
|
+
|
|
154
|
+
self.weight = 0.0
|
|
155
|
+
for count, ele in zip(self.elementCount, self.elements):
|
|
156
|
+
self.weight += count * ele.mass
|
|
157
|
+
|
|
158
|
+
if fitStr == 'KURUCZ_70':
|
|
159
|
+
self.equilibrium_constant = equilibrium_constant_kurucz_70(
|
|
160
|
+
self.formationTempRange,
|
|
161
|
+
self.Nnuclei - 1 - self.charge,
|
|
162
|
+
self.Ediss, self.eqcCoeffs)
|
|
163
|
+
elif fitStr == 'KURUCZ_85':
|
|
164
|
+
self.equilibrium_constant = equilibrium_constant_kurucz_85(
|
|
165
|
+
self.formationTempRange,
|
|
166
|
+
self.Nnuclei - 1 - self.charge,
|
|
167
|
+
self.Ediss, self.eqcCoeffs)
|
|
168
|
+
elif fitStr == 'SAUVAL_TATUM_84':
|
|
169
|
+
self.equilibrium_constant = equilibrium_constant_sauval_tatum(
|
|
170
|
+
self.formationTempRange,
|
|
171
|
+
self.Ediss, self.eqcCoeffs)
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError(('Unknown molecular equilibrium constant fit '
|
|
174
|
+
'method %s in molecule %s') % (fitStr, self.name))
|
|
175
|
+
|
|
176
|
+
class MolecularTable:
|
|
177
|
+
'''
|
|
178
|
+
Stores a set of molecular models, can be indexed by model name to return
|
|
179
|
+
the associated model (as a string).
|
|
180
|
+
'''
|
|
181
|
+
def __init__(self, paths: Optional[List[str]]=None):
|
|
182
|
+
|
|
183
|
+
self.molecules: List[Molecule] = []
|
|
184
|
+
if paths is None:
|
|
185
|
+
self.indices = OrderedDict()
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
for path in paths:
|
|
189
|
+
self.molecules.append(Molecule(path))
|
|
190
|
+
|
|
191
|
+
self.indices = OrderedDict(zip([m.name for m in self.molecules],
|
|
192
|
+
list(range(len(self.molecules)))))
|
|
193
|
+
|
|
194
|
+
def __getitem__(self, name: str) -> Molecule:
|
|
195
|
+
name = name.upper()
|
|
196
|
+
return self.molecules[self.indices[name]]
|
|
197
|
+
|
|
198
|
+
def __contains__(self, name: Union[int, Tuple[int, int], str, Element]) -> bool:
|
|
199
|
+
if type(name) is not str:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
name = name.upper() # type: ignore
|
|
203
|
+
return name in self.indices.keys()
|
|
204
|
+
|
|
205
|
+
def __len__(self) -> int:
|
|
206
|
+
return len(self.molecules)
|
|
207
|
+
|
|
208
|
+
def __iter__(self) -> 'MolecularTableIterator':
|
|
209
|
+
return MolecularTableIterator(self)
|
|
210
|
+
|
|
211
|
+
class MolecularTableIterator():
|
|
212
|
+
def __init__(self, table: MolecularTable):
|
|
213
|
+
self.table = table
|
|
214
|
+
self.index = 0
|
|
215
|
+
|
|
216
|
+
def __iter__(self):
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def __next__(self):
|
|
220
|
+
if self.index < len(self.table):
|
|
221
|
+
mol = self.table.molecules[self.index]
|
|
222
|
+
self.index += 1
|
|
223
|
+
return mol
|
|
224
|
+
|
|
225
|
+
raise StopIteration
|
lightweaver/multi.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
import astropy.units as u
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
import lightweaver.constants as C
|
|
9
|
+
from .atmosphere import Atmosphere, ScaleType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class MultiMetadata:
|
|
14
|
+
'''
|
|
15
|
+
Metadata that is stored in a MULTI atmosphere, but doesn't really belong
|
|
16
|
+
in a Lightweaver atmosphere.
|
|
17
|
+
'''
|
|
18
|
+
name: str
|
|
19
|
+
logG: float
|
|
20
|
+
|
|
21
|
+
def read_multi_atmos(filename: str) -> Tuple[MultiMetadata, Atmosphere]:
|
|
22
|
+
'''
|
|
23
|
+
Load a MULTI atmosphere definition from a file for use in Lightweaver.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
filename : str
|
|
28
|
+
The path to load the file from.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
meta : MultiMetadata
|
|
33
|
+
Additional metadata from the MULTI metadata that doesn't appear in a
|
|
34
|
+
Lightweaver atmosphere (name, gravitational acceleration used).
|
|
35
|
+
atmos : Atmosphere
|
|
36
|
+
The atmosphere loaded into the standard Lightweaver format.
|
|
37
|
+
|
|
38
|
+
Raises
|
|
39
|
+
------
|
|
40
|
+
ValueError
|
|
41
|
+
if file isn't found, or cannot be parsed correctly.
|
|
42
|
+
'''
|
|
43
|
+
try:
|
|
44
|
+
with open(filename, 'r') as f:
|
|
45
|
+
lines = f.readlines()
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
raise ValueError('Atmosphere file not found (%s)' % filename)
|
|
48
|
+
|
|
49
|
+
def get_line(commentPattern=r'^\s*\*'):
|
|
50
|
+
while len(lines) > 0:
|
|
51
|
+
line = lines.pop(0)
|
|
52
|
+
if not re.match(commentPattern, line):
|
|
53
|
+
return line.strip()
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
atmosName = get_line()
|
|
57
|
+
|
|
58
|
+
scaleStr = get_line()
|
|
59
|
+
logG = float(get_line()) - 2 # For conversion to log[m.s^-2]
|
|
60
|
+
Nspace = int(get_line())
|
|
61
|
+
|
|
62
|
+
dscale = np.zeros(Nspace)
|
|
63
|
+
temp = np.zeros(Nspace)
|
|
64
|
+
ne = np.zeros(Nspace)
|
|
65
|
+
vlos = np.zeros(Nspace)
|
|
66
|
+
vturb = np.zeros(Nspace)
|
|
67
|
+
for k in range(Nspace):
|
|
68
|
+
vals = get_line().split()
|
|
69
|
+
vals = [float(v) for v in vals]
|
|
70
|
+
dscale[k] = vals[0]
|
|
71
|
+
temp[k] = vals[1]
|
|
72
|
+
ne[k] = vals[2]
|
|
73
|
+
vlos[k] = vals[3]
|
|
74
|
+
vturb[k] = vals[4]
|
|
75
|
+
|
|
76
|
+
scaleMode = scaleStr[0].upper()
|
|
77
|
+
if scaleMode == 'M':
|
|
78
|
+
scaleType = ScaleType.ColumnMass
|
|
79
|
+
dscale = ((10**dscale) << u.Unit('g cm-2')).to('kg m-2').value
|
|
80
|
+
elif scaleMode == 'T':
|
|
81
|
+
scaleType = ScaleType.Tau500
|
|
82
|
+
dscale = 10**dscale
|
|
83
|
+
elif scaleMode == 'H':
|
|
84
|
+
scaleType = ScaleType.Geometric
|
|
85
|
+
dscale = (dscale << u.Unit('km')).to('m').value
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError('Unknown scale type: %s (expected M, T, or H)' % scaleStr)
|
|
88
|
+
|
|
89
|
+
vlos = (vlos << u.Unit('km s-1')).to('m s-1').value
|
|
90
|
+
vturb = (vturb << u.Unit('km s-1')).to('m s-1').value
|
|
91
|
+
ne = (ne << u.Unit('cm-3')).to('m-3').value
|
|
92
|
+
|
|
93
|
+
if len(lines) <= Nspace:
|
|
94
|
+
raise ValueError('Hydrogen populations not supplied!')
|
|
95
|
+
|
|
96
|
+
hPops = np.zeros((6, Nspace))
|
|
97
|
+
for k in range(Nspace):
|
|
98
|
+
vals = get_line().split()
|
|
99
|
+
vals = [float(v) for v in vals]
|
|
100
|
+
hPops[:, k] = vals
|
|
101
|
+
|
|
102
|
+
hPops = (hPops << u.Unit('cm-3')).to('m-3').value
|
|
103
|
+
|
|
104
|
+
meta = MultiMetadata(atmosName, logG)
|
|
105
|
+
atmos = Atmosphere.make_1d(scale=scaleType,
|
|
106
|
+
depthScale=dscale,
|
|
107
|
+
temperature=temp,
|
|
108
|
+
vlos=vlos,
|
|
109
|
+
vturb=vturb,
|
|
110
|
+
ne=ne,
|
|
111
|
+
hydrogenPops=hPops)
|
|
112
|
+
|
|
113
|
+
return (meta, atmos)
|
lightweaver/nr_update.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .atomic_set import lte_pops
|
|
4
|
+
from .atomic_table import PeriodicTable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def nr_post_update(self, fdCollisionRates=True, hOnly=False,
|
|
8
|
+
timeDependentData=None, chunkSize=5,
|
|
9
|
+
ngUpdate=None, printUpdate=None, extraParams=None):
|
|
10
|
+
'''
|
|
11
|
+
Compute the Newton-Raphson terms for updating the electron density
|
|
12
|
+
through charge conservation. Is attached to the Context object.
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
fdCollisionRates : bool, optional
|
|
16
|
+
Whether to use a finite difference approximation to the collisional
|
|
17
|
+
rates to find the population gradient WRT ne (default: True i.e. use
|
|
18
|
+
finite-difference, if False, collisional rates are ignored for this
|
|
19
|
+
process.)
|
|
20
|
+
hOnly : bool, optional
|
|
21
|
+
Ignore atoms other than Hydrogen (the primary electron contributor)
|
|
22
|
+
(default: False)
|
|
23
|
+
timeDependentData : dict, optional
|
|
24
|
+
The presence of this argument indicates that the time-dependent
|
|
25
|
+
formalism should be used. Should contain the keys 'dt' with a
|
|
26
|
+
floating point timestep, and 'nPrev' with a list of population
|
|
27
|
+
vectors from the start of the time integration step, in the order of
|
|
28
|
+
the active atoms. This latter term can be obtained from the previous
|
|
29
|
+
state provied by `Context.time_dep_update`.
|
|
30
|
+
chunkSize : int, optional
|
|
31
|
+
Not currently used.
|
|
32
|
+
ngUpdate : bool, optional
|
|
33
|
+
Whether to apply Ng Acceleration (default: None, to apply automatic
|
|
34
|
+
behaviour), will only accelerate if the counter on the Ng accelerator
|
|
35
|
+
has seen enough steps since the previous acceleration (set in Context
|
|
36
|
+
initialisation).
|
|
37
|
+
printUpdate : bool, optional
|
|
38
|
+
Whether to print information on the size of the update (default:
|
|
39
|
+
None, to apply automatic behaviour).
|
|
40
|
+
extraParams : dict, optional
|
|
41
|
+
Dict of extra parameters to be converted through the
|
|
42
|
+
`dict2ExtraParams` function and passed onto the C++ core.
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
dPops : float
|
|
46
|
+
The maximum relative change of any of the NLTE populations in the
|
|
47
|
+
atmosphere.
|
|
48
|
+
'''
|
|
49
|
+
if self.activeAtoms[0].element != PeriodicTable[1]:
|
|
50
|
+
raise ValueError('Calling nr_post_update without Hydrogen active.')
|
|
51
|
+
|
|
52
|
+
if ngUpdate is None:
|
|
53
|
+
ngUpdate = self.conserveCharge
|
|
54
|
+
|
|
55
|
+
if printUpdate is None:
|
|
56
|
+
printUpdate = ngUpdate
|
|
57
|
+
|
|
58
|
+
atoms = self.activeAtoms[:1] if hOnly else self.activeAtoms
|
|
59
|
+
crswVal = self.crswCallback.val
|
|
60
|
+
|
|
61
|
+
if hOnly:
|
|
62
|
+
backgroundAtoms = [model for model in self.kwargs['spect'].radSet if model.element != PeriodicTable[1]]
|
|
63
|
+
else:
|
|
64
|
+
backgroundAtoms = self.kwargs['spect'].radSet.detailedAtoms + self.kwargs['spect'].radSet.passiveAtoms
|
|
65
|
+
|
|
66
|
+
backgroundNe = np.zeros_like(self.atmos.ne)
|
|
67
|
+
for atomModel in backgroundAtoms:
|
|
68
|
+
lteStages = np.array([l.stage for l in atomModel.levels])
|
|
69
|
+
atom = self.kwargs['eqPops'].atomicPops[atomModel.element]
|
|
70
|
+
backgroundNe += (lteStages[:, None] * atom.n[:, :]).sum(axis=0)
|
|
71
|
+
|
|
72
|
+
neStart = np.copy(self.atmos.ne)
|
|
73
|
+
|
|
74
|
+
dC = []
|
|
75
|
+
if fdCollisionRates:
|
|
76
|
+
for atom in atoms:
|
|
77
|
+
atom.compute_collisions(fillDiagonal=True)
|
|
78
|
+
Cprev = np.copy(atom.C)
|
|
79
|
+
pertSize = 1e-4
|
|
80
|
+
pert = neStart * pertSize
|
|
81
|
+
self.atmos.ne[:] += pert
|
|
82
|
+
nStarPrev = np.copy(atom.nStar)
|
|
83
|
+
atom.nStar[:] = lte_pops(atom.atomicModel, self.atmos.temperature,
|
|
84
|
+
self.atmos.ne, atom.nTotal)
|
|
85
|
+
atom.compute_collisions(fillDiagonal=True)
|
|
86
|
+
self.atmos.ne[:] = neStart
|
|
87
|
+
atom.nStar[:] = nStarPrev
|
|
88
|
+
dC.append(crswVal * (atom.C - Cprev) / pert)
|
|
89
|
+
atom.C[:] = Cprev
|
|
90
|
+
|
|
91
|
+
self._nr_post_update_impl(atoms, dC, backgroundNe,
|
|
92
|
+
timeDependentData=timeDependentData, chunkSize=chunkSize, extraParams=extraParams)
|
|
93
|
+
self.eqPops.update_lte_atoms_Hmin_pops(self.atmos.pyAtmos, conserveCharge=False, quiet=True)
|
|
94
|
+
|
|
95
|
+
if ngUpdate:
|
|
96
|
+
update = self.rel_diff_ng_accelerate(printUpdate=printUpdate)
|
|
97
|
+
else:
|
|
98
|
+
update = self.rel_diff_pops(printUpdate=printUpdate)
|
|
99
|
+
neDiff = ((np.asarray(self.atmos.ne) - neStart)
|
|
100
|
+
/ np.asarray(self.atmos.ne))
|
|
101
|
+
neDiffMaxIdx = neDiff.argmax()
|
|
102
|
+
neDiffMax = neDiff[neDiffMaxIdx]
|
|
103
|
+
update.updatedNe = True
|
|
104
|
+
update.dNeMax = neDiffMax
|
|
105
|
+
update.dNeMaxIdx = neDiffMaxIdx
|
|
106
|
+
return update
|