xdust 1.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.
- xdust/__init__.py +5 -0
- xdust/graindist/__init__.py +6 -0
- xdust/graindist/composition/__init__.py +165 -0
- xdust/graindist/composition/cmdrude.py +72 -0
- xdust/graindist/composition/cmgraphite.py +61 -0
- xdust/graindist/composition/cmsilicate.py +32 -0
- xdust/graindist/composition/minerals.py +91 -0
- xdust/graindist/graindist.py +190 -0
- xdust/graindist/shape/__init__.py +42 -0
- xdust/graindist/shape/sphere.py +54 -0
- xdust/graindist/sizedist/__init__.py +85 -0
- xdust/graindist/sizedist/astrodust.py +137 -0
- xdust/graindist/sizedist/exp_cutoff.py +130 -0
- xdust/graindist/sizedist/grain.py +74 -0
- xdust/graindist/sizedist/powerlaw.py +126 -0
- xdust/graindist/sizedist/weingartner.py +366 -0
- xdust/graindist/tables/PAHion_30 +36129 -0
- xdust/graindist/tables/PAHneu_30 +36129 -0
- xdust/graindist/tables/Table1.WD.dat +22 -0
- xdust/graindist/tables/Table3_LMC2.WD.dat +10 -0
- xdust/graindist/tables/Table3_LMCavg.WD.dat +9 -0
- xdust/graindist/tables/Table3_SMC.WD.dat +7 -0
- xdust/graindist/tables/callindex.out_CpaD03_0.01 +391 -0
- xdust/graindist/tables/callindex.out_CpaD03_0.10 +391 -0
- xdust/graindist/tables/callindex.out_CpeD03_0.01 +388 -0
- xdust/graindist/tables/callindex.out_CpeD03_0.10 +388 -0
- xdust/graindist/tables/callindex.out_sil.D03 +842 -0
- xdust/grainpop.py +587 -0
- xdust/halos/__init__.py +2 -0
- xdust/halos/galhalo.py +605 -0
- xdust/halos/halo.py +449 -0
- xdust/helpers.py +103 -0
- xdust/scatteringmodel/__init__.py +247 -0
- xdust/scatteringmodel/ggadt.py +21 -0
- xdust/scatteringmodel/make_ggadt.py +243 -0
- xdust/scatteringmodel/make_ggadt_astrodust.py +157 -0
- xdust/scatteringmodel/miescat.py +340 -0
- xdust/scatteringmodel/pah.py +182 -0
- xdust/scatteringmodel/rgscat.py +131 -0
- xdust-1.0.dist-info/METADATA +68 -0
- xdust-1.0.dist-info/RECORD +44 -0
- xdust-1.0.dist-info/WHEEL +5 -0
- xdust-1.0.dist-info/licenses/LICENSE +25 -0
- xdust-1.0.dist-info/top_level.txt +1 -0
xdust/__init__.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import astropy.units as u
|
|
4
|
+
|
|
5
|
+
GTYPES = ['Graphite', 'Silicate', 'Drude']
|
|
6
|
+
|
|
7
|
+
def _find_cmfile(name):
|
|
8
|
+
root_path = os.path.dirname(__file__).rstrip('composition')
|
|
9
|
+
data_path = root_path + 'tables/'
|
|
10
|
+
return data_path + name
|
|
11
|
+
|
|
12
|
+
class Composition(object):
|
|
13
|
+
"""
|
|
14
|
+
Composition class for storing information about grain material
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
cmtype : str
|
|
19
|
+
Label for the compound.
|
|
20
|
+
|
|
21
|
+
rho : float
|
|
22
|
+
Density of the material [g cm^-3].
|
|
23
|
+
|
|
24
|
+
citation : str
|
|
25
|
+
Citation for the optical constants loaded.
|
|
26
|
+
|
|
27
|
+
wavel : astropy.units.Quantity
|
|
28
|
+
Wavelength grid for optical constants.
|
|
29
|
+
|
|
30
|
+
revals : numpy.ndarray
|
|
31
|
+
Real part of the complex index of refraction.
|
|
32
|
+
|
|
33
|
+
imvals : numpy.ndarray
|
|
34
|
+
Imaginary part of the complex index of refraction.
|
|
35
|
+
"""
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.cmtype = None
|
|
38
|
+
self.rho = None
|
|
39
|
+
self.citation = None
|
|
40
|
+
self.wavel = None
|
|
41
|
+
self.revals = 1.0
|
|
42
|
+
self.imvals = 0.0
|
|
43
|
+
|
|
44
|
+
def rp(self, x):
|
|
45
|
+
"""
|
|
46
|
+
Interpolate over the input wavelength grid to get the real part of the
|
|
47
|
+
complex index of refraction.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
x : astropy.units.Quantity or numpy.ndarray
|
|
52
|
+
Wavelength or energy; if an ``astropy.units.Quantity``, converted to
|
|
53
|
+
the same units as ``self.wavel``; if a plain array, keV units are assumed.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
numpy.ndarray
|
|
58
|
+
``np.interp(x, self.wavel, self.revals, left=1.0, right=1.0)``
|
|
59
|
+
"""
|
|
60
|
+
# If the input is an astropy quantity, convert it to the same unit as wavel
|
|
61
|
+
if isinstance(x, u.Quantity):
|
|
62
|
+
new_x = x.to(self.wavel.unit, equivalencies=u.spectral()).value
|
|
63
|
+
# Otherwise, assume the unit is keV
|
|
64
|
+
else:
|
|
65
|
+
new_x = (x * u.keV).to(self.wavel.unit, equivalencies=u.spectral()).value
|
|
66
|
+
return np.interp(new_x, self.wavel.value, self.revals, left=1.0, right=1.0)
|
|
67
|
+
|
|
68
|
+
def ip(self, x):
|
|
69
|
+
"""
|
|
70
|
+
Interpolate over the input wavelength grid to get the imaginary part of the
|
|
71
|
+
complex index of refraction.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
x : astropy.units.Quantity or numpy.ndarray
|
|
76
|
+
Wavelength or energy; if an ``astropy.units.Quantity``, converted to
|
|
77
|
+
the same units as ``self.wavel``; if a plain array, keV units are assumed.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
numpy.ndarray
|
|
82
|
+
``np.interp(x, self.wavel, self.imvals, left=0.0, right=0.0)``
|
|
83
|
+
"""
|
|
84
|
+
# If the input is an astropy quantity, convert it to the same unit as wavel
|
|
85
|
+
if isinstance(x, u.Quantity):
|
|
86
|
+
new_x = x.to(self.wavel.unit, equivalencies=u.spectral()).value
|
|
87
|
+
# Otherwise, assume the unit is keV
|
|
88
|
+
else:
|
|
89
|
+
new_x = (x * u.keV).to(self.wavel.unit, equivalencies=u.spectral()).value
|
|
90
|
+
return np.interp(new_x, self.wavel.value, self.imvals, left=0.0, right=0.0)
|
|
91
|
+
|
|
92
|
+
def cm(self, x):
|
|
93
|
+
"""
|
|
94
|
+
Returns the complex index of refraction using Python complex numbers
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
x : astropy.units.Quantity or numpy.ndarray
|
|
99
|
+
Wavelength or energy; if an ``astropy.units.Quantity``, converted to
|
|
100
|
+
the same units as ``self.wavel``; if a plain array, keV units are assumed.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
complex or numpy.ndarray of complex
|
|
105
|
+
Complex index of refraction :math:`n = n_r + i n_i`.
|
|
106
|
+
"""
|
|
107
|
+
return self.rp(x) + 1j * self.ip(x)
|
|
108
|
+
|
|
109
|
+
def plot(self, ax, lam=None, rppart=True, impart=True, xunit=None, label=''):
|
|
110
|
+
"""
|
|
111
|
+
Plots the different parts of the complex index of refraction on a matplotlib axis
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
ax : matplotlib.pyplot.Axes
|
|
116
|
+
The axes on which to plot.
|
|
117
|
+
|
|
118
|
+
lam : astropy.units.Quantity or numpy.ndarray, optional
|
|
119
|
+
If ``None``, plots using the default ``self.wavel`` grid; otherwise
|
|
120
|
+
interpolates onto the provided grid. Plain arrays are assumed to be
|
|
121
|
+
in keV.
|
|
122
|
+
|
|
123
|
+
rppart : bool
|
|
124
|
+
If ``True`` (default), plot the real part :math:`|Re(m)-1|`.
|
|
125
|
+
|
|
126
|
+
impart : bool
|
|
127
|
+
If ``True`` (default), plot the imaginary part :math:`Im(m)`.
|
|
128
|
+
|
|
129
|
+
xunit : str, optional
|
|
130
|
+
Unit string for the x-axis. Defaults to ``self.wavel.unit``.
|
|
131
|
+
|
|
132
|
+
label : str
|
|
133
|
+
Prefix string appended to legend labels.
|
|
134
|
+
"""
|
|
135
|
+
if xunit is None:
|
|
136
|
+
xunit = self.wavel.unit
|
|
137
|
+
# If no grid specified, plot the default one
|
|
138
|
+
if lam is None:
|
|
139
|
+
rp_m1 = np.abs(self.revals - 1.0)
|
|
140
|
+
ip = self.imvals
|
|
141
|
+
x = self.wavel.to(xunit, equivalencies=u.spectral()).value
|
|
142
|
+
# Else, plot the interpolated values
|
|
143
|
+
else:
|
|
144
|
+
rp_m1 = np.abs(self.rp(lam)-1.0)
|
|
145
|
+
ip = self.ip(lam)
|
|
146
|
+
# Check if the input value had units
|
|
147
|
+
if isinstance(lam, u.Quantity):
|
|
148
|
+
x = lam.to(xunit, equivalencies=u.spectral()).value
|
|
149
|
+
# If not, assume keV units
|
|
150
|
+
else:
|
|
151
|
+
x = (lam * u.keV).to(xunit, equivalencies=u.spectral()).value
|
|
152
|
+
# If the user wants to plot Real Part
|
|
153
|
+
if rppart:
|
|
154
|
+
ax.plot(x, rp_m1, ls='-', label='{} |Re(m-1)|'.format(label))
|
|
155
|
+
# If the user wants to plot Imaginary Part
|
|
156
|
+
if impart:
|
|
157
|
+
ax.plot(x, ip, ls='--', label='{} Im(m)'.format(label))
|
|
158
|
+
ax.set_xlabel(xunit)
|
|
159
|
+
ax.legend()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
from .cmdrude import CmDrude
|
|
164
|
+
from .cmsilicate import CmSilicate
|
|
165
|
+
from .cmgraphite import CmGraphite
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import astropy.units as u
|
|
3
|
+
import astropy.constants as c
|
|
4
|
+
|
|
5
|
+
from . import Composition
|
|
6
|
+
|
|
7
|
+
__all__ = ['CmDrude']
|
|
8
|
+
|
|
9
|
+
RHO_DRUDE = 3.0 # g cm^-3
|
|
10
|
+
|
|
11
|
+
## -- Some constants to use for the calculation
|
|
12
|
+
# Classical electron radius
|
|
13
|
+
RE_CM = 2.8179403227e-15 * u.m.to('cm')
|
|
14
|
+
# Mass of proton
|
|
15
|
+
MP_G = c.m_p.to('g').value
|
|
16
|
+
|
|
17
|
+
class CmDrude(Composition):
|
|
18
|
+
"""
|
|
19
|
+
Optical constants under the Drude approximation.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, rho=RHO_DRUDE): # Returns a CM using the Drude approximation
|
|
22
|
+
Composition.__init__(self)
|
|
23
|
+
self.cmtype = 'Drude'
|
|
24
|
+
self.rho = rho
|
|
25
|
+
self.citation = "Using the Drude approximation.\nBohren, C. F. & Huffman, D. R., 1983, Absorption and Scattering of Light by Small Particles (New York: Wiley)"
|
|
26
|
+
|
|
27
|
+
# Set up default values so that inherited plotting method from Composition will work
|
|
28
|
+
self.wavel = np.linspace(1.0, 10.0, 50) * u.keV
|
|
29
|
+
self.revals = self.rp(self.wavel)
|
|
30
|
+
self.imvals = self.ip(self.wavel)
|
|
31
|
+
|
|
32
|
+
def rp(self, x):
|
|
33
|
+
"""
|
|
34
|
+
Calculate the real part of the complex index of refraction under the Drude approximation.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
x : astropy.units.Quantity or numpy.ndarray
|
|
39
|
+
Wavelength or energy; plain arrays are assumed to be in keV.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
numpy.ndarray
|
|
44
|
+
:math:`1 + (\\rho / 2 m_p)(r_e / 2\\pi)\\lambda^2`
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(x, u.Quantity):
|
|
47
|
+
lam_cm = x.to('cm', equivalencies=u.spectral()).value
|
|
48
|
+
else:
|
|
49
|
+
lam_cm = (x * u.keV).to('cm', equivalencies=u.spectral()).value
|
|
50
|
+
|
|
51
|
+
mm1 = self.rho / (2.0*MP_G) * RE_CM/(2.0*np.pi) * np.power(lam_cm, 2)
|
|
52
|
+
return mm1 + 1.0
|
|
53
|
+
|
|
54
|
+
def ip(self, x):
|
|
55
|
+
"""
|
|
56
|
+
Gives the imaginary part of the complex index of refraction under the Drude approximation.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
x : astropy.units.Quantity or numpy.ndarray
|
|
61
|
+
Wavelength or energy; plain arrays are assumed to be in keV.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
float or numpy.ndarray
|
|
66
|
+
``0.0`` for scalar input; array of zeros matching the length of ``x``
|
|
67
|
+
for array input.
|
|
68
|
+
"""
|
|
69
|
+
if np.size(x) > 1:
|
|
70
|
+
return np.zeros(np.size(x))
|
|
71
|
+
else:
|
|
72
|
+
return 0.0
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from astropy.io import ascii
|
|
3
|
+
from astropy import units as u
|
|
4
|
+
|
|
5
|
+
from . import _find_cmfile
|
|
6
|
+
from . import Composition
|
|
7
|
+
|
|
8
|
+
__all__ = ['CmGraphite']
|
|
9
|
+
|
|
10
|
+
RHO_GRA = 2.2 # g cm^-3
|
|
11
|
+
|
|
12
|
+
class CmGraphite(Composition):
|
|
13
|
+
"""
|
|
14
|
+
Optical constants for Graphite from
|
|
15
|
+
`Draine (2003), ApJ 598, 1026 <http://adsabs.harvard.edu/abs/2003ApJ...598.1026D>`_
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
size : str
|
|
20
|
+
``'big'`` gives results for 0.1 um sized graphite grains at 20 K;
|
|
21
|
+
``'small'`` gives results for 0.01 um sized grains at 20 K.
|
|
22
|
+
|
|
23
|
+
orient : str
|
|
24
|
+
``'perp'`` gives results for E-field perpendicular to the c-axis;
|
|
25
|
+
``'para'`` gives results for E-field parallel to the c-axis.
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self, rho=RHO_GRA, size='big', orient='perp'):
|
|
28
|
+
Composition.__init__(self)
|
|
29
|
+
self.cmtype = 'Graphite'
|
|
30
|
+
self.rho = rho
|
|
31
|
+
self.citation = "Using optical constants for graphite,\nDraine, B. T. 2003, ApJ, 598, 1026\nhttp://adsabs.harvard.edu/abs/2003ApJ...598.1026D"
|
|
32
|
+
# Additional info not in default class
|
|
33
|
+
self.size = size
|
|
34
|
+
self.orient = orient
|
|
35
|
+
|
|
36
|
+
# Populate the wavelength and optical constants info
|
|
37
|
+
if size == 'big':
|
|
38
|
+
D03file_para = _find_cmfile('callindex.out_CpaD03_0.10')
|
|
39
|
+
D03file_perp = _find_cmfile('callindex.out_CpeD03_0.10')
|
|
40
|
+
if size == 'small':
|
|
41
|
+
D03file_para = _find_cmfile('callindex.out_CpaD03_0.01')
|
|
42
|
+
D03file_perp = _find_cmfile('callindex.out_CpeD03_0.01')
|
|
43
|
+
|
|
44
|
+
D03dat_para = ascii.read(D03file_para, header_start=4, data_start=5)
|
|
45
|
+
D03dat_perp = ascii.read(D03file_perp, header_start=4, data_start=5)
|
|
46
|
+
|
|
47
|
+
# The wavelength grid needs to be in ascending order
|
|
48
|
+
# for np.interp to run correctly
|
|
49
|
+
if orient == 'perp':
|
|
50
|
+
wavel = D03dat_perp['wave(um)'] * u.micron
|
|
51
|
+
wsort = np.argsort(wavel.value)
|
|
52
|
+
self.wavel = wavel[wsort]
|
|
53
|
+
self.revals = 1.0 + D03dat_perp['Re(n)-1'][wsort]
|
|
54
|
+
self.imvals = D03dat_perp['Im(n)'][wsort]
|
|
55
|
+
|
|
56
|
+
if orient == 'para':
|
|
57
|
+
wavel = D03dat_para['wave(um)'] * u.micron
|
|
58
|
+
wsort = np.argsort(wavel)
|
|
59
|
+
self.wavel = wavel[wsort]
|
|
60
|
+
self.revals = 1.0 + D03dat_para['Re(n)-1'][wsort]
|
|
61
|
+
self.imvals = D03dat_para['Im(n)'][wsort]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from astropy.io import ascii
|
|
3
|
+
from astropy import units as u
|
|
4
|
+
|
|
5
|
+
from . import _find_cmfile
|
|
6
|
+
from . import Composition
|
|
7
|
+
|
|
8
|
+
__all__ = ['CmSilicate']
|
|
9
|
+
|
|
10
|
+
RHO_SIL = 3.8 # g cm^-3
|
|
11
|
+
|
|
12
|
+
class CmSilicate(Composition):
|
|
13
|
+
"""
|
|
14
|
+
Optical constants for Silicate from
|
|
15
|
+
`Draine (2003), ApJ 598, 1026 <http://adsabs.harvard.edu/abs/2003ApJ...598.1026D>`_
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, rho=RHO_SIL):
|
|
18
|
+
Composition.__init__(self)
|
|
19
|
+
self.cmtype = 'Silicate'
|
|
20
|
+
self.rho = rho
|
|
21
|
+
self.citation = "Using optical constants for astrosilicate,\nDraine, B. T. 2003, ApJ, 598, 1026\nhttp://adsabs.harvard.edu/abs/2003ApJ...598.1026D"
|
|
22
|
+
|
|
23
|
+
D03file = _find_cmfile('callindex.out_sil.D03')
|
|
24
|
+
D03dat = ascii.read(D03file, header_start=4, data_start=5)
|
|
25
|
+
|
|
26
|
+
# The wavelength grid needs to be in ascending order for np.interp to run correctly
|
|
27
|
+
wavel = D03dat['wave(um)'] * u.micron
|
|
28
|
+
wsort = np.argsort(wavel.value)
|
|
29
|
+
|
|
30
|
+
self.wavel = wavel[wsort]
|
|
31
|
+
self.revals = 1.0 + D03dat['Re(n)-1'][wsort]
|
|
32
|
+
self.imvals = D03dat['Im(n)'][wsort]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#
|
|
2
|
+
# minerals.py -- Some tables for ISM abundances and depletion factors
|
|
3
|
+
# that are useful for calculating dust mass and dust-to-gas ratios
|
|
4
|
+
#
|
|
5
|
+
# 2016.01.22 - lia@space.mit.edu
|
|
6
|
+
##----------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
amu = {'H':1.008,'He':4.0026,'C':12.011,'N':14.007,'O':15.999,'Ne':20.1797,
|
|
11
|
+
'Na':22.989,'Mg':24.305,'Al':26.981,'Si':28.085,'P':30.973,'S':32.06,
|
|
12
|
+
'Cl':35.45,'Ar':39.948,'Ca':40.078,'Ti':47.867,'Cr':51.9961,'Mn':54.938,
|
|
13
|
+
'Fe':55.845,'Co':58.933,'Ni':58.6934}
|
|
14
|
+
amu_g = 1.661e-24 # g
|
|
15
|
+
mp = 1.673e-24 # g (proton mass)
|
|
16
|
+
|
|
17
|
+
wilms = {'H':12.0, 'He':10.99, 'C':8.38, 'N':7.88, 'O':8.69, 'Ne':7.94,
|
|
18
|
+
'Na':6.16, 'Mg':7.40, 'Al':6.33, 'Si':7.27, 'P':5.42, 'S':7.09,
|
|
19
|
+
'Cl':5.12, 'Ar':6.41, 'Ca':6.20, 'Ti':4.81, 'Cr':5.51, 'Mn':5.34,
|
|
20
|
+
'Fe':7.43, 'Co':4.92, 'Ni':6.05} # 12 + log A_z
|
|
21
|
+
|
|
22
|
+
# Fraction of elements still in gas form
|
|
23
|
+
wilms_1mbeta = {'H':1.0, 'He':1.0, 'C':0.5, 'N':1.0, 'O':0.6, 'Ne':1.0, 'Na':0.25,
|
|
24
|
+
'Mg':0.2, 'Al':0.02, 'Si':0.1, 'P':0.6, 'S':0.6, 'Cl':0.5, 'Ar':1.0,
|
|
25
|
+
'Ca':0.003, 'Ti':0.002, 'Cr':0.03, 'Mn':0.07, 'Fe':0.3, 'Co':0.05,
|
|
26
|
+
'Ni':0.04}
|
|
27
|
+
|
|
28
|
+
class Mineral(object):
|
|
29
|
+
"""
|
|
30
|
+
| Use a dictionary to define the composition.
|
|
31
|
+
| e.g. Olivines of pure MgFe^{2+}SiO_4 composition would be
|
|
32
|
+
| olivine_halfMg = Mineral( {'Mg':1.0, 'Fe':1.0, 'Si':1.0, 'O':4.0} )
|
|
33
|
+
| self.composition : dictionary containing elements and their weights
|
|
34
|
+
|
|
|
35
|
+
| @property
|
|
36
|
+
| self._weight_amu : amu weight of unit crystal
|
|
37
|
+
| self.weight_g : g weight of unit crystal
|
|
38
|
+
"""
|
|
39
|
+
def __init__(self, comp):
|
|
40
|
+
self.composition = comp
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def weight_amu(self):
|
|
44
|
+
result = 0.0
|
|
45
|
+
for atom in self.composition.keys():
|
|
46
|
+
result += self.composition[atom] * amu[atom]
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def weight_g(self):
|
|
51
|
+
return self.weight_amu * amu_g
|
|
52
|
+
|
|
53
|
+
def calc_mass_conversion(elem, mineral):
|
|
54
|
+
"""
|
|
55
|
+
| Returns the number of atoms per gram of a particular mineral object
|
|
56
|
+
| Useful for converting mass column to a number density column for an element
|
|
57
|
+
"""
|
|
58
|
+
assert type(mineral) == Mineral
|
|
59
|
+
assert type(elem) == str
|
|
60
|
+
return mineral.composition[elem] / mineral.weight_g # g^{-1}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def calc_element_column(NH, fmineral, atom, mineral, d2g=0.009):
|
|
64
|
+
"""
|
|
65
|
+
Calculate the column density of an element for a particular NH value,
|
|
66
|
+
assuming a dust-to-gas ratio (d2g) and the fraction of dust in that
|
|
67
|
+
particular mineral species (fmineral)
|
|
68
|
+
"""
|
|
69
|
+
dust_mass = NH * mp * d2g * fmineral # g cm^{-2}
|
|
70
|
+
print('Dust mass = %.3e g cm^-2' % (dust_mass))
|
|
71
|
+
return calc_mass_conversion(atom, mineral) * dust_mass # cm^{-2}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_ISM_abund(elem, abund_table=wilms):
|
|
75
|
+
"""
|
|
76
|
+
Given an abundance table, calculate the number per H atom of a
|
|
77
|
+
given element in any ISM form
|
|
78
|
+
"""
|
|
79
|
+
assert type(elem) == str
|
|
80
|
+
assert type(abund_table) == dict
|
|
81
|
+
return np.power(10.0, abund_table[elem] - 12.0) # number per H atom
|
|
82
|
+
|
|
83
|
+
def get_dust_abund(elem, abund_table=wilms, gas_ratio=wilms_1mbeta):
|
|
84
|
+
"""
|
|
85
|
+
Given an abundance table (dict) and a table of gas ratios (dict),
|
|
86
|
+
calculate the number per H atom of a given ISM element in *solid* form
|
|
87
|
+
"""
|
|
88
|
+
assert type(elem) == str
|
|
89
|
+
assert type(abund_table) == dict
|
|
90
|
+
assert type(gas_ratio) == dict
|
|
91
|
+
return get_ISM_abund(elem, abund_table) * (1.0 - gas_ratio[elem]) # number per H atom
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import sizedist
|
|
3
|
+
from . import composition
|
|
4
|
+
from . import shape as sh
|
|
5
|
+
|
|
6
|
+
MD_DEFAULT = 1.e-4 # g cm^-2
|
|
7
|
+
RHO = 3.0 # g cm^-3
|
|
8
|
+
AMAX = 0.3 # um
|
|
9
|
+
|
|
10
|
+
ALLOWED_SIZES = ['Grain','Powerlaw','ExpCutoff','Astrodust','WD01']
|
|
11
|
+
ALLOWED_COMPS = ['Drude', 'Silicate', 'Graphite']
|
|
12
|
+
SHAPES = {'Sphere':sh.Sphere()}
|
|
13
|
+
|
|
14
|
+
__all__ = ['GrainDist']
|
|
15
|
+
|
|
16
|
+
class GrainDist(object):
|
|
17
|
+
"""
|
|
18
|
+
Graindist ties together the size distribution (which has no set column density)
|
|
19
|
+
and composition (which contains optical constants and density) and anchors them
|
|
20
|
+
with dust mass density and a series of convenience functions to get at some of the
|
|
21
|
+
frequently needed information.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
dtype : str or xdust.graindist.sizedist object
|
|
26
|
+
Grain radius distribution. String options: ``'Grain'``, ``'Powerlaw'``,
|
|
27
|
+
``'ExpCutoff'``, ``'Astrodust'``, ``'WD01'``.
|
|
28
|
+
|
|
29
|
+
cmtype : str or xdust.graindist.composition object
|
|
30
|
+
Optical constants and compound density. String options: ``'Drude'``,
|
|
31
|
+
``'Silicate'``, ``'Graphite'``.
|
|
32
|
+
|
|
33
|
+
shape : str
|
|
34
|
+
Grain shape. ``'Sphere'`` is the only supported option.
|
|
35
|
+
|
|
36
|
+
md : float
|
|
37
|
+
Dust mass column [g cm^-2].
|
|
38
|
+
|
|
39
|
+
rho : float, optional
|
|
40
|
+
If provided, passed to the ``rho`` keyword of the composition object.
|
|
41
|
+
|
|
42
|
+
**kwargs
|
|
43
|
+
Extra inputs passed to ``sizedist.__init__``.
|
|
44
|
+
|
|
45
|
+
Attributes
|
|
46
|
+
----------
|
|
47
|
+
size : :ref:`sizedist` object
|
|
48
|
+
Grain size distribution.
|
|
49
|
+
|
|
50
|
+
comp : :ref:`composition` object
|
|
51
|
+
Optical constants and material density.
|
|
52
|
+
|
|
53
|
+
shape : :ref:`shape` object
|
|
54
|
+
Grain shape object.
|
|
55
|
+
|
|
56
|
+
md : float
|
|
57
|
+
Dust mass column density [g cm^-2].
|
|
58
|
+
"""
|
|
59
|
+
def __init__(self, dtype, cmtype, shape='Sphere', md=MD_DEFAULT, rho=None, **kwargs):
|
|
60
|
+
self.md = md
|
|
61
|
+
|
|
62
|
+
if isinstance(dtype, str):
|
|
63
|
+
self._assign_sizedist_from_string(dtype, **kwargs)
|
|
64
|
+
else:
|
|
65
|
+
self.size = dtype
|
|
66
|
+
|
|
67
|
+
if isinstance(cmtype, str):
|
|
68
|
+
self._assign_comp_from_string(cmtype, rho)
|
|
69
|
+
else:
|
|
70
|
+
self.comp = cmtype
|
|
71
|
+
|
|
72
|
+
if isinstance(shape, str):
|
|
73
|
+
self._assign_shape_from_string(shape)
|
|
74
|
+
else:
|
|
75
|
+
self.shape = shape
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def a(self):
|
|
79
|
+
"""
|
|
80
|
+
Grain radius grid
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
astropy.units.Quantity
|
|
85
|
+
"""
|
|
86
|
+
return self.size.a
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def ndens(self):
|
|
90
|
+
"""
|
|
91
|
+
Number column density of dust grains as a function of radius [cm^-2 um^-1], using the dust mass column density in ``self.md`` and the grain material density in ``self.comp.rho``.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
numpy.ndarray
|
|
96
|
+
"""
|
|
97
|
+
return self.size.ndens(self.md, rho=self.comp.rho, shape=self.shape)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def mdens(self):
|
|
101
|
+
"""
|
|
102
|
+
Mass column density of dust grains as a function of radius [g cm^-2 um^-1], using the dust mass column density in ``self.md`` and the grain material density in ``self.comp.rho``.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
numpy.ndarray
|
|
107
|
+
"""
|
|
108
|
+
mg = self.shape.vol(self.a) * self.comp.rho # mass of each dust grain [g]
|
|
109
|
+
return self.ndens * mg
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def rho(self):
|
|
113
|
+
"""
|
|
114
|
+
Grain material density [g cm^-3]
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
float
|
|
119
|
+
"""
|
|
120
|
+
return self.comp.rho
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def cgeo(self):
|
|
124
|
+
"""
|
|
125
|
+
Geometric cross section of the grains [float, cm^2].
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
numpy.ndarray
|
|
130
|
+
"""
|
|
131
|
+
return self.shape.cgeo(self.a)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def vol(self):
|
|
135
|
+
"""
|
|
136
|
+
Volume of the grains [float, cm^3].
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
numpy.ndarray
|
|
141
|
+
"""
|
|
142
|
+
return self.shape.vol(self.a)
|
|
143
|
+
|
|
144
|
+
def plot(self, ax, **kwargs):
|
|
145
|
+
"""
|
|
146
|
+
Plots the grain size distribution on a given matplotlib axis
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
ax : matplotlib.axes.Axes
|
|
151
|
+
Axes on which to plot the grain size distribution
|
|
152
|
+
"""
|
|
153
|
+
ax.plot(self.a.to('micron').value, self.ndens * np.power(self.a.to('micron').value, 4), **kwargs)
|
|
154
|
+
ax.set_xlabel("Radius (micron)")
|
|
155
|
+
ax.set_ylabel("$(dn/da) a^4$ (cm$^{-2}$ um$^{3}$)")
|
|
156
|
+
ax.set_xscale('log')
|
|
157
|
+
ax.set_yscale('log')
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
def _assign_sizedist_from_string(self, dtype, **kwargs):
|
|
161
|
+
assert dtype in ALLOWED_SIZES
|
|
162
|
+
if dtype == 'Grain':
|
|
163
|
+
self.size = sizedist.Grain(**kwargs)
|
|
164
|
+
if dtype == 'Powerlaw':
|
|
165
|
+
self.size = sizedist.Powerlaw(**kwargs)
|
|
166
|
+
if dtype == 'ExpCutoff':
|
|
167
|
+
self.size = sizedist.ExpCutoff(**kwargs)
|
|
168
|
+
if dtype == 'Astrodust':
|
|
169
|
+
self.size = sizedist.Astrodust(**kwargs)
|
|
170
|
+
if dtype == 'WD01':
|
|
171
|
+
self.size = sizedist.WD01(**kwargs)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
def _assign_comp_from_string(self, cmtype, rho):
|
|
175
|
+
assert cmtype in ALLOWED_COMPS
|
|
176
|
+
if cmtype == 'Drude':
|
|
177
|
+
if rho is not None: self.comp = composition.CmDrude(rho=rho)
|
|
178
|
+
else: self.comp = composition.CmDrude()
|
|
179
|
+
if cmtype == 'Silicate':
|
|
180
|
+
if rho is not None: self.comp = composition.CmSilicate(rho=rho)
|
|
181
|
+
else: self.comp = composition.CmSilicate()
|
|
182
|
+
if cmtype == 'Graphite':
|
|
183
|
+
if rho is not None: self.comp = composition.CmGraphite(rho=rho)
|
|
184
|
+
else: self.comp = composition.CmGraphite()
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
def _assign_shape_from_string(self, shape):
|
|
188
|
+
assert shape in SHAPES.keys()
|
|
189
|
+
self.shape = SHAPES[shape]
|
|
190
|
+
return
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
class Shape:
|
|
3
|
+
"""
|
|
4
|
+
Abstract class for grain shape.
|
|
5
|
+
"""
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.shape = 'Undefined'
|
|
8
|
+
|
|
9
|
+
def vol(self, a):
|
|
10
|
+
"""
|
|
11
|
+
Volume of the grain as a function of grain size.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
a : astropy.units.Quantity or float
|
|
16
|
+
Grain size; plain floats are assumed to be in microns.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
numpy.ndarray
|
|
21
|
+
Volume of the grain [cm^-3]
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
24
|
+
|
|
25
|
+
def cgeo(self, a):
|
|
26
|
+
"""
|
|
27
|
+
Geometric cross-section of the grain as a function of grain size.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
a : astropy.units.Quantity or float
|
|
32
|
+
Grain size; plain floats are assumed to be in microns.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
float or numpy.ndarray
|
|
37
|
+
Geometric cross-section of the grain [cm^2]
|
|
38
|
+
"""
|
|
39
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
from .sphere import Sphere
|