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.

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-310-x86_64-linux-gnu.so +0 -0
  37. lightweaver/DefaultIterSchemes/SimdImpl_AVX512.cpython-310-x86_64-linux-gnu.so +0 -0
  38. lightweaver/DefaultIterSchemes/SimdImpl_SSE2.cpython-310-x86_64-linux-gnu.so +0 -0
  39. lightweaver/LwCompiled.cpython-310-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,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)
Binary file
@@ -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)
@@ -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