167 lines
7.1 KiB
Python
167 lines
7.1 KiB
Python
"""Asserts correct behaviour of the geo-referenced graph navigation.
|
|
|
|
See Also:
|
|
tests/common/raster_datasets/test_transformers_concrete.py
|
|
"""
|
|
|
|
# Standard library
|
|
from copy import deepcopy
|
|
import os.path
|
|
from tempfile import TemporaryDirectory
|
|
from unittest import TestCase
|
|
|
|
# Scientific
|
|
import numpy
|
|
from numpy import arange
|
|
from numpy import array
|
|
from numpy import empty
|
|
from numpy.testing import assert_array_equal
|
|
from pandas import DataFrame
|
|
|
|
# Graph generation / Module under test
|
|
from pyrate.common.raster_datasets import transformers_concrete
|
|
from pyrate.plan.graph import create_earth_graph
|
|
from pyrate.plan.graph import GeoNavigationGraph
|
|
from pyrate.plan.graph import min_required_frequency
|
|
|
|
# CI/Testing helpers
|
|
from ... import _open_test_geo_dataset
|
|
|
|
|
|
from .generate.test_graph_generation import EXAMPLE_DISTANCES_KILOMETERS
|
|
|
|
|
|
class TestGeoNavigationGraph(TestCase):
|
|
"""Tests properties specific to :class:`pyrate.plan.graph.GeoNavigationGraph`."""
|
|
|
|
def test_create_invalid_duplicate_argument_nodes(self) -> None:
|
|
"""Tests supplying nodes to from_coordinates_radians/from_coordinates_degrees raises an Exception."""
|
|
for function in [
|
|
GeoNavigationGraph.from_coordinates_degrees,
|
|
GeoNavigationGraph.from_coordinates_radians,
|
|
]:
|
|
with self.subTest(msg=f"function {str(function)}"):
|
|
with self.assertRaises(Exception): # noqa: H202
|
|
function( # type: ignore
|
|
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), nodes=DataFrame()
|
|
)
|
|
|
|
def test_node_radius_constructor(self) -> None:
|
|
"""Tests that only invalid inputs to node_radius raise exceptions."""
|
|
GeoNavigationGraph.from_coordinates_degrees(
|
|
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=0
|
|
)
|
|
GeoNavigationGraph.from_coordinates_degrees(
|
|
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=100_000
|
|
)
|
|
|
|
with self.assertRaises(Exception): # noqa: H202
|
|
GeoNavigationGraph.from_coordinates_degrees(
|
|
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=-1e-9
|
|
)
|
|
|
|
def test_set_node_properties(self) -> None:
|
|
"""Tests that passing ``node_properties`` works."""
|
|
graph = GeoNavigationGraph.from_coordinates_radians(
|
|
latitudes=array([42]),
|
|
longitudes=array([21]),
|
|
edges=empty((0, 2)),
|
|
node_radius=100,
|
|
node_properties=DataFrame(data={"col1": [99], "col2": ["text"]}),
|
|
)
|
|
self.assertEqual(graph.node_radius, 100)
|
|
assert_array_equal(graph.node_properties["col1"], [99])
|
|
assert_array_equal(graph.node_properties["col2"], ["text"])
|
|
|
|
def test_read_write(self) -> None:
|
|
"""Tests that a *geo* navigation graph can be serialized and deserialized again."""
|
|
latitudes = array([49.8725144])
|
|
longitudes = array([8.6528707])
|
|
edges = empty((0, 2))
|
|
|
|
# `graph.neighbors` is cached, so we want to try it with and without the cached neighbors being set
|
|
for set_neighbors in [True, False]:
|
|
with self.subTest(f"neighbors set = {set_neighbors}"):
|
|
graph = GeoNavigationGraph.from_coordinates_degrees(
|
|
latitudes, longitudes, edges=edges, max_neighbors=42, node_radius=1000
|
|
)
|
|
if set_neighbors:
|
|
_ = graph.neighbors
|
|
|
|
with TemporaryDirectory() as directory:
|
|
path = os.path.join(directory, "some_file.hdf5")
|
|
graph.to_disk(path)
|
|
new_graph = GeoNavigationGraph.from_disk(path)
|
|
|
|
self.assertEqual(graph, new_graph)
|
|
assert_array_equal(new_graph.neighbors, graph.neighbors)
|
|
|
|
|
|
class TestNavigationGraphPruningGeo(TestCase):
|
|
"""Tests that navigation graphs can be pruned by testing it with earth graphs."""
|
|
|
|
def test_pruning_artificial(self) -> None:
|
|
"""Tests that pruning half of the points works as expected."""
|
|
|
|
for distance_km in EXAMPLE_DISTANCES_KILOMETERS:
|
|
with self.subTest(f"Test with distance {distance_km} km"):
|
|
# create a grid
|
|
graph = create_earth_graph(min_required_frequency(distance_km * 1000, in_meters=True))
|
|
|
|
# keep all nodes at even latitudes
|
|
keep_condition = arange(0, len(graph)) % 2 == 0
|
|
pruned_graph = deepcopy(graph)
|
|
pruned_graph.prune_nodes(keep_condition)
|
|
|
|
self.assertGreater(len(pruned_graph), 0, "some node must remain")
|
|
|
|
# test the reduction ratio
|
|
delta_nodes = len(pruned_graph) / len(graph)
|
|
delta_edges = pruned_graph.num_edges / graph.num_edges
|
|
self.assertAlmostEqual(delta_nodes, 0.5, msg="suspicious node count reduction")
|
|
# about a fifth of all edges should be removed since each of the removed nodes removed five
|
|
# edges
|
|
self.assertAlmostEqual(delta_edges, 1 / 5, delta=0.15, msg="suspicious edge count reduction")
|
|
|
|
# test the values in the edges, since they were rewritten as they point to new indices
|
|
self.assertTrue(numpy.all(pruned_graph.edges[:, :] >= 0), "indices must be non-negative")
|
|
self.assertTrue(
|
|
numpy.all(pruned_graph.edges[:, :] < len(pruned_graph)),
|
|
"some filtered edges reference (now) non-existent points",
|
|
)
|
|
|
|
def test_pruning_depth(self) -> None:
|
|
"""Supplements :meth`~test_pruning_artificial` by a real-world application.
|
|
|
|
Only checks application-specific properties and not, for example, the general shapes of the result.
|
|
"""
|
|
# create a grid
|
|
distance_meters = 500_000
|
|
graph = create_earth_graph(min_required_frequency(distance_meters, in_meters=True))
|
|
|
|
# fetch properties
|
|
mode = transformers_concrete.BathymetricTransformer.Modes.AVERAGE_DEPTH
|
|
graph.append_property(transformers_concrete.BathymetricTransformer(_open_test_geo_dataset(), [mode]))
|
|
|
|
# keep all nodes that are below sea level
|
|
keep_condition = (graph.node_properties[mode.column_name] < 0.0).to_numpy()
|
|
|
|
# Remove the now useless property
|
|
graph.clear_node_properties()
|
|
|
|
# perform pruning
|
|
pruned_graph = deepcopy(graph)
|
|
pruned_graph.prune_nodes(keep_condition)
|
|
|
|
# test the reduction ratio
|
|
delta_nodes = len(pruned_graph) / len(graph)
|
|
delta_edges = pruned_graph.num_edges / graph.num_edges
|
|
earth_fraction_water = 0.708 # see https://en.wikipedia.org/wiki/World_Ocean
|
|
# although we go by topography and not water coverage, this should still be fairly correct
|
|
self.assertAlmostEqual(
|
|
delta_nodes, earth_fraction_water, delta=0.1, msg="suspicious node count reduction"
|
|
)
|
|
self.assertAlmostEqual(
|
|
delta_edges, earth_fraction_water, delta=0.1, msg="suspicious edge count reduction"
|
|
)
|