litebird_sim 0.12.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.
- default_imo/schema.json.gz +0 -0
- images/logo.png +0 -0
- litebird_sim/__init__.py +287 -0
- litebird_sim/bandpasses.py +344 -0
- litebird_sim/compress.py +41 -0
- litebird_sim/constants.py +14 -0
- litebird_sim/coordinates.py +314 -0
- litebird_sim/datautils/Cls_Planck2018_for_PTEP_2020_r0.fits +0 -0
- litebird_sim/datautils/Cls_Planck2018_for_PTEP_2020_tensor_r1.fits +0 -0
- litebird_sim/detectors.py +435 -0
- litebird_sim/dipole.py +308 -0
- litebird_sim/distribute.py +218 -0
- litebird_sim/gaindrifts.py +600 -0
- litebird_sim/healpix.py +395 -0
- litebird_sim/hwp.py +145 -0
- litebird_sim/hwp_sys/__init__.py +1 -0
- litebird_sim/hwp_sys/bandpass_template_module.py +218 -0
- litebird_sim/hwp_sys/examples/HWP_systematics.txt +4 -0
- litebird_sim/hwp_sys/examples/MFT_100_h_beta_z.txt +23 -0
- litebird_sim/hwp_sys/examples/simple_scan.ipynb +1648 -0
- litebird_sim/hwp_sys/hwp_sys.py +1392 -0
- litebird_sim/imo/__init__.py +12 -0
- litebird_sim/imo/imo.py +178 -0
- litebird_sim/imobrowser.py +360 -0
- litebird_sim/install_imo.py +278 -0
- litebird_sim/io.py +646 -0
- litebird_sim/madam.py +461 -0
- litebird_sim/mapmaking/__init__.py +32 -0
- litebird_sim/mapmaking/binner.py +396 -0
- litebird_sim/mapmaking/common.py +567 -0
- litebird_sim/mapmaking/destriper.py +2078 -0
- litebird_sim/mbs/__init__.py +3 -0
- litebird_sim/mbs/fg_models/pysm_ame_1.cfg +21 -0
- litebird_sim/mbs/fg_models/pysm_dust_0.cfg +16 -0
- litebird_sim/mbs/fg_models/pysm_dust_1.cfg +16 -0
- litebird_sim/mbs/fg_models/pysm_dust_4.cfg +32 -0
- litebird_sim/mbs/fg_models/pysm_dust_5.cfg +23 -0
- litebird_sim/mbs/fg_models/pysm_dust_7.cfg +23 -0
- litebird_sim/mbs/fg_models/pysm_dust_8.cfg +24 -0
- litebird_sim/mbs/fg_models/pysm_freefree_1.cfg +9 -0
- litebird_sim/mbs/fg_models/pysm_synch_0.cfg +14 -0
- litebird_sim/mbs/fg_models/pysm_synch_1.cfg +14 -0
- litebird_sim/mbs/mbs.py +1016 -0
- litebird_sim/mpi.py +62 -0
- litebird_sim/noise.py +248 -0
- litebird_sim/observations.py +708 -0
- litebird_sim/pointings.py +221 -0
- litebird_sim/quaternions.py +404 -0
- litebird_sim/scan_map.py +217 -0
- litebird_sim/scanning.py +904 -0
- litebird_sim/simulations.py +1779 -0
- litebird_sim/spacecraft.py +448 -0
- litebird_sim/toast_destriper/__init__.py +432 -0
- litebird_sim/version.py +4 -0
- litebird_sim-0.12.0.dist-info/LICENSE +674 -0
- litebird_sim-0.12.0.dist-info/METADATA +214 -0
- litebird_sim-0.12.0.dist-info/RECORD +73 -0
- litebird_sim-0.12.0.dist-info/WHEEL +4 -0
- static/report_template.html +47 -0
- static/sakura.css +165 -0
- templates/madam_parameter_file.txt +44 -0
- templates/madam_simulation_file.txt +38 -0
- templates/report_appendix.md +66 -0
- templates/report_binned_map.md +6 -0
- templates/report_binned_map_splits.md +8 -0
- templates/report_destriper.md +30 -0
- templates/report_dipole.md +9 -0
- templates/report_gaindrift.md +25 -0
- templates/report_header.md +14 -0
- templates/report_noise.md +3 -0
- templates/report_pointings.md +8 -0
- templates/report_quaternions.md +7 -0
- templates/report_scan_map.md +8 -0
|
Binary file
|
images/logo.png
ADDED
|
Binary file
|
litebird_sim/__init__.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
import importlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import numba
|
|
5
|
+
|
|
6
|
+
from .compress import (
|
|
7
|
+
rle_compress,
|
|
8
|
+
rle_decompress,
|
|
9
|
+
)
|
|
10
|
+
from .distribute import distribute_evenly, distribute_optimally
|
|
11
|
+
from .detectors import (
|
|
12
|
+
DetectorInfo,
|
|
13
|
+
FreqChannelInfo,
|
|
14
|
+
InstrumentInfo,
|
|
15
|
+
detector_list_from_parameters,
|
|
16
|
+
)
|
|
17
|
+
from .bandpasses import BandPassInfo
|
|
18
|
+
from .healpix import (
|
|
19
|
+
nside_to_npix,
|
|
20
|
+
npix_to_nside,
|
|
21
|
+
is_npix_ok,
|
|
22
|
+
map_type,
|
|
23
|
+
get_pixel_format,
|
|
24
|
+
write_healpix_map_to_hdu,
|
|
25
|
+
write_healpix_map_to_file,
|
|
26
|
+
)
|
|
27
|
+
from .hwp import (
|
|
28
|
+
HWP,
|
|
29
|
+
IdealHWP,
|
|
30
|
+
)
|
|
31
|
+
from .hwp_sys.hwp_sys import (
|
|
32
|
+
HwpSys,
|
|
33
|
+
)
|
|
34
|
+
from .imo import (
|
|
35
|
+
Imo,
|
|
36
|
+
FormatSpecification,
|
|
37
|
+
Entity,
|
|
38
|
+
Quantity,
|
|
39
|
+
Release,
|
|
40
|
+
)
|
|
41
|
+
from .madam import save_simulation_for_madam
|
|
42
|
+
from .mbs.mbs import Mbs, MbsParameters, MbsSavedMapInfo
|
|
43
|
+
from .mpi import MPI_COMM_WORLD, MPI_ENABLED, MPI_CONFIGURATION
|
|
44
|
+
from .observations import Observation, TodDescription
|
|
45
|
+
from .pointings import (
|
|
46
|
+
apply_hwp_to_obs,
|
|
47
|
+
get_pointing_buffer_shape,
|
|
48
|
+
get_pointings,
|
|
49
|
+
get_pointings_for_observations,
|
|
50
|
+
)
|
|
51
|
+
from .quaternions import (
|
|
52
|
+
quat_rotation_x,
|
|
53
|
+
quat_rotation_y,
|
|
54
|
+
quat_rotation_z,
|
|
55
|
+
quat_right_multiply,
|
|
56
|
+
quat_left_multiply,
|
|
57
|
+
rotate_vector,
|
|
58
|
+
rotate_x_vector,
|
|
59
|
+
rotate_y_vector,
|
|
60
|
+
rotate_z_vector,
|
|
61
|
+
all_rotate_vectors,
|
|
62
|
+
all_rotate_x_vectors,
|
|
63
|
+
all_rotate_y_vectors,
|
|
64
|
+
all_rotate_z_vectors,
|
|
65
|
+
)
|
|
66
|
+
from .scanning import (
|
|
67
|
+
compute_pointing_and_orientation,
|
|
68
|
+
all_compute_pointing_and_orientation,
|
|
69
|
+
spin_to_ecliptic,
|
|
70
|
+
all_spin_to_ecliptic,
|
|
71
|
+
calculate_sun_earth_angles_rad,
|
|
72
|
+
Spin2EclipticQuaternions,
|
|
73
|
+
ScanningStrategy,
|
|
74
|
+
SpinningScanningStrategy,
|
|
75
|
+
get_det2ecl_quaternions,
|
|
76
|
+
get_ecl2det_quaternions,
|
|
77
|
+
)
|
|
78
|
+
from litebird_sim.mapmaking import (
|
|
79
|
+
make_binned_map,
|
|
80
|
+
check_valid_splits,
|
|
81
|
+
BinnerResult,
|
|
82
|
+
make_destriped_map,
|
|
83
|
+
DestriperParameters,
|
|
84
|
+
DestriperResult,
|
|
85
|
+
ExternalDestriperParameters,
|
|
86
|
+
)
|
|
87
|
+
from .simulations import (
|
|
88
|
+
NUMBA_NUM_THREADS_ENVVAR,
|
|
89
|
+
Simulation,
|
|
90
|
+
MpiObservationDescr,
|
|
91
|
+
MpiProcessDescr,
|
|
92
|
+
MpiDistributionDescr,
|
|
93
|
+
)
|
|
94
|
+
from .noise import (
|
|
95
|
+
add_white_noise,
|
|
96
|
+
add_one_over_f_noise,
|
|
97
|
+
add_noise,
|
|
98
|
+
add_noise_to_observations,
|
|
99
|
+
)
|
|
100
|
+
from .scan_map import scan_map, scan_map_in_observations
|
|
101
|
+
from .coordinates import (
|
|
102
|
+
DEFAULT_COORDINATE_SYSTEM,
|
|
103
|
+
DEFAULT_TIME_SCALE,
|
|
104
|
+
ECL_TO_GAL_ROT_MATRIX,
|
|
105
|
+
CoordinateSystem,
|
|
106
|
+
coord_sys_to_healpix_string,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
from .spacecraft import (
|
|
110
|
+
compute_l2_pos_and_vel,
|
|
111
|
+
compute_lissajous_pos_and_vel,
|
|
112
|
+
spacecraft_pos_and_vel,
|
|
113
|
+
SpacecraftOrbit,
|
|
114
|
+
SpacecraftPositionAndVelocity,
|
|
115
|
+
)
|
|
116
|
+
from .dipole import add_dipole, add_dipole_to_observations, DipoleType
|
|
117
|
+
from .io import (
|
|
118
|
+
write_list_of_observations,
|
|
119
|
+
write_observations,
|
|
120
|
+
read_list_of_observations,
|
|
121
|
+
read_observations,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
from .gaindrifts import (
|
|
125
|
+
GainDriftType,
|
|
126
|
+
SamplingDist,
|
|
127
|
+
GainDriftParams,
|
|
128
|
+
apply_gaindrift_for_one_detector,
|
|
129
|
+
apply_gaindrift_to_tod,
|
|
130
|
+
apply_gaindrift_to_observations,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
from .version import __author__, __version__
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Check if the TOAST2 mapmaker is available
|
|
137
|
+
TOAST_ENABLED = importlib.util.find_spec("OpMapMaker", "toast.todmap") is not None
|
|
138
|
+
if not TOAST_ENABLED:
|
|
139
|
+
|
|
140
|
+
def destripe_with_toast2(*args, **kwargs):
|
|
141
|
+
raise ImportError(
|
|
142
|
+
"Install the toast package using `pip` to use destripe_with_toast2"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
TOAST_ENABLED = False
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Privilege TBB over OpenPM and the internal Numba implementation of a
|
|
149
|
+
# work queue
|
|
150
|
+
numba.config.THREADING_LAYER_CONFIG = ["tbb", "omp", "workqueue"]
|
|
151
|
+
|
|
152
|
+
PTEP_IMO_LOCATION = Path(__file__).parent.parent / "default_imo"
|
|
153
|
+
|
|
154
|
+
__all__ = [
|
|
155
|
+
"__author__",
|
|
156
|
+
"__version__",
|
|
157
|
+
"PTEP_IMO_LOCATION",
|
|
158
|
+
# compress.py
|
|
159
|
+
"rle_compress",
|
|
160
|
+
"rle_decompress",
|
|
161
|
+
# bandpasses.py
|
|
162
|
+
"BandPassInfo",
|
|
163
|
+
# healpix.py
|
|
164
|
+
"nside_to_npix",
|
|
165
|
+
"npix_to_nside",
|
|
166
|
+
"is_npix_ok",
|
|
167
|
+
"map_type",
|
|
168
|
+
"get_pixel_format",
|
|
169
|
+
"write_healpix_map_to_hdu",
|
|
170
|
+
"write_healpix_map_to_file",
|
|
171
|
+
# distribute.py
|
|
172
|
+
"distribute_evenly",
|
|
173
|
+
"distribute_optimally",
|
|
174
|
+
# imo.py
|
|
175
|
+
"Imo",
|
|
176
|
+
# imoobjects
|
|
177
|
+
"FormatSpecification",
|
|
178
|
+
"Entity",
|
|
179
|
+
"Quantity",
|
|
180
|
+
"Release",
|
|
181
|
+
# detectors.py
|
|
182
|
+
"DetectorInfo",
|
|
183
|
+
"FreqChannelInfo",
|
|
184
|
+
"InstrumentInfo",
|
|
185
|
+
"detector_list_from_parameters",
|
|
186
|
+
# hwp.py
|
|
187
|
+
"HWP",
|
|
188
|
+
"IdealHWP",
|
|
189
|
+
# hwp_sys/hwp_sys.py
|
|
190
|
+
"HwpSys",
|
|
191
|
+
# madam.py
|
|
192
|
+
"save_simulation_for_madam",
|
|
193
|
+
# mbs.py
|
|
194
|
+
"Mbs",
|
|
195
|
+
"MbsParameters",
|
|
196
|
+
"MbsSavedMapInfo",
|
|
197
|
+
# mpi.py
|
|
198
|
+
"MPI_COMM_WORLD",
|
|
199
|
+
"MPI_ENABLED",
|
|
200
|
+
"MPI_CONFIGURATION",
|
|
201
|
+
# observations.py
|
|
202
|
+
"Observation",
|
|
203
|
+
"TodDescription",
|
|
204
|
+
# quaternions.py
|
|
205
|
+
"quat_rotation_x",
|
|
206
|
+
"quat_rotation_y",
|
|
207
|
+
"quat_rotation_z",
|
|
208
|
+
"quat_right_multiply",
|
|
209
|
+
"quat_left_multiply",
|
|
210
|
+
"rotate_vector",
|
|
211
|
+
"rotate_x_vector",
|
|
212
|
+
"rotate_y_vector",
|
|
213
|
+
"rotate_z_vector",
|
|
214
|
+
"all_rotate_vectors",
|
|
215
|
+
"all_rotate_x_vectors",
|
|
216
|
+
"all_rotate_y_vectors",
|
|
217
|
+
"all_rotate_z_vectors",
|
|
218
|
+
# scanning.py
|
|
219
|
+
"compute_pointing_and_orientation",
|
|
220
|
+
"all_compute_pointing_and_orientation",
|
|
221
|
+
"spin_to_ecliptic",
|
|
222
|
+
"all_spin_to_ecliptic",
|
|
223
|
+
"calculate_sun_earth_angles_rad",
|
|
224
|
+
"Spin2EclipticQuaternions",
|
|
225
|
+
"ScanningStrategy",
|
|
226
|
+
"SpinningScanningStrategy",
|
|
227
|
+
"get_det2ecl_quaternions",
|
|
228
|
+
"get_ecl2det_quaternions",
|
|
229
|
+
# pointings.py
|
|
230
|
+
"apply_hwp_to_obs",
|
|
231
|
+
"get_pointing_buffer_shape",
|
|
232
|
+
"get_pointings",
|
|
233
|
+
"get_pointings_for_observations",
|
|
234
|
+
# mapmaking
|
|
235
|
+
"make_binned_map",
|
|
236
|
+
"check_valid_splits",
|
|
237
|
+
"BinnerResult",
|
|
238
|
+
"make_destriped_map",
|
|
239
|
+
"DestriperParameters",
|
|
240
|
+
"DestriperResult",
|
|
241
|
+
"ExternalDestriperParameters",
|
|
242
|
+
# toast_destriper.py
|
|
243
|
+
"TOAST_ENABLED",
|
|
244
|
+
"destripe_with_toast2",
|
|
245
|
+
# simulations.py
|
|
246
|
+
"NUMBA_NUM_THREADS_ENVVAR",
|
|
247
|
+
"Simulation",
|
|
248
|
+
"MpiObservationDescr",
|
|
249
|
+
"MpiProcessDescr",
|
|
250
|
+
"MpiDistributionDescr",
|
|
251
|
+
# noise.py
|
|
252
|
+
"add_white_noise",
|
|
253
|
+
"add_one_over_f_noise",
|
|
254
|
+
"add_noise",
|
|
255
|
+
"add_noise_to_observations",
|
|
256
|
+
# scan_map.py
|
|
257
|
+
"scan_map",
|
|
258
|
+
"scan_map_in_observations",
|
|
259
|
+
# dipole.py
|
|
260
|
+
"add_dipole",
|
|
261
|
+
"add_dipole_to_observations",
|
|
262
|
+
"DipoleType",
|
|
263
|
+
# coordinates.py
|
|
264
|
+
"DEFAULT_COORDINATE_SYSTEM",
|
|
265
|
+
"DEFAULT_TIME_SCALE",
|
|
266
|
+
"ECL_TO_GAL_ROT_MATRIX",
|
|
267
|
+
"CoordinateSystem",
|
|
268
|
+
"coord_sys_to_healpix_string",
|
|
269
|
+
# spacecraft.py
|
|
270
|
+
"compute_l2_pos_and_vel",
|
|
271
|
+
"compute_lissajous_pos_and_vel",
|
|
272
|
+
"spacecraft_pos_and_vel",
|
|
273
|
+
"SpacecraftOrbit",
|
|
274
|
+
"SpacecraftPositionAndVelocity",
|
|
275
|
+
# io
|
|
276
|
+
"write_list_of_observations",
|
|
277
|
+
"write_observations",
|
|
278
|
+
"read_list_of_observations",
|
|
279
|
+
"read_observations",
|
|
280
|
+
# gaindrifts.py
|
|
281
|
+
"GainDriftType",
|
|
282
|
+
"SamplingDist",
|
|
283
|
+
"GainDriftParams",
|
|
284
|
+
"apply_gaindrift_for_one_detector",
|
|
285
|
+
"apply_gaindrift_to_tod",
|
|
286
|
+
"apply_gaindrift_to_observations",
|
|
287
|
+
]
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy as sp
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional, Union
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from .imo import Imo
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _apod(x, a, b):
|
|
15
|
+
return (1 + np.cos((x - a) / (b - a) * np.pi)) / 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class BandPassInfo:
|
|
20
|
+
"""A class wrapping the basic information about a detector bandpass.
|
|
21
|
+
|
|
22
|
+
This class encodes the basic properties of a frequency band.
|
|
23
|
+
|
|
24
|
+
It can be initialized in three ways:
|
|
25
|
+
|
|
26
|
+
- The default constructor will generate top-hat bandpasses,
|
|
27
|
+
assuming that band centroid and width are provided.
|
|
28
|
+
|
|
29
|
+
- Through the class method :meth:`.from_imo`, which reads the
|
|
30
|
+
detector bandpass from the LiteBIRD Instrument Model (see
|
|
31
|
+
the :class:`.Imo` class).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
|
|
35
|
+
- bandcenter_ghz (float): center frequency in GHz
|
|
36
|
+
|
|
37
|
+
- bandwidth_ghz (float): width of the band (default=0.)
|
|
38
|
+
|
|
39
|
+
- bandlow_ghz (float): lowest frequency to sample the band,
|
|
40
|
+
i.e. the first element of the frequency array considered
|
|
41
|
+
(default: bandcenter_ghz - bandwidth_ghz)
|
|
42
|
+
|
|
43
|
+
- bandhigh_ghz (float): highest frequency to sample the band,
|
|
44
|
+
i.e. the last element of frequency array considered
|
|
45
|
+
(default: bandcenter_ghz + bandwidth_ghz)
|
|
46
|
+
|
|
47
|
+
- nsamples_inband (int): number of elements to sample the band
|
|
48
|
+
(default=128)
|
|
49
|
+
|
|
50
|
+
- name (str) : ID of the band
|
|
51
|
+
|
|
52
|
+
- normalize(bool) : If set to true bandpass weights will be
|
|
53
|
+
normalized to 1
|
|
54
|
+
|
|
55
|
+
- bandtype (str): a string describing the band profile. It can be
|
|
56
|
+
one of the following:
|
|
57
|
+
|
|
58
|
+
- ``top-hat`` (default)
|
|
59
|
+
|
|
60
|
+
- ``top-hat-exp``: the edges of the band are apodized with an
|
|
61
|
+
exponential profile
|
|
62
|
+
|
|
63
|
+
- ``top-hat-cosine``: the edges of the band are apodized with
|
|
64
|
+
a cosine profile
|
|
65
|
+
|
|
66
|
+
- ``cheby``: the bandpass encodes a Chebyshev profile
|
|
67
|
+
|
|
68
|
+
- alpha_exp (float): out-of-band exponential decay index for
|
|
69
|
+
low freq edge.
|
|
70
|
+
|
|
71
|
+
- beta_exp (float) : out-of-band exponential decay index for
|
|
72
|
+
high freq edge
|
|
73
|
+
|
|
74
|
+
- cosine_apo_length (int): the numerical factor related to
|
|
75
|
+
the cosine apodization length
|
|
76
|
+
|
|
77
|
+
- cheby_poly_order (int): chebyshev filter order.
|
|
78
|
+
|
|
79
|
+
- cheby_ripple_dB (int): maximum ripple amplitude in decibels.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
bandcenter_ghz: float = 0.0
|
|
83
|
+
bandwidth_ghz: float = 0.0
|
|
84
|
+
bandlow_ghz: Union[float, None] = None
|
|
85
|
+
bandhigh_ghz: Union[float, None] = None
|
|
86
|
+
nsamples_inband: int = 128
|
|
87
|
+
name: str = ""
|
|
88
|
+
bandtype: str = "top-hat"
|
|
89
|
+
normalize: bool = False
|
|
90
|
+
model: str = None
|
|
91
|
+
|
|
92
|
+
alpha_exp: float = 1
|
|
93
|
+
beta_exp: float = 1
|
|
94
|
+
cosine_apo_length: float = 5
|
|
95
|
+
cheby_poly_order: int = 3
|
|
96
|
+
cheby_ripple_dB: int = 3
|
|
97
|
+
|
|
98
|
+
def __post_init__(self):
|
|
99
|
+
"""
|
|
100
|
+
Constructor of the class
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
if self.bandcenter_ghz <= 0.0:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
"The band center must be strictly positive, \
|
|
106
|
+
assign a value to bandcenter_ghz > 0.0"
|
|
107
|
+
)
|
|
108
|
+
self.f0, self.f1 = self.get_edges()
|
|
109
|
+
# we extend the wings out-of-band of the top-hat bandpass
|
|
110
|
+
# before and after the edges
|
|
111
|
+
if not self.bandlow_ghz:
|
|
112
|
+
self.bandlow_ghz = self.bandcenter_ghz - self.bandwidth_ghz
|
|
113
|
+
if not self.bandhigh_ghz:
|
|
114
|
+
self.bandhigh_ghz = self.bandcenter_ghz + self.bandwidth_ghz
|
|
115
|
+
self.freqs_ghz = np.linspace(
|
|
116
|
+
self.bandlow_ghz, self.bandhigh_ghz, self.nsamples_inband
|
|
117
|
+
)
|
|
118
|
+
# checking that the bandpass edges lie within the freq range
|
|
119
|
+
if self.bandlow_ghz > self.f0 or self.bandhigh_ghz < self.f1 - 1:
|
|
120
|
+
raise ValueError("The bandpass is out of the frequency range")
|
|
121
|
+
|
|
122
|
+
self.isnormalized = False
|
|
123
|
+
if self.bandtype == "top-hat":
|
|
124
|
+
self._get_top_hat_bandpass(normalize=self.normalize)
|
|
125
|
+
elif self.bandtype == "top-hat-cosine":
|
|
126
|
+
self._get_top_hat_bandpass(apodization="cosine", normalize=self.normalize)
|
|
127
|
+
elif self.bandtype == "top-hat-exp":
|
|
128
|
+
self._get_top_hat_bandpass(apodization="exp", normalize=self.normalize)
|
|
129
|
+
elif self.bandtype == "cheby":
|
|
130
|
+
self._get_chebyshev_bandpass(normalize=self.normalize)
|
|
131
|
+
else:
|
|
132
|
+
logging.warning(f"{self.bandtype} profile not implemented. Assume top-hat")
|
|
133
|
+
self._get_top_hat_bandpass(normalize=self.normalize)
|
|
134
|
+
|
|
135
|
+
def get_edges(self):
|
|
136
|
+
"""
|
|
137
|
+
get the edges of the tophat band
|
|
138
|
+
"""
|
|
139
|
+
return (
|
|
140
|
+
self.bandcenter_ghz - self.bandwidth_ghz / 2,
|
|
141
|
+
self.bandcenter_ghz + self.bandwidth_ghz / 2,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def _get_top_hat_bandpass(self, normalize=False, apodization: Optional[str] = None):
|
|
145
|
+
"""
|
|
146
|
+
Sample a top-hat bandpass, given the centroid and the bandwidth.
|
|
147
|
+
If the `normalize` flag is set to ``True``, the transmission
|
|
148
|
+
coefficients are
|
|
149
|
+
normalized so that its integral is 1 over the frequency band.
|
|
150
|
+
The parameter `apodization` must be either ``"cosine"``, ``"exp"``,
|
|
151
|
+
or ``None``: in the latter case, no apodization of the beamshape
|
|
152
|
+
is performed.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
self.weights = np.zeros_like(self.freqs_ghz)
|
|
156
|
+
mask = np.ma.masked_inside(self.freqs_ghz, self.f0, self.f1).mask
|
|
157
|
+
self.weights[mask] = 1.0
|
|
158
|
+
|
|
159
|
+
if apodization == "cosine":
|
|
160
|
+
self._cosine_apodize_bandpass()
|
|
161
|
+
elif apodization == "exp":
|
|
162
|
+
self._exp_apodize_bandpass()
|
|
163
|
+
|
|
164
|
+
if normalize:
|
|
165
|
+
self.normalize_band()
|
|
166
|
+
|
|
167
|
+
def normalize_band(self):
|
|
168
|
+
"""
|
|
169
|
+
Normalize the band transmission coefficients
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
A = np.trapz(self.weights, self.freqs_ghz)
|
|
173
|
+
self.weights /= A
|
|
174
|
+
self.isnormalized = True
|
|
175
|
+
|
|
176
|
+
def _exp_apodize_bandpass(self):
|
|
177
|
+
"""Define a bandpass with exponential tails and unit transmission
|
|
178
|
+
in band freqs: frequency in GHz
|
|
179
|
+
"""
|
|
180
|
+
mask_beta = np.ma.masked_greater(self.freqs_ghz, self.f1).mask
|
|
181
|
+
self.weights[mask_beta] = np.exp(
|
|
182
|
+
-self.beta_exp * (self.freqs_ghz[mask_beta] - self.f1)
|
|
183
|
+
)
|
|
184
|
+
mask_alpha = np.ma.masked_less(self.freqs_ghz, self.f0).mask
|
|
185
|
+
self.weights[mask_alpha] = np.exp(
|
|
186
|
+
self.alpha_exp * (self.freqs_ghz[mask_alpha] - self.f0)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _cosine_apodize_bandpass(self):
|
|
190
|
+
"""
|
|
191
|
+
Define a bandpass with cosine tails and unit transmission in band
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
apolength = self.bandwidth_ghz / self.cosine_apo_length
|
|
195
|
+
f_above = self.bandcenter_ghz * (1 + self.bandwidth_ghz / 2 + apolength)
|
|
196
|
+
f_below = self.bandcenter_ghz * (1 - self.bandwidth_ghz / 2 - apolength)
|
|
197
|
+
mask_above = np.ma.masked_inside(self.freqs_ghz, self.f1, f_above).mask
|
|
198
|
+
|
|
199
|
+
x_ab = np.linspace(self.f1, f_above, self.freqs_ghz[mask_above].size)
|
|
200
|
+
|
|
201
|
+
self.weights[mask_above] = _apod(x_ab, self.f1, f_above)
|
|
202
|
+
|
|
203
|
+
mask_below = np.ma.masked_inside(self.freqs_ghz, f_below, self.f0).mask
|
|
204
|
+
x_bel = np.linspace(f_below, self.f0, self.freqs_ghz[mask_below].size)
|
|
205
|
+
self.weights[mask_below] = _apod(x_bel, self.f0, f_below)
|
|
206
|
+
|
|
207
|
+
# Chebyshev profile bandpass
|
|
208
|
+
def _get_chebyshev_bandpass(self, normalize=False):
|
|
209
|
+
"""
|
|
210
|
+
Define a bandpass with chebyshev prototype.
|
|
211
|
+
"""
|
|
212
|
+
b, a = sp.signal.cheby1(
|
|
213
|
+
self.cheby_poly_order,
|
|
214
|
+
self.cheby_ripple_dB,
|
|
215
|
+
[2.0 * np.pi * self.f0 * 1e9, 2.0 * np.pi * self.f1 * 1e9],
|
|
216
|
+
"bandpass",
|
|
217
|
+
analog=True,
|
|
218
|
+
)
|
|
219
|
+
w, h = sp.signal.freqs(b, a, worN=self.freqs_ghz * 2 * np.pi * 1e9)
|
|
220
|
+
|
|
221
|
+
self.weights = abs(h)
|
|
222
|
+
if normalize:
|
|
223
|
+
A = self.get_normalization()
|
|
224
|
+
self.weights /= A
|
|
225
|
+
self.isnormalized = True
|
|
226
|
+
|
|
227
|
+
def get_normalization(self):
|
|
228
|
+
"""
|
|
229
|
+
Estimate the integral over the frequency band
|
|
230
|
+
"""
|
|
231
|
+
return np.trapz(self.weights, self.freqs_ghz)
|
|
232
|
+
|
|
233
|
+
# Find effective central frequency of a bandpass profile
|
|
234
|
+
def find_central_frequency(self):
|
|
235
|
+
"""Find the effective central frequency of
|
|
236
|
+
a bandpass profile as defined in https://arxiv.org/abs/1303.5070
|
|
237
|
+
"""
|
|
238
|
+
if self.isnormalized:
|
|
239
|
+
return np.trapz(self.freqs_ghz * self.weights, self.freqs_ghz)
|
|
240
|
+
else:
|
|
241
|
+
return (
|
|
242
|
+
np.trapz(self.freqs_ghz * self.weights, self.freqs_ghz)
|
|
243
|
+
/ self.get_normalization()
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def from_imo(imo: Imo, url: Union[UUID, str]):
|
|
248
|
+
"""Create a `BandPassInfo` object from a definition in the IMO
|
|
249
|
+
The `url` must either specify a UUID or a full URL to the
|
|
250
|
+
object.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
obj = imo.query(url)
|
|
254
|
+
return BandPassInfo.from_dict(obj.metadata)
|
|
255
|
+
|
|
256
|
+
def _interpolate_band(self):
|
|
257
|
+
"""
|
|
258
|
+
This function aims at building the sampler in order to
|
|
259
|
+
generate random samples
|
|
260
|
+
statistically equivalent to the model bandpass
|
|
261
|
+
"""
|
|
262
|
+
# normalize band
|
|
263
|
+
|
|
264
|
+
if not self.isnormalized:
|
|
265
|
+
self.normalize_band()
|
|
266
|
+
# Interpolate the band
|
|
267
|
+
b = sp.interpolate.interp1d(x=self.freqs_ghz, y=self.weights)
|
|
268
|
+
# estimate the CDF
|
|
269
|
+
Pnu = np.array(
|
|
270
|
+
[
|
|
271
|
+
sp.integrate.quad(b, a=self.freqs_ghz.min(), b=inu)[0]
|
|
272
|
+
for inu in self.freqs_ghz[1:]
|
|
273
|
+
]
|
|
274
|
+
)
|
|
275
|
+
# interpolate the inverse CDF
|
|
276
|
+
self.Sampler = sp.interpolate.interp1d(
|
|
277
|
+
Pnu,
|
|
278
|
+
self.freqs_ghz[:-1] + np.diff(self.freqs_ghz),
|
|
279
|
+
bounds_error=False,
|
|
280
|
+
fill_value="extrapolate",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def bandpass_resampling(self, bstrap_size=1000, nresample=54, model=None):
|
|
284
|
+
"""
|
|
285
|
+
Resample a bandpass with bootstrap resampling.
|
|
286
|
+
|
|
287
|
+
Note that the user can provide any sampler built with the
|
|
288
|
+
`interpolate_band` method; otherwise, an error will be raised!
|
|
289
|
+
|
|
290
|
+
This function should be used when the user wants to generate
|
|
291
|
+
many realizations of bandpasses, e.g. per detector bands.
|
|
292
|
+
There is no need to initialize many
|
|
293
|
+
instances of the class :class:`.BandPassInfo`, just rerun this
|
|
294
|
+
functions multiple times issuing the same bandpass model instance.
|
|
295
|
+
|
|
296
|
+
Args :
|
|
297
|
+
|
|
298
|
+
- `bstrap_size` (int): encodes the size of the random dataset
|
|
299
|
+
to be generated from the Sampler
|
|
300
|
+
|
|
301
|
+
- `nresample` (int): define how fine is the grid for the
|
|
302
|
+
resampled bandpass
|
|
303
|
+
|
|
304
|
+
- `model` (BandPassInfo.model): We can resample from a model previously
|
|
305
|
+
constructed with this function. The default value is set to ``None``:
|
|
306
|
+
in this case, it initializes the bandpass sampler with the model set
|
|
307
|
+
in the class instance (recommended use).
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
if model is not None:
|
|
311
|
+
logging.info(f"Bandpass sampler from {model.name }")
|
|
312
|
+
sampler = model.Sampler
|
|
313
|
+
else:
|
|
314
|
+
try:
|
|
315
|
+
sampler = self.Sampler
|
|
316
|
+
except AttributeError:
|
|
317
|
+
print(
|
|
318
|
+
"Can't resample if no sampler is built and/or provided, "
|
|
319
|
+
"initializing the sampler and interpolating the band"
|
|
320
|
+
)
|
|
321
|
+
self._interpolate_band()
|
|
322
|
+
sampler = self.Sampler
|
|
323
|
+
except AttributeError:
|
|
324
|
+
logging.warning(
|
|
325
|
+
"Can't resample if no sampler is built and/or provided, "
|
|
326
|
+
"initializing the sampler and interpolating the band"
|
|
327
|
+
)
|
|
328
|
+
self._interpolate_band()
|
|
329
|
+
sampler = self.Sampler
|
|
330
|
+
|
|
331
|
+
X = np.random.uniform(size=bstrap_size)
|
|
332
|
+
bins_nu = np.linspace(self.freqs_ghz.min(), self.freqs_ghz.max(), nresample)
|
|
333
|
+
h, xb = np.histogram(sampler(X), density=True, bins=bins_nu)
|
|
334
|
+
|
|
335
|
+
nu_b = xb[:-1] + np.diff(xb)
|
|
336
|
+
resampled_bpass = abs(
|
|
337
|
+
sp.interpolate.interp1d(
|
|
338
|
+
nu_b, h, kind="cubic", bounds_error=False, fill_value=0.0
|
|
339
|
+
)(self.freqs_ghz)
|
|
340
|
+
)
|
|
341
|
+
if self.isnormalized:
|
|
342
|
+
return resampled_bpass / np.trapz(resampled_bpass, self.freqs_ghz)
|
|
343
|
+
else:
|
|
344
|
+
return resampled_bpass
|
litebird_sim/compress.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numba import njit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def rle_compress(arr):
|
|
8
|
+
"""Perform a Run-Length encoding of the input array
|
|
9
|
+
|
|
10
|
+
Returns a NumPy matrix of shape 2×N, where N is an integer; the first
|
|
11
|
+
row contains the *lengths* of each run, and the second row contains
|
|
12
|
+
the *values*."""
|
|
13
|
+
|
|
14
|
+
# This code was adapted from https://stackoverflow.com/a/32681075/3967151
|
|
15
|
+
|
|
16
|
+
ia = np.asarray(arr) # force numpy
|
|
17
|
+
nsamples = len(ia)
|
|
18
|
+
if nsamples == 0:
|
|
19
|
+
return None, None
|
|
20
|
+
else:
|
|
21
|
+
y = ia[1:] != ia[:-1]
|
|
22
|
+
indexes = np.append(np.where(y), nsamples - 1)
|
|
23
|
+
runs = np.diff(np.append(-1, indexes))
|
|
24
|
+
return np.array([runs, ia[indexes]])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@njit
|
|
28
|
+
def _rle_decompress(compressed_arr, result):
|
|
29
|
+
out_idx = 0
|
|
30
|
+
for in_idx in range(compressed_arr.shape[1]):
|
|
31
|
+
length = compressed_arr[0, in_idx]
|
|
32
|
+
val = compressed_arr[1, in_idx]
|
|
33
|
+
for i in range(length):
|
|
34
|
+
result[out_idx] = val
|
|
35
|
+
out_idx += 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def rle_decompress(compressed_arr):
|
|
39
|
+
result = np.empty(np.sum(compressed_arr[0, :]))
|
|
40
|
+
_rle_decompress(compressed_arr, result)
|
|
41
|
+
return result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
from astropy.constants import c as c_light
|
|
3
|
+
from astropy.constants import h, k_B
|
|
4
|
+
|
|
5
|
+
C_LIGHT_KM_S = c_light.value / 1e3
|
|
6
|
+
H_OVER_K_B = h.value / k_B.value
|
|
7
|
+
|
|
8
|
+
T_CMB_K = 2.72548 # Fixsen 2009 http://arxiv.org/abs/0911.1955
|
|
9
|
+
|
|
10
|
+
SOLAR_VELOCITY_KM_S = 369.8160
|
|
11
|
+
SOLAR_VELOCITY_GAL_LAT_RAD = 0.842_173_724
|
|
12
|
+
SOLAR_VELOCITY_GAL_LON_RAD = 4.608_035_744_4
|
|
13
|
+
|
|
14
|
+
EARTH_L2_DISTANCE_KM = 1_496_509.30522
|