Пример #1
0
    def test_overlapping_entire_segment(self):
        """ Test that a segment overlapping the entire time period of the segment does not
        get added to the DB.
        """
        sat = Satellite(platform_name="TEST")
        sat.save()
        DB.session.commit()

        orbit_data = []
        # time, posx, posy, posz, velx, vely, velz
        segment_start = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
        segment_end = [1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
        orbit_tuple = OrbitPoint(segment_start[0], segment_start[1:4],
                                 segment_start[4:7])
        orbit_data.append(orbit_tuple)
        orbit_tuple = OrbitPoint(segment_end[0], segment_end[1:4],
                                 segment_end[4:7])
        orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)

        # try to add a segment overlapping the start time of the above segment
        orbit_data = []
        # time, posx, posy, posz, velx, vely, velz
        segment_start = [0.3, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
        segment_end = [1.2, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
        orbit_tuple = OrbitPoint(segment_start[0], segment_start[1:4],
                                 segment_start[4:7])
        orbit_data.append(orbit_tuple)
        orbit_tuple = OrbitPoint(segment_end[0], segment_end[1:4],
                                 segment_end[4:7])
        orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)
        self.assertTrue(len(OrbitSegment.query.all()) == 1)
Пример #2
0
    def test_duplicate_segments(self):
        """ Test that a duplicated segment for a satellite does not get added to the DB
        """
        sat = Satellite(platform_name="TEST")
        sat.save()
        DB.session.commit()

        orbit_data = []
        for i in range(0, 20):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)

        # try to add the same segment again
        orbit_data = []
        for i in range(0, 20):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)
        self.assertTrue(len(OrbitSegment.query.all()) == 1)
Пример #3
0
    def test_reduce_poi_with_access_file(self, test_data):
        """Test reduce_poi with access file"""

        access_file, interval, error_threshold = test_data
        interval = TimeInterval(*interval)

        # Get access information
        access_info = self.parse_access_file(access_file)
        trimmed_accesses = time_intervals.trim_poi_segments(
            access_info.accesses, interval)

        # Gather data for every 24 hour period of the input interval
        q_mag = Satellite.get_by_name(access_info.sat_name)[0].maximum_altitude
        sat_platform_id = Satellite.get_by_name(
            access_info.sat_name)[0].platform_id
        sat_irp = Interpolator(sat_platform_id)

        poi_list = [
            TimeInterval(start, start + 24 * 60 * 60)
            for start in range(interval[0], interval[1], 24 * 60 * 60)
        ]

        sampling_time_list = [time.start for time in poi_list]
        sampling_time_list.append(interval[1])

        sat_pos_ecef_list, sat_vel_ecef_list = map(
            list, zip(*[sat_irp.interpolate(t) for t in sampling_time_list]))
        sat_position_velocity_pairs = ecef_to_eci(
            np.transpose(np.asarray(sat_pos_ecef_list)),
            np.transpose(np.asarray(sat_vel_ecef_list)), sampling_time_list)

        # Run viewing cone
        reduced_poi_list = [
            reduced_poi for idx, poi in enumerate(poi_list)
            for reduced_poi in view_cone.reduce_poi(
                access_info.target, sat_position_velocity_pairs[idx:idx +
                                                                2], q_mag, poi)
        ]

        reduced_poi_list = time_intervals.fuse_neighbor_intervals(
            reduced_poi_list)

        # Check coverage
        for poi in reduced_poi_list:
            trimmed_accesses = filter(
                lambda access: not (
                    (poi.start - error_threshold < access.start) and
                    (poi.end + error_threshold > access.end)),
                trimmed_accesses)

        if trimmed_accesses:
            print("Remaining accesses: ", trimmed_accesses)
            raise Exception("Some accesses are not covered")
Пример #4
0
    def __init__(self, platform_id):
        # check that the platform_id refers to a known satellite
        if not Satellite.get_by_id(platform_id):
            raise ValueError(
                "platform_id does not exist: {}".format(platform_id))

        self.platform_id = platform_id
        self.segment_times = {}  # segment_id : list of times
        self.segment_positions = {}  # segment_id : list of positions
        self.segment_velocities = {}  # segment_id : list of velocities
        self.segments = Satellite.get_by_id(
            platform_id).orbit_segments  # Store orbit segments
Пример #5
0
    def setUpClass(cls):
        super(TestInterpolator, cls).setUpClass()

        # create a fake satellite
        cls.platform_id = 1
        satellite = Satellite(platform_id=cls.platform_id,
                              platform_name="testsat")
        satellite.save()
        DB.session.commit()

        # add a segment for this satellite
        cls.segment1 = OrbitSegment(platform_id=cls.platform_id,
                                    start_time=1.,
                                    end_time=3.)
        cls.segment1.save()
        DB.session.commit()

        # simple segment data
        for record in range(1, 4):
            t = float(record)
            orbit_record = OrbitRecord(segment_id=cls.segment1.segment_id,
                                       platform_id=cls.platform_id,
                                       time=t,
                                       position=(t, t, t),
                                       velocity=(t, t, t))
            orbit_record.save()

        # another segment
        cls.segment2 = OrbitSegment(platform_id=cls.platform_id,
                                    start_time=4.,
                                    end_time=10.)
        cls.segment2.save()
        DB.session.commit()

        # more complex data this time
        times = np.arange(4, 11)
        positions = np.column_stack(
            (np.sin(times), np.sin(times), np.sin(times)))
        velocities = np.column_stack(
            (np.exp(0.2 * times), np.exp(0.2 * times), np.exp(0.2 * times)))
        for i in range(times.size):
            orbit_record = OrbitRecord(segment_id=cls.segment2.segment_id,
                                       platform_id=cls.platform_id,
                                       time=times[i],
                                       position=tuple(positions[i]),
                                       velocity=tuple(velocities[i]))
            orbit_record.save()

        DB.session.commit()
Пример #6
0
    def test_db_add_correct_num_rows(self):
        """Test that the add_segment_to_db adds the correct number of rows to the DB.  """
        sat = Satellite(platform_name="TEST")
        sat.save()
        DB.session.commit()

        orbit_data = []
        for i in range(0, 20):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)
        self.assertTrue(len(OrbitRecord.query.all()) == 20)
Пример #7
0
    def test_full_visibility(self, test_data):
        """Tests that the visibility finder produces the same results as the access file.

        Args:
            test_data (tuple): A three tuple containing the:
                                1 - The path of KAOS access test file
                                2 - A tuple of the desired test duration
                                3 - The maximum tolerated deviation in seconds
        Note:
            Ranges provided must not start or end at an access boundary. This is a result of the
            strict checking method provided.
        """
        access_file, interval, max_error = test_data

        access_info = self.parse_access_file(access_file)
        finder = VisibilityFinder(
            Satellite.get_by_name(access_info.sat_name)[0].platform_id,
            access_info.target, interval)
        access_times = finder.determine_visibility()

        for predicted_access in access_times:
            found = False
            for actual_access in access_info.accesses:
                if (interval[0] > actual_access[0]) and (interval[1] <
                                                         actual_access[1]):
                    if ((abs(interval[0] - predicted_access[0]) < max_error)
                            and
                        (abs(interval[1] - predicted_access[1]) < max_error)):
                        found = True
                        break
                if interval[0] > actual_access[0]:
                    if ((abs(interval[0] - predicted_access[0]) < max_error)
                            and (abs(actual_access[1] - predicted_access[1]) <
                                 max_error)):
                        found = True
                        break
                elif interval[1] < actual_access[1]:
                    if ((abs(actual_access[0] - predicted_access[0]) <
                         max_error) and
                        (abs(interval[1] - predicted_access[1]) < max_error)):
                        found = True
                        break

                if ((abs(actual_access[0] - predicted_access[0]) < max_error)
                        and
                    (abs(actual_access[1] - predicted_access[1]) < max_error)):
                    found = True
                    break

            if not found:
                raise Exception('Wrong access: {}'.format(predicted_access))
Пример #8
0
    def test_single_segment_min_max(self):
        """Test that a single orbital segment has the correct
        start and end time."""
        sat = Satellite(platform_name="TEST")
        sat.save()
        DB.session.commit()

        start = 0.0
        end = 1000.0

        orbit_segment = OrbitSegment(platform_id=sat.platform_id,
                                     start_time=start,
                                     end_time=end)
        orbit_segment.save()
        DB.session.commit()

        query_min = orbit_segment.query.filter(
            orbit_segment.start_time == start).all()
        query_max = orbit_segment.query.filter(
            orbit_segment.end_time == end).all()

        for q_min, q_max in zip(query_min, query_max):
            self.assertTrue(q_min.start_time == start)
            self.assertTrue(q_max.end_time == end)
Пример #9
0
    def test_db_add_correct_orbit_data(self):
        """Test that the add_segment_to_db adds the correct row data to the DB. Validates time,
        position, and velocity for each DB row added."""
        sat = Satellite(platform_name="TEST")
        sat.save()
        DB.session.commit()

        orbit_data = []
        for i in range(0, 20):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)

        orbit_db = OrbitRecord()
        self.assertTrue(len(orbit_db.query.all()) == 20)

        orbits = orbit_db.query.all()
        for orbit_point, orbit in zip(orbit_data, orbits):
            self.assertTrue(orbit_point.time == orbit.time)
            self.assertTrue(orbit_point.pos == orbit.position)
            self.assertTrue(orbit_point.vel == orbit.velocity)
Пример #10
0
    def test_db_add_num_segments(self):
        """Test that the add_segment_to_db adds the correct number of "segments" to the db. Each
        call to add_segment_to_db should create only one segment at a time.  """

        # create and add first segment to DB
        sat = Satellite(platform_name="TEST1")
        sat.save()
        DB.session.commit()

        orbit_data = []
        for i in range(0, 20):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat.platform_id)

        self.assertTrue(len(OrbitSegment.query.all()) == 1)

        # create and add second segment
        sat2 = Satellite(platform_name="TEST2")
        sat2.save()
        DB.session.commit()

        orbit_data = []
        for i in range(40, 60):
            orbit_point = [float(j) for j in range(i, i + 7)]
            orbit_tuple = OrbitPoint(orbit_point[0], orbit_point[1:4],
                                     orbit_point[4:7])
            orbit_data.append(orbit_tuple)

        add_segment_to_db(orbit_data, sat2.platform_id)

        # make sure we have two distincts segments in the DB
        self.assertTrue(len(OrbitSegment.query.all()) == 2)
Пример #11
0
def parse_ephemeris_file(filename):
    """Parse the given ephemeris file and store the orbital data in OrbitRecords. We assume that
    each row in the ephemeris file is a 7-tuple containing an orbital point, formatted as:

    time posx posy posz velx vely velz

    Calculate the maximum distance from earth center to the position if the satellite. Insert it
    in Satellite.
    """
    sat_name = os.path.splitext(os.path.basename(filename))[0].split('.')[0]
    existing_sat = Satellite.get_by_name(sat_name)
    if not existing_sat:
        sat = Satellite(platform_name=sat_name)
        sat.save()
        DB.session.commit()
        sat_id = -1
    else:
        sat = existing_sat[0]

    max_distance = 0

    with open(filename, "rU") as f:
        segment_boundaries = []
        segment_tuples = []
        start_time = float(0)

        read_segment_boundaries = False
        read_orbital_data = False

        # Remember the last seen segment boundary while reading the ephemeris rows. Needed to
        # differentiate between the beginning of a new segment and the end of en existing segment of
        # data
        last_seen_segment_boundary = 0

        for line in f:
            line = line.rstrip('\n')
            if "Epoch in JDate format:" in line:
                start_time = float(line.split(':')[1])
                start_time = jdate_to_unix(start_time)
                last_seen_segment_boundary = start_time

            # For now, we assume that the coord system will always be J2000
            # if "CoordinateSystem" in line:
            #     coord_system = str(line.split()[1])

            if "END SegmentBoundaryTimes" in line:
                read_segment_boundaries = False

            if read_segment_boundaries:
                line = line.strip()
                if line:
                    segment_boundaries.append(start_time + float(line))

            if "BEGIN SegmentBoundaryTimes" in line:
                read_segment_boundaries = True

            if "END Ephemeris" in line:
                add_segment_to_db(segment_tuples, sat.platform_id)
                read_orbital_data = False

            if read_orbital_data:
                line = line.strip()
                if line:
                    ephemeris_row = [float(num) for num in line.split()]
                    orbit_tuple = OrbitPoint(start_time + ephemeris_row[0],
                                             ephemeris_row[1:4],
                                             ephemeris_row[4:7])
                    segment_tuples.append(orbit_tuple)

                    # Keep track of the magnitude of the position vector and update with a bigger
                    # value
                    max_distance = max(max_distance,
                                       np.linalg.norm(ephemeris_row[1:4]))

                    # The line we just read is a segment boundary, So first check that this is the
                    # *end* of a segment, not the beginning of a new one, and then add this segment
                    # to the db.
                    if (orbit_tuple.time in segment_boundaries and
                            last_seen_segment_boundary != orbit_tuple.time):
                        last_seen_segment_boundary = orbit_tuple.time
                        sat_id = add_segment_to_db(segment_tuples,
                                                   sat.platform_id)
                        segment_tuples = []

            if "EphemerisTimePosVel" in line:
                read_orbital_data = True

            # After getting the q_max, insert it into Satellite"""
            sat.maximum_altitude = max_distance
            sat.save()
        DB.session.commit()
        return sat_id
Пример #12
0
    def test_visibility(self, test_data):
        """Tests that the visibility finder produces the same results as the access file.

        Args:
            test_data (tuple): A three tuple containing the:
                                1 - The path of KAOS access test file
                                2 - A tuple of the desired test duration
                                3 - The maximum tolerated deviation in seconds
        Note:
            Ranges provided must not start or end at an access boundary. This is a result of the
            strict checking method provided.
        """
        access_file, interval, max_error = test_data

        posix_interval = (utc_to_unix(interval[0]), utc_to_unix(interval[1]))
        access_info = self.parse_access_file(access_file, posix_interval)
        satellite_id = Satellite.get_by_name(
            access_info.sat_name)[0].platform_id

        request = {
            'Target': access_info.target,
            'POI': {
                'startTime': interval[0],
                'endTime': interval[1]
            },
            'PlatformID': [satellite_id]
        }

        with self.app.test_client() as client:
            response = client.post('/visibility/search', json=request)

        self.assertTrue(response.is_json)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.json['Opportunities']),
                         len(access_info.accesses))

        for oppertunity in response.json['Opportunities']:
            self.assertEqual(satellite_id, oppertunity['PlatformID'])
            predicted_access = (oppertunity['start_time'],
                                oppertunity['end_time'])
            interval = posix_interval
            found = False
            for actual_access in access_info.accesses:
                if (interval[0] > actual_access[0]) and (interval[1] <
                                                         actual_access[1]):
                    if ((abs(interval[0] - predicted_access[0]) < max_error)
                            and
                        (abs(interval[1] - predicted_access[1]) < max_error)):
                        found = True
                        break
                if interval[0] > actual_access[0]:
                    if ((abs(interval[0] - predicted_access[0]) < max_error)
                            and (abs(actual_access[1] - predicted_access[1]) <
                                 max_error)):
                        found = True
                        break
                elif interval[1] < actual_access[1]:
                    if ((abs(actual_access[0] - predicted_access[0]) <
                         max_error) and
                        (abs(interval[1] - predicted_access[1]) < max_error)):
                        found = True
                        break

                if ((abs(actual_access[0] - predicted_access[0]) < max_error)
                        and
                    (abs(actual_access[1] - predicted_access[1]) < max_error)):
                    found = True
                    break

            if not found:
                raise Exception('Wrong access: {}'.format(predicted_access))
Пример #13
0
    def interpolate(self, timestamp, kind="linear"):
        """Estimate the position and velocity of the satellite at a given time.

        Args:
            timestamp: The time for which to get the estimated position and velocity, in Unix
                epoch seconds.
            kind: The type of interpolation to do. This defaults to "linear." Alternatives
                include "quadratic", "cubic", etc. See scipy.interpolate.

        Return:
            A tuple (pos, vel). Each of pos, vel is a 3-tuple representing the vector
            components of the position and velocity, respectively.

        Raise:
            ValueError if interpolation could not be performed for the given timestamp.
        """
        # find the correct segment from stored data
        segment = next(
            (segment
             for segment in self.segments if segment.start_time <= timestamp
             and segment.end_time >= timestamp), None)

        if not segment:
            # segment was not in stored data, ask DB
            segment = OrbitSegment.get_by_platform_and_time(
                self.platform_id, timestamp)

            if not segment:
                raise InterpolationError("No segment found: {}, {}".format(
                    self.platform_id, timestamp))
            else:
                # update stored segments
                self.segments = Satellite.get_by_id(
                    self.platform_id).orbit_segments

        # get orbit records for the segment
        segment_id = segment.segment_id
        if segment_id not in self.segment_times:
            records = OrbitRecord.get_by_segment(segment_id)

            # don't bother to proceed if there are too few records for an interpolation
            if not records or len(records) < 2:
                raise InterpolationError(
                    "No orbit records found: {}, {}".format(
                        self.platform_id, segment_id))

            # extract and cache time, position, velocity
            self.segment_times[segment_id] = np.array(
                [rec.time for rec in records])
            self.segment_positions[segment_id] = np.array(
                [np.array(rec.position) for rec in records])
            self.segment_velocities[segment_id] = np.array(
                [np.array(rec.velocity) for rec in records])

        # interpolate the position and velocity
        position = Interpolator.vector_interp(
            self.segment_times[segment_id],
            self.segment_positions[segment_id], [timestamp],
            kind=kind)[0]
        velocity = Interpolator.vector_interp(
            self.segment_times[segment_id],
            self.segment_velocities[segment_id], [timestamp],
            kind=kind)[0]

        return tuple(position), tuple(velocity)
Пример #14
0
    def test_visibility_perf(self, test_data):
        """Tests that the visibility finder produces the same results as the access file and prints
        accuracy and performance measurements at the end.
        This test is meant to be run manually (not as part of the automated CI tests).

        Use: pytest -s test/algorithm/perf_visibiliry_finder.py

        Args:
            test_data (tuple): A three tuple containing the:
                                1 - The path of KAOS access test file
                                2 - A tuple of the desired test duration
        """
        # Use viewing cone algorithm or not
        use_view_cone = True
        # Do viewing cone coordinate conversion in series or as a vector
        vectorized = True

        access_file, interval = test_data
        interval = TimeInterval(interval[0], interval[1])
        access_info = self.parse_access_file(access_file)
        sat_id = Satellite.get_by_name(access_info.sat_name)[0].platform_id
        max_q = Satellite.get_by_name(access_info.sat_name)[0].maximum_altitude
        sat_irp = Interpolator(sat_id)
        print("\n")

        if use_view_cone is False:
            finder = VisibilityFinder(sat_id, access_info.target, interval)
            # NOTE: change to burte_force=True to switch to brute-force method.
            access_times = np.asarray(
                finder.profile_determine_visibility(brute_force=False))
        else:
            import cProfile
            import pstats
            import sys
            profile = cProfile.Profile()
            profile.enable()

            # Vectorized
            if vectorized is True:
                poi_list = [
                    TimeInterval(start, start + 24 * 60 * 60)
                    for start in range(interval[0], interval[1], 24 * 60 * 60)
                ]

                sampling_time_list = [time.start for time in poi_list]
                sampling_time_list.append(interval[1])
                sat_pos_ecef_list, sat_vel_ecef_list = map(
                    list,
                    zip(*[sat_irp.interpolate(t) for t in sampling_time_list]))

                sat_position_velocity_pairs = ecef_to_eci(
                    np.transpose(np.asarray(sat_pos_ecef_list)),
                    np.transpose(np.asarray(sat_vel_ecef_list)),
                    sampling_time_list)

                reduced_poi_list = [
                    reduced_poi for idx, poi in enumerate(poi_list)
                    for reduced_poi in view_cone.reduce_poi(
                        access_info.target,
                        sat_position_velocity_pairs[idx:idx + 2], max_q, poi)
                ]
            # NON vectorized
            else:
                reduced_poi_list = []
                for start_time in range(interval[0], interval[1],
                                        24 * 60 * 60):
                    poi = TimeInterval(start_time, start_time + 24 * 60 * 60)
                    # get sat at start_time
                    sat_pos_ecef, sat_vel_ecef = sat_irp.interpolate(
                        start_time + 12 * 60 * 60)
                    sat_pos, sat_vel = ecef_to_eci(np.asarray(sat_pos_ecef),
                                                   np.asarray(sat_vel_ecef),
                                                   start_time + 12 * 60 * 60)

                    reduced_poi_list.extend(
                        view_cone.reduce_poi(access_info.target, sat_pos[0],
                                             sat_vel[0], max_q, poi))

            trimmed_reduced_poi_list = interval_utils.fuse_neighbor_intervals(
                reduced_poi_list)

            access_times = []
            for reduced_poi in trimmed_reduced_poi_list:
                finder = VisibilityFinder(sat_id, access_info.target,
                                          reduced_poi)
                access_times.extend(np.asarray(finder.determine_visibility()))

            profile.disable()
            stats = pstats.Stats(profile, stream=sys.stdout)
            stats.strip_dirs().sort_stats('cumtime').print_stats(50)

            reduced_time = 0
            for time in trimmed_reduced_poi_list:
                reduced_time += time[1] - time[0]
            print("Viewing cone stats:")
            print("Reduced time is: {}".format(mp.nstr(reduced_time, 12)))
            print("Input   time is: {}".format(interval[1] - interval[0]))

        interval = TimeInterval(*interval)
        expected_accesses = interval_utils.trim_poi_segments(
            access_info.accesses, interval)

        # Check the visibility times
        fail = False
        total_error = 0
        print(
            "=============================== visibility report ==============================="
        )
        for exp_start, exp_end in expected_accesses:
            idx = (np.abs(np.transpose(access_times)[0] - exp_start)).argmin()
            error = abs(exp_start -
                        access_times[idx][0]) + abs(exp_end -
                                                    access_times[idx][1])
            if error > 600:
                fail = True
                print("start, {}, ------------, {}".format(
                    exp_start, mp.nstr(access_times[idx][0] - exp_start, 6)))
                print("end,   {}, ------------, {}".format(
                    exp_end, mp.nstr(access_times[idx][1] - exp_end, 6)))
            else:
                print("start, {}, {}, {}".format(
                    exp_start, mp.nstr(access_times[idx][0], 11),
                    mp.nstr(access_times[idx][0] - exp_start, 6)))
                print("end  , {}, {}, {}".format(
                    exp_end, mp.nstr(access_times[idx][1], 11),
                    mp.nstr(access_times[idx][1] - exp_end, 6)))
                total_error += error
                access_times = np.delete(access_times, idx, axis=0)

        print("\nTotal Error: {}".format(mp.nstr(total_error, 12)))
        print("Average Error: {}".format(
            mp.nstr(total_error / (len(expected_accesses) * 2))))
        if fail:
            raise Exception(
                "Missing accesses. Unmatched: {}".format(access_times))
        if access_times.size > 0:
            raise Exception(
                "Extra accesses. Unmatched: {}".format(access_times))