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 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)
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))
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
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
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())
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
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
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)
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))
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)
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
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
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)
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())
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))
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)
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)
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)
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"
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
# 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(
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)
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
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 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)