Skip to content

Commit 91d96e9

Browse files
authored
Disambiguate between “polarization angle” and “orientation” (#305)
* Substitute “polarization angle” with “orientation” where appropriate * Fix warnings produced by Sphinx * Add a warning in get_pointings if a HWP is being used. * Implement a new API for the HWP * [skip ci] Update CHANGELOG
1 parent 47c2e53 commit 91d96e9

File tree

12 files changed

+191
-114
lines changed

12 files changed

+191
-114
lines changed

CHANGELOG.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# HEAD
22

3-
- Add data splits in time and detector space to binned maps [#291](https://github.com/litebird/litebird_sim/pull/291)
3+
- **Breaking change**: Disambiguate between “polarization angle” and “orientation” [#305](https://github.com/litebird/litebird_sim/pull/305). A few functions have been renamed as a consequence of this change; however, they are low-level functions that are used internally (`compute_pointing_and_polangle`, `all_compute_pointing_and_polangle`, `polarization_angle`), so external codes should be unaffected by this PR.
4+
5+
- **Breaking change**: Reworking of the IO, `write_observations` and `read_observations` are now part of the class simulation [#293](https://github.com/litebird/litebird_sim/pull/293)
46

57
- Mbs optionally returns alms instead of maps [#306](https://github.com/litebird/litebird_sim/pull/306)
68

79
- Include the possibility to pass components to fill_tods, add_dipole and add_noise [#302](https://github.com/litebird/litebird_sim/issues/302)
810

9-
- Fixing bug in mbs to pass general bandpass to mbs [#271](https://github.com/litebird/litebird_sim/pull/271)
11+
- Add data splits in time and detector space to binned maps [#291](https://github.com/litebird/litebird_sim/pull/291)
1012

11-
- **Breaking change**: Reworking of the IO, `write_observations` and `read_observations` are now part of the class simulation [#293](https://github.com/litebird/litebird_sim/pull/293)
13+
- Add support for partial multithreading using Numba [#276](https://github.com/litebird/litebird_sim/pull/276)
14+
15+
- Fixing bug in mbs to pass general bandpass to mbs [#271](https://github.com/litebird/litebird_sim/pull/271)
1216

1317
- Support for numpy.float128 made optional, this fixes importing issue on ARM architectures [#286](https://github.com/litebird/litebird_sim/pull/286)
1418

@@ -20,8 +24,6 @@
2024

2125
- New module to simulate HWP systematics [#232](https://github.com/litebird/litebird_sim/pull/232)
2226

23-
- Add support for partial multithreading using Numba [#276](https://github.com/litebird/litebird_sim/pull/276)
24-
2527
# Version 0.11.0
2628

2729
- **Breaking change**: Change the interface to the binner, implement a new destriper, and make the dependency on TOAST optional [#260](https://github.com/litebird/litebird_sim/pull/260)

benchmarks/pointing_generation.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
# Compute the pointings by running a "slerp" operation
6363
start = time.perf_counter_ns()
64-
pointings_and_polangle = lbs.get_pointings(
64+
pointings_and_orientation = lbs.get_pointings(
6565
obs,
6666
spin2ecliptic_quats=sim.spin2ecliptic_quats,
6767
detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]),
@@ -71,7 +71,9 @@
7171
elapsed_time = (stop - start) * 1.0e-9
7272

7373
print("Elapsed time for get_pointings: {} s".format((stop - start) * 1e-9))
74-
print("Shape of the pointings: ", pointings_and_polangle.shape)
74+
print("Shape of the pointings: ", pointings_and_orientation.shape)
7575
print(
76-
"Speed: {:.1e} pointings/s".format(pointings_and_polangle.shape[1] / elapsed_time),
76+
"Speed: {:.1e} pointings/s".format(
77+
pointings_and_orientation.shape[1] / elapsed_time
78+
),
7779
)
File renamed without changes.

docs/source/scanning.rst

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ for now just keep in mind the overall shape of the code:
192192
2. When the simulation code needs to determine where a detector is
193193
pointing to (the detector ``det`` in our example), the quaternions
194194
are used to retrieve (1) the coordinates on the Sky sphere, and (2)
195-
the polarization angle. Both quantities are computed in the
195+
the orientation angle (ψ). Both quantities are computed in the
196196
Ecliptic reference frame using the sampling rate of the detector,
197197
which in our example is 10 Hz (i.e., ten samples per second). In
198198
the example above, this is done by the function
@@ -201,7 +201,7 @@ for now just keep in mind the overall shape of the code:
201201
3. The function :func:`.get_pointings` returns a ``(N, 3)``
202202
matrix, where the first column contains the colatitude
203203
:math:`\theta`, the second column the longitude :math:`\phi`, and
204-
the third column the polarization angle :math:`\psi`, all expressed
204+
the third column the orientation angle :math:`\psi`, all expressed
205205
in radians.
206206

207207

@@ -215,7 +215,9 @@ number of transformations that need to be carried out:
215215

216216
We start from the detector's reference frame, which assumes that the
217217
main beam of the radiation pattern is aligned with the `z` axis, and
218-
that the detector is sensitive to the polarization along the `x` axis.
218+
that the beam of the detector is oriented using the `x` axis as the
219+
reference axis. (In other words, the `x` axis provides a reference frame
220+
for asymmetric beams.)
219221

220222
The next reference frame is the *boresight*, and to convert from the
221223
detector's reference frame to the boresight there is a rotation, which
@@ -349,30 +351,30 @@ split in several blocks inside the :class:`.Observation` class.
349351
unlikely to be relevant.
350352

351353
Once all the quaternions have been computed at the proper sampling
352-
rate, the direction of the detector on the sky and its polarization
354+
rate, the direction of the detector on the sky and its orientation
353355
angle are computed as follows:
354356

355357
- The direction is the vector :math:`\vec d = R \hat e_z`, where
356358
:math:`R` is the overall rotation from the detector's reference
357359
frame to the Ecliptic reference frame, and :math:`\hat e_z = (0, 0,
358360
1)` is the one-length vector aligned with the `z` axis.
359361

360-
- The polarization angle is given by the angle between the North
362+
- The orientation angle is given by the angle between the North
361363
direction passing through the vector :math:`\vec d` (i.e., along the
362364
meridian of :math:`\vec d`) and the vector :math:`\vec p = R \hat
363365
e_x`, where :math:`R` is the same as above and :math:`\hat e_x = (1,
364366
0, 0)`, as shown in the following figure (note that :math:`\hat e_x`
365367
has been drawn twice because the one in the upper side represents
366-
the polarization direction in the detector's reference frame):
368+
the orientation direction in the detector's reference frame):
367369

368-
.. image:: images/polarization-direction.svg
370+
.. image:: images/orientation-direction.svg
369371

370372
The purpose of the method :meth:`.Simulation.compute_pointings`, used
371373
in the example at the beginning of this chapter, is to call
372374
:func:`.get_det2ecl_quaternions` to compute the quaternions at the
373375
same sampling frequency as the scientific datastream, and then to
374376
apply the two definitions above to compute the direction and the
375-
polarization angle.
377+
orientation angle.
376378

377379

378380
How the boresight is specified
@@ -437,7 +439,7 @@ of the :math:`n_d` detectors kept in the field ``pointings`` of the
437439
:class:`.Observation`; they are laid out in memory as a :math:`(n_d,
438440
N, 2)` matrix, where :math:`N` is the number of samples in the
439441
timeline, and the last dimension holds the colatitude and longitude
440-
(in radians). The polarization angle is kept in ``Observation.psi``.
442+
(in radians). The orientation angle is kept in ``Observation.psi``.
441443
Let's visualize the position of these pointings on a Healpix map::
442444

443445
import healpy, numpy as np
@@ -719,7 +721,7 @@ Here is the result: we're indeed scanning the Ecliptic plane!
719721
Half Wave Plate
720722
---------------
721723

722-
The rotation of the polarization angle, induced by a HWP, can be
724+
The rotation of the polarization angle induced by a HWP can be
723725
included in the returned pointing information by passing an instance
724726
of a descendant of the class :class:`.HWP` to the method
725727
:meth:`.Simulation.set_hwp`. Here is an example::
@@ -796,7 +798,7 @@ provides the functions
796798
containing the ``N`` quaternions that transform from the Ecliptic
797799
reference frame to the detector's. Thus, this method can be used to
798800
estimate how far from the main beam axis a celestial object is, and
799-
its orientation with the polarization axis.
801+
its orientation with respect to the orientation of the detector.
800802

801803
Here we show a simple example; the first part is identical to the
802804
examples shown above (using the same scanning strategy as CORE's), but

docs/source/singularity_shell.cast

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
[37.248091, "o", "\r\n"]
203203
[37.248202, "o", "test/test_quaternions.py::test_collective_quick_rotations "]
204204
[37.426887, "o", "\u001b[32mPASSED\u001b[0m\u001b[32m [ 66%]\u001b[0m"]
205-
[37.42769, "o", "\r\ntest/test_scanning.py::test_compute_pointing_and_polangle "]
205+
[37.42769, "o", "\r\ntest/test_scanning.py::test_compute_pointing_and_orientation "]
206206
[37.610971, "o", "\u001b[32mPASSED\u001b[0m\u001b[32m [ 68%]\u001b[0m"]
207207
[37.611738, "o", "\r\ntest/test_scanning.py::test_spin_to_ecliptic "]
208208
[37.612417, "o", "\u001b[32mPASSED\u001b[0m\u001b[32m [ 70%]\u001b[0m"]
@@ -211,7 +211,7 @@
211211
[37.61575, "o", "\u001b[32m [ 72%]\u001b[0m"]
212212
[37.616256, "o", "\r\ntest/test_scanning.py::test_simulation_pointings_still "]
213213
[38.069883, "o", "\u001b[32mPASSED\u001b[0m\u001b[32m [ 75%]\u001b[0m"]
214-
[38.07046, "o", "\r\ntest/test_scanning.py::test_simulation_pointings_polangle "]
214+
[38.07046, "o", "\r\ntest/test_scanning.py::test_simulation_pointings_orientation "]
215215
[38.810859, "o", "\u001b[32mPASSED\u001b[0m"]
216216
[38.810898, "o", "\u001b[32m [ 77%]\u001b[0m"]
217217
[38.811539, "o", "\r\ntest/test_scanning.py::test_simulation_pointings_spinning "]

litebird_sim/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
all_rotate_z_vectors,
6565
)
6666
from .scanning import (
67-
compute_pointing_and_polangle,
68-
all_compute_pointing_and_polangle,
67+
compute_pointing_and_orientation,
68+
all_compute_pointing_and_orientation,
6969
spin_to_ecliptic,
7070
all_spin_to_ecliptic,
7171
calculate_sun_earth_angles_rad,
@@ -216,8 +216,8 @@ def destripe_with_toast2(*args, **kwargs):
216216
"all_rotate_y_vectors",
217217
"all_rotate_z_vectors",
218218
# scanning.py
219-
"compute_pointing_and_polangle",
220-
"all_compute_pointing_and_polangle",
219+
"compute_pointing_and_orientation",
220+
"all_compute_pointing_and_orientation",
221221
"spin_to_ecliptic",
222222
"all_spin_to_ecliptic",
223223
"calculate_sun_earth_angles_rad",

litebird_sim/hwp.py

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,45 @@ class HWP:
1616
classes like :class:`.IdealHWP`.
1717
"""
1818

19-
def add_hwp_angle(self, pointing_buffer, start_time_s: float, delta_time_s: float):
19+
def get_hwp_angle(
20+
self, output_buffer, start_time_s: float, delta_time_s: float
21+
) -> None:
22+
"""
23+
Calculate the rotation angle of the HWP
24+
25+
This method must be redefined in derived classes. The parameter `start_time_s`
26+
specifies the time of the first sample in `pointings` and must be a floating-point
27+
value; this means that you should already have converted any AstroPy time to a plain
28+
scalar before calling this method. The parameter `delta_time_s` is the inverse
29+
of the sampling frequency and must be expressed in seconds.
30+
31+
The result will be saved in `output_buffer`, which must have already been allocated
32+
with the appropriate number of samples.
33+
"""
34+
raise NotImplementedError(
35+
"You should not use the HWP class in your code, use IdealHWP instead"
36+
)
37+
38+
def add_hwp_angle(
39+
self, pointing_buffer, start_time_s: float, delta_time_s: float
40+
) -> None:
2041
"""
2142
Modify pointings so that they include the effect of the HWP
2243
2344
This method must be redefined in derived classes. The parameter
2445
``pointing_buffer`` must be a D×N×3 matrix representing the three angles
25-
``(colatitude, longitude, polangle)`` for D detectors and N measurements.
26-
The function only alters ``polangle`` and returns nothing.
46+
``(colatitude, longitude, orientation)`` for D detectors and N measurements.
47+
The function only alters ``orientation`` and returns nothing.
2748
2849
The parameters `start_time_s` and `delta_time_s` specify the time of the
2950
first sample in `pointings` and must be floating-point values; this means
3051
that you should already have converted any AstroPy time to a plain scalar
3152
before calling this method.
53+
54+
*Warning*: this changes the interpretation of the ψ variable in the pointings!
55+
You should better use :meth:`.get_hwp_angle` and keep ψ and the α angle
56+
of the HWP separate. This method is going to be deprecated in a future
57+
release of the LiteBIRD Simulation Framework.
3258
"""
3359
raise NotImplementedError(
3460
"You should not use the HWP class in your code, use IdealHWP instead"
@@ -41,18 +67,33 @@ def __str__(self):
4167

4268

4369
@njit
70+
def _get_ideal_hwp_angle(
71+
output_buffer, start_time_s, delta_time_s, start_angle_rad, ang_speed_radpsec
72+
):
73+
for sample_idx in range(output_buffer.size):
74+
angle = (
75+
start_angle_rad
76+
+ (start_time_s + delta_time_s * sample_idx) * 2 * ang_speed_radpsec
77+
) % (2 * np.pi)
78+
79+
output_buffer[sample_idx] = angle
80+
81+
4482
def _add_ideal_hwp_angle(
4583
pointing_buffer, start_time_s, delta_time_s, start_angle_rad, ang_speed_radpsec
4684
):
4785
detectors, samples, _ = pointing_buffer.shape
48-
for det_idx in range(detectors):
49-
for sample_idx in range(samples):
50-
angle = (
51-
start_angle_rad
52-
+ (start_time_s + delta_time_s * sample_idx) * 2 * ang_speed_radpsec
53-
) % (2 * np.pi)
86+
hwp_angles = np.empty(samples, dtype=pointing_buffer.dtype)
87+
_get_ideal_hwp_angle(
88+
output_buffer=hwp_angles,
89+
start_time_s=start_time_s,
90+
delta_time_s=delta_time_s,
91+
start_angle_rad=start_angle_rad,
92+
ang_speed_radpsec=ang_speed_radpsec,
93+
)
5494

55-
pointing_buffer[det_idx, sample_idx, 2] += angle
95+
for det_idx in range(detectors):
96+
pointing_buffer[det_idx, :, 2] += hwp_angles
5697

5798

5899
class IdealHWP(HWP):
@@ -75,7 +116,20 @@ def __init__(self, ang_speed_radpsec: float, start_angle_rad=0.0):
75116
self.ang_speed_radpsec = ang_speed_radpsec
76117
self.start_angle_rad = start_angle_rad
77118

78-
def add_hwp_angle(self, pointing_buffer, start_time_s: float, delta_time_s: float):
119+
def get_hwp_angle(
120+
self, output_buffer, start_time_s: float, delta_time_s: float
121+
) -> None:
122+
_get_ideal_hwp_angle(
123+
output_buffer=output_buffer,
124+
start_time_s=start_time_s,
125+
delta_time_s=delta_time_s,
126+
start_angle_rad=self.start_angle_rad,
127+
ang_speed_radpsec=self.ang_speed_radpsec,
128+
)
129+
130+
def add_hwp_angle(
131+
self, pointing_buffer, start_time_s: float, delta_time_s: float
132+
) -> None:
79133
_add_ideal_hwp_angle(
80134
pointing_buffer=pointing_buffer,
81135
start_time_s=start_time_s,

litebird_sim/hwp_sys/hwp_sys.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,19 +1128,24 @@ def fill_tod(
11281128
):
11291129
r"""It fills tod and/or :math:`A^T A` and :math:`A^T d` for the
11301130
"on the fly" map production
1131+
11311132
Args:
1132-
obs class:`Observations`: container for tod. If the tod is
1133-
not required, obs.tod can be not allocated
1134-
i.e. in lbs.Observation allocate_tod=False.
1135-
pointings (float): pointing for each sample and detector
1136-
generated by func:lbs.get_pointings
1137-
Optional if already allocated in obs.
1133+
1134+
obs (:class:`Observation`): container for tod. If the tod is
1135+
not required, you can avoid allocating ``obs.tod``
1136+
i.e. in ``lbs.Observation`` use ``allocate_tod=False``.
1137+
1138+
pointings (float): pointing for each sample and detector
1139+
generated by :func:`lbs.get_pointings`
1140+
Optional if already allocated in ``obs``.
11381141
When generating pointing information, set the variable
1139-
`hwp` to None since the hwp rotation angle is added to
1140-
the polarization angle within the `fill_tod` function.
1141-
hwp_radpsec (float): hwp rotation speed in radiants per second
1142-
save_tod (bool): if True, the `tod` is saved in `obs.tod`, if False,
1143-
`tod` gets deleted
1142+
``hwp`` to None since the hwp rotation angle is added to
1143+
the orientation angle within the ``fill_tod`` function.
1144+
1145+
hwp_radpsec (float): hwp rotation speed in radiants per second
1146+
1147+
save_tod (bool): if True, ``tod`` is saved in ``obs.tod``; if False,
1148+
``tod`` gets deleted
11441149
"""
11451150

11461151
if pointings is None:

litebird_sim/pointings.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from .scanning import (
1616
get_det2ecl_quaternions,
17-
all_compute_pointing_and_polangle,
17+
all_compute_pointing_and_orientation,
1818
)
1919

2020
from .coordinates import CoordinateSystem
@@ -25,7 +25,7 @@ def apply_hwp_to_obs(obs, hwp: HWP, pointing_matrix):
2525
2626
This function modifies the variable `pointing_matrix` (a D×N×3 matrix,
2727
with D the number of detectors and N the number of samples) so that the
28-
polarization angle considers the behavior of the half-wave plate in
28+
orientation angle considers the behavior of the half-wave plate in
2929
`hwp`.
3030
"""
3131

@@ -99,6 +99,11 @@ def get_pointings(
9999
If `HWP` is not ``None``, this specifies the HWP to use for the
100100
computation of proper polarization angles.
101101
102+
**Warning**: if `hwp` is not ``None``, the code adds the α angle of the
103+
HWP to the orientation angle ψ, which is generally not correct! This
104+
is going to be fixed in the next release of the LiteBIRD Simulation
105+
Framework.
106+
102107
The return value is a ``(D x N × 3)`` tensor: the colatitude (in
103108
radians) is stored in column 0 (e.g., ``result[:, :, 0]``), the
104109
longitude (ditto) in column 1, and the polarization angle
@@ -152,7 +157,7 @@ def get_pointings(
152157
)
153158

154159
# Compute the pointing direction for each sample
155-
all_compute_pointing_and_polangle(
160+
all_compute_pointing_and_orientation(
156161
result_matrix=pointing_buffer[idx, :, :].reshape(
157162
(1, pointing_buffer.shape[1], 3)
158163
),

0 commit comments

Comments
 (0)