diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 83124e92..c7489553 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,115 +3,136 @@ { "version": "2.0.0", "tasks": [ - // Release mode { - "label": "Configure Conan Release", + "label": "Clean Build Folder", + "type": "shell", + "command": "rm -r ${workspaceFolder}/build", + "problemMatcher": [], + }, + // TEST RELEASE + { + "label": "Configure Conan (Release)", "type": "shell", "command": "conan install conanfile.py --profile:build=conan/profiles/linux-armv8-gcc12-release --profile:host=conan/profiles/linux-armv8-gcc12-release --build=missing --output-folder=build", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, "dependsOn": [], }, { - "label": "Configure CMake Release", + "label": "Configure CMake (Release)", "type": "shell", "command": "cmake -B ${workspaceFolder}/build -S . -DCMAKE_BUILD_TYPE=Release --toolchain ${workspaceFolder}/build/conan_toolchain.cmake", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Configure Conan Release"], + "dependsOn": ["Configure Conan (Release)"], }, { - "label": "Unit Tests Build Release", + "label": "Build Unit Tests (Release)", "type": "shell", "command": "cmake --build ${workspaceFolder}/build --config Release", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Configure CMake Release"], + "dependsOn": ["Configure CMake (Release)"], }, { - "label": "Unit Tests Run Release", + "label": "Run Unit Tests (Release)", "type": "shell", "command": "cd ${workspaceFolder}/build && ctest -C Release --rerun-failed --output-on-failure", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Unit Tests Build Release"], + "problemMatcher": [], + "dependsOn": ["Build Unit Tests (Release)"], }, - // Debug mode + // TEST DEBUG { - "label": "Configure Conan Debug", + "label": "Configure Conan (Debug)", "type": "shell", "command": "conan install conanfile.py --profile:build=conan/profiles/linux-armv8-gcc12-debug --profile:host=conan/profiles/linux-armv8-gcc12-debug --build=missing --output-folder=build", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, "dependsOn": [], }, { - "label": "Configure CMake Debug", + "label": "Configure CMake (Debug)", "type": "shell", - "command": "cmake -B ${workspaceFolder}/build -S . -DCMAKE_BUILD_TYPE=Debug --toolchain ${workspaceFolder}/build/conan_toolchain.cmake", + "command": "cmake -B ${workspaceFolder}/build -S . -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON --toolchain ${workspaceFolder}/build/conan_toolchain.cmake", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Configure Conan Debug"], + "dependsOn": ["Configure Conan (Debug)"], }, { - "label": "Unit Tests Build Debug", + "label": "Build Unit Tests (Debug)", "type": "shell", "command": "cmake --build ${workspaceFolder}/build --config Debug", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Configure CMake Debug"], + "dependsOn": ["Configure CMake (Debug)"], }, { - "label": "Unit Tests Run Debug", + "label": "Run Unit Tests (Debug)", "type": "shell", "command": "cd ${workspaceFolder}/build && ctest -C Debug --rerun-failed --output-on-failure", "group": "test", "presentation": { "reveal": "always", - "panel": "new" + "panel": "shared" }, - "dependsOn": ["Unit Tests Build Debug"], + "problemMatcher": [], + "dependsOn": ["Build Unit Tests (Debug)"], }, + // TEMPLATE PROJECT { - "label": "Build Main", + "label": "Configure CMake Template (Debug)", "type": "shell", - "command": "g++ -g main.cpp -o main.out", - "group": { - "kind": "build", - "isDefault": true + "command": "cmake -B ${workspaceFolder}/build -S . -DCMAKE_BUILD_TYPE=Debug --toolchain ${workspaceFolder}/build/conan_toolchain.cmake", + "group": "test", + "presentation": { + "reveal": "always", + "panel": "shared" }, - //"dependsOn": ["Configure CMake"], + "dependsOn": ["Configure Conan (Debug)"], }, { - "label": "Clean Build Folder", + "label": "Build Template Project (Debug)", "type": "shell", - "command": "rm -r ${workspaceFolder}/build", - "group": { - "kind": "build", - "isDefault": true + "command": "cd ${workspaceFolder}/build && cmake --build . ", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "dependsOn": ["Configure CMake Template (Debug)"], + }, + { + "label": "Run Template Project (Debug)", + "type": "shell", + "command": "cd ${workspaceFolder}/build/template && ./main ", + "presentation": { + "reveal": "always", + "panel": "shared" }, - //"dependsOn": ["Configure CMake"], - } + "dependsOn": ["Build Template Project (Debug)"], + "problemMatcher": [], + }, ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 29e00dd7..3b28d880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,16 @@ add_subdirectory(src) # generate project documentation add_subdirectory(docs) -enable_testing() -# add unit tests -add_subdirectory(test) -# add usage example -add_subdirectory(example) +# add template project +add_subdirectory(template) + +# We default to build tests if option -D CMAKE_BUILD_TESTS=ON not provided (no FORCE, so the previous value, if set, stays) +set(BUILD_TESTS OFF CACHE STRING "Build module test") + +if(BUILD_TESTS) + enable_testing() + # add unit tests + add_subdirectory(test) + # add usage example + add_subdirectory(example) +endif() \ No newline at end of file diff --git a/docs/2-installation.md b/docs/2-installation.md index 4b3657bd..b433967d 100644 --- a/docs/2-installation.md +++ b/docs/2-installation.md @@ -12,25 +12,32 @@ The library provides many options for reuse, and the most suitable approach depe ## Structure and Dependencies -This repository contains three independent CMake-based projects: +This repository contains several independent CMake-based projects: +* `./template` + - entry point for user development and onboarding, [integrated with Visual Studio Code](#easiest-devcontainer-on-visual-studio-code) + - the `template/main.cpp` file includes the library and can be easily edited and recompiled without breaking the library. * `./src` - header-only project containing the Quetzal-CoaTL library - - `./src/CMakeList.txt` file is intended as an entry point for library users - it depends on the following external dependencies: - [GDAL](https://gdal.org/) for geographic data manipulation - [Boost](https://www.boost.org/doc/libs/master/index.html) for general purpose C++ utilities - [range-v3](https://github.com/ericniebler/range-v3), a range library for C++14/17/20. +* `./test` + - the unit and integration tests suite +* `./example` + - the example tests suite that are used in the Tutorial documentation section * `.` - entry point for library development and continuous deployment (CI/CD) processes. - it wraps `./src` project with a cmake project, examples, tests and docs. - - if you want to build the documentation without Conan, [Doxygen](https://www.doxygen.nl/) + - if you want to build the documentation, [Doxygen](https://www.doxygen.nl/) is required to generate the documentation from the annotated C++ sources. * `./test_package` - library installation and Conan packaging verification @remark -There are two entry points: +There are three entry points: +- The `./template/CMakeLists.txt` is not meant to be modified directly (unless the user decides), but it makes the template demonstration project [easier to use in Visual Studio Code](#easiest-devcontainer-on-visual-studio-code) - The top-level `./CMakeLists.txt` file is intended solely for Quetzal developers and contributors to build the entire project code (example and tests) with highly restrictive compilation flags. - The `./src/CMakeLists.txt` file, which contains a pure library definition and is intended for customers who prefer using CMake's `add_subdirectory()` to manage dependencies. @@ -53,6 +60,13 @@ To swiftly open an integrated development environment (IDE), begin coding to exp This will download the project, builds a Docker Image, manage the dependencies and automatically build and run the tests. If it's the first time you build the image, it may take some time, so you're free to go get a coffee! +Then, +1. navigate to the `template/main.cpp` file +2. Open the VSC Command Palette with `Shift + Command + P` (Mac) or `Ctrl + Shift + P` (Windows/Linux) +3. Click on `Run Task > Run Template Project (Debug)` +4. Read the output in the integrated terminal +5. Edit `template/main.cpp` to your liking and re-run the task, observe the difference in the terminal. + --- ### Copy diff --git a/example/expressive_3.cpp b/example/expressive_3.cpp index e06de2b6..927a8b99 100644 --- a/example/expressive_3.cpp +++ b/example/expressive_3.cpp @@ -30,7 +30,7 @@ int main() // We need to make choices here concerning how NA are handled auto suit = expressive::use([s_view](location_type x, time_type t){ return s_view(x,t).value_or(0.0); }); - auto elev = expressive::use([s_view](location_type x, time_type t){ return s_view(x,t).value_or(0.0); }); + auto elev = expressive::use([e_view](location_type x, time_type t){ return e_view(x,t).value_or(0.0); }); std::random_device rd; // a seed source for the random number engine std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd() diff --git a/example/geography_landscape_1.cpp b/example/geography_landscape_1.cpp index d972eb53..de6572f9 100644 --- a/example/geography_landscape_1.cpp +++ b/example/geography_landscape_1.cpp @@ -6,24 +6,16 @@ using namespace quetzal; int main() { - // What type you want to use to identify the variable: could also be e.g. an integer - using key_type = std::string; - - // What type you want to use to identify the variable: could also be e.g. a time period - using time_type = int; - - // Let's define a shorter alias - using landscape_type = quetzal::geography::landscape; - 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 ... 2011. - std::vector times(10); + // 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. - auto env = landscape_type::from_files( { {"bio1", file1}, {"bio12", file2} }, times ); + using landscape_type = quetzal::geography::landscape<>; + auto env = quetzal::geography::landscape<>::from_files( { {"bio1", file1}, {"bio12", file2} }, times ); std::cout << env << std::endl; // We indeed recorded 2 variables: bio1 and bio12 diff --git a/src/include/quetzal/geography/landscape.hpp b/src/include/quetzal/geography/landscape.hpp index 6e2055b4..1401213f 100644 --- a/src/include/quetzal/geography/landscape.hpp +++ b/src/include/quetzal/geography/landscape.hpp @@ -21,6 +21,7 @@ #include // std::cref #include #include +#include namespace quetzal::geography { @@ -35,7 +36,7 @@ namespace quetzal::geography /// @tparam Key A key used to uniquely identifie a variable, e.g. std::string. /// @tparam Time Type used as time period for every band, e.g. std::string with `4.2-0.3 ka` /// @ingroup geography - template + template class landscape { diff --git a/template/CMakeLists.txt b/template/CMakeLists.txt new file mode 100644 index 00000000..5df10d19 --- /dev/null +++ b/template/CMakeLists.txt @@ -0,0 +1,37 @@ +# requires cxx_std_20 new in CMake 3.12 +cmake_minimum_required(VERSION 3.12) + +# Tell find_package() to first search using Config mode before falling back to Module mode (for conan) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) + +# Global approach Gdal +find_package(GDAL REQUIRED) + +# range-v3 +find_package(range-v3) + +# Include Boost as an imported target: 1.73 important for compilation with gcc 10 and C++20 +find_package(Boost 1.79 REQUIRED COMPONENTS unit_test_framework filesystem serialization) +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) # Find Boost headers only +ENDIF() + +# Copy the data required by I/O examples +file(COPY data DESTINATION ${CMAKE_BINARY_DIR}/template) + +# Create director to store the examples output (for future doc) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/template/output) + +# Add compile target +add_executable(main main.cpp) +# Require the standard +target_compile_features(main PUBLIC cxx_std_20) +# Ignore warnings about subtle ABI change +target_compile_options(main PUBLIC "-Wno-psabi") +# Link to Boost libraries AND other targets and dependencies +target_link_libraries(main quetzal::quetzal boost::boost GDAL::GDAL range-v3::range-v3) +# Specifies include directories to use when compiling a given target +target_include_directories( + main PRIVATE + $ + $) diff --git a/template/data/bio1.tif b/template/data/bio1.tif new file mode 100644 index 00000000..2b095006 Binary files /dev/null and b/template/data/bio1.tif differ diff --git a/template/data/bio12.tif b/template/data/bio12.tif new file mode 100644 index 00000000..0b4c9a82 Binary files /dev/null and b/template/data/bio12.tif differ diff --git a/template/data/elevation.tif b/template/data/elevation.tif new file mode 100644 index 00000000..0b4c9a82 Binary files /dev/null and b/template/data/elevation.tif differ diff --git a/template/data/suitability.tif b/template/data/suitability.tif new file mode 100644 index 00000000..3d16cd00 Binary files /dev/null and b/template/data/suitability.tif differ diff --git a/template/main.cpp b/template/main.cpp new file mode 100644 index 00000000..ca60f38e --- /dev/null +++ b/template/main.cpp @@ -0,0 +1,78 @@ +#include "quetzal/quetzal.hpp" + +#include +#include +#include + +int main() +{ + // Load the suitability and elevation maps + auto file1 = std::filesystem::current_path() / "data/suitability.tif"; + auto file2 = std::filesystem::current_path() / "data/elevation.tif"; + + // The rasters 10 bands are indexed by the year they represent: 2001 ... 2010 + std::vector times(10); + std::iota(times.begin(), times.end(), 2001); + + // Initialize the landscape: for each spatial variable a string key and a file value, for all a time series. + auto landscape = quetzal::geography::landscape<>::from_files({{"suit", file1}, {"DEM", file2}}, times); + std::cout << landscape << std::endl; + + // Declares some type aliases to shorten notation + using location_type = quetzal::geography::landscape<>::location_descriptor; + using time_type= quetzal::geography::landscape<>::time_descriptor; + + // lightweight functors for suitability and digital elevation models that return empty optionals where NA are encounters + auto suit_view = landscape["suit"].to_view(); + auto elev_view = landscape["DEM"].to_view(); + + // We need to make choices here concerning how NA are handled + auto suit = quetzal::expressive::use([&](location_type x, time_type t) + { return suit_view(x, t).value_or(0.0); }); + auto elev = quetzal::expressive::use([&](location_type x, time_type t) + { return elev_view(x, t).value_or(0.0); }); + + std::random_device rd; // a seed source for the random number engine + std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd() + + // Small-scale ice-free shelters for the species randomly pop above the snow cover at high-altitude (>123m) + auto nunatak_suitability = [&](location_type x, time_type t) + { + std::bernoulli_distribution d(0.1); // give "false" 9/10 of the time + bool is_nunatak = d(gen); + return (elev(x, t) >= 123.0) ? static_cast(is_nunatak) * suit(x, t) : suit(x, t); + }; + + // To allow dispersal across ocean, we can compose expressions: + auto capacity_with_rafting = [&](location_type x, time_type t) + { + std::bernoulli_distribution d(0.1); // give "false" 9/10 of the time + if (suit(x, t) == 0.0 and elev(x, t) == 0.0) // ocean cell case: + return static_cast(d(gen)) * 2; // will (rarely) allow 2 individuals to survive in the ocean cell + else if (suit(x, 0) == 0.0 and elev(x, t) > 0.0) // unsuitable continental cell case: + return 0.0; // suitability is minimal, so should be the capacity + else // habitable continental cells: + return nunatak_suitability(x,t); // evaluates suitability simulating nunataks + }; + + // Account for different dispersal modes + auto friction_with_rafting = [&](location_type x, time_type t) + { + if (suit(x, t) == 0.0 and elev(x, t) == 0.0) // ocean cell case: + return 0.0; // the raft should move freely on the water + else if (suit(x, 0) == 0.0 and elev(x, t) > 0.0) // hostile continental cell case: + return 1.00; // max friction as the cell is not attractive + else // favorable continental cell case: + return 1.0 - suit(x, 0); // higher the suitability, easier the travel + }; + + // Expressions can be evaluated with a location_type and a time_type argument: + auto x = *landscape.locations().begin(); + auto t = *landscape.times().begin(); + + std::cout << "Friction f( x = " << x << ", t = " << t << " ) = " << friction_with_rafting(x,t) << std::endl; + std::cout << "Carrying capacity K( x = " << x << ", t = " << t << " ) = " << capacity_with_rafting(x,t) << std::endl; + + // Spatial graph + +}