예제 #1
0
    def __init__(
        self, time_list, value_list, crossing_value=0, neg_to_pos_is_start=True
    ):

        # Check whether time_list and value_list size are equal
        if len(time_list) != len(value_list):
            raise ValueError(
                f"Time list and value list are of different sizes. "
                f"({len(time_list)} vs. ({len(value_list)}))"
            )

        self.search_interval = TimeInterval(time_list[0], time_list[-1])

        # Convert time to "days since epoch"
        t_float_list = (time_list - time_list[0]).jd

        # Init interpolators (for splines, root finding possible for 3rd degree
        # (cubics) only)
        self._interpolator = interpolate.CubicSpline(
            t_float_list, value_list - crossing_value
        )

        self._deriv_interpolator = self._interpolator.derivative()

        # Find start / end intervals
        self.start_end_intervals = self._find_intervals(time_list, neg_to_pos_is_start)

        # Find max / min events
        self.max_min_table = self._find_extrema_events(time_list[0])

        # Return the table to absolute values
        self.max_min_table["value"] = self.max_min_table["value"] + crossing_value
예제 #2
0
def test_interval_init_with_copy(init_times, durations):
    """Test initialisation with copy."""
    interval_1 = TimeInterval(init_times, durations)
    interval_2 = TimeInterval(interval_1, None)
    interval_3 = TimeInterval(interval_1, None, replicate=True)

    assert interval_1.is_equal(interval_2)
    assert interval_1.is_equal(interval_3)
예제 #3
0
def test_interval_list_union(init_times, durations):
    """Test interval union."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    intervals = TimeIntervalList(
        init_intervals,
        start_valid=Time("2020-04-11T00:00:00.000"),
        end_valid=Time("2020-04-14T00:00:00.000"),
    )

    interval_part_intersect = TimeInterval(Time("2020-04-10T23:50:00.000"), 60 * u.min)
    interval_no_intersect = TimeInterval(Time("2020-04-11T12:00:00.000"), 5 * u.min)
    interval_full_intersect = TimeInterval(Time("2020-04-13T00:00:00.000"), 5 * u.min)
    interval_outside = TimeInterval(Time("2020-04-13T23:50:00.000"), 15 * u.min)

    # *** Test TimeIntervalList vs. TimeInterval intersection ***
    assert "[ 2020-04-13T00:00:00.000  2020-04-13T00:30:00.000 ]" == str(
        intervals.union(interval_full_intersect)
    )

    assert "[ 2020-04-10T23:50:00.000  2020-04-11T00:50:00.000 ]" == str(
        intervals.union(interval_part_intersect)
    )

    assert intervals.union(interval_no_intersect) is None

    # *** Test TimeIntervalList vs. TimeIntervalList intersection ***

    test_intervals = TimeIntervalList(
        [
            interval_full_intersect,
            interval_part_intersect,
            interval_no_intersect,
            interval_outside,
        ]
    )

    # print(intervals.union_list(test_intervals))
    truth_txt = (
        "[ 2020-04-11T00:00:00.000  2020-04-11T00:50:00.000 ]\n"
        "[ 2020-04-11T12:00:00.000  2020-04-11T12:05:00.000 ]\n"
        "[ 2020-04-12T00:00:00.000  2020-04-12T00:20:00.000 ]\n"
        "[ 2020-04-13T00:00:00.000  2020-04-13T00:30:00.000 ]\n"
        "[ 2020-04-13T23:50:00.000  2020-04-14T00:00:00.000 ]\n"
    )

    assert truth_txt == str(intervals.union_list(test_intervals))
예제 #4
0
def test_union(init_times, durations):
    """Test `union` method."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    interval = TimeIntervalList(init_intervals).get_interval(0)

    assert interval.union(before) is None
    assert interval.union(within).is_equal(interval)
    assert interval.union(intersect).is_equal(
        TimeInterval(intersect.start, interval.end))
    assert interval.union(exact).is_equal(interval)
    assert interval.union(after) is None
예제 #5
0
    def __init__(self, coords_list: SkyCoord, replicate=False):
        """
        Parameters
        ----------
        coords_list : SkyCoord
            `SkyCoord` object containing the trajectory
        replicate : bool, optional
            If `True` replicates the `coords_list` into the object, otherwise
            creates a shallow copy
            of the `coords_list`. Default is False.
        """

        if replicate:
            # replicate internal coordinate data
            self._coord_list = coords_list.replicate()
        else:
            # shallow copy the internal coordinate data
            self._coord_list = coords_list

        # save the frame name for easy access when creating new instances
        self._frame_name = self._coord_list.frame[0].name

        # save the begin and end times for the internal trajectory
        # and the interpolator
        self._interval = TimeInterval(
            self._coord_list[0].obstime, self._coord_list[-1].obstime
        )

        # Check whether we have velocity data
        if self._coord_list.data.differentials:
            self._has_velocity = True
예제 #6
0
def test_invert(init_times, durations):
    """Test `invert` method."""

    end_times = init_times + durations

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], end_times[i]))

    intervals = TimeIntervalList(
        init_intervals,
        start_valid=init_times[0] - TimeDelta(1.0, format="jd"),
        end_valid=end_times[-1] + TimeDelta(1.0, format="jd"),
    )

    inverted_intervals = intervals.invert()

    truth_txt = ("[ 2020-04-10T00:00:00.000  2020-04-11T00:00:00.000 ]\n"
                 "[ 2020-04-11T00:10:00.000  2020-04-12T00:00:00.000 ]\n"
                 "[ 2020-04-12T00:20:00.000  2020-04-13T00:00:00.000 ]\n"
                 "[ 2020-04-13T00:30:00.000  2020-04-14T00:30:00.000 ]\n")

    truth_validity = "[ 2020-04-10T00:00:00.000  2020-04-14T00:30:00.000 ]"

    assert truth_txt == str(inverted_intervals)
    assert truth_validity == str(inverted_intervals.validity_interval())
예제 #7
0
def test_gen_trajectory(init_tle_vgd):
    # init TLE
    tle = init_tle_vgd
    # init SGP4
    sgp4 = SGP4Propagator(stepsize=120 * u.s)

    #  Generate trajectory
    interval = TimeInterval(tle.epoch, 0.223456789 * u.day)
    traj = sgp4.gen_trajectory(tle, interval)

    # Generate true trajectory and compute max pos & vel error
    r_diff_max = 0 * u.mm
    v_diff_max = 0 * u.mm / u.s
    for time in traj.coord_list.obstime:
        rv = traj(time)
        rv_true = SGP4Propagator.propagate(tle, time)
        r_diff, v_diff = rv_diff(rv, rv_true)
        if r_diff > r_diff_max:
            r_diff_max = r_diff
        if v_diff > v_diff_max:
            v_diff_max = v_diff

    # print(r_diff_max.to(u.mm))
    # print(v_diff_max.to(u.mm / u.s))

    assert r_diff_max.to(u.mm) < 5e-6 * u.mm
    assert v_diff_max.to(u.mm / u.s) < 4e-9 * u.mm / u.s
예제 #8
0
def propagation_engine(
    rv_init,
    stepsize,
    solver_type,
    init_time_offset,
    duration,
    rtol,
    atol,
    central_body=EARTH,
):
    """Initialises and runs the propagation engine to yield the resulting trajectory."""

    # init propagator
    prop = NumericalPropagator(
        stepsize,
        solver_type=solver_type,
        rtol=rtol,
        atol=atol,
        name="",
        central_body=central_body,
    )

    prop_start = rv_init.obstime + init_time_offset
    prop_duration = duration

    trajectory = prop.gen_trajectory(rv_init,
                                     TimeInterval(prop_start, prop_duration))

    return trajectory
예제 #9
0
def test_interval_init_with_end_times(init_times, durations):
    """Test initialisation with explicit end times."""
    end_times = init_times + durations

    interval = TimeInterval(init_times, end_times, replicate=True, end_inclusive=False)

    truth_txt = "[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 )"
    assert truth_txt == str(interval)
예제 #10
0
def test_interval_list_intersection(init_times, durations):
    """Test interval intersection."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    intervals = TimeIntervalList(init_intervals)

    interval_full_intersect = TimeInterval(Time("2020-04-13T00:00:00.000"), 5 * u.min)
    interval_part_intersect = TimeInterval(Time("2020-04-10T23:50:00.000"), 60 * u.min)
    interval_no_intersect = TimeInterval(Time("2020-04-11T12:00:00.000"), 5 * u.min)

    # *** Test TimeIntervalList vs. TimeInterval intersection ***
    assert intervals.is_intersecting(interval_full_intersect) is True
    assert "[ 2020-04-13T00:00:00.000  2020-04-13T00:05:00.000 ]" == str(
        intervals.intersect(interval_full_intersect)
    )

    assert intervals.is_intersecting(interval_part_intersect) is True
    assert "[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 ]" == str(
        intervals.intersect(interval_part_intersect)
    )

    assert intervals.is_intersecting(interval_no_intersect) is False
    assert intervals.intersect(interval_no_intersect) is None

    # *** Test TimeIntervalList vs. TimeIntervalList intersection ***

    test_intervals = TimeIntervalList(
        [interval_full_intersect, interval_part_intersect, interval_no_intersect]
    )

    truth_txt = (
        "[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 ]\n"
        "[ 2020-04-13T00:00:00.000  2020-04-13T00:05:00.000 ]\n"
    )

    assert truth_txt == str(intervals.intersect_list(test_intervals))
예제 #11
0
def test_interval_list_init_with_durations(init_times, durations):
    """Test initialisation with durations."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    intervals = TimeIntervalList(init_intervals)

    truth_txt = ("[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 ]\n"
                 "[ 2020-04-12T00:00:00.000  2020-04-12T00:20:00.000 ]\n"
                 "[ 2020-04-13T00:00:00.000  2020-04-13T00:30:00.000 ]\n")

    assert truth_txt == str(intervals)
예제 #12
0
def test_contains(init_times, durations):
    """Test `contains` method."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    interval = TimeIntervalList(init_intervals).get_interval(0)

    assert interval.contains(before) is False
    assert interval.contains(within) is True
    assert interval.contains(exact) is True
    assert interval.contains(intersect) is False
    assert interval.contains(after) is False
예제 #13
0
def test_is_in_interval(init_times, durations):
    """Test `is_in_interval` method."""

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], durations[i]))

    interval = TimeIntervalList(init_intervals).get_interval(0)

    t_before = Time("2020-04-09T00:00:00", scale="utc")
    t_eqinit = Time("2020-04-11T00:00:00", scale="utc")
    t_within = Time("2020-04-11T00:05:00", scale="utc")
    t_eqend = Time("2020-04-11T00:10:00", scale="utc")
    t_after = Time("2020-04-11T12:00:00", scale="utc")

    assert interval.is_in_interval(t_before) is False
    assert interval.is_in_interval(t_eqinit) is True
    assert interval.is_in_interval(t_within) is True
    assert interval.is_in_interval(t_eqend) is True
    assert interval.is_in_interval(t_after) is False
예제 #14
0
    def __init__(self, time_list, value_list, axis=0):

        self.data_interval = TimeInterval(time_list[0], time_list[-1])
        self._init_time = time_list[0]
        # Convert time to "days since epoch"
        t_float_list = (time_list - self._init_time).jd

        # Init interpolators (for splines, root finding possible for 3rd degree
        # (cubics) only)
        self._interpolator = ip.CubicSpline(t_float_list,
                                            value_list,
                                            axis=axis,
                                            extrapolate=False)

        # set the unit, if available
        if isinstance(value_list, Quantity):
            self._unit = value_list.unit

        self._deriv_interpolator = self._interpolator.derivative(nu=1)
        self._sec_deriv_interpolator = self._interpolator.derivative(nu=2)
예제 #15
0
def test_interval_list_init_with_end_times(init_times, durations):
    """Test initialisation with explicit end times."""

    end_times = init_times + durations

    init_intervals = []
    for i, init_time in enumerate(init_times):
        init_intervals.append(TimeInterval(init_times[i], end_times[i]))

    intervals = TimeIntervalList(init_intervals,
                                 start_valid=init_times[0],
                                 end_valid=end_times[-1])

    truth_txt = ("[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 ]\n"
                 "[ 2020-04-12T00:00:00.000  2020-04-12T00:20:00.000 ]\n"
                 "[ 2020-04-13T00:00:00.000  2020-04-13T00:30:00.000 ]\n")

    truth_validity = "[ 2020-04-11T00:00:00.000  2020-04-13T00:30:00.000 ]"

    assert truth_txt == str(intervals)
    assert truth_validity == str(intervals.validity_interval())
예제 #16
0
def _check_entry_exit_times(intervals, truth_intervals, start_diff, end_diff):
    """Checks the `intervals` against the `truth_intervals` with the given
    tolerances."""

    for i, truth_event_entry, truth_event_exit in truth_intervals:

        truth_event = TimeInterval(Time(truth_event_entry),
                                   Time(truth_event_exit))
        interval = intervals.get_interval(i)

        # print(
        #     i,
        #     (interval.start - truth_event.start).to(u.ms),
        #     (interval.end - truth_event.end).to(u.ms),
        # )

        assert (interval.start - truth_event.start).to_value(u.s) == approx(
            0.0, abs=start_diff.to_value(u.s))

        assert (interval.end - truth_event.end).to_value(u.s) == approx(
            0.0, abs=end_diff.to_value(u.s))
예제 #17
0
def test_interval_init_switched_err(init_times, durations):
    """Test `init` with switched init and end times - should raise `ValueError`."""
    with pytest.raises(ValueError):
        end_times = init_times + durations
        TimeInterval(end_times, init_times)
예제 #18
0
def test_interval_init_with_durations(init_times, durations):
    """Test initialisation with durations."""
    interval = TimeInterval(init_times, durations)

    truth_txt = "[ 2020-04-11T00:00:00.000  2020-04-11T00:10:00.000 ]"
    assert truth_txt == str(interval)
예제 #19
0
def test_multi_body_occultation_intervals():
    """Tests the umbra and penumbra intervals against GMAT, where the occulting
    body is Earth and Moon.

    Using a stepsize of 60 seconds gives more points to evaluate and increases the
    accuracy of the entry-exit times by a few milliseconds.

    The majority of the difference with respect to GMAT is likely due to apparent vs.
    geometric Sun - this is not very clear from GMAT documentation.
    """

    print_results = False

    # init timer
    begin = time.time()

    # Init trajectory
    pvt0 = _init_orbit_luna()

    # Set up propagation config
    stepsize = 180 * u.s
    prop_interval = TimeInterval(pvt0.obstime, 2.0 * u.day)

    # init propagator with defaults
    # run propagation and get trajectory
    trajectory = _init_trajectory(pvt0,
                                  stepsize,
                                  prop_interval,
                                  central_body=MOON)

    # end timer
    end = time.time()
    if print_results:
        print(f"Propagation and interpolations: {end - begin} seconds")

    # init timer
    begin = time.time()

    occult_bodies = [EARTH, MOON]

    # Compute occultation intervals
    output_dict = multi_body_occultation_intervals(trajectory,
                                                   occult_bodies,
                                                   illum_body=SUN,
                                                   ephemeris="jpl")

    # end timer
    end = time.time()
    if print_results:
        print(f"Intervals finding: {end - begin} seconds")

    umbra_intervals_earth, penumbra_intervals_earth = output_dict[EARTH.name]
    umbra_intervals_moon, penumbra_intervals_moon = output_dict[MOON.name]

    # ------------------- check umbra times -------------

    if print_results:
        print("Umbra Start-End Intervals - Earth:")
        print(umbra_intervals_earth)
        print("Umbra Start-End Intervals - Moon:")
        print(umbra_intervals_moon)

    # check Umbra params
    gmat_umbra_times_earth: List[Tuple] = []

    start_diff = 140 * u.ms
    end_diff = 80 * u.ms

    _check_entry_exit_times(umbra_intervals_earth, gmat_umbra_times_earth,
                            start_diff, end_diff)

    gmat_umbra_times_moon = [
        (0, "2020-01-10T11:52:56.832", "2020-01-10T12:09:45.608"),
        (2, "2020-01-11T03:52:40.660", "2020-01-11T04:10:24.428"),
        (5, "2020-01-12T03:52:17.623", "2020-01-12T04:11:21.276"),
    ]

    start_diff = 140 * u.ms
    end_diff = 80 * u.ms

    _check_entry_exit_times(umbra_intervals_moon, gmat_umbra_times_moon,
                            start_diff, end_diff)

    # ------------------- check penumbra times -------------

    if print_results:
        print("Penumbra Start-End Intervals - Earth:")
        print(penumbra_intervals_earth)
        print("Penumbra Start-End Intervals - Moon:")
        print(penumbra_intervals_moon)

    # check Penumbra params
    gmat_penumbra_times_moon = [
        (0, "2020-01-10T11:50:49.031", "2020-01-10T11:52:56.832"),
        (2, "2020-01-10T19:50:43.540", "2020-01-10T19:52:48.654"),
        (9, "2020-01-11T20:11:02.505", "2020-01-11T20:13:01.895"),
    ]

    start_diff = 65 * u.ms
    end_diff = 120 * u.ms

    _check_entry_exit_times(penumbra_intervals_moon, gmat_penumbra_times_moon,
                            start_diff, end_diff)

    gmat_penumbra_times_earth = [(0, "2020-01-10T18:41:49.529",
                                  "2020-01-10T20:26:28.746")]

    start_diff = 45 * u.s
    end_diff = 40 * u.s

    _check_entry_exit_times(penumbra_intervals_earth,
                            gmat_penumbra_times_earth, start_diff, end_diff)
예제 #20
0
def test_min_max_event():
    """Tests the min max event finding with apoapsis and periapsis."""

    time = Time("2020-01-11T11:00:00.000", scale="utc")
    central_body = EARTH

    sm_axis = 7056.0 * u.km
    ecc = 0.02 * u.dimensionless_unscaled
    incl = 0 * u.deg
    raan = 0 * u.deg
    arg_perigee = 90 * u.deg
    true_an = 20 * u.deg

    init_orb_elems = OsculatingKeplerianOrbElems(
        time, sm_axis, ecc, incl, raan, arg_perigee, true_an, central_body
    )

    # generate cartesian initial conditions
    init_pvt = init_orb_elems.to_cartesian()

    # Set up propagation config
    stepsize = 10 * u.s

    prop_start = init_pvt.obstime
    prop_duration = init_orb_elems.period

    # init propagator with defaults - run propagation and get trajectory
    trajectory = NumericalPropagator(stepsize).gen_trajectory(
        init_pvt, TimeInterval(prop_start, prop_duration)
    )

    # Extract search range
    time_list = trajectory.coord_list.obstime
    r_list = trajectory.coord_list.cartesian.without_differentials().norm()

    # Find time events
    events = DiscreteTimeEvents(time_list, r_list)

    # Min / Max Event times
    # print(events.max_min_table)

    # Cross-check with orbital elems
    # print("\nCheck with initial conditions:")
    # print(f"Apoapsis : {init_orb_elems.apoapsis}")
    # print(f"Periapsis: {init_orb_elems.periapsis}")

    assert (
        pytest.approx(
            (events.max_min_table[0]["value"] - init_orb_elems.apoapsis).to_value(u.mm),
            abs=(1e-3 * u.mm).to_value(u.mm),
        )
        == 0.0
    )

    assert (
        pytest.approx(
            (events.max_min_table[1]["value"] - init_orb_elems.periapsis).to_value(
                u.mm
            ),
            abs=(5e-3 * u.mm).to_value(u.mm),
        )
        == 0.0
    )

    assert events.max_min_table[0]["type"] == "max"
    assert events.max_min_table[1]["type"] == "min"
예제 #21
0
def test_interval_init_zero_dur_err(init_times):
    """Test `init` with equal init and end times - should return None."""

    interval = TimeInterval(init_times, init_times + 0.1 * _EPS_TIME)

    assert interval is None
예제 #22
0
# Copyright (C) 2020 Egemen Imre
#
# Licensed under GNU GPL v3.0. See LICENSE.rst for more info.
"""
Test `TimeInterval` class and associated methods and functionalities.

"""
import numpy as np
import pytest
from astropy.time import Time, TimeDelta

from satmad.utils.timeinterval import _EPS_TIME, TimeInterval, TimeIntervalList

before = TimeInterval(
    Time("2020-04-09T00:00:00", scale="utc"),
    Time("2020-04-11T00:00:00", scale="utc"),
    end_inclusive=False,
)
within = TimeInterval(
    Time("2020-04-11T00:05:00", scale="utc"),
    Time("2020-04-11T00:08:00", scale="utc"),
)
intersect = TimeInterval(
    Time("2020-04-10T00:00:00", scale="utc"),
    Time("2020-04-11T00:08:00", scale="utc"),
)
exact = TimeInterval(
    Time("2020-04-11T00:00:00", scale="utc"),
    Time("2020-04-11T00:10:00", scale="utc"),
)
after = TimeInterval(
예제 #23
0
def test_interval_init_zero_dur_err(init_times):
    """Test `init` with equal init and end times - should raise `ValueError`."""
    with pytest.raises(ValueError):
        TimeInterval(init_times, init_times + 0.1 * _EPS_TIME)
예제 #24
0
class DiscreteTimeEvents:
    """
    Discrete time events and intervals finding and storage class.

    An *time event* is described as a function value crossing a certain threshold in
    increasing (negative to positive) and decreasing (positive to negative) directions,
    defining a time interval.

    The class initialises interpolators to find where the `value_list` crosses the
    `crossing_value`, which is essentially a root finding problem.

    Parameters
    ----------
    time_list : Time
        List of time instances
    value_list : ndarray or list
        List of values corresponding to time instances (should be of the same size as
        `time_list`)
    crossing_value : float
        Crossing value for the `value_list`
    neg_to_pos_is_start : bool
        If `True` value turning from negative to positive marks a *start* event,
        otherwise marks an *end* event

    Raises
    ------
    ValueError
        `time_list` and `value_list` are of different sizes
    """

    def __init__(
        self, time_list, value_list, crossing_value=0, neg_to_pos_is_start=True
    ):

        # Check whether time_list and value_list size are equal
        if len(time_list) != len(value_list):
            raise ValueError(
                f"Time list and value list are of different sizes. "
                f"({len(time_list)} vs. ({len(value_list)}))"
            )

        self.search_interval = TimeInterval(time_list[0], time_list[-1])

        # Convert time to "days since epoch"
        t_float_list = (time_list - time_list[0]).jd

        # Init interpolators (for splines, root finding possible for 3rd degree
        # (cubics) only)
        self._interpolator = interpolate.CubicSpline(
            t_float_list, value_list - crossing_value
        )

        self._deriv_interpolator = self._interpolator.derivative()

        # Find start / end intervals
        self.start_end_intervals = self._find_intervals(time_list, neg_to_pos_is_start)

        # Find max / min events
        self.max_min_table = self._find_extrema_events(time_list[0])

        # Return the table to absolute values
        self.max_min_table["value"] = self.max_min_table["value"] + crossing_value

    def _find_intervals(self, time_list, neg_to_pos_is_start):
        """
        Finds the start / end intervals within the time list.

        Parameters
        ----------
        time_list : Time
            List of time instances
        neg_to_pos_is_start : bool
            If `True` value turning from negative to positive marks a *start* event,
            otherwise marks an *end* event

        Returns
        -------
        intervals : TimeIntervalList
            Time intervals marked with start and end events
        """

        init_time = time_list[0]
        end_time = time_list[-1]

        # *** Find start / end events (do not extrapolate to find roots) ***
        # No roots mean no events during this time
        start_end_events = self._interpolator.roots(extrapolate=False)

        intervals = []
        if start_end_events.size != 0:
            # there should be some roots within the search range to check for intervals

            # classify start / end events and fill the timetable
            events = self._classify_start_end_events(
                init_time, start_end_events, neg_to_pos_is_start
            )

            # Put events into intervals
            event_start = init_time

            for event in events:
                # ignore events outside the search interval
                if self.search_interval.is_in_interval(event["time"]):
                    if event["type"] == "start":
                        event_start = event["time"]
                    else:
                        intervals.append(TimeInterval(event_start, event["time"]))

            # check for the last event - force end time if it is out of search bounds
            if events[-1]["type"] == "end":
                if self.search_interval.is_in_interval(events[-1]["time"]):
                    intervals.append(TimeInterval(event_start, events[-1]["time"]))
                else:
                    intervals.append(
                        TimeInterval(event_start, self.search_interval.end)
                    )
            else:
                intervals.append(TimeInterval(event_start, self.search_interval.end))

        # If interval list is empty by this stage, either the complete duration is
        # is a valid interval with no event (e.g. a continuously visible satellite)
        # or the complete duration is an invalid interval with no event (e.g. a
        # satellite continuously outside visibility)
        if not intervals:
            mid_time = 0.5 * (self.search_interval.end - self.search_interval.start)
            value = self._interpolator(mid_time.to_value(u.day))

            if neg_to_pos_is_start:
                if value > 0:
                    # event already started
                    intervals.append(
                        TimeInterval(
                            self.search_interval.start, self.search_interval.end
                        )
                    )
            else:
                if value < 0:
                    # event already started
                    intervals.append(
                        TimeInterval(
                            self.search_interval.start, self.search_interval.end
                        )
                    )

        # generate the list of time intervals
        return TimeIntervalList(intervals, start_valid=init_time, end_valid=end_time)

    def _classify_start_end_events(
        self, init_time, start_end_events, neg_to_pos_is_start
    ):
        """
        Classify the list of events into a timetable of start and end events.

        Parameters
        ----------
        init_time : Time
            Initial time
        start_end_events : ndarray
            Array of start and end event times in days, starting from `init_time`
        neg_to_pos_is_start : bool
            If `True` value turning from negative to positive marks a *start* event,
            otherwise marks an *end* event

        Returns
        -------
        events_table : TimeSeries
            Timetable of start and end events
        """

        # loop through each root and classify them as start / end events
        events_list = []
        for event_time in start_end_events:

            deriv = self._deriv_interpolator(event_time)

            if neg_to_pos_is_start:
                if deriv > 0:
                    events_list.append("start")
                else:
                    events_list.append("end")
            else:
                if deriv < 0:
                    events_list.append("start")
                else:
                    events_list.append("end")

        # init events table
        events_table = TimeSeries(
            time=init_time + start_end_events * u.day, data={"type": events_list}
        )

        return events_table

    def _find_extrema_events(self, init_time):
        """
        Finds the extrema (max/min) events, contained within the time intervals.

        Parameters
        ----------
        init_time : Time
            Initial time

        Returns
        -------
        max_min_table : TimeSeries
            Timetable of max and min events

        """

        # *** Find max / min events ***
        min_max_events = self._deriv_interpolator.roots()

        # loop through each root and classify them as max / min events
        events_list = []
        value_list = []
        for event_time in min_max_events:
            value = self._interpolator(event_time)
            value_list.append(value)
            if value > 0:
                events_list.append("max")
            else:
                events_list.append("min")

        # init events table
        max_min_table = TimeSeries(
            time=init_time + min_max_events * u.day,
            data={"type": events_list, "value": value_list},
        )

        # Filter the events to those within the intervals
        i = 0
        remove_indexes = []
        for event_row in max_min_table:
            event_within_interval = self.start_end_intervals.is_in_interval(
                event_row["time"]
            )
            if not event_within_interval:
                remove_indexes.append(i)
            i = i + 1

        # Remove the ones outside the intervals
        max_min_table.remove_rows(remove_indexes)

        return max_min_table
예제 #25
0
    def _find_intervals(self, time_list, neg_to_pos_is_start):
        """
        Finds the start / end intervals within the time list.

        Parameters
        ----------
        time_list : Time
            List of time instances
        neg_to_pos_is_start : bool
            If `True` value turning from negative to positive marks a *start* event,
            otherwise marks an *end* event

        Returns
        -------
        intervals : TimeIntervalList
            Time intervals marked with start and end events
        """

        init_time = time_list[0]
        end_time = time_list[-1]

        # *** Find start / end events (do not extrapolate to find roots) ***
        # No roots mean no events during this time
        start_end_events = self._interpolator.roots(extrapolate=False)

        intervals = []
        if start_end_events.size != 0:
            # there should be some roots within the search range to check for intervals

            # classify start / end events and fill the timetable
            events = self._classify_start_end_events(
                init_time, start_end_events, neg_to_pos_is_start
            )

            # Put events into intervals
            event_start = init_time

            for event in events:
                # ignore events outside the search interval
                if self.search_interval.is_in_interval(event["time"]):
                    if event["type"] == "start":
                        event_start = event["time"]
                    else:
                        intervals.append(TimeInterval(event_start, event["time"]))

            # check for the last event - force end time if it is out of search bounds
            if events[-1]["type"] == "end":
                if self.search_interval.is_in_interval(events[-1]["time"]):
                    intervals.append(TimeInterval(event_start, events[-1]["time"]))
                else:
                    intervals.append(
                        TimeInterval(event_start, self.search_interval.end)
                    )
            else:
                intervals.append(TimeInterval(event_start, self.search_interval.end))

        # If interval list is empty by this stage, either the complete duration is
        # is a valid interval with no event (e.g. a continuously visible satellite)
        # or the complete duration is an invalid interval with no event (e.g. a
        # satellite continuously outside visibility)
        if not intervals:
            mid_time = 0.5 * (self.search_interval.end - self.search_interval.start)
            value = self._interpolator(mid_time.to_value(u.day))

            if neg_to_pos_is_start:
                if value > 0:
                    # event already started
                    intervals.append(
                        TimeInterval(
                            self.search_interval.start, self.search_interval.end
                        )
                    )
            else:
                if value < 0:
                    # event already started
                    intervals.append(
                        TimeInterval(
                            self.search_interval.start, self.search_interval.end
                        )
                    )

        # generate the list of time intervals
        return TimeIntervalList(intervals, start_valid=init_time, end_valid=end_time)
예제 #26
0
def test_leo_occultation_intervals():
    """Tests the umbra and penumbra intervals against GMAT, where the occulting
    body is Earth only.

    Using a stepsize of 60 seconds gives more points to evaluate and increases the
    accuracy of the entry-exit times by a few milliseconds.

    The majority of the difference with respect to GMAT is likely due to apparent vs.
    geometric Sun - this is not very clear from GMAT documentation.
    """

    print_results = False

    # init timer
    begin = time.time()

    # Init trajectory
    pvt0 = _init_orbit_leo()

    # Set up propagation config
    stepsize = 120 * u.s
    prop_interval = TimeInterval(pvt0.obstime, 2.0 * u.day)

    # init propagator with defaults
    # run propagation and get trajectory
    trajectory = _init_trajectory(pvt0, stepsize, prop_interval)

    # end timer
    end = time.time()
    if print_results:
        print(f"Propagation and interpolations: {end - begin} seconds")

    # init timer
    begin = time.time()

    occulting_body = EARTH

    # Compute occultation intervals
    umbra_intervals, penumbra_intervals = occultation_intervals(
        trajectory, occulting_body, illum_body=SUN, ephemeris="jpl")

    # # plot umbra params if required
    # from satmad.plots.basic_plots import plot_time_param
    #
    # plot_time_param(time_list, umbra_params)

    # end timer
    end = time.time()
    if print_results:
        print(f"Intervals finding: {end - begin} seconds")

    # ------------------- check umbra times -------------
    if print_results:
        print("Umbra Start-End Intervals:")
        print(umbra_intervals)

    # check Umbra params
    gmat_umbra_times = [
        (0, "2020-01-01T11:43:39.068", "2020-01-01T12:16:44.181"),
        (2, "2020-01-01T15:05:58.227", "2020-01-01T15:39:04.946"),
        (6, "2020-01-01T21:50:36.564", "2020-01-01T22:23:46.456"),
        (15, "2020-01-02T13:01:02.907", "2020-01-02T13:34:19.753"),
        (22, "2020-01-03T00:49:10.141", "2020-01-03T01:22:32.221"),
    ]

    start_diff = 16 * u.ms
    end_diff = 50 * u.ms

    _check_entry_exit_times(umbra_intervals, gmat_umbra_times, start_diff,
                            end_diff)

    # ------------------- check penumbra times -------------
    if print_results:
        print("Penumbra Start-End Intervals:")
        print(penumbra_intervals)

    # check Penumbra params
    gmat_penumbra_times = [
        (0, "2020-01-01T11:43:27.775", "2020-01-01T11:43:39.068"),
        (6, "2020-01-01T16:46:56.547", "2020-01-01T16:47:07.809"),
        (18, "2020-01-02T02:53:54.129", "2020-01-02T02:54:05.332"),
        (36, "2020-01-02T18:04:20.596", "2020-01-02T18:04:31.713"),
        (44, "2020-01-03T00:48:59.061", "2020-01-03T00:49:10.141"),
    ]

    start_diff = 73 * u.ms
    end_diff = 16 * u.ms

    _check_entry_exit_times(penumbra_intervals, gmat_penumbra_times,
                            start_diff, end_diff)