pythonradex 1.0.8__py3-none-any.whl → 2.0.0__py3-none-any.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.
- pythonradex/LAMDA_file.py +88 -53
- pythonradex/__init__.py +3 -1
- pythonradex/atomic_transition.py +209 -158
- pythonradex/escape_probability.py +92 -62
- pythonradex/escape_probability_functions.py +206 -165
- pythonradex/helpers.py +75 -28
- pythonradex/intensity.py +271 -0
- pythonradex/molecule.py +236 -188
- pythonradex/radiative_transfer.py +489 -344
- pythonradex/rate_equations.py +278 -189
- {pythonradex-1.0.8.dist-info → pythonradex-2.0.0.dist-info}/METADATA +17 -3
- pythonradex-2.0.0.dist-info/RECORD +15 -0
- {pythonradex-1.0.8.dist-info → pythonradex-2.0.0.dist-info}/WHEEL +1 -1
- pythonradex/flux.py +0 -209
- pythonradex-1.0.8.dist-info/RECORD +0 -15
- {pythonradex-1.0.8.dist-info → pythonradex-2.0.0.dist-info}/licenses/LICENSE.txt +0 -0
pythonradex/atomic_transition.py
CHANGED
|
@@ -11,74 +11,89 @@ from scipy import constants
|
|
|
11
11
|
import numba as nb
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@nb.jit(nopython=True,cache=True)
|
|
15
|
-
def
|
|
16
|
-
return
|
|
14
|
+
@nb.jit(nopython=True, cache=True)
|
|
15
|
+
def tau(A21, phi_nu, g_low, g_up, N1, N2, nu):
|
|
16
|
+
return (
|
|
17
|
+
constants.c**2 / (8 * np.pi * nu**2) * A21 * phi_nu * (g_up / g_low * N1 - N2)
|
|
18
|
+
)
|
|
17
19
|
|
|
18
|
-
@nb.jit(nopython=True,cache=True,error_model='numpy')
|
|
19
|
-
def Tex(Delta_E,g_low,g_up,x1,x2):
|
|
20
|
-
return np.where((x1==0) & (x2==0),0,
|
|
21
|
-
-Delta_E/(constants.k*np.log(g_low*x2/(g_up*x1))))
|
|
22
20
|
|
|
23
|
-
@nb.jit(nopython=True,cache=True)
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
@nb.jit(nopython=True, cache=True, error_model="numpy")
|
|
22
|
+
def Tex(Delta_E, g_low, g_up, x1, x2):
|
|
23
|
+
return np.where(
|
|
24
|
+
(x1 == 0) & (x2 == 0),
|
|
25
|
+
0,
|
|
26
|
+
-Delta_E / (constants.k * np.log(g_low * x2 / (g_up * x1))),
|
|
27
|
+
)
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
@nb.jit(nopython=True, cache=True)
|
|
31
|
+
def compute_K12(K21, g_up, g_low, Delta_E, Tkin):
|
|
32
|
+
# see RADEX manual
|
|
33
|
+
return g_up / g_low * K21 * np.exp(-Delta_E / (constants.k * Tkin))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LineProfile:
|
|
37
|
+
"""Abstract class representing a general, normalised line profile."""
|
|
32
38
|
|
|
33
39
|
n_nu_elements_for_coarse_array = 10
|
|
34
40
|
|
|
35
|
-
def __init__(self,nu0,width_v):
|
|
41
|
+
def __init__(self, nu0, width_v):
|
|
36
42
|
self.nu0 = nu0
|
|
37
43
|
self.width_v = width_v
|
|
38
|
-
self.width_nu = helpers.Delta_nu(Delta_v=self.width_v,nu0=self.nu0)
|
|
44
|
+
self.width_nu = helpers.Delta_nu(Delta_v=self.width_v, nu0=self.nu0)
|
|
39
45
|
self.initialise_phi_nu_params()
|
|
40
|
-
|
|
41
|
-
# self.coarse_phi_nu_array = self.phi_nu(nu=self.coarse_nu_array)
|
|
46
|
+
self.set_nu_extent()
|
|
42
47
|
self.phi_nu0 = self.phi_nu(nu=self.nu0)
|
|
43
48
|
|
|
44
49
|
def initialise_phi_nu_params(self):
|
|
45
50
|
raise NotImplementedError
|
|
46
|
-
|
|
47
|
-
def phi_nu(self,nu):
|
|
48
|
-
r
|
|
49
|
-
|
|
51
|
+
|
|
52
|
+
def phi_nu(self, nu):
|
|
53
|
+
r"""The value of the line profile in frequency space.
|
|
54
|
+
|
|
50
55
|
The line profile is normalised such that its integral over frequency equals 1.
|
|
51
56
|
|
|
52
57
|
Args:
|
|
53
58
|
nu (float or numpy.ndarray): frequency in [Hz]
|
|
54
|
-
|
|
59
|
+
|
|
55
60
|
Returns:
|
|
56
61
|
float or numpy.ndarray: The line profile in [Hz\ :sup:`-1`]
|
|
57
|
-
|
|
62
|
+
"""
|
|
58
63
|
raise NotImplementedError
|
|
59
64
|
|
|
60
|
-
def phi_v(self,v):
|
|
61
|
-
r
|
|
62
|
-
|
|
65
|
+
def phi_v(self, v):
|
|
66
|
+
r"""The value of the line profile in velocity space.
|
|
67
|
+
|
|
63
68
|
The line profile is normalised such that its integral over velocity equals 1.
|
|
64
69
|
|
|
65
70
|
Args:
|
|
66
71
|
v (float or numpy.ndarray): velocity in [m/s]
|
|
67
|
-
|
|
72
|
+
|
|
68
73
|
Returns:
|
|
69
74
|
float or numpy.ndarra: The line profile in [(m/s)\ :sup:`-1`]
|
|
70
|
-
|
|
71
|
-
nu = self.nu0*(1-v/constants.c)
|
|
72
|
-
return self.phi_nu(nu)*self.nu0/constants.c
|
|
75
|
+
"""
|
|
76
|
+
nu = self.nu0 * (1 - v / constants.c)
|
|
77
|
+
return self.phi_nu(nu) * self.nu0 / constants.c
|
|
73
78
|
|
|
74
|
-
def average_over_phi_nu(self,func):
|
|
79
|
+
def average_over_phi_nu(self, func):
|
|
75
80
|
raise NotImplementedError
|
|
76
81
|
|
|
82
|
+
def set_nu_extent(self):
|
|
83
|
+
# this function should calculate the min and max frequency covered by the
|
|
84
|
+
# profile; this is used to calculate overlapping lines
|
|
85
|
+
raise NotImplementedError
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
def covers_frequency(self, nu):
|
|
88
|
+
# check if the line profile covers a given frequency
|
|
89
|
+
# don't do self.nu_min <= nu <= self.nu_max, otherwise it does not work
|
|
90
|
+
# if nu is an array
|
|
91
|
+
return (self.nu_min <= nu) & (nu <= self.nu_max)
|
|
79
92
|
|
|
93
|
+
|
|
94
|
+
class GaussianLineProfile(LineProfile):
|
|
80
95
|
"""Represents a Gaussian line profile
|
|
81
|
-
|
|
96
|
+
|
|
82
97
|
Attributes:
|
|
83
98
|
nu0 (:obj:`float`): rest frequency in [Hz]
|
|
84
99
|
width_nu (:obj:`float`): FWHM in [Hz]
|
|
@@ -87,125 +102,143 @@ class GaussianLineProfile(LineProfile):
|
|
|
87
102
|
|
|
88
103
|
def initialise_phi_nu_params(self):
|
|
89
104
|
self.sigma_nu = helpers.FWHM2sigma(self.width_nu)
|
|
90
|
-
self.normalisation = 1/(self.sigma_nu*np.sqrt(2*np.pi))
|
|
91
|
-
|
|
92
|
-
def phi_nu(self,nu):
|
|
93
|
-
return self.normalisation*np.exp(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
105
|
+
self.normalisation = 1 / (self.sigma_nu * np.sqrt(2 * np.pi))
|
|
106
|
+
|
|
107
|
+
def phi_nu(self, nu):
|
|
108
|
+
return self.normalisation * np.exp(
|
|
109
|
+
-((nu - self.nu0) ** 2) / (2 * self.sigma_nu**2)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def average_over_phi_nu(self, func):
|
|
113
|
+
# choose +- 1 FWHM initially, where the Gaussian is at ~6% of the peak
|
|
114
|
+
# note that at least two iterations are done, so the final width is
|
|
115
|
+
# at least +-1.65 FWHM
|
|
116
|
+
width = 2 # units of width_nu
|
|
100
117
|
elements_per_width = 20
|
|
101
118
|
old_average = np.inf
|
|
102
119
|
residual = np.inf
|
|
103
120
|
niter = 0
|
|
104
121
|
while residual > 1e-2:
|
|
105
|
-
n_elements = int(elements_per_width*width)
|
|
106
|
-
nu = np.linspace(
|
|
107
|
-
|
|
122
|
+
n_elements = int(elements_per_width * width)
|
|
123
|
+
nu = np.linspace(
|
|
124
|
+
self.nu0 - width / 2 * self.width_nu,
|
|
125
|
+
self.nu0 + width / 2 * self.width_nu,
|
|
126
|
+
n_elements,
|
|
127
|
+
)
|
|
108
128
|
phi_nu = self.phi_nu(nu)
|
|
109
|
-
norm = np.trapezoid(y=phi_nu,x=nu)
|
|
110
|
-
new_average = np.trapezoid(y=func(nu)*phi_nu,x=nu)/norm
|
|
111
|
-
residual = helpers.relative_difference(
|
|
112
|
-
|
|
129
|
+
norm = np.trapezoid(y=phi_nu, x=nu)
|
|
130
|
+
new_average = np.trapezoid(y=func(nu) * phi_nu, x=nu) / norm
|
|
131
|
+
residual = helpers.relative_difference(
|
|
132
|
+
a=np.array((new_average,)), b=np.array((old_average,))
|
|
133
|
+
)
|
|
113
134
|
old_average = new_average
|
|
114
|
-
#I want to cover at least the width corresponding to the definition of
|
|
115
|
-
#overlapping lines, which is 3.14; so add 1.3 to make it 2+1.3=3.3
|
|
135
|
+
# I want to cover at least the width corresponding to the definition of
|
|
136
|
+
# overlapping lines, which is 3.14; so add 1.3 to make it 2+1.3=3.3
|
|
116
137
|
width += 1.3
|
|
117
138
|
elements_per_width += 15
|
|
118
139
|
niter += 1
|
|
119
140
|
return new_average
|
|
120
141
|
|
|
142
|
+
def set_nu_extent(self):
|
|
143
|
+
# a Gaussian is never zero, so I choose +- 1.57 FWHM, where the Gaussian
|
|
144
|
+
# is 0.1% of the peak
|
|
145
|
+
self.nu_min = self.nu0 - 1.57 * self.width_nu
|
|
146
|
+
self.nu_max = self.nu0 + 1.57 * self.width_nu
|
|
121
147
|
|
|
122
|
-
class RectangularLineProfile(LineProfile):
|
|
123
148
|
|
|
124
|
-
|
|
149
|
+
class RectangularLineProfile(LineProfile):
|
|
150
|
+
"""Represents a rectangular line profile, i.e. constant over width_v, 0 outside"""
|
|
125
151
|
|
|
126
152
|
def initialise_phi_nu_params(self):
|
|
127
|
-
self.normalisation = 1/self.width_nu
|
|
153
|
+
self.normalisation = 1 / self.width_nu
|
|
128
154
|
|
|
129
|
-
def phi_nu(self,nu):
|
|
130
|
-
inside_line = (nu>=self.
|
|
131
|
-
return np.where(inside_line,self.normalisation,0)
|
|
155
|
+
def phi_nu(self, nu):
|
|
156
|
+
inside_line = (nu >= self.nu_min) & (nu <= self.nu_max)
|
|
157
|
+
return np.where(inside_line, self.normalisation, 0)
|
|
132
158
|
|
|
133
|
-
def average_over_phi_nu(self,func):
|
|
134
|
-
#easier than Gaussian, just have to iterate over n_elements
|
|
135
|
-
#note that the func doesn't need to be constant over the line profile,
|
|
136
|
-
#so I need to have a sufficient number of elements
|
|
159
|
+
def average_over_phi_nu(self, func):
|
|
160
|
+
# easier than Gaussian, just have to iterate over n_elements
|
|
161
|
+
# note that the func doesn't need to be constant over the line profile,
|
|
162
|
+
# so I need to have a sufficient number of elements
|
|
137
163
|
n_elements = 20
|
|
138
164
|
old_average = np.inf
|
|
139
165
|
residual = np.inf
|
|
140
166
|
while residual > 1e-2:
|
|
141
|
-
nu = np.linspace(self.
|
|
142
|
-
n_elements)
|
|
167
|
+
nu = np.linspace(self.nu_min, self.nu_max, n_elements)
|
|
143
168
|
phi_nu = self.phi_nu(nu)
|
|
144
|
-
norm = np.trapezoid(y=phi_nu,x=nu)
|
|
145
|
-
new_average = np.trapezoid(y=func(nu)*phi_nu,x=nu)/norm
|
|
146
|
-
residual = helpers.relative_difference(
|
|
147
|
-
|
|
169
|
+
norm = np.trapezoid(y=phi_nu, x=nu)
|
|
170
|
+
new_average = np.trapezoid(y=func(nu) * phi_nu, x=nu) / norm
|
|
171
|
+
residual = helpers.relative_difference(
|
|
172
|
+
a=np.array((new_average,)), b=np.array((old_average,))
|
|
173
|
+
)
|
|
148
174
|
old_average = new_average
|
|
149
175
|
n_elements += 15
|
|
150
176
|
return new_average
|
|
151
177
|
|
|
152
|
-
|
|
178
|
+
def set_nu_extent(self):
|
|
179
|
+
self.nu_min = self.nu0 - self.width_nu / 2
|
|
180
|
+
self.nu_max = self.nu0 + self.width_nu / 2
|
|
181
|
+
|
|
153
182
|
|
|
183
|
+
line_profiles = {"Gaussian": GaussianLineProfile, "rectangular": RectangularLineProfile}
|
|
154
184
|
|
|
155
|
-
|
|
156
|
-
|
|
185
|
+
|
|
186
|
+
class Level:
|
|
187
|
+
"""Represents an atomic / molecular level.
|
|
157
188
|
|
|
158
189
|
Attributes:
|
|
159
190
|
g (:obj:`float`): the statistical weight of the level
|
|
160
191
|
E (:obj:`float`): the energy of the level in [J]
|
|
161
|
-
|
|
162
|
-
|
|
192
|
+
index (:obj:`int`): the index of the level; if constructed from a
|
|
193
|
+
LAMDA-formatted file, this index indicates the postiion in the file,
|
|
194
|
+
with the first level listed in the file having index 0.
|
|
195
|
+
"""
|
|
163
196
|
|
|
164
|
-
def __init__(self,g,E,
|
|
197
|
+
def __init__(self, g, E, index):
|
|
165
198
|
self.g = g
|
|
166
199
|
self.E = E
|
|
167
|
-
self.
|
|
200
|
+
self.index = index
|
|
168
201
|
|
|
169
|
-
def
|
|
170
|
-
|
|
202
|
+
def Boltzmann_level_population(self, Z, T):
|
|
203
|
+
"""Calculates the fractional population of the level using a Boltzmann
|
|
204
|
+
distribution.
|
|
171
205
|
|
|
172
206
|
Args:
|
|
173
207
|
Z (:obj:`float`): The partition function.
|
|
174
208
|
T (:obj:`float`): The temperature in [K].
|
|
175
|
-
|
|
209
|
+
|
|
176
210
|
Returns:
|
|
177
|
-
float: The fractional population of the level
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
|
|
211
|
+
float: The fractional population of the level using a Boltzmann
|
|
212
|
+
distribution.
|
|
213
|
+
"""
|
|
214
|
+
# this is a convenience function that is not used when solving the non-LTE
|
|
215
|
+
# radiative transfer
|
|
216
|
+
return self.g * np.exp(-self.E / (constants.k * T)) / Z
|
|
182
217
|
|
|
183
218
|
|
|
184
|
-
class Transition
|
|
219
|
+
class Transition:
|
|
185
220
|
|
|
186
|
-
def __init__(self,up,low):
|
|
221
|
+
def __init__(self, up, low):
|
|
187
222
|
self.up = up
|
|
188
223
|
self.low = low
|
|
189
|
-
self.Delta_E = self.up.E-self.low.E
|
|
190
|
-
self.name = f
|
|
224
|
+
self.Delta_E = self.up.E - self.low.E
|
|
225
|
+
self.name = f"{self.up.index}-{self.low.index}"
|
|
226
|
+
|
|
227
|
+
def Tex(self, x1, x2):
|
|
228
|
+
"""Computes the excitation temperature.
|
|
191
229
|
|
|
192
|
-
def Tex(self,x1,x2):
|
|
193
|
-
'''Computes the excitation temperature.
|
|
194
|
-
|
|
195
230
|
Args:
|
|
196
231
|
x1: (numpy.ndarray): fractional population of the lower level
|
|
197
232
|
x2: (numpy.ndarray): fractional population of the upper level
|
|
198
233
|
|
|
199
234
|
Returns:
|
|
200
235
|
numpy.ndarray: excitation temperature in [K]
|
|
201
|
-
|
|
202
|
-
return Tex(Delta_E=self.Delta_E,g_up=self.up.g,g_low=self.low.g,
|
|
203
|
-
x1=x1,x2=x2)
|
|
236
|
+
"""
|
|
237
|
+
return Tex(Delta_E=self.Delta_E, g_up=self.up.g, g_low=self.low.g, x1=x1, x2=x2)
|
|
204
238
|
|
|
205
239
|
|
|
206
240
|
class RadiativeTransition(Transition):
|
|
207
|
-
|
|
208
|
-
r'''Represents the radiative transition between two energy levels.
|
|
241
|
+
r"""Represents the radiative transition between two energy levels.
|
|
209
242
|
|
|
210
243
|
Attributes:
|
|
211
244
|
up (pythonradex.atomic_transition.Level): the upper level of the transition
|
|
@@ -216,25 +249,27 @@ class RadiativeTransition(Transition):
|
|
|
216
249
|
A21 (:obj:`float`): Einstein A21 coefficient in [s\ :sup:`-1`]
|
|
217
250
|
B21 (:obj:`float`): Einstein B21 coefficient in [sr m\ :sup:`2` Hz / Jy]
|
|
218
251
|
B12 (:obj:`float`): Einstein B12 coefficient in [sr m\ :sup:`2` Hz / Jy]
|
|
219
|
-
|
|
252
|
+
"""
|
|
220
253
|
|
|
221
|
-
def __init__(self,up,low,A21,nu0=None):
|
|
222
|
-
Transition.__init__(self,up=up,low=low)
|
|
223
|
-
assert self.Delta_E > 0,
|
|
254
|
+
def __init__(self, up, low, A21, nu0=None):
|
|
255
|
+
Transition.__init__(self, up=up, low=low)
|
|
256
|
+
assert self.Delta_E > 0, "non-positive Delta_E for radiative transition"
|
|
224
257
|
self.A21 = A21
|
|
225
|
-
nu0_from_Delta_E = self.Delta_E/constants.h
|
|
258
|
+
nu0_from_Delta_E = self.Delta_E / constants.h
|
|
226
259
|
if nu0 is None:
|
|
227
260
|
self.nu0 = nu0_from_Delta_E
|
|
228
261
|
else:
|
|
229
|
-
#when reading LAMDA files, it is useful to specify nu0 directly, since
|
|
230
|
-
#it is sometimes given with more significant digits
|
|
231
|
-
assert np.isclose(nu0_from_Delta_E,nu0,atol=0,rtol=1e-3)
|
|
262
|
+
# when reading LAMDA files, it is useful to specify nu0 directly, since
|
|
263
|
+
# it is sometimes given with more significant digits
|
|
264
|
+
assert np.isclose(nu0_from_Delta_E, nu0, atol=0, rtol=1e-3)
|
|
232
265
|
self.nu0 = nu0
|
|
233
|
-
self.B21 = helpers.B21(A21=self.A21,nu=self.nu0)
|
|
234
|
-
self.B12 = helpers.B12(A21=self.A21,nu=self.nu0,g1=self.low.g,g2=self.up.g)
|
|
266
|
+
self.B21 = helpers.B21(A21=self.A21, nu=self.nu0)
|
|
267
|
+
self.B12 = helpers.B12(A21=self.A21, nu=self.nu0, g1=self.low.g, g2=self.up.g)
|
|
235
268
|
|
|
236
|
-
def source_function(self,x1,x2):
|
|
237
|
-
S = np.where(
|
|
269
|
+
def source_function(self, x1, x2):
|
|
270
|
+
S = np.where(
|
|
271
|
+
(x1 == 0) & (x2 == 0), 0, self.A21 * x2 / (x1 * self.B12 - x2 * self.B21)
|
|
272
|
+
)
|
|
238
273
|
# if np.any(S < 0):
|
|
239
274
|
# raise RuntimeError('negative source function, potentially due to '
|
|
240
275
|
# +'high columndensity and/or low collider density')
|
|
@@ -242,10 +277,9 @@ class RadiativeTransition(Transition):
|
|
|
242
277
|
|
|
243
278
|
|
|
244
279
|
class EmissionLine(RadiativeTransition):
|
|
245
|
-
|
|
246
|
-
r'''Represents an emission line arising from the radiative transition between
|
|
280
|
+
r"""Represents an emission line arising from the radiative transition between
|
|
247
281
|
two levels
|
|
248
|
-
|
|
282
|
+
|
|
249
283
|
Attributes:
|
|
250
284
|
up (pythonradex.atomic_transition.Level): the upper level of the transition
|
|
251
285
|
low (pythonradex.atomic_transition.Level): the lower level of the transition
|
|
@@ -258,24 +292,33 @@ class EmissionLine(RadiativeTransition):
|
|
|
258
292
|
B12 (:obj:`float`): Einstein B12 coefficient in [sr m\ :sup:`2` Hz / Jy]
|
|
259
293
|
line_profile (pythonradex.atomic_transition.LineProfile): object representing
|
|
260
294
|
the shape of the line profile
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
self
|
|
265
|
-
self.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(self, up, low, A21, line_profile_type, width_v, nu0=None):
|
|
298
|
+
RadiativeTransition.__init__(self, up=up, low=low, A21=A21, nu0=nu0)
|
|
299
|
+
self.line_profile = line_profiles[line_profile_type](
|
|
300
|
+
nu0=self.nu0, width_v=width_v
|
|
301
|
+
)
|
|
302
|
+
self.tau_kwargs = {"A21": self.A21, "g_up": self.up.g, "g_low": self.low.g}
|
|
266
303
|
|
|
267
304
|
@classmethod
|
|
268
|
-
def from_radiative_transition(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
def from_radiative_transition(
|
|
306
|
+
cls, radiative_transition, line_profile_type, width_v
|
|
307
|
+
):
|
|
308
|
+
"""Alternative constructor, taking an instance of RadiativeTransition,
|
|
309
|
+
a line profile class and the width of the line"""
|
|
310
|
+
return cls(
|
|
311
|
+
up=radiative_transition.up,
|
|
312
|
+
low=radiative_transition.low,
|
|
313
|
+
A21=radiative_transition.A21,
|
|
314
|
+
line_profile_type=line_profile_type,
|
|
315
|
+
width_v=width_v,
|
|
316
|
+
nu0=radiative_transition.nu0,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def tau(self, N1, N2, nu):
|
|
320
|
+
r"""Computes the optical depth
|
|
321
|
+
|
|
279
322
|
Args:
|
|
280
323
|
N1: (:obj:`float`): column density of molecules in the lower level
|
|
281
324
|
in [m\ :sup:`-2`]
|
|
@@ -285,14 +328,15 @@ class EmissionLine(RadiativeTransition):
|
|
|
285
328
|
|
|
286
329
|
Returns:
|
|
287
330
|
numpy.ndarray: the optical depth at the requested frequencies
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
return tau(
|
|
334
|
+
phi_nu=self.line_profile.phi_nu(nu), N1=N1, N2=N2, nu=nu, **self.tau_kwargs
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def tau_nu0(self, N1, N2):
|
|
338
|
+
r"""Computes the optical depth at the rest frequency
|
|
339
|
+
|
|
296
340
|
Args:
|
|
297
341
|
N1: (:obj:`float` or numpy.ndarray): column density of molecules
|
|
298
342
|
in the lower level in [m\ :sup:`-2`]
|
|
@@ -301,33 +345,37 @@ class EmissionLine(RadiativeTransition):
|
|
|
301
345
|
|
|
302
346
|
Returns:
|
|
303
347
|
float or numpy.ndarray: the optical depth at the rest frequency
|
|
304
|
-
|
|
305
|
-
'''
|
|
306
|
-
return tau_nu(phi_nu=self.line_profile.phi_nu0,N1=N1,N2=N2,nu=self.nu0,
|
|
307
|
-
**self.tau_kwargs)
|
|
308
348
|
|
|
309
|
-
|
|
349
|
+
"""
|
|
350
|
+
return tau(
|
|
351
|
+
phi_nu=self.line_profile.phi_nu0,
|
|
352
|
+
N1=N1,
|
|
353
|
+
N2=N2,
|
|
354
|
+
nu=self.nu0,
|
|
355
|
+
**self.tau_kwargs,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
310
359
|
class CollisionalTransition(Transition):
|
|
360
|
+
"""Represents the collisional transition between two energy levels
|
|
311
361
|
|
|
312
|
-
'''Represents the collisional transition between two energy levels
|
|
313
|
-
|
|
314
362
|
Attributes:
|
|
315
363
|
up (pythonradex.atomic_transition.Level): the upper level of the transition
|
|
316
364
|
low (pythonradex.atomic_transition.Level): the lower level of the transition
|
|
317
365
|
Delta_E (:obj:`float`): the energy difference between the upper and lower level
|
|
318
366
|
in [J]
|
|
319
367
|
name (:obj:`str`): name of the transition
|
|
320
|
-
|
|
368
|
+
"""
|
|
321
369
|
|
|
322
|
-
def __init__(self,up,low,K21_data,Tkin_data):
|
|
323
|
-
Transition.__init__(self,up=up,low=low)
|
|
370
|
+
def __init__(self, up, low, K21_data, Tkin_data):
|
|
371
|
+
Transition.__init__(self, up=up, low=low)
|
|
324
372
|
assert np.all(K21_data >= 0)
|
|
325
373
|
self.K21_data = K21_data
|
|
326
374
|
self.Tkin_data = Tkin_data
|
|
327
|
-
self.Tkin_data_limits = np.min(self.Tkin_data),np.max(self.Tkin_data)
|
|
375
|
+
self.Tkin_data_limits = np.min(self.Tkin_data), np.max(self.Tkin_data)
|
|
328
376
|
|
|
329
|
-
def coeffs(self,Tkin):
|
|
330
|
-
r
|
|
377
|
+
def coeffs(self, Tkin):
|
|
378
|
+
r"""
|
|
331
379
|
Computes the collisional coefficients
|
|
332
380
|
|
|
333
381
|
Args:
|
|
@@ -339,13 +387,16 @@ class CollisionalTransition(Transition):
|
|
|
339
387
|
|
|
340
388
|
Raises:
|
|
341
389
|
AssertionError: If Tkin is outside the available temperature range.
|
|
342
|
-
|
|
343
|
-
Tmin,Tmax = self.Tkin_data_limits
|
|
344
|
-
assert np.all(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
390
|
+
"""
|
|
391
|
+
Tmin, Tmax = self.Tkin_data_limits
|
|
392
|
+
assert np.all(
|
|
393
|
+
Tmin <= Tkin
|
|
394
|
+
), "requested temperature below minimum collider temperature"
|
|
395
|
+
assert np.all(
|
|
396
|
+
Tkin <= Tmax
|
|
397
|
+
), "requested temperature above maximum collider temperature"
|
|
398
|
+
K21 = np.interp(Tkin, self.Tkin_data, self.K21_data)
|
|
399
|
+
K12 = compute_K12(
|
|
400
|
+
K21=K21, g_up=self.up.g, g_low=self.low.g, Delta_E=self.Delta_E, Tkin=Tkin
|
|
401
|
+
)
|
|
402
|
+
return [K12, K21]
|