mwdust 1.5__tar.gz → 1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mwdust might be problematic. Click here for more details.

Files changed (61) hide show
  1. {mwdust-1.5 → mwdust-1.6}/HISTORY.txt +4 -0
  2. {mwdust-1.5 → mwdust-1.6}/PKG-INFO +16 -2
  3. {mwdust-1.5 → mwdust-1.6}/README.rst +5 -0
  4. {mwdust-1.5 → mwdust-1.6}/mwdust/DustMap3D.py +4 -3
  5. {mwdust-1.5 → mwdust-1.6}/mwdust/HierarchicalHealpixMap.py +4 -4
  6. {mwdust-1.5 → mwdust-1.6}/mwdust/Marshall06.py +1 -1
  7. mwdust-1.6/mwdust/Zucker25.py +107 -0
  8. {mwdust-1.5 → mwdust-1.6}/mwdust/__init__.py +5 -2
  9. {mwdust-1.5 → mwdust-1.6}/mwdust/util/fortranfile.py +6 -5
  10. {mwdust-1.5 → mwdust-1.6}/mwdust/util/healpix.py +39 -21
  11. mwdust-1.6/mwdust/util/read_SFD.py +129 -0
  12. {mwdust-1.5 → mwdust-1.6}/mwdust.egg-info/PKG-INFO +16 -2
  13. {mwdust-1.5 → mwdust-1.6}/mwdust.egg-info/SOURCES.txt +1 -0
  14. {mwdust-1.5 → mwdust-1.6}/setup.py +1 -1
  15. mwdust-1.6/tests/test_sfd.py +64 -0
  16. mwdust-1.5/mwdust/util/read_SFD.py +0 -123
  17. mwdust-1.5/tests/test_sfd.py +0 -47
  18. {mwdust-1.5 → mwdust-1.6}/AUTHORS.txt +0 -0
  19. {mwdust-1.5 → mwdust-1.6}/LICENSE +0 -0
  20. {mwdust-1.5 → mwdust-1.6}/MANIFEST.in +0 -0
  21. {mwdust-1.5 → mwdust-1.6}/mwdust/Combined15.py +0 -0
  22. {mwdust-1.5 → mwdust-1.6}/mwdust/Combined19.py +0 -0
  23. {mwdust-1.5 → mwdust-1.6}/mwdust/Drimmel03.py +0 -0
  24. {mwdust-1.5 → mwdust-1.6}/mwdust/Green15.py +0 -0
  25. {mwdust-1.5 → mwdust-1.6}/mwdust/Green17.py +0 -0
  26. {mwdust-1.5 → mwdust-1.6}/mwdust/Green19.py +0 -0
  27. {mwdust-1.5 → mwdust-1.6}/mwdust/SFD.py +0 -0
  28. {mwdust-1.5 → mwdust-1.6}/mwdust/Sale14.py +0 -0
  29. {mwdust-1.5 → mwdust-1.6}/mwdust/Zero.py +0 -0
  30. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/interface.h +0 -0
  31. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_asciifile.c +0 -0
  32. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_asciifile.h +0 -0
  33. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_common_math.h +0 -0
  34. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_common_string.h +0 -0
  35. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_fits.c +0 -0
  36. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_fits.h +0 -0
  37. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_inoutput.c +0 -0
  38. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_inoutput.h +0 -0
  39. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_lambert.c +0 -0
  40. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_lambert.h +0 -0
  41. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_memory.c +0 -0
  42. {mwdust-1.5 → mwdust-1.6}/mwdust/util/SFD_CodeC/subs_memory.h +0 -0
  43. {mwdust-1.5 → mwdust-1.6}/mwdust/util/__init__.py +0 -0
  44. {mwdust-1.5 → mwdust-1.6}/mwdust/util/combine_dustmaps19.py +0 -0
  45. {mwdust-1.5 → mwdust-1.6}/mwdust/util/download.py +0 -0
  46. {mwdust-1.5 → mwdust-1.6}/mwdust/util/extCurves/apj398709t6_ascii.txt +0 -0
  47. {mwdust-1.5 → mwdust-1.6}/mwdust/util/extCurves/extinction.tbl +0 -0
  48. {mwdust-1.5 → mwdust-1.6}/mwdust/util/extCurves.py +0 -0
  49. {mwdust-1.5 → mwdust-1.6}/mwdust/util/healpix_CodeC/subs_pixfunc.c +0 -0
  50. {mwdust-1.5 → mwdust-1.6}/mwdust/util/healpix_CodeC/subs_pixfunc.h +0 -0
  51. {mwdust-1.5 → mwdust-1.6}/mwdust/util/read_Drimmel.py +0 -0
  52. {mwdust-1.5 → mwdust-1.6}/mwdust/util/tools.py +0 -0
  53. {mwdust-1.5 → mwdust-1.6}/mwdust.egg-info/dependency_links.txt +0 -0
  54. {mwdust-1.5 → mwdust-1.6}/mwdust.egg-info/requires.txt +0 -0
  55. {mwdust-1.5 → mwdust-1.6}/mwdust.egg-info/top_level.txt +0 -0
  56. {mwdust-1.5 → mwdust-1.6}/pyproject.toml +0 -0
  57. {mwdust-1.5 → mwdust-1.6}/setup.cfg +0 -0
  58. {mwdust-1.5 → mwdust-1.6}/tests/test_download.py +0 -0
  59. {mwdust-1.5 → mwdust-1.6}/tests/test_ebv.py +0 -0
  60. {mwdust-1.5 → mwdust-1.6}/tests/test_extcurve.py +0 -0
  61. {mwdust-1.5 → mwdust-1.6}/tests/test_green19.py +0 -0
@@ -1,3 +1,7 @@
1
+ v1.6 (XXXX-XX-XX)
2
+ ==================
3
+
4
+
1
5
  v1.5 (2023-10-13)
2
6
  ==================
3
7
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mwdust
3
- Version: 1.5
3
+ Version: 1.6
4
4
  Summary: Dust in the Milky Way
5
5
  Home-page: https://github.com/jobovy/mwdust
6
6
  Author: Jo Bovy
@@ -24,6 +24,15 @@ Requires-Dist: h5py
24
24
  Requires-Dist: tqdm
25
25
  Requires-Dist: requests
26
26
  Requires-Dist: healpy
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: home-page
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: requires-dist
35
+ Dynamic: summary
27
36
 
28
37
  mwdust
29
38
  ======
@@ -98,6 +107,9 @@ roughly the following lay-out::
98
107
  bayestar2017.h5
99
108
  green19/
100
109
  bayestar2019.h5
110
+ zucker25/
111
+ decaps_mean.h5
112
+ decaps_mean_and_samples.h5
101
113
  maps/
102
114
  SFD_dust_4096_ngp.fits
103
115
  SFD_dust_4096_sgp.fits
@@ -238,6 +250,8 @@ map that you use:
238
250
 
239
251
  * **mwdust.Green19**: `Green et al. (2019) <https://ui.adsabs.harvard.edu/abs/2019arXiv190502734G>`__ (added by `@jan-rybizki <https://github.com/jan-rybizki>`__)
240
252
 
253
+ * **mwdust.Zucker25**: `Zucker et al. (2025) <https://ui.adsabs.harvard.edu/abs/2025arXiv250302657Z>`__
254
+
241
255
  * **mwdust.Combined15**: Combination of
242
256
 
243
257
  * `Marshall et al. (2006) <http://adsabs.harvard.edu/abs/2006A%26A...453..635M>`__ (**mwdust.Marshall06**),
@@ -79,6 +79,9 @@ roughly the following lay-out::
79
79
  bayestar2017.h5
80
80
  green19/
81
81
  bayestar2019.h5
82
+ zucker25/
83
+ decaps_mean.h5
84
+ decaps_mean_and_samples.h5
82
85
  maps/
83
86
  SFD_dust_4096_ngp.fits
84
87
  SFD_dust_4096_sgp.fits
@@ -219,6 +222,8 @@ map that you use:
219
222
 
220
223
  * **mwdust.Green19**: `Green et al. (2019) <https://ui.adsabs.harvard.edu/abs/2019arXiv190502734G>`__ (added by `@jan-rybizki <https://github.com/jan-rybizki>`__)
221
224
 
225
+ * **mwdust.Zucker25**: `Zucker et al. (2025) <https://ui.adsabs.harvard.edu/abs/2025arXiv250302657Z>`__
226
+
222
227
  * **mwdust.Combined15**: Combination of
223
228
 
224
229
  * `Marshall et al. (2006) <http://adsabs.harvard.edu/abs/2006A%26A...453..635M>`__ (**mwdust.Marshall06**),
@@ -11,9 +11,9 @@ try:
11
11
  except ImportError:
12
12
  _BOVY_PLOT_LOADED= False
13
13
 
14
- class DustMap3D:
14
+ class DustMap3D(object):
15
15
  """top-level class for a 3D dust map; all other dust maps inherit from this"""
16
- def __init__(self,filter=None):
16
+ def __init__(self, filter=None, **download_kwargs):
17
17
  """
18
18
  NAME:
19
19
  __init__
@@ -26,7 +26,8 @@ class DustMap3D:
26
26
  2013-11-24 - Started - Bovy (IAS)
27
27
  """
28
28
  self._filter= filter
29
- self.download() # download the map
29
+ if hasattr(self, "download"):
30
+ self.download(**download_kwargs) # download the map
30
31
 
31
32
  def __call__(self,*args,**kwargs):
32
33
  """
@@ -14,7 +14,7 @@ _DEGTORAD= numpy.pi/180.
14
14
  class HierarchicalHealpixMap(DustMap3D):
15
15
  """General class for extinction maps given as a hierarchical HEALPix
16
16
  pixelation (e.g., Green et al. 2015) """
17
- def __init__(self,filter=None,sf10=True):
17
+ def __init__(self,filter=None,sf10=True, **download_kwargs):
18
18
  """
19
19
  NAME:
20
20
  __init__
@@ -28,8 +28,8 @@ class HierarchicalHealpixMap(DustMap3D):
28
28
  HISTORY:
29
29
  2015-07-28 - Started - Bovy (UofT)
30
30
  """
31
- DustMap3D.__init__(self,filter=filter)
32
- self._sf10= sf10
31
+ super(HierarchicalHealpixMap, self).__init__(filter=filter, **download_kwargs)
32
+ self._sf10 = sf10
33
33
  return None
34
34
 
35
35
 
@@ -221,4 +221,4 @@ class HierarchicalHealpixMap(DustMap3D):
221
221
  format=r'$%g$',
222
222
  cmap='gist_yarg',
223
223
  **kwargs)
224
- return None
224
+ return None
@@ -80,7 +80,7 @@ class Marshall06(DustMap3D):
80
80
  2013-12-12 - Started - Bovy (IAS)
81
81
  """
82
82
  if isinstance(l,numpy.ndarray) or isinstance(b,numpy.ndarray):
83
- raise NotImplementedError("array input for l and b for Drimmel dust map not implemented")
83
+ raise NotImplementedError("array input for l and b for Marshall06 dust map not implemented")
84
84
  lbIndx= self._lbIndx(l,b)
85
85
  if self._intps[lbIndx] != 0:
86
86
  out= self._intps[lbIndx](d)
@@ -0,0 +1,107 @@
1
+ ###############################################################################
2
+ #
3
+ # DECaPS25: extinction model from Zucker et al. (2025)
4
+ #
5
+ ###############################################################################
6
+ import os, os.path
7
+ import numpy
8
+ import h5py
9
+ from mwdust.util.download import dust_dir, downloader
10
+ from mwdust.HierarchicalHealpixMap import HierarchicalHealpixMap
11
+
12
+ _DEGTORAD = numpy.pi/180.
13
+ _decapsdir = os.path.join(dust_dir, 'zucker25')
14
+
15
+ class Zucker25(HierarchicalHealpixMap):
16
+ """DECaPS 3D dust-reddening map (Zucker et al. 2025)"""
17
+ def __init__(self, filter=None, sf10=True, load_samples=False, interpk=1):
18
+ """
19
+ NAME:
20
+ __init__
21
+ PURPOSE:
22
+ Initialize the DECaPS (2025) dust map
23
+ INPUT:
24
+ filter= filter to return the extinction in
25
+ sf10= (True) if True, use the Schlafly & Finkbeiner calibrations
26
+ load_samples= (False) if True, also load the samples
27
+ interpk= (1) interpolation order
28
+ OUTPUT:
29
+ object
30
+ HISTORY:
31
+ 2025-10-01 - Adopted
32
+ """
33
+ HierarchicalHealpixMap.__init__(self, filter=filter, sf10=sf10, samples=load_samples)
34
+ if not os.path.isdir(_decapsdir):
35
+ os.mkdir(_decapsdir)
36
+ fname = 'decaps_mean_and_samples.h5' if load_samples else 'decaps_mean.h5'
37
+ fpath = os.path.join(_decapsdir, fname)
38
+ if not os.path.exists(fpath):
39
+ self.download(samples=load_samples)
40
+ self._f = h5py.File(fpath, 'r')
41
+ self._best_fit = self._f['/mean'][:, 0, :]
42
+ p = self._f['/pixel_info']
43
+ hpx = p['healpix_index'][:]
44
+ nside_attr = int(p.attrs['nside'])
45
+ pix_dtype = numpy.dtype([('healpix_index', hpx.dtype), ('nside', numpy.int64)])
46
+ self._pix_info = numpy.empty(hpx.shape[0], dtype=pix_dtype)
47
+ self._pix_info['healpix_index'] = hpx
48
+ self._pix_info['nside'] = nside_attr
49
+ dm = numpy.array(p.attrs['DM_bin_edges'], dtype=numpy.float64)
50
+ if dm.shape[0] != self._best_fit.shape[1]:
51
+ raise RuntimeError("DM_bin_edges length does not match radial dimension")
52
+ self._distmods = dm
53
+ if load_samples:
54
+ if 'samples' not in self._f:
55
+ raise RuntimeError("Requested load_samples=True, but 'samples' dataset not found.")
56
+ self._samples_dset = self._f['/samples']
57
+ else:
58
+ self._samples_dset = None
59
+ self._minnside = numpy.amin(self._pix_info['nside'])
60
+ self._maxnside = numpy.amax(self._pix_info['nside'])
61
+ nlevels = int(numpy.log2(self._maxnside // self._minnside)) + 1
62
+ self._nsides = [self._maxnside // 2**ii for ii in range(nlevels)]
63
+ self._indexArray = numpy.arange(len(self._pix_info['healpix_index']))
64
+ self._intps = numpy.zeros(len(self._pix_info['healpix_index']), dtype='object')
65
+ self._interpk = interpk
66
+ return None
67
+
68
+ def substitute_sample(self, samplenum):
69
+ """
70
+ NAME:
71
+ substitute_sample
72
+ PURPOSE:
73
+ substitute a sample for the best fit to get the extinction from a sample with the same tools; need to have setup the instance with load_samples=True
74
+ INPUT:
75
+ samplenum - sample's index to load
76
+ OUTPUT:
77
+ (none; just resets the instance to use the sample rather than the best fit)
78
+ HISTORY:
79
+ 2025-10-01 - Adopted
80
+ """
81
+ if self._samples_dset is None:
82
+ raise RuntimeError('No samples present in DECaPS file')
83
+ self._best_fit = self._samples_dset[:, samplenum, :]
84
+ self._intps = numpy.zeros(len(self._pix_info['healpix_index']), dtype='object')
85
+ return None
86
+
87
+ @classmethod
88
+ def download(cls, samples=False, test=False):
89
+ subdir = os.path.join(dust_dir, "decaps25")
90
+ if not os.path.exists(subdir):
91
+ os.mkdir(subdir)
92
+ if samples:
93
+ target = os.path.join(subdir, "decaps_mean_and_samples.h5")
94
+ url = "https://dataverse.harvard.edu/api/access/datafile/11840498"
95
+ else:
96
+ target = os.path.join(subdir, "decaps_mean.h5")
97
+ url = "https://dataverse.harvard.edu/api/access/datafile/11838924"
98
+ if not os.path.exists(target):
99
+ downloader(url, target, cls.__name__, test=test)
100
+ return None
101
+
102
+ def __del__(self):
103
+ try:
104
+ if hasattr(self, "_f") and self._f:
105
+ self._f.close()
106
+ except Exception:
107
+ pass
@@ -7,18 +7,21 @@ from mwdust.Green17 import Green17
7
7
  from mwdust.Green19 import Green19
8
8
  from mwdust.Combined15 import Combined15
9
9
  from mwdust.Combined19 import Combined19
10
+ from mwdust.Zucker25 import Zucker25
10
11
  from mwdust.Zero import Zero
11
12
 
12
- __version__ = "1.5"
13
+ __version__ = "1.6"
13
14
 
14
15
 
15
16
  def download_all(test=False):
16
17
  SFD.download(test=test)
17
18
  Marshall06.download(test=test)
18
19
  Drimmel03.download(test=test)
19
- Sale14.download(test=test)
20
+ if not test:
21
+ Sale14.download(test=test)
20
22
  Green15.download(test=test)
21
23
  Green17.download(test=test)
22
24
  Green19.download(test=test)
23
25
  Combined15.download(test=test)
24
26
  Combined19.download(test=test)
27
+ Zucker25.download(test=test)
@@ -138,9 +138,10 @@ class FortranFile(file):
138
138
  data += read_data
139
139
 
140
140
  def _read_check(self):
141
- return numpy.fromstring(self._read_exactly(self._header_length),
142
- dtype=self.ENDIAN+self.HEADER_PREC
143
- )[0]
141
+ return numpy.frombuffer(
142
+ self._read_exactly(self._header_length),
143
+ dtype=self.ENDIAN + self.HEADER_PREC,
144
+ )[0]
144
145
 
145
146
  def _write_check(self, number_of_bytes):
146
147
  """Write the header for the given number of bytes"""
@@ -205,7 +206,7 @@ class FortranFile(file):
205
206
  raise ValueError('Not an appropriate precision')
206
207
 
207
208
  data_str = self.readRecord()
208
- return numpy.fromstring(data_str, dtype=self.ENDIAN+prec)
209
+ return numpy.frombuffer(data_str, dtype=self.ENDIAN + prec)
209
210
 
210
211
  def writeReals(self, reals, prec='f'):
211
212
  """Write an array of floats in given precision
@@ -240,7 +241,7 @@ class FortranFile(file):
240
241
  raise ValueError('Not an appropriate precision')
241
242
 
242
243
  data_str = self.readRecord()
243
- return numpy.fromstring(data_str, dtype=self.ENDIAN+prec)
244
+ return numpy.frombuffer(data_str, dtype=self.ENDIAN + prec)
244
245
 
245
246
  def writeInts(self, ints, prec='i'):
246
247
  """Write an array of integers in given precision
@@ -1,5 +1,5 @@
1
1
  import sys
2
- import distutils.sysconfig as sysconfig
2
+ import sysconfig
3
3
  import ctypes
4
4
  import ctypes.util
5
5
  from numpy.ctypeslib import ndpointer
@@ -7,7 +7,7 @@ import numpy as np
7
7
  from pathlib import Path
8
8
 
9
9
  # healpy number to represent bad numbers
10
- UNSEEN = -1.6375e+30
10
+ UNSEEN = -1.6375e30
11
11
 
12
12
  # Find and load the library
13
13
  _lib = None
@@ -37,13 +37,18 @@ if _lib is None:
37
37
  def check_nside(nside, nest=False):
38
38
  """
39
39
  Utility function
40
-
40
+
41
41
  Check if healpix map with nside is valid in general
42
42
  """
43
43
  # nside can only be a power of 2 for nest, but generally less than 2**29
44
44
  nside_arr = np.array(nside).astype(np.int64)
45
45
  is_ok = True
46
- if np.all(np.logical_and(np.logical_and(nside == nside_arr, np.all(np.less(0, nside))), nside_arr <= 2**29)):
46
+ if np.all(
47
+ np.logical_and(
48
+ np.logical_and(nside == nside_arr, np.all(np.less(0, nside))),
49
+ nside_arr <= 2**29,
50
+ )
51
+ ):
47
52
  if nest:
48
53
  is_ok = (nside_arr & (nside_arr - 1)) == 0
49
54
  else:
@@ -55,7 +60,7 @@ def check_nside(nside, nest=False):
55
60
  def check_npix(npix):
56
61
  """
57
62
  Utility function
58
-
63
+
59
64
  Check if total pixel number of a healpix map are valid in general
60
65
  """
61
66
  # check if npix is a valid value for healpix map size
@@ -67,7 +72,7 @@ def check_npix(npix):
67
72
  def check_ipix_nside(ipix, nside):
68
73
  """
69
74
  Utility function
70
-
75
+
71
76
  Check if pixel number(s) are valid in a healpix map with nside
72
77
  """
73
78
  # check if all ipix are valid for a healpix map size
@@ -144,7 +149,7 @@ def ang2pix(nside, theta, phi, nest=False, lonlat=False):
144
149
  else:
145
150
  ang2pix_c = _lib.ang2pix_nest
146
151
  ndarrayFlags = ("C_CONTIGUOUS", "WRITEABLE")
147
-
152
+
148
153
  ang2pix_c.argtypes = [
149
154
  ctypes.c_long,
150
155
  ndpointer(dtype=np.float64, flags=ndarrayFlags),
@@ -182,7 +187,7 @@ def ang2vec(theta, phi, lonlat=False):
182
187
  Angular coordinates to unit 3-vector direction
183
188
  INPUT:
184
189
  theta - colatitude
185
- phi - longitude
190
+ phi - longitude
186
191
  lonlat - input in longitude and latitude (deg)?
187
192
  OUTPUT:
188
193
  x, y, z - unit 3-vector direction
@@ -196,7 +201,7 @@ def ang2vec(theta, phi, lonlat=False):
196
201
 
197
202
  ang2vec_c = _lib.ang2vec
198
203
  ndarrayFlags = ("C_CONTIGUOUS", "WRITEABLE")
199
-
204
+
200
205
  ang2vec_c.argtypes = [
201
206
  ndpointer(dtype=np.float64, flags=ndarrayFlags),
202
207
  ndpointer(dtype=np.float64, flags=ndarrayFlags),
@@ -213,7 +218,7 @@ def ang2vec(theta, phi, lonlat=False):
213
218
  phi.astype(np.float64, order="C", copy=False),
214
219
  ctypes.c_long(nstars),
215
220
  )
216
- result = np.fromiter(res, dtype=np.float64, count=nstars*3)
221
+ result = np.fromiter(res, dtype=np.float64, count=nstars * 3)
217
222
  result = result.reshape(nstars, 3)
218
223
 
219
224
  # Reset input arrays
@@ -224,6 +229,7 @@ def ang2vec(theta, phi, lonlat=False):
224
229
 
225
230
  return result
226
231
 
232
+
227
233
  def pix2vec(nside, ipix, nest=False):
228
234
  """
229
235
  NAME:
@@ -266,7 +272,7 @@ def pix2vec(nside, ipix, nest=False):
266
272
  npix,
267
273
  ipix.astype(np.int64, order="C", copy=False),
268
274
  )
269
- result = np.fromiter(res, dtype=np.float64, count=npix*3)
275
+ result = np.fromiter(res, dtype=np.float64, count=npix * 3)
270
276
  result = result.reshape(npix, 3)
271
277
 
272
278
  # Reset input arrays
@@ -320,7 +326,7 @@ def pix2ang(nside, ipix, nest=False, lonlat=False):
320
326
  npix,
321
327
  ipix.astype(np.int64, order="C", copy=False),
322
328
  )
323
- result = np.fromiter(res, dtype=np.float64, count=npix*2)
329
+ result = np.fromiter(res, dtype=np.float64, count=npix * 2)
324
330
  result = result.reshape(npix, 2)
325
331
 
326
332
  # Reset input arrays
@@ -348,7 +354,7 @@ def nside2pixarea(nside):
348
354
  HISTORY:
349
355
  2023-03-01 - Written - Henry Leung (Toronto)
350
356
  """
351
- return np.pi / (3 * nside ** 2)
357
+ return np.pi / (3 * nside**2)
352
358
 
353
359
 
354
360
  def nside2npix(nside):
@@ -367,7 +373,15 @@ def nside2npix(nside):
367
373
  return 12 * nside * nside
368
374
 
369
375
 
370
- def ud_grade(map_in, nside_plot, pess=False, order_in="RING", order_out=None, power=None, dtype=None):
376
+ def ud_grade(
377
+ map_in,
378
+ nside_plot,
379
+ pess=False,
380
+ order_in="RING",
381
+ order_out=None,
382
+ power=None,
383
+ dtype=None,
384
+ ):
371
385
  """
372
386
  NAME:
373
387
  dust_vals_disk
@@ -383,29 +397,33 @@ def ud_grade(map_in, nside_plot, pess=False, order_in="RING", order_out=None, po
383
397
  """
384
398
  # check if arguements are implemented
385
399
  if order_in != "NEST" or (order_out is not None and order_out != "NEST"):
386
- raise NotImplementedError("order_in and order_out for RING scheme is not implemented for now")
400
+ raise NotImplementedError(
401
+ "order_in and order_out for RING scheme is not implemented for now"
402
+ )
387
403
  if power is not None:
388
404
  raise NotImplementedError("power is not implemented for now")
389
405
  if pess is not False:
390
406
  raise NotImplementedError("pess=True is not implemented for now")
391
407
  if dtype is not False:
392
- raise NotImplementedError("dtype is implemented for now, output map always has the same dtype as input map")
408
+ raise NotImplementedError(
409
+ "dtype is implemented for now, output map always has the same dtype as input map"
410
+ )
393
411
  check_nside(nside_plot, nest=order_in != "RING")
394
412
  map_in = np.asarray(map_in)
395
413
 
396
414
  num_of_map = len(np.atleast_2d(map_in))
397
415
  if num_of_map != 1:
398
416
  raise ValueError("This function only support one map at each time")
399
-
417
+
400
418
  nside_in = npix2nside(len(map_in))
401
419
  npix_in = nside2npix(nside_in)
402
420
  npix_out = nside2npix(nside_in)
403
421
 
404
- if nside_plot > nside_in: # upgrade
422
+ if nside_plot > nside_in: # upgrade
405
423
  rat2 = npix_out // npix_in
406
424
  fact = np.ones(rat2, dtype=map_in.dtype)
407
425
  map_out = np.outer(map_in, fact).reshape(npix_out)
408
- elif nside_plot < nside_in: # degrade
426
+ elif nside_plot < nside_in: # degrade
409
427
  rat2 = npix_in // npix_out
410
428
  mr = map_in.reshape(npix_out, rat2)
411
429
  goods = ~(np.isclose(mr, UNSEEN) | (~np.isfinite(mr)) | (~np.isnan(mr)))
@@ -418,5 +436,5 @@ def ud_grade(map_in, nside_plot, pess=False, order_in="RING", order_out=None, po
418
436
  pass
419
437
  else:
420
438
  map_out = map_in
421
-
422
- return map_out
439
+
440
+ return map_out
@@ -0,0 +1,129 @@
1
+ import sys
2
+ import sysconfig
3
+ import ctypes
4
+ import ctypes.util
5
+ from numpy.ctypeslib import ndpointer
6
+ import os, os.path
7
+ from pathlib import Path
8
+ import numpy
9
+ import platform
10
+ import tqdm
11
+ from mwdust.util.download import dust_dir
12
+
13
+ WIN32 = platform.system() == "Windows"
14
+ # Find and load the library
15
+ _lib = None
16
+ _libname = ctypes.util.find_library("sfd_c")
17
+ PY3 = sys.version > "3"
18
+ if PY3: # pragma: no cover
19
+ _ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
20
+ else:
21
+ _ext_suffix = sysconfig.get_config_var("SO")
22
+ if _libname:
23
+ _lib = ctypes.CDLL(_libname)
24
+ if _lib is None:
25
+ # Add top-level mwdust repository directory for pip install (-e) .,
26
+ # just becomes site-packages for regular install
27
+ paths = sys.path
28
+ paths.append(str(Path(__file__).parent.parent.parent.absolute()))
29
+ for path in [Path(p) for p in paths]:
30
+ if not path.is_dir():
31
+ continue
32
+ try:
33
+ _lib = ctypes.CDLL(str(path / f"sfd_c{_ext_suffix}"))
34
+ except OSError:
35
+ _lib = None
36
+ else:
37
+ break
38
+ if _lib is None:
39
+ raise IOError("SFD/C module not found")
40
+
41
+ # MAP path names
42
+ ebvFileN = os.path.join(dust_dir, "maps", "SFD_dust_4096_ngp.fits")
43
+ ebvFileS = os.path.join(dust_dir, "maps", "SFD_dust_4096_sgp.fits")
44
+
45
+
46
+ def read_SFD_EBV(glon, glat, interp=True, noloop=False, verbose=False, pbar=True):
47
+ """
48
+ NAME:
49
+ read_SFD_EBV
50
+ PURPOSE:
51
+ read an E(B-V) value from the Schlegel, Finkbeiner, & Davis (1998) maps
52
+ INPUT:
53
+ glon - Galactic longitude (deg), can be an array
54
+ glat - Galactic latitude (deg), can be an array
55
+ interp= (True) if True, interpolate using the nearest pixels
56
+ noloop= (False) if True, don't loop through the glons
57
+ verbose= (False) if True, be verbose
58
+ pbar= (True) if True, show progress bar
59
+ OUTPUT:
60
+ array of E(B-V) from Schlegel, Finkbeiner, & Davis (1998)
61
+ HISTORY:
62
+ 2013-11-23 - Written - Bovy (IAS)
63
+ """
64
+ # Parse input
65
+ if isinstance(glon, (int, float, numpy.float32, numpy.float64)):
66
+ glon = numpy.array([glon])
67
+ if isinstance(glat, (int, float, numpy.float32, numpy.float64)):
68
+ glat = numpy.array([glat])
69
+
70
+ nstar = len(glon)
71
+ if nstar > 1 and pbar:
72
+ pbar = tqdm.tqdm(total=nstar, leave=False)
73
+ pbar_func_ctype = ctypes.CFUNCTYPE(None)
74
+ pbar_c = pbar_func_ctype(pbar.update)
75
+ else: # pragma: no cover
76
+ pbar_c = None
77
+
78
+ # Set up the C code
79
+ ndarrayFlags = ("C_CONTIGUOUS", "WRITEABLE")
80
+ evalFunc = _lib.lambert_getval
81
+ evalFunc.argtypes = [
82
+ ctypes.c_char_p,
83
+ ctypes.c_char_p,
84
+ ctypes.c_long,
85
+ ndpointer(dtype=numpy.float32, flags=ndarrayFlags),
86
+ ndpointer(dtype=numpy.float32, flags=ndarrayFlags),
87
+ ctypes.c_int,
88
+ ctypes.c_int,
89
+ ctypes.c_int,
90
+ ndpointer(dtype=numpy.int32, flags=ndarrayFlags),
91
+ ctypes.c_void_p,
92
+ ]
93
+ evalFunc.restype = ctypes.POINTER(ctypes.c_float)
94
+
95
+ # Array requirements, first store old order
96
+ f_cont = [glon.flags["F_CONTIGUOUS"], glat.flags["F_CONTIGUOUS"]]
97
+ glon = numpy.require(glon, dtype=numpy.float64, requirements=["C", "W"])
98
+ glat = numpy.require(glat, dtype=numpy.float64, requirements=["C", "W"])
99
+ err = numpy.require(0, dtype=numpy.int32, requirements=["C", "W"])
100
+
101
+ # Check that the filename isn't too long for the SFD code
102
+ if len(ebvFileN.encode("ascii")) >= 120 or len(ebvFileS.encode("ascii")) >= 120:
103
+ raise RuntimeError(
104
+ f"The path of the file that contains the SFD dust maps is too long ({len(ebvFileN.encode('ascii'))}); please shorten the path of DUST_DIR"
105
+ )
106
+
107
+ res = evalFunc(
108
+ ctypes.c_char_p(ebvFileN.encode("ascii")),
109
+ ctypes.c_char_p(ebvFileS.encode("ascii")),
110
+ ctypes.c_long(nstar),
111
+ glon.astype(numpy.float32, order="C", copy=False),
112
+ glat.astype(numpy.float32, order="C", copy=False),
113
+ ctypes.c_int(interp),
114
+ ctypes.c_int(noloop),
115
+ ctypes.c_int(verbose),
116
+ err,
117
+ pbar_c,
118
+ )
119
+ if numpy.any(err == -10):
120
+ raise KeyboardInterrupt("Interrupted by CTRL-C (SIGINT)")
121
+ result = numpy.fromiter(res, dtype=float, count=nstar)
122
+
123
+ # Reset input arrays
124
+ if f_cont[0]:
125
+ glon = numpy.asfortranarray(glon)
126
+ if f_cont[1]:
127
+ glat = numpy.asfortranarray(glat)
128
+
129
+ return result
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mwdust
3
- Version: 1.5
3
+ Version: 1.6
4
4
  Summary: Dust in the Milky Way
5
5
  Home-page: https://github.com/jobovy/mwdust
6
6
  Author: Jo Bovy
@@ -24,6 +24,15 @@ Requires-Dist: h5py
24
24
  Requires-Dist: tqdm
25
25
  Requires-Dist: requests
26
26
  Requires-Dist: healpy
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: home-page
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: requires-dist
35
+ Dynamic: summary
27
36
 
28
37
  mwdust
29
38
  ======
@@ -98,6 +107,9 @@ roughly the following lay-out::
98
107
  bayestar2017.h5
99
108
  green19/
100
109
  bayestar2019.h5
110
+ zucker25/
111
+ decaps_mean.h5
112
+ decaps_mean_and_samples.h5
101
113
  maps/
102
114
  SFD_dust_4096_ngp.fits
103
115
  SFD_dust_4096_sgp.fits
@@ -238,6 +250,8 @@ map that you use:
238
250
 
239
251
  * **mwdust.Green19**: `Green et al. (2019) <https://ui.adsabs.harvard.edu/abs/2019arXiv190502734G>`__ (added by `@jan-rybizki <https://github.com/jan-rybizki>`__)
240
252
 
253
+ * **mwdust.Zucker25**: `Zucker et al. (2025) <https://ui.adsabs.harvard.edu/abs/2025arXiv250302657Z>`__
254
+
241
255
  * **mwdust.Combined15**: Combination of
242
256
 
243
257
  * `Marshall et al. (2006) <http://adsabs.harvard.edu/abs/2006A%26A...453..635M>`__ (**mwdust.Marshall06**),
@@ -17,6 +17,7 @@ mwdust/Marshall06.py
17
17
  mwdust/SFD.py
18
18
  mwdust/Sale14.py
19
19
  mwdust/Zero.py
20
+ mwdust/Zucker25.py
20
21
  mwdust/__init__.py
21
22
  mwdust.egg-info/PKG-INFO
22
23
  mwdust.egg-info/SOURCES.txt
@@ -48,7 +48,7 @@ install_requires= ['numpy','scipy','matplotlib','astropy','h5py','tqdm', 'reques
48
48
  if not WIN32:
49
49
  install_requires.append('healpy')
50
50
  setup(name='mwdust',
51
- version='1.5',
51
+ version='1.6',
52
52
  description='Dust in the Milky Way',
53
53
  author='Jo Bovy',
54
54
  author_email='bovy@astro.utoronto.ca',
@@ -0,0 +1,64 @@
1
+ import pathlib
2
+ import numpy
3
+ from numpy.random import default_rng
4
+
5
+ rng = default_rng()
6
+
7
+
8
+ def test_distance_independent():
9
+ # Test that the SFD extinction is indepdendent of distance
10
+ # for a given line of sight.
11
+ from mwdust import SFD
12
+
13
+ sfd = SFD()
14
+ glons = rng.uniform(0.0, 360.0, size=20)
15
+ glats = rng.uniform(-90.0, 90.0, size=20)
16
+ dists = rng.uniform(0.1, 10.0, size=5)
17
+ ebvs = numpy.array(
18
+ [
19
+ [sfd(glons[ii], glats[ii], dists[jj]) for ii in range(len(glons))]
20
+ for jj in range(len(dists))
21
+ ]
22
+ ).T
23
+ assert numpy.all(
24
+ numpy.fabs(ebvs - ebvs[0]) < 10.0**-10.0
25
+ ), "SFD extinction is not independent of distance for a given line of sight"
26
+ return None
27
+
28
+
29
+ def test_against_known_values():
30
+ # Test that the SFD extinction without interpolation agrees with a table of known values
31
+ # These were computed using mwdust on an M1 mac, so this test is really
32
+ # of consistency between other OSs and architectures
33
+ from mwdust import SFD
34
+
35
+ sfd = SFD(interp=False)
36
+ known = numpy.loadtxt(
37
+ pathlib.Path(__file__).parent / "sfd_benchmark.dat", delimiter=","
38
+ )
39
+ glons = known.T[0]
40
+ glats = known.T[1]
41
+ ebvs = known.T[2]
42
+ assert numpy.all(
43
+ ebvs - [sfd(glon, glat, 1.0)[0] for glon, glat in zip(glons, glats)]
44
+ < 10.0**-7.0
45
+ ), f"SFD extinction without interpolation does not agree with known values, with max difference {numpy.amax(numpy.fabs(ebvs-numpy.array([sfd(glon,glat,1.)[0] for glon,glat in zip(glons,glats)])))}"
46
+
47
+ # Test that the SFD extinction with interpolation agrees with a table of known values
48
+ # These were computed using mwdust on a linux server, same cavert above
49
+ from mwdust import SFD
50
+
51
+ sfd = SFD()
52
+ # specifically contains lb = (30, 3), a previously known inconsistency between Linux and Windows
53
+ known = numpy.loadtxt(
54
+ pathlib.Path(__file__).parent / "sfd_mwdust12_linux_benchmark.dat",
55
+ delimiter=",",
56
+ )
57
+ glons = known.T[0]
58
+ glats = known.T[1]
59
+ ebvs = known.T[2]
60
+ assert numpy.all(
61
+ ebvs - [sfd(glon, glat, 1.0)[0] for glon, glat in zip(glons, glats)]
62
+ < 10.0**-7.0
63
+ ), f"SFD extinction does not agree with known values, with max difference {numpy.amax(numpy.fabs(ebvs-numpy.array([sfd(glon,glat,1.)[0] for glon,glat in zip(glons,glats)])))}"
64
+ return None
@@ -1,123 +0,0 @@
1
- import sys
2
- import distutils.sysconfig as sysconfig
3
- import ctypes
4
- import ctypes.util
5
- from numpy.ctypeslib import ndpointer
6
- import os, os.path
7
- from pathlib import Path
8
- import numpy
9
- import platform
10
- import tqdm
11
- from mwdust.util.download import dust_dir
12
-
13
- WIN32= platform.system() == 'Windows'
14
- #Find and load the library
15
- _lib = None
16
- _libname = ctypes.util.find_library('sfd_c')
17
- PY3= sys.version > '3'
18
- if PY3: #pragma: no cover
19
- _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX')
20
- else:
21
- _ext_suffix= sysconfig.get_config_var('SO')
22
- if _libname:
23
- _lib = ctypes.CDLL(_libname)
24
- if _lib is None:
25
- # Add top-level mwdust repository directory for pip install (-e) .,
26
- # just becomes site-packages for regular install
27
- paths = sys.path
28
- paths.append(str(Path(__file__).parent.parent.parent.absolute()))
29
- for path in [Path(p) for p in paths]:
30
- if not path.is_dir():
31
- continue
32
- try:
33
- _lib = ctypes.CDLL(str(path / f"sfd_c{_ext_suffix}"))
34
- except OSError:
35
- _lib = None
36
- else:
37
- break
38
- if _lib is None:
39
- raise IOError('SFD/C module not found')
40
-
41
- #MAP path names
42
- ebvFileN= os.path.join(dust_dir,'maps','SFD_dust_4096_ngp.fits')
43
- ebvFileS= os.path.join(dust_dir,'maps','SFD_dust_4096_sgp.fits')
44
-
45
- def read_SFD_EBV(glon,glat,interp=True,noloop=False,verbose=False,pbar=True):
46
- """
47
- NAME:
48
- read_SFD_EBV
49
- PURPOSE:
50
- read an E(B-V) value from the Schlegel, Finkbeiner, & Davis (1998) maps
51
- INPUT:
52
- glon - Galactic longitude (deg), can be an array
53
- glat - Galactic latitude (deg), can be an array
54
- interp= (True) if True, interpolate using the nearest pixels
55
- noloop= (False) if True, don't loop through the glons
56
- verbose= (False) if True, be verbose
57
- pbar= (True) if True, show progress bar
58
- OUTPUT:
59
- array of E(B-V) from Schlegel, Finkbeiner, & Davis (1998)
60
- HISTORY:
61
- 2013-11-23 - Written - Bovy (IAS)
62
- """
63
- #Parse input
64
- if isinstance(glon,(int,float,numpy.float32,numpy.float64)):
65
- glon= numpy.array([glon])
66
- if isinstance(glat,(int,float,numpy.float32,numpy.float64)):
67
- glat= numpy.array([glat])
68
-
69
- nstar = len(glon)
70
- if nstar > 1 and pbar:
71
- pbar= tqdm.tqdm(total=nstar, leave=False)
72
- pbar_func_ctype= ctypes.CFUNCTYPE(None)
73
- pbar_c= pbar_func_ctype(pbar.update)
74
- else: # pragma: no cover
75
- pbar_c= None
76
-
77
- #Set up the C code
78
- ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE')
79
- evalFunc= _lib.lambert_getval
80
- evalFunc.argtypes= [ctypes.c_char_p,
81
- ctypes.c_char_p,
82
- ctypes.c_long,
83
- ndpointer(dtype=numpy.float32,flags=ndarrayFlags),
84
- ndpointer(dtype=numpy.float32,flags=ndarrayFlags),
85
- ctypes.c_int,
86
- ctypes.c_int,
87
- ctypes.c_int,
88
- ndpointer(dtype=numpy.int32,flags=ndarrayFlags),
89
- ctypes.c_void_p]
90
- evalFunc.restype= ctypes.POINTER(ctypes.c_float)
91
-
92
- #Array requirements, first store old order
93
- f_cont= [glon.flags['F_CONTIGUOUS'],
94
- glat.flags['F_CONTIGUOUS']]
95
- glon= numpy.require(glon,dtype=numpy.float64,requirements=['C','W'])
96
- glat= numpy.require(glat,dtype=numpy.float64,requirements=['C','W'])
97
- err= numpy.require(0,dtype=numpy.int32,requirements=['C','W'])
98
-
99
- # Check that the filename isn't too long for the SFD code
100
- if len(ebvFileN.encode('ascii')) >= 120 \
101
- or len(ebvFileS.encode('ascii')) >= 120:
102
- raise RuntimeError(f"The path of the file that contains the SFD dust maps is too long ({len(ebvFileN.encode('ascii'))}); please shorten the path of DUST_DIR")
103
-
104
- res= evalFunc(ctypes.c_char_p(ebvFileN.encode('ascii')),
105
- ctypes.c_char_p(ebvFileS.encode('ascii')),
106
- ctypes.c_long(nstar),
107
- glon.astype(numpy.float32,order='C',copy=False),
108
- glat.astype(numpy.float32,order='C',copy=False),
109
- ctypes.c_int(interp),
110
- ctypes.c_int(noloop),
111
- ctypes.c_int(verbose),
112
- err,
113
- pbar_c
114
- )
115
- if numpy.any(err == -10):
116
- raise KeyboardInterrupt("Interrupted by CTRL-C (SIGINT)")
117
- result= numpy.fromiter(res,dtype=float,count=nstar)
118
-
119
- #Reset input arrays
120
- if f_cont[0]: glon= numpy.asfortranarray(glon)
121
- if f_cont[1]: glat= numpy.asfortranarray(glat)
122
-
123
- return result
@@ -1,47 +0,0 @@
1
- import pathlib
2
- import numpy
3
- from numpy.random import default_rng
4
- rng= default_rng()
5
-
6
- def test_distance_independent():
7
- # Test that the SFD extinction is indepdendent of distance
8
- # for a given line of sight.
9
- from mwdust import SFD
10
- sfd= SFD()
11
- glons= rng.uniform(0.,360.,size=20)
12
- glats= rng.uniform(-90.,90.,size=20)
13
- dists= rng.uniform(0.1,10.,size=5)
14
- ebvs= numpy.array([[sfd(glons[ii],glats[ii],dists[jj]) \
15
- for ii in range(len(glons))]
16
- for jj in range(len(dists))]).T
17
- assert numpy.all(numpy.fabs(ebvs-ebvs[0]) < 10.**-10.), \
18
- 'SFD extinction is not independent of distance for a given line of sight'
19
- return None
20
-
21
- def test_against_known_values():
22
- # Test that the SFD extinction without interpolation agrees with a table of known values
23
- # These were computed using mwdust on an M1 mac, so this test is really
24
- # of consistency between other OSs and architectures
25
- from mwdust import SFD
26
- sfd= SFD(interp=False)
27
- known= numpy.loadtxt(pathlib.Path(__file__).parent / 'sfd_benchmark.dat',delimiter=',')
28
- glons= known.T[0]
29
- glats= known.T[1]
30
- ebvs= known.T[2]
31
- assert numpy.all(ebvs-[sfd(glon,glat,1.)[0]
32
- for glon,glat in zip(glons,glats)] < 10.**-8.), \
33
- 'SFD extinction without interpolation does not agree with known values'
34
-
35
- # Test that the SFD extinction with interpolation agrees with a table of known values
36
- # These were computed using mwdust on a linux server, same cavert above
37
- from mwdust import SFD
38
- sfd= SFD()
39
- # specifically contains lb = (30, 3), a previously known inconsistency between Linux and Windows
40
- known= numpy.loadtxt(pathlib.Path(__file__).parent / 'sfd_mwdust12_linux_benchmark.dat',delimiter=',')
41
- glons= known.T[0]
42
- glats= known.T[1]
43
- ebvs= known.T[2]
44
- assert numpy.all(ebvs-[sfd(glon,glat,1.)[0]
45
- for glon,glat in zip(glons,glats)] < 10.**-8.), \
46
- 'SFD extinction does not agree with known values'
47
- return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes