|
2 | 2 |
|
3 | 3 | """Focused tests for AMC-native ArduPlane landing-attempt analysis.""" |
4 | 4 |
|
| 5 | +# pylint: disable=too-many-lines |
| 6 | + |
5 | 7 | from collections.abc import Sequence |
6 | 8 |
|
7 | 9 | import numpy as np |
@@ -365,6 +367,94 @@ def test_attempt_times_use_scaled_seconds() -> None: |
365 | 367 | assert evidence[0].time_s == 15.0 |
366 | 368 |
|
367 | 369 |
|
| 370 | +def test_inactive_gps_receiver_cannot_create_false_stop() -> None: |
| 371 | + log_data = _plane_log(messages=((40.0, "Throttle disarmed"),)) |
| 372 | + _add_columns( |
| 373 | + log_data, |
| 374 | + "GPS", |
| 375 | + ( |
| 376 | + (0.0, 6.0, 0, 1), |
| 377 | + (2.0, 6.0, 0, 1), |
| 378 | + (11.0, 1.0, 1, 0), |
| 379 | + (13.0, 1.0, 1, 0), |
| 380 | + (20.0, 6.0, 0, 1), |
| 381 | + (40.0, 6.0, 0, 1), |
| 382 | + ), |
| 383 | + [("TimeUS", "f8"), ("Spd", "f8"), ("I", "u1"), ("U", "u1")], |
| 384 | + ) |
| 385 | + segment = PlaneFlightSegmentDetector.detect(log_data, {}).segments[0] |
| 386 | + |
| 387 | + attempt = PlaneLandingAttemptDetector.detect(log_data, segment)[0] |
| 388 | + |
| 389 | + assert (attempt.start_s, attempt.end_s, attempt.end_reason) == (10.0, 40.0, PlaneLandingEndReason.DISARM) |
| 390 | + |
| 391 | + |
| 392 | +def test_inactive_gps_receiver_cannot_supply_stage_speed() -> None: |
| 393 | + log_data = _plane_log( |
| 394 | + land=((5.0, 0), (10.0, 1), (15.0, 2)), |
| 395 | + messages=((40.0, "Throttle disarmed"),), |
| 396 | + ) |
| 397 | + _add_columns( |
| 398 | + log_data, |
| 399 | + "GPS", |
| 400 | + ( |
| 401 | + (0.0, 6.0, 0, 1), |
| 402 | + (2.0, 6.0, 0, 1), |
| 403 | + (14.0, 7.0, 0, 1), |
| 404 | + (15.0, 1.0, 1, 0), |
| 405 | + (16.0, 8.0, 0, 1), |
| 406 | + (40.0, 6.0, 0, 1), |
| 407 | + ), |
| 408 | + [("TimeUS", "f8"), ("Spd", "f8"), ("I", "u1"), ("U", "u1")], |
| 409 | + ) |
| 410 | + segment = PlaneFlightSegmentDetector.detect(log_data, {}).segments[0] |
| 411 | + attempt = PlaneLandingAttemptDetector.detect(log_data, segment)[0] |
| 412 | + |
| 413 | + evidence = PlaneLandingEvidenceExtractor.extract(log_data, attempt, ParameterHistory()) |
| 414 | + |
| 415 | + assert evidence[0].gps_ground_speed_m_s == 7.0 |
| 416 | + |
| 417 | + |
| 418 | +def test_inactive_gps_receiver_cannot_supply_target_distance_position() -> None: |
| 419 | + log_data = _gps_stop_log(((1.0, 1, 0, 21, 0.0, 1.0),)) |
| 420 | + _add_columns( |
| 421 | + log_data, |
| 422 | + "GPS", |
| 423 | + ( |
| 424 | + (20.0, 2.0, 10.0, 10.0, 1, 0), |
| 425 | + (20.0, 2.0, 0.0, 1.001, 0, 1), |
| 426 | + ), |
| 427 | + [("TimeUS", "f8"), ("Spd", "f8"), ("Lat", "f8"), ("Lng", "f8"), ("I", "u1"), ("U", "u1")], |
| 428 | + ) |
| 429 | + segment = FlightSegment(start_s=0.0, end_s=30.0, is_complete=True) |
| 430 | + attempt = PlaneLandingAttempt( |
| 431 | + flight_segment=segment, |
| 432 | + start_s=10.0, |
| 433 | + end_s=20.0, |
| 434 | + end_reason=PlaneLandingEndReason.GPS_STOP, |
| 435 | + ) |
| 436 | + |
| 437 | + distance = PlaneLandingMissionTargetExtractor.distance_at_gps_stop(log_data, attempt) |
| 438 | + |
| 439 | + assert distance is not None |
| 440 | + assert (distance.aircraft_latitude_deg, distance.aircraft_longitude_deg) == (0.0, 1.001) |
| 441 | + assert distance.distance_m == pytest.approx(111.1949266) |
| 442 | + |
| 443 | + |
| 444 | +def test_gps_without_use_flag_retains_existing_stop_behavior() -> None: |
| 445 | + log_data = _plane_log( |
| 446 | + gps=((0.0, 6.0), (2.0, 6.0), (20.0, 2.0), (22.0, 2.0), (30.0, 6.0)), |
| 447 | + ) |
| 448 | + gps = log_data.get_message_columns("GPS") |
| 449 | + assert gps is not None |
| 450 | + assert "U" not in (gps.dtype.names or ()) |
| 451 | + segment = PlaneFlightSegmentDetector.detect(log_data, {}).segments[0] |
| 452 | + |
| 453 | + attempt = PlaneLandingAttemptDetector.detect(log_data, segment)[0] |
| 454 | + |
| 455 | + assert (attempt.end_s, attempt.end_reason) == (20.0, PlaneLandingEndReason.GPS_STOP) |
| 456 | + |
| 457 | + |
368 | 458 | def test_stage_evidence_uses_land_and_nearest_optional_telemetry() -> None: |
369 | 459 | log_data = _plane_log( |
370 | 460 | gps=( |
@@ -910,6 +1000,81 @@ def test_rfnd_lifecycle_flat_outcomes_leave_stage_point_measurement_unchanged() |
910 | 1000 | ] |
911 | 1001 |
|
912 | 1002 |
|
| 1003 | +@pytest.mark.parametrize("non_finite_value", [float("nan"), float("inf"), float("-inf")]) |
| 1004 | +def test_non_finite_event_time_landing_parameters_are_unavailable(non_finite_value: float) -> None: |
| 1005 | + log_data = _plane_log( |
| 1006 | + land=((5.0, 0), (10.0, 1), (15.0, 2), (20.0, 3)), |
| 1007 | + messages=((40.0, "Throttle disarmed"),), |
| 1008 | + ) |
| 1009 | + parameter_names = ("LAND_PF_ALT", "LAND_PF_SEC", "LAND_FLARE_ALT", "LAND_FLARE_SEC", "LAND_PITCH_DEG") |
| 1010 | + history = ParameterHistory(dict.fromkeys(parameter_names, non_finite_value)) |
| 1011 | + segment = PlaneFlightSegmentDetector.detect(log_data, {}).segments[0] |
| 1012 | + attempt = PlaneLandingAttemptDetector.detect(log_data, segment)[0] |
| 1013 | + |
| 1014 | + evidence = PlaneLandingEvidenceExtractor.extract(log_data, attempt, history) |
| 1015 | + result = PlaneLandingAnalysis(log_data, _context(history)).analyse() |
| 1016 | + |
| 1017 | + assert all(value is None for item in evidence for value in item.parameter_values.values()) |
| 1018 | + attempt_outcome = next(outcome for outcome in result.outcomes if outcome.message.startswith("AUTO landing attempt")) |
| 1019 | + assert attempt_outcome.value is None |
| 1020 | + assert "LAND_FLARE_ALT was unavailable" in attempt_outcome.message |
| 1021 | + assert not any( |
| 1022 | + parameter_name in outcome.message and "effective value" in outcome.message |
| 1023 | + for parameter_name in parameter_names |
| 1024 | + for outcome in result.outcomes |
| 1025 | + ) |
| 1026 | + |
| 1027 | + |
| 1028 | +def test_finite_event_time_landing_parameters_are_emitted() -> None: |
| 1029 | + log_data = _plane_log( |
| 1030 | + land=((5.0, 0), (10.0, 1), (15.0, 2), (20.0, 3)), |
| 1031 | + messages=((40.0, "Throttle disarmed"),), |
| 1032 | + ) |
| 1033 | + parameter_values = { |
| 1034 | + "LAND_PF_ALT": 6.0, |
| 1035 | + "LAND_PF_SEC": 2.0, |
| 1036 | + "LAND_FLARE_ALT": 3.0, |
| 1037 | + "LAND_FLARE_SEC": 1.5, |
| 1038 | + "LAND_PITCH_DEG": 4.0, |
| 1039 | + } |
| 1040 | + |
| 1041 | + result = PlaneLandingAnalysis(log_data, _context(ParameterHistory(parameter_values))).analyse() |
| 1042 | + |
| 1043 | + attempt_outcome = next(outcome for outcome in result.outcomes if outcome.message.startswith("AUTO landing attempt")) |
| 1044 | + assert attempt_outcome.value == 3.0 |
| 1045 | + parameter_outcomes = [outcome for outcome in result.outcomes if "effective value" in outcome.message] |
| 1046 | + emitted_parameter_names = { |
| 1047 | + parameter_name |
| 1048 | + for parameter_name in parameter_values |
| 1049 | + if any(parameter_name in item.message for item in parameter_outcomes) |
| 1050 | + } |
| 1051 | + assert emitted_parameter_names == set(parameter_values) |
| 1052 | + |
| 1053 | + |
| 1054 | +def test_finite_parameter_change_at_event_time_remains_effective() -> None: |
| 1055 | + log_data = _plane_log( |
| 1056 | + land=((5.0, 0), (10.0, 1), (15.0, 2), (20.0, 3)), |
| 1057 | + messages=((40.0, "Throttle disarmed"),), |
| 1058 | + ) |
| 1059 | + history = ParameterHistory( |
| 1060 | + {"LAND_PF_ALT": 5.0, "LAND_FLARE_ALT": 3.0}, |
| 1061 | + { |
| 1062 | + "LAND_PF_ALT": (ParameterChange(time_s=15.0, value=6.0),), |
| 1063 | + "LAND_FLARE_ALT": (ParameterChange(time_s=20.0, value=4.0),), |
| 1064 | + }, |
| 1065 | + ) |
| 1066 | + |
| 1067 | + result = PlaneLandingAnalysis(log_data, _context(history)).analyse() |
| 1068 | + |
| 1069 | + attempt_outcome = next(outcome for outcome in result.outcomes if outcome.message.startswith("AUTO landing attempt")) |
| 1070 | + assert attempt_outcome.value == 3.0 |
| 1071 | + parameter_outcomes = [outcome for outcome in result.outcomes if "effective value" in outcome.message] |
| 1072 | + assert [(outcome.timestamp_us, outcome.value) for outcome in parameter_outcomes] == [ |
| 1073 | + (15_000_000, 6.0), |
| 1074 | + (20_000_000, 4.0), |
| 1075 | + ] |
| 1076 | + |
| 1077 | + |
913 | 1078 | def test_event_time_parameters_change_between_attempts_without_changing_boundaries() -> None: |
914 | 1079 | log_data = _plane_log( |
915 | 1080 | land=( |
|
0 commit comments