def test_simulation_pointings_mjd(tmp_path): sim = lbs.Simulation( base_path=tmp_path / "simulation_dir", start_time=Time("2020-01-01T00:00:00"), duration_s=130.0, ) fakedet = create_fake_detector() sim.create_observations(detectors=[fakedet], num_of_obs_per_detector=2, split_list_over_processes=False) sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=10.0, precession_rate_hz=10.0, spin_rate_hz=0.1) sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=60.0) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=np.deg2rad(20.0)) for idx, obs in enumerate(sim.observations): pointings_and_polangle = lbs.get_pointings( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]), bore2spin_quat=instr.bore2spin_quat, ) filename = Path( __file__).parent / f"reference_obs_pointings{idx:03d}.npy" reference = np.load(filename, allow_pickle=False) assert np.allclose(pointings_and_polangle, reference)
def test_simulation_pointings_polangle(tmp_path): sim = lbs.Simulation(base_path=tmp_path / "simulation_dir", start_time=0.0, duration_s=61.0) fakedet = create_fake_detector(sampling_rate_hz=50.0) sim.create_observations(detectors=[fakedet], num_of_obs_per_detector=1, split_list_over_processes=False) assert len(sim.observations) == 1 obs = sim.observations[0] sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=0.0, precession_rate_hz=0.0, spin_rate_hz=1.0 / 60) sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=0.5) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=0.0) pointings_and_polangle = lbs.get_pointings( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]), bore2spin_quat=instr.bore2spin_quat, ) polangle = pointings_and_polangle[..., 2] # Check that the polarization angle scans every value between -π # and +π assert np.allclose(np.max(polangle), np.pi, atol=0.01) assert np.allclose(np.min(polangle), -np.pi, atol=0.01) # Simulate the generation of a report sim.flush()
def test_make_bin_map_api_simulation(tmp_path): # We should add a more meaningful observation: # Currently this test just shows the interface sim = lbs.Simulation(base_path=tmp_path / "tut04", start_time=0.0, duration_s=86400.0) sim.generate_spin2ecl_quaternions( scanning_strategy=lbs.SpinningScanningStrategy( spin_sun_angle_rad=np.radians(30), # CORE-specific parameter spin_rate_hz=0.5 / 60, # Ditto # We use astropy to convert the period (4 days) in # minutes, the unit expected for the precession period precession_rate_hz=1 / (4 * u.day).to("s").value, )) instr = lbs.InstrumentInfo(name="core", spin_boresight_angle_rad=np.radians(65)) det = lbs.DetectorInfo(name="foo", sampling_rate_hz=10) obss = sim.create_observations(detectors=[det]) pointings = lbs.get_pointings( obss[0], sim.spin2ecliptic_quats, detector_quats=[det.quat], bore2spin_quat=instr.bore2spin_quat, ) nside = 64 obss[0].pixind = hp.ang2pix(nside, pointings[..., 0], pointings[..., 1]) obss[0].psi = pointings[..., 2] mapping.make_bin_map(obss, nside)
def __write_complex_observation(tmp_path, use_mjd: bool): start_time = AstroTime("2021-01-01") if use_mjd else 0 time_span_s = 60 sampling_hz = 10 sim = lbs.Simulation( base_path=tmp_path, start_time=start_time, duration_s=time_span_s, ) scanning = lbs.SpinningScanningStrategy( spin_sun_angle_rad=0.785_398_163_397_448_3, precession_rate_hz=8.664_850_513_998_931e-05, spin_rate_hz=0.000_833_333_333_333_333_4, start_time=start_time, ) spin2ecliptic_quats = scanning.generate_spin2ecl_quaternions( start_time, time_span_s, delta_time_s=1.0) instr = lbs.InstrumentInfo( boresight_rotangle_rad=0.0, spin_boresight_angle_rad=0.872_664_625_997_164_8, spin_rotangle_rad=3.141_592_653_589_793, ) det = lbs.DetectorInfo( name="Dummy detector", sampling_rate_hz=sampling_hz, bandcenter_ghz=100.0, quat=[0.0, 0.0, 0.0, 1.0], ) sim.create_observations(detectors=[det]) obs = sim.observations[0] obs.tod = np.random.random(obs.tod.shape) obs.pointings = lbs.scanning.get_pointings( obs, spin2ecliptic_quats=spin2ecliptic_quats, detector_quats=[det.quat], bore2spin_quat=instr.bore2spin_quat, ) obs.local_flags = np.zeros(obs.tod.shape, dtype="uint16") obs.local_flags[0, 12:15] = 1 obs.global_flags = np.zeros(obs.tod.shape[1], dtype="uint32") obs.global_flags[12:15] = 15 return obs, det, lbs.write_observations(sim=sim, subdir_name="")
def test_simulation_two_detectors(): sim = lbs.Simulation(start_time=0.0, duration_s=86400.0) # Two detectors, the second rotated by 45° quaternions = [ np.array([0.0, 0.0, 0.0, 1.0]), np.array([0.0, 0.0, 1.0, 1.0]) / np.sqrt(2), ] fakedet1 = create_fake_detector(sampling_rate_hz=1 / 3600, quat=quaternions[0]) fakedet2 = create_fake_detector(sampling_rate_hz=1 / 3600, quat=quaternions[1]) sim.create_observations( detectors=[fakedet1, fakedet2], num_of_obs_per_detector=1, split_list_over_processes=False, ) assert len(sim.observations) == 1 obs = sim.observations[0] # The spacecraft stands still in L2, with no spinning nor precession sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=0.0, precession_rate_hz=0.0, spin_rate_hz=0.0) sim.generate_spin2ecl_quaternions(sstr, delta_time_s=60.0) assert sim.spin2ecliptic_quats.quats.shape == (24 * 60 + 1, 4) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=0.0) pointings_and_polangle = lbs.get_pointings( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=quaternions, bore2spin_quat=instr.bore2spin_quat, ) assert pointings_and_polangle.shape == (2, 24, 3) assert np.allclose(pointings_and_polangle[0, :, 0], pointings_and_polangle[1, :, 0]) assert np.allclose(pointings_and_polangle[0, :, 1], pointings_and_polangle[1, :, 1]) # The ψ angle should differ by 45° assert np.allclose( np.abs(pointings_and_polangle[0, :, 2] - pointings_and_polangle[1, :, 2]), np.pi / 2, )
def test_instrument_creation(): instr = lbs.InstrumentInfo( name="test", boresight_rotangle_rad=np.deg2rad(10.0), spin_boresight_angle_rad=np.deg2rad(15.0), spin_rotangle_rad=np.deg2rad(20.0), ) assert instr.name == "test" assert np.allclose(instr.boresight_rotangle_rad, np.deg2rad(10.0)) assert np.allclose(instr.spin_boresight_angle_rad, np.deg2rad(15.0)) assert np.allclose(instr.spin_rotangle_rad, np.deg2rad(20.0)) assert np.allclose( instr.bore2spin_quat, np.array([-0.01137611, 0.1300295, 0.25660481, 0.9576622]) )
def test_simulation_pointings_still(): sim = lbs.Simulation(start_time=0.0, duration_s=86400.0) fakedet = create_fake_detector(sampling_rate_hz=1 / 3600) sim.create_observations(detectors=[fakedet], num_of_obs_per_detector=1, split_list_over_processes=False) assert len(sim.observations) == 1 obs = sim.observations[0] # The spacecraft stands still in L2, with no spinning nor precession sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=0.0, precession_rate_hz=0.0, spin_rate_hz=0.0) sim.generate_spin2ecl_quaternions(sstr, delta_time_s=60.0) assert sim.spin2ecliptic_quats.quats.shape == (24 * 60 + 1, 4) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=0.0) # Move the Z vector manually using the last quaternion and check # that it's rotated by 1/365.25 of a complete circle boresight = np.empty(3) lbs.rotate_z_vector(boresight, *sim.spin2ecliptic_quats.quats[-1, :]) assert np.allclose(np.arctan2(boresight[1], boresight[0]), 2 * np.pi / 365.25) # Now redo the calculation using get_pointings pointings_and_polangle = lbs.get_pointings( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]), bore2spin_quat=instr.bore2spin_quat, ) colatitude = pointings_and_polangle[..., 0] longitude = pointings_and_polangle[..., 1] polangle = pointings_and_polangle[..., 2] assert np.allclose(colatitude, np.pi / 2), colatitude assert np.allclose(np.abs(polangle), np.pi / 2), polangle # The longitude should have changed by a fraction 23 hours / # 365.25 days of a complete circle (we have 24 samples, from t = 0 # to t = 23 hr) assert np.allclose(np.abs(longitude[..., -1] - longitude[..., 0]), 2 * np.pi * 23 / 365.25 / 24)
def test_scanning_quaternions(tmp_path): sim = lbs.Simulation(base_path=tmp_path / "simulation_dir", start_time=0.0, duration_s=61.0) fakedet = create_fake_detector(sampling_rate_hz=50.0) sim.create_observations(detectors=[fakedet], num_of_obs_per_detector=1, split_list_over_processes=False) assert len(sim.observations) == 1 obs = sim.observations[0] sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=0.0, precession_rate_hz=0.0, spin_rate_hz=1.0) sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=0.5) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=np.deg2rad(15.0)) detector_quat = np.array([[0.0, 0.0, 0.0, 1.0]]) det2ecl_quats = lbs.get_det2ecl_quaternions( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=detector_quat, bore2spin_quat=instr.bore2spin_quat, ) ecl2det_quats = lbs.get_ecl2det_quaternions( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=detector_quat, bore2spin_quat=instr.bore2spin_quat, ) identity = np.array([0, 0, 0, 1]) det2ecl_quats = det2ecl_quats.reshape(-1, 4) ecl2det_quats = ecl2det_quats.reshape(-1, 4) for i in range(det2ecl_quats.shape[0]): # Check that the two quaternions (ecl2det and det2ecl) are # actually one the inverse of the other quat = np.copy(det2ecl_quats[i, :]) lbs.quat_right_multiply(quat, *ecl2det_quats[i, :]) assert np.allclose(quat, identity)
def test_simulation_pointings_spinning(tmp_path): sim = lbs.Simulation(base_path=tmp_path / "simulation_dir", start_time=0.0, duration_s=61.0) fakedet = create_fake_detector(sampling_rate_hz=50.0) sim.create_observations(detectors=[fakedet], num_of_obs_per_detector=1, split_list_over_processes=False) assert len(sim.observations) == 1 obs = sim.observations[0] sstr = lbs.SpinningScanningStrategy(spin_sun_angle_rad=0.0, precession_rate_hz=0.0, spin_rate_hz=1.0) sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=0.5) instr = lbs.InstrumentInfo(spin_boresight_angle_rad=np.deg2rad(15.0)) pointings_and_polangle = lbs.get_pointings( obs, spin2ecliptic_quats=sim.spin2ecliptic_quats, detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]), bore2spin_quat=instr.bore2spin_quat, ) colatitude = pointings_and_polangle[..., 0] reference_spin2ecliptic_file = Path( __file__).parent / "reference_spin2ecl.txt.gz" reference = np.loadtxt(reference_spin2ecliptic_file) assert np.allclose(sim.spin2ecliptic_quats.quats, reference) reference_pointings_file = Path( __file__).parent / "reference_pointings.txt.gz" reference = np.loadtxt(reference_pointings_file) assert np.allclose(pointings_and_polangle[0, :, :], reference) # Check that the colatitude does not depart more than ±15° from # the Ecliptic assert np.allclose(np.rad2deg(np.max(colatitude)), 90 + 15, atol=0.01) assert np.allclose(np.rad2deg(np.min(colatitude)), 90 - 15, atol=0.01) sim.flush()
import litebird_sim as lbs sim = lbs.Simulation(base_path="/tmp/destriper_output", start_time=0, duration_s=86400.0) sim.generate_spin2ecl_quaternions( scanning_strategy=lbs.SpinningScanningStrategy( spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter spin_rate_hz=0.5 / 60, # Ditto # We use astropy to convert the period (4 days) in # seconds precession_rate_hz=1.0 / (4 * u.day).to("s").value, )) instr = lbs.InstrumentInfo(name="core", spin_boresight_angle_rad=np.deg2rad(65)) # We create two detectors, whose polarization angles are separated by π/2 sim.create_observations( detectors=[ lbs.DetectorInfo(name="0A", sampling_rate_hz=10), lbs.DetectorInfo(name="0B", sampling_rate_hz=10, quat=lbs.quat_rotation_z(np.pi / 2)), ], dtype_tod=np.float64, n_blocks_time=lbs.MPI_COMM_WORLD.size, split_list_over_processes=False, ) # Generate some white noise
def test_solar_dipole_fit(tmpdir): tmpdir = Path(tmpdir) test = unittest.TestCase() # The purpose of this test is to simulate the motion of the spacecraft # for one year (see `time_span_s`) and produce *two* timelines: the first # is associated with variables `*_s_o` and refers to the case of a nonzero # velocity of the Solar System, and the second is associated with variables # `*_o` and assumes that the reference frame of the Solar System is the # same as the CMB's (so that there is no dipole). start_time = Time("2022-01-01") time_span_s = 365 * 24 * 3600 nside = 256 sampling_hz = 1 sim = lbs.Simulation(start_time=start_time, duration_s=time_span_s) scanning = lbs.SpinningScanningStrategy( spin_sun_angle_rad=0.785_398_163_397_448_3, precession_rate_hz=8.664_850_513_998_931e-05, spin_rate_hz=0.000_833_333_333_333_333_4, start_time=start_time, ) spin2ecliptic_quats = scanning.generate_spin2ecl_quaternions( start_time, time_span_s, delta_time_s=7200 ) instr = lbs.InstrumentInfo( boresight_rotangle_rad=0.0, spin_boresight_angle_rad=0.872_664_625_997_164_8, spin_rotangle_rad=3.141_592_653_589_793, ) det = lbs.DetectorInfo( name="Boresight_detector", sampling_rate_hz=sampling_hz, bandcenter_ghz=100.0, quat=[0.0, 0.0, 0.0, 1.0], ) (obs_s_o,) = sim.create_observations(detectors=[det]) (obs_o,) = sim.create_observations(detectors=[det]) pointings = lbs.scanning.get_pointings( obs_s_o, spin2ecliptic_quats=spin2ecliptic_quats, detector_quats=[det.quat], bore2spin_quat=instr.bore2spin_quat, ) orbit_s_o = lbs.SpacecraftOrbit(obs_s_o.start_time) orbit_o = lbs.SpacecraftOrbit(obs_o.start_time, solar_velocity_km_s=0.0) assert orbit_s_o.solar_velocity_km_s == 369.8160 assert orbit_o.solar_velocity_km_s == 0.0 pos_vel_s_o = lbs.spacecraft_pos_and_vel(orbit_s_o, obs_s_o, delta_time_s=86400.0) pos_vel_o = lbs.spacecraft_pos_and_vel(orbit_o, obs_o, delta_time_s=86400.0) assert pos_vel_s_o.velocities_km_s.shape == (366, 3) assert pos_vel_o.velocities_km_s.shape == (366, 3) lbs.add_dipole_to_observations( obs_s_o, pointings, pos_vel_s_o, dipole_type=lbs.DipoleType.LINEAR ) lbs.add_dipole_to_observations( obs_o, pointings, pos_vel_o, dipole_type=lbs.DipoleType.LINEAR ) npix = hp.nside2npix(nside) pix_indexes = hp.ang2pix(nside, pointings[0, :, 0], pointings[0, :, 1]) h = np.zeros(npix) m = np.zeros(npix) map_s_o = np.zeros(npix) map_o = np.zeros(npix) bin_map( tod=obs_s_o.tod, pixel_indexes=pix_indexes, binned_map=map_s_o, accum_map=m, hit_map=h, ) import healpy healpy.write_map(tmpdir / "map_s_o.fits.gz", map_s_o, overwrite=True) bin_map( tod=obs_o.tod, pixel_indexes=pix_indexes, binned_map=map_o, accum_map=m, hit_map=h, ) healpy.write_map(tmpdir / "map_o.fits.gz", map_o, overwrite=True) dip_map = map_s_o - map_o assert np.abs(np.nanmean(map_s_o) * 1e6) < 1 assert np.abs(np.nanmean(map_o) * 1e6) < 1 assert np.abs(np.nanmean(dip_map) * 1e6) < 1 dip_map[np.isnan(dip_map)] = healpy.UNSEEN mono, dip = hp.fit_dipole(dip_map) r = hp.Rotator(coord=["E", "G"]) l, b = hp.vec2ang(r(dip), lonlat=True) # Amplitude, longitude and latitude test.assertAlmostEqual(np.sqrt(np.sum(dip ** 2)) * 1e6, 3362.08, 1) test.assertAlmostEqual(l[0], 264.021, 1) test.assertAlmostEqual(b[0], 48.253, 1)
def test_destriper(tmp_path): sim = lbs.Simulation(base_path=tmp_path / "destriper_output", start_time=0, duration_s=86400.0) sim.generate_spin2ecl_quaternions( scanning_strategy=lbs.SpinningScanningStrategy( spin_sun_angle_rad=np.deg2rad(30), # CORE-specific parameter spin_rate_hz=0.5 / 60, # Ditto # We use astropy to convert the period (4 days) in # seconds precession_rate_hz=1.0 / (4 * u.day).to("s").value, )) instr = lbs.InstrumentInfo(name="core", spin_boresight_angle_rad=np.deg2rad(65)) sim.create_observations( detectors=[ lbs.DetectorInfo(name="0A", sampling_rate_hz=10), lbs.DetectorInfo(name="0B", sampling_rate_hz=10, quat=lbs.quat_rotation_z(np.pi / 2)), ], # num_of_obs_per_detector=lbs.MPI_COMM_WORLD.size, dtype_tod=np.float64, n_blocks_time=lbs.MPI_COMM_WORLD.size, split_list_over_processes=False, ) # Generate some white noise rs = RandomState(MT19937(SeedSequence(123456789))) for curobs in sim.observations: curobs.tod *= 0.0 curobs.tod += rs.randn(*curobs.tod.shape) params = lbs.DestriperParameters( nside=16, nnz=3, baseline_length=100, iter_max=10, return_hit_map=True, return_binned_map=True, return_destriped_map=True, return_npp=True, return_invnpp=True, return_rcond=True, ) results = lbs.destripe(sim, instr, params=params) ref_map_path = Path(__file__).parent / "destriper_reference" hit_map_filename = ref_map_path / "destriper_hit_map.fits.gz" # healpy.write_map(hit_map_filename, results.hit_map, dtype="int32", overwrite=True) np.testing.assert_allclose( results.hit_map, healpy.read_map(hit_map_filename, field=None, dtype=np.int32)) binned_map_filename = ref_map_path / "destriper_binned_map.fits.gz" # healpy.write_map( # binned_map_filename, # results.binned_map, # dtype=list((np.float32 for i in range(3))), # overwrite=True, # ) ref_binned = healpy.read_map(binned_map_filename, field=None, dtype=list((np.float32 for i in range(3)))) assert results.binned_map.shape == ref_binned.shape np.testing.assert_allclose(results.binned_map, ref_binned, rtol=1e-2, atol=1e-3) destriped_map_filename = ref_map_path / "destriper_destriped_map.fits.gz" # healpy.write_map( # destriped_map_filename, # results.destriped_map, # dtype=list((np.float32 for i in range(3))), # overwrite=True, # ) ref_destriped = healpy.read_map(destriped_map_filename, field=None, dtype=list((np.float32 for i in range(3)))) assert results.destriped_map.shape == ref_destriped.shape np.testing.assert_allclose(results.destriped_map, ref_destriped, rtol=1e-2, atol=1e-3) npp_filename = ref_map_path / "destriper_npp.fits.gz" # healpy.write_map( # npp_filename, # results.npp, # dtype=list((np.float32 for i in range(6))), # overwrite=True, # ) ref_npp = healpy.read_map(npp_filename, field=None, dtype=list((np.float32 for i in range(6)))) assert results.npp.shape == ref_npp.shape np.testing.assert_allclose(results.npp, ref_npp, rtol=1e-2, atol=1e-3) invnpp_filename = ref_map_path / "destriper_invnpp.fits.gz" # healpy.write_map( # invnpp_filename, # results.invnpp, # dtype=list((np.float32 for i in range(6))), # overwrite=True, # ) ref_invnpp = healpy.read_map(invnpp_filename, field=None, dtype=list((np.float32 for i in range(6)))) assert results.invnpp.shape == ref_invnpp.shape np.testing.assert_allclose(results.invnpp, ref_invnpp, rtol=1e-2, atol=1e-3) rcond_filename = ref_map_path / "destriper_rcond.fits.gz" # healpy.write_map( # rcond_filename, # results.rcond, # dtype=np.float32, # overwrite=True, # ) assert np.allclose( results.rcond, healpy.read_map(rcond_filename, field=None, dtype=np.float32))
def test_scan_map(): # The purpose of this test is to simulate the motion of the spacecraft # for one year (see `time_span_s`) and produce *two* maps: the first # is associated with the Observation `obs1` and is built using # `scan_map_in_observations` and `make_bin_map`, the second is associated # the Observation `obs2` and is built directly filling in the test # `tod`, `psi` and `pixind` and then using `make_bin_map` # In the final test `out_map1` is compared with both `out_map2` and the # input map. Both simulations use two orthogonal detectors at the boresight # and input maps generated with `np.random.normal`. start_time = 0 time_span_s = 365 * 24 * 3600 nside = 16 sampling_hz = 1 hwp_radpsec = 4.084_070_449_666_731 npix = hp.nside2npix(nside) sim = lbs.Simulation(start_time=start_time, duration_s=time_span_s) scanning = lbs.SpinningScanningStrategy( spin_sun_angle_rad=0.785_398_163_397_448_3, precession_rate_hz=8.664_850_513_998_931e-05, spin_rate_hz=0.000_833_333_333_333_333_4, start_time=start_time, ) spin2ecliptic_quats = scanning.generate_spin2ecl_quaternions( start_time, time_span_s, delta_time_s=7200) instr = lbs.InstrumentInfo( boresight_rotangle_rad=0.0, spin_boresight_angle_rad=0.872_664_625_997_164_8, spin_rotangle_rad=3.141_592_653_589_793, ) detT = lbs.DetectorInfo( name="Boresight_detector_T", sampling_rate_hz=sampling_hz, bandcenter_ghz=100.0, quat=[0.0, 0.0, 0.0, 1.0], ) detB = lbs.DetectorInfo( name="Boresight_detector_B", sampling_rate_hz=sampling_hz, bandcenter_ghz=100.0, quat=[0.0, 0.0, 1.0 / np.sqrt(2.0), 1.0 / np.sqrt(2.0)], ) np.random.seed(seed=123_456_789) maps = np.random.normal(0, 1, (3, npix)) in_map = {"Boresight_detector_T": maps, "Boresight_detector_B": maps} (obs1, ) = sim.create_observations(detectors=[detT, detB]) (obs2, ) = sim.create_observations(detectors=[detT, detB]) pointings = lbs.scanning.get_pointings( obs1, spin2ecliptic_quats=spin2ecliptic_quats, detector_quats=[detT.quat, detB.quat], bore2spin_quat=instr.bore2spin_quat, ) lbs.scan_map_in_observations(obs1, pointings, hwp_radpsec, in_map, fill_psi_and_pixind_in_obs=True) out_map1 = lbs.make_bin_map(obs1, nside).T times = obs2.get_times() obs2.pixind = np.empty(obs2.tod.shape, dtype=np.int32) obs2.psi = np.empty(obs2.tod.shape) for idet in range(obs2.n_detectors): obs2.pixind[idet, :] = hp.ang2pix(nside, pointings[idet, :, 0], pointings[idet, :, 1]) obs2.psi[idet, :] = np.mod( pointings[idet, :, 2] + 2 * times * hwp_radpsec, 2 * np.pi) for idet in range(obs2.n_detectors): obs2.tod[idet, :] = ( maps[0, obs2.pixind[idet, :]] + np.cos(2 * obs2.psi[idet, :]) * maps[1, obs2.pixind[idet, :]] + np.sin(2 * obs2.psi[idet, :]) * maps[2, obs2.pixind[idet, :]]) out_map2 = lbs.make_bin_map(obs2, nside).T np.testing.assert_allclose(out_map1, in_map["Boresight_detector_T"], rtol=1e-6, atol=1e-6) np.testing.assert_allclose(out_map1, out_map2, rtol=1e-6, atol=1e-6)
def main(): warnings.filterwarnings("ignore", category=ErfaWarning) sim = lbs.Simulation( parameter_file=sys.argv[1], name="Observation of planets", description=""" This report contains the result of a simulation of the observation of the sky, particularly with respect to the observation of planets. """, ) params = load_parameters(sim) if lbs.MPI_ENABLED: log.info("Using MPI with %d processes", lbs.MPI_COMM_WORLD.size) else: log.info("Not using MPI, serial execution") log.info("Generating the quaternions") scanning_strategy = read_scanning_strategy( sim.parameters["scanning_strategy"], sim.imo, sim.start_time) sim.generate_spin2ecl_quaternions( scanning_strategy=scanning_strategy, delta_time_s=params.spin2ecl_delta_time_s) log.info("Creating the observations") instr = lbs.InstrumentInfo( name="instrum", spin_boresight_angle_rad=params.spin_boresight_angle_rad) detector = lbs.DetectorInfo( sampling_rate_hz=params.detector_sampling_rate_hz) conversions = [ ("years", astropy.units.year), ("year", astropy.units.year), ("days", astropy.units.day), ("day", astropy.units.day), ("hours", astropy.units.hour), ("hour", astropy.units.hour), ("minutes", astropy.units.minute), ("min", astropy.units.minute), ("sec", astropy.units.second), ("s", astropy.units.second), ("km", astropy.units.kilometer), ("Km", astropy.units.kilometer), ("au", astropy.units.au), ("AU", astropy.units.au), ("deg", astropy.units.deg), ("rad", astropy.units.rad), ] def conversion(x, new_unit): if isinstance(x, str): for conv_str, conv_unit in conversions: if x.endswith(" " + conv_str): value = float(x.replace(conv_str, "")) return (value * conv_unit).to(new_unit).value break else: return float(x) sim_params = sim.parameters["simulation"] durations = ["duration_s", "duration_of_obs_s"] for dur in durations: sim_params[dur] = conversion(sim_params[dur], "s") delta_t_s = sim_params["duration_of_obs_s"] sim.create_observations( detectors=[detector], num_of_obs_per_detector=int(sim_params["duration_s"] / sim_params["duration_of_obs_s"]), ) ################################################################# # Here begins the juicy part log.info("The loop starts on %d processes", lbs.MPI_COMM_WORLD.size) sky_hitmap = np.zeros(healpy.nside2npix(params.output_nside), dtype=np.int32) detector_hitmap = np.zeros(healpy.nside2npix(params.output_nside), dtype=np.int32) dist_map_m2 = np.zeros(len(detector_hitmap)) iterator = tqdm if lbs.MPI_ENABLED and lbs.MPI_COMM_WORLD.rank != 0: iterator = lambda x: x # Time variable inizialized at the beginning of the simulation t = 0.0 for obs in iterator(sim.observations): solar_system_ephemeris.set("builtin") times = obs.get_times(astropy_times=True) # We only compute the planet's position for the first sample in # the observation and then assume that it does not move # significantly. (In Ecliptic coordinates, Jupiter moves by # fractions of an arcmin over a time span of one hour) time0 = times[0] icrs_pos = get_ecliptic_vec( get_body_barycentric(params.planet_name, time0)) earth_pos = get_ecliptic_vec(get_body_barycentric("earth", time0)) # Move the spacecraft to L2 L2_pos = earth_pos * ( 1.0 + params.earth_L2_distance_km / norm(earth_pos).to("km").value) # Creating a Lissajous orbit R1 = conversion(params.radius_au[0], "au") R2 = conversion(params.radius_au[1], "au") phi1_t = params.L2_orbital_velocity_rad_s[0] * t phi2_t = params.L2_orbital_velocity_rad_s[1] * t phase = conversion(params.phase_rad, "rad") orbit_pos = np.array([ -R1 * np.sin(np.arctan(L2_pos[1] / L2_pos[0])) * np.cos(phi1_t), R1 * np.cos(np.arctan(L2_pos[1] / L2_pos[0])) * np.cos(phi1_t), R2 * np.sin(phi2_t + phase), ]) orbit_pos = astropy.units.Quantity(orbit_pos, unit="AU") # Move the spacecraft to a Lissajous orbit around L2 sat_pos = orbit_pos + L2_pos # Compute the distance between the spacecraft and the planet distance_m = norm(sat_pos - icrs_pos).to("m").value # This is the direction of the solar system body with respect # to the spacecraft, in Ecliptic coordinates ecl_vec = (icrs_pos - sat_pos).value # The variable ecl_vec is a 3-element vector. We normalize it so # that it has length one (using the L_2 norm, hence ord=2) ecl_vec /= np.linalg.norm(ecl_vec, axis=0, ord=2) # Convert the matrix to a N×3 shape by repeating the vector: # planets move slowly, so we assume that the planet stays fixed # during this observation. ecl_vec = np.repeat(ecl_vec.reshape(1, 3), len(times), axis=0) # Calculate the quaternions that convert the Ecliptic # reference system into the detector's reference system quats = lbs.get_ecl2det_quaternions( obs, sim.spin2ecliptic_quats, detector_quats=[detector.quat], bore2spin_quat=instr.bore2spin_quat, ) # Make room for the xyz vectors in the detector's reference frame det_vec = np.empty_like(ecl_vec) # Do the rotation! lbs.all_rotate_vectors(det_vec, quats[0], ecl_vec) pixidx = healpy.vec2pix(params.output_nside, det_vec[:, 0], det_vec[:, 1], det_vec[:, 2]) bincount = np.bincount(pixidx, minlength=len(detector_hitmap)) detector_hitmap += bincount dist_map_m2 += bincount / ((4 * np.pi * (distance_m**2))**2) pointings = lbs.get_pointings(obs, sim.spin2ecliptic_quats, [detector.quat], instr.bore2spin_quat)[0] pixidx = healpy.ang2pix(params.output_nside, pointings[:, 0], pointings[:, 1]) bincount = np.bincount(pixidx, minlength=len(sky_hitmap)) sky_hitmap += bincount # updating the time variable t += delta_t_s if lbs.MPI_ENABLED: sky_hitmap = lbs.MPI_COMM_WORLD.allreduce(sky_hitmap) detector_hitmap = lbs.MPI_COMM_WORLD.allreduce(detector_hitmap) dist_map_m2 = lbs.MPI_COMM_WORLD.allreduce(dist_map_m2) time_map_s = detector_hitmap / params.detector_sampling_rate_hz dist_map_m2[dist_map_m2 > 0] = np.power(dist_map_m2[dist_map_m2 > 0], -0.5) obs_time_per_radius_s = [ time_per_radius(time_map_s, angular_radius_rad=np.deg2rad(radius_deg)) for radius_deg in params.radii_deg ] if lbs.MPI_COMM_WORLD.rank == 0: # Create a plot of the observation time of the planet as a # function of the angular radius fig, ax = plt.subplots() ax.loglog(params.radii_deg, obs_time_per_radius_s) ax.set_xlabel("Radius [deg]") ax.set_ylabel("Observation time [s]") # Create a map showing how the observation time is distributed on # the sphere (in the reference frame of the detector) healpy.orthview(time_map_s, title="Time spent observing the source", unit="s") if scanning_strategy.spin_rate_hz != 0: spin_period_min = 1.0 / (60.0 * scanning_strategy.spin_rate_hz) else: spin_period_min = 0.0 if scanning_strategy.precession_rate_hz != 0: precession_period_min = 1.0 / ( 60.0 * scanning_strategy.precession_rate_hz) else: precession_period_min = 0.0 sim.append_to_report( """ ## Scanning strategy parameters Parameter | Value --------- | -------------- Angle between the spin axis and the Sun-Earth axis | {{ sun_earth_angle_deg }} deg Angle between the spin axis and the boresight | {{ spin_boresight_angle_deg }} deg Precession period | {{ precession_period_min }} min Spin period | {{ spin_period_min }} min ## Observation of {{ params.planet_name | capitalize }} ![](detector_hitmap.png) The overall time spent in the map is {{ overall_time_s }} seconds. The time resolution of the simulation was {{ delta_time_s }} seconds. Angular radius [deg] | Time spent [s] -------------------- | ------------------------ {% for row in radius_vs_time_s -%} {{ "%.1f"|format(row[0]) }} | {{ "%.1f"|format(row[1]) }} {% endfor -%} ![](radius_vs_time.svg) """, figures=[(plt.gcf(), "detector_hitmap.png"), (fig, "radius_vs_time.svg")], params=params, overall_time_s=np.sum(detector_hitmap) / params.detector_sampling_rate_hz, radius_vs_time_s=list(zip(params.radii_deg, obs_time_per_radius_s)), delta_time_s=1.0 / params.detector_sampling_rate_hz, sun_earth_angle_deg=np.rad2deg( scanning_strategy.spin_sun_angle_rad), spin_boresight_angle_deg=np.rad2deg( params.spin_boresight_angle_rad), precession_period_min=precession_period_min, spin_period_min=spin_period_min, det=detector, ) healpy.write_map( params.output_map_file_name, (detector_hitmap, time_map_s, dist_map_m2, sky_hitmap), coord="DETECTOR", column_names=["HITS", "OBSTIME", "SQDIST", "SKYHITS"], column_units=["", "s", "m^2", ""], dtype=[np.int32, np.float32, np.float64, np.int32], overwrite=True, ) np.savetxt( params.output_table_file_name, np.array(list(zip(params.radii_deg, obs_time_per_radius_s))), fmt=["%.2f", "%.5e"], ) with (sim.base_path / "parameters.json").open("wt") as outf: time_value = scanning_strategy.start_time if not isinstance(time_value, (int, float)): time_value = str(time_value) json.dump( { "scanning_strategy": { "spin_sun_angle_rad": scanning_strategy.spin_sun_angle_rad, "precession_rate_hz": scanning_strategy.precession_rate_hz, "spin_rate_hz": scanning_strategy.spin_rate_hz, "start_time": time_value, }, "detector": { "sampling_rate_hz": params.detector_sampling_rate_hz }, "planet": { "name": params.planet_name }, }, outf, indent=2, ) sim.flush()
precession_rate_hz=0, spin_rate_hz=1 / 60, start_time=start_time, ) # The spacecraft spins pretty fast (once per minute!), so we # need to pick delta_time_s ≪ 1/spin_rate_hz spin2ecliptic_quats = scanning.generate_spin2ecl_quaternions(start_time, time_span_s, delta_time_s=5.0) # We simulate an instrument whose boresight is perpendicular to # the spin axis. instr = lbs.InstrumentInfo( boresight_rotangle_rad=0.0, spin_boresight_angle_rad=np.deg2rad(90), spin_rotangle_rad=np.deg2rad(75), ) # A simple detector looking along the boresight direction det = lbs.DetectorInfo( name="Boresight_detector", sampling_rate_hz=sampling_hz, bandcenter_ghz=100.0, ) (obs, ) = sim.create_observations(detectors=[det]) pointings = lbs.scanning.get_pointings( obs, spin2ecliptic_quats=spin2ecliptic_quats, detector_quats=[det.quat],