Added pyrate as a direct dependency.
This commit is contained in:
171
pyrate/tests/plan/geometry/helpers/test_difference.py
Normal file
171
pyrate/tests/plan/geometry/helpers/test_difference.py
Normal file
@ -0,0 +1,171 @@
|
||||
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry.helpers` functions
|
||||
for calculating differences.
|
||||
"""
|
||||
|
||||
# Python standard
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from math import isfinite
|
||||
from math import isnan
|
||||
import warnings
|
||||
|
||||
# Typing
|
||||
from typing import Callable
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Numeric testing
|
||||
from numpy import allclose
|
||||
from numpy import array
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Test helpers
|
||||
from pyrate.plan.geometry.helpers import difference_direction
|
||||
from pyrate.plan.geometry.helpers import difference_latitude
|
||||
from pyrate.plan.geometry.helpers import difference_longitude
|
||||
from pyrate.plan.geometry.helpers import ScalarOrArray
|
||||
|
||||
|
||||
class TestDifference(TestCase, ABC):
|
||||
"""Makes sure the distance measure is well-behaved.
|
||||
|
||||
Keep in mind that it is formally not a metric since the triangle inequality does not hold.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
|
||||
"""Get the function to be tested."""
|
||||
|
||||
@abstractmethod
|
||||
def _get_max(self) -> float:
|
||||
"""Get the desired maximum value (inclusive)."""
|
||||
|
||||
@abstractmethod
|
||||
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
|
||||
"""Get some concrete values to be tested as a sequence of ``(value a, value b, distance between)``."""
|
||||
|
||||
@given(st.floats(), st.floats())
|
||||
def test_distance_measuring_commutes_and_is_in_bounds(self, first: float, second: float) -> None:
|
||||
"""Assures flipping the sides when calculating distances does not make a significant difference."""
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
distance_1 = self._get_difference_function()(first, second)
|
||||
distance_2 = self._get_difference_function()(second, first)
|
||||
|
||||
if isfinite(distance_1) and isfinite(distance_1):
|
||||
# make sure it commutes
|
||||
self.assertAlmostEqual(distance_1, distance_2)
|
||||
|
||||
# make sure the distance is always positive
|
||||
self.assertGreaterEqual(distance_1, 0.0)
|
||||
self.assertGreaterEqual(distance_2, 0.0)
|
||||
|
||||
# make sure the distance is within bounds
|
||||
self.assertLessEqual(distance_1, self._get_max())
|
||||
self.assertLessEqual(distance_2, self._get_max())
|
||||
|
||||
else:
|
||||
self.assertTrue(isnan(distance_1))
|
||||
self.assertTrue(isnan(distance_2))
|
||||
|
||||
@given(st.floats())
|
||||
def test_distance_measuring_to_itself_is_zero(self, thing: float) -> None:
|
||||
"""Assures flipping the sides when calculating distances does not make a significant difference."""
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
distance = self._get_difference_function()(thing, thing)
|
||||
|
||||
# make sure the distance is always positive and very close to zero
|
||||
if isfinite(distance):
|
||||
self.assertGreaterEqual(distance, 0.0)
|
||||
self.assertAlmostEqual(distance, 0.0)
|
||||
else:
|
||||
self.assertTrue(isnan(distance))
|
||||
|
||||
def test_concrete_examples(self) -> None:
|
||||
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
|
||||
function = self._get_difference_function()
|
||||
|
||||
for index, (value_a, value_b, expected_result) in enumerate(self._get_concrete_examples()):
|
||||
with self.subTest(f"example triple #{index}"):
|
||||
self.assertAlmostEqual(function(value_a, value_b), expected_result, delta=1e-12)
|
||||
|
||||
def test_concrete_examples_as_array(self) -> None:
|
||||
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
|
||||
function = self._get_difference_function()
|
||||
data = array(self._get_concrete_examples()).T
|
||||
self.assertTrue(allclose(function(data[0, :], data[1, :]), data[2, :]))
|
||||
|
||||
|
||||
class TestDifferenceLatitude(TestDifference):
|
||||
"""Tests :func:`pyrate.plan.geometry.helpers.difference_latitude`."""
|
||||
|
||||
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
|
||||
return difference_latitude
|
||||
|
||||
def _get_max(self) -> float:
|
||||
return 180.0
|
||||
|
||||
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
|
||||
return [
|
||||
(0, 0, 0),
|
||||
(-90, 90, 180),
|
||||
(-89.5, 0, 89.5),
|
||||
(-89.5, 0.5, 90),
|
||||
(-89.5, -0.5, 89),
|
||||
(-45, 45, 90),
|
||||
]
|
||||
|
||||
|
||||
class TestDifferenceLongitude(TestDifference):
|
||||
"""Tests :func:`pyrate.plan.geometry.helpers.difference_longitude`."""
|
||||
|
||||
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
|
||||
return difference_longitude
|
||||
|
||||
def _get_max(self) -> float:
|
||||
return 180.0
|
||||
|
||||
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
|
||||
return [
|
||||
(0, 0, 0),
|
||||
(-90, 90, 180),
|
||||
(-89.5, 0, 89.5),
|
||||
(-89.5, 0.5, 90),
|
||||
(-89.5, -0.5, 89),
|
||||
(180, -180, 0),
|
||||
(100, -100, 160),
|
||||
(-45, 45, 90),
|
||||
]
|
||||
|
||||
|
||||
class TestDifferenceDirection(TestDifference):
|
||||
"""Tests :func:`pyrate.plan.geometry.helpers.difference_direction`."""
|
||||
|
||||
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
|
||||
return difference_direction
|
||||
|
||||
def _get_max(self) -> float:
|
||||
return 180.0
|
||||
|
||||
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
|
||||
return [
|
||||
(0, 0, 0),
|
||||
(-90, 90, 180),
|
||||
(0, 360, 0),
|
||||
(10, -10, 20),
|
||||
(10, 350, 20),
|
||||
(370, 20, 10),
|
||||
]
|
||||
|
||||
|
||||
# Do not execute the base class as a test, see https://stackoverflow.com/a/43353680/3753684
|
||||
del TestDifference
|
Reference in New Issue
Block a user