Пример #1
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)
Пример #2
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()
Пример #3
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)
Пример #4
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,
    )
Пример #5
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,
                            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)
Пример #6
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()
Пример #7
0
    def __init__(self, spin2ecliptic_quats, obs, bore2spin_quat, nside):
        self.obs = obs

        self.keydict = {"timestamps": obs.get_times()}
        nsamples = len(self.keydict["timestamps"])

        self.keydict["flags"] = np.zeros(nsamples, dtype="uint8")

        pointings = lbs.get_pointings(
            obs=obs,
            spin2ecliptic_quats=spin2ecliptic_quats,
            detector_quats=obs.quat,
            bore2spin_quat=bore2spin_quat,
        )
        healpix_base = healpix.Healpix_Base(nside=nside, scheme="NEST")
        for (i, det) in enumerate(obs.name):
            if pointings[i].dtype == np.float64:
                curpnt = pointings[i]
            else:
                logging.warning(
                    "converting pointings for %s from %s to float64",
                    obs.name[i],
                    str(pointings[i].dtype),
                )
                curpnt = np.array(pointings[i], dtype=np.float64)

            if obs.tod[i].dtype == np.float64:
                self.keydict[f"signal_{det}"] = obs.tod[i]
            else:
                logging.warning(
                    "converting TODs for %s from %s to float64",
                    obs.name[i],
                    str(pointings[i].dtype),
                )
                self.keydict[f"signal_{det}"] = np.array(obs.tod[i], dtype=np.float64)

            self.keydict[f"pixels_{det}"] = healpix_base.ang2pix(curpnt[:, 0:2])
            self.keydict[f"weights_{det}"] = np.stack(
                (np.ones(nsamples), np.cos(2 * curpnt[:, 2]), np.sin(2 * curpnt[:, 2]))
            ).transpose()
Пример #8
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()