diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 5d829d6d..1499a351 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -7,16 +7,42 @@ on: branches: [ master ] env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug jobs: build: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ubuntu-22.04 - + name: "C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + build_type: ["Release", "Debug"] + std: [20, 23] + config: + - { + name: "GCC-12", + os: ubuntu-24.04, + compiler: + { + type: GCC, + version: 12, + cc: "gcc-12", + cxx: "g++-12", + }, + conan-config: "", + } + - { + name: "GCC-13", + os: ubuntu-24.04, + compiler: + { + type: GCC, + version: 13, + cc: "gcc-13", + cxx: "g++-13", + }, + conan-config: "", + } steps: - uses: actions/checkout@v4 with: @@ -27,47 +53,47 @@ jobs: run: > sudo apt-get update -y && sudo apt-get install -y --no-install-recommends - vim - git - gcc-12 - g++-12 build-essential - libboost-all-dev cmake - unzip - tar - ca-certificates - doxygen - graphviz - python3-pip - - name: Conan installation - run: pip install conan==2.0.13 + - name: Set Up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x - - name: Conan version - run: echo "${{ steps.conan.outputs.version }}" + - name: Conan installation + run: | + pip install -U conan - # - name: Conan profile configuration - # run: | - # conan profile detect --name profile - # conan profile update settings.compiler="gcc" profile - # conan profile update settings.build_type="Release" profile - # conan profile update settings.compiler.version=12 profile - # conan profile update settings.compiler.cppstd=20 profile - # conan profile update settings.compiler.libcxx="libstdc++11" profile - # conan profile update env.CC=[/usr/bin/gcc-12] profile - # conan profile update env.CXX=[/usr/bin/g++-12] profile + - name: Cache Conan + uses: actions/cache@v4 + if: always() + env: + cache-name: cache-conan-data + with: + path: ~/.conan2/p + key: ${{ matrix.config.os }}-{{ matrix.std }}-${{ matrix.config.name }}-${{ matrix.build_type }}-${{ hashFiles('conanfile.txt') }} + + - name: Conan Configure + shell: bash + run: | + conan profile detect --force + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default + sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default + conan profile show -pr default - - name: Conan install dependencies + - name: Conan Install run: > - conan install conanfile.py --profile:build=conan/profiles/linux-x86-gcc12-debug --profile:host=conan/profiles/linux-x86-gcc12-debug --build=missing --output-folder=build + conan install conanfile.py + --build=missing + --output-folder=build - name: CMake configuration run: > cmake -B ${{github.workspace}}/build -S . - -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_TESTS=ON --toolchain ${{github.workspace}}/build/conan_toolchain.cmake @@ -75,22 +101,12 @@ jobs: run: > cmake --build ${{github.workspace}}/build - --config ${{env.BUILD_TYPE}} + --config ${{ matrix.build_type }} - name: CMake test working-directory: ${{github.workspace}}/build - run: ctest -C ${{env.BUILD_TYPE}} --rerun-failed --output-on-failure - - - name: Doxygen documentation generation - working-directory: ${{github.workspace}}/build - run: make docs - - # - name: Moving Files - # run: | - # mv ${{github.workspace}}/build/docs/html ./docs/api - - - name: Deploy to Github Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{github.workspace}}/build/docs/html + run: > + ctest + -C ${{ matrix.build_type }} + --rerun-failed + --output-on-failure diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..d915ca2e --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,90 @@ +name: Documentation and Examples + +on: + push: + branches: [ master ] + +env: + BUILD_TYPE: Debug + STD: 23 + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Get Apt packages + run: > + sudo apt-get update -y && + sudo apt-get install -y --no-install-recommends + build-essential + cmake + doxygen + graphviz + + - name: Set Up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - name: Set Up Conan + run: pip install conan==2.0.13 + + - name: Cache Conan + uses: actions/cache@v4 + if: always() + env: + cache-name: cache-conan-data + with: + path: ~/.conan2/p + key: documentation-${{ hashFiles('conanfile.txt') }} + + - name: Conan Configure + shell: bash + run: | + conan profile detect --force + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{env.STD}}/' ~/.conan2/profiles/default + sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{env.BUILD_TYPE}}/' ~/.conan2/profiles/default + conan profile show -pr default + + - name: Conan Install + run: > + conan install conanfile.py + --profile:build=~/.conan2/profiles/default + --profile:host=~/.conan2/profiles/default + --build=missing + --output-folder=build + + - name: CMake Configure + run: > + cmake + -B ${{github.workspace}}/build + -S . + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -DBUILD_TESTS=ON + --toolchain ${{github.workspace}}/build/conan_toolchain.cmake + + - name: CMake Build + run: > + cmake + --build ${{github.workspace}}/build + --config ${{env.BUILD_TYPE}} + + - name: CMake Test + working-directory: ${{github.workspace}}/build + run: ctest -L "example" + + - name: Doxygen Generation + working-directory: ${{github.workspace}}/build + run: make docs + + - name: Deploy to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ${{github.workspace}}/build/docs/html \ No newline at end of file diff --git a/conan/profiles/linux-armv8-gcc13-debug b/conan/profiles/linux-armv8-gcc13-debug new file mode 100644 index 00000000..6b9a0df8 --- /dev/null +++ b/conan/profiles/linux-armv8-gcc13-debug @@ -0,0 +1,7 @@ +[settings] +os=Linux +arch=armv8 +compiler=gcc +compiler.version=13 +compiler.libcxx=libstdc++11 +build_type=Debug \ No newline at end of file diff --git a/conan/profiles/linux-x86-gcc13-debug b/conan/profiles/linux-x86-gcc13-debug new file mode 100644 index 00000000..2a2cb6e4 --- /dev/null +++ b/conan/profiles/linux-x86-gcc13-debug @@ -0,0 +1,7 @@ +[settings] +os=Linux +arch=x86_64 +compiler=gcc +compiler.version=13 +compiler.libcxx=libstdc++11 +build_type=Debug \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 3950a9e5..b8cd4d1f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -37,20 +37,24 @@ def package_id(self): self.info.clear() def requirements(self): - if self.settings.os == "Macos": - if self.settings.compiler == "apple-clang": - self.requires("boost/[>1.75 <1.80]") - self.requires("gdal/[>=3.4.0]") - self.requires("range-v3/0.12.0") - self.requires("mp-units/2.0.0") - if self.settings.os == "Linux": - if self.settings.compiler == "gcc": - self.requires("boost/[>1.75 <1.80]") - self.requires("gdal/[>=3.4.0 <3.5.1]") - self.requires("mp-units/2.0.0") - self.requires("range-v3/0.12.0") - self.requires("libtiff/4.5.1", override=True) # Version conflict: libgeotiff/1.7.1->libtiff/4.6.0, gdal/3.4.3->libtiff/4.5.1. - self.requires("libdeflate/1.18", override=True) # Version conflict: libtiff/4.6.0->libdeflate/1.19, gdal/3.4.3->libdeflate/1.18. - self.requires("proj/9.2.1", override=True) # Version conflict: libgeotiff/1.7.1->proj/9.3.0, gdal/3.4.3->proj/9.2.1. - self.requires("sqlite3/3.42.0", override=True) # Version conflict: proj/9.3.0->sqlite3/3.43.2, gdal/3.4.3->sqlite3/3.42.0. - self.requires("zlib/1.2.13", override=True) # Version conflict: gdal/3.4.3->zlib/1.2.13, quetzal-CoaTL/3.1.0->zlib/1.3. \ No newline at end of file + self.requires("boost/1.86.0") + self.requires("gdal/3.8.3") + self.requires("range-v3/0.12.0") + self.requires("mp-units/2.2.1") + # if self.settings.os == "Macos": + # if self.settings.compiler == "apple-clang": + # self.requires("boost/[>1.75 <1.80]") + # self.requires("gdal/[>=3.4.0]") + # self.requires("range-v3/0.12.0") + # self.requires("mp-units/2.2.1") + # if self.settings.os == "Linux": + # if self.settings.compiler == "gcc": + # self.requires("boost/[>1.75 <1.80]") + # self.requires("gdal/[>=3.4.0 <3.5.1]") + # self.requires("mp-units/2.2.1") + # self.requires("range-v3/0.12.0") + # self.requires("libtiff/4.5.1", override=True) # Version conflict: libgeotiff/1.7.1->libtiff/4.6.0, gdal/3.4.3->libtiff/4.5.1. + # self.requires("libdeflate/1.18", override=True) # Version conflict: libtiff/4.6.0->libdeflate/1.19, gdal/3.4.3->libdeflate/1.18. + # self.requires("proj/9.2.1", override=True) # Version conflict: libgeotiff/1.7.1->proj/9.3.0, gdal/3.4.3->proj/9.2.1. + # self.requires("sqlite3/3.42.0", override=True) # Version conflict: proj/9.3.0->sqlite3/3.43.2, gdal/3.4.3->sqlite3/3.42.0. + # self.requires("zlib/1.2.13", override=True) # Version conflict: gdal/3.4.3->zlib/1.2.13, quetzal-CoaTL/3.1.0->zlib/1.3. \ No newline at end of file diff --git a/docs/4-tutorials.md b/docs/4-tutorials.md index 1b82ae6e..10e24274 100644 --- a/docs/4-tutorials.md +++ b/docs/4-tutorials.md @@ -821,7 +821,7 @@ dynamically adding vertices and edges based on user-defined conditions, resultin --- [//]: # (----------------------------------------------------------------------) -@page spatial_graph_information Embedding information along Vertices and Edges +@page spatial_graph_information Embedding vertex/edge information @tableofcontents The focal point of the provided code snippet revolves around the process of constructing a spatial graph from raster data. This spatial graph serves as a powerful tool for visualizing and analyzing spatial relationships and patterns present within the underlying geographic data. @@ -971,7 +971,7 @@ The general idea is to define lambda expressions that embed stochastic aspects o --- [//]: # (----------------------------------------------------------------------) -@page spatial_graph_local_process Defining autoregressive processes on +@page spatial_graph_local_process Autoregressive processes and time series @tableofcontents ## Background @@ -1130,6 +1130,8 @@ Quetzal incorporates various kernel types available in the `quetzal::demography: 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. +Here we show how to simply print out the probabilities computed at each edge of the spatial graph. + **Input** @include{lineno} geography_dispersal_kernel_2.cpp @@ -1137,5 +1139,20 @@ Users can create a spatial graph from landscape data, customizing assumptions ab **Output** @include{lineno} geography_dispersal_kernel_2.txt + --- +### Using Kernels with Spatial Graphs + +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. + +Here we show how to modify the edge property in order to conserve the probabilities computed at each edge of the spatial graph. + +**Input** + +@include{lineno} geography_dispersal_kernel_3.cpp + +**Output** + +@include{lineno} geography_dispersal_kernel_3.txt diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c38d77a2..48074330 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -47,5 +47,8 @@ foreach(testSrc ${SRCS}) add_test( NAME ${testName} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/example - COMMAND sh -c "$ > output/${testName}.txt" ) + COMMAND sh -c "$ > output/${testName}.txt" ) + + set_tests_properties(${testName} PROPERTIES LABELS "documentation example") + endforeach(testSrc) diff --git a/example/geography_dispersal_kernel_3.cpp b/example/geography_dispersal_kernel_3.cpp new file mode 100644 index 00000000..a806c978 --- /dev/null +++ b/example/geography_dispersal_kernel_3.cpp @@ -0,0 +1,49 @@ +#include "quetzal/quetzal.hpp" + +#include +#include +#include + +namespace geo = quetzal::geography; +using namespace mp_units::si::unit_symbols; // SI system: km, m, s + +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); + + // Define the type of distance-based kernel to use + using kernel = quetzal::demography::dispersal_kernel::exponential_power<>; + + // Edges will store the result of the kernel's probability distribution function + using vertex_info = geo::no_property; + using edge_info = kernel::pdf_result_type; + + // 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()); + + // Define a helper function to compute distance on Earth between two location_descriptors + auto sphere_distance = [&](auto point1, auto point2){ + return land.to_latlon(point1).great_circle_distance_to(land.to_latlon(point2)); + }; + + // Transform the edges of the graph to a vector of probability to disperse from source to target + for (const auto& e : graph.edges() ) { + auto r = sphere_distance( graph.source( e ), graph.target( e ) ); + graph[e] = kernel::pdf( r, { .a=200.*km , .b=5.5 } ); + } + + // Print the result + for (const auto& e : graph.edges() ) { + std::cout << e << " : " << graph[e] << '\n'; + } +} diff --git a/src/include/quetzal/demography/FlowHashMapImplementation.hpp b/src/include/quetzal/demography/FlowHashMapImplementation.hpp index 83b9d885..9ca0002f 100644 --- a/src/include/quetzal/demography/FlowHashMapImplementation.hpp +++ b/src/include/quetzal/demography/FlowHashMapImplementation.hpp @@ -148,7 +148,7 @@ template class FlowHashMapImplem /** * \brief Makes reverse_key_type hashable (can be stored in hash maps) */ - struct reverse_key_hash : public std::unary_function + struct reverse_key_hash { std::size_t operator()(const reverse_key_type &k) const { @@ -161,7 +161,7 @@ template class FlowHashMapImplem /** * \brief Makes key_type hashable (can be stored in hash maps) */ - struct key_hash : public std::unary_function + struct key_hash { std::size_t operator()(const key_type &k) const { diff --git a/src/include/quetzal/demography/FlowOnDiskImplementation.hpp b/src/include/quetzal/demography/FlowOnDiskImplementation.hpp index f1173dc2..401ef11f 100644 --- a/src/include/quetzal/demography/FlowOnDiskImplementation.hpp +++ b/src/include/quetzal/demography/FlowOnDiskImplementation.hpp @@ -72,7 +72,7 @@ template class FlowOnDiskImpleme /** * \brief Makes key_type hashable in unordered_map */ - struct key_hash : public std::unary_function + struct key_hash { std::size_t operator()(const key_type &k) const { diff --git a/src/include/quetzal/demography/dispersal_kernel.hpp b/src/include/quetzal/demography/dispersal_kernel.hpp index e4041ec1..94fa2e21 100644 --- a/src/include/quetzal/demography/dispersal_kernel.hpp +++ b/src/include/quetzal/demography/dispersal_kernel.hpp @@ -12,9 +12,8 @@ #include // tgamma pow #include // pi -#include // mp_units::tgamma, exp etc -#include // QuantityOf -#include +#include +#include namespace quetzal { @@ -34,6 +33,8 @@ using std::numbers::pi; template Distance = mp_units::quantity> struct gaussian { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; /// @brief Dispersal location kernel parameter struct param_type @@ -46,7 +47,7 @@ struct gaussian /// @param r The distance radius from the source to the target /// @param p Parameters of the distribution /// @return The value of the expression \f$ \frac{1}{\pi a^2}.exp(-\frac{r^2}{a^2}) \f$ - static constexpr auto pdf(Distance r, param_type const &p) + static constexpr pdf_result_type pdf(Distance r, param_type const &p) { assert(p.a > 0. * m && r >= 0. * m); return (1. / (pi * p.a * p.a)) * exp(-(r * r) / (p.a * p.a)); @@ -68,6 +69,8 @@ struct gaussian /// @details Suitable for frequent long-distance dispersal events and weak effect of distance close to the source. template Distance = mp_units::quantity> struct logistic { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; /// @brief Dispersal location kernel parameter struct param_type @@ -84,7 +87,7 @@ template Distance = mp_units::quantity Distance = mp_units::quantity Distance = mp_units::quantity> struct negative_exponential { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -130,7 +136,7 @@ struct negative_exponential /// @param r The distance radius from the source to the target /// @param p Parameters of the distribution /// @return The value of the expression \f$ \frac{1}{2 \pi a^2} exp(-\frac{r}{a}) \f$ - static constexpr auto pdf(Distance r, param_type const &p) + static constexpr pdf_result_type pdf(Distance r, param_type const &p) { using std::exp; Distance a = p.a; @@ -156,6 +162,9 @@ struct negative_exponential /// comparisons. template Distance = mp_units::quantity> struct exponential_power { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -170,7 +179,7 @@ template Distance = mp_units::quantity Distance = mp_units::quantity Distance = mp_units::quantity> struct two_dt { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -213,7 +225,7 @@ template Distance = mp_units::quantity Distance = mp_units::quantity Distance = mp_units::quantity> struct inverse_power_law { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -260,7 +275,7 @@ template Distance = mp_units::quantity Distance = mp_units::quantity Distance = mp_units::quantity> struct lognormal { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -306,7 +324,7 @@ template Distance = mp_units::quantity Distance = mp_units::quantity Distance = mp_units::quantity> struct gaussian_mixture { + /// @brief Probability density function value type + using pdf_result_type = quantity(Distance::reference)), typename Distance::rep>; + /// @brief Dispersal location kernel parameter struct param_type { @@ -350,7 +371,7 @@ template Distance = mp_units::quantity #include -#include // QuantityOf -#include +#include #include namespace quetzal::geography diff --git a/src/include/quetzal/geography/graph/detail/concepts.hpp b/src/include/quetzal/geography/graph/detail/concepts.hpp index 40606980..6ef09f10 100644 --- a/src/include/quetzal/geography/graph/detail/concepts.hpp +++ b/src/include/quetzal/geography/graph/detail/concepts.hpp @@ -29,10 +29,24 @@ concept is_any_of = (std::same_as || ...); template struct edge_construction { - static inline constexpr void delegate(auto s, auto t, auto& graph, auto const& grid) + template + requires + std::same_as and + std::default_initializable and + ( ! std::constructible_from ) + static inline constexpr void delegate(Graph::vertex_descriptor s, Graph::vertex_descriptor t, Graph& graph, Grid const& grid) + { + graph.add_edge(s, t, T{}); + } + + template + requires std::same_as and + std::constructible_from + static inline constexpr void delegate(Graph::vertex_descriptor s, Graph::vertex_descriptor t, Graph& graph, Grid const& grid) { graph.add_edge(s, t, T(s,t, grid)); } + }; template<>