def test_check_exists(): """Tests for check_exists""" sim_pros_mock = mock.Mock() callsign = types.Callsign("FAKE") # Test error handling sim_pros_mock.aircraft.exists.return_value = "Error" with api.FLASK_APP.test_request_context(): resp = utils.check_exists(sim_pros_mock, callsign) assert isinstance(resp, Response) assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR assert resp.data.decode( ) == "Could not check if the aircraft exists: Error" # Test missing callsign sim_pros_mock.aircraft.exists.return_value = False with api.FLASK_APP.test_request_context(): resp = utils.check_exists(sim_pros_mock, callsign) assert isinstance(resp, Response) assert resp.status_code == HTTPStatus.BAD_REQUEST assert resp.data.decode() == 'Aircraft "FAKE" does not exist' callsign = types.Callsign("TEST") # Test valid callsign sim_pros_mock.aircraft.exists.return_value = True with api.FLASK_APP.test_request_context(): resp = utils.check_exists(sim_pros_mock, callsign) assert not resp
def pairwise_separation_metric(*args, **kwargs): """ The Aviary aircraft separation metric function. Expected *args are: callsign1: str, callsign2: str See: https://github.com/alan-turing-institute/aviary/blob/develop/aviary/metrics/separation_metric.py # noqa: E501 """ assert len(args) == 2 and all(isinstance(x, str) for x in args), "Expected 2 string arguments" aircraft_controls: ProxyAircraftControls = kwargs["aircraft_controls"] props1 = aircraft_controls.properties(types.Callsign(args[0])) if not isinstance(props1, props.AircraftProperties): err_resp = f": {props1}" if props1 else "" raise ValueError(f"Could not get properties for {args[0]}{err_resp}") props2 = aircraft_controls.properties(types.Callsign(args[1])) if not isinstance(props2, props.AircraftProperties): err_resp = f": {props2}" if props2 else "" raise ValueError(f"Could not get properties for {args[1]}{err_resp}") return aviary_metrics.pairwise_separation_metric( lon1=props1.position.lon_degrees, lat1=props1.position.lat_degrees, alt1=props1.altitude.meters, lon2=props2.position.lon_degrees, lat2=props2.position.lat_degrees, alt2=props2.altitude.meters, )
def test_create(scenario_test_data): """Tests that ProxyAircraftControls implements create""" mock_aircraft_controls = mock.Mock() proxy_aircraft_controls = ProxyAircraftControls(mock_aircraft_controls) # Test error when callsign exists _, sim_data = scenario_test_data test_callsign = list(sim_data)[0] proxy_aircraft_controls.set_initial_properties(_TEST_SECTOR_ELEMENT, TEST_SCENARIO) err = proxy_aircraft_controls.create(test_callsign, None, None, None, None, None) assert err == "Aircraft already exists" # Test error from create mock_create = mock.Mock(return_value="Sim error (create)") mock_aircraft_controls.create = mock_create new_callsign = types.Callsign("NEW") err = proxy_aircraft_controls.create(new_callsign, None, None, None, None, None) assert err == "Sim error (create)" # Test error when checking for sim data for new aircraft all_properties_mock = mock.PropertyMock( return_value="Sim error (all_properties)") type(mock_aircraft_controls).all_properties = all_properties_mock mock_create.return_value = None err = proxy_aircraft_controls.create(new_callsign, None, None, None, None, None) assert err == "Sim error (all_properties)" # Test error when no sim data received for newly created aircraft new_callsign = types.Callsign("NEW2") all_properties_mock.return_value = sim_data err = proxy_aircraft_controls.create(new_callsign, None, None, None, None, None) assert err == "New callsign missing from sim data" # Test valid response all_properties_mock.return_value = { **sim_data, new_callsign: sim_data[test_callsign], } err = proxy_aircraft_controls.create(new_callsign, None, None, None, None, None) assert not err
def test_convert_aircraft_props(): """Tests for convert_aircraft_props""" ac_props = props.AircraftProperties( aircraft_type="A380", altitude=types.Altitude(18_500), callsign=types.Callsign("TEST"), cleared_flight_level=types.Altitude("FL225"), ground_speed=types.GroundSpeed(23), heading=types.Heading(47), initial_flight_level=types.Altitude(18_500), position=types.LatLon(43.8, 123.4), requested_flight_level=types.Altitude(25_000), route_name=None, vertical_speed=types.VerticalSpeed(32), ) converted = utils.convert_aircraft_props(ac_props) assert isinstance(converted, dict) assert len(converted) == 1 converted_props = converted["TEST"] assert len(converted_props) == 9 assert converted_props["actype"] == "A380" assert converted_props["cleared_fl"] == 22_500 assert converted_props["current_fl"] == 18_500 assert converted_props["gs"] == 23.0 assert converted_props["hdg"] == 47 assert converted_props["lat"] == 43.8 assert converted_props["lon"] == 123.4 assert converted_props["requested_fl"] == 25_000 assert converted_props["vs"] == 32.0
def test_exists(scenario_test_data): """Tests that ProxyAircraftControls implements exists""" mock_aircraft_controls = mock.Mock() proxy_aircraft_controls = ProxyAircraftControls(mock_aircraft_controls) # Test error error from callsigns property all_properties_mock = mock.PropertyMock(return_value="Sim error") type(mock_aircraft_controls).all_properties = all_properties_mock err = proxy_aircraft_controls.exists(None) assert err == "Sim error" # Test False for unknown callsign full_data, sim_data = scenario_test_data all_properties_mock.return_value = sim_data exists = proxy_aircraft_controls.exists(types.Callsign("MISS")) assert exists is False # Test Tru for known callsign proxy_aircraft_controls.set_initial_properties(_TEST_SECTOR_ELEMENT, TEST_SCENARIO) exists = proxy_aircraft_controls.exists(list(full_data)[0]) assert exists is True
def test_properties(scenario_test_data): """Tests that ProxyAircraftControls implements properties""" mock_aircraft_controls = mock.Mock() proxy_aircraft_controls = ProxyAircraftControls(mock_aircraft_controls) # Test error from all_properties all_properties_mock = mock.PropertyMock(return_value="Sim error") type(mock_aircraft_controls).all_properties = all_properties_mock err = proxy_aircraft_controls.properties(None) assert err == "Sim error" # Test error for unknown callsign proxy_aircraft_controls.set_initial_properties(_TEST_SECTOR_ELEMENT, TEST_SCENARIO) full_data, sim_data = scenario_test_data all_properties_mock.return_value = sim_data err = proxy_aircraft_controls.properties(types.Callsign("MISS")) assert err == "Unknown callsign MISS" # Test valid response test_callsign = list(full_data)[0] aircraft_props = proxy_aircraft_controls.properties(test_callsign) assert aircraft_props == full_data[test_callsign]
def _convert_to_ac_props( self, data: dict, ) -> Union[Dict[types.Callsign, props.AircraftProperties], str]: ac_props = {} try: for i in range(len(data["id"])): callsign = types.Callsign(data["id"][i]) ac_props[callsign] = props.AircraftProperties( aircraft_type=data["actype"][i], altitude=types.Altitude(data["alt"][i] / METERS_PER_FOOT), callsign=callsign, cleared_flight_level=None, ground_speed=types.GroundSpeed(int(data["gs"][i])), heading=types.Heading(int(data["trk"][i])), initial_flight_level=None, position=types.LatLon(data["lat"][i], data["lon"][i]), requested_flight_level=None, route_name=None, vertical_speed=types.VerticalSpeed( int(data["vs"][i] * 60 / METERS_PER_FOOT)), ) return ac_props except Exception: return f"Error parsing ac data from stream: {traceback.format_exc()}"
def set_initial_properties(self, sector_element: SectorElement, scenario_content: dict) -> None: """ Set any properties which are not tracked by the simulator - i.e. the flight levels, routes, and aircraft types """ for route in sector_element.routes(): self._routes[route.name] = route new_props: Dict[types.Callsign, AircraftProperties] = {} for aircraft in scenario_content["aircraft"]: callsign = types.Callsign(aircraft["callsign"]) new_props[callsign] = AircraftProperties.from_data(aircraft) if "route" not in aircraft: new_props[callsign].route_name = None continue # Match the route name to the waypoints in the scenario data aircraft_route_waypoints = [ x["fixName"] for x in aircraft["route"] ] new_props[callsign].route_name = next( x for x in self._routes if self._routes[x].fix_names() == aircraft_route_waypoints) self._ac_props = new_props self._data_valid = False
def callsigns(self) -> Union[List[types.Callsign], str]: callsigns = self._mc_client().get_active_callsigns() self._raise_for_no_data(callsigns) if callsigns is None: return [] if not isinstance(callsigns, list): return callsigns return [types.Callsign(x) for x in callsigns]
def test_aircraft_properties_from_data(): aircraft_data = TEST_SCENARIO["aircraft"][0] assert AircraftProperties.from_data(aircraft_data) == AircraftProperties( aircraft_type=aircraft_data["type"], altitude=types.Altitude(f'FL{aircraft_data["currentFlightLevel"]}'), callsign=types.Callsign(aircraft_data["callsign"]), cleared_flight_level=types.Altitude( f'FL{aircraft_data["clearedFlightLevel"]}'), ground_speed=None, heading=None, initial_flight_level=types.Altitude( f'FL{aircraft_data["currentFlightLevel"]}'), position=types.LatLon(aircraft_data["startPosition"][1], aircraft_data["startPosition"][0]), requested_flight_level=types.Altitude( f'FL{aircraft_data["requestedFlightLevel"]}'), route_name=None, vertical_speed=None, )
def _parse_aircraft_properties( ac_props: dict, ) -> Union[props.AircraftProperties, str]: try: # TODO Not currently available: gs, hdg, pos, vs return props.AircraftProperties( aircraft_type=ac_props["flight-data"]["type"], altitude=types.Altitude(f'FL{ac_props["pos"]["afl"]}'), callsign=types.Callsign(ac_props["flight-data"]["callsign"]), cleared_flight_level=None, ground_speed=types.GroundSpeed(ac_props["pos"]["speed"]), heading=types.Heading(0), initial_flight_level=None, position=types.LatLon(ac_props["pos"]["lat"], ac_props["pos"]["long"]), requested_flight_level=None, route_name=None, vertical_speed=types.VerticalSpeed(0), ) except Exception: return f"Error parsing AircraftProperties: {traceback.format_exc()}"
def sector_exit_metric(*args, **kwargs): """ The Aviary sector exit metric function. Expected *args are: callsign: Callsign See: https://github.com/alan-turing-institute/aviary/blob/develop/aviary/metrics/sector_exit_metric.py # noqa: E501 """ assert len(args) == 1 and isinstance(args[0], str), "Expected 1 string argument" callsign = types.Callsign(args[0]) aircraft_controls: ProxyAircraftControls = kwargs["aircraft_controls"] simulator_controls: ProxySimulatorControls = kwargs["simulator_controls"] assert simulator_controls.sector, "A sector definition is required" current_props = aircraft_controls.properties(callsign) assert isinstance(current_props, props.AircraftProperties) all_prev_props = aircraft_controls.prev_ac_props() prev_props = all_prev_props.get(callsign, None) if not all_prev_props or not prev_props: # NOTE (RJ 2020-01-30) aircraft_controls.prev_ac_props() is empty before STEP is # called for the first time. aviary_metrics.sector_exit_metric() returns None if # aircraft is still in the sector # NOTE (rkm 2020-01-30) This also covers the case where a new aircraft has been # created on this step - i.e. no previous data return None return aviary_metrics.sector_exit_metric( current_props.position.lon_degrees, current_props.position.lat_degrees, current_props.altitude.meters, prev_props.position.lon_degrees, prev_props.position.lat_degrees, prev_props.altitude.meters, prev_props.requested_flight_level, simulator_controls.sector.element, prev_props. route_name, # TODO(rkm 2020-01-28) Check the required arg type )
def fuel_efficiency_metric(*args, **kwargs): """ The Aviary fuel efficiency meric functions. Expected *args are: callsign: Callsign See: https://github.com/alan-turing-institute/aviary/blob/develop/aviary/metrics/fuel_efficiency_metric.py # noqa: E501 """ assert len(args) == 1 and isinstance(args[0], str), "Expected 1 string argument" callsign = types.Callsign(args[0]) aircraft_controls: ProxyAircraftControls = kwargs["aircraft_controls"] current_props = aircraft_controls.properties(callsign) assert isinstance(current_props, props.AircraftProperties) return aviary_metrics.fuel_efficiency_metric( current_props.altitude.meters, current_props.requested_flight_level.meters, current_props.initial_flight_level.meters, )
def from_data(cls, data: Dict[str, Any]) -> "AircraftProperties": """ Create an AircraftProperties from the current sector element and an "aircraft" object from the scenario json """ return cls( aircraft_type=data["type"], altitude=types.Altitude(data["currentFlightLevel"]), callsign=types.Callsign(data["callsign"]), cleared_flight_level=types.Altitude(data["clearedFlightLevel"]), ground_speed=None, # TODO(rkm 2020-01-22) Check if we should know the initial heading here heading=None, position=types.LatLon(data["startPosition"][1], data["startPosition"][0]), requested_flight_level=types.Altitude( data["requestedFlightLevel"]), route_name=None, vertical_speed=None, initial_flight_level=types.Altitude(data["currentFlightLevel"]), )