From ba77933a9a6c49209fcb6f5536b7fe101266ddfb Mon Sep 17 00:00:00 2001 From: Becheler <8360330+Becheler@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:04:17 +0000 Subject: [PATCH] doc: direct initialization of spatial graph edges --- .devcontainer/Dockerfile-ubuntu | 2 +- docs/4-tutorials.md | 39 +++++++++++--- example/geography_dispersal_kernel_4.cpp | 53 +++++++++++++++++++ .../quetzal/geography/graph/from_grid.hpp | 23 ++++++-- 4 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 example/geography_dispersal_kernel_4.cpp diff --git a/.devcontainer/Dockerfile-ubuntu b/.devcontainer/Dockerfile-ubuntu index def2b147..7540055e 100644 --- a/.devcontainer/Dockerfile-ubuntu +++ b/.devcontainer/Dockerfile-ubuntu @@ -1,4 +1,4 @@ -FROM ubuntu:23.04 +FROM ubuntu:24.04 ARG DEBIAN_FRONTEND=noninteractive diff --git a/docs/4-tutorials.md b/docs/4-tutorials.md index 10e24274..26aab6b4 100644 --- a/docs/4-tutorials.md +++ b/docs/4-tutorials.md @@ -1106,7 +1106,7 @@ Where: A straightforward approach to determine the dispersal mode across a complete spatial graph involves correlating the geographic distance between two locations with the likelihood of dispersal between them. The objective of this section is to highlight this model choice by parametrizing a simple Dispersal Location Kernel (in the sense of Nathan et al. 2012). Additionally, we will calculate essential metrics, such as the distance between two coordinates, the dispersal probability between them, and the average dispersal distance anticipated under this distribution. -### Basics +### Model's Basics The dispersal location kernel represents the statistical pattern of dispersal distances within a population. In this context, it essentially serves as the probability density function (pdf) that outlines the distribution of locations after dispersal in relation to the original location. The expressions have been adjusted to incorporate a scale parameter, \f$a\f$, which is consistent with a distance unit, and a shape parameter, @@ -1126,7 +1126,7 @@ Quetzal incorporates various kernel types available in the `quetzal::demography: --- -### Using Kernels with Spatial Graphs +### Kernels from Spatial Graphs Users can create a spatial graph from landscape data, customizing assumptions about connectivity and topology. By defining a dispersal kernel and calculating dispersal probabilities for each edge based on distance metrics, users can uncover insights into potential dispersal patterns within the spatial graph. This approach offers a powerful means for exploring and understanding spatial dynamics and connectivity, facilitating deeper analysis of ecological or geographic phenomena. @@ -1142,12 +1142,25 @@ Here we show how to simply print out the probabilities computed at each edge of --- -### Using Kernels with Spatial Graphs +### Using Kernels with Spatial Graphs Edges -In the simulation context, it can be a better alternative to compute the dispersal probabilities along the edges -of the graph once for all - especially if those are constant through time. +In a simulation context, it can be more efficient to compute the dispersal probabilities along the graph's edges once, especially if these probabilities remain constant over time. This saves repeated computations during each simulation step. -Here we show how to modify the edge property in order to conserve the probabilities computed at each edge of the spatial graph. +There are two main approaches for setting edge properties: + +1. **Default Initialization and Later Update:** You can create the graph with default edge properties and then update those properties later by iterating over the edges. It's simple to implement, but you pay the cost of iterating twice over the edges. + +2. **Direct Initialization:** If you want to initialize the edge properties directly, the property itself should be a user-defined class with a proper default constructor, allowing for efficient initialization during graph creation. This approach helps optimize performance, particularly in simulations with many edges or static dispersal probabilities. + +#### Default Initialization and Later Update + +Here, we explain how to create a weighted graph with default edge properties and update those properties later by iterating over the edges. + +First, when constructing the graph, each edge is initialized with a default set of properties (e.g., weights, dispersal probabilities). This allows you to quickly set up the graph structure without needing to provide specific values for every edge upfront. + +Once the graph is created, you can update the edge properties by looping through each edge and assigning more accurate or context-specific values. This method is particularly useful when the final edge properties are derived from complex calculations or external data that may not be immediately available at graph creation. + +By initializing the graph with default values, you maintain flexibility, allowing you to efficiently adjust edge properties later in the simulation or process without needing to recreate the entire graph structure. **Input** @@ -1156,3 +1169,17 @@ Here we show how to modify the edge property in order to conserve the probabilit **Output** @include{lineno} geography_dispersal_kernel_3.txt + +#### Direct Initialization + +Here, we demonstrate how to directly initialize the edge properties during graph creation. This approach is particularly useful when working with large graphs, where manually updating each edge later would be inefficient. By initializing the properties in place, you can ensure that each edge has the correct values from the start. + +This method requires defining a user-specific Edge Property class, which includes a default constructor to allow for proper instantiation. Doing so enables you to efficiently assign properties such as dispersal probabilities or weights directly to the graph’s edges during the initial setup, streamlining the overall process. + +**Input** + +@include{lineno} geography_dispersal_kernel_4.cpp + +**Output** + +@include{lineno} geography_dispersal_kernel_4.txt \ No newline at end of file diff --git a/example/geography_dispersal_kernel_4.cpp b/example/geography_dispersal_kernel_4.cpp new file mode 100644 index 00000000..903fee81 --- /dev/null +++ b/example/geography_dispersal_kernel_4.cpp @@ -0,0 +1,53 @@ +#include "quetzal/quetzal.hpp" + +#include +#include +#include + +namespace geo = quetzal::geography; +using namespace mp_units::si::unit_symbols; // SI system: km, m, s + +using kernel_type = quetzal::demography::dispersal_kernel::exponential_power<>; + +struct edge_info +{ + using probability_type = kernel_type::pdf_result_type; + probability_type weight; + auto compute(auto source, auto target, const auto & grid) + { + auto r = grid.to_latlon(source).great_circle_distance_to(grid.to_latlon(target)); + return kernel_type::pdf( r, { .a=200.*km , .b=5.5 } ); + } + edge_info(auto source, auto target, const auto & grid): weight(compute(source, target, grid)) {} + edge_info() = default; +}; + +int main() +{ + auto file1 = std::filesystem::current_path() / "data/bio1.tif"; + auto file2 = std::filesystem::current_path() / "data/bio12.tif"; + + // The raster have 10 bands that we will assign to 2001 ... 2010. + std::vector times(10); + std::iota(times.begin(), times.end(), 2001); + + // Initialize the landscape: for each var a key and a file, for all a time series. + using landscape_type = geo::landscape<>; + auto land = landscape_type::from_file({{"bio1", file1}, {"bio12", file2}}, times); + + // Edges will store the result of the kernel's probability distribution function + using vertex_info = geo::no_property; + + // We convert the grid to a graph, passing our assumptions + auto graph = geo::from_grid(land, + vertex_info(), + edge_info(), + geo::connect_fully(), + geo::isotropy(), + geo::mirror()); + + // Print the result + for (const auto& e : graph.edges() ) { + std::cout << e << " : " << graph[e].weight << '\n'; + } +} diff --git a/src/include/quetzal/geography/graph/from_grid.hpp b/src/include/quetzal/geography/graph/from_grid.hpp index 0067e6ed..b694dbe4 100644 --- a/src/include/quetzal/geography/graph/from_grid.hpp +++ b/src/include/quetzal/geography/graph/from_grid.hpp @@ -25,12 +25,22 @@ namespace quetzal::geography /// @param directionality A policy class giving the directionality (isotropy or anisotropy) /// @param bound A BoundPolicy /// @return A graph with the desired level of connectivity -template +template< + two_dimensional SpatialGrid, + directional Directionality, + class VertexProperty, + class EdgeProperty, + class Vicinity, + class Policy +> auto from_grid(SpatialGrid const &grid, VertexProperty const &v, EdgeProperty const &e, Vicinity const &vicinity, Directionality dir, Policy const &bounding_policy) { - using graph_type = quetzal::geography::graph; + namespace geo = quetzal::geography; + using connectedness = Vicinity::connectedness; + using graph_type = geo::graph; + using vertex_t = graph_type::vertex_descriptor; + graph_type graph( grid.width() * grid.height() + bounding_policy.num_extra_vertices() ) ; vicinity.connect(graph, grid, bounding_policy); @@ -42,7 +52,12 @@ auto from_grid(SpatialGrid const &grid, VertexProperty const &v, EdgeProperty co if constexpr ( ! std::is_same_v) { for(auto edge : graph.edges()){ - graph[edge] = e; + if constexpr (std::constructible_from){ + graph[edge] = EdgeProperty(edge.source(), edge.target(), grid); + } else { + static_assert(std::is_default_constructible_v); + graph[edge] = e; + } } }