Exemplo n.º 1
0
def test_parameter_dict(tmp_path):
    from datetime import date

    sim = lbs.Simulation(
        parameters={
            "general": {
                "a": 10,
                "b": 20.0,
                "c": False,
                "subtable": {
                    "d": date(2020, 7, 1),
                    "e": "Hello, world!"
                },
            }
        })

    assert not sim.parameter_file
    assert isinstance(sim.parameters, dict)

    assert "general" in sim.parameters
    assert sim.parameters["general"]["a"] == 10
    assert sim.parameters["general"]["b"] == 20.0
    assert not sim.parameters["general"]["c"]

    assert "subtable" in sim.parameters["general"]
    assert sim.parameters["general"]["subtable"]["d"] == date(2020, 7, 1)
    assert sim.parameters["general"]["subtable"]["e"] == "Hello, world!"

    try:
        sim = lbs.Simulation(parameter_file="dummy", parameters={"a": 10})
        assert False, "Simulation object should have asserted"
    except AssertionError:
        pass
Exemplo n.º 2
0
def test_parameter_file():
    from datetime import date

    with NamedTemporaryFile(mode="wt", delete=False) as conf_file:
        conf_file_name = conf_file.name
        conf_file.write("""# test_parameter_file
[simulation]
start_time = "2020-01-01T00:00:00"
duration_s = 11.0
description = "Dummy description"

[general]
a = 10
b = 20.0
c = false

[general.subtable]
d = 2020-07-01
e = "Hello, world!"
""")

    with TemporaryDirectory() as tmpdirname:
        sim = lbs.Simulation(base_path=tmpdirname,
                             parameter_file=conf_file_name)

        assert isinstance(sim.parameter_file, pathlib.Path)
        assert isinstance(sim.parameters, dict)

        assert "simulation" in sim.parameters
        assert isinstance(sim.start_time, astropy.time.Time)
        assert sim.duration_s == 11.0
        assert sim.description == "Dummy description"

        assert "general" in sim.parameters
        assert sim.parameters["general"]["a"] == 10
        assert sim.parameters["general"]["b"] == 20.0
        assert not sim.parameters["general"]["c"]

        assert "subtable" in sim.parameters["general"]
        assert sim.parameters["general"]["subtable"]["d"] == date(2020, 7, 1)
        assert sim.parameters["general"]["subtable"]["e"] == "Hello, world!"

    # Check that the code does not complain if the output directory is
    # the same as the one containing the parameter file

    sim = lbs.Simulation(base_path=Path(conf_file_name).parent,
                         parameter_file=conf_file_name)

    os.unlink(conf_file_name)
Exemplo n.º 3
0
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()
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
def test_imo_in_report(tmp_path):
    curpath = pathlib.Path(__file__).parent
    imo = lbs.Imo(flatfile_location=curpath / "mock_imo")

    sim = lbs.Simulation(
        base_path=tmp_path / "simulation_dir",
        name="My simulation",
        description="Lorem ipsum",
        imo=imo,
    )

    entity_uuid = UUID("dd32cb51-f7d5-4c03-bf47-766ce87dc3ba")
    _ = sim.imo.query(f"/entities/{entity_uuid}")

    quantity_uuid = UUID("e9916db9-a234-4921-adfd-6c3bb4f816e9")
    _ = sim.imo.query(f"/quantities/{quantity_uuid}")

    data_file_uuid = UUID("37bb70e4-29b2-4657-ba0b-4ccefbc5ae36")
    _ = sim.imo.query(f"/data_files/{data_file_uuid}")

    # This data file is an older version of 37bb70e4
    data_file_uuid = UUID("bd8e16eb-2e9d-46dd-a971-f446e953b9dc")
    _ = sim.imo.query(f"/data_files/{data_file_uuid}")

    html_file = sim.flush()
    assert isinstance(html_file, pathlib.Path)
    assert html_file.exists()
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
def test_mbs():
    try:
        # PySM3 produces a lot of warnings when it downloads stuff from Internet
        warnings.filterwarnings("ignore")

        with NamedTemporaryFile(mode="w+t", suffix=".toml") as simfile:
            with TemporaryDirectory() as outdir:
                simfile.write(PARAMETER_FILE.format(output_directory=outdir))
            simfile.flush()

            sim = lbs.Simulation(base_path=outdir, parameter_file=simfile.name)

            myinst = {}
            myinst["mock"] = {
                "bandcenter_ghz": 140.0,
                "bandwidth_ghz": 42.0,
                "fwhm_arcmin": 30.8,
                "p_sens_ukarcmin": 6.39,
            }
            mbs = lbs.Mbs(sim,
                          sim.parameters["map_based_sims"],
                          instrument=myinst)
            (maps, saved_maps) = mbs.run_all()

            curpath = Path(__file__).parent
            map_ref = hp.read_map(curpath / "reference_mbs.fits", (0, 1, 2))
            assert np.allclose(maps["mock"], map_ref, atol=1e-6)
    finally:
        # Reset the standard warnings filter
        warnings.resetwarnings()
Exemplo n.º 8
0
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,
        distribute=False,
    )

    sstr = lbs.SpinningScanningStrategy(
        spin_sun_angle_deg=10.0,
        precession_period_min=10.0,
        spin_rate_rpm=0.1,
    )
    sim.generate_spin2ecl_quaternions(scanning_strategy=sstr,
                                      delta_time_s=60.0)

    instr = lbs.Instrument(spin_boresight_angle_deg=20.0)

    for obs in sim.observations:
        pointings_and_polangle = obs.get_pointings(
            spin2ecliptic_quats=sim.spin2ecliptic_quats,
            detector_quats=np.array([[0.0, 0.0, 0.0, 1.0]]),
            bore2spin_quat=instr.bore2spin_quat,
        )
Exemplo n.º 9
0
def test_distribution():
    comm_world = lbs.MPI_COMM_WORLD

    if comm_world.rank == 0:
        print(f"MPI configuration: {lbs.MPI_CONFIGURATION}")

    sim = lbs.Simulation(mpi_comm=comm_world)
    det1 = lbs.Detector(name="pol01",
                        beam_z=[0, 0, 1],
                        sampfreq_hz=5,
                        simulation=sim)
    det2 = lbs.Detector(name="pol02",
                        beam_z=[0, 0, 1],
                        sampfreq_hz=50,
                        simulation=sim)
    sim.create_observations(
        [det1, det2],
        num_of_obs_per_detector=10,
        start_time=0.0,
        duration_s=86400.0 * 10,
    )

    assert (
        sim.observations
    ), "No observations have been defined for process {lbs.MPI_RANK + 1}/{lbs.MPI_SIZE}"

    if comm_world.size > 1:
        allobs = comm_world.allreduce(
            [x.detector.name for x in sim.observations])
    else:
        allobs = sim.observations

    assert len(allobs) == 20
Exemplo n.º 10
0
def test_parameter_file(tmp_path):
    from datetime import date

    conf_file = pathlib.Path(tmp_path) / "configuration.toml"
    with conf_file.open("wt") as outf:
        outf.write("""[simulation]
start_time = "2020-01-01T00:00:00"
duration_s = 11.0
description = "Dummy description"

[general]
a = 10
b = 20.0
c = false

[general.subtable]
d = 2020-07-01
e = "Hello, world!"
""")

    sim = lbs.Simulation(parameter_file=conf_file)

    assert isinstance(sim.parameter_file, pathlib.Path)
    assert isinstance(sim.parameters, dict)

    assert "simulation" in sim.parameters
    assert isinstance(sim.start_time, astropy.time.Time)
    assert sim.duration_s == 11.0
    assert sim.description == "Dummy description"

    assert "general" in sim.parameters
    assert sim.parameters["general"]["a"] == 10
    assert sim.parameters["general"]["b"] == 20.0
    assert not sim.parameters["general"]["c"]

    assert "subtable" in sim.parameters["general"]
    assert sim.parameters["general"]["subtable"]["d"] == date(2020, 7, 1)
    assert sim.parameters["general"]["subtable"]["e"] == "Hello, world!"

    # Check that the code does not complain if the output directory is
    # the same as the one containing the parameter file

    sim = lbs.Simulation(base_path=tmp_path, parameter_file=conf_file)
Exemplo n.º 11
0
def test_distribute_observation(tmp_path):
    sim = lbs.Simulation(base_path=tmp_path / "simulation_dir",
                         start_time=1.0,
                         duration_s=11.0)
    det = lbs.Detector("dummy", sampling_rate_hz=15)
    obs_list = sim.create_observations(detectors=[det],
                                       num_of_obs_per_detector=5)

    assert len(obs_list) == 5
    assert int(obs_list[-1].get_times()[-1] - obs_list[0].get_times()[0]) == 10
    assert sum([o.n_samples
                for o in obs_list]) == sim.duration_s * det.sampling_rate_hz
Exemplo n.º 12
0
def test_markdown_report(tmp_path):
    sim = lbs.Simulation(
        base_path=tmp_path / "simulation_dir",
        name="My simulation",
        description="Lorem ipsum",
        start_time=1.0,
        duration_s=3600.0,
    )
    output_file = sim.write_healpix_map(filename="test.fits.gz",
                                        pixels=np.zeros(12))

    assert isinstance(output_file, pathlib.Path)
    assert output_file.exists()

    sim.append_to_report(
        """Here is a plot:

![](myplot.png)

And here are the data points:
{% for sample in data_points -%}
- {{ sample }}
{% endfor %}
 """,
        figures=[(MockPlot(), "myplot.png")],
        data_points=[0, 1, 2],
    )

    reference = """# My simulation

Lorem ipsum


The simulation starts at t0=1.0 and lasts 3600.0 seconds.

[TOC]



Here is a plot:

![](myplot.png)

And here are the data points:
- 0
- 1
- 2
"""

    print(sim.report)
    assert reference.strip() in sim.report.strip()

    sim.flush()
Exemplo n.º 13
0
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,
        distribute=False,
    )
    assert len(sim.observations) == 1
    obs = sim.observations[0]

    sstr = lbs.SpinningScanningStrategy(
        spin_sun_angle_deg=0.0,
        precession_period_min=0.0,
        spin_rate_rpm=1.0,
    )
    sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=0.5)

    instr = lbs.Instrument(spin_boresight_angle_deg=15.0)

    pointings_and_polangle = obs.get_pointings(
        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]

    with open("spin2ecliptic_quats.txt", "wt") as outf:
        for i in range(sim.spin2ecliptic_quats.quats.shape[0]):
            print(*sim.spin2ecliptic_quats.quats[i, :], file=outf)

    with open("pointings.txt", "wt") as outf:
        import healpy

        for i in range(pointings_and_polangle.shape[1]):
            print(
                *healpy.ang2vec(pointings_and_polangle[0, i, 0],
                                pointings_and_polangle[0, i, 1]),
                file=outf,
            )

    # 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()
Exemplo n.º 14
0
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="")
Exemplo n.º 15
0
def test_duration_units_in_parameter_file(tmp_path):
    conf_file = pathlib.Path(tmp_path) / "configuration.toml"
    with conf_file.open("wt") as outf:
        outf.write("""[simulation]
start_time = "2020-01-01T00:00:00"
duration_s = "1 day"
""")

    sim = lbs.Simulation(parameter_file=conf_file)

    assert "simulation" in sim.parameters
    assert isinstance(sim.start_time, astropy.time.Time)
    assert sim.duration_s == 86400.0
Exemplo n.º 16
0
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,
    )
Exemplo n.º 17
0
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,
        distribute=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_deg=0.0,
        precession_period_min=0.0,
        spin_rate_rpm=0.0,
    )
    sim.generate_spin2ecl_quaternions(sstr, delta_time_s=60.0)
    assert sim.spin2ecliptic_quats.quats.shape == (24 * 60 + 1, 4)

    instr = lbs.Instrument(spin_boresight_angle_deg=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 Observation.get_pointings
    pointings_and_polangle = obs.get_pointings(
        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)
Exemplo n.º 18
0
def test_duration_units_in_parameter_file():
    with NamedTemporaryFile(mode="wt", delete=False) as conf_file:
        conf_file_name = conf_file.name
        conf_file.write("""# test_duration_units_in_parameter_file
[simulation]
start_time = "2020-01-01T00:00:00"
duration_s = "1 day"
""")

    with TemporaryDirectory() as tmpdirname:
        sim = lbs.Simulation(base_path=tmpdirname,
                             parameter_file=conf_file_name)

        assert "simulation" in sim.parameters
        assert isinstance(sim.start_time, astropy.time.Time)
        assert sim.duration_s == 86400.0
Exemplo n.º 19
0
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,
        distribute=False,
    )
    assert len(sim.observations) == 1
    obs = sim.observations[0]

    sstr = lbs.SpinningScanningStrategy(
        spin_sun_angle_deg=0.0,
        precession_period_min=0.0,
        spin_rate_rpm=1.0,
    )
    sim.generate_spin2ecl_quaternions(scanning_strategy=sstr, delta_time_s=0.5)

    instr = lbs.Instrument(spin_boresight_angle_deg=15.0)
    detector_quat = np.array([[0.0, 0.0, 0.0, 1.0]])

    det2ecl_quats = obs.get_det2ecl_quaternions(
        spin2ecliptic_quats=sim.spin2ecliptic_quats,
        detector_quats=detector_quat,
        bore2spin_quat=instr.bore2spin_quat,
    )

    ecl2det_quats = obs.get_ecl2det_quaternions(
        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)
Exemplo n.º 20
0
def test_healpix_map_write(tmp_path):
    sim = lbs.Simulation(base_path=tmp_path / "simulation_dir")
    output_file = sim.write_healpix_map(filename="test.fits.gz",
                                        pixels=np.zeros(12))

    assert isinstance(output_file, pathlib.Path)
    assert output_file.exists()

    sim.append_to_report(
        """Here is a plot:

 ![](myplot.png)
 """,
        [(MockPlot(), "myplot.png")],
    )

    sim.flush()
Exemplo n.º 21
0
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()
Exemplo n.º 22
0
def test_write_hdf5_mpi(tmp_path):
    start_time = 0
    time_span_s = 60
    sampling_hz = 10

    sim = lbs.Simulation(
        base_path=tmp_path,
        start_time=start_time,
        duration_s=time_span_s,
    )

    det = lbs.DetectorInfo(
        name="Dummy detector",
        sampling_rate_hz=sampling_hz,
        bandcenter_ghz=100.0,
        quat=[0.0, 0.0, 0.0, 1.0],
    )

    num_of_obs = 12
    sim.create_observations(detectors=[det],
                            num_of_obs_per_detector=num_of_obs)

    file_names = lbs.write_observations(
        sim,
        subdir_name="tod",
        file_name_mask="litebird_tod{global_index:04d}.h5")

    assert len(file_names) == len(sim.observations)

    if lbs.MPI_ENABLED:
        # Wait that all the processes have completed writing the files
        lbs.MPI_COMM_WORLD.barrier()

    tod_path = sim.base_path / "tod"
    files_found = list(tod_path.glob("litebird_tod*.h5"))
    assert len(files_found) == num_of_obs, (
        f"{len(files_found)} files found in {tod_path} instead of " +
        f"{num_of_obs}: {files_found}")
    for idx in range(num_of_obs):
        cur_tod = tod_path / f"litebird_tod{idx:04d}.h5"
        assert cur_tod.is_file(), f"File {cur_tod} was expected but not found"
Exemplo n.º 23
0
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)
Exemplo n.º 24
0
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))
Exemplo n.º 25
0
def main(data_path: Path):
    sim = lbs.Simulation(
        parameter_file=sys.argv[1],
        name="In-flight estimation of the beam properties",
        description="""
This report contains the result of a simulation of the reconstruction
of in-flight beam parameters, assuming a scanning strategy and some
noise/optical properties of a detector.
""",
    )

    params = load_parameters(sim)

    det = read_detector(sim.parameters["detector"], sim.imo)

    # TODO: This should be done by the framework
    copyfile(src=params.sed_file_name, dst=sim.base_path / params.sed_file_name.name)

    # Calculate the brightness temperature of the planet over the band
    sed_data = np.loadtxt(params.sed_file_name, delimiter=",")
    sed_fn = interpolate.interp1d(sed_data[:, 0], sed_data[:, 1])
    planet_temperature_k = (
        integrate.quad(
            sed_fn,
            params.detector.bandcenter_ghz - params.detector.bandwidth_ghz / 2,
            params.detector.bandcenter_ghz + params.detector.bandwidth_ghz / 2,
        )[0]
        / params.detector.bandwidth_ghz
    )
    beam_solid_angle = calc_beam_solid_angle(
        fwhm_arcmin=params.fwhm_arcmin, ecc=params.ecc
    )
    sampling_time_s = 1.0 / params.detector.sampling_rate_hz

    input_map_file_name = params.scanning_simulation / "map.fits.gz"

    hit_map, time_map_s, dist_map_m2 = healpy.read_map(
        input_map_file_name, field=(0, 1, 2), verbose=False, dtype=np.float32
    )
    nside = healpy.npix2nside(len(dist_map_m2))
    pixel_theta, pixel_phi = healpy.pix2ang(nside, np.arange(len(hit_map)))

    gamma_map = beamfunc(
        healpy.pix2ang(nside, np.arange(len(hit_map))),
        params.fwhm_arcmin,
        params.amplitude,
        params.ecc,
        np.deg2rad(params.incl_deg),
        np.deg2rad(params.beam_deg[0]),
        np.deg2rad(params.beam_deg[1]),
    )
    mask = (hit_map > 0.0) & (gamma_map > params.amplitude * np.exp(-np.log(2) * 9 / 2))
    assert hit_map[mask].size > 0, "no data available for the fit"

    sim.append_to_report(
        r"""
## Detector properties
Parameter | Value
--------- | -----------------
Channel | {{det.channel}}
Sampling time | {{ "%.3f"|format(sampling_time_s) }} s
NET | {{det.net_ukrts}} μK·√s
Bandwidth | {{det.bandwidth_ghz}} GHz
Band center | {{det.bandcenter_ghz}} GHz
FWHM | {{det.fwhm_arcmin}} arcmin
Beam solid angle | {{ "%.3e"|format(beam_solid_angle)}} sterad
Amplitude | {{ "%.3e"|format(params.amplitude) }} 

## Properties of the planet
Parameter | Value
--------- | ----------------
Brightness temperature | {{ "%.1f"|format(planet_temperature_k) }} K
Effective radius | {{ "%.0e"|format(params.planet_radius_m) }} m

## Beam scanning
Parameter | Value
--------- | ----------------
Pixels used in the fit | {{ num_of_pixels_used }}
Integration time | {{ integration_time_s }} s
""",
        det=params.detector,
        params=params,
        beam_solid_angle=beam_solid_angle,
        sampling_time_s=sampling_time_s,
        planet_temperature_k=planet_temperature_k,
        num_of_pixels_used=len(mask[mask]),
        integration_time_s=np.sum(time_map_s[mask]),
    )

    error_amplitude_map = (
        beam_solid_angle
        * (params.detector.net_ukrts * 1e-6)
        / (
            np.pi
            * (params.planet_radius_m ** 2)
            * planet_temperature_k
            * np.sqrt(sampling_time_s)
        )
    ) * dist_map_m2

    (
        plot_size_deg,
        gamma_fig,
        gamma_error_fig,
        gamma_over_error_fig,
    ) = create_gamma_plots(
        gamma_map,
        error_amplitude_map,
        nside,
        fwhm_arcmin=params.detector.fwhm_arcmin,
        theta_beam=np.deg2rad(params.beam_deg[0]),
        phi_beam=np.deg2rad(params.beam_deg[1]),
    )

    sim.append_to_report(
        r"""
## Error on beam estimation
This is a representation of the model of the far sidelobe beam used in the
simulation, in $`(u,v)`$ coordinates, where
$`u = \sin\theta\,\cos\phi`$ and
$`v = \sin\theta\sin\phi`$:
![](gamma.svg)
Here is the result of the estimate of $`\delta\gamma`$, using
the following formula:
```math
\delta\gamma(\vec r) = 
    \frac{\Omega_b \cdot \text{WN}}{\pi r_\text{pl}^2\,T_\text{br,pl}\,\sqrt\tau} 
    \sqrt{\frac1{\sum_{i=1}^N \left(\frac1{4\pi d_\text{pl}^2(t_i)}\right)^2}},
```
where $`N`$ is the number of samples observed along direction $`\vec
r`$, WN is the white noise level expressed as
$`\text{K}\cdot\sqrt{s}`$, $`r_\text{pl}`$ is the planet's radius,
$`d_\text{pl}(t)`$ is the planet-spacecraft distance at time $`t`$,
and $`\tau`$ is the sample integration time (assumed equal for all the
samples).
And here is a plot of $`\delta\gamma`$, again in $`(u, v)`$
coordinates:
![](gamma_error.svg)
The ratio $`\gamma / \delta\gamma`$ represents the S/N ratio:
![](gamma_over_error.svg)
The size of each plot is {{ "%.1f"|format(plot_size_deg) }}°, and the
white shadow represents the size of the FWHM
({{ "%.1f"|format(fwhm_arcmin) }} arcmin).
""",
        fwhm_arcmin=params.detector.fwhm_arcmin,
        plot_size_deg=plot_size_deg,
        figures=[
            (gamma_fig, "gamma.svg"),
            (gamma_error_fig, "gamma_error.svg"),
            (gamma_over_error_fig, "gamma_over_error.svg"),
        ],
    )

    destfile = sim.base_path / params.error_amplitude_map_file_name
    healpy.write_map(
        destfile,
        [gamma_map, error_amplitude_map],
        coord="DETECTOR",
        column_names=["GAMMA", "ERR"],
        column_units=["", ""],
        dtype=[np.float32, np.float32, np.float32],
        overwrite=True,
    )
    fwhm_estimates_arcmin = np.empty(params.num_of_mc_runs)
    ampl_estimates = np.empty(len(fwhm_estimates_arcmin))
    ecc_estimates = np.empty(len(fwhm_estimates_arcmin))
    incl_estimates = np.empty(len(fwhm_estimates_arcmin))
    theta_beam_estimates = np.empty(len(fwhm_estimates_arcmin))
    phi_beam_estimates = np.empty(len(fwhm_estimates_arcmin))

    for i in tqdm(range(len(fwhm_estimates_arcmin))):
        noise_gamma_map = gamma_map + error_amplitude_map * np.random.randn(
            len(dist_map_m2)
        )
        # Run the fit
        best_fit, pcov = optimize.curve_fit(
            beamfunc,
            np.vstack((pixel_theta[mask], pixel_phi[mask])),
            noise_gamma_map[mask],
            p0=[
                params.fwhm_arcmin,
                params.amplitude,
                params.ecc,
                np.deg2rad(params.incl_deg),
                np.deg2rad(params.beam_deg[0]),
                np.deg2rad(params.beam_deg[1]),
            ],
            maxfev=5000,
        )

        fwhm_estimates_arcmin[i] = best_fit[0]
        ampl_estimates[i] = best_fit[1]
        ecc_estimates[i] = best_fit[2]
        incl_estimates[i] = best_fit[3]
        theta_beam_estimates[i] = best_fit[4]
        phi_beam_estimates[i] = best_fit[5]

    figs = []

    for (dataset, name) in [
        (fwhm_estimates_arcmin, "FWHM [arcmin]"),
        (ampl_estimates, "AMPL [arcmin]"),
        (ecc_estimates, "ECC"),
        (incl_estimates, "INCL [rad]"),
        (theta_beam_estimates, "THETA [rad]"),
        (phi_beam_estimates, "PHI [rad]"),
    ]:
        fig, ax = plt.subplots()
        ax.hist(dataset)
        ax.set_xlabel(name)
        ax.set_ylabel("Counts")
        figs.append(fig)

    sim.append_to_report(
        """
## Results of the Monte Carlo simulation
Parameter  | Value
---------- | -----------------
# of runs  | {{ num_of_runs }}
FWHM       | {{"%.3f"|format(fwhm_arcmin)}} ± {{"%.3e"|format(fwhm_err)}} arcmin
γ0         | {{"%.3f"|format(ampl)}} ± {{"%.3e"|format(ampl_err)}} arcmin
Ecc        | {{"%.3f"|format(ecc)}} ± {{"%.3e"|format(ecc_err)}} 
Inclination | {{"%.3f"|format(incl)}} ± {{"%.3e"|format(incl_err)}} rad
Theta_beam | {{"%.3f"|format(theta)}} ± {{"%.3e"|format(theta_err)}} rad
Phi_beam | {{"%.3f"|format(phi)}} ± {{"%.3e"|format(phi_err)}} rad
![](fwhm_distribution.svg)
![](ampl_distribution.svg)
![](ecc_distribution.svg)
![](incl_distribution.svg)
![](theta_distribution.svg)
![](phi_distribution.svg)
""",
        figures=[
            (figs[0], "fwhm_distribution.svg"),
            (figs[1], "ampl_distribution.svg"),
            (figs[2], "ecc_distribution.svg"),
            (figs[3], "incl_distribution.svg"),
            (figs[4], "theta_distribution.svg"),
            (figs[5], "phi_distribution.svg"),
        ],
        num_of_runs=len(fwhm_estimates_arcmin),
        fwhm_arcmin=np.mean(fwhm_estimates_arcmin),
        fwhm_err=np.std(fwhm_estimates_arcmin),
        ampl=np.mean(ampl_estimates),
        ampl_err=np.std(ampl_estimates),
        ecc=np.mean(ecc_estimates),
        ecc_err=np.std(ampl_estimates),
        incl=np.mean(incl_estimates),
        incl_err=np.std(incl_estimates),
        theta=np.mean(theta_beam_estimates),
        theta_err=np.std(theta_beam_estimates),
        phi=np.mean(phi_beam_estimates),
        phi_err=np.std(phi_beam_estimates),
    )

    json_file_name = sim.base_path / "results.json"
    with json_file_name.open("w") as f:
        json.dump(
            {
                "mc_fwhm_arcmin": fwhm_estimates_arcmin.tolist(),
                "mc_fwhm_arcmin_mean": np.mean(fwhm_estimates_arcmin),
                "mc_fwhm_err": np.std(fwhm_estimates_arcmin),
                "mc_amplitudes": ampl_estimates.tolist(),
                "mc_amplitudes_mean": np.mean(ampl_estimates),
                "mc_amplitudes_err": np.std(ampl_estimates),
                "mc_eccentricity": ecc_estimates.tolist(),
                "mc_eccentricity_mean": np.mean(ecc_estimates),
                "mc_eccentricity_err": np.std(ampl_estimates),
                "mc_inclinations": incl_estimates.tolist(),
                "mc_inclinations_mean": np.mean(incl_estimates),
                "mc_inclinations_err": np.std(incl_estimates),
                "mc_theta_beam": theta_beam_estimates.tolist(),
                "mc_theta_mean": np.mean(theta_beam_estimates),
                "mc_theta_err": np.std(theta_beam_estimates),
                "mc_phi_beam": phi_beam_estimates.tolist(),
                "mc_phi_mean": np.mean(phi_beam_estimates),
                "mc_phi_err": np.std(phi_beam_estimates),
            },
            f,
        )

    sim.flush()
Exemplo n.º 26
0
import numpy as np
import astropy.units as u
from numpy.random import MT19937, RandomState, SeedSequence

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,
Exemplo n.º 27
0
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)
Exemplo n.º 28
0
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()
Exemplo n.º 29
0
from astropy.time import Time
import numpy as np
import litebird_sim as lbs
import matplotlib.pylab as plt

start_time = Time("2022-01-01")
time_span_s = 120.0  # Two minutes
sampling_hz = 20

sim = lbs.Simulation(start_time=start_time, duration_s=time_span_s)

# We pick a simple scanning strategy where the spin axis is aligned
# with the Sun-Earth axis, and the spacecraft spins once every minute
scanning = lbs.SpinningScanningStrategy(
    spin_sun_angle_rad=np.deg2rad(0),
    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),