Skip to content

Commit 2b68ca8

Browse files
committed
HWP simulation function now uses units. Add extra helper function to run a simple satellite sim for use in unit tests. Restore unit tests for healpix pointing.
1 parent 5d513c1 commit 2b68ca8

File tree

8 files changed

+118
-125
lines changed

8 files changed

+118
-125
lines changed

src/toast/future_ops/sim_hwp.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@function_timer
1313
def simulate_hwp_response(
1414
ob,
15+
ob_time_key=None,
1516
ob_angle_key=None,
1617
ob_mueller_key=None,
1718
hwp_start=None,
@@ -64,9 +65,9 @@ def simulate_hwp_response(
6465
# convert to radians / second
6566
hwp_rate = hwp_rpm * 2.0 * np.pi / 60.0
6667

67-
if hwp_step_deg is not None:
68+
if hwp_step is not None:
6869
# convert to radians and seconds
69-
hwp_step_rad = hwp_set.to_value(u.radian)
70+
hwp_step_rad = hwp_step.to_value(u.radian)
7071
hwp_step_time_s = hwp_step_time.to_value(u.second)
7172

7273
# Only the first process in each grid column simulates the common HWP angle
@@ -78,7 +79,7 @@ def simulate_hwp_response(
7879
hwp_angle = None
7980
hwp_mueller = None
8081

81-
if ob.grid_comm_col is None or ob.grid_comm_col.rank == 0:
82+
if ob.comm_col is None or ob.comm_col.rank == 0:
8283
if hwp_rate is not None:
8384
# continuous HWP
8485
# HWP increment per sample is:
@@ -111,7 +112,7 @@ def simulate_hwp_response(
111112
# Store the angle and / or the Mueller matrix
112113
if ob_angle_key is not None:
113114
ob.shared.create(
114-
ob_angle_key, shape=(n_sample,), dtype=np.float64, comm=ob.grid_comm_col
115+
ob_angle_key, shape=(n_sample,), dtype=np.float64, comm=ob.comm_col
115116
)
116117
ob.shared[ob_angle_key].set(hwp_angle, offset=(0,), fromrank=0)
117118

src/toast/future_ops/sim_satellite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ def _exec(self, data, detectors=None, **kwargs):
510510

511511
simulate_hwp_response(
512512
ob,
513+
ob_time_key=self.times,
513514
ob_angle_key=self.hwp_angle,
514515
ob_mueller_key=None,
515516
hwp_start=obsrange[obindx].start * u.second,

src/toast/mpi.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import sys
4242
import itertools
43+
from contextlib import contextmanager
4344

4445
import numpy as np
4546

@@ -234,8 +235,7 @@ def comm_rank(self):
234235

235236
@property
236237
def cuda(self):
237-
"""The CUDA device properties for this process.
238-
"""
238+
"""The CUDA device properties for this process."""
239239
return self._cuda
240240

241241
def __repr__(self):
@@ -253,3 +253,34 @@ def __repr__(self):
253253
else:
254254
lines.append(" Using CUDA device {}".format(self._cuda.device_index))
255255
return "<toast.Comm\n{}\n>".format("\n".join(lines))
256+
257+
258+
@contextmanager
259+
def exception_guard(comm=None):
260+
"""Ensure that if one MPI process raises an un-caught exception, all of them do.
261+
262+
Args:
263+
comm (mpi4py.MPI.Comm): The MPI communicator or None.
264+
265+
"""
266+
log = Logger.get()
267+
failed = 0
268+
try:
269+
yield
270+
except:
271+
msg = "Exception on process {}:\n".format(comm.rank)
272+
exc_type, exc_value, exc_traceback = sys.exc_info()
273+
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
274+
msg += "\n".join(lines)
275+
log.error(msg)
276+
failed = 1
277+
278+
failcount = None
279+
if comm is None:
280+
failcount = failed
281+
else:
282+
failcount = comm.allreduce(failed, op=MPI.SUM)
283+
if failcount > 0:
284+
raise RuntimeError("One or more MPI processes raised an exception")
285+
286+
return

src/toast/tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ install(FILES
2121
ops_applygain.py
2222
ops_sim_tod_noise.py
2323
covariance.py
24-
ops_pmat.py
24+
ops_pointing_healpix.py
2525
ops_dipole.py
2626
ops_groundfilter.py
2727
sim_focalplane.py

src/toast/tests/_helpers.py

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import numpy as np
88

9-
# from contextlib import contextmanager
9+
from astropy import units as u
1010

1111
from ..mpi import Comm
1212

@@ -20,8 +20,10 @@
2020

2121
from ..observation import DetectorData, Observation
2222

23+
from .. import future_ops as ops
2324

24-
ZAXIS = np.array([0, 0, 1.0])
25+
26+
ZAXIS = np.array([0.0, 0.0, 1.0])
2527

2628

2729
# These are helper routines for common operations used in the unit tests.
@@ -78,7 +80,7 @@ def create_comm(mpicomm):
7880
return toastcomm
7981

8082

81-
def create_telescope(group_size):
83+
def create_telescope(group_size, sample_rate=10.0 * u.Hz):
8284
"""Create a fake telescope with at least one detector per process."""
8385
npix = 1
8486
ring = 1
@@ -90,10 +92,10 @@ def create_telescope(group_size):
9092

9193

9294
def create_distdata(mpicomm, obs_per_group=1, samples=10):
93-
"""Create a toast communicator and distributed data object.
95+
"""Create a toast communicator and (empty) distributed data object.
9496
9597
Use the specified MPI communicator to attempt to create 2 process groups,
96-
each with some observations.
98+
each with some empty observations.
9799
98100
Args:
99101
mpicomm (MPI.Comm): the MPI communicator (or None).
@@ -119,6 +121,42 @@ def create_distdata(mpicomm, obs_per_group=1, samples=10):
119121
return data
120122

121123

124+
def create_satellite_data(
125+
mpicomm, obs_per_group=1, sample_rate=10.0 * u.Hz, obs_time=5.0 * u.minute
126+
):
127+
"""Create a data object with a simple satellite sim.
128+
129+
Use the specified MPI communicator to attempt to create 2 process groups. Create
130+
a fake telescope and run the satellite sim to make some observations for each
131+
group. This is useful for testing many operators that need some pre-existing
132+
observations with boresight pointing.
133+
134+
Args:
135+
mpicomm (MPI.Comm): the MPI communicator (or None).
136+
obs_per_group (int): the number of observations assigned to each group.
137+
samples (int): number of samples per observation.
138+
139+
Returns:
140+
toast.Data: the distributed data with named observations.
141+
142+
"""
143+
toastcomm = create_comm(mpicomm)
144+
data = Data(toastcomm)
145+
146+
tele = create_telescope(toastcomm.group_size, sample_rate=sample_rate)
147+
148+
sim_sat = ops.SimSatellite(
149+
name="sim_sat",
150+
n_observation=(toastcomm.ngroups * obs_per_group),
151+
telescope=tele,
152+
hwp_rpm=10.0,
153+
observation_time=obs_time,
154+
)
155+
sim_sat.apply(data)
156+
157+
return data
158+
159+
122160
def uniform_chunks(samples, nchunk=100):
123161
"""Divide some number of samples into chunks.
124162
@@ -141,37 +179,3 @@ def uniform_chunks(samples, nchunk=100):
141179
for r in range(remain):
142180
chunks[r] += 1
143181
return chunks
144-
145-
146-
#
147-
# @contextmanager
148-
# def mpi_guard(comm=MPI.COMM_WORLD):
149-
# """Ensure that if one MPI process raises an exception, all of them do.
150-
#
151-
# Args:
152-
# comm (mpi4py.MPI.Comm): The MPI communicator.
153-
#
154-
# """
155-
# failed = 0
156-
# print(comm.rank, ": guard: enter", flush=True)
157-
# try:
158-
# print(comm.rank, ": guard: yield", flush=True)
159-
# yield
160-
# except:
161-
# print(comm.rank, ": guard: except", flush=True)
162-
# msg = "Exception on process {}:\n".format(comm.rank)
163-
# exc_type, exc_value, exc_traceback = sys.exc_info()
164-
# lines = traceback.format_exception(exc_type, exc_value,
165-
# exc_traceback)
166-
# msg += "\n".join(lines)
167-
# print(msg, flush=True)
168-
# failed = 1
169-
# print(comm.rank, ": guard: except done", flush=True)
170-
#
171-
# print(comm.rank, ": guard: failcount reduce", flush=True)
172-
# failcount = comm.allreduce(failed, op=MPI.SUM)
173-
# if failcount > 0:
174-
# raise RuntimeError("One or more MPI processes raised an exception")
175-
# print(comm.rank, ": guard: done", flush=True)
176-
#
177-
# return

src/toast/tests/ops_memory_counter.py

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,30 @@
1111

1212
from .. import future_ops as ops
1313

14-
from .. import config as tc
15-
16-
from ._helpers import create_outdir, create_distdata, create_telescope
14+
from ._helpers import create_outdir, create_satellite_data
1715

1816

1917
class MemoryCounterTest(MPITestCase):
2018
def setUp(self):
2119
fixture_name = os.path.splitext(os.path.basename(__file__))[0]
2220
self.outdir = create_outdir(self.comm, fixture_name)
2321

24-
# One observation per group
25-
self.data = create_distdata(self.comm, obs_per_group=1)
26-
27-
# Make a fake telescope for every observation
28-
29-
tele = create_telescope(self.data.comm.group_size)
22+
def test_counter(self):
23+
# Create a fake satellite data set for testing
24+
data = create_satellite_data(self.comm)
3025

3126
# Set up a pipeline that generates some data
32-
3327
pipe_ops = [
34-
ops.SimSatellite(
35-
name="sim_satellite",
36-
telescope=tele,
37-
n_observation=self.data.comm.ngroups,
38-
),
39-
ops.DefaultNoiseModel(name="noise_model"),
40-
ops.SimNoise(name="sim_noise"),
28+
ops.DefaultNoiseModel(),
29+
ops.SimNoise(),
4130
]
4231

43-
self.pipe = ops.Pipeline(name="sim_pipe")
44-
self.pipe.operators = pipe_ops
45-
46-
def test_counter(self):
47-
# Start with empty data
48-
self.data.clear()
49-
50-
# Run a standard pipeline to simulate some data
51-
self.pipe.apply(self.data)
32+
pipe = ops.Pipeline()
33+
pipe.operators = pipe_ops
34+
pipe.apply(data)
5235

5336
# Get the memory used
5437
mcount = ops.MemoryCounter()
55-
bytes = mcount.apply(self.data)
38+
bytes = mcount.apply(data)
5639

5740
return

src/toast/tests/ops_pmat.py renamed to src/toast/tests/ops_pointing_healpix.py

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,21 @@
1010
import numpy as np
1111

1212
from .._libtoast import pointing_matrix_healpix
13-
from ..healpix import HealpixPixels
14-
from ..todmap import TODHpixSpiral, OpPointingHpix
13+
1514
from .. import qarray as qa
1615

17-
from ._helpers import create_outdir, create_distdata, boresight_focalplane
16+
from ..healpix import HealpixPixels
17+
18+
from .. import future_ops as ops
19+
20+
from ._helpers import create_outdir, create_satellite_data
1821

1922

20-
class OpPointingHpixTest(MPITestCase):
23+
class PointingHealpixTest(MPITestCase):
2124
def setUp(self):
2225
fixture_name = os.path.splitext(os.path.basename(__file__))[0]
2326
self.outdir = create_outdir(self.comm, fixture_name)
2427

25-
# Create one observation per group, and each observation will have
26-
# one detector per process and a single chunk. Data within an
27-
# observation is distributed by detector.
28-
29-
self.data = create_distdata(self.comm, obs_per_group=1)
30-
self.ndet = self.data.comm.group_size
31-
32-
# Create detectors with default properties
33-
(
34-
dnames,
35-
dquat,
36-
depsilon,
37-
drate,
38-
dnet,
39-
dfmin,
40-
dfknee,
41-
dalpha,
42-
) = boresight_focalplane(self.ndet)
43-
44-
# A small number of samples
45-
self.totsamp = 10
46-
47-
# Populate the observations (one per group)
48-
49-
tod = TODHpixSpiral(
50-
self.data.comm.comm_group,
51-
dquat,
52-
self.totsamp,
53-
detranks=self.data.comm.group_size,
54-
)
55-
56-
self.data.obs[0]["tod"] = tod
57-
58-
def tearDown(self):
59-
del self.data
60-
6128
def test_pointing_matrix_healpix2(self):
6229
nside = 64
6330
npix = 12 * nside ** 2
@@ -255,31 +222,37 @@ def test_pointing_matrix_healpix_hwp(self):
255222
return
256223

257224
def test_hpix_simple(self):
225+
# Create a fake satellite data set for testing
226+
data = create_satellite_data(self.comm)
227+
228+
pointing = ops.PointingHealpix(nside=64, mode="IQU", hwp_angle="hwp_angle")
229+
pointing.apply(data)
230+
258231
rank = 0
259232
if self.comm is not None:
260233
rank = self.comm.rank
261-
op = OpPointingHpix()
262-
op.exec(self.data)
263234

264235
handle = None
265236
if rank == 0:
266237
handle = open(os.path.join(self.outdir, "out_test_hpix_simple_info"), "w")
267-
self.data.info(handle=handle)
238+
data.info(handle=handle)
268239
if rank == 0:
269240
handle.close()
270-
return
271241

272242
def test_hpix_hwpnull(self):
243+
# Create a fake satellite data set for testing
244+
data = create_satellite_data(self.comm)
245+
246+
pointing = ops.PointingHealpix(nside=64, mode="IQU")
247+
pointing.apply(data)
248+
273249
rank = 0
274250
if self.comm is not None:
275251
rank = self.comm.rank
276-
op = OpPointingHpix(mode="IQU")
277-
op.exec(self.data)
278252

279253
handle = None
280254
if rank == 0:
281-
handle = open(os.path.join(self.outdir, "out_test_hpix_hwpnull_info"), "w")
282-
self.data.info(handle=handle)
255+
handle = open(os.path.join(self.outdir, "out_test_hpix_hwpnull"), "w")
256+
data.info(handle=handle)
283257
if rank == 0:
284258
handle.close()
285-
return

0 commit comments

Comments
 (0)