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.
Files changed (44) hide show
  1. xdust/__init__.py +5 -0
  2. xdust/graindist/__init__.py +6 -0
  3. xdust/graindist/composition/__init__.py +165 -0
  4. xdust/graindist/composition/cmdrude.py +72 -0
  5. xdust/graindist/composition/cmgraphite.py +61 -0
  6. xdust/graindist/composition/cmsilicate.py +32 -0
  7. xdust/graindist/composition/minerals.py +91 -0
  8. xdust/graindist/graindist.py +190 -0
  9. xdust/graindist/shape/__init__.py +42 -0
  10. xdust/graindist/shape/sphere.py +54 -0
  11. xdust/graindist/sizedist/__init__.py +85 -0
  12. xdust/graindist/sizedist/astrodust.py +137 -0
  13. xdust/graindist/sizedist/exp_cutoff.py +130 -0
  14. xdust/graindist/sizedist/grain.py +74 -0
  15. xdust/graindist/sizedist/powerlaw.py +126 -0
  16. xdust/graindist/sizedist/weingartner.py +366 -0
  17. xdust/graindist/tables/PAHion_30 +36129 -0
  18. xdust/graindist/tables/PAHneu_30 +36129 -0
  19. xdust/graindist/tables/Table1.WD.dat +22 -0
  20. xdust/graindist/tables/Table3_LMC2.WD.dat +10 -0
  21. xdust/graindist/tables/Table3_LMCavg.WD.dat +9 -0
  22. xdust/graindist/tables/Table3_SMC.WD.dat +7 -0
  23. xdust/graindist/tables/callindex.out_CpaD03_0.01 +391 -0
  24. xdust/graindist/tables/callindex.out_CpaD03_0.10 +391 -0
  25. xdust/graindist/tables/callindex.out_CpeD03_0.01 +388 -0
  26. xdust/graindist/tables/callindex.out_CpeD03_0.10 +388 -0
  27. xdust/graindist/tables/callindex.out_sil.D03 +842 -0
  28. xdust/grainpop.py +587 -0
  29. xdust/halos/__init__.py +2 -0
  30. xdust/halos/galhalo.py +605 -0
  31. xdust/halos/halo.py +449 -0
  32. xdust/helpers.py +103 -0
  33. xdust/scatteringmodel/__init__.py +247 -0
  34. xdust/scatteringmodel/ggadt.py +21 -0
  35. xdust/scatteringmodel/make_ggadt.py +243 -0
  36. xdust/scatteringmodel/make_ggadt_astrodust.py +157 -0
  37. xdust/scatteringmodel/miescat.py +340 -0
  38. xdust/scatteringmodel/pah.py +182 -0
  39. xdust/scatteringmodel/rgscat.py +131 -0
  40. xdust-1.0.dist-info/METADATA +68 -0
  41. xdust-1.0.dist-info/RECORD +44 -0
  42. xdust-1.0.dist-info/WHEEL +5 -0
  43. xdust-1.0.dist-info/licenses/LICENSE +25 -0
  44. xdust-1.0.dist-info/top_level.txt +1 -0
xdust/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from . import helpers
2
+ from . import graindist
3
+ from . import scatteringmodel
4
+ from . import halos
5
+ from .grainpop import *
@@ -0,0 +1,6 @@
1
+
2
+ from . import sizedist
3
+ from . import composition
4
+ from . import shape
5
+
6
+ from .graindist import *
@@ -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