ppdmod 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.
- ppdmod/__init__.py +1 -0
- ppdmod/base.py +225 -0
- ppdmod/components.py +557 -0
- ppdmod/config/standard_parameters.toml +290 -0
- ppdmod/data.py +485 -0
- ppdmod/fitting.py +546 -0
- ppdmod/options.py +164 -0
- ppdmod/parameter.py +152 -0
- ppdmod/plot.py +1241 -0
- ppdmod/utils.py +575 -0
- ppdmod-2.0.0.dist-info/METADATA +68 -0
- ppdmod-2.0.0.dist-info/RECORD +15 -0
- ppdmod-2.0.0.dist-info/WHEEL +5 -0
- ppdmod-2.0.0.dist-info/licenses/LICENSE +21 -0
- ppdmod-2.0.0.dist-info/top_level.txt +1 -0
ppdmod/components.py
ADDED
@@ -0,0 +1,557 @@
|
|
1
|
+
from functools import partial
|
2
|
+
|
3
|
+
import astropy.units as u
|
4
|
+
import numpy as np
|
5
|
+
from astropy.modeling.models import BlackBody
|
6
|
+
from scipy.interpolate import interp1d
|
7
|
+
from scipy.special import j0, jv
|
8
|
+
|
9
|
+
from .base import Component, FourierComponent
|
10
|
+
from .options import OPTIONS
|
11
|
+
from .parameter import Parameter
|
12
|
+
from .utils import compare_angles
|
13
|
+
|
14
|
+
|
15
|
+
class NBandFit(Component):
|
16
|
+
name = "NBandFit"
|
17
|
+
description = "A fit to the SED of a star."
|
18
|
+
label = "NBandFit"
|
19
|
+
|
20
|
+
def __init__(self, **kwargs):
|
21
|
+
"""The class's constructor."""
|
22
|
+
super().__init__(**kwargs)
|
23
|
+
self.tempc = Parameter(base="tempc")
|
24
|
+
self.pah = Parameter(base="pah")
|
25
|
+
self.scale_pah = Parameter(base="scale_pah")
|
26
|
+
self.f = Parameter(
|
27
|
+
name="f",
|
28
|
+
unit=u.one,
|
29
|
+
description="Offset",
|
30
|
+
free=True,
|
31
|
+
base="f",
|
32
|
+
)
|
33
|
+
|
34
|
+
self.materials = list(
|
35
|
+
["_".join(key.split("_")[1:]) for key in kwargs.keys() if "kappa" in key]
|
36
|
+
)
|
37
|
+
|
38
|
+
for material in self.materials:
|
39
|
+
for prefix in ["kappa", "weight"]:
|
40
|
+
key = "kappa_sil" if prefix == "kappa" else "weight_cont"
|
41
|
+
param_name = f"{prefix}_{material}"
|
42
|
+
param = Parameter(
|
43
|
+
name=param_name,
|
44
|
+
description=f"The mass fraction for {param_name}",
|
45
|
+
base=key,
|
46
|
+
)
|
47
|
+
|
48
|
+
setattr(self, param_name, param)
|
49
|
+
|
50
|
+
self.eval(**kwargs)
|
51
|
+
|
52
|
+
def get_opacity(
|
53
|
+
self, t: int, wl: u.um, kind: str = "comb", norm: bool = False
|
54
|
+
) -> u.cm**2 / u.g:
|
55
|
+
"""Gets the summed opacities."""
|
56
|
+
weights, kappas = [], []
|
57
|
+
for m in self.materials:
|
58
|
+
if kind == "sil" and m == "cont":
|
59
|
+
continue
|
60
|
+
elif kind == "cont" and m != "cont":
|
61
|
+
continue
|
62
|
+
|
63
|
+
weights.append(getattr(self, f"weight_{m}")(t, wl).value / 1e2)
|
64
|
+
kappas.append(getattr(self, f"kappa_{m}")(t, wl).value)
|
65
|
+
|
66
|
+
wnorm = np.sum(weights) if norm else 1
|
67
|
+
return np.sum([w * k / wnorm for w, k in zip(weights, kappas)], axis=0)
|
68
|
+
|
69
|
+
def flux_func(self, t: int, wl: u.um, **kwargs) -> np.ndarray:
|
70
|
+
"""Returns the flux weight of the point source."""
|
71
|
+
bb = BlackBody(temperature=self.tempc(t, wl))(wl)
|
72
|
+
opacity = self.get_opacity(t, wl, **kwargs)
|
73
|
+
flux = (bb * opacity * u.sr * 10.0**-self.f.value).to(u.Jy)
|
74
|
+
if kwargs.get("kind", "comb") == "comb":
|
75
|
+
flux += self.scale_pah(t, wl) * self.pah(t, wl)
|
76
|
+
return flux.value.reshape((wl.size, 1))
|
77
|
+
|
78
|
+
|
79
|
+
class Point(FourierComponent):
|
80
|
+
"""Point source."""
|
81
|
+
|
82
|
+
name = "Point"
|
83
|
+
description = "Point source."
|
84
|
+
|
85
|
+
def __init__(self, **kwargs):
|
86
|
+
super().__init__(**kwargs)
|
87
|
+
self.f = Parameter(base="f")
|
88
|
+
self.dist = Parameter(base="dist")
|
89
|
+
self.eval(**kwargs)
|
90
|
+
|
91
|
+
def flux_func(self, t: int, wl: u.um) -> np.ndarray:
|
92
|
+
"""Computes the flux of the star."""
|
93
|
+
flux = self.f(t, wl).value
|
94
|
+
if not isinstance(flux, (tuple, list, np.ndarray)):
|
95
|
+
flux = np.array([flux])[:, np.newaxis]
|
96
|
+
else:
|
97
|
+
flux = flux.reshape((wl.size, 1))
|
98
|
+
return flux
|
99
|
+
|
100
|
+
def vis_func(
|
101
|
+
self, spf: 1 / u.rad, psi: u.rad, t: int, wl: u.um, **kwargs
|
102
|
+
) -> np.ndarray:
|
103
|
+
"""Computes the complex visibility."""
|
104
|
+
vis = np.zeros_like(spf).value
|
105
|
+
vis[:] = self.flux_func(t, wl)[
|
106
|
+
(...,) + (len(spf.shape[1:]) - 1) * (np.newaxis,)
|
107
|
+
]
|
108
|
+
return vis.astype(OPTIONS.data.dtype.complex)
|
109
|
+
|
110
|
+
def image_func(
|
111
|
+
self, xx: u.mas, yy: u.mas, pixel_size: u.mas, t: int = None, wl: u.m = None
|
112
|
+
) -> np.ndarray:
|
113
|
+
"""Computes the image from a 2D grid."""
|
114
|
+
image, rho = np.zeros((wl.size, *xx.shape)), np.hypot(xx, yy)
|
115
|
+
y_ind, x_ind = np.unravel_index(np.argmin(rho), xx.shape)
|
116
|
+
star_flux = (self.compute_flux(t, wl) / 4)[..., np.newaxis]
|
117
|
+
image[:, y_ind - 1 : y_ind + 1, x_ind - 1 : x_ind + 1] = star_flux
|
118
|
+
return image
|
119
|
+
|
120
|
+
|
121
|
+
class Gauss(FourierComponent):
|
122
|
+
"""A Gaussian.
|
123
|
+
|
124
|
+
Parameters
|
125
|
+
----------
|
126
|
+
fwhm : astropy.units.mas
|
127
|
+
The FWHM of the Gaussian.
|
128
|
+
"""
|
129
|
+
|
130
|
+
name = "Gauss"
|
131
|
+
description = "A 2D Gaussian"
|
132
|
+
|
133
|
+
def __init__(self, **kwargs):
|
134
|
+
super().__init__(**kwargs)
|
135
|
+
self.f = Parameter(base="f")
|
136
|
+
self.dist = Parameter(base="dist")
|
137
|
+
self.fwhm = Parameter(base="fwhm")
|
138
|
+
|
139
|
+
self.eval(**kwargs)
|
140
|
+
|
141
|
+
def flux_func(self, t: int, wl: u.um) -> np.ndarray:
|
142
|
+
"""Computes the total flux."""
|
143
|
+
return self.f(t, wl).value.astype(OPTIONS.data.dtype.real)
|
144
|
+
|
145
|
+
def vis_func(
|
146
|
+
self, spf: 1 / u.rad, psi: u.rad, t: int, wl: u.um, **kwargs
|
147
|
+
) -> np.ndarray:
|
148
|
+
"""Computes the complex visibility."""
|
149
|
+
if self.fwhm(t, wl).unit == u.au:
|
150
|
+
fwhm_mas = ((self.fwhm(t, wl) / self.dist(t, wl)).value * u.arcsec).to(
|
151
|
+
u.mas
|
152
|
+
)
|
153
|
+
else:
|
154
|
+
fwhm_mas = self.fwhm(t, wl)
|
155
|
+
|
156
|
+
return self.flux_func(t, wl)[:, np.newaxis] * np.exp(
|
157
|
+
-((np.pi * fwhm_mas.to(u.rad) * spf) ** 2) / (4 * np.log(2))
|
158
|
+
)
|
159
|
+
|
160
|
+
def image_func(
|
161
|
+
self,
|
162
|
+
xx: u.mas,
|
163
|
+
yy: u.mas,
|
164
|
+
pixel_size: u.mas,
|
165
|
+
t: int = None,
|
166
|
+
wl: u.m = None,
|
167
|
+
**kwargs,
|
168
|
+
) -> np.ndarray:
|
169
|
+
"""Computes the image from a 2D grid."""
|
170
|
+
if self.fwhm(t, wl).unit == u.au:
|
171
|
+
fwhm_mas = ((self.fwhm(t, wl) / self.dist(t, wl)).value * u.arcsec).to(
|
172
|
+
u.mas
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
fwhm_mas = self.fwhm(t, wl)
|
176
|
+
|
177
|
+
image = np.exp(-4 * np.log(2) * np.hypot(xx, yy) ** 2 / fwhm_mas**2)
|
178
|
+
factor = self.flux_func(t, wl) / np.sum(image)
|
179
|
+
return factor * image.reshape(1, *image.shape).value
|
180
|
+
|
181
|
+
|
182
|
+
class BBGauss(Gauss):
|
183
|
+
"""A blackbody Gaussian.
|
184
|
+
|
185
|
+
Parameters
|
186
|
+
----------
|
187
|
+
fwhm : astropy.units.mas
|
188
|
+
The FWHM of the Gaussian.
|
189
|
+
"""
|
190
|
+
|
191
|
+
name = "BBGauss"
|
192
|
+
description = "A 2D Blackbody Gaussian"
|
193
|
+
|
194
|
+
def __init__(self, **kwargs):
|
195
|
+
super().__init__(**kwargs)
|
196
|
+
self.r0 = Parameter(base="r0")
|
197
|
+
self.q = Parameter(base="q")
|
198
|
+
self.temp0 = Parameter(base="temp0")
|
199
|
+
self.kappa_sil = Parameter(base="kappa_sil")
|
200
|
+
self.kappa_cont = Parameter(base="kappa_cont")
|
201
|
+
self.weight_cont = Parameter(base="weight_cont")
|
202
|
+
|
203
|
+
self.factor = Parameter(value=18, min=0, max=25, free=True, base="fr")
|
204
|
+
self.sigma = Parameter(base="sigma0")
|
205
|
+
|
206
|
+
self.eval(**kwargs)
|
207
|
+
|
208
|
+
def flux_func(self, t: int, wl: u.um) -> np.ndarray:
|
209
|
+
"""Computes the total flux."""
|
210
|
+
temp = self.temp0(t, wl) * (np.abs(self.r(t, wl)) / self.r0(t, wl)) ** self.q(
|
211
|
+
t, wl
|
212
|
+
)
|
213
|
+
op_sil = self.kappa_sil(t, wl) * (1 - self.weight_cont(t, wl).value / 1e2)
|
214
|
+
op_cont = self.weight_cont(t, wl).value / 1e2 * self.kappa_sil(t, wl)
|
215
|
+
emissivity = 1 - np.exp(-self.sigma(t, wl) * (op_sil + op_cont))
|
216
|
+
bb = 10 ** -self.factor(t, wl) * BlackBody(temp)(wl)
|
217
|
+
return (bb * emissivity * u.sr).to(u.Jy).astype(OPTIONS.data.dtype.real)
|
218
|
+
|
219
|
+
|
220
|
+
class Ring(FourierComponent):
|
221
|
+
"""A ring.
|
222
|
+
|
223
|
+
Parameters
|
224
|
+
----------
|
225
|
+
rin : astropy.units.mas
|
226
|
+
The inner radius of the ring
|
227
|
+
thin : bool
|
228
|
+
If toggled the ring is infinitesimal. Default is 'True'.
|
229
|
+
rout : astropy.units.mas
|
230
|
+
The outer radius of the ring. Applies only for 'False' thin
|
231
|
+
"""
|
232
|
+
|
233
|
+
name = "Ring"
|
234
|
+
description = "A simple ring."
|
235
|
+
thin = True
|
236
|
+
|
237
|
+
def __init__(self, **kwargs):
|
238
|
+
super().__init__(**kwargs)
|
239
|
+
self.rin = Parameter(base="rin")
|
240
|
+
self.rout = Parameter(base="rout")
|
241
|
+
|
242
|
+
self.eval(**kwargs)
|
243
|
+
|
244
|
+
def compute_internal_grid(self, t: int, wl: u.um) -> u.Quantity:
|
245
|
+
"""Computes the model grid.
|
246
|
+
|
247
|
+
Returns
|
248
|
+
-------
|
249
|
+
radial_grid
|
250
|
+
"""
|
251
|
+
rin, rout = self.rin(t, wl).value, self.rout(t, wl).value
|
252
|
+
dim, dtype = self.dim.value, OPTIONS.data.dtype.real
|
253
|
+
if OPTIONS.model.gridtype == "linear":
|
254
|
+
return np.linspace(rin, rout, dim).astype(dtype) * self.rin.unit
|
255
|
+
return (
|
256
|
+
np.logspace(np.log10(rin), np.log10(rout), dim).astype(dtype)
|
257
|
+
* self.rin.unit
|
258
|
+
)
|
259
|
+
|
260
|
+
def vis_func(
|
261
|
+
self, spf: 1 / u.rad, psi: u.rad, t: int, wl: u.um, **kwargs
|
262
|
+
) -> np.ndarray:
|
263
|
+
"""Computes the complex visibility."""
|
264
|
+
mod_amps, cos_diff, bessel_funcs = [], [], []
|
265
|
+
if self.asymmetric:
|
266
|
+
for i in range(1, self.modulation.value + 1):
|
267
|
+
rho = getattr(self, f"rho{i}")(t, wl)
|
268
|
+
theta = getattr(self, f"theta{i}")(t, wl)
|
269
|
+
mod_amps.append((-1j) ** i * rho)
|
270
|
+
cos_diff.append(
|
271
|
+
np.cos(i * compare_angles(psi.value, theta.to(u.rad).value))
|
272
|
+
)
|
273
|
+
bessel_funcs.append(partial(jv, i))
|
274
|
+
|
275
|
+
mod_amps = np.array(mod_amps)
|
276
|
+
cos_diff = np.array(cos_diff)
|
277
|
+
bessel_funcs = np.array(bessel_funcs)
|
278
|
+
|
279
|
+
def _vis_func(xx: np.ndarray):
|
280
|
+
"""Shorthand for the vis calculation."""
|
281
|
+
nonlocal mod_amps, cos_diff, bessel_funcs
|
282
|
+
|
283
|
+
vis = j0(xx).astype(complex)
|
284
|
+
if self.asymmetric:
|
285
|
+
bessel_funcs = np.array(list(map(lambda x: x(xx), bessel_funcs)))
|
286
|
+
mod_amps = mod_amps.reshape(
|
287
|
+
(mod_amps.shape[0],) + (1,) * (bessel_funcs.ndim - 1)
|
288
|
+
)
|
289
|
+
vis += (mod_amps * cos_diff * bessel_funcs).sum(axis=0)
|
290
|
+
return vis
|
291
|
+
|
292
|
+
if self.thin:
|
293
|
+
vis = _vis_func(2 * np.pi * self.rin().to(u.rad) * spf)
|
294
|
+
else:
|
295
|
+
intensity_func = kwargs.pop("intensity_func", None)
|
296
|
+
radius, intensity = self.compute_internal_grid(t, wl), 1
|
297
|
+
if intensity_func is not None:
|
298
|
+
intensity = intensity_func(radius, t, wl).to(
|
299
|
+
u.erg / (u.rad**2 * u.cm**2 * u.s * u.Hz)
|
300
|
+
)
|
301
|
+
intensity = intensity[:, np.newaxis]
|
302
|
+
|
303
|
+
if radius.unit not in [u.rad, u.mas]:
|
304
|
+
radius = (radius.to(u.au) / self.dist().to(u.pc)).value * 1e3 * u.mas
|
305
|
+
|
306
|
+
radius = radius.to(u.rad)
|
307
|
+
vis = np.trapezoid(
|
308
|
+
radius * intensity * _vis_func(2 * np.pi * radius * spf), radius
|
309
|
+
)
|
310
|
+
|
311
|
+
vis *= 2 * np.pi * self.cinc()
|
312
|
+
if intensity == 1:
|
313
|
+
vis /= vis[:, 0][:, np.newaxis]
|
314
|
+
else:
|
315
|
+
vis = vis.to(u.Jy)
|
316
|
+
|
317
|
+
return vis.value.astype(OPTIONS.data.dtype.complex)
|
318
|
+
|
319
|
+
def image_func(
|
320
|
+
self, xx: u.mas, yy: u.mas, pixel_size: u.mas, t: int, wl: u.um, **kwargs
|
321
|
+
) -> np.ndarray:
|
322
|
+
"""Computes the image from a 2D grid.
|
323
|
+
|
324
|
+
Parameters
|
325
|
+
----------
|
326
|
+
xx : u.mas
|
327
|
+
yy : u.mas
|
328
|
+
wavelength : u.um
|
329
|
+
|
330
|
+
Returns
|
331
|
+
-------
|
332
|
+
image : astropy.units.Jy
|
333
|
+
"""
|
334
|
+
if self.rin.unit == u.au:
|
335
|
+
xx = (xx / 1e3 * self.dist(t, wl)).value * u.au
|
336
|
+
yy = (yy / 1e3 * self.dist(t, wl)).value * u.au
|
337
|
+
|
338
|
+
radius = np.hypot(xx, yy)[np.newaxis, ...]
|
339
|
+
dx = np.max([np.diff(xx), np.diff(yy)]) * self.rin.unit
|
340
|
+
if not self.thin:
|
341
|
+
dx = self.rout(t, wl) - self.rin(t, wl)
|
342
|
+
|
343
|
+
radial_profile = (radius >= self.rin(t, wl)) & (
|
344
|
+
radius <= (self.rin(t, wl) + dx)
|
345
|
+
)
|
346
|
+
intensity_func = kwargs.pop("intensity_func", None)
|
347
|
+
if intensity_func is None:
|
348
|
+
intensity = 1 / (2 * np.pi * dx.value)
|
349
|
+
else:
|
350
|
+
intensity = (
|
351
|
+
intensity_func(radius, t, wl).to(
|
352
|
+
u.erg / (u.cm**2 * u.mas**2 * u.s * u.Hz)
|
353
|
+
)
|
354
|
+
* pixel_size**2
|
355
|
+
)
|
356
|
+
intensity = intensity.to(u.Jy)
|
357
|
+
|
358
|
+
image = intensity * radial_profile
|
359
|
+
if self.asymmetric:
|
360
|
+
polar_angle, modulations = np.arctan2(yy, xx), []
|
361
|
+
for i in range(1, self.modulation.value + 1):
|
362
|
+
try:
|
363
|
+
rho = getattr(self, f"rho{i}")(t, wl)
|
364
|
+
theta = getattr(self, f"theta{i}")(t, wl)
|
365
|
+
except IndexError:
|
366
|
+
breakpoint()
|
367
|
+
modulations.append(
|
368
|
+
rho
|
369
|
+
* np.cos(
|
370
|
+
compare_angles(theta.to(u.rad).value, i * polar_angle.value)
|
371
|
+
)
|
372
|
+
)
|
373
|
+
|
374
|
+
modulations = u.Quantity(modulations)
|
375
|
+
image = image * (1 + np.sum(modulations, axis=0))
|
376
|
+
|
377
|
+
return image.astype(OPTIONS.data.dtype.real)
|
378
|
+
|
379
|
+
|
380
|
+
class TempGrad(Ring):
|
381
|
+
"""The base class for the component.
|
382
|
+
|
383
|
+
Parameters
|
384
|
+
----------
|
385
|
+
xx : float
|
386
|
+
The x-coordinate of the component.
|
387
|
+
yy : float
|
388
|
+
The x-coordinate of the component.
|
389
|
+
dim : float
|
390
|
+
The dimension [px].
|
391
|
+
"""
|
392
|
+
|
393
|
+
name = "TempGrad"
|
394
|
+
thin = False
|
395
|
+
optically_thick = False
|
396
|
+
const_temperature = False
|
397
|
+
continuum_contribution = True
|
398
|
+
|
399
|
+
def __init__(self, **kwargs):
|
400
|
+
"""The class's constructor."""
|
401
|
+
super().__init__(**kwargs)
|
402
|
+
self.rin.unit = self.rout.unit = u.au
|
403
|
+
self.dist = Parameter(base="dist")
|
404
|
+
self.eff_temp = Parameter(base="eff_temp")
|
405
|
+
self.eff_radius = Parameter(base="eff_radius")
|
406
|
+
|
407
|
+
self.r0 = Parameter(base="r0")
|
408
|
+
self.q = Parameter(base="q")
|
409
|
+
self.temp0 = Parameter(base="temp0")
|
410
|
+
self.p = Parameter(base="p")
|
411
|
+
self.sigma0 = Parameter(base="sigma0")
|
412
|
+
|
413
|
+
self.weights, self.radii, self.matrix = None, None, None
|
414
|
+
self.kappa_sil = Parameter(base="kappa_sil")
|
415
|
+
self.kappa_cont = Parameter(base="kappa_cont")
|
416
|
+
self.weight_cont = Parameter(base="weight_cont")
|
417
|
+
|
418
|
+
if self.const_temperature:
|
419
|
+
self.q.free = self.temp0.free = False
|
420
|
+
|
421
|
+
if not self.continuum_contribution:
|
422
|
+
self.weight_cont.free = False
|
423
|
+
|
424
|
+
self.eval(**kwargs)
|
425
|
+
|
426
|
+
def get_opacity(self, t: int, wl: u.um) -> u.cm**2 / u.g:
|
427
|
+
"""Set the opacity from wavelength."""
|
428
|
+
kappa_sil = self.kappa_sil(t, wl)
|
429
|
+
if self.continuum_contribution:
|
430
|
+
cont_weight, kappa_cont = (
|
431
|
+
self.weight_cont(t, wl).value / 1e2,
|
432
|
+
self.kappa_cont(t, wl),
|
433
|
+
)
|
434
|
+
opacity = (1 - cont_weight) * kappa_sil + cont_weight * kappa_cont
|
435
|
+
else:
|
436
|
+
opacity = kappa_sil
|
437
|
+
|
438
|
+
opacity = opacity.astype(OPTIONS.data.dtype.real)
|
439
|
+
if opacity.size == 1:
|
440
|
+
return opacity.squeeze()
|
441
|
+
|
442
|
+
return opacity
|
443
|
+
|
444
|
+
def compute_temperature(self, radius: u.au, t: int, wl: u.um) -> u.K:
|
445
|
+
"""Computes a 1D-temperature profile."""
|
446
|
+
if self.const_temperature:
|
447
|
+
if self.matrix is not None:
|
448
|
+
interp_op_temps = interp1d(self.weights, self.matrix, axis=0)(
|
449
|
+
self.weight_cont(t, wl).value / 1e2
|
450
|
+
)
|
451
|
+
temp = np.interp(radius.value, self.radii, interp_op_temps) * u.K
|
452
|
+
else:
|
453
|
+
temp = np.sqrt(
|
454
|
+
self.eff_radius(t, wl).to(u.au) / (2 * radius)
|
455
|
+
) * self.eff_temp(t, wl)
|
456
|
+
else:
|
457
|
+
temp = self.temp0(t, wl) * (radius / self.r0(t, wl)) ** self.q(t, wl)
|
458
|
+
return temp.astype(OPTIONS.data.dtype.real)
|
459
|
+
|
460
|
+
def compute_surface_density(self, radius: u.au, t: int, wl: u.um) -> u.one:
|
461
|
+
"""Computes a 1D-surface density profile."""
|
462
|
+
sigma = self.sigma0(t, wl) * (radius / self.r0(t, wl)) ** self.p(t, wl)
|
463
|
+
return sigma.astype(OPTIONS.data.dtype.real)
|
464
|
+
|
465
|
+
def compute_optical_depth(self, radius: u.au, t: int, wl: u.um) -> u.one:
|
466
|
+
"""Computes a 1D-optical depth profile."""
|
467
|
+
tau = self.compute_surface_density(radius, t, wl) * self.get_opacity(t, wl)
|
468
|
+
return tau.astype(OPTIONS.data.dtype.real)
|
469
|
+
|
470
|
+
def compute_emissivity(self, radius: u.au, t: int, wl: u.um) -> u.one:
|
471
|
+
"""Computes a 1D-emissivity profile."""
|
472
|
+
if wl.shape == ():
|
473
|
+
wl.reshape((wl.size,))
|
474
|
+
|
475
|
+
if self.optically_thick:
|
476
|
+
return np.array([1])[:, np.newaxis]
|
477
|
+
|
478
|
+
tau = self.compute_optical_depth(radius, t, wl)
|
479
|
+
epsilon = 1 - np.exp(-tau / self.cinc(t))
|
480
|
+
return epsilon.astype(OPTIONS.data.dtype.real)
|
481
|
+
|
482
|
+
def compute_intensity(self, radius: u.au, t: int, wl: u.um) -> u.Jy:
|
483
|
+
"""Computes a 1D-brightness profile from a dust-surface density- and
|
484
|
+
temperature profile.
|
485
|
+
|
486
|
+
Parameters
|
487
|
+
----------
|
488
|
+
wl : astropy.units.um
|
489
|
+
Wavelengths.
|
490
|
+
|
491
|
+
Returns
|
492
|
+
-------
|
493
|
+
brightness_profile : astropy.units.Jy
|
494
|
+
"""
|
495
|
+
temperature = self.compute_temperature(radius, t, wl)
|
496
|
+
emissivity = self.compute_emissivity(radius, t, wl)
|
497
|
+
intensity = BlackBody(temperature)(wl) * emissivity
|
498
|
+
return intensity.astype(OPTIONS.data.dtype.real)
|
499
|
+
|
500
|
+
def flux_func(self, t: int, wl: u.um) -> np.ndarray:
|
501
|
+
"""Computes the total flux from the hankel transformation."""
|
502
|
+
radius = self.compute_internal_grid(t, wl)
|
503
|
+
intensity = self.compute_intensity(radius, t, wl[:, np.newaxis])
|
504
|
+
if self.rin.unit == u.au:
|
505
|
+
radius = (radius.to(u.au) / self.dist().to(u.pc)).value * 1e3 * u.mas
|
506
|
+
|
507
|
+
flux = 2 * np.pi * self.cinc() * np.trapz(radius * intensity, radius).to(u.Jy)
|
508
|
+
return flux.value.reshape((flux.shape[0], 1)).astype(OPTIONS.data.dtype.real)
|
509
|
+
|
510
|
+
def vis_func(self, *args) -> np.ndarray:
|
511
|
+
"""Computes the correlated fluxes via the hankel transformation.
|
512
|
+
|
513
|
+
Parameters
|
514
|
+
----------
|
515
|
+
radius : astropy.units.mas
|
516
|
+
The radius.
|
517
|
+
baseline : 1/astropy.units.rad
|
518
|
+
The deprojected baselines.
|
519
|
+
baseline_angles : astropy.units.rad
|
520
|
+
The deprojected baseline angles.
|
521
|
+
wavelength : astropy.units.um
|
522
|
+
The wavelengths.
|
523
|
+
|
524
|
+
Returns
|
525
|
+
-------
|
526
|
+
vis : numpy.ndarray
|
527
|
+
The correlated fluxes.
|
528
|
+
"""
|
529
|
+
return super().vis_func(*args, intensity_func=self.compute_intensity)
|
530
|
+
|
531
|
+
def image_func(self, *args) -> np.ndarray:
|
532
|
+
"""Computes the image."""
|
533
|
+
return super().image_func(*args, intensity_func=self.compute_intensity)
|
534
|
+
|
535
|
+
|
536
|
+
class AsymTempGrad(TempGrad):
|
537
|
+
"""An analytical implementation of an asymmetric temperature
|
538
|
+
gradient."""
|
539
|
+
|
540
|
+
name = "AsymTempGrad"
|
541
|
+
asymmetric = True
|
542
|
+
|
543
|
+
|
544
|
+
class GreyBody(TempGrad):
|
545
|
+
"""An analytical implementation of an asymmetric temperature
|
546
|
+
gradient."""
|
547
|
+
|
548
|
+
name = "GreyBody"
|
549
|
+
const_temperature = True
|
550
|
+
|
551
|
+
|
552
|
+
class AsymGreyBody(GreyBody):
|
553
|
+
"""An analytical implementation of an asymmetric temperature
|
554
|
+
gradient."""
|
555
|
+
|
556
|
+
name = "AsymGreyBody"
|
557
|
+
asymmetric = True
|