From 42a27b3175196fc24c28f0ae79e4d543d4abe7f4 Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Wed, 15 Jan 2025 12:14:14 +0100 Subject: [PATCH 001/121] add fastscapelib cmake config --- CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 571808902f1..bb32c086386 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,6 +231,28 @@ if(ASPECT_WITH_FASTSCAPE) endif() +# Fastscapelib (C++) +set(ASPECT_WITH_FASTSCAPELIB OFF CACHE BOOL "Whether the user wants to compile ASPECT with the landscape evolution C++ code Fastscape(lib), or not.") +message(STATUS "Using ASPECT_WITH_FASTSCAPELIB = '${ASPECT_WITH_FASTSCAPE}'") +if(ASPECT_WITH_FASTSCAPELIB) + find_package(fastscapelib CONFIG) + if(${fastscapelib_FOUND}) + message(STATUS "Using ASPECT_WITH_FASTSCAPELIB = '${ASPECT_WITH_FASTSCAPELIB}'") + message(STATUS " fastscapelib_INCLUDE_DIR: ${fastscapelib_INCLUDE_DIRS}") + message(STATUS " fastscapelib_VERSION: ${fastscapelib_VERSION}") + # fastscapelib dependencies + find_package(xtensor REQUIRED) + message(STATUS " xtensor_INCLUDE_DIR: ${xtensor_INCLUDE_DIRS}") + message(STATUS " xtensor_VERSION: ${xtensor_VERSION}") + else() + message(STATUS "Fastscapelib not found. Disabling ASPECT_WITH_FASTSCAPELIB. You can specify a hint to your installation directory with fastscapelib_DIR.") + set(ASPECT_WITH_FASTSCAPELIB OFF CACHE BOOL "" FORCE) + endif() +else() + message(STATUS "Using ASPECT_WITH_FASTSCAPELIB = 'OFF'") +endif() + + # NetCDF (c including parallel) set(ASPECT_WITH_NETCDF ON CACHE BOOL "Check if the user wants to compile ASPECT with the NetCDF libraries.") @@ -882,6 +904,15 @@ if (FASTSCAPE) endforeach() endif() +# Fastscapelib +if (${fastscapelib_FOUND}) + message(STATUS "Linking ASPECT against Fastscapelib (header-only)") + foreach(_T ${TARGET_EXECUTABLES}) + target_include_directories(${_T} PRIVATE ${fastscapelib_INCLUDE_DIRS}) + target_include_directories(${_T} PRIVATE ${xtensor_INCLUDE_DIRS}) + endforeach() +endif() + # NetCDF if(${NETCDF_FOUND}) message(STATUS "Linking ASPECT against NetCDF") From 14087d875b28d24ec600008b90a02bebb8240ebc Mon Sep 17 00:00:00 2001 From: Minerallo Date: Sat, 8 Jul 2023 11:52:46 -0700 Subject: [PATCH 002/121] Add fastscapelib xtensor xtl to contrib and Cmakelist --- CMakeLists.txt | 6 + .../fastscapelib/algo/flow_routing.hpp | 505 +++ .../include/fastscapelib/algo/pflood.hpp | 243 + .../fastscapelib/eroders/diffusion_adi.hpp | 380 ++ .../include/fastscapelib/eroders/spl.hpp | 393 ++ .../include/fastscapelib/flow/basin_graph.hpp | 814 ++++ .../include/fastscapelib/flow/flow_graph.hpp | 341 ++ .../fastscapelib/flow/flow_graph_impl.hpp | 411 ++ .../fastscapelib/flow/flow_operator.hpp | 497 ++ .../include/fastscapelib/flow/flow_router.hpp | 248 + .../fastscapelib/flow/flow_snapshot.hpp | 167 + .../fastscapelib/flow/sink_resolver.hpp | 359 ++ .../include/fastscapelib/grid/base.hpp | 722 +++ .../fastscapelib/grid/profile_grid.hpp | 411 ++ .../include/fastscapelib/grid/raster_grid.hpp | 1024 +++++ .../fastscapelib/grid/structured_grid.hpp | 115 + .../include/fastscapelib/grid/trimesh.hpp | 427 ++ .../include/fastscapelib/utils/consts.hpp | 33 + .../include/fastscapelib/utils/iterators.hpp | 186 + .../include/fastscapelib/utils/union_find.hpp | 174 + .../include/fastscapelib/utils/utils.hpp | 107 + .../fastscapelib/utils/xtensor_utils.hpp | 73 + .../include/fastscapelib/version.hpp | 22 + .../xtensor/include/xtensor/xaccessible.hpp | 347 ++ .../xtensor/include/xtensor/xaccumulator.hpp | 364 ++ contrib/xtensor/include/xtensor/xadapt.hpp | 921 ++++ contrib/xtensor/include/xtensor/xarray.hpp | 667 +++ contrib/xtensor/include/xtensor/xassign.hpp | 1367 ++++++ .../include/xtensor/xaxis_iterator.hpp | 349 ++ .../include/xtensor/xaxis_slice_iterator.hpp | 367 ++ .../include/xtensor/xblockwise_reducer.hpp | 532 +++ .../xtensor/xblockwise_reducer_functors.hpp | 503 +++ .../xtensor/include/xtensor/xbroadcast.hpp | 459 ++ .../include/xtensor/xbuffer_adaptor.hpp | 1282 ++++++ contrib/xtensor/include/xtensor/xbuilder.hpp | 1215 +++++ .../include/xtensor/xchunked_array.hpp | 686 +++ .../include/xtensor/xchunked_assign.hpp | 378 ++ .../xtensor/include/xtensor/xchunked_view.hpp | 295 ++ contrib/xtensor/include/xtensor/xcomplex.hpp | 264 ++ .../xtensor/include/xtensor/xcontainer.hpp | 1192 +++++ contrib/xtensor/include/xtensor/xcsv.hpp | 275 ++ .../xtensor/include/xtensor/xdynamic_view.hpp | 808 ++++ contrib/xtensor/include/xtensor/xeval.hpp | 177 + .../xtensor/include/xtensor/xexception.hpp | 373 ++ .../xtensor/include/xtensor/xexpression.hpp | 769 ++++ .../include/xtensor/xexpression_holder.hpp | 273 ++ .../include/xtensor/xexpression_traits.hpp | 198 + contrib/xtensor/include/xtensor/xfixed.hpp | 991 ++++ contrib/xtensor/include/xtensor/xfunction.hpp | 1157 +++++ .../xtensor/include/xtensor/xfunctor_view.hpp | 1649 +++++++ .../xtensor/include/xtensor/xgenerator.hpp | 513 +++ .../xtensor/include/xtensor/xhistogram.hpp | 613 +++ .../xtensor/include/xtensor/xindex_view.hpp | 851 ++++ contrib/xtensor/include/xtensor/xinfo.hpp | 142 + contrib/xtensor/include/xtensor/xio.hpp | 832 ++++ contrib/xtensor/include/xtensor/xiterable.hpp | 1369 ++++++ contrib/xtensor/include/xtensor/xiterator.hpp | 1253 +++++ contrib/xtensor/include/xtensor/xjson.hpp | 189 + contrib/xtensor/include/xtensor/xlayout.hpp | 104 + .../xtensor/include/xtensor/xmanipulation.hpp | 1145 +++++ .../xtensor/include/xtensor/xmasked_view.hpp | 676 +++ contrib/xtensor/include/xtensor/xmath.hpp | 3329 ++++++++++++++ contrib/xtensor/include/xtensor/xmime.hpp | 431 ++ .../include/xtensor/xmultiindex_iterator.hpp | 131 + contrib/xtensor/include/xtensor/xnoalias.hpp | 230 + contrib/xtensor/include/xtensor/xnorm.hpp | 661 +++ contrib/xtensor/include/xtensor/xnpy.hpp | 803 ++++ .../xtensor/include/xtensor/xoffset_view.hpp | 95 + .../xtensor/include/xtensor/xoperation.hpp | 997 ++++ contrib/xtensor/include/xtensor/xoptional.hpp | 1354 ++++++ .../include/xtensor/xoptional_assembly.hpp | 745 +++ .../xtensor/xoptional_assembly_base.hpp | 1049 +++++ .../xtensor/xoptional_assembly_storage.hpp | 571 +++ contrib/xtensor/include/xtensor/xpad.hpp | 323 ++ contrib/xtensor/include/xtensor/xrandom.hpp | 1007 +++++ contrib/xtensor/include/xtensor/xreducer.hpp | 1897 ++++++++ contrib/xtensor/include/xtensor/xrepeat.hpp | 703 +++ contrib/xtensor/include/xtensor/xscalar.hpp | 1098 +++++ contrib/xtensor/include/xtensor/xsemantic.hpp | 759 ++++ .../include/xtensor/xset_operation.hpp | 213 + contrib/xtensor/include/xtensor/xshape.hpp | 578 +++ contrib/xtensor/include/xtensor/xslice.hpp | 1671 +++++++ contrib/xtensor/include/xtensor/xsort.hpp | 1292 ++++++ contrib/xtensor/include/xtensor/xstorage.hpp | 1984 ++++++++ .../xtensor/include/xtensor/xstrided_view.hpp | 807 ++++ .../include/xtensor/xstrided_view_base.hpp | 970 ++++ contrib/xtensor/include/xtensor/xstrides.hpp | 915 ++++ contrib/xtensor/include/xtensor/xtensor.hpp | 983 ++++ .../include/xtensor/xtensor_config.hpp | 137 + .../include/xtensor/xtensor_forward.hpp | 209 + .../xtensor/include/xtensor/xtensor_simd.hpp | 333 ++ contrib/xtensor/include/xtensor/xutils.hpp | 987 ++++ .../xtensor/include/xtensor/xvectorize.hpp | 104 + contrib/xtensor/include/xtensor/xview.hpp | 2315 ++++++++++ .../xtensor/include/xtensor/xview_utils.hpp | 283 ++ contrib/xtl/include/xtl/xany.hpp | 477 ++ contrib/xtl/include/xtl/xbase64.hpp | 77 + .../xtl/include/xtl/xbasic_fixed_string.hpp | 2434 ++++++++++ contrib/xtl/include/xtl/xclosure.hpp | 435 ++ contrib/xtl/include/xtl/xcompare.hpp | 179 + contrib/xtl/include/xtl/xcomplex.hpp | 1361 ++++++ contrib/xtl/include/xtl/xcomplex_sequence.hpp | 578 +++ contrib/xtl/include/xtl/xdynamic_bitset.hpp | 1352 ++++++ contrib/xtl/include/xtl/xfunctional.hpp | 44 + contrib/xtl/include/xtl/xhalf_float.hpp | 41 + contrib/xtl/include/xtl/xhalf_float_impl.hpp | 4019 +++++++++++++++++ contrib/xtl/include/xtl/xhash.hpp | 208 + .../xtl/include/xtl/xhierarchy_generator.hpp | 73 + contrib/xtl/include/xtl/xiterator_base.hpp | 422 ++ contrib/xtl/include/xtl/xjson.hpp | 98 + contrib/xtl/include/xtl/xmasked_value.hpp | 546 +++ .../xtl/include/xtl/xmasked_value_meta.hpp | 41 + contrib/xtl/include/xtl/xmeta_utils.hpp | 640 +++ contrib/xtl/include/xtl/xmultimethods.hpp | 422 ++ contrib/xtl/include/xtl/xoptional.hpp | 1331 ++++++ contrib/xtl/include/xtl/xoptional_meta.hpp | 141 + .../xtl/include/xtl/xoptional_sequence.hpp | 622 +++ contrib/xtl/include/xtl/xplatform.hpp | 42 + contrib/xtl/include/xtl/xproxy_wrapper.hpp | 48 + contrib/xtl/include/xtl/xsequence.hpp | 215 + contrib/xtl/include/xtl/xspan.hpp | 21 + contrib/xtl/include/xtl/xspan_impl.hpp | 779 ++++ contrib/xtl/include/xtl/xsystem.hpp | 114 + contrib/xtl/include/xtl/xtl_config.hpp | 39 + contrib/xtl/include/xtl/xtype_traits.hpp | 452 ++ contrib/xtl/include/xtl/xvariant.hpp | 206 + contrib/xtl/include/xtl/xvariant_impl.hpp | 2818 ++++++++++++ contrib/xtl/include/xtl/xvisitor.hpp | 195 + 128 files changed, 83637 insertions(+) create mode 100644 contrib/fastscape/include/fastscapelib/algo/flow_routing.hpp create mode 100644 contrib/fastscape/include/fastscapelib/algo/pflood.hpp create mode 100644 contrib/fastscape/include/fastscapelib/eroders/diffusion_adi.hpp create mode 100644 contrib/fastscape/include/fastscapelib/eroders/spl.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/basin_graph.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/flow_graph.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/flow_graph_impl.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/flow_operator.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/flow_router.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/flow_snapshot.hpp create mode 100644 contrib/fastscape/include/fastscapelib/flow/sink_resolver.hpp create mode 100644 contrib/fastscape/include/fastscapelib/grid/base.hpp create mode 100644 contrib/fastscape/include/fastscapelib/grid/profile_grid.hpp create mode 100644 contrib/fastscape/include/fastscapelib/grid/raster_grid.hpp create mode 100644 contrib/fastscape/include/fastscapelib/grid/structured_grid.hpp create mode 100644 contrib/fastscape/include/fastscapelib/grid/trimesh.hpp create mode 100644 contrib/fastscape/include/fastscapelib/utils/consts.hpp create mode 100644 contrib/fastscape/include/fastscapelib/utils/iterators.hpp create mode 100644 contrib/fastscape/include/fastscapelib/utils/union_find.hpp create mode 100644 contrib/fastscape/include/fastscapelib/utils/utils.hpp create mode 100644 contrib/fastscape/include/fastscapelib/utils/xtensor_utils.hpp create mode 100644 contrib/fastscape/include/fastscapelib/version.hpp create mode 100644 contrib/xtensor/include/xtensor/xaccessible.hpp create mode 100644 contrib/xtensor/include/xtensor/xaccumulator.hpp create mode 100644 contrib/xtensor/include/xtensor/xadapt.hpp create mode 100644 contrib/xtensor/include/xtensor/xarray.hpp create mode 100644 contrib/xtensor/include/xtensor/xassign.hpp create mode 100644 contrib/xtensor/include/xtensor/xaxis_iterator.hpp create mode 100644 contrib/xtensor/include/xtensor/xaxis_slice_iterator.hpp create mode 100644 contrib/xtensor/include/xtensor/xblockwise_reducer.hpp create mode 100644 contrib/xtensor/include/xtensor/xblockwise_reducer_functors.hpp create mode 100644 contrib/xtensor/include/xtensor/xbroadcast.hpp create mode 100644 contrib/xtensor/include/xtensor/xbuffer_adaptor.hpp create mode 100644 contrib/xtensor/include/xtensor/xbuilder.hpp create mode 100644 contrib/xtensor/include/xtensor/xchunked_array.hpp create mode 100644 contrib/xtensor/include/xtensor/xchunked_assign.hpp create mode 100644 contrib/xtensor/include/xtensor/xchunked_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xcomplex.hpp create mode 100644 contrib/xtensor/include/xtensor/xcontainer.hpp create mode 100644 contrib/xtensor/include/xtensor/xcsv.hpp create mode 100644 contrib/xtensor/include/xtensor/xdynamic_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xeval.hpp create mode 100644 contrib/xtensor/include/xtensor/xexception.hpp create mode 100644 contrib/xtensor/include/xtensor/xexpression.hpp create mode 100644 contrib/xtensor/include/xtensor/xexpression_holder.hpp create mode 100644 contrib/xtensor/include/xtensor/xexpression_traits.hpp create mode 100644 contrib/xtensor/include/xtensor/xfixed.hpp create mode 100644 contrib/xtensor/include/xtensor/xfunction.hpp create mode 100644 contrib/xtensor/include/xtensor/xfunctor_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xgenerator.hpp create mode 100644 contrib/xtensor/include/xtensor/xhistogram.hpp create mode 100644 contrib/xtensor/include/xtensor/xindex_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xinfo.hpp create mode 100644 contrib/xtensor/include/xtensor/xio.hpp create mode 100644 contrib/xtensor/include/xtensor/xiterable.hpp create mode 100644 contrib/xtensor/include/xtensor/xiterator.hpp create mode 100644 contrib/xtensor/include/xtensor/xjson.hpp create mode 100644 contrib/xtensor/include/xtensor/xlayout.hpp create mode 100644 contrib/xtensor/include/xtensor/xmanipulation.hpp create mode 100644 contrib/xtensor/include/xtensor/xmasked_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xmath.hpp create mode 100644 contrib/xtensor/include/xtensor/xmime.hpp create mode 100644 contrib/xtensor/include/xtensor/xmultiindex_iterator.hpp create mode 100644 contrib/xtensor/include/xtensor/xnoalias.hpp create mode 100644 contrib/xtensor/include/xtensor/xnorm.hpp create mode 100644 contrib/xtensor/include/xtensor/xnpy.hpp create mode 100644 contrib/xtensor/include/xtensor/xoffset_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xoperation.hpp create mode 100644 contrib/xtensor/include/xtensor/xoptional.hpp create mode 100644 contrib/xtensor/include/xtensor/xoptional_assembly.hpp create mode 100644 contrib/xtensor/include/xtensor/xoptional_assembly_base.hpp create mode 100644 contrib/xtensor/include/xtensor/xoptional_assembly_storage.hpp create mode 100644 contrib/xtensor/include/xtensor/xpad.hpp create mode 100644 contrib/xtensor/include/xtensor/xrandom.hpp create mode 100644 contrib/xtensor/include/xtensor/xreducer.hpp create mode 100644 contrib/xtensor/include/xtensor/xrepeat.hpp create mode 100644 contrib/xtensor/include/xtensor/xscalar.hpp create mode 100644 contrib/xtensor/include/xtensor/xsemantic.hpp create mode 100644 contrib/xtensor/include/xtensor/xset_operation.hpp create mode 100644 contrib/xtensor/include/xtensor/xshape.hpp create mode 100644 contrib/xtensor/include/xtensor/xslice.hpp create mode 100644 contrib/xtensor/include/xtensor/xsort.hpp create mode 100644 contrib/xtensor/include/xtensor/xstorage.hpp create mode 100644 contrib/xtensor/include/xtensor/xstrided_view.hpp create mode 100644 contrib/xtensor/include/xtensor/xstrided_view_base.hpp create mode 100644 contrib/xtensor/include/xtensor/xstrides.hpp create mode 100644 contrib/xtensor/include/xtensor/xtensor.hpp create mode 100644 contrib/xtensor/include/xtensor/xtensor_config.hpp create mode 100644 contrib/xtensor/include/xtensor/xtensor_forward.hpp create mode 100644 contrib/xtensor/include/xtensor/xtensor_simd.hpp create mode 100644 contrib/xtensor/include/xtensor/xutils.hpp create mode 100644 contrib/xtensor/include/xtensor/xvectorize.hpp create mode 100644 contrib/xtensor/include/xtensor/xview.hpp create mode 100644 contrib/xtensor/include/xtensor/xview_utils.hpp create mode 100644 contrib/xtl/include/xtl/xany.hpp create mode 100644 contrib/xtl/include/xtl/xbase64.hpp create mode 100644 contrib/xtl/include/xtl/xbasic_fixed_string.hpp create mode 100644 contrib/xtl/include/xtl/xclosure.hpp create mode 100644 contrib/xtl/include/xtl/xcompare.hpp create mode 100644 contrib/xtl/include/xtl/xcomplex.hpp create mode 100644 contrib/xtl/include/xtl/xcomplex_sequence.hpp create mode 100644 contrib/xtl/include/xtl/xdynamic_bitset.hpp create mode 100644 contrib/xtl/include/xtl/xfunctional.hpp create mode 100644 contrib/xtl/include/xtl/xhalf_float.hpp create mode 100644 contrib/xtl/include/xtl/xhalf_float_impl.hpp create mode 100644 contrib/xtl/include/xtl/xhash.hpp create mode 100644 contrib/xtl/include/xtl/xhierarchy_generator.hpp create mode 100644 contrib/xtl/include/xtl/xiterator_base.hpp create mode 100644 contrib/xtl/include/xtl/xjson.hpp create mode 100644 contrib/xtl/include/xtl/xmasked_value.hpp create mode 100644 contrib/xtl/include/xtl/xmasked_value_meta.hpp create mode 100644 contrib/xtl/include/xtl/xmeta_utils.hpp create mode 100644 contrib/xtl/include/xtl/xmultimethods.hpp create mode 100644 contrib/xtl/include/xtl/xoptional.hpp create mode 100644 contrib/xtl/include/xtl/xoptional_meta.hpp create mode 100644 contrib/xtl/include/xtl/xoptional_sequence.hpp create mode 100644 contrib/xtl/include/xtl/xplatform.hpp create mode 100644 contrib/xtl/include/xtl/xproxy_wrapper.hpp create mode 100644 contrib/xtl/include/xtl/xsequence.hpp create mode 100644 contrib/xtl/include/xtl/xspan.hpp create mode 100644 contrib/xtl/include/xtl/xspan_impl.hpp create mode 100644 contrib/xtl/include/xtl/xsystem.hpp create mode 100644 contrib/xtl/include/xtl/xtl_config.hpp create mode 100644 contrib/xtl/include/xtl/xtype_traits.hpp create mode 100644 contrib/xtl/include/xtl/xvariant.hpp create mode 100644 contrib/xtl/include/xtl/xvariant_impl.hpp create mode 100644 contrib/xtl/include/xtl/xvisitor.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bb32c086386..1e6d58feea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,6 +380,12 @@ if(ASPECT_WITH_WORLD_BUILDER) endif() message(STATUS "") +# include Fastscape and Xtensor files +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/contrib/fastscape/include/") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/contrib/xtensor/include/") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/contrib/xtensor/include/xtensor/") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/contrib/xtl/include/") + # # Other stuff about external tools and how we interact with the system: diff --git a/contrib/fastscape/include/fastscapelib/algo/flow_routing.hpp b/contrib/fastscape/include/fastscapelib/algo/flow_routing.hpp new file mode 100644 index 00000000000..8fce047b391 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/algo/flow_routing.hpp @@ -0,0 +1,505 @@ +/** + * @brief Functions used to route (water) flow on a topographic + * surface and compute flow path-related features or structures. + * + */ +#ifndef FASTSCAPELIB_ALGO_FLOW_ROUTING_H +#define FASTSCAPELIB_ALGO_FLOW_ROUTING_H + +#include +#include +#include +#include +#include +#include + +#include "xtensor/xtensor.hpp" +#include "xtensor/xview.hpp" +#include "xtensor/xmanipulation.hpp" + +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/consts.hpp" + + +namespace fastscapelib +{ + namespace detail + { + + + inline auto get_d8_distances(double dx, double dy) -> std::array + { + std::array d8_dists; + + for (std::size_t k = 0; k < 9; ++k) + { + d8_dists[k] + = std::sqrt(std::pow(dy * fastscapelib::consts::d8_row_offsets[k], 2.0) + + std::pow(dx * fastscapelib::consts::d8_col_offsets[k], 2.0)); + } + + return d8_dists; + } + + + template + void add2stack(index_t& nstack, S&& stack, N&& ndonors, D&& donors, index_t inode) + { + for (index_t k = 0; k < ndonors(inode); ++k) + { + const auto idonor = donors(inode, k); + stack(nstack) = idonor; + ++nstack; + add2stack(nstack, stack, ndonors, donors, idonor); + } + } + + + /** + * compute_receivers_d8 implementation. + */ + template + void compute_receivers_d8_impl(R&& receivers, + D&& dist2receivers, + E&& elevation, + A&& active_nodes, + double dx, + double dy) + { + using elev_t = typename std::decay_t::value_type; + const auto d8_dists = detail::get_d8_distances(dx, dy); + + const auto elev_shape = elevation.shape(); + const auto nrows = static_cast(elev_shape[0]); + const auto ncols = static_cast(elev_shape[1]); + + for (index_t r = 0; r < nrows; ++r) + { + for (index_t c = 0; c < ncols; ++c) + { + const index_t inode = r * ncols + c; + + receivers(inode) = inode; + dist2receivers(inode) = 0.; + + if (!active_nodes(r, c)) + { + continue; + } + + double slope_max = std::numeric_limits::min(); + + for (std::size_t k = 1; k <= 8; ++k) + { + const index_t kr = r + fastscapelib::consts::d8_row_offsets[k]; + const index_t kc = c + fastscapelib::consts::d8_col_offsets[k]; + + if (!fastscapelib::detail::in_bounds(elev_shape, kr, kc)) + { + continue; + } + + const index_t ineighbor = kr * ncols + kc; + const double slope = (elevation(r, c) - elevation(kr, kc)) / d8_dists[k]; + + if (slope > slope_max) + { + slope_max = slope; + receivers(inode) = ineighbor; + dist2receivers(inode) = d8_dists[k]; + } + } + } + } + } + + + /** + * compute_donors implementation. + */ + template + void compute_donors_impl(N&& ndonors, D&& donors, R&& receivers) + { + const auto nnodes = static_cast(receivers.size()); + + std::fill(ndonors.begin(), ndonors.end(), 0); + + for (index_t inode = 0; inode < nnodes; ++inode) + { + if (receivers(inode) != inode) + { + index_t irec = receivers(inode); + donors(irec, ndonors(irec)) = inode; + ++ndonors(irec); + } + } + } + + + /** + * compute_stack implementation. + */ + template + void compute_stack_impl(S&& stack, N&& ndonors, D&& donors, R&& receivers) + { + const auto nnodes = static_cast(receivers.size()); + index_t nstack = 0; + + for (index_t inode = 0; inode < nnodes; ++inode) + { + if (receivers(inode) == inode) + { + stack(nstack) = inode; + ++nstack; + add2stack(nstack, stack, ndonors, donors, inode); + } + } + } + + + /** + * compute_basins implementation. + */ + template + index_t compute_basins_impl(B&& basins, O&& outlets_or_pits, S&& stack, R&& receivers) + { + index_t ibasin = -1; + + for (auto&& istack : stack) + { + const auto irec = receivers(istack); + + if (irec == istack) + { + ++ibasin; + outlets_or_pits(ibasin) = istack; + } + + basins(istack) = ibasin; + } + + index_t nbasins = ibasin + 1; + + return nbasins; + } + + + /** + * find_pits implementation. + */ + template + index_t find_pits_impl(P&& pits, O&& outlets_or_pits, A&& active_nodes, index_t nbasins) + { + index_t ipit = 0; + const auto active_nodes_flat = xt::flatten(active_nodes); + + for (index_t ibasin = 0; ibasin < nbasins; ++ibasin) + { + const index_t inode = outlets_or_pits(ibasin); + + if (active_nodes_flat(inode)) + { + pits(ipit) = inode; + ++ipit; + } + } + + index_t npits = ipit; + + return npits; + } + + + /** + * compute_drainage_area implementation. + */ + template + void compute_drainage_area_impl(D&& drainage_area, C&& cell_area, S&& stack, R&& receivers) + { + // reset drainage area values (must use a view to prevent resizing + // drainage_area to 0-d when cell_area is 0-d!) + auto drainage_area_ = xt::view(drainage_area, xt::all(), xt::all()); + drainage_area_ = cell_area; + + // update drainage area values + auto drainage_area_flat = xt::flatten(drainage_area); + + for (auto inode = stack.crbegin(); inode != stack.crend(); ++inode) + { + if (receivers(*inode) != *inode) + { + drainage_area_flat(receivers(*inode)) += drainage_area_flat(*inode); + } + } + } + } // namespace detail + + + /** + * Compute flow receivers on a rectangular grid using D8 single flow + * routing method. + * + * Each node on the grid is assigned one receiver among its 8 direct + * neighboring nodes according to the steepest slope (O’Callaghan and + * Mark, 1984). + * + * When no downslope neighbor exist (i.e., pit nodes or grid + * boundaries), the assigned receiver is the node itself. When two or + * more neighbors have the same slope, the chosen neighbor is the + * first one considered by the algorithm. + * + * This function also computes the planimetric distance between the + * node and its receiver, which equals to the grid spacing in x or y + * or to the distance between two diagonal neighbor nodes or 0. + * + * @param receivers : ``[intent=out, shape=(nnodes)]`` + * Index of flow receiver at grid node. + * @param dist2receivers : ``[intent=out, shape=(nnodes)]`` + * Distance to receiver at grid node. + * @param elevation : ``[intent=in, shape=(nrows, ncols)]`` + * Topographic elevation at grid node + * @param active_nodes : ``[intent=in, shape=(nrows, ncols)]`` + * Boolean array for boundaries + * @param dx : ``[intent=in]`` + * Grid spacing in x + * @param dy : ``[intent=in]`` + * Grid spacing in y + */ + template + void compute_receivers_d8(xtensor_t& receivers, + xtensor_t& dist2receivers, + const xtensor_t& elevation, + const xtensor_t& active_nodes, + double dx, + double dy) + { + detail::compute_receivers_d8_impl(receivers.derived_cast(), + dist2receivers.derived_cast(), + elevation.derived_cast(), + active_nodes.derived_cast(), + dx, + dy); + } + + + namespace detail + { + template + void compute_receivers_impl(R&& receivers, D&& dist2receivers, E&& elevation, G& grid) + { + using neighbors_type = typename G::neighbors_type; + + double slope, slope_max; + neighbors_type neighbors; + + for (std::size_t i = 0; i < grid.size(); ++i) + { + receivers(i, 0) = i; + dist2receivers(i, 0) = 0; + slope_max = std::numeric_limits::min(); + + grid.neighbors(i, neighbors); + + for (auto n = neighbors.begin(); n != neighbors.end(); ++n) + { + slope = (elevation.data()[i] - elevation.data()[n->idx]) / n->distance; + + if (slope > slope_max) + { + slope_max = slope; + receivers(i, 0) = n->idx; + dist2receivers(i, 0) = n->distance; + } + } + } + } + } + + template + void compute_receivers(xtensor_t& receivers, + xtensor_t& dist2receivers, + const xtensor_t& elevation, + G& grid) + { + detail::compute_receivers_impl(receivers.derived_cast(), + dist2receivers.derived_cast(), + elevation.derived_cast(), + grid); + } + + + /** + * Compute flow donors for each grid/mesh node. + * + * Flow donors are retrieved by simply inverting flow + * receivers. + * + * @param ndonors : ``[intent=out, shape=(nnodes)]`` + * Number of flow donors at grid node. + * @param donors : ``[intent=out, shape=(nnodes, :)]`` + * Indexes of flow donors at grid node. + * @param receivers : ``[intent=in, shape=(nnodes)]`` + * Index of flow receiver at grid node. + */ + template + void compute_donors(xtensor_t& ndonors, xtensor_t& donors, const xtensor_t& receivers) + { + detail::compute_donors_impl( + ndonors.derived_cast(), donors.derived_cast(), receivers.derived_cast()); + } + + + /** + * Compute a stack of grid/mesh nodes to be used for flow tree + * traversal. + * + * The stack is calculated recursively from outlets (or sinks) to + * sources, using Braun and Willet's (2013) algorithm. + * + * @param stack : ``[intent=out, shape=(nnodes)]`` + * Stack position at grid node. + * @param ndonors : ``[intent=in, shape=(nnodes)]`` + * Number of flow donors at grid node. + * @param donors : ``[intent=in, shape=(nnodes, :)]`` + * Indexes of flow donors at grid node. + * @param receivers : ``[intent=in, shape=(nnodes)]`` + * Index of flow receiver at grid node. + */ + template + void compute_stack(xtensor_t& stack, + const xtensor_t& ndonors, + const xtensor_t& donors, + const xtensor_t& receivers) + { + detail::compute_stack_impl(stack.derived_cast(), + ndonors.derived_cast(), + donors.derived_cast(), + receivers.derived_cast()); + } + + + /** + * Assign an id (integer) to each node of the grid/mesh that + * corresponds to the catchment to which it belongs. + * + * A catchment (or drainage basin) is defined by an ensemble of + * adjacent nodes through which all flow converges towards a common, + * single node (outlet or pit). + * + * The algorithm performs a single traversal of the flow tree (in the + * stack order) and increments the catchment id each time an outlet or + * a pit is found (i.e., when the index of the flow receiver equals + * the index of the node itself). + * + * This functions also computes the grid/mesh node indexes of + * catchment outlets (or pits) and returns the total number of + * catchments found inside the domain. + * + * @param basins: ``[intent=out, shape=(nnodes)]`` + * Basin id at grid node. + * @param outlets_or_pits : ``[intent=out, shape=(nnodes)]`` + * Grid node index of the outlet (or pit) + * for basin id=0,1,...,nbasins-1. + * @param stack :``[intent=in, shape=(nnodes)]`` + * Stack position at grid node. + * @param receivers : ``[intent=in, shape=(nnodes)]`` + * Index of flow receiver at grid node. + * + * @returns + * Total number of drainage basins + * (``1 <= nbasins <= nnodes``). + */ + template + index_t compute_basins(xtensor_t& basins, + xtensor_t& outlets_or_pits, + const xtensor_t& stack, + const xtensor_t& receivers) + { + return detail::compute_basins_impl(basins.derived_cast(), + outlets_or_pits.derived_cast(), + stack.derived_cast(), + receivers.derived_cast()); + } + + + /** + * Find grid/mesh nodes that are pits. + * + * @param pits: ``[intent=out, shape=(nnodes)]`` + * Grid node index of the pit for pits in [0,1,...,npits-1]. + * @param outlets_or_pits : ``[intent=in, shape=(nnodes)]`` + * Grid node index of the outlet (or pit) + * for basin id=0,1,...,nbasins-1. + * @param active_nodes : ``[intent=in, shape=(nrows, ncols)]`` + * Boolean array for boundaries + * @param nbasins : ``[intent=in]`` + * Total number of drainage basins (``1 <= nbasins <= nnodes``). + * + * @returns + * Total number of pits found (``0 <= npits <= nbasins``). + */ + template + index_t find_pits(xtensor_t

& pits, + const xtensor_t& outlets_or_pits, + const xtensor_t& active_nodes, + index_t nbasins) + { + return detail::find_pits_impl(pits.derived_cast(), + outlets_or_pits.derived_cast(), + active_nodes.derived_cast(), + nbasins); + } + + + /** + * Compute drainage area for each node on a generic grid/mesh. + * + * @param drainage_area : ``[intent=out, shape=(nrows, ncols)||(nnodes)]`` + * Drainage area at grid node. + * @param cell_area : ``[intent=in, shape=(nrows, ncols)||(nnodes)||()]`` + * Grid/mesh cell area at grid node (also accepts a scalar). + * @param stack :``[intent=in, shape=(nnodes)]`` + * Stack position at grid node. + * @param receivers : ``[intent=in, shape=(nnodes)]`` + * Index of flow receiver at grid node. + */ + template + void compute_drainage_area(xtensor_t& drainage_area, + const xtensor_t& cell_area, + const xtensor_t& stack, + const xtensor_t& receivers) + { + detail::compute_drainage_area_impl(drainage_area.derived_cast(), + cell_area.derived_cast(), + stack.derived_cast(), + receivers.derived_cast()); + } + + + /** + * Compute drainage area for each node on a 2-d rectangular grid. + * + * @param drainage_area : ``[intent=inout, shape=(nrows, ncols)]`` + * Drainage area at grid node. + * @param stack :``[intent=in, shape=(nnodes)]`` + * Stack position at grid node. + * @param receivers : ``[intent=in, shape=(nnodes)]`` + * Index of flow receiver at grid node. + * @param dx : ``[intent=in]`` + * Grid spacing in x. + * @param dy : ``[intent=in]`` + * Grid spacing in y. + */ + template + void compute_drainage_area(xtensor_t& drainage_area, + const xtensor_t& stack, + const xtensor_t& receivers, + double dx, + double dy) + { + xt::xtensor cell_area = { dx * dy }; + compute_drainage_area(drainage_area, cell_area, stack, receivers); + } +} // namespace fastscapelib + +#endif diff --git a/contrib/fastscape/include/fastscapelib/algo/pflood.hpp b/contrib/fastscape/include/fastscapelib/algo/pflood.hpp new file mode 100644 index 00000000000..bd8a6c62186 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/algo/pflood.hpp @@ -0,0 +1,243 @@ +/** + * Provides implementation of priority-flood algorithms for + * depression filling or pit resolving. + */ +#ifndef FASTSCAPELIB_ALGO_PFLOOD_H +#define FASTSCAPELIB_ALGO_PFLOOD_H + +#include +#include +#include +#include +#include +#include + +#include "xtensor/xtensor.hpp" + +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/consts.hpp" +#include "fastscapelib/grid/structured_grid.hpp" + + +namespace fastscapelib +{ + namespace detail + { + + /** + * Grid node structure. + * + * Stores both grid position and elevation at that position. + * Also defines operator '>' that compares only on `elevation`. + * + * The main purpose of this container is for using with + * priority-flood algorithms. + */ + template + struct pflood_node + { + using size_type = typename G::size_type; + + size_type m_idx; + T m_elevation; + + pflood_node() + { + } + pflood_node(size_type idx, T elevation) + : m_idx(idx) + , m_elevation(elevation) + { + } + + bool operator>(const pflood_node& other) const + { + return m_elevation > other.m_elevation; + } + }; + + + template + using pflood_pr_queue = std::priority_queue, + std::vector>, + std::greater>>; + + + template + using pflood_queue = std::queue>; + + + /** + * Initialize priority flood algorithms. + * + * Add fixed value grid nodes to the priority queue and mark them as + * resolved. + */ + template ::value_type> + void init_pflood(G& grid, + E&& elevation, + xt::xtensor& closed, + pflood_pr_queue& open) + { + using size_type = typename G::size_type; + + // TODO: assert elevation shape match grid shape + + const auto elevation_flat = xt::flatten(elevation); + + for (size_type idx = 0; idx < grid.size(); ++idx) + { + if (grid.nodes_status()[idx] == fastscapelib::node_status::fixed_value) + { + open.emplace(pflood_node(idx, elevation_flat(idx))); + closed(idx) = true; + } + } + } + + + /** + * fill_sinks_flat implementation. + */ + template + void fill_sinks_flat_impl(G& grid, E&& elevation) + { + using neighbors_indices_type = typename G::neighbors_indices_type; + using elev_t = typename std::decay_t::value_type; + + auto elevation_flat = xt::flatten(elevation); + neighbors_indices_type neighbors_indices; + + pflood_pr_queue open; + xt::xtensor closed = xt::zeros({ grid.size() }); + + init_pflood(grid, elevation, closed, open); + + while (open.size() > 0) + { + pflood_node inode = open.top(); + open.pop(); + + for (auto n_idx : grid.neighbors_indices(inode.m_idx, neighbors_indices)) + { + if (closed(n_idx)) + { + continue; + } + + elevation_flat(n_idx) = std::max(elevation_flat(n_idx), inode.m_elevation); + open.emplace(pflood_node(n_idx, elevation_flat(n_idx))); + closed(n_idx) = true; + } + } + } + + + /** + * fill_sinks_sloped implementation. + */ + template + void fill_sinks_sloped_impl(G& grid, E&& elevation) + { + using neighbors_indices_type = typename G::neighbors_indices_type; + using elev_t = typename std::decay_t::value_type; + + neighbors_indices_type neighbors_indices; + + pflood_pr_queue open; + pflood_queue pit; + xt::xtensor closed = xt::zeros({ grid.size() }); + + init_pflood(grid, elevation, closed, open); + + while (!open.empty() || !pit.empty()) + { + pflood_node inode, knode; + + if (!pit.empty() + && (open.empty() || open.top().m_elevation == pit.front().m_elevation)) + { + inode = pit.front(); + pit.pop(); + } + else + { + inode = open.top(); + open.pop(); + } + + elev_t elev_tiny_step + = std::nextafter(inode.m_elevation, std::numeric_limits::infinity()); + + for (auto n_idx : grid.neighbors_indices(inode.m_idx, neighbors_indices)) + { + if (closed(n_idx)) + { + continue; + } + + if (elevation.flat(n_idx) <= elev_tiny_step) + { + elevation.flat(n_idx) = elev_tiny_step; + knode = pflood_node(n_idx, elevation.flat(n_idx)); + pit.emplace(knode); + } + else + { + knode = pflood_node(n_idx, elevation.flat(n_idx)); + open.emplace(knode); + } + + closed(n_idx) = true; + } + } + } + + } // namespace detail + + + /** + * Fill all pits and remove all digital dams from a digital + * elevation model (rectangular grid). + * + * Elevation values may be updated so that no depression (sinks or + * local minima) remains. + * + * The algorithm is based on priority queues and is detailed + * in Barnes (2014). It fills sinks with flat areas. + * + * @param grid Grid object + * @param elevation Elevation values at grid nodes + */ + template + void fill_sinks_flat(G& grid, xtensor_t& elevation) + { + detail::fill_sinks_flat_impl(grid, elevation.derived_cast()); + } + + + /** + * Fill all pits and remove all digital dams from a digital + * elevation model (rectangular grid). + * + * Elevation values may be updated so that no depression (sinks or + * local minima) remains. + * + * The algorithm is based on priority queues and is detailed in Barnes + * (2014). This variant fills sinks with nearly flat areas + * (i.e. elevation is increased by small amount) so that there is no + * drainage singularities. + * + * @param grid Grid object + * @param elevation Elevation values at grid nodes + * + * @sa fill_sinks_flat + */ + template + void fill_sinks_sloped(G& grid, xtensor_t& elevation) + { + detail::fill_sinks_sloped_impl(grid, elevation.derived_cast()); + } +} // namespace fastscapelib + +#endif diff --git a/contrib/fastscape/include/fastscapelib/eroders/diffusion_adi.hpp b/contrib/fastscape/include/fastscapelib/eroders/diffusion_adi.hpp new file mode 100644 index 00000000000..14871aa3551 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/eroders/diffusion_adi.hpp @@ -0,0 +1,380 @@ +/** + * Functions to compute hillslope erosion. + */ +#ifndef FASTSCAPELIB_ERODERS_DIFFUSION_ADI_H +#define FASTSCAPELIB_ERODERS_DIFFUSION_ADI_H + +#include +#include +#include +#include +#include + +#include "xtensor/xbroadcast.hpp" +#include "xtensor/xbuilder.hpp" +#include "xtensor/xnoalias.hpp" +#include "xtensor/xview.hpp" +#include "xtensor/xmanipulation.hpp" + +#include "fastscapelib/grid/structured_grid.hpp" +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + template + struct is_raster_grid + { + static constexpr bool value = G::is_structured() && G::is_uniform() && G::xt_ndims() == 2; + }; + + + /** + * Hillslope erosion using linear diffusion. + * + * It numerically solves the diffusion equation using an Alternating + * Direction Implicit (ADI) scheme. + * + * The equation is given by: + * + * @f[ + * \frac{\partial h}{\partial t} = K \nabla^2 h + * @f] + * + * where \f$K\f$ is an erosion coefficient (diffusivity) and \f$\nabla^2 + * h\f$ is the local curvature of the topographic surface. + * + * This equation implies that the amount of sediment eroded is linearly + * proportional to the local gradient of the topographic surface. + * + * \rst + * .. warning:: + * Only raster grids are supported. + * + * .. note:: + * This eroder assumes Dirichlet boundary conditions at the border + * nodes of the raster grid. + * \endrst + * + * @tparam FG The raster grid type. + * @tparam S The xtensor container selector for data array members. + * + */ + template + class diffusion_adi_eroder + { + public: + using grid_type = G; + using xt_selector = S; + + using data_type = typename grid_type::grid_data_type; + using data_array_type = xt_array_t; + using data_tensor_type = xt_tensor_t; + + /** + * Create a new diffusion ADI eroder. + * + * @param grid The input raster grid. + * @param k_coef The \f$K\f$ value (spatially uniform or variable). + * + * @tparam K Either a scalar float type or an array (xtensor expression) type. + */ + template + diffusion_adi_eroder(G& grid, + K&& k_coef, + typename std::enable_if_t::value>* = 0) + : m_grid(grid) + { + auto grid_shape = m_grid.shape(); + m_nrows = grid_shape[0]; + m_ncols = grid_shape[1]; + + m_erosion.resize({ m_nrows, m_ncols }); + + set_k_coef(k_coef); + }; + + /** + * Return the \f$K\f$ value. + */ + data_array_type k_coef() + { + if (m_k_coef_is_scalar) + { + return xt::ones(m_grid.shape()) * m_k_coef_scalar; + } + else + { + return m_k_coef_array; + } + } + + /** + * Set a spatially uniform, scalar \f$K\f$ value. + */ + template + void set_k_coef(T value, typename std::enable_if_t::value>* = 0) + { + m_k_coef_is_scalar = true; + m_k_coef_scalar = value; + m_k_coef_array.resize({ 0 }); + set_factors(); + }; + + /** + * Set a spatially variable, array \f$K\f$ value. + * + * The array expression or container must have the same shape than the + * raster grid arrays. + */ + template + void set_k_coef(K&& value, typename std::enable_if_t::value>* = 0) + { + if (!xt::same_shape(value.shape(), m_grid.shape())) + { + throw std::runtime_error("cannot set k_coef value: shape mismatch"); + } + + m_k_coef_is_scalar = false; + m_k_coef_array = value; + set_factors(); + }; + + /** + * Solve diffusion for one time step. + * + * @param elevation The elevation of surface topography at each grid node. + * @param dt The duration of the time step. + */ + const data_array_type& erode(const data_array_type& elevation, double dt); + + private: + grid_type& m_grid; + + using size_type = typename grid_type::size_type; + using shape_type = typename grid_type::shape_type; + size_type m_nrows, m_ncols; + + data_array_type m_erosion; + + bool m_k_coef_is_scalar; + data_type m_k_coef_scalar; + data_tensor_type m_k_coef_array; + + using factors_type = xt::xtensor; + factors_type m_factors_row; + factors_type m_factors_col; + + void set_factors(); + + // tridiagonal matrix system arrays + xt::xtensor m_vec; + xt::xtensor m_lower; + xt::xtensor m_diag; + xt::xtensor m_upper; + + void resize_tridiagonal(size_type size); + xt::xtensor solve_tridiagonal(); + + template + auto solve_adi_row(E&& elevation, + F1&& factors_row, + F2&& factors_col, + size_type nrows, + size_type ncols, + double dt); + }; + + /* + * Set constant ADI factors + */ + template + void diffusion_adi_eroder::set_factors() + { + auto spacing = m_grid.spacing(); + data_type dx = spacing[1]; + data_type dy = spacing[0]; + + std::array factors_shape({ { 3, m_nrows, m_ncols } }); + data_type fr, fc; + + if (m_k_coef_is_scalar) + { + fr = m_k_coef_scalar * 0.5 / (dy * dy); + fc = m_k_coef_scalar * 0.5 / (dx * dx); + + m_factors_row = xt::ones(factors_shape) * fr; + m_factors_col = xt::ones(factors_shape) * fc; + } + else + { + fr = 0.25 / (dy * dy); + fc = 0.25 / (dx * dx); + + m_factors_row = xt::empty(factors_shape); + m_factors_col = xt::empty(factors_shape); + + const auto& k = m_k_coef_array; + + for (size_type r = 1; r < m_nrows - 1; ++r) + { + for (size_type c = 1; c < m_ncols - 1; ++c) + { + m_factors_row(0, r, c) = fr * (k(r - 1, c) + k(r, c)); + m_factors_row(1, r, c) = fr / 2 * (k(r - 1, c) + 2 * k(r, c) + k(r + 1, c)); + m_factors_row(2, r, c) = fr * (k(r, c) + k(r + 1, c)); + + m_factors_col(0, r, c) = fc * (k(r, c - 1) + k(r, c)); + m_factors_col(1, r, c) = fc / 2 * (k(r, c - 1) + 2 * k(r, c) + k(r, c + 1)); + m_factors_col(2, r, c) = fc * (k(r, c) + k(r, c + 1)); + } + } + } + } + + + template + void diffusion_adi_eroder::resize_tridiagonal(size_type size) + { + m_vec.resize({ size }); + m_upper.resize({ size }); + m_diag.resize({ size }); + m_lower.resize({ size }); + } + + + /* + * Solve tri-diagonal system of equations using Thomas' algorithm (TDMA). + */ + template + auto diffusion_adi_eroder::solve_tridiagonal() -> xt::xtensor + { + size_type n = m_vec.size(); + + auto result = xt::empty_like(m_vec); + auto gam = xt::empty_like(m_vec); + + if (m_diag(0) == 0) + { + throw std::runtime_error("division by zero while solving tri-diagonal system"); + } + + auto bet = m_diag(0); + result(0) = m_vec(0) / bet; + + for (size_type i = 1; i < n; ++i) + { + gam(i) = m_upper(i - 1) / bet; + bet = m_diag(i) - m_lower(i) * gam(i); + + if (bet == 0) + { + throw std::runtime_error("division by zero while solving tri-diagonal system"); + } + + result(i) = (m_vec(i) - m_lower(i) * result(i - 1)) / bet; + } + + for (int i = static_cast(n) - 2; i > -1; --i) + { + result(i) -= gam(i + 1) * result(i + 1); + } + + return result; + } + + + /* + * Solve ADI linear diffusion for the row direction. + */ + template + template + auto diffusion_adi_eroder::solve_adi_row(E&& elevation, + F1&& factors_row, + F2&& factors_col, + size_type nrows, + size_type ncols, + double dt) + { + xt::xtensor elevation_out = elevation; + + for (size_type r = 1; r < nrows - 1; ++r) + { + m_lower = -1 * xt::view(factors_col, 0, r, xt::all()) * dt; + m_diag = 1 + 2 * xt::view(factors_col, 1, r, xt::all()) * dt; + m_upper = -1 * xt::view(factors_col, 2, r, xt::all()) * dt; + + for (size_type c = 1; c < ncols - 1; ++c) + { + m_vec(c) = ((1 - 2 * factors_row(1, r, c) * dt) * elevation(r, c) + + factors_row(0, r, c) * elevation(r - 1, c) * dt + + factors_row(2, r, c) * elevation(r + 1, c) * dt); + } + + // ignore grid boundary status and assume fixed value boundary conditions + auto ilast = ncols - 1; + + m_lower(0) = 0; + m_lower(ilast) = 0; + m_diag(0) = 1; + m_diag(ilast) = 1; + m_upper(0) = 0; + m_upper(ilast) = 0; + m_vec(0) = elevation(r, 0); + m_vec(ilast) = elevation(r, ilast); + + auto elevation_out_r = xt::view(elevation_out, r, xt::all()); + + elevation_out_r = solve_tridiagonal(); + } + + return elevation_out; + } + + + template + auto diffusion_adi_eroder::erode(const data_array_type& elevation, double dt) + -> const data_array_type& + { + // solve for rows + resize_tridiagonal(m_ncols); + auto elevation_tmp + = solve_adi_row(elevation, m_factors_row, m_factors_col, m_nrows, m_ncols, dt); + + // solve for cols (i.e., transpose) + resize_tridiagonal(m_nrows); + auto tranposed_dims = std::array{ 0, 2, 1 }; + + auto elevation_next = solve_adi_row(xt::transpose(elevation_tmp), + xt::transpose(m_factors_col, tranposed_dims), + xt::transpose(m_factors_row, tranposed_dims), + m_ncols, + m_nrows, + dt); + + auto erosion_v = xt::view(m_erosion, xt::all(), xt::all()); + erosion_v = elevation - xt::transpose(elevation_next); + + return m_erosion; + } + + + /** + * Helper to create a new diffusion ADI eroder. + * + * @param grid The input raster grid. + * @param k_coef The \f$K\f$ value (spatially uniform or variable). + * + * @tparam G The raster grid type. + * @tparam K Either a scalar float type or an array (xtensor expression) type. + */ + template + diffusion_adi_eroder make_diffusion_adi_eroder(G& grid, K&& k_coef) + { + return diffusion_adi_eroder(grid, k_coef); + } + +} // namespace fastscapelib + +#endif diff --git a/contrib/fastscape/include/fastscapelib/eroders/spl.hpp b/contrib/fastscape/include/fastscapelib/eroders/spl.hpp new file mode 100644 index 00000000000..ece97538ce0 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/eroders/spl.hpp @@ -0,0 +1,393 @@ +/** + * Functions to compute bedrock channel erosion. + */ +#ifndef FASTSCAPELIB_ERODERS_SPL_H +#define FASTSCAPELIB_ERODERS_SPL_H + +#include +#include +#include +#include +#include +#include + +#include "xtensor/xbroadcast.hpp" +#include "xtensor/xtensor.hpp" +#include "xtensor/xmanipulation.hpp" + +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + /** + * Bedrock channel erosion modelled using the Stream Power Law. + * + * It numerically solves the Stream Power Law (SPL) using an implicit finite + * difference scheme 1st order in space and time. The method is detailed in + * Braun and Willet's (2013) and has been slightly adapted. + * + * SPL is defined as: + * + * @f[ + * \frac{\partial h}{\partial t} = - K A^m (\nabla h)^n + * @f] + * + * where \f$K\f$ is an erosion coefficient, \f$A\f$ is the upslope + * contributing (or drainage) area and \f$\nabla h\f$ is the local gradient + * of the topographic surface. + * + * For the linear case \f$n = 1\f$, solving the equation is trivial. For the + * non-linear case \f$n \neq 1\f$, the Newton-Raphson method is used to find + * the optimal solution. + * + * Solving the SPL equation requires setting boundary conditions. In this + * implementation, the erosion at the base level nodes of the flow graph is + * set to zero. + * + * \rst + * + * See :cite:t:`Braun2013` for more details on the numerical scheme used here. + * + * .. note:: + * This implementation supports multiple direction flow, although only for + * the linear case. + * + * .. note:: + * This implementation also prevents the formation of new closed depressions + * (lakes). The erosion within existing closed depressions of the input + * topographic surface will depend on the given flow graph: + * + * - If the flow graph handles routing accross those depressions, no erosion + * will take place within them (lakes). + * - If the flow graph does not resolve those depressions (no sink resolver + * applied), they will be eroded like the rest of the topographic surface + * (no lake). + * + * .. warning:: + * The numerical scheme used here is stable but not implicit with + * respect to upslope contributing area (A). Using large time steps may + * still have a great impact on the solution, especially on transient + * states. + * \endrst + * + * @tparam FG The flow graph type. + * @tparam S The xtensor container selector for data array members. + */ + template + class spl_eroder + { + public: + using flow_graph_type = FG; + using xt_selector = S; + + using size_type = typename flow_graph_type::size_type; + using shape_type = typename flow_graph_type::shape_type; + using data_type = typename flow_graph_type::data_type; + using data_array_type = xt_array_t; + + /** + * Create a new SPL eroder. + * + * @param flow_graph The flow graph instance + * @param k_coef The \f$K\f$ value (spatially uniform or variable) + * @param area_exp The \f$A\f$ exponent value + * @param slope_exp The \f$\nabla h\f$ exponent value + * @param tolerance The tolerance of Newton-Raphson convergence for the non-linear case. + * + * @tparam K Either a scalar float type or an array (xtensor expression) type. + */ + template + spl_eroder( + FG& flow_graph, K&& k_coef, double area_exp, double slope_exp, double tolerance = 1e-3) + : m_flow_graph(flow_graph) + , m_shape(flow_graph.grid_shape()) + , m_tolerance(tolerance) + { + set_k_coef(k_coef); + set_area_exp(area_exp); + set_slope_exp(slope_exp); + + m_erosion.resize(m_shape); + }; + + /** + * Return the \f$K\f$ value. + */ + const data_array_type& k_coef() + { + return m_k_coef; + }; + + /** + * Set a spatially uniform, scalar \f$K\f$ value. + */ + template + void set_k_coef(T value, typename std::enable_if_t::value>* = 0) + { + m_k_coef = xt::broadcast(std::forward(value), { m_flow_graph.size() }); + }; + + /** + * Set a spatially variable, array \f$K\f$ value. + * + * The array expression or container must have the same shape than the + * grid arrays. + */ + template + void set_k_coef(K&& value, typename std::enable_if_t::value>* = 0) + { + if (!xt::same_shape(value.shape(), m_shape)) + { + throw std::runtime_error("cannot set k_coef value: shape mismatch"); + } + m_k_coef = xt::flatten(value); + }; + + /** + * Return the \f$A\f$ exponent value. + */ + double area_exp() + { + return m_area_exp; + }; + + /** + * Set the \f$A\f$ exponent value. + */ + void set_area_exp(double value) + { + // TODO: validate value + m_area_exp = value; + }; + + /** + * Return the \f$\nabla h\f$ exponent value. + */ + double slope_exp() + { + return m_slope_exp; + }; + + /** + * Set the \f$\nabla h\f$ exponent value. + */ + void set_slope_exp(double value) + { + // TODO: validate value + m_slope_exp = value; + m_linear = (std::fabs(value) - 1) <= std::numeric_limits::epsilon(); + + if (!m_linear && !m_flow_graph.single_flow()) + { + throw std::invalid_argument( + "SPL slope exponent != 1 is not supported for multiple flow directions"); + } + }; + + /** + * Return the tolerance controlling the convergence of the + * Newton-Raphson iterations for the non-linear case. + */ + double tolerance() + { + return m_tolerance; + } + + /** + * Returns the number of nodes for which erosion has been arbitrarily + * limited during the last computed time-step. + * + * To ensure numerical stability, channel erosion may not lower the + * elevation of a node such that it reverts the slope with any of its + * direct neighbors. This prevents the formation of new closed + * depressions. + */ + size_type n_corr() + { + return m_n_corr; + }; + + /** + * Solve SPL for one time step. + * + * @param elevation The elevation of surface topography at each grid node. + * @param drainage_area The upslope drainage area at each grid node. + * @param dt The duration of the time step. + */ + const data_array_type& erode(const data_array_type& elevation, + const data_array_type& drainage_area, + double dt); + + private: + flow_graph_type& m_flow_graph; + shape_type m_shape; + data_array_type m_k_coef; + data_array_type m_erosion; + double m_area_exp; + double m_slope_exp; + double m_tolerance; + bool m_linear; + size_type m_n_corr; + }; + + template + auto spl_eroder::erode(const data_array_type& elevation, + const data_array_type& drainage_area, + double dt) -> const data_array_type& + { + auto& flow_graph_impl = m_flow_graph.impl(); + + const auto& receivers = flow_graph_impl.receivers(); + const auto& receivers_count = flow_graph_impl.receivers_count(); + const auto& receivers_distance = flow_graph_impl.receivers_distance(); + const auto& receivers_weight = flow_graph_impl.receivers_weight(); + + // reset + m_erosion.fill(0); + m_n_corr = 0; + + // iterate over graph nodes in the bottom->up direction + for (const auto& inode : flow_graph_impl.nodes_indices_bottomup()) + { + data_type inode_elevation = elevation.flat(inode); + auto r_count = receivers_count[inode]; + + if (r_count == 1 && receivers(inode, 0) == inode) + { + // current node is a basin outlet or pit (no erosion) + continue; + } + + // ``elevation_flooded`` represents the level below which no erosion + // should happen (the node is already within a lake or in order + // prevent the formation of new lakes). It corresponds to the lowest + // elevation at step + 1 found among the node receivers + double elevation_flooded = std::numeric_limits::max(); + + for (size_type r = 0; r < r_count; ++r) + { + size_type irec = receivers(inode, r); + data_type irec_elevation_next = elevation.flat(irec) - m_erosion.flat(irec); + + if (irec_elevation_next < elevation_flooded) + { + elevation_flooded = irec_elevation_next; + } + } + + if (inode_elevation <= elevation_flooded) + { + // current node is inside a lake (no erosion) + continue; + } + + // init discrete equation numerator and denominator values + double eq_num = inode_elevation; + double eq_den = 1.0; + + // iterate over receivers + for (size_type r = 0; r < r_count; ++r) + { + size_type irec = receivers(inode, r); + + data_type irec_elevation = elevation.flat(irec); + // elevation of receiver node at step + 1 + data_type irec_elevation_next = irec_elevation - m_erosion.flat(irec); + + if (irec_elevation > inode_elevation) + { + // current node is next to a lake spill ; + // the current receiver is not the lake spill, it doesn't contribute to + // eroding the current node + continue; + } + + data_type irec_weight = receivers_weight(inode, r); + data_type irec_distance = receivers_distance(inode, r); + + auto factor = (m_k_coef(inode) * dt + * std::pow(drainage_area.flat(inode) * irec_weight, m_area_exp)); + + if (m_linear) + { + // fast path for the linear case + factor /= irec_distance; + eq_num += factor * irec_elevation_next; + eq_den += factor; + } + else + { + // 1st order Newton-Raphson iterations for the non-linear case + factor /= std::pow(irec_distance, m_slope_exp); + + // solve directly for the difference of elevation + // faster? + // (only valid for single flow direction) + double delta_0 = inode_elevation - irec_elevation_next; + double delta_k = delta_0; + + while (true) + { + auto factor_delta_exp = factor * std::pow(delta_k, m_slope_exp); + auto func = delta_k + factor_delta_exp - delta_0; + + if (func <= m_tolerance) + { + break; + } + + auto func_deriv = 1 + m_slope_exp * factor_delta_exp / delta_k; + delta_k -= func / func_deriv; + + if (delta_k <= 0) + { + break; + } + } + + eq_num = inode_elevation - (delta_0 - delta_k); + } + } + + data_type inode_elevation_updated = eq_num / eq_den; + + if (inode_elevation_updated < elevation_flooded) + { + // numerical stability: prevent the creation of new + // depressions / flat channels by arbitrarily limiting + // erosion + m_n_corr++; + inode_elevation_updated = elevation_flooded + std::numeric_limits::min(); + } + + m_erosion.flat(inode) = inode_elevation - inode_elevation_updated; + } + + return m_erosion; + }; + + + /** + * Helper to create a new SPL eroder. + * + * @param flow_graph The flow graph instance + * @param k_coef The \f$K\f$ value (spatially uniform or variable) + * @param area_exp The \f$A\f$ exponent value + * @param slope_exp The \f$\nabla h\f$ exponent value + * @param tolerance The tolerance of Newton-Raphson convergence for the non-linear case. + * + * @tparam FG The flow graph type. + * @tparam K Either a scalar float type or an array (xtensor expression) type. + */ + template + spl_eroder make_spl_eroder( + FG& flow_graph, K&& k_coef, double area_exp, double slope_exp, double tolerance) + { + return spl_eroder(flow_graph, k_coef, area_exp, slope_exp, tolerance); + } +} // namespace fastscapelib + +#endif diff --git a/contrib/fastscape/include/fastscapelib/flow/basin_graph.hpp b/contrib/fastscape/include/fastscapelib/flow/basin_graph.hpp new file mode 100644 index 00000000000..925f1d2e412 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/basin_graph.hpp @@ -0,0 +1,814 @@ +#ifndef FASTSCAPELIB_FLOW_BASIN_GRAPH_H +#define FASTSCAPELIB_FLOW_BASIN_GRAPH_H + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/flow/flow_graph_impl.hpp" +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/consts.hpp" +#include "fastscapelib/utils/union_find.hpp" + + +namespace fastscapelib +{ + + /** + * Algorithm used to compute the reduced graph (tree) of basins (minimum + * spanning tree). + * + * Kruskal's algorithm is rather simple and has non-linear complexity \f$O(E + * \log V)\f$ where \f$E\f$ is the number of basin connections and \f$V\f$ + * is the number of basins. + * + * Boruvka's algorithm is more advanced and has linear complexity for planar + * graphs (most flow graphs are planar except, e.g., those built on raster + * grids with connectivity including the diagonals). + * + * The gain in performance of Boruvka's algorithm over Kruskal's one is + * significant only for (very) large graphs. + */ + enum class mst_method + { + kruskal, /**< Kruskal's algorithm. */ + boruvka /**< Boruvka's algorithm. */ + }; + + inline std::ostream& operator<<(std::ostream& os, const mst_method meth) + { + switch (meth) + { + case mst_method::kruskal: + os << "kruskal"; + break; + case mst_method::boruvka: + os << "boruvka"; + break; + } + return os; + } + + namespace testing + { + // The corresponding test needs to access private members of basin_graph + class basin_graph_orient_edges_Test; + } + + + /** + * Represents a graph of adjacent basins (catchments) connected via a two + * "pass" nodes where the water flow spills from one basin into another. + * + * \rst + * Such graph is built and used by :cpp:class:`~fastscapelib::mst_sink_resolver` + * to resolve flow routing accross closed depressions in the surface topography. + * \endrst + * + * @tparam FG The flow graph implementation type (only + * ``flow_graph_fixed_array_tag`` is supported). + */ + template + class basin_graph + { + public: + using flow_graph_impl_type = FG; + + static_assert( + std::is_same::value, + "basin graph requires the fixed array flow graph implementation"); + + using size_type = typename flow_graph_impl_type::size_type; + + using data_type = typename flow_graph_impl_type::data_type; + using data_array_type = typename flow_graph_impl_type::data_array_type; + + /** + * Represents an edge of the graph of basins. + */ + struct edge + { + size_type link[2]; /**< Indices of the two connected basins */ + size_type pass[2]; /**< Indices of the grid nodes forming the pass accross the basins */ + data_type pass_elevation; /**< Maximum elevation among the two pass nodes */ + data_type pass_length; /**< Distance between the two pass nodes */ + + /** + * Create a new edge with no assigned pass yet. + */ + static edge make_edge(const size_type& from, const size_type& to) + { + return edge{ { from, to }, + { size_type(-1), size_type(-1) }, + std::numeric_limits::lowest(), + 0. }; + } + + bool operator==(const edge& other) + { + return link[0] == other.link[0] && link[1] == other.link[1] + && pass[0] == other.pass[0] && pass[1] == other.pass[1] + && pass_elevation == other.pass_elevation + && pass_length == other.pass_length; + } + }; + + /** + * Create a new graph of basins from a flow graph. + * + * @param flow_graph_impl A flow graph implementation instance (fixed array). + * @param basin_method The algorithm used to compute the reduced tree of basins. + * + * \rst + * .. warning:: + * A basin graph requires a flow graph with single direction flow paths as + * its current state. However, no validation is done here since the state of + * the same flow graph may later change from single to multiple direction + * after the basin graph has been (re)computed. + * \endrst + */ + basin_graph(const flow_graph_impl_type& flow_graph_impl, mst_method basin_method) + : m_flow_graph_impl(flow_graph_impl) + , m_mst_method(basin_method) + { + m_perf_boruvka = 0; + } + + /** + * Returns the total number of basins. + */ + inline size_type basins_count() const + { + return m_flow_graph_impl.outlets().size(); + } + + /** + * Returns a vector of node indices that represent the outlets of each + * of the basins in this graph. + */ + inline const std::vector& outlets() const + { + return m_flow_graph_impl.outlets(); + } + + /** + * Returns the edges of the graph of basins. + */ + const std::vector& edges() const + { + return m_edges; + } + + /** + * Returns the reduced graph (tree) of basins after applying the + * selected minimum spanning tree algorithm. + */ + const std::vector& tree() const + { + return m_tree; + } + + /** + * Update (re-compute from-scratch) the graph and its reduced tree from + * the given topographic surface. + * + * @param elevation The topographic surface elevation at each grid node. + */ + void update_routes(const data_array_type& elevation); + + /** + * Boruvka's algorithm performance diagnostic. + */ + size_t perf_boruvka() const + { + return m_perf_boruvka; + } + + protected: + void connect_basins(const data_array_type& elevation); + void compute_tree_kruskal(); + void compute_tree_boruvka(); + void orient_edges(); + + private: + const flow_graph_impl_type& m_flow_graph_impl; + + mst_method m_mst_method; + + std::vector m_outlets; // bottom nodes of basins + std::vector m_edges; + std::vector m_tree; // indices of edges + + // root is used as a virtual basin graph node to which all outer basins are + // connected, thus collecting the flow from the whole modeled domain. It + // is used for the computation of the basin tree (minimum spanning + // tree). As an implementation detail, this virtual node is actually + // assigned to one of the outer basins, chosen arbitrarily. + size_type m_root; + + // optimization for basin connections (arrays storing the positions of + // already created edges) + std::vector m_edge_positions; + std::vector m_edge_positions_tmp; + + // kruskal + std::vector m_edges_indices; + detail::union_find m_basins_uf; + + // boruvka + std::vector> m_link_basins; + + struct Connect + { + size_type begin; + size_type size; + }; + + std::vector m_adjacency; + + struct EdgeParse + { + size_type link_id; + size_type next; + }; + + std::vector m_adjacency_list; + std::vector m_low_degrees; + std::vector m_large_degrees; + std::vector m_edge_bucket; + std::vector m_edge_in_bucket; + + // TODO: make it an option + // 16 for 8-connectivity raster grid, 8 for plannar graph + size_type m_max_low_degree = 16; + + template + inline void check_capacity(std::vector vec) const + { + ((void) (vec)); + assert(vec.size() <= vec.capacity()); + } + + size_t m_perf_boruvka; + + inline void increase_perf_boruvka() + { + ++m_perf_boruvka; + } + + // reorder tree + std::vector m_nodes_connects_size; + std::vector m_nodes_connects_ptr; + std::vector m_nodes_adjacency; + std::vector> + m_reorder_stack; + + // TODO: make it an option (true for sink route fill sloped) + bool m_keep_order = false; + + std::vector m_pass_stack; + std::vector m_parent_basins; + + friend class testing::basin_graph_orient_edges_Test; + }; + + + template + void basin_graph::update_routes(const data_array_type& elevation) + { + connect_basins(elevation); + + if (m_mst_method == mst_method::kruskal) + { + compute_tree_kruskal(); + } + else + { + compute_tree_boruvka(); + } + + orient_edges(); + } + + template + void basin_graph::connect_basins(const data_array_type& elevation) + { + using neighbors_type = typename flow_graph_impl_type::grid_type::neighbors_type; + + auto nbasins = basins_count(); + + const auto& basins = m_flow_graph_impl.basins(); + const auto& receivers = m_flow_graph_impl.receivers(); + const auto& dfs_indices = m_flow_graph_impl.dfs_indices(); + + auto& grid = m_flow_graph_impl.grid(); + const auto& nodes_status = grid.nodes_status(); + + // used to (re)initalize container index / position + size_type init_idx = static_cast(-1); + + neighbors_type neighbors; + + size_type ibasin; + size_type current_basin = init_idx; + + // assume iteration starts at a base level node (outer basin)! + bool is_inner_basin = false; + + // reset + m_root = init_idx; + m_edges.clear(); + m_edges.reserve(4 * nbasins); + + m_edge_positions.resize(nbasins); + std::fill(m_edge_positions.begin(), m_edge_positions.end(), init_idx); + m_edge_positions_tmp.reserve(nbasins); + m_edge_positions_tmp.clear(); + + for (const auto idfs : dfs_indices) + { + const auto irec = receivers(idfs, 0); + + // any new basin visited + if (irec == idfs) + { + ibasin = basins(idfs); + is_inner_basin = nodes_status.flat(idfs) != node_status::fixed_value; + + if (!is_inner_basin) + { + if (m_root == init_idx) + { + // assign root to an existing outer basin + m_root = ibasin; + } + else + { + m_edges.push_back(edge::make_edge(m_root, ibasin)); + } + } + } + + // any node in an inner basin + if (is_inner_basin) + { + const data_type ielev = elevation.flat(idfs); + + for (auto n : grid.neighbors(idfs, neighbors)) + { + const size_type nbasin = basins(n.idx); + + // skip if neighbor node is in the same basin or in an + // already connected adjacent basin unless the latter is an + // outer basin + bool skip = ibasin >= nbasin; + node_status nstatus = nodes_status.flat(outlets()[nbasin]); + bool is_inner_nbasin = nstatus != node_status::fixed_value; + if (skip && is_inner_nbasin) + { + continue; + } + + const data_type pass_elevation = std::max(ielev, elevation.flat(n.idx)); + + // just jumped from one basin to another + // -> update current basin and reset its visited neighbor basins + if (current_basin != ibasin) + { + for (const auto& ivisited : m_edge_positions_tmp) + { + m_edge_positions[ivisited] = init_idx; + } + m_edge_positions_tmp.clear(); + current_basin = ibasin; + } + + // try getting the position (index) of the edge connecting + // with the adjacent basin: + // - if it is undefined (-1), add a new edge + // - if it is defined, update the edge if a pass of lower elevation is found + const size_type edge_idx = m_edge_positions[nbasin]; + + if (edge_idx == init_idx) + { + m_edge_positions[nbasin] = m_edges.size(); + m_edge_positions_tmp.push_back(nbasin); + + m_edges.push_back( + { { ibasin, nbasin }, { idfs, n.idx }, pass_elevation, n.distance }); + } + else if (pass_elevation < m_edges[edge_idx].pass_elevation) + { + m_edges[edge_idx] = edge{ + { ibasin, nbasin }, { idfs, n.idx }, pass_elevation, n.distance + }; + } + } + } + } + } + + template + void basin_graph::compute_tree_kruskal() + { + m_tree.reserve(basins_count() - 1); + m_tree.clear(); + + // sort edges indices by edge weight (pass elevation) + m_edges_indices.resize(m_edges.size()); + std::iota(m_edges_indices.begin(), m_edges_indices.end(), 0); + std::sort(m_edges_indices.begin(), + m_edges_indices.end(), + [&m_edges = m_edges](const size_type& i0, const size_type& i1) + { return m_edges[i0].pass_elevation < m_edges[i1].pass_elevation; }); + + m_basins_uf.resize(basins_count()); + m_basins_uf.clear(); + + for (size_type edge_idx : m_edges_indices) + { + size_type* link = m_edges[edge_idx].link; + + if (m_basins_uf.find(link[0]) != m_basins_uf.find(link[1])) + { + m_tree.push_back(edge_idx); + m_basins_uf.merge(link[0], link[1]); + } + } + } + + template + void basin_graph::compute_tree_boruvka() + { + const auto nbasins = basins_count(); + + // used to (re)initalize container index / position + size_type init_idx = static_cast(-1); + + m_adjacency.clear(); + m_adjacency.resize(nbasins, { 0, 0 }); + m_low_degrees.reserve(nbasins); + m_large_degrees.reserve(nbasins); + + m_edge_bucket.clear(); + m_edge_bucket.resize(nbasins, init_idx); + + // copy link basins + m_link_basins.resize(m_edges.size()); + for (size_t i = 0; i < m_link_basins.size(); ++i) + { + m_link_basins[i][0] = m_edges[i].link[0]; + m_link_basins[i][1] = m_edges[i].link[1]; + } + + // first pass: create edge vector and compute adjacency size + for (size_t lid = 0; lid < m_edges.size(); ++lid) + { + ++m_adjacency[m_link_basins[lid][0]].size; + ++m_adjacency[m_link_basins[lid][1]].size; + } + + // compute adjacency pointers + m_adjacency[0].begin = 0; + for (size_t nid = 1; nid < nbasins; ++nid) + { + m_adjacency[nid].begin = m_adjacency[nid - 1].begin + m_adjacency[nid - 1].size; + m_adjacency[nid - 1].size = 0; + } + + m_adjacency_list.resize(m_adjacency.back().begin + m_adjacency.back().size); + m_adjacency.back().size = 0; + + for (size_t adj_data_i = 0; adj_data_i < m_adjacency_list.size(); ++adj_data_i) + m_adjacency_list[adj_data_i].next = adj_data_i + 1; + + // second pass on edges: fill adjacency list + for (size_t lid = 0; lid < m_edges.size(); ++lid) + { + auto& basins = m_link_basins[lid]; + + m_adjacency_list[m_adjacency[basins[0]].begin + m_adjacency[basins[0]].size].link_id + = lid; + m_adjacency_list[m_adjacency[basins[1]].begin + m_adjacency[basins[1]].size].link_id + = lid; + + ++m_adjacency[basins[0]].size; + ++m_adjacency[basins[1]].size; + } + + for (size_t nid = 0; nid < nbasins; ++nid) + { + check_capacity(m_low_degrees); + check_capacity(m_large_degrees); + // if degree is low enough + if (m_adjacency[nid].size <= m_max_low_degree) + m_low_degrees.push_back(nid); + else + m_large_degrees.push_back(nid); + } + + m_perf_boruvka = 0; + + // compute the minimum spanning tree + m_tree.reserve(nbasins - 1); + m_tree.clear(); + + while (m_low_degrees.size()) + { + for (size_type nid : m_low_degrees) + { + // the node may have large degree after collapse + if (m_adjacency[nid].size > m_max_low_degree) + { + check_capacity(m_large_degrees); + m_large_degrees.push_back(nid); + continue; + } + + // get the minimal weight edge that leaves that node + size_type found_edge = init_idx; + size_type node_B_id = init_idx; + data_type found_edge_weight = std::numeric_limits::max(); + + size_type adjacency_data_ptr = m_adjacency[nid].begin; + for (size_t step = 0; step < m_adjacency[nid].size; ++step) + { + increase_perf_boruvka(); + + // find next adjacent edge in the list + size_type parsed_edge_id = m_adjacency_list[adjacency_data_ptr].link_id; + adjacency_data_ptr = m_adjacency_list[adjacency_data_ptr].next; + + // check if the edge is valid (connected to a existing node) + // and if the weight is better than the previously found one + size_type opp_node = m_link_basins[parsed_edge_id][0]; + if (opp_node == nid) + opp_node = m_link_basins[parsed_edge_id][1]; + + if (opp_node != nid && m_adjacency[opp_node].size > 0 + && m_edges[parsed_edge_id].pass_elevation < found_edge_weight) + { + found_edge = parsed_edge_id; + found_edge_weight = m_edges[parsed_edge_id].pass_elevation; + node_B_id = opp_node; + } + } + + if (found_edge == init_idx) + continue; // TODO does it happens? + + // add edge to the tree + check_capacity(m_tree); + m_tree.push_back(found_edge); + + // and collapse it toward opposite node + + // rename all A to B in adjacency of A + adjacency_data_ptr = m_adjacency[nid].begin; + for (size_t step = 0; step < m_adjacency[nid].size; ++step) + { + increase_perf_boruvka(); + + // find next adjacent edge in the list + size_type edge_AC_id = m_adjacency_list[adjacency_data_ptr].link_id; + + // TODO optimize that out? + if (step != m_adjacency[nid].size - 1) + adjacency_data_ptr = m_adjacency_list[adjacency_data_ptr].next; + + // avoid self loop. A doesn't exist anymore, so edge AB + // will be discarded + + if (m_link_basins[edge_AC_id][0] == nid) + m_link_basins[edge_AC_id][0] = node_B_id; + else + m_link_basins[edge_AC_id][1] = node_B_id; + } + + // Append adjacency of B at the end of A + m_adjacency_list[adjacency_data_ptr].next = m_adjacency[node_B_id].begin; + + // And collapse A into B + m_adjacency[node_B_id].begin = m_adjacency[nid].begin; + m_adjacency[node_B_id].size += m_adjacency[nid].size; + + // Remove the node from the graph + m_adjacency[nid].size = 0; + } + + m_low_degrees.clear(); + + // Clean up graph (many edges are duplicates or self loops). + size_type cur_large_degree = 0; + for (size_type node_A_id : m_large_degrees) + { + // we will store all edges from A in the bucket, so that each edge + // can appear only once + m_edge_in_bucket.clear(); + size_type adjacency_data_ptr = m_adjacency[node_A_id].begin; + + for (size_t step = 0; step < m_adjacency[node_A_id].size; ++step) + { + increase_perf_boruvka(); + + size_type edge_AB_id = m_adjacency_list[adjacency_data_ptr].link_id; + adjacency_data_ptr = m_adjacency_list[adjacency_data_ptr].next; + + // find node B + size_type node_B_id = m_link_basins[edge_AB_id][0]; + if (node_B_id == node_A_id) + node_B_id = m_link_basins[edge_AB_id][1]; + + if (m_adjacency[node_B_id].size > 0 && node_B_id != node_A_id) + { + // edge_bucket contain the edge_id connecting to opp_node_id + // or NodeId(-1)) if this is the first time we see it + size_type edge_AB_id_in_bucket = m_edge_bucket[node_B_id]; + + // first time we see + if (edge_AB_id_in_bucket == init_idx) + { + m_edge_bucket[node_B_id] = edge_AB_id; + m_edge_in_bucket.push_back(node_B_id); + } + else + { + // get weight of AB and of previously stored weight + data_type weight_in_bucket + = m_edges[edge_AB_id_in_bucket].pass_elevation; + data_type weight_AB = m_edges[edge_AB_id].pass_elevation; + + // if both weight are the same, we choose edge + // with min id + if (weight_in_bucket == weight_AB) + m_edge_bucket[node_B_id] + = std::min(edge_AB_id_in_bucket, edge_AB_id); + else if (weight_AB < weight_in_bucket) + m_edge_bucket[node_B_id] = edge_AB_id; + } + } + } + + // recompute connectivity of node A + size_type cur_ptr = m_adjacency[node_A_id].begin; + m_adjacency[node_A_id].size = m_edge_in_bucket.size(); + + for (size_type node_B_id : m_edge_in_bucket) + { + increase_perf_boruvka(); + + m_adjacency_list[cur_ptr].link_id = m_edge_bucket[node_B_id]; + cur_ptr = m_adjacency_list[cur_ptr].next; + + // reset occupency of edge_bucket for latter use + m_edge_bucket[node_B_id] = init_idx; + } + + + // update low degree information, if node A has low degree + if (m_adjacency[node_A_id].size <= m_max_low_degree) + { + check_capacity(m_low_degrees); + // add the node in low degree list + if (m_adjacency[node_A_id].size > 0) + m_low_degrees.push_back(node_A_id); + } + else + m_large_degrees[cur_large_degree++] = node_A_id; + } + m_large_degrees.resize(cur_large_degree); + check_capacity(m_large_degrees); + } + } + + /* + * Orient the edges of the basin graph (tree) in the upward (or counter) + * flow direction. + * + * If needed, swap the link (i.e., basin node indices) and pass (i.e., grid + * node indices) of the edges. + */ + template + void basin_graph::orient_edges() + { + const auto nbasins = basins_count(); + + // used to (re)initalize container index / position + size_type init_idx = static_cast(-1); + + // nodes connections + m_nodes_connects_size.resize(nbasins); + std::fill(m_nodes_connects_size.begin(), m_nodes_connects_size.end(), size_t(0)); + m_nodes_connects_ptr.resize(nbasins); + + // parse the edges to compute the number of edges per node + for (size_type l_id : m_tree) + { + m_nodes_connects_size[m_edges[l_id].link[0]] += 1; + m_nodes_connects_size[m_edges[l_id].link[1]] += 1; + } + + // compute the id of first edge in adjacency table + m_nodes_connects_ptr[0] = 0; + for (size_t i = 1; i < nbasins; ++i) + { + m_nodes_connects_ptr[i] = (m_nodes_connects_ptr[i - 1] + m_nodes_connects_size[i - 1]); + m_nodes_connects_size[i - 1] = 0; + } + + // create the adjacency table + m_nodes_adjacency.resize(m_nodes_connects_ptr.back() + m_nodes_connects_size.back()); + m_nodes_connects_size.back() = 0; + + // parse the edges to update the adjacency + for (size_type l_id : m_tree) + { + size_type n0 = m_edges[l_id].link[0]; + size_type n1 = m_edges[l_id].link[1]; + m_nodes_adjacency[m_nodes_connects_ptr[n0] + m_nodes_connects_size[n0]] = l_id; + m_nodes_adjacency[m_nodes_connects_ptr[n1] + m_nodes_connects_size[n1]] = l_id; + m_nodes_connects_size[n0] += 1; + m_nodes_connects_size[n1] += 1; + } + + // depth-first parse of the tree, starting from basin0 + // stack of node, parent + m_reorder_stack.reserve(nbasins); + m_reorder_stack.clear(); + + m_reorder_stack.push_back({ m_root, + m_root, + std::numeric_limits::min(), + std::numeric_limits::min() }); + + if (m_keep_order) + { + m_pass_stack.clear(); + m_parent_basins.resize(nbasins); + m_parent_basins[m_root] = m_root; + } + + while (m_reorder_stack.size()) + { + size_type node, parent; + data_type pass_elevation, parent_pass_elevation; + std::tie(node, parent, pass_elevation, parent_pass_elevation) = m_reorder_stack.back(); + m_reorder_stack.pop_back(); + + + for (size_t i = m_nodes_connects_ptr[node]; + i < m_nodes_connects_ptr[node] + m_nodes_connects_size[node]; + ++i) + { + edge& edg = m_edges[m_nodes_adjacency[i]]; + + // the edge comming from the parent node has already been updated. + // in this case, the edge is (parent, node) + if (edg.link[0] == parent && node != parent) + { + if (m_keep_order) + { + // force children of base nodes to be parsed + if (pass_elevation <= parent_pass_elevation && edg.pass[0] != init_idx) + // the pass is bellow the water level of the parent basin + m_parent_basins[edg.link[1]] = m_parent_basins[edg.link[0]]; + else + { + m_parent_basins[edg.link[1]] = edg.link[1]; + m_pass_stack.push_back(m_nodes_adjacency[i]); + } + } + } + else + { + // we want the edge to be (node, next), where next is upper in flow order + // we check if the first node of the edge is not "node" + if (node != edg.link[0]) + { + std::swap(edg.link[0], edg.link[1]); + std::swap(edg.pass[0], edg.pass[1]); + } + + m_reorder_stack.push_back({ edg.link[1], + node, + std::max(edg.pass_elevation, pass_elevation), + pass_elevation }); + } + } + } + } +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/flow/flow_graph.hpp b/contrib/fastscape/include/fastscapelib/flow/flow_graph.hpp new file mode 100644 index 00000000000..3929ed83f30 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/flow_graph.hpp @@ -0,0 +1,341 @@ +#ifndef FASTSCAPELIB_FLOW_FLOW_GRAPH_H +#define FASTSCAPELIB_FLOW_FLOW_GRAPH_H + +#include +#include +#include +#include +#include +#include + +#include "xtensor/xstrided_view.hpp" + +#include "fastscapelib/flow/flow_graph_impl.hpp" +#include "fastscapelib/flow/flow_operator.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + /** + * Main class used to compute or follow flow routes on + * the topographic surface. + * + * @tparam G The grid type. + * @tparam S The xtensor container selector for data array members. + * @tparam Tag The flow graph implementation tag. + */ + template + class flow_graph + { + public: + using self_type = flow_graph; + using grid_type = G; + using xt_selector = S; + using impl_type = detail::flow_graph_impl; + using operators_type = flow_operator_sequence; + + using size_type = typename grid_type::size_type; + + using data_type = typename grid_type::grid_data_type; + using data_array_type = xt_array_t; + using shape_type = typename data_array_type::shape_type; + using data_array_size_type = xt_array_t; + + using graph_map = std::map>; + using graph_impl_map = std::map; + using elevation_map = std::map>; + + /** + * Flow graph constructor. + * + * @param grid The grid object. + * @param operators The sequence of flow operators to use for updating the graph. + */ + flow_graph(G& grid, operators_type operators) + : m_grid(grid) + , m_impl(grid, operators.all_single_flow()) + , m_operators(std::move(operators)) + { + // sanity checks + if (!m_operators.graph_updated()) + { + throw std::invalid_argument( + "must have at least one operator that updates the flow graph"); + } + if (m_operators.out_flowdir() == flow_direction::undefined) + { + throw std::invalid_argument( + "must have at least one operator that defines the output flow direction type"); + } + + // pre-allocate graph and elevation snapshots + for (const auto& key : m_operators.graph_snapshot_keys()) + { + bool single_flow = m_operators.snapshot_single_flow(key); + auto graph = new self_type(grid, single_flow); + + m_graph_snapshots.insert({ key, std::unique_ptr(std::move(graph)) }); + m_graph_impl_snapshots.insert({ key, (*m_graph_snapshots.at(key)).m_impl }); + } + for (const auto& key : m_operators.elevation_snapshot_keys()) + { + auto snapshot = data_array_type::from_shape(grid.shape()); + m_elevation_snapshots.insert( + { key, std::make_unique(std::move(snapshot)) }); + } + + // pre-allocate hydrologically corrected elevation + if (m_operators.elevation_updated()) + { + m_elevation_copy = xt::empty(grid.shape()); + } + } + + /** + * Returns a vector of pointers to the flow operators objects used in + * this graph. + * + * Note: the ``flow_operator`` base class has very limited polymorphism + * (i.e., only its ``name`` method is virtual). It is still possible to + * downcast the returned pointers (e.g., using the visitor pattern). + */ + const std::vector& operators() const + { + return m_operators.m_op_vec; + } + + /** + * Returns ``true`` if the graph has single flow directions. + */ + bool single_flow() const + { + return m_operators.out_flowdir() == flow_direction::single; + } + + /* + * Returns the names of the graph snapshots (if any). + */ + const std::vector& graph_snapshot_keys() const + { + return m_operators.graph_snapshot_keys(); + } + + /** + * Graph snapshot getter. + * + * @param name Name of the snapshot. + */ + self_type& graph_snapshot(std::string name) const + { + return *(m_graph_snapshots.at(name)); + } + + /** + * Returns the names of the elevation snapshots. + */ + const std::vector& elevation_snapshot_keys() const + { + return m_operators.elevation_snapshot_keys(); + } + + /** + * Elevation snapshot getter. + * + * @param name Name of the snapshot. + */ + const data_array_type& elevation_snapshot(std::string name) const + { + return *(m_elevation_snapshots.at(name)); + } + + /** + * Update flow routes from the input topographic surface. + * + * This applies in chain the flow operators and takes snapshots (if any). + * + * @param elevation The input topographic surface elevation. + * + * @return Either the input elevation unchanged or the elevation that + * has been updated in order to route flow accross closed depressions. + * + */ + const data_array_type& update_routes(const data_array_type& elevation) + { + if (!m_writeable) + { + throw std::runtime_error("cannot update routes (graph is read-only)"); + } + + data_array_type* elevation_ptr; + + if (m_operators.elevation_updated()) + { + // reset and use hydrologically corrected elevation + m_elevation_copy = elevation; + elevation_ptr = &m_elevation_copy; + } + else + { + // pretty safe to remove the const qualifier (shouldn't be updated) + elevation_ptr = const_cast(&elevation); + } + + // loop over flow operator implementations + for (auto op = m_operators.impl_begin(); op != m_operators.impl_end(); ++op) + { + op->apply(m_impl, *elevation_ptr); + op->save(m_impl, m_graph_impl_snapshots, *elevation_ptr, m_elevation_snapshots); + } + + return *elevation_ptr; + } + + /** + * Returns a reference to the corresponding grid object. + */ + grid_type& grid() const + { + return m_grid; + } + + /** + * Returns the total number of nodes in the graph (equals to the size of + * the grid). + */ + size_type size() const + { + return m_grid.size(); + } + + /** + * Returns the shape of the grid node arrays. + */ + shape_type grid_shape() const + { + // grid shape may have a different type (e.g., from xtensor containers) + auto shape = m_grid.shape(); + shape_type data_array_shape(shape.begin(), shape.end()); + return data_array_shape; + } + + /** + * Returns a reference to the flow graph implementation. + */ + const impl_type& impl() const + { + return m_impl; + } + + /** + * Traverse the flow graph in the top->down direction and accumulate + * locally produced quantities or fluxes. + * + * The local quantitites or fluxes (i.e., source) may for example + * correspond to precipitation, surface water runoff, sediment flux, + * etc. + * + * The accumulated values represent at each node of the graph the + * integral of the source over the node upslope contributing area. + * + * For example, if the source units are meters (height), the units of + * the output accumulated values are cubic meters (volume). + * + * @param src The source, must be given per area unit and have the same + * shape than ``grid_shape``. + * + * @return The output accumulated values (array of same shape than ``grid_shape``). + */ + data_array_type accumulate(const data_array_type& src) const + { + return m_impl.accumulate(src); + } + + /** + * Traverse the flow graph in the top->down direction and accumulate + * locally produced quantities or fluxes. + * + * This version allows reusing a pre-allocated array to store the output. + * + * @param acc The output accumulated values. + * @param src The source, must be given per area unit. + * + * Both `acc` and `src` must of the same shape then ``grid_shape``. + */ + void accumulate(data_array_type& acc, const data_array_type& src) const + { + return m_impl.accumulate(acc, src); + } + + /** + * Traverse the flow graph in the top->down direction and accumulate + * locally produced quantities or fluxes. + * + * @param src The spatially uniform source (scalar). + * + * @return The output accumulated values (array of same shape than ``grid_shape``). + */ + data_array_type accumulate(data_type src) const + { + return m_impl.accumulate(src); + } + + /** + * Traverse the flow graph in the top->down direction and accumulate + * locally produced quantities or fluxes. + * + * @param acc The output accumulated values. + * @param src The spatially uniform source (scalar). + * + * Both `acc` and `src` must of the same shape then ``grid_shape``. + */ + void accumulate(data_array_type& acc, data_type src) const + { + return m_impl.accumulate(acc, src); + } + + /** + * Delineate catchments (or basins). + * + * A catchment is defined by all adjacent nodes that flow towards the + * same outlet (or pit) graph node. + * + * @return Catchment ids (array of same shape than ``grid_shape``). + * + * Note: results may be cached. + */ + data_array_size_type basins() + { + data_array_size_type basins = data_array_type::from_shape(m_grid.shape()); + auto basins_flat = xt::flatten(basins); + + m_impl.compute_basins(); + basins_flat = m_impl.basins(); + + return basins; + } + + private: + bool m_writeable = true; + grid_type& m_grid; + impl_type m_impl; + data_array_type m_elevation_copy; + + graph_map m_graph_snapshots; + graph_impl_map m_graph_impl_snapshots; + elevation_map m_elevation_snapshots; + + operators_type m_operators; + + // used internally for creating graph snapshots + explicit flow_graph(grid_type& grid, bool single_flow) + : m_writeable(false) + , m_grid(grid) + , m_impl(grid, single_flow) + { + } + }; +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/flow/flow_graph_impl.hpp b/contrib/fastscape/include/fastscapelib/flow/flow_graph_impl.hpp new file mode 100644 index 00000000000..648b997a769 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/flow_graph_impl.hpp @@ -0,0 +1,411 @@ +#ifndef FASTSCAPELIB_FLOW_GRAPH_IMPL_H_ +#define FASTSCAPELIB_FLOW_GRAPH_IMPL_H_ + +#include +#include +#include + +#include "xtensor/xbroadcast.hpp" +#include "xtensor/xstrided_view.hpp" +#include "xtensor/xview.hpp" + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/utils/iterators.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + namespace detail + { + + /** + * Flow operator implementation forward declaration. + */ + template + class flow_operator_impl; + + /** + * Flow graph implementation. + * + * Implements the graph underlying data structures and API + * (excepted for graph update that is implemented in flow operators). + * + * This template class has no definition, it must be specialized + * using the Tag parameter. + * + * @tparam G The grid type + * @tparam S The xtensor selector type + * @tparam Tag The flow graph implementation Tag + * + */ + template + class flow_graph_impl; + } + + /** + * Fixed array flow graph implementation. + * + * This implementation uses fixed shape arrays to store the graph + * topology (node receivers, donors, etc.). + * + */ + struct flow_graph_fixed_array_tag + { + }; + + + namespace detail + { + + template + class flow_graph_impl + { + public: + using self_type = flow_graph_impl; + using grid_type = G; + using xt_selector = S; + using tag = flow_graph_fixed_array_tag; + + using size_type = typename grid_type::size_type; + using data_type = typename grid_type::grid_data_type; + using data_array_type = xt_array_t; + + using donors_type = xt_tensor_t; + using donors_count_type = xt_tensor_t; + using receivers_type = donors_type; + using receivers_count_type = donors_count_type; + using receivers_distance_type = xt_tensor_t; + using receivers_weight_type = xt_tensor_t; + using dfs_indices_type = xt_tensor_t; + + using basins_type = xt_tensor_t; + + flow_graph_impl(grid_type& grid, bool single_flow = false) + : m_single_flow(single_flow) + , m_grid(grid) + { + size_type n_receivers_max = grid_type::n_neighbors_max(); + + if (single_flow) + { + n_receivers_max = 1; + } + + using shape_type = std::array; + const shape_type receivers_shape = { grid.size(), n_receivers_max }; + const shape_type donors_shape = { grid.size(), grid_type::n_neighbors_max() + 1 }; + + m_receivers = xt::ones(receivers_shape) * -1; + m_receivers_count = xt::zeros({ grid.size() }); + m_receivers_distance = xt::ones(receivers_shape) * -1; + m_receivers_weight = xt::zeros(receivers_shape); + + m_donors = xt::ones(donors_shape) * -1; + m_donors_count = xt::zeros({ grid.size() }); + + m_dfs_indices = xt::ones({ grid.size() }) * -1; + + // TODO: basins are not always needed (only init on-demand) + m_basins = xt::empty({ grid.size() }); + }; + + bool single_flow() const + { + return m_single_flow; + } + + G& grid() const + { + return m_grid; + }; + + size_type size() const + { + return m_grid.size(); + }; + + const receivers_type& receivers() const + { + return m_receivers; + }; + + const receivers_count_type& receivers_count() const + { + return m_receivers_count; + }; + + const receivers_distance_type& receivers_distance() const + { + return m_receivers_distance; + }; + + const receivers_weight_type& receivers_weight() const + { + return m_receivers_weight; + }; + + const donors_type& donors() const + { + return m_donors; + }; + + const donors_count_type& donors_count() const + { + return m_donors_count; + }; + + void compute_donors(); + + const dfs_indices_type& dfs_indices() const + { + return m_dfs_indices; + }; + + void compute_dfs_indices_bottomup(); + void compute_dfs_indices_topdown(); + + /* + * Should be used for graph traversal in an explicit direction. + */ + inline stl_container_iterator_wrapper nodes_indices_bottomup() const + { + return m_dfs_indices; + } + + const std::vector& outlets() const + { + return m_outlets; + } + + const std::vector& pits(); + + const basins_type& basins() const + { + return m_basins; + }; + + void compute_basins(); + + template + void accumulate(data_array_type& acc, T&& src) const; + + template + data_array_type accumulate(T&& src) const; + + private: + bool m_single_flow; + grid_type& m_grid; + + donors_type m_donors; + donors_count_type m_donors_count; + + receivers_type m_receivers; + receivers_count_type m_receivers_count; + receivers_distance_type m_receivers_distance; + receivers_weight_type m_receivers_weight; + + dfs_indices_type m_dfs_indices; + + basins_type m_basins; + std::vector m_outlets; + std::vector m_pits; + + template + friend class flow_router_impl; + + template + friend class sink_resolver_impl; + + template + friend class flow_operator_impl; + }; + + template + void flow_graph_impl::compute_donors() + { + m_donors_count.fill(0); + + for (size_type i = 0; i < size(); ++i) + { + if (m_receivers(i, 0) != i) + { + // TODO: make it work with multiple flow + auto irec = m_receivers(i, 0); + m_donors(irec, m_donors_count(irec)++) = i; + } + } + } + + /* + * Perform depth-first search in the downstream->upstream direction + * and store the node indices for faster graph traversal. + */ + template + void flow_graph_impl::compute_dfs_indices_bottomup() + { + size_type nstack = 0; + + std::stack tmp; + + for (size_type i = 0; i < size(); ++i) + { + if (m_receivers(i, 0) == i) + { + tmp.push(i); + m_dfs_indices(nstack++) = i; + } + + while (!tmp.empty()) + { + size_type istack = tmp.top(); + tmp.pop(); + + for (size_type k = 0; k < m_donors_count(istack); ++k) + { + const auto idonor = m_donors(istack, k); + if (idonor != istack) + { + m_dfs_indices(nstack++) = idonor; + tmp.push(idonor); + } + } + } + } + + assert(nstack == size()); + } + + /* + * Perform depth-first search in the upstream->dowstream direction + * and store the node indices for faster graph traversal. + * + * Note: indices are stored in the downstream->upstream direction! + */ + template + void flow_graph_impl::compute_dfs_indices_topdown() + { + size_type nstack = 0; + std::stack tmp; + + std::vector visited_count(size(), 0); + + for (size_type i = 0; i < size(); ++i) + { + if (m_donors_count(i) == 0) + { + tmp.push(i); + } + + while (!tmp.empty()) + { + size_type istack = tmp.top(); + tmp.pop(); + + m_dfs_indices(nstack++) = istack; + + for (size_type k = 0; k < m_receivers_count(istack); ++k) + { + const auto irec = m_receivers(istack, k); + visited_count[irec]++; + + if (visited_count[irec] == m_donors_count(irec)) + { + tmp.push(irec); + } + } + } + } + + assert(nstack == size()); + + // downstream->upstream order + std::reverse(m_dfs_indices.begin(), m_dfs_indices.end()); + } + + template + void flow_graph_impl::compute_basins() + { + size_type current_basin = 0; + + m_outlets.clear(); + + for (const auto& inode : nodes_indices_bottomup()) + { + // outlet node has only one receiver: itself + if (inode == m_receivers(inode, 0)) + { + m_outlets.push_back(inode); + current_basin++; + } + + m_basins(inode) = current_basin - 1; + } + + assert(m_outlets.size() == current_basin); + } + + template + auto flow_graph_impl::pits() + -> const std::vector& + { + const auto& nodes_status = grid().nodes_status(); + m_pits.clear(); + + for (const auto outlet : m_outlets) + { + if (nodes_status.flat(outlet) != node_status::fixed_value) + { + m_pits.push_back(outlet); + } + } + + return m_pits; + } + + template + template + void flow_graph_impl::accumulate(data_array_type& acc, + T&& src) const + { + // TODO: assert(acc.shape() == m_grid.shape()) with compatible shape types + auto src_arr = xt::broadcast(std::forward(src), m_grid.shape()); + + // re-init accumulated values + acc.fill(0); + + auto nodes_indices = nodes_indices_bottomup(); + + for (auto inode_ptr = nodes_indices.rbegin(); inode_ptr != nodes_indices.rend(); + ++inode_ptr) + { + const auto inode = *inode_ptr; + + acc.flat(inode) += m_grid.nodes_areas(inode) * src_arr(inode); + + for (size_type r = 0; r < m_receivers_count[inode]; ++r) + { + size_type ireceiver = m_receivers(inode, r); + if (ireceiver != inode) + { + acc.flat(ireceiver) += acc.flat(inode) * m_receivers_weight(inode, r); + } + } + } + } + + template + template + auto flow_graph_impl::accumulate(T&& src) const + -> data_array_type + { + data_array_type acc = data_array_type::from_shape(m_grid.shape()); + accumulate(acc, std::forward(src)); + return acc; + } + } +} + + +#endif // FASTSCAPELIB_FLOW_GRAPH_IMPL_H_ diff --git a/contrib/fastscape/include/fastscapelib/flow/flow_operator.hpp b/contrib/fastscape/include/fastscapelib/flow/flow_operator.hpp new file mode 100644 index 00000000000..5daad3e3aec --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/flow_operator.hpp @@ -0,0 +1,497 @@ +#ifndef FASTSCAPELIB_FLOW_OPERATOR_H_ +#define FASTSCAPELIB_FLOW_OPERATOR_H_ + +#include +#include +#include +#include + + +namespace fastscapelib +{ + + /** + * Flow direction enum class. + * + * Used as a tag to specify the kind of flow graph (i.e., single, multiple + * or undefined) expected as input or computed as output of a flow operator. + * + * A ``single`` direction flow (directed acyclic) graph is a tree (or a + * forest of trees) where the total amount of matter (e.g., water, sediment) + * at each grid node is propagated to a unique (downslope) node neighbor. In + * a ``multiple`` direction flow graph, that amount is partionned among one + * or more node neighbors. + * + * Use ``undefined`` for a flow operator that works on both single and + * multiple flow direction graphs or that do not use the graph (e.g., sink + * resolver only correcting the topographic elevation). + * + */ + enum class flow_direction + { + undefined, + single, + multi + }; + + /** + * Flow operator. + * + * It represents a logical unit that can read and/or modify in-place the flow + * graph and topographic elevation. It is a common type for flow routers, sink + * resolvers and snapshots (save intermediate graph and/or elevation states). + * + * A flow operator may have one or more implementations, each relative to a + * specific flow graph implementation. Note: this class and its derived classes + * only contain the operators' (static) properties and parameters + * (implementations are in separate classes). + * + * Do not use this class directly (it has no implementation). Use instead its + * derived classes. + * + */ + class flow_operator + { + public: + virtual ~flow_operator() + { + } + + /** + * Returns the name of the operator. + */ + inline virtual std::string name() const noexcept = 0; + + /** + * Whether the operator updates topographic elevation. + */ + static constexpr bool elevation_updated = false; + + /** + * Whether the operator updates the flow graph. + */ + static constexpr bool graph_updated = false; + + /** + * The type of flow direction required as input of this operator. + */ + static constexpr flow_direction in_flowdir = flow_direction::undefined; + + /** + * The output flow direction type of this operator. + */ + static constexpr flow_direction out_flowdir = flow_direction::undefined; + }; + + + namespace detail + { + + /** + * Flow operator implementation base class. + * + * It exposes two methods: + * + * - ``apply``: may update in-place a flow graph implementation and/or + * topographic elevation + * - ``save`` : only used by the flow_snapshot operator. + * + * By default, these two methods do nothing. A flow operator implementation + * should re-implement only the ``apply`` method. + * + * The methods are not declared virtual (no polymorphism). Template + * specialization + type erasure is used instead. + * + * A flow operator implementation is decoupled from its flow operator + * corresponding instance (shared pointer). While flow operators may be + * instantiated outside of the flow_graph class, flow operator implementations + * are instantiated inside the flow_graph class as they need the + * (grid-dependent) flow graph implementation and topographic elevation types. + * + * @tparam FG The flow graph implementation type (grid-dependent) + * @tparam OP The flow operator type + * + */ + template + class flow_operator_impl_base + { + public: + using graph_impl_type = FG; + using data_array_type = typename graph_impl_type::data_array_type; + using graph_impl_map = std::map; + using elevation_map = std::map>; + + void apply(FG& /*graph_impl*/, data_array_type& /*elevation*/) + { + } + + void save(const FG& /*graph_impl*/, + graph_impl_map& /*graph_impl_snapshots*/, + const data_array_type& /*elevation*/, + elevation_map& /*elevation_snapshots*/) const + { + } + + protected: + flow_operator_impl_base(std::shared_ptr ptr) + : m_op_ptr(std::move(ptr)) + { + } + + ~flow_operator_impl_base() = default; + + std::shared_ptr m_op_ptr; + }; + + /** + * Flow operator implementation. + * + * This template class is not directly constructible. Template specialized + * implementation classes must be provided for each operator (OP) for at least + * one of the available flow graph implementations (Tag). + * + * @tparam FG The flow graph implementation type (grid-dependent) + * @tparam OP The flow operator type + * @tparam Tag The flow graph implementation tag + * + */ + template + class flow_operator_impl : public flow_operator_impl_base + { + public: + flow_operator_impl(std::shared_ptr ptr) = delete; + }; + + /** + * Flow operator implementation facade. + * + * This facade class implements type erasure so that multiple flow operators + * of different types can be applied in chain in a flow graph. + * + * It takes a flow operator instance as input and creates a wrapper to the + * corresponding flow operator implementation instance (if such implementation + * exists for the flow graph implementation given as template argument). + * + * @tparam FG The flow graph implementation type (grid-dependent) + * + */ + template + class flow_operator_impl_facade + { + public: + using data_array_type = typename FG::data_array_type; + using graph_impl_map = std::map; + using elevation_map = std::map>; + + template + flow_operator_impl_facade(std::shared_ptr ptr) + : m_wrapper_ptr(std::make_unique>(std::move(ptr))) + { + } + + // implement move semantics only (entity of flow_sequence) + flow_operator_impl_facade(flow_operator_impl_facade& op_impl_facade) = delete; + explicit flow_operator_impl_facade(flow_operator_impl_facade&& op_impl_facade) + : m_wrapper_ptr(std::move(op_impl_facade.m_wrapper_ptr)) + { + } + + void apply(FG& graph_impl, data_array_type& elevation) + { + return m_wrapper_ptr->apply(graph_impl, elevation); + } + + void save(const FG& graph_impl, + graph_impl_map& graph_impl_snapshots, + const data_array_type& elevation, + elevation_map& elevation_snapshots) const + { + m_wrapper_ptr->save( + graph_impl, graph_impl_snapshots, elevation, elevation_snapshots); + } + + struct flow_operator_impl_wrapper_base + { + virtual ~flow_operator_impl_wrapper_base() + { + } + virtual void apply(FG& graph_impl, data_array_type& elevation) = 0; + virtual void save(const FG& graph_impl, + graph_impl_map& graph_impl_snapshots, + const data_array_type& elevation, + elevation_map& elevation_snapshots) const + = 0; + }; + + template + class flow_operator_impl_wrapper : public flow_operator_impl_wrapper_base + { + public: + flow_operator_impl_wrapper(std::shared_ptr ptr) + : m_op_impl(std::move(ptr)) + { + } + + void apply(FG& graph_impl, data_array_type& elevation) override + { + return m_op_impl.apply(graph_impl, elevation); + } + + void save(const FG& graph_impl, + graph_impl_map& graph_impl_snapshots, + const data_array_type& elevation, + elevation_map& elevation_snapshots) const override + { + m_op_impl.save( + graph_impl, graph_impl_snapshots, elevation, elevation_snapshots); + } + + private: + flow_operator_impl m_op_impl; + }; + + std::unique_ptr m_wrapper_ptr; + }; + } + + + // forward delcarations + class flow_snapshot; + + template + class flow_graph; + + /** + * Immutable container of flow operators (e.g., flow routers, sink resolvers, + * flow snapshots) that are applied in chain when updating a flow graph. + * + * This class is not intended to be used as a stand-alone container. It is + * used internally as an entity of flow_graph and can (should) be created + * implicitly in the flow_graph constructor. + * + * @tparam FG The flow graph implementation type. + */ + template + class flow_operator_sequence + { + public: + using impl_type = FG; + using operator_impl_type = detail::flow_operator_impl_facade; + using iterator_type = typename std::vector::iterator; + + template + flow_operator_sequence(OPs&&... operators) + { + int i = 0; + ( + [&] + { + ++i; + add_operator(std::forward(operators)); + }(), + ...); + } + + // implement move semantics only (entity of flow_graph) + flow_operator_sequence(flow_operator_sequence& operators) = delete; + flow_operator_sequence(flow_operator_sequence&& operators) + : m_op_vec(std::move(operators.m_op_vec)) + , m_op_impl_vec(std::move(operators.m_op_impl_vec)) + , m_graph_snapshot_keys(operators.graph_snapshot_keys()) + , m_graph_snapshot_single_flow(operators.m_graph_snapshot_single_flow) + , m_elevation_snapshot_keys(operators.elevation_snapshot_keys()) + , m_elevation_updated(operators.elevation_updated()) + , m_graph_updated(operators.graph_updated()) + , m_out_flowdir(operators.out_flowdir()) + , m_all_single_flow(operators.all_single_flow()) + { + } + + flow_operator_sequence& operator=(flow_operator_sequence& operators) = delete; + flow_operator_sequence& operator=(flow_operator_sequence&& operators) + { + m_op_vec = std::move(operators.m_op_vec); + m_op_impl_vec = std::move(operators.m_op_impl_vec); + m_graph_snapshot_keys = std::move(operators.graph_snapshot_keys()); + m_graph_snapshot_single_flow = std::move(operators.m_graph_snapshot_single_flow); + m_elevation_snapshot_keys = std::move(operators.elevation_snapshot_keys()); + m_elevation_updated = operators.elevation_updated(); + m_graph_updated = operators.graph_updated(); + m_out_flowdir = operators.out_flowdir(); + m_all_single_flow = operators.all_single_flow(); + return *this; + } + + /** + * STL-compatible iterator pointing to the 1st operator. + */ + iterator_type begin() + { + return m_op_vec.begin(); + } + + /** + * STL-compatible iterator pointing to the last operator. + */ + iterator_type end() + { + return m_op_vec.end(); + } + + /** + * Returns true if at least one operator in the sequence + * updates topographic elevation. + */ + bool elevation_updated() const + { + return m_elevation_updated; + } + + /** + * Returns true if at least one operator in the sequence + * updates the flow graph. + */ + bool graph_updated() const + { + return m_graph_updated; + } + + /** + * Returns the flow direction type (single, multiple or undefined) + * of the final state of the flow graph, after having applied all operators. + */ + flow_direction out_flowdir() const + { + return m_out_flowdir; + } + + /** + * Returns true if all intermediate states of the flow graph + * have single flow directions. + */ + bool all_single_flow() const + { + return m_all_single_flow; + } + + /** + * Returns true if the graph snapshot given by ``name`` is single flow. + */ + bool snapshot_single_flow(std::string name) + { + return m_graph_snapshot_single_flow.at(name); + } + + /** + * Returns the names of all flow graph snapshots to create. + */ + const std::vector& graph_snapshot_keys() const + { + return m_graph_snapshot_keys; + } + + /** + * Returns the names of all topographic elevation snapshots to create. + */ + const std::vector& elevation_snapshot_keys() const + { + return m_elevation_snapshot_keys; + } + + private: + std::vector m_op_vec; + std::vector m_op_impl_vec; + + std::vector m_graph_snapshot_keys; + std::map m_graph_snapshot_single_flow; + std::vector m_elevation_snapshot_keys; + + bool m_elevation_updated = false; + bool m_graph_updated = false; + flow_direction m_out_flowdir = flow_direction::undefined; + bool m_all_single_flow = true; + + template + void add_operator(OP&& op) + { + auto op_ptr = std::make_shared(std::forward(op)); + add_operator(std::move(op_ptr)); + } + + template + void add_operator(std::shared_ptr ptr); + + void update_snapshots(const flow_snapshot& snapshot); + + // Only for bindings (in case the variadic templates constructor cannot be used). + template + friend flow_operator_sequence<_FG> make_flow_operator_sequence(OPs&& operators); + + // iterate through operator implementations (in flow_graph) + iterator_type impl_begin() + { + return m_op_impl_vec.begin(); + } + + iterator_type impl_end() + { + return m_op_impl_vec.end(); + } + + template + friend class flow_graph; + }; + + + // Add an operator of an abitrary type to the container, create an instance + // of the corresponding implementation and update the sequence properties + // accordingly. + // + // The current (final state) flow direction is updated only if the operator + // updates the flow graph and explicitly defines an output flow direction + // type (i.e., single or multiple). + // + // Also checks consistency between the current flow direction of the + // sequence and the expected input flow direction of the operator to add. + // + template + template + void flow_operator_sequence::add_operator(std::shared_ptr ptr) + { + static_assert(std::is_base_of_v, "not a flow_operator type"); + + if constexpr (std::is_same_v) + { + update_snapshots(*ptr); + } + if (ptr->in_flowdir != flow_direction::undefined && ptr->in_flowdir != m_out_flowdir) + { + throw std::invalid_argument("flow operator " + ptr->name() + + " has incompatible input flow directions"); + } + if (ptr->elevation_updated) + { + m_elevation_updated = true; + } + if (ptr->graph_updated) + { + m_graph_updated = true; + + if (ptr->out_flowdir != flow_direction::undefined) + { + m_out_flowdir = ptr->out_flowdir; + + if (ptr->out_flowdir != flow_direction::single) + { + m_all_single_flow = false; + } + } + } + + m_op_vec.push_back(ptr.get()); + m_op_impl_vec.push_back(operator_impl_type(std::move(ptr))); + } +} + + +#endif // FASTSCAPELIB_FLOW_OPERATOR_H_ diff --git a/contrib/fastscape/include/fastscapelib/flow/flow_router.hpp b/contrib/fastscape/include/fastscapelib/flow/flow_router.hpp new file mode 100644 index 00000000000..fe7b0860d10 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/flow_router.hpp @@ -0,0 +1,248 @@ +#ifndef FASTSCAPELIB_FLOW_FLOW_ROUTER_H +#define FASTSCAPELIB_FLOW_FLOW_ROUTER_H + + +#include "fastscapelib/flow/flow_graph_impl.hpp" +#include "fastscapelib/flow/flow_operator.hpp" +#include "fastscapelib/grid/base.hpp" + + +namespace fastscapelib +{ + + /** + * Single direction flow router operator. + * + * This flow operator routes all the flow passing through a grid node + * towards its neighbors node of steepest slope. + * + * \rst + * On a raster grid with 8-node connectivity, this is equivalent to the + * so-called "D8" algorithm :cite:p:`OCallaghan1984`. + * \endrst + * + */ + class single_flow_router : public flow_operator + { + public: + inline std::string name() const noexcept override + { + return "single_flow_router"; + } + + static constexpr bool graph_updated = true; + static constexpr flow_direction out_flowdir = flow_direction::single; + }; + + + namespace detail + { + + /** + * Single flow router operator implementation. + */ + template + class flow_operator_impl + : public flow_operator_impl_base + { + public: + using graph_impl_type = FG; + using base_type = flow_operator_impl_base; + using data_array_type = typename graph_impl_type::data_array_type; + + flow_operator_impl(std::shared_ptr ptr) + : base_type(std::move(ptr)){}; + + void apply(graph_impl_type& graph_impl, data_array_type& elevation) + { + // single flow optimization + graph_impl.m_receivers_count.fill(1); + auto weights = xt::col(graph_impl.m_receivers_weight, 0); + weights.fill(1.); + + using neighbors_type = typename graph_impl_type::grid_type::neighbors_type; + + double slope, slope_max; + neighbors_type neighbors; + + auto& grid = graph_impl.grid(); + auto& donors = graph_impl.m_donors; + auto& donors_count = graph_impl.m_donors_count; + auto& receivers = graph_impl.m_receivers; + auto& dist2receivers = graph_impl.m_receivers_distance; + + donors_count.fill(0); + + for (auto i : grid.nodes_indices()) + { + receivers(i, 0) = i; + dist2receivers(i, 0) = 0; + slope_max = std::numeric_limits::min(); + + if (grid.nodes_status().flat(i) == node_status::fixed_value) + { + continue; + } + + for (auto n : grid.neighbors(i, neighbors)) + { + slope = (elevation.flat(i) - elevation.flat(n.idx)) / n.distance; + + if (slope > slope_max) + { + slope_max = slope; + receivers(i, 0) = n.idx; + dist2receivers(i, 0) = n.distance; + } + } + + // fastpath for single flow + auto irec = receivers(i, 0); + donors(irec, donors_count(irec)++) = i; + } + + graph_impl.compute_dfs_indices_bottomup(); + } + }; + } + + + /** + * Multiple direction flow router operator. + * + * This flow operator partitions the flow passing through a grid node among + * its downslope neighbor nodes. Flow partitioning is proportional to the + * local slope between a node and its neighbors (power relationship with a + * fixed exponent parameter). + * + * The fraction \f$f_{i,j}\f$ of flow routed from node \f$i\f$ to its + * neighbor \f$j\f$ is given by + * + * @f[ + * f_{i,j} = \frac{\max (0, S_{i, j}^p)}{\sum_{k \in N} \max (0, S_{i, k}^p)} + * @f] + * + * where \f$p\f$ is the slope exponent parameter, \f$S_{i, j}\f$ is the + * slope between \f$i\f$, \f$j\f$ and \f$N\f$ is the set of all neighbors of + * \f$i\f$. + * + * \rst + * Depending on the value of the slope exponent parameter, this is equivalent to + * the methods described in :cite:t:`Quinn1991` or :cite:t:`Holmgren1994`. + * \endrst + * + */ + class multi_flow_router : public flow_operator + { + public: + inline std::string name() const noexcept override + { + return "multi_flow_router"; + } + + static constexpr bool graph_updated = true; + static constexpr flow_direction out_flowdir = flow_direction::multi; + + /** + * Create a new multi flow router operator. + * + * @param slope_exp The flow partition slope exponent. + */ + multi_flow_router(double slope_exp) + : m_slope_exp(slope_exp) + { + } + + double m_slope_exp = 1.0; + }; + + + namespace detail + { + + /** + * Multiple direction flow router operator implementation. + */ + template + class flow_operator_impl + : public flow_operator_impl_base + { + public: + using graph_impl_type = FG; + using base_type = flow_operator_impl_base; + using data_array_type = typename graph_impl_type::data_array_type; + using size_type = typename graph_impl_type::grid_type::size_type; + + flow_operator_impl(std::shared_ptr ptr) + : base_type(std::move(ptr)){}; + + void apply(graph_impl_type& graph_impl, data_array_type& elevation) + { + using neighbors_type = typename graph_impl_type::grid_type::neighbors_type; + + double slope; + double weight, weights_sum; + neighbors_type neighbors; + size_type nrec; + + auto& grid = graph_impl.grid(); + auto& donors = graph_impl.m_donors; + auto& donors_count = graph_impl.m_donors_count; + auto& receivers = graph_impl.m_receivers; + auto& receivers_count = graph_impl.m_receivers_count; + auto& receivers_weight = graph_impl.m_receivers_weight; + auto& dist2receivers = graph_impl.m_receivers_distance; + + donors_count.fill(0); + + for (auto i : grid.nodes_indices()) + { + if (grid.nodes_status().data()[i] == node_status::fixed_value) + { + receivers_count(i) = 1; + receivers(i, 0) = i; + receivers_weight(i, 0) = 0; + dist2receivers(i, 0) = 0; + continue; + } + + nrec = 0; + weights_sum = 0; + + for (auto n : grid.neighbors(i, neighbors)) + { + if (elevation.flat(i) > elevation.flat(n.idx)) + { + slope = (elevation.flat(i) - elevation.flat(n.idx)) / n.distance; + + receivers(i, nrec) = n.idx; + dist2receivers(i, nrec) = n.distance; + + weight = std::pow(slope, this->m_op_ptr->m_slope_exp); + weights_sum += weight; + receivers_weight(i, nrec) = weight; + + // update donors (note: not thread safe if later parallelization) + donors(n.idx, donors_count(n.idx)++) = i; + + nrec++; + } + } + + receivers_count(i) = nrec; + + // normalize weights + for (size_type j = 0; j < nrec; j++) + { + receivers_weight(i, j) /= weights_sum; + } + } + + // DFS upstream->downstream so that it works with multi-directions flow + graph_impl.compute_dfs_indices_topdown(); + } + }; + } +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/flow/flow_snapshot.hpp b/contrib/fastscape/include/fastscapelib/flow/flow_snapshot.hpp new file mode 100644 index 00000000000..06668f82ce4 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/flow_snapshot.hpp @@ -0,0 +1,167 @@ +#ifndef FASTSCAPELIB_FLOW_SNAPSHOT_H_ +#define FASTSCAPELIB_FLOW_SNAPSHOT_H_ + +#include "fastscapelib/flow/flow_graph_impl.hpp" +#include "fastscapelib/flow/flow_operator.hpp" + + +namespace fastscapelib +{ + + /** + * Flow snapshot operator. + * + * A special flow operator used to save intermediate states + * of the flow graph and/or topographic elevation values while + * applying the other operators in chain. + * + * Those saved states are accessible from the ``flow_graph`` object. + * + */ + class flow_snapshot : public flow_operator + { + public: + inline std::string name() const noexcept override + { + return "flow_snapshot"; + } + + flow_snapshot(std::string snapshot_name, + bool save_graph = true, + bool save_elevation = false) + : m_snapshot_name(snapshot_name) + , m_save_graph(save_graph) + , m_save_elevation(save_elevation) + { + } + + const std::string& snapshot_name() const noexcept + { + return m_snapshot_name; + } + + bool save_graph() const noexcept + { + return m_save_graph; + } + + bool save_elevation() const noexcept + { + return m_save_elevation; + } + + private: + std::string m_snapshot_name; + bool m_save_graph; + bool m_save_elevation; + }; + + + namespace detail + { + + /* + * Flow snapshot operator implementation (fixed array graph). + */ + template + class flow_operator_impl + : public flow_operator_impl_base + { + public: + using base_type = flow_operator_impl_base; + using data_array_type = typename base_type::data_array_type; + + using graph_impl_map = std::map; + using elevation_map = std::map>; + + flow_operator_impl(std::shared_ptr ptr) + : base_type(std::move(ptr)){}; + + void save(const FG& graph_impl, + graph_impl_map& graph_impl_snapshots, + const data_array_type& elevation, + elevation_map& elevation_snapshots) const + { + if (this->m_op_ptr->save_graph()) + { + _save(graph_impl, get_snapshot(graph_impl_snapshots)); + } + if (this->m_op_ptr->save_elevation()) + { + _save(elevation, get_snapshot(elevation_snapshots)); + } + } + + private: + FG& get_snapshot(graph_impl_map& graph_impl_snapshots) const + { + return graph_impl_snapshots.at(this->m_op_ptr->snapshot_name()); + } + + data_array_type& get_snapshot(elevation_map& elevation_snapshots) const + { + return *(elevation_snapshots.at(this->m_op_ptr->snapshot_name())); + } + + void _save(const FG& graph_impl, FG& graph_impl_snapshot) const + { + graph_impl_snapshot.m_receivers_count = graph_impl.m_receivers_count; + graph_impl_snapshot.m_donors_count = graph_impl.m_donors_count; + graph_impl_snapshot.m_dfs_indices = graph_impl.m_dfs_indices; + + if (graph_impl_snapshot.single_flow()) + { + auto receivers_col = xt::col(graph_impl_snapshot.m_receivers, 0); + receivers_col = xt::col(graph_impl.m_receivers, 0); + auto receivers_distance_col + = xt::col(graph_impl_snapshot.m_receivers_distance, 0); + receivers_distance_col = xt::col(graph_impl.m_receivers_distance, 0); + auto receivers_weight_col = xt::col(graph_impl_snapshot.m_receivers_weight, 0); + receivers_weight_col = xt::col(graph_impl.m_receivers_weight, 0); + auto donors_col = xt::col(graph_impl_snapshot.m_donors, 0); + donors_col = xt::col(graph_impl.m_donors, 0); + } + else + { + graph_impl_snapshot.m_receivers = graph_impl.m_receivers; + graph_impl_snapshot.m_receivers_distance = graph_impl.m_receivers_distance; + graph_impl_snapshot.m_receivers_weight = graph_impl.m_receivers_weight; + graph_impl_snapshot.m_donors = graph_impl.m_donors; + } + } + + void _save(const data_array_type& elevation, data_array_type& elevation_snapshot) const + { + elevation_snapshot = elevation; + } + }; + } + + + // Now we can implement flow_operator_sequence::update_snapshots + template + void flow_operator_sequence::update_snapshots(const flow_snapshot& snapshot) + { + const auto& snapshot_name = snapshot.snapshot_name(); + + if (snapshot.save_graph()) + { + m_graph_snapshot_keys.push_back(snapshot_name); + + if (m_out_flowdir == flow_direction::undefined) + { + throw std::invalid_argument( + "no flow routing operator defined before graph snapshot."); + } + + bool single_flow = m_out_flowdir == flow_direction::single ? true : false; + m_graph_snapshot_single_flow.insert({ snapshot_name, single_flow }); + } + if (snapshot.save_elevation()) + { + m_elevation_snapshot_keys.push_back(snapshot_name); + } + } +} + +#endif // FASTSCAPELIB_FLOW_SNAPSHOT_H_ diff --git a/contrib/fastscape/include/fastscapelib/flow/sink_resolver.hpp b/contrib/fastscape/include/fastscapelib/flow/sink_resolver.hpp new file mode 100644 index 00000000000..1c847cdb773 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/flow/sink_resolver.hpp @@ -0,0 +1,359 @@ +/** + * @brief Class used to resolve local depressions of + * the topographic surface. + * + * ``sink_resolver`` is meant to be used in combination + * with ``flow_router`` through a ``flow_graph``. + * + */ +#ifndef FASTSCAPELIB_FLOW_SINK_RESOLVER_H +#define FASTSCAPELIB_FLOW_SINK_RESOLVER_H + +#include +#include +#include + +#include "fastscapelib/algo/pflood.hpp" +#include "fastscapelib/flow/flow_graph_impl.hpp" +#include "fastscapelib/flow/flow_operator.hpp" +#include "fastscapelib/flow/basin_graph.hpp" + + +namespace fastscapelib +{ + + /** + * Priority-flood sink resolver operator. + * + * This flow operator fills the closed depressions in the topographic + * surface using the priority flood algorithm +epsilon variant (Barnes et + * al., 2014). This variant prevents flat surfaces and hence ensure + * that the flow can be routed towards the outlets without disruption. + * + * \rst + * See :cite:t:`Barnes2014` for a more detailed description of the algorithm. + * + * \endrst + * + */ + struct pflood_sink_resolver : public flow_operator + { + inline std::string name() const noexcept override + { + return "pflood_sink_resolver"; + } + + static constexpr bool elevation_updated = true; + }; + + + namespace detail + { + + /** + * Priority-flood sink resolver operator implementation. + */ + template + class flow_operator_impl + : public flow_operator_impl_base + { + public: + using graph_impl_type = FG; + using base_type = flow_operator_impl_base; + + using data_array_type = typename graph_impl_type::data_array_type; + + flow_operator_impl(std::shared_ptr ptr) + : base_type(std::move(ptr)){}; + + void apply(graph_impl_type& graph_impl, data_array_type& elevation) + { + fill_sinks_sloped(graph_impl.grid(), elevation); + } + }; + } + + + /** + * Method used by ``mst_sink_resolver`` to route flow within each closed + * depressions. + * + * The ``basic`` method is the most efficient one but does not result in a + * "realistic", planar flow graph. + * + * The ``carve`` method mimics carving a narrow canyon within the + * depression. + */ + enum class mst_route_method + { + basic, /**< Connect the pit node directly to the depression spill node. */ + carve /**< Revert the (unique) flow path between the spill and pit nodes */ + }; + + inline std::ostream& operator<<(std::ostream& os, const mst_route_method meth) + { + switch (meth) + { + case mst_route_method::basic: + os << "basic"; + break; + case mst_route_method::carve: + os << "carve"; + break; + } + return os; + } + + /** + * Minimum Spanning Tree (MST) sink resolver operator. + * + * This flow operator re-routes the flow trapped in closed depressions + * towards their spill, using an efficient algorithm that explicitly + * computes a graph of (inner and outer) basins and reduces it as a tree + * (Cordonnier et al., 2019). + * + * It requires a single flow graph as input. + * + * This operator also use the updated routes in closed depressions to + * fill these with nearly flat surfaces (a tiny slope ensure natural + * flow routing for the operators applied after this one). + * + * \rst + * See :cite:t:`Cordonnier2019` for a more detailed description of the algorithm. + * + * \endrst + * + * @see fastscapelib::basin_graph + * + */ + class mst_sink_resolver : public flow_operator + { + public: + inline std::string name() const noexcept override + { + return "mst_sink_resolver"; + } + + static constexpr bool graph_updated = true; + static constexpr bool elevation_updated = true; + static constexpr flow_direction in_flowdir = flow_direction::single; + static constexpr flow_direction out_flowdir = flow_direction::single; + + mst_sink_resolver(mst_method basin_method = mst_method::kruskal, + mst_route_method route_method = mst_route_method::carve) + : m_basin_method(basin_method) + , m_route_method(route_method) + { + } + + mst_method m_basin_method = mst_method::kruskal; + mst_route_method m_route_method = mst_route_method::carve; + }; + + inline std::ostream& operator<<(std::ostream& os, const mst_sink_resolver resolver) + { + os << resolver.m_basin_method << "-" << resolver.m_route_method; + return os; + } + + + namespace detail + { + + /** + * Minimum Spanning Tree (MST) sink resolver operator implementation. + */ + template + class flow_operator_impl + : public flow_operator_impl_base + { + public: + using graph_impl_type = FG; + using base_type = flow_operator_impl_base; + + using size_type = typename graph_impl_type::size_type; + using data_type = typename graph_impl_type::data_type; + using data_array_type = typename graph_impl_type::data_array_type; + + using basin_graph_type = basin_graph; + + flow_operator_impl(std::shared_ptr ptr) + : base_type(std::move(ptr)){}; + + void apply(graph_impl_type& graph_impl, data_array_type& elevation) + { + // make sure the basins are up-to-date + graph_impl.compute_basins(); + + if (graph_impl.pits().empty()) + { + // return early, no sink to resolve + return; + } + + get_basin_graph(graph_impl).update_routes(elevation); + + if (this->m_op_ptr->m_route_method == mst_route_method::basic) + { + update_routes_sinks_basic(graph_impl, elevation); + } + else + { + // carve + update_routes_sinks_carve(graph_impl); + } + + // finalize flow route update (donors and dfs graph traversal indices) + graph_impl.compute_donors(); + graph_impl.compute_dfs_indices_bottomup(); + + // fill sinks with tiny tilted surface + fill_sinks_sloped(graph_impl, elevation); + }; + + private: + std::unique_ptr m_basin_graph_ptr; + + // basin graph edges are oriented in the counter flow direction + static constexpr std::uint8_t outflow = 0; + static constexpr std::uint8_t inflow = 1; + + /* + * Get the basin graph instance, create it if it doesn't exists. + */ + basin_graph_type& get_basin_graph(const graph_impl_type& graph_impl) + { + if (!m_basin_graph_ptr) + { + m_basin_graph_ptr = std::make_unique( + graph_impl, this->m_op_ptr->m_basin_method); + } + + return *m_basin_graph_ptr; + } + + void update_routes_sinks_basic(graph_impl_type& graph_impl, + const data_array_type& elevation); + void update_routes_sinks_carve(graph_impl_type& graph_impl); + + void fill_sinks_sloped(graph_impl_type& graph_impl, data_array_type& elevation); + }; + + template + void flow_operator_impl:: + update_routes_sinks_basic(graph_impl_type& graph_impl, const data_array_type& elevation) + { + const auto& basin_graph = get_basin_graph(graph_impl); + auto& receivers = graph_impl.m_receivers; + auto& dist2receivers = graph_impl.m_receivers_distance; + + // pits corresponds to outlets in inner basins + const auto& pits = basin_graph.outlets(); + + for (size_type edge_idx : basin_graph.tree()) + { + auto& edge = basin_graph.edges()[edge_idx]; + + // skip outer basins + if (edge.pass[outflow] == static_cast(-1)) + { + continue; + } + + size_type pit_inflow = pits[edge.link[inflow]]; + + // set infinite distance from the pit node to its receiver + // (i.e., one of the two pass nodes). + dist2receivers(pit_inflow, 0) = std::numeric_limits::max(); + + if (elevation.flat(edge.pass[inflow]) < elevation.flat(edge.pass[outflow])) + { + receivers(pit_inflow, 0) = edge.pass[outflow]; + } + else + { + // also need to resolve the flow between the two + // nodes forming the pass, i.e., route the flow like this: + // pit -> pass (inflow) -> pass (outflow) + receivers(pit_inflow, 0) = edge.pass[inflow]; + receivers(edge.pass[inflow], 0) = edge.pass[outflow]; + dist2receivers(edge.pass[inflow], 0) = edge.pass_length; + } + } + } + + template + void flow_operator_impl:: + update_routes_sinks_carve(graph_impl_type& graph_impl) + { + const auto& basin_graph = get_basin_graph(graph_impl); + auto& receivers = graph_impl.m_receivers; + auto& dist2receivers = graph_impl.m_receivers_distance; + + // pits corresponds to outlets in inner basins + const auto& pits = basin_graph.outlets(); + + for (size_type edge_idx : basin_graph.tree()) + { + auto& edge = basin_graph.edges()[edge_idx]; + + // skip outer basins + if (edge.pass[outflow] == static_cast(-1)) + { + continue; + } + + size_type pit_inflow = pits[edge.link[inflow]]; + + // start at the pass (inflow) and then below follow the + // receivers until the pit is found + size_type current_node = edge.pass[inflow]; + size_type next_node = receivers(current_node, 0); + data_type previous_dist = dist2receivers(current_node, 0); + + // re-route the pass inflow to the pass outflow + receivers(current_node, 0) = edge.pass[outflow]; + dist2receivers(current_node, 0) = edge.pass_length; + + while (current_node != pit_inflow) + { + auto rec_next_node = receivers(next_node, 0); + receivers(next_node, 0) = current_node; + std::swap(dist2receivers(next_node, 0), previous_dist); + current_node = next_node; + next_node = rec_next_node; + } + } + } + + template + void + flow_operator_impl::fill_sinks_sloped( + graph_impl_type& graph_impl, data_array_type& elevation) + { + const auto& dfs_indices = graph_impl.dfs_indices(); + const auto& receivers = graph_impl.receivers(); + + for (const auto& idfs : dfs_indices) + { + const auto& irec = receivers(idfs, 0); + + if (idfs == irec) + { + continue; + } + + const auto& irec_elev = elevation.flat(irec); + + if (elevation.flat(idfs) <= elevation.flat(irec)) + { + auto tiny_step + = std::nextafter(irec_elev, std::numeric_limits::infinity()); + elevation.flat(idfs) = tiny_step; + } + } + } + } +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/grid/base.hpp b/contrib/fastscape/include/fastscapelib/grid/base.hpp new file mode 100644 index 00000000000..664207282fb --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/grid/base.hpp @@ -0,0 +1,722 @@ +/** + * Common grid element types (node status, neighbors). + * grid base (abstract) class. + */ +#ifndef FASTSCAPELIB_GRID_BASE_H +#define FASTSCAPELIB_GRID_BASE_H + +#include +#include +#include +#include +#include + +#include "xtensor/xadapt.hpp" +#include "xtensor/xtensor.hpp" +#include "xtensor/xview.hpp" + +#include "fastscapelib/utils/iterators.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + //***************** + //* Grid boundaries + //***************** + + /** + * Node status values. + * + * Node status is a label assigned to each grid/mesh node. It is used to + * define the structure and boundaries of the modeled domain. + * + * The description of each value is given for indicative pruposes only. + * Exact semantics may differ depending on the grid type or the objects that + * are consuming grids. + * + * For example, ``looped`` is used only for structured grids. + * + */ + enum class node_status : std::uint8_t + { + core = 0, /**< Inner grid node */ + fixed_value = 1, /**< Dirichlet boundary condition */ + fixed_gradient = 2, /**< Neumann boundary condition */ + looped = 3 /**< Reflective boundaries */ + }; + + namespace detail + { + + inline bool node_status_cmp(node_status a, node_status b) + { + static std::map priority{ { node_status::core, 0 }, + { node_status::looped, 1 }, + { node_status::fixed_gradient, 2 }, + { node_status::fixed_value, 3 } }; + + return priority[a] < priority[b]; + } + } // namespace detail + + + //*************** + //* Grid elements + //*************** + + /** + * Represents a grid/mesh node. + */ + struct node + { + std::size_t idx; /**< Node index */ + node_status status; /**< Node status */ + }; + + /** + * Represents a grid/mesh node neighbor. + */ + struct neighbor + { + std::size_t idx; /**< Index of the neighbor node */ + double distance; /**< Distance to the neighbor node */ + node_status status; /**< Status at the neighbor node */ + + bool operator==(const neighbor& rhs) const + { + return (idx == rhs.idx) && (distance == rhs.distance) && (status == rhs.status); + } + }; + + namespace detail + { + /** + * Add a given offset (positive, negative or zero) to a grid node index. + * + * This utility function is mainly to avoid signed/unsigned conversion + * warnings. Use it carefully. + */ + inline std::size_t add_offset(std::size_t idx, std::ptrdiff_t offset) + { + return static_cast(static_cast(idx) + offset); + } + } + + /********************************** + * Grid neighbors indices caching * + **********************************/ + + /** + * Provides a cache for grid neighbor indices look-up. + * + * @tparam N The size of each array entry in the cache (should correspond to + * the fixed maximum number of neighbors). + */ + template + class neighbors_cache + { + public: + static constexpr unsigned int cache_width = N; + + template + using storage_type = std::array; + + using neighbors_indices_type = storage_type; + + neighbors_cache(std::size_t size) + : m_cache(cache_shape_type({ size })) + { + for (std::size_t i = 0; i < size; ++i) + { + m_cache[i].fill(std::numeric_limits::max()); + } + } + + bool has(const std::size_t& idx) const + { + return m_cache[idx][0] == std::numeric_limits::max() ? false : true; + } + + neighbors_indices_type& get(const std::size_t& idx) + { + return m_cache[idx]; + } + + neighbors_indices_type& get_storage(const std::size_t& idx) + { + return m_cache[idx]; + } + + void store(const std::size_t& idx, const neighbors_indices_type& neighbors_indices) + { + m_cache[idx] = neighbors_indices; + } + + std::size_t cache_size() const + { + return m_cache.size(); + }; + + std::size_t cache_used() const + { + std::size_t count = 0; + + for (std::size_t i = 0; i < m_cache.size(); ++i) + { + if (m_cache[i][0] != std::numeric_limits::max()) + { + count += 1; + } + } + + return count; + }; + + void reset() + { + for (std::size_t i = 0; i < m_cache.size(); ++i) + { + m_cache[i].fill(std::numeric_limits::max()); + } + } + + void remove(const std::size_t& idx) + { + m_cache[idx].fill(std::numeric_limits::max()); + } + + protected: + using cache_type = xt::xtensor; + using cache_shape_type = typename cache_type::shape_type; + + cache_type m_cache; + }; + + + /** + * A simple pass-through for grid neighbor indices look-up. + * + * @tparam N The size of temporary storage of indices (should correspond to + * the fixed maximum number of neighbors). If set to zero, the cache will + * use a variable size container (``std::vector``) storage type. + */ + template + class neighbors_no_cache + { + public: + static constexpr unsigned int cache_width = N; + + template + using storage_type = std::conditional_t, std::array>; + + using neighbors_indices_type = storage_type; + + neighbors_no_cache(std::size_t /*size*/) + { + } + + bool has(const std::size_t& /*idx*/) const + { + return false; + } + + neighbors_indices_type& get(const std::size_t& /*idx*/) + { + return m_node_neighbors; + } + + neighbors_indices_type& get_storage(const std::size_t& /*idx*/) + { + return m_node_neighbors; + } + + void store(const std::size_t& /*idx*/, const neighbors_indices_type neighbors_indices) + { + m_node_neighbors = neighbors_indices; + } + + std::size_t cache_size() const + { + return 0; + } + + std::size_t cache_used() const + { + return 0; + } + + void reset() + { + } + + void remove(const std::size_t& /*idx*/) + { + } + + protected: + neighbors_indices_type m_node_neighbors; + }; + + //**************** + //* Grid interface + //**************** + + // clang-format off + /** + * Small template class that holds a few types (static members) specialized + * for each grid type. + * + * It is used for accessing those types from within the grid base classes. + * + * \rst + * .. seealso:: + * :cpp:class:`~template\ fastscapelib::grid_inner_types\>`, + * :cpp:class:`~template\ fastscapelib::grid_inner_types\>`, + * :cpp:class:`~template\ fastscapelib::grid_inner_types\>` + * \endrst + */ + // clang-format on + template + struct grid_inner_types + { + }; + + /** + * Base class for all grid or mesh types. + * + * This class defines grid properties as well as a common API for iterating + * through grid nodes and their neighbors. + * + * @tparam G The derived grid type. + */ + template + class grid + { + public: + using derived_grid_type = G; + using inner_types = grid_inner_types; + + static constexpr bool is_structured(); + static constexpr bool is_uniform(); + static constexpr std::size_t xt_ndims(); + static constexpr std::uint8_t n_neighbors_max(); + + using grid_data_type = typename inner_types::grid_data_type; + using xt_selector = typename inner_types::xt_selector; + using xt_type = xt_tensor_t; + + using size_type = typename xt_type::size_type; + using shape_type = typename xt_type::shape_type; + + using neighbors_cache_type = typename inner_types::neighbors_cache_type; + + static_assert(neighbors_cache_type::cache_width == 0 + || neighbors_cache_type::cache_width >= n_neighbors_max(), + "Cache width is too small!"); + + using neighbors_type = std::vector; + + // using xt:xtensor for indices as not all containers support resizing + // (e.g., using pyarray may cause segmentation faults with Python) + using neighbors_indices_type = xt::xtensor; + using neighbors_distances_type = xt::xtensor; + + using node_status_type = xt_tensor_t; + + size_type size() const noexcept; + shape_type shape() const noexcept; + + const node_status_type& nodes_status() const; + node_status nodes_status(const size_type& idx) const; + + inline grid_nodes_indices nodes_indices() const; + inline grid_nodes_indices nodes_indices(node_status status) const; + + xt_type nodes_areas() const; + grid_data_type nodes_areas(const size_type& idx) const noexcept; + + size_type neighbors_count(const size_type& idx) const; + neighbors_distances_type neighbors_distances(const size_type& idx) const; + + // no const since it may update the cache internally + neighbors_indices_type neighbors_indices(const size_type& idx); + neighbors_indices_type& neighbors_indices(const size_type& idx, + neighbors_indices_type& neighbors_indices); + + neighbors_type neighbors(const size_type& idx); + neighbors_type& neighbors(const size_type& idx, neighbors_type& neighbors); + + const neighbors_cache_type& neighbors_indices_cache(); + + protected: + using neighbors_indices_impl_type = + typename neighbors_cache_type::template storage_type; + using neighbors_distances_impl_type = + typename neighbors_cache_type::template storage_type; + + grid(std::size_t size) + : m_neighbors_indices_cache(neighbors_cache_type(size)){}; + ~grid() = default; + + const derived_grid_type& derived_grid() const noexcept; + derived_grid_type& derived_grid() noexcept; + + inline xt_type nodes_areas_impl() const; + inline grid_data_type nodes_areas_impl(const size_type& idx) const noexcept; + + inline size_type neighbors_count_impl(const size_type& idx) const; + + void neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const; + + inline const neighbors_indices_impl_type& get_nb_indices_from_cache(const size_type& idx); + + inline const neighbors_distances_impl_type& neighbors_distances_impl( + const size_type& idx) const; + + neighbors_cache_type m_neighbors_indices_cache; + }; + + /** + * @name Grid static properties + */ + //@{ + /** + * True if the grid is strutured or False if the grid is an unstructured mesh. + */ + template + constexpr bool grid::is_structured() + { + return inner_types::is_structured; + } + + /** + * True if the grid is uniform (i.e., constant spacing along each + * dimension). + */ + template + constexpr bool grid::is_uniform() + { + return inner_types::is_uniform; + } + + /** + * Number of dimensions of the grid field arrays. + */ + template + constexpr std::size_t grid::xt_ndims() + { + return inner_types::xt_ndims; + } + + /** + * Maximum number of grid node neighbors. + * + * For strutured grids this corresponds to the actual (fixed) number of + * neighbors. + */ + template + constexpr std::uint8_t grid::n_neighbors_max() + { + return inner_types::n_neighbors_max; + } + //@} + + template + inline auto grid::derived_grid() const noexcept -> const derived_grid_type& + { + return *static_cast(this); + } + + template + inline auto grid::derived_grid() noexcept -> derived_grid_type& + { + return *static_cast(this); + } + + /** + * @name Grid properties + */ + //@{ + /** + * Returns the total number of grid nodes. + */ + template + inline auto grid::size() const noexcept -> size_type + { + return derived_grid().m_size; + } + + /** + * Returns the shape of the grid node arrays. + */ + template + inline auto grid::shape() const noexcept -> shape_type + { + return derived_grid().m_shape; + } + + /** + * Returns the cache used to store node neighbor indices. + */ + template + auto grid::neighbors_indices_cache() -> const neighbors_cache_type& + { + return m_neighbors_indices_cache; + }; + //@} + + /** + * @name Node methods + */ + /** + * Returns a virtual container that may be used to iterate over all grid + * nodes. + */ + template + inline grid_nodes_indices grid::nodes_indices() const + { + const auto& derived = derived_grid(); + return grid_nodes_indices(derived); + }; + + /** + * Returns a virtual container that may be used to iterate over all grid + * nodes of a given status. + * + * @param status The node status. + */ + template + inline grid_nodes_indices grid::nodes_indices(node_status status) const + { + const auto& derived = derived_grid(); + return grid_nodes_indices(derived, + [=](const grid& grid, size_type idx) + { return grid.nodes_status().flat(idx) == status; }); + }; + + /** + * Returns a constant reference to the array of status at grid nodes. + */ + template + inline auto grid::nodes_status() const -> const node_status_type& + { + return derived_grid().m_nodes_status; + } + + /** + * Returns the status at a given grid node. + * + * @param idx The grid node flat index. + */ + template + inline auto grid::nodes_status(const size_type& idx) const -> node_status + { + return derived_grid().m_nodes_status.flat(idx); + } + + /** + * Returns the areas of the direct vicinity of each grid node as an array. + * + * Note: this creates a new container or returns a copy. + */ + template + inline auto grid::nodes_areas() const -> xt_type + { + return std::move(nodes_areas_impl()); + } + + /** + * Returns the area of the direct vicinity of a grid node. + * + * @param idx The grid node flat index. + */ + template + inline auto grid::nodes_areas(const size_type& idx) const noexcept -> grid_data_type + { + return nodes_areas_impl(idx); + } + //@} + + /** + * @name Neighbor methods + */ + /** + * Returns the number of neighbors of a given grid node. + * + * @param idx The grid node flat index. + */ + template + inline auto grid::neighbors_count(const size_type& idx) const -> size_type + { + return neighbors_count_impl(idx); + } + + /** + * Returns an array of the indices of the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * @param idx The grid node flat index. + */ + template + inline auto grid::neighbors_indices(const size_type& idx) -> neighbors_indices_type + { + neighbors_indices_type indices = xt::adapt(get_nb_indices_from_cache(idx)); + auto view = xt::view(indices, xt::range(0, neighbors_count(idx))); + + return view; + } + + /** + * Resize and fills an array with the neighbors indices of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * This method prevents allocating a new container for better performance. + * + * @param idx The grid node flat index. + * @param neighbors_indices Reference to the container to be updated with the neighbors indices. + */ + template + inline auto grid::neighbors_indices(const size_type& idx, + neighbors_indices_type& neighbors_indices) + -> neighbors_indices_type& + { + const auto& n_count = neighbors_count(idx); + const auto& n_indices = get_nb_indices_from_cache(idx); + + if (neighbors_indices.size() != n_count) + { + neighbors_indices.resize({ n_count }); + } + + for (size_type i = 0; i < n_count; ++i) + { + neighbors_indices[i] = n_indices[i]; + } + + return neighbors_indices; + } + + /** + * Returns an array of the distances to the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * @param idx The grid node flat index. + */ + template + inline auto grid::neighbors_distances(const size_type& idx) const -> neighbors_distances_type + { + neighbors_distances_type distances = xt::adapt(neighbors_distances_impl(idx)); + auto view = xt::view(distances, xt::range(0, neighbors_count(idx))); + + return view; + } + + /** + * Returns a vector of the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * @param idx The grid node flat index. + * @return A vector of neighbor node objects. + */ + template + inline auto grid::neighbors(const size_type& idx) -> neighbors_type + { + neighbors_type nb; + neighbors(idx, nb); + + return nb; + } + + /** + * Resize and fills a vactor with the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * This method prevents allocating a new container for better performance. + * + * @param idx The grid node flat index. + * @param neighbors Reference to the vector to be updated with the neighbor objects. + */ + template + inline auto grid::neighbors(const size_type& idx, neighbors_type& neighbors) + -> neighbors_type& + { + size_type n_idx; + const auto& n_count = neighbors_count(idx); + const auto& n_indices = get_nb_indices_from_cache(idx); + const auto& n_distances = neighbors_distances_impl(idx); + + if (neighbors.size() != n_count) + { + neighbors.resize({ n_count }); + } + + for (size_type i = 0; i < n_count; ++i) + { + n_idx = n_indices[i]; + neighbors[i] = neighbor({ n_idx, n_distances[i], nodes_status()[n_idx] }); + } + + return neighbors; + } + //@} + + template + inline auto grid::nodes_areas_impl() const -> xt_type + { + return derived_grid().nodes_areas_impl(); + } + + template + inline auto grid::nodes_areas_impl(const size_type& idx) const noexcept -> grid_data_type + { + return derived_grid().nodes_areas_impl(idx); + } + + template + inline auto grid::neighbors_count_impl(const size_type& idx) const -> size_type + { + return derived_grid().neighbors_count_impl(idx); + } + + template + inline auto grid::get_nb_indices_from_cache(const size_type& idx) + -> const neighbors_indices_impl_type& + { + if (m_neighbors_indices_cache.has(idx)) + { + neighbors_indices_impl_type& n_indices = m_neighbors_indices_cache.get(idx); + return n_indices; + } + else + { + neighbors_indices_impl_type& n_indices = m_neighbors_indices_cache.get_storage(idx); + this->derived_grid().neighbors_indices_impl(n_indices, idx); + return n_indices; + } + } + + template + inline auto grid::neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const -> void + { + return derived_grid().neighbors_indices_impl(neighbors, idx); + } + + template + inline auto grid::neighbors_distances_impl(const size_type& idx) const + -> const neighbors_distances_impl_type& + { + return derived_grid().neighbors_distances_impl(idx); + } + +} + +#endif // FASTSCAPELIB_GRID_BASE_H diff --git a/contrib/fastscape/include/fastscapelib/grid/profile_grid.hpp b/contrib/fastscape/include/fastscapelib/grid/profile_grid.hpp new file mode 100644 index 00000000000..350617fe832 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/grid/profile_grid.hpp @@ -0,0 +1,411 @@ +/** + * A profile grid is a one dimensional grid. + */ +#ifndef FASTSCAPELIB_GRID_PROFILE_GRID_H +#define FASTSCAPELIB_GRID_PROFILE_GRID_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/grid/structured_grid.hpp" + + +namespace fastscapelib +{ + + /** + * Status at grid boundary nodes. + */ + class profile_boundary_status : public boundary_status + { + public: + node_status left = node_status::core; /**< Status at left edge/border node(s) */ + node_status right = node_status::core; /**< Status at right edge/border node(s) */ + + profile_boundary_status(const node_status status); + profile_boundary_status(const node_status left_status, const node_status right_status); + profile_boundary_status(const std::array& status); + + bool is_horizontal_looped() const; + + protected: + void check_looped_symmetrical(); + }; + + /** + * @name Constructors + */ + //@{ + /** + * Set the same status for all boundary nodes. + */ + inline profile_boundary_status::profile_boundary_status(node_status status) + : left(status) + , right(status) + { + check_looped_symmetrical(); + } + + /** + * Set status at the left and right edge nodes of a profile grid. + */ + inline profile_boundary_status::profile_boundary_status(node_status left_status, + node_status right_status) + : left(left_status) + , right(right_status) + { + check_looped_symmetrical(); + } + + /** + * Set status at the left and right edge nodes of a profile grid. + */ + inline profile_boundary_status::profile_boundary_status( + const std::array& status) + : left(status[0]) + , right(status[1]) + { + check_looped_symmetrical(); + } + //@} + + /** + * Return true if periodic (looped) conditions are set for ``left`` and ``right``. + */ + inline bool profile_boundary_status::is_horizontal_looped() const + { + return is_looped(left) && is_looped(right); + } + + inline void profile_boundary_status::check_looped_symmetrical() + { + if (is_looped(left) ^ is_looped(right)) + { + throw std::invalid_argument("looped boundaries are not symmetrical"); + } + } + + //******************* + //* Profile grid (1D) + //******************* + + template + class profile_grid_xt; + + /** + * Profile grid specialized types. + */ + template + struct grid_inner_types> + { + static constexpr bool is_structured = true; + static constexpr bool is_uniform = true; + + using grid_data_type = double; + + using xt_selector = S; + static constexpr std::size_t xt_ndims = 1; + + static constexpr uint8_t n_neighbors_max = 2u; + using neighbors_cache_type = C; + + // structured_grid types + using length_type = double; + using spacing_type = double; + }; + + /** + * 1-dimensional uniform grid. + * + * Useful for modeling single channel or hillslope profiles. + * + * @tparam S The xtensor container selector for data array members. + * @tparam C The grid neighbor nodes cache type. + */ + template > + class profile_grid_xt : public structured_grid> + { + public: + using self_type = profile_grid_xt; + using base_type = structured_grid; + using inner_types = grid_inner_types; + + using grid_data_type = typename base_type::grid_data_type; + + using xt_selector = typename base_type::xt_selector; + using xt_type = xt_tensor_t; + + using size_type = typename base_type::size_type; + using shape_type = typename base_type::shape_type; + + using length_type = typename base_type::length_type; + using spacing_type = typename base_type::spacing_type; + + using code_type = std::uint8_t; + + using neighbors_type = typename base_type::neighbors_type; + using neighbors_indices_type = typename base_type::neighbors_indices_type; + using neighbors_distances_type = typename base_type::neighbors_distances_type; + + using node_status_type = typename base_type::node_status_type; + + profile_grid_xt(size_type size, + spacing_type spacing, + const profile_boundary_status& bounds_status, + const std::vector& nodes_status = {}); + + static profile_grid_xt from_length(size_type size, + length_type length, + const profile_boundary_status& bounds_status, + const std::vector& nodes_status = {}); + + protected: + using neighbors_distances_impl_type = typename base_type::neighbors_distances_impl_type; + using neighbors_indices_impl_type = typename base_type::neighbors_indices_impl_type; + using coded_ndistances_type = std::array; + + shape_type m_shape; + size_type m_size; + spacing_type m_spacing; + length_type m_length; + grid_data_type m_node_area; + + xt::xtensor m_gcode_idx; + + node_status_type m_nodes_status; + profile_boundary_status m_bounds_status; + + void set_nodes_status(const std::vector& nodes_status); + + static constexpr std::array offsets{ { 0, -1, 1 } }; + std::vector m_all_neighbors; + + coded_ndistances_type m_neighbors_distances; + void build_neighbors_distances(); + + void build_gcode(); + code_type gcode(const size_type& idx) const; + + std::array m_neighbors_count; + void build_neighbors_count(); + + inline xt_type nodes_areas_impl() const; + inline grid_data_type nodes_areas_impl(const size_type& idx) const noexcept; + + inline size_type neighbors_count_impl(const size_type& idx) const noexcept; + + void neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const; + + const neighbors_distances_impl_type& neighbors_distances_impl(const size_type& idx) const; + + friend class structured_grid; + friend class grid; + }; + + template + constexpr std::array profile_grid_xt::offsets; + + /** + * @name Constructors + */ + //@{ + /** + * Creates a new profile grid. + * + * @param size Total number of grid nodes. + * @param spacing Distance between two adjacent grid nodes. + * @param bounds_status Status at boundary nodes (left/right grid edges). + * @param nodes_status Manually define the status at any node on the grid. + */ + template + profile_grid_xt::profile_grid_xt(size_type size, + spacing_type spacing, + const profile_boundary_status& bounds_status, + const std::vector& nodes_status) + : base_type(size) + , m_size(size) + , m_spacing(spacing) + , m_bounds_status(bounds_status) + { + m_shape = { static_cast(m_size) }; + m_length = static_cast(size - 1) * spacing; + m_node_area = spacing; + set_nodes_status(nodes_status); + build_gcode(); + build_neighbors_distances(); + build_neighbors_count(); + } + //@} + + /** + * @name Factories + */ + //@{ + /** + * Creates a new profile grid. + * + * @param size Total number of grid nodes. + * @param length Total physical length of the grid. + * @param bounds_status Status at boundary nodes (left & right grid edges). + * @param nodes_status Manually define the status at any node on the grid. + */ + template + profile_grid_xt profile_grid_xt::from_length( + size_type size, + length_type length, + const profile_boundary_status& bounds_status, + const std::vector& nodes_status) + { + spacing_type spacing = length / static_cast(size - 1); + return profile_grid_xt(size, spacing, bounds_status, nodes_status); + } + //@} + + template + void profile_grid_xt::build_gcode() + { + m_gcode_idx.resize({ m_size }); + + m_gcode_idx.fill(1); + m_gcode_idx[0] = 0; + m_gcode_idx[m_size - 1] = 2; + } + + template + auto profile_grid_xt::gcode(const size_type& idx) const -> code_type + { + return m_gcode_idx[idx]; + } + + template + void profile_grid_xt::build_neighbors_distances() + { + m_neighbors_distances.fill({ m_spacing, m_spacing }); + } + + template + void profile_grid_xt::build_neighbors_count() + { + if (m_bounds_status.is_horizontal_looped()) + { + m_neighbors_count = std::array({ 2, 2, 2 }); + } + else + { + m_neighbors_count = std::array({ 1, 2, 1 }); + } + } + + template + void profile_grid_xt::set_nodes_status(const std::vector& nodes_status) + { + node_status_type temp_nodes_status(m_shape, node_status::core); + + temp_nodes_status(0) = m_bounds_status.left; + temp_nodes_status(m_size - 1) = m_bounds_status.right; + + for (const node& n : nodes_status) + { + if (n.status == node_status::looped) + { + throw std::invalid_argument("node_status::looped is not allowed in " + "'nodes_status' " + "(use 'bounds_status' instead)"); + } + else if (temp_nodes_status.at(n.idx) == node_status::looped) + { + throw std::invalid_argument("cannot overwrite the status of a " + "looped boundary node"); + } + + temp_nodes_status.at(n.idx) = n.status; + } + + m_nodes_status = temp_nodes_status; + } + + template + inline auto profile_grid_xt::nodes_areas_impl() const -> xt_type + { + return xt::broadcast(m_node_area, m_shape); + } + + template + inline auto profile_grid_xt::nodes_areas_impl(const size_type& /*idx*/) const noexcept + -> grid_data_type + { + return m_node_area; + } + + template + inline auto profile_grid_xt::neighbors_count_impl(const size_type& idx) const noexcept + -> size_type + { + return m_neighbors_count[gcode(idx)]; + } + + template + auto profile_grid_xt::neighbors_distances_impl(const size_type& /*idx*/) const + -> const neighbors_distances_impl_type& + { + return m_neighbors_distances[0]; + } + + template + inline auto profile_grid_xt::neighbors_indices_impl( + neighbors_indices_impl_type& neighbors, const size_type& idx) const -> void + { + if (idx == 0) + { + if (m_bounds_status.is_horizontal_looped()) + { + neighbors[0] = m_size - 1; + neighbors[1] = 1; + } + else + { + neighbors[0] = 1; + } + } + else if (idx == m_size - 1) + { + neighbors[0] = m_size - 2; + if (m_bounds_status.is_horizontal_looped()) + { + neighbors[1] = 0; + } + } + else + { + for (std::size_t k = 1; k < 3; ++k) + { + std::size_t nb_idx = detail::add_offset(idx, offsets[k]); + neighbors[k - 1] = nb_idx; + } + } + } + + /** + * @typedef profile_grid + * + * \rst + * Alias template on ``profile_grid_xt`` with :cpp:type:`xt::xtensor` + * used as array container type for data members. + * + * This is mainly for convenience when using in C++ applications. + * \endrst + */ + using profile_grid = profile_grid_xt; +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/grid/raster_grid.hpp b/contrib/fastscape/include/fastscapelib/grid/raster_grid.hpp new file mode 100644 index 00000000000..4cafa4cd5ed --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/grid/raster_grid.hpp @@ -0,0 +1,1024 @@ +/** + * A raster grid represents a 2D uniform grid. + */ +#ifndef FASTSCAPELIB_GRID_RASTER_GRID_H +#define FASTSCAPELIB_GRID_RASTER_GRID_H + +#include +#include + +#include +#include + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/grid/structured_grid.hpp" +#include "fastscapelib/grid/profile_grid.hpp" + + +namespace fastscapelib +{ + /***************** + * Grid elements * + *****************/ + + /** + * Represents a raster grid node. + */ + struct raster_node + { + std::size_t row; /**< Row index */ + std::size_t col; /**< Column index */ + node_status status; /**< Node status */ + }; + + /** + * Represents a raster grid node neighbor. + */ + struct raster_neighbor + { + std::size_t flatten_idx; /**< Flat index of the neighbor node */ + std::size_t row; /**< Row index of the neighbor node */ + std::size_t col; /**< Column index of the neighbor node */ + double distance; /**< Distance to the neighbor node */ + node_status status; /**< Status at the neighbor node */ + + bool operator==(const raster_neighbor& rhs) const + { + return (flatten_idx == rhs.flatten_idx) && (row == rhs.row) && (col == rhs.col) + && (distance == rhs.distance) && (status == rhs.status); + } + }; + + /** + * Status at grid boundary nodes. + * + * To disambiguate the cases at each of the four raster grid corners, node + * status is set from one of their two overlaping borders according to the + * following precedance order: + * + * fixed value > fixed gradient > looped > core + */ + class raster_boundary_status : public boundary_status + { + public: + node_status left = node_status::core; /**< Status at left border nodes */ + node_status right = node_status::core; /**< Status at right border nodes */ + node_status top = node_status::core; /**< Status at top border nodes */ + node_status bottom = node_status::core; /**< Status at bottom border nodes */ + + raster_boundary_status(node_status status); + raster_boundary_status(const std::array& status); + + bool is_vertical_looped() const; + bool is_horizontal_looped() const; + + protected: + void check_looped_symmetrical() const; + }; + + /** + * @name Constructors + */ + //@{ + /** + * Set the same status for all boundary nodes. + */ + inline raster_boundary_status::raster_boundary_status(node_status status) + : left(status) + , right(status) + , top(status) + , bottom(status) + { + check_looped_symmetrical(); + } + + /** + * Set status at the left, right, top and bottom border nodes of a raster grid. + */ + inline raster_boundary_status::raster_boundary_status(const std::array& status) + : left(status[0]) + , right(status[1]) + , top(status[2]) + , bottom(status[3]) + { + check_looped_symmetrical(); + } + //@} + + /** + * Return true if periodic (looped) conditions are set for ``left`` and ``right``. + */ + inline bool raster_boundary_status::is_horizontal_looped() const + { + return is_looped(left) && is_looped(right); + } + + /** + * Return true if periodic (looped) conditions are set for ``top`` and ``bottom``. + */ + inline bool raster_boundary_status::is_vertical_looped() const + { + return is_looped(top) && is_looped(bottom); + } + + /** + * Throw an error if the boundary status is not symmetrical. + */ + inline void raster_boundary_status::check_looped_symmetrical() const + { + if (is_looped(left) ^ is_looped(right) || is_looped(top) ^ is_looped(bottom)) + { + throw std::invalid_argument("looped boundaries are not symmetrical"); + } + } + + /******************* + * Raster grid (2D) * + ********************/ + + /** + * Raster grid node connectivity. + */ + enum class raster_connect + { + rook = 0, /**< 4-connectivity (vertical or horizontal) */ + queen = 1, /**< 8-connectivity (including diagonals) */ + bishop = 2 /**< 4-connectivity (only diagonals) */ + }; + + /************************* + * raster_neighbors_base * + *************************/ + + struct raster_neighbors_base + { + using neighbors_offsets_type + = xt::xtensor>, 1>; + using size_type = std::size_t; + }; + + /******************** + * raster_neighbors * + ********************/ + + template + struct raster_neighbors + { + }; + + + template <> + struct raster_neighbors : public raster_neighbors_base + { + static constexpr std::uint8_t _n_neighbors_max = 8u; + + neighbors_offsets_type node_neighbors_offsets(std::ptrdiff_t up, + std::ptrdiff_t down, + std::ptrdiff_t left, + std::ptrdiff_t right) const; + + std::array build_neighbors_count( + const raster_boundary_status& bounds_status) const; + }; + + /** + * Given the possible moves (up/down/left/right) depending on grid periodicity, + * return a list of offsets corresponding to "queen" neighbors relative locations. + * Offsets are returned in row-major layout when accessible (else dropped). + * + * Examples: + * + * All w/o left w/o top + * 0 1 2 0 1 + * \ | / | / + * 3 - N - 4 N - 2 0 - N - 1 + * / | \ | \ / | \ + * 5 6 7 3 4 2 3 4 + */ + inline auto raster_neighbors::node_neighbors_offsets( + std::ptrdiff_t up, std::ptrdiff_t down, std::ptrdiff_t left, std::ptrdiff_t right) const + -> neighbors_offsets_type + { + xt::xtensor mask{ + (up != 0 && left != 0), up != 0, (up != 0 && right != 0), left != 0, right != 0, + (down != 0 && left != 0), down != 0, (down != 0 && right != 0) + }; + + neighbors_offsets_type full_offsets{ { up, left }, { up, 0 }, { up, right }, + { 0, left }, { 0, right }, { down, left }, + { down, 0 }, { down, right } }; + + return xt::filter(full_offsets, mask); + } + + /** + * Build the neighbors count of each node code (e.g. position on the grid) + * depending on the periodicity of the grid. + */ + inline auto raster_neighbors::build_neighbors_count( + const raster_boundary_status& bounds_status) const -> std::array + { + std::array neighbors_count; + + if (bounds_status.is_vertical_looped() && bounds_status.is_horizontal_looped()) + { + neighbors_count.fill(8); + } + else if (bounds_status.is_vertical_looped()) + { + neighbors_count = std::array({ 5, 8, 5, 5, 8, 5, 5, 8, 5 }); + } + else if (bounds_status.is_horizontal_looped()) + { + neighbors_count = std::array({ 5, 5, 5, 8, 8, 8, 5, 5, 5 }); + } + else + { + neighbors_count = std::array({ 3, 5, 3, 5, 8, 5, 3, 5, 3 }); + } + + return neighbors_count; + } + + + template <> + struct raster_neighbors : public raster_neighbors_base + { + static constexpr std::uint8_t _n_neighbors_max = 4u; + + neighbors_offsets_type node_neighbors_offsets(std::ptrdiff_t up, + std::ptrdiff_t down, + std::ptrdiff_t left, + std::ptrdiff_t right) const; + + std::array build_neighbors_count( + const raster_boundary_status& bounds_status) const; + }; + + /** + * Given the possible moves (up/down/left/right) depending on grid periodicity, + * return a list of offsets corresponding to "rook" neighbors relative locations. + * Offsets are returned in row-major layout when accessible (else dropped). + * + * Examples: + * + * All w/o left w/o top etc. + * 0 0 + * | | + * 1 -- N -- 2 N -- 1 0 -- N -- 1 + * | | | + * 3 2 2 + */ + inline auto raster_neighbors::node_neighbors_offsets( + std::ptrdiff_t up, std::ptrdiff_t down, std::ptrdiff_t left, std::ptrdiff_t right) const + -> neighbors_offsets_type + { + xt::xtensor mask{ up != 0, left != 0, right != 0, down != 0 }; + + neighbors_offsets_type full_offsets{ { up, 0 }, { 0, left }, { 0, right }, { down, 0 } }; + + return xt::filter(full_offsets, mask); + } + + /** + * Build the neighbors count of each node code (e.g. position on the grid) + * depending on the periodicity of the grid. + */ + inline auto raster_neighbors::build_neighbors_count( + const raster_boundary_status& bounds_status) const -> std::array + { + std::array neighbors_count; + + if (bounds_status.is_vertical_looped() && bounds_status.is_horizontal_looped()) + { + neighbors_count.fill(4); + } + else if (bounds_status.is_vertical_looped()) + { + neighbors_count = std::array({ 3, 4, 3, 3, 4, 3, 3, 4, 3 }); + } + else if (bounds_status.is_horizontal_looped()) + { + neighbors_count = std::array({ 3, 3, 3, 4, 4, 4, 3, 3, 3 }); + } + else + { + neighbors_count = std::array({ 2, 3, 2, 3, 4, 3, 2, 3, 2 }); + } + + return neighbors_count; + } + + + template <> + struct raster_neighbors : public raster_neighbors_base + { + static constexpr std::uint8_t _n_neighbors_max = 4u; + + neighbors_offsets_type node_neighbors_offsets(std::ptrdiff_t up, + std::ptrdiff_t down, + std::ptrdiff_t left, + std::ptrdiff_t right) const; + + std::array build_neighbors_count( + const raster_boundary_status& bounds_status) const; + }; + + /** + * Given the possible moves (up/down/left/right) depending on grid periodicity, + * return a list of offsets corresponding to "bishop" neighbors relative locations. + * Offsets are returned in row-major layout when accessible (else dropped). + * + * Examples: + * + * All w/o left w/o top + * 0 1 0 + * \ / / + * N N N + * / \ \ / \ + * 2 3 1 0 1 + */ + inline auto raster_neighbors::node_neighbors_offsets( + std::ptrdiff_t up, std::ptrdiff_t down, std::ptrdiff_t left, std::ptrdiff_t right) const + -> neighbors_offsets_type + { + xt::xtensor mask{ (up != 0 && left != 0), + (up != 0 && right != 0), + (down != 0 && left != 0), + (down != 0 && right != 0) }; + + neighbors_offsets_type full_offsets{ + { up, left }, { up, right }, { down, left }, { down, right } + }; + + return xt::filter(full_offsets, mask); + } + + /** + * Build the neighbors count of each node code (e.g. position on the grid) + * depending on the periodicity of the grid. + */ + inline auto raster_neighbors::build_neighbors_count( + const raster_boundary_status& bounds_status) const -> std::array + { + std::array neighbors_count; + + if (bounds_status.is_vertical_looped() && bounds_status.is_horizontal_looped()) + { + neighbors_count.fill(4); + } + else if (bounds_status.is_vertical_looped()) + { + neighbors_count = std::array({ 2, 4, 2, 2, 4, 2, 2, 4, 2 }); + } + else if (bounds_status.is_horizontal_looped()) + { + neighbors_count = std::array({ 2, 2, 2, 4, 4, 4, 2, 2, 2 }); + } + else + { + neighbors_count = std::array({ 1, 2, 1, 2, 4, 2, 1, 2, 1 }); + } + + return neighbors_count; + } + + /******************** + * grid_inner_types * + ********************/ + + template + class raster_grid_xt; + + /** + * Raster grid specialized types. + */ + template + struct grid_inner_types> + { + static constexpr bool is_structured = true; + static constexpr bool is_uniform = true; + + using grid_data_type = double; + + using xt_selector = S; + static constexpr std::size_t xt_ndims = 2; + + static constexpr uint8_t n_neighbors_max = raster_neighbors::_n_neighbors_max; + using neighbors_cache_type = C; + + // structured_grid types + using length_type = xt::xtensor_fixed>; + using spacing_type = xt::xtensor_fixed>; + }; + + /** + * 2-dimensional uniform (raster) grid. + * + * @tparam S The xtensor container selector for data array members. + * @tparam RC The kind of raster node connectivity. + * @tparam C The grid neighbor nodes cache type. + */ + template ::_n_neighbors_max>> + class raster_grid_xt + : public structured_grid> + , public raster_neighbors + { + public: + using self_type = raster_grid_xt; + using base_type = structured_grid; + using inner_types = grid_inner_types; + + using grid_data_type = typename base_type::grid_data_type; + + using xt_selector = typename base_type::xt_selector; + using xt_type = xt_tensor_t; + + using size_type = typename base_type::size_type; + using shape_type = typename base_type::shape_type; + + using length_type = typename base_type::length_type; + using spacing_type = typename base_type::spacing_type; + + using code_type = std::uint8_t; + + // row, col index pair + using raster_idx_type = std::pair; + + using neighbors_type = typename base_type::neighbors_type; + using neighbors_indices_type = typename base_type::neighbors_indices_type; + using neighbors_distances_type = typename base_type::neighbors_distances_type; + using neighbors_indices_raster_type = std::vector; + using neighbors_raster_type = std::vector; + + using node_status_type = typename base_type::node_status_type; + + raster_grid_xt(const shape_type& shape, + const spacing_type& spacing, + const raster_boundary_status& bounds_status, + const std::vector& nodes_status = {}); + + static raster_grid_xt from_length(const shape_type& shape, + const length_type& length, + const raster_boundary_status& bounds_status, + const std::vector& nodes_status = {}); + + shape_type shape() const noexcept; + raster_boundary_status bounds_status() const noexcept; + + using base_type::neighbors_indices; + + inline neighbors_indices_raster_type& neighbors_indices( + const size_type& row, + const size_type& col, + neighbors_indices_raster_type& neighbors_indices); + + inline neighbors_indices_raster_type neighbors_indices(const size_type& row, + const size_type& col); + + using base_type::neighbors_distances; + + using base_type::neighbors; + inline neighbors_raster_type& neighbors(const size_type& row, + const size_type& col, + neighbors_raster_type& neighbors); + inline neighbors_raster_type neighbors(const size_type& row, const size_type& col); + + code_type nodes_codes(const size_type& row, const size_type& col) const noexcept; + code_type nodes_codes(const size_type& idx) const noexcept; + + private: + using neighbors_distances_impl_type = typename base_type::neighbors_distances_impl_type; + using neighbors_indices_impl_type = typename base_type::neighbors_indices_impl_type; + using neighbors_offsets_type = typename raster_neighbors::neighbors_offsets_type; + + using coded_ncount_type = std::array; + using coded_noffsets_type = std::array; + using coded_ndistances_type = std::array; + using nodes_codes_type = xt::xtensor; + + shape_type m_shape; + size_type m_size; + spacing_type m_spacing; + length_type m_length; + grid_data_type m_node_area; + + node_status_type m_nodes_status; + raster_boundary_status m_bounds_status; + + struct corner_node + { + size_type row; + size_type col; + node_status row_border; + node_status col_border; + }; + + nodes_codes_type m_nodes_codes; + + coded_ncount_type m_neighbors_count; + coded_noffsets_type m_neighbor_offsets; + coded_ndistances_type m_neighbor_distances; + + inline size_type ravel_idx(const size_type& row, const size_type& col) const noexcept; + inline raster_idx_type unravel_idx(const size_type& idx) const noexcept; + + void set_nodes_status(const std::vector& nodes_status); + + void build_nodes_codes(); + coded_noffsets_type build_coded_neighbors_offsets(); + coded_ndistances_type build_coded_neighbors_distances(); + + inline const neighbors_offsets_type& neighbor_offsets(code_type code) const noexcept; + + inline xt_type nodes_areas_impl() const; + inline grid_data_type nodes_areas_impl(const size_type& idx) const noexcept; + + inline size_type neighbors_count_impl(const size_type& idx) const noexcept; + + inline const neighbors_distances_impl_type& neighbors_distances_impl( + const size_type& idx) const noexcept; + + void neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const; + + friend class structured_grid; + friend class grid; + }; + + /** + * @name Constructors + */ + //@{ + /** + * Creates a new raster grid. + * + * @param shape Shape of the grid (number of rows and cols). + * @param spacing Distance between two adjacent rows / cols. + * @param bounds_status Status at boundary nodes (left/right/top/bottom borders). + * @param nodes_status Manually define the status at any node on the grid. + */ + template + raster_grid_xt::raster_grid_xt(const shape_type& shape, + const spacing_type& spacing, + const raster_boundary_status& bounds_status, + const std::vector& nodes_status) + : base_type(shape[0] * shape[1]) + , m_shape(shape) + , m_spacing(spacing) + , m_bounds_status(bounds_status) + { + m_size = shape[0] * shape[1]; + m_length = (xt::adapt(shape) - 1) * spacing; + m_node_area = xt::prod(spacing)(); + + build_nodes_codes(); + m_neighbors_count = this->build_neighbors_count(bounds_status); + + set_nodes_status(nodes_status); + + m_neighbor_offsets = build_coded_neighbors_offsets(); + m_neighbor_distances = build_coded_neighbors_distances(); + } + //@} + + /** + * @name Factories + */ + //@{ + /** + * Creates a new raster grid. + * + * @param shape Shape of the grid (number of rows and cols). + * @param length Total physical lengths of the grid. + * @param bounds_status Status at boundary nodes (left/right/top/bottom borders). + * @param nodes_status Manually define the status at any node on the grid. + */ + template + raster_grid_xt raster_grid_xt::from_length( + const shape_type& shape, + const length_type& length, + const raster_boundary_status& bounds_status, + const std::vector& nodes_status) + { + spacing_type spacing = length / (xt::adapt(shape) - 1); + return raster_grid_xt(shape, spacing, bounds_status, nodes_status); + } + //@} + + /** + * @name Grid properties + */ + //@{ + /** + * Returns the shape of the grid node arrays. + */ + template + auto raster_grid_xt::shape() const noexcept -> shape_type + { + return m_shape; + } + + /** + * Returns the grid node status at grid left / right / top / bottom borders. + */ + template + auto raster_grid_xt::bounds_status() const noexcept -> raster_boundary_status + { + return m_bounds_status; + } + //@} + + template + inline auto raster_grid_xt::ravel_idx(const size_type& row, + const size_type& col) const noexcept + -> size_type + { + // TODO: assumes row-major layout -> support col-major? + return row * m_shape[1] + col; + } + + template + inline auto raster_grid_xt::unravel_idx(const size_type& idx) const noexcept + -> raster_idx_type + { + // TODO: assumes row-major layout -> support col-major? + auto ncols = m_shape[1]; + size_type row = idx / ncols; + size_type col = idx - row * ncols; + + return std::make_pair(row, col); + } + + template + void raster_grid_xt::set_nodes_status(const std::vector& nodes_status) + { + node_status_type temp_nodes_status(m_shape, node_status::core); + const auto nrows = static_cast(m_shape[0]); + const auto ncols = static_cast(m_shape[1]); + + // set border nodes + auto left = xt::view(temp_nodes_status, xt::all(), 0); + left = m_bounds_status.left; + + auto right = xt::view(temp_nodes_status, xt::all(), xt::keep(-1)); + right = m_bounds_status.right; + + auto top = xt::view(temp_nodes_status, 0, xt::all()); + top = m_bounds_status.top; + + auto bottom = xt::view(temp_nodes_status, xt::keep(-1), xt::all()); + bottom = m_bounds_status.bottom; + + // set corner nodes + std::vector corners + = { { 0, 0, m_bounds_status.top, m_bounds_status.left }, + { 0, ncols - 1, m_bounds_status.top, m_bounds_status.right }, + { nrows - 1, 0, m_bounds_status.bottom, m_bounds_status.left }, + { nrows - 1, ncols - 1, m_bounds_status.bottom, m_bounds_status.right } }; + + for (const auto& c : corners) + { + node_status cs = std::max(c.row_border, c.col_border, detail::node_status_cmp); + temp_nodes_status(c.row, c.col) = cs; + } + + // set user-defined nodes + for (const raster_node& n : nodes_status) + { + if (n.status == node_status::looped) + { + throw std::invalid_argument("node_status::looped is not allowed in " + "'nodes_status' " + "(use 'bounds_status' instead)"); + } + else if (temp_nodes_status.at(n.row, n.col) == node_status::looped) + { + throw std::invalid_argument("cannot overwrite the status of a " + "looped boundary node"); + } + + temp_nodes_status.at(n.row, n.col) = n.status; + } + + m_nodes_status = temp_nodes_status; + } + + /** + * Pre-store for each (row, col) dimension 1-d arrays + * that will be used to get the characteristic location of a node + * on the grid. + */ + template + void raster_grid_xt::build_nodes_codes() + { + std::array, 2> gcode_rc; + + for (std::uint8_t dim = 0; dim < 2; ++dim) + { + auto fill_value = static_cast(3 - dim * 2); + std::vector gcode_component(m_shape[dim], fill_value); + + gcode_component[0] = 0; + gcode_component[m_shape[dim] - 1] = static_cast(fill_value * 2); + + gcode_rc[dim] = gcode_component; + } + + m_nodes_codes.resize({ m_size }); + for (std::size_t r = 0; r < m_shape[0]; ++r) + { + for (std::size_t c = 0; c < m_shape[1]; ++c) + { + m_nodes_codes[ravel_idx(r, c)] + = static_cast(gcode_rc[0][r] + gcode_rc[1][c]); + } + } + } + + /** + * Pre-store the (row, col) index offsets of grid node neighbors for + * each of the 9-characteristic locations on the grid. + * + * Those offsets take into account looped boundary conditions (if any). + * The order of the returned offsets corresponds to the row-major layout. + */ + template + auto raster_grid_xt::build_coded_neighbors_offsets() -> coded_noffsets_type + { + auto dr = static_cast(m_shape[0] - 1); + auto dc = static_cast(m_shape[1] - 1); + + if (!m_bounds_status.is_vertical_looped()) + { + dr = 0; + } + if (!m_bounds_status.is_horizontal_looped()) + { + dc = 0; + } + + return { + this->node_neighbors_offsets(dr, 1, dc, 1), // top-left corner + this->node_neighbors_offsets(dr, 1, -1, 1), // top border + this->node_neighbors_offsets(dr, 1, -1, -dc), // top-right corner + this->node_neighbors_offsets(-1, 1, dc, 1), // left border + this->node_neighbors_offsets(-1, 1, -1, 1), // inner + this->node_neighbors_offsets(-1, 1, -1, -dc), // right border + this->node_neighbors_offsets(-1, -dr, dc, 1), // bottom-left corner + this->node_neighbors_offsets(-1, -dr, -1, 1), // bottom border + this->node_neighbors_offsets(-1, -dr, -1, -dc), // bottom-right corner + }; + } + + template + inline auto raster_grid_xt::neighbor_offsets(code_type code) const noexcept + -> const neighbors_offsets_type& + { + return m_neighbor_offsets[code]; + } + + template + auto raster_grid_xt::build_coded_neighbors_distances() -> coded_ndistances_type + { + coded_ndistances_type nb_distances; + auto xspacing = m_spacing; + + auto to_dist = [&xspacing](auto&& offset) -> double + { + auto drc = xt::where(xt::equal(offset, 0), 0., 1.) * xspacing; + return std::sqrt(xt::sum(xt::square(drc))(0)); + }; + + for (std::uint8_t k = 0; k < 9; ++k) + { + auto offsets = neighbor_offsets(k); + auto distances = neighbors_distances_impl_type(); + + std::transform(offsets.cbegin(), offsets.cend(), distances.begin(), to_dist); + + nb_distances[k] = distances; + } + + return nb_distances; + } + + /** + * @name Node methods + */ + /** + * Given row and col indices, return a code in the range [0,8], which + * corresponds to one of the following characteristic locations on the + * grid (i.e., inner/border/corner). Use a row-major layout: + * + * 0 -- 1 -- 2 + * | | + * 3 4 5 + * | | + * 6 -- 7 -- 8 + */ + template + inline auto raster_grid_xt::nodes_codes(const size_type& row, + const size_type& col) const noexcept + -> code_type + { + return m_nodes_codes[ravel_idx(row, col)]; + } + + /** + * Given a flat index, return a code in the range [0,8], which + * corresponds to one of the following characteristic locations on the + * grid (i.e., inner/border/corner). Use a row-major layout: + * + * 0 -- 1 -- 2 + * | | + * 3 4 5 + * | | + * 6 -- 7 -- 8 + */ + template + inline auto raster_grid_xt::nodes_codes(const size_type& idx) const noexcept + -> code_type + { + return m_nodes_codes[idx]; + } + //@} + + /** + * @name Neighbor methods + */ + /** + * Returns an array of the indices of the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * @param row The grid node row index. + * @param col The grid node column index. + */ + template + inline auto raster_grid_xt::neighbors_indices(const size_type& row, + const size_type& col) + -> neighbors_indices_raster_type + { + neighbors_indices_raster_type indices; + neighbors_indices(row, col, indices); + + return indices; + } + + /** + * Resize and fills an array with the neighbors indices of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * This method prevents allocating a new container for better performance. + * + * @param row The grid node row index. + * @param col The grid node column index. + * @param neighbors_indices Reference to the container to be updated with the neighbors indices. + */ + template + inline auto raster_grid_xt::neighbors_indices( + const size_type& row, + const size_type& col, + neighbors_indices_raster_type& neighbors_indices) -> neighbors_indices_raster_type& + { + const size_type flat_idx = ravel_idx(row, col); + const auto& n_count = neighbors_count_impl(flat_idx); + const auto& n_indices = this->get_nb_indices_from_cache(flat_idx); + + if (neighbors_indices.size() != n_count) + { + neighbors_indices.resize({ n_count }); + } + + for (size_type i = 0; i < n_count; ++i) + { + neighbors_indices[i] = unravel_idx(n_indices[i]); + } + + return neighbors_indices; + } + + /** + * Returns a vector of the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * @param row The grid node row index. + * @param col The grid node column index. + * @return A vector of neighbor node objects. + */ + template + inline auto raster_grid_xt::neighbors(const size_type& row, const size_type& col) + -> neighbors_raster_type + { + neighbors_raster_type nb; + neighbors(row, col, nb); + + return nb; + } + + /** + * Resize and fills a vactor with the neighbors of a given grid node. + * + * Follows looped boundary conditions, if any. + * + * This method prevents allocating a new container for better performance. + * + * @param row The grid node row index. + * @param col The grid node column index. + * @param neighbors Reference to the vector to be updated with the neighbor objects. + */ + template + inline auto raster_grid_xt::neighbors(const size_type& row, + const size_type& col, + neighbors_raster_type& neighbors) + -> neighbors_raster_type& + { + size_type n_flat_idx; + raster_idx_type n_raster_idx; + + const size_type flat_idx = ravel_idx(row, col); + const auto& n_count = neighbors_count_impl(flat_idx); + const auto& n_indices = this->get_nb_indices_from_cache(flat_idx); + const auto& n_distances = neighbors_distances_impl(flat_idx); + + if (neighbors.size() != n_count) + { + neighbors.resize({ n_count }); + } + + for (size_type i = 0; i < n_count; ++i) + { + n_flat_idx = n_indices[i]; + n_raster_idx = unravel_idx(n_flat_idx); + neighbors[i] = raster_neighbor({ n_flat_idx, + n_raster_idx.first, + n_raster_idx.second, + n_distances[i], + this->nodes_status()[n_flat_idx] }); + } + + return neighbors; + } + //@} + + template + inline auto raster_grid_xt::nodes_areas_impl() const -> xt_type + { + return xt::broadcast(m_node_area, m_shape); + } + + template + inline auto raster_grid_xt::nodes_areas_impl(const size_type& /*idx*/) const noexcept + -> grid_data_type + { + return m_node_area; + } + + template + inline auto raster_grid_xt::neighbors_count_impl(const size_type& idx) const noexcept + -> size_type + { + return m_neighbors_count[m_nodes_codes(idx)]; + } + + template + inline auto raster_grid_xt::neighbors_distances_impl( + const size_type& idx) const noexcept -> const neighbors_distances_impl_type& + { + return m_neighbor_distances[nodes_codes(idx)]; + } + + template + inline auto raster_grid_xt::neighbors_indices_impl( + neighbors_indices_impl_type& neighbors, const size_type& idx) const -> void + { + const auto& offsets = neighbor_offsets(nodes_codes(idx)); + + for (size_type i = 0; i < offsets.size(); ++i) + { + const auto offset = offsets[i]; + neighbors.at(i) = static_cast((offset)[0]) * m_shape[1] + + static_cast((offset)[1]) + idx; + } + } + + /** + * @typedef raster_grid + * + * \rst + * Alias template on ``raster_grid_xt`` with :cpp:type:`xt::xtensor` + * used as array container type for data members. + * + * This is mainly for convenience when using in C++ applications. + * \endrst + */ + using raster_grid = raster_grid_xt; + +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/grid/structured_grid.hpp b/contrib/fastscape/include/fastscapelib/grid/structured_grid.hpp new file mode 100644 index 00000000000..f6d97cbfd0b --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/grid/structured_grid.hpp @@ -0,0 +1,115 @@ +/** + * structured grid abstract class. + */ +#ifndef FASTSCAPELIB_GRID_STRUCTURED_GRID_H +#define FASTSCAPELIB_GRID_STRUCTURED_GRID_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xtensor/xbuilder.hpp" +#include "xtensor/xarray.hpp" +#include "xtensor/xfixed.hpp" +#include "xtensor/xtensor.hpp" +#include "xtensor/xview.hpp" +#include "xtensor/xindex_view.hpp" +#include "xtensor/xnoalias.hpp" + +#include "xtl/xiterator_base.hpp" + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/utils/utils.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + /** + * Base class for setting the status at the border nodes of structured grids. + */ + class boundary_status + { + protected: + bool is_looped(node_status status) const; + }; + + inline bool boundary_status::is_looped(const node_status status) const + { + return status == node_status::looped; + } + + /** + * Extends the common grid interface for all structured grid types. + * + * @tparam G The derived grid type. + */ + template + class structured_grid : public grid + { + public: + using base_type = grid; + using inner_types = grid_inner_types; + + using shape_type = typename base_type::shape_type; + using length_type = typename inner_types::length_type; + using spacing_type = typename inner_types::spacing_type; + + using spacing_t = std::conditional_t::value, + spacing_type, + const spacing_type&>; + + spacing_t spacing() const noexcept; + length_type length() const noexcept; + shape_type shape() const noexcept; + + protected: + using grid::grid; + ~structured_grid() = default; + }; + + /** + * @name Grid properties + */ + //@{ + /** + * Returns the (uniform) spacing between two adjacent grid nodes. + * + * Depending on the dimensions of the grid, returns either a single value + * or an array (constant reference). + */ + template + inline auto structured_grid::spacing() const noexcept -> spacing_t + { + return this->derived_grid().m_spacing; + } + + /** + * Returns the length of the grid for all its dimensions. + */ + template + inline auto structured_grid::length() const noexcept -> length_type + { + return this->derived_grid().m_length; + } + + /** + * Returns the shape of the grid node arrays. + */ + template + inline auto structured_grid::shape() const noexcept -> shape_type + { + return this->derived_grid().m_shape; + } + //@} +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/grid/trimesh.hpp b/contrib/fastscape/include/fastscapelib/grid/trimesh.hpp new file mode 100644 index 00000000000..f5ad01cb57a --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/grid/trimesh.hpp @@ -0,0 +1,427 @@ +#ifndef FASTSCAPELIB_GRID_TRIMESH_H +#define FASTSCAPELIB_GRID_TRIMESH_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xtensor/xstrided_view.hpp" +#include "xtensor/xhistogram.hpp" + +#include "fastscapelib/grid/base.hpp" +#include "fastscapelib/utils/xtensor_utils.hpp" + + +namespace fastscapelib +{ + + namespace detail + { + + /** + * Used to extract unique edges in the mesh (or count their occurence). + * + * This hash function yields the same output for simple permutations, + * which is what we want since the pairs of node indices (2, 5) and + * (5, 2) both refer to the same edge. + */ + template + struct tri_edge_hash + { + std::size_t operator()(const std::pair& p) const + { + auto h1 = std::hash()(p.first); + auto h2 = std::hash()(p.second); + + return h1 ^ h2; + } + }; + + /** + * Used to extract unique edges in the mesh (or count their occurence). + * + * This comparator returns true for simple permuations. It is needed in + * addition to ``edge_hash`` in order to build a map of unique edges + * ignoring permutations. + */ + template + struct tri_edge_equal + { + using pair_type = std::pair; + + bool operator()(const pair_type& p1, const pair_type& p2) const + { + if (p1.first == p2.second && p1.second == p2.first) + { + return true; + } + else + { + return p1.first == p2.first && p1.second == p2.second; + } + } + }; + + template + using tri_edge_type = std::pair; + + template + using tri_edge_map = std:: + unordered_map, std::size_t, tri_edge_hash, tri_edge_equal>; + } + + + template + class trimesh_xt; + + /** + * 2-d triangular mesh specialized types. + */ + template + struct grid_inner_types> + { + static constexpr bool is_structured = false; + static constexpr bool is_uniform = false; + + using grid_data_type = double; + + using xt_selector = S; + static constexpr std::size_t xt_ndims = 1; + + static constexpr uint8_t n_neighbors_max = N; + using neighbors_cache_type = neighbors_no_cache<0>; + }; + + /** + * @brief 2-dimensional triangular (unstructured) mesh. + * + * Fastscapelib grid adapter for a 2-d triangular mesh. This class + * requires an input mesh (it doesn't provide any meshing capability). + * + * @tparam S The xtensor container selector for data array members. + * @tparam N The maximum number of node neighbors. + */ + template + class trimesh_xt : public grid> + { + public: + using self_type = trimesh_xt; + using base_type = grid; + + using grid_data_type = typename base_type::grid_data_type; + + using xt_selector = typename base_type::xt_selector; + using size_type = typename base_type::size_type; + using shape_type = typename base_type::shape_type; + + using points_type = xt_tensor_t; + using triangles_type = xt_tensor_t; + using indices_type = xt_tensor_t; + using areas_type = xt_tensor_t; + + using neighbors_type = typename base_type::neighbors_type; + using neighbors_indices_type = typename base_type::neighbors_indices_type; + using neighbors_distances_type = typename base_type::neighbors_distances_type; + + using node_status_type = typename base_type::node_status_type; + + trimesh_xt(const points_type& points, + const triangles_type& triangles, + const std::vector& nodes_status = {}); + + protected: + using neighbors_distances_impl_type = typename base_type::neighbors_distances_impl_type; + using neighbors_indices_impl_type = typename base_type::neighbors_indices_impl_type; + + shape_type m_shape; + size_type m_size; + + points_type m_nodes_points; + areas_type m_nodes_areas; + std::unordered_set m_boundary_nodes; + node_status_type m_nodes_status; + + std::vector m_neighbors_indices; + std::vector m_neighbors_distances; + + void set_neighbors(const points_type& points, const triangles_type& triangles); + void set_nodes_status(const std::vector& nodes_status); + void set_nodes_areas(const points_type& points, const triangles_type& triangles); + + inline areas_type nodes_areas_impl() const; + inline grid_data_type nodes_areas_impl(const size_type& idx) const noexcept; + + inline size_type neighbors_count_impl(const size_type& idx) const; + + void neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const; + + inline const neighbors_distances_impl_type& neighbors_distances_impl( + const size_type& idx) const; + + friend class grid; + }; + + + /** + * @name Constructors + */ + //@{ + /** + * Creates a new triangular mesh. + * + * @param points The mesh node x,y coordinates (array of shape [N, 2]). + * @param triangles The node indices of the triangles (array of shape [K, 3]). + * @param nodes_status Manually define the status at any node on the mesh. + * + * If ``nodes_status`` is empty, a "fixed value" status is set for all + * boundary nodes (i.e., the end-points of all the edges that are not shared + * by more than one triangle). + */ + template + trimesh_xt::trimesh_xt(const points_type& points, + const triangles_type& triangles, + const std::vector& nodes_status) + : base_type(0) + , m_nodes_points(points) + { + if (points.shape()[1] != 2) + { + throw std::invalid_argument("invalid shape for points arrays (expects shape [N, 2])"); + } + if (triangles.shape()[1] != 3) + { + throw std::invalid_argument( + "invalid shape for triangles arrays (expects shape [K, 2])"); + } + + m_size = points.shape()[0]; + m_shape = { static_cast(m_size) }; + + set_neighbors(points, triangles); + set_nodes_status(nodes_status); + set_nodes_areas(points, triangles); + } + //@} + + template + void trimesh_xt::set_neighbors(const points_type& points, const triangles_type& triangles) + { + // extract and count triangle edges + + using edge_type = detail::tri_edge_type; + using edge_map = detail::tri_edge_map; + + edge_map edges_count; + const std::array, 3> tri_local_indices{ + { { 1, 2 }, { 2, 0 }, { 0, 1 } } + }; + + size_type n_triangles = triangles.shape()[0]; + + for (size_type i = 0; i < n_triangles; i++) + { + for (const auto& edge_idx : tri_local_indices) + { + const edge_type key(triangles(i, edge_idx[0]), triangles(i, edge_idx[1])); + + auto result = edges_count.insert({ key, 1 }); + // increment edge count if already inserted + if (!result.second) + { + result.first->second += 1; + } + } + } + + // fill node neighbor data and find boundary nodes + + m_boundary_nodes.clear(); + m_neighbors_indices.resize(m_size); + m_neighbors_distances.resize(m_size); + + for (const auto& edge : edges_count) + { + const edge_type& edge_points = edge.first; + size_type count = edge.second; + + if (count == 1) + { + m_boundary_nodes.insert(edge_points.first); + m_boundary_nodes.insert(edge_points.second); + } + + m_neighbors_indices[edge_points.first].push_back(edge_points.second); + m_neighbors_indices[edge_points.second].push_back(edge_points.first); + + const auto x1 = points(edge_points.first, 0); + const auto y1 = points(edge_points.first, 1); + const auto x2 = points(edge_points.second, 0); + const auto y2 = points(edge_points.second, 1); + auto distance = std::sqrt(((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))); + m_neighbors_distances[edge_points.first].push_back(distance); + m_neighbors_distances[edge_points.second].push_back(distance); + } + } + + template + void trimesh_xt::set_nodes_areas(const points_type& points, + const triangles_type& triangles) + { + size_type n_points = points.shape()[0]; + size_type n_triangles = triangles.shape()[0]; + double just_above_zero = std::numeric_limits::min(); + + std::array, 3> local_idx{ { { 1, 2 }, { 2, 0 }, { 0, 1 } } }; + + std::array coords_shape{ { 3, n_triangles, 2 } }; + xt::xtensor half_edge_coords = xt::empty(coords_shape); + + for (size_type t = 0; t < n_triangles; t++) + { + for (size_type i = 0; i < 3; i++) + { + auto v1 = local_idx[i][0]; + auto v2 = local_idx[i][1]; + + for (size_type j = 0; j < 2; j++) + { + auto p1 = triangles(t, v1); + auto p2 = triangles(t, v2); + half_edge_coords(i, t, j) = points(p2, j) - points(p1, j); + } + } + } + + xt::xtensor ei_dot_ei = xt::sum(half_edge_coords * half_edge_coords, 2); + xt::xtensor ei_dot_ej = ei_dot_ei - xt::sum(ei_dot_ei, 0) / 2.0; + + xt::xtensor triangles_areas; + triangles_areas.resize({ n_triangles }); + + for (size_type t = 0; t < n_triangles; t++) + { + double area_square + = 0.25 + * (ei_dot_ej(2, t) * ei_dot_ej(0, t) + ei_dot_ej(0, t) * ei_dot_ej(1, t) + + ei_dot_ej(1, t) * ei_dot_ej(2, t)); + + // prevent negative values due to round-off errors + triangles_areas(t) = std::sqrt(std::max(area_square, just_above_zero)); + } + + auto ce_ratios = -ei_dot_ej * 0.25 / triangles_areas; + xt::xtensor tri_partitions = ei_dot_ei / 2 * ce_ratios / (3 - 1); + + xt::xtensor weights; + weights.resize({ n_triangles * 3 }); + + for (size_type t = 0; t < n_triangles; t++) + { + weights(t) = tri_partitions(1, t) + tri_partitions(2, t); + weights(n_triangles + t) = tri_partitions(2, t) + tri_partitions(0, t); + weights(n_triangles * 2 + t) = tri_partitions(0, t) + tri_partitions(1, t); + } + + auto triangles_t_flat = xt::flatten(xt::transpose(triangles)); + m_nodes_areas = xt::bincount(triangles_t_flat, weights, n_points); + + // avoid area = 0 for isolated nodes (not nice for logarithm) + for (size_type i = 0; i < n_points; i++) + { + if (m_nodes_areas(i) == 0 && neighbors_count_impl(i) == 0) + { + m_nodes_areas(i) = just_above_zero; + } + } + } + + template + void trimesh_xt::set_nodes_status(const std::vector& nodes_status) + { + node_status_type temp_nodes_status(m_shape, node_status::core); + + if (nodes_status.size() > 0) + { + for (const node& n : nodes_status) + { + if (n.status == node_status::looped) + { + throw std::invalid_argument("node_status::looped is not allowed in " + "triangular meshes"); + } + + temp_nodes_status.at(n.idx) = n.status; + } + } + else + { + // if no status at node is given, set fixed value boundaries for all boundary nodes + for (const size_type& idx : m_boundary_nodes) + { + temp_nodes_status[idx] = node_status::fixed_value; + } + } + + m_nodes_status = temp_nodes_status; + } + + template + inline auto trimesh_xt::neighbors_count_impl(const size_type& idx) const -> size_type + { + return m_neighbors_indices[idx].size(); + } + + template + inline auto trimesh_xt::nodes_areas_impl() const -> areas_type + { + return m_nodes_areas; + } + + template + inline auto trimesh_xt::nodes_areas_impl(const size_type& idx) const noexcept + -> grid_data_type + { + return m_nodes_areas(idx); + } + + template + void trimesh_xt::neighbors_indices_impl(neighbors_indices_impl_type& neighbors, + const size_type& idx) const + { + const auto& size = m_neighbors_indices[idx].size(); + neighbors.resize(size); + + for (size_type i = 0; i < size; i++) + { + neighbors[i] = m_neighbors_indices[idx][i]; + } + } + + template + auto trimesh_xt::neighbors_distances_impl(const size_type& idx) const + -> const neighbors_distances_impl_type& + { + return m_neighbors_distances[idx]; + } + + + /** + * @typedef trimesh + * + * \rst + * Alias template on ``trimesh_xt`` with :cpp:type:`xt::xtensor` + * used as array container type for data members. + * + * This is mainly for convenience when using in C++ applications. + * \endrst + */ + using trimesh = trimesh_xt; +} + +#endif // FASTSCAPELIB_GRID_TRIMESH_H diff --git a/contrib/fastscape/include/fastscapelib/utils/consts.hpp b/contrib/fastscape/include/fastscapelib/utils/consts.hpp new file mode 100644 index 00000000000..0d0e0e9bd63 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/utils/consts.hpp @@ -0,0 +1,33 @@ +/** + * @file + * @brief Defines a number of constants used in this library. + */ + +#pragma once + +namespace fastscapelib +{ + + namespace consts + { + + /* + * @brief Row index offsets (from a central grid point) of D8 + * neighbour directions. + * + * The first element correspond to the central grid point itself. + */ + const short d8_row_offsets[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 }; + + + /** + * @brief Column index offsets (from a central grid point) of D8 + * neighbour directions. + * + * The first element correspond to the central grid point itself. + */ + const short d8_col_offsets[9] = { 0, 0, -1, -1, -1, 0, 1, 1, 1 }; + + } // namespace consts + +} // namespace fastscapelib diff --git a/contrib/fastscape/include/fastscapelib/utils/iterators.hpp b/contrib/fastscape/include/fastscapelib/utils/iterators.hpp new file mode 100644 index 00000000000..982b1a7863b --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/utils/iterators.hpp @@ -0,0 +1,186 @@ +#ifndef FASTSCAPELIB_UTILS_ITERATORS_H +#define FASTSCAPELIB_UTILS_ITERATORS_H + +#include + +#include "xtl/xiterator_base.hpp" + + +namespace fastscapelib +{ + + namespace detail + { + + template + struct grid_node_index_iterator + : public xtl::xbidirectional_iterator_base, + typename G::size_type> + { + public: + using self_type = grid_node_index_iterator; + using base_type = xtl::xbidirectional_iterator_base; + + using value_type = typename base_type::value_type; + using reference = typename base_type::reference; + using pointer = typename base_type::pointer; + using difference_type = typename base_type::difference_type; + + using filter_func_type = std::function; + + grid_node_index_iterator(const G& grid, filter_func_type func, value_type position = 0) + : m_idx(position) + , m_grid(grid) + , m_filter_func(func) + { + // iterate to the actual starting position (1st node that pass the filter test) + while ((!m_filter_func(m_grid, m_idx)) && (m_idx < m_grid.size())) + { + ++m_idx; + } + } + + inline self_type& operator++() + { + do + { + ++m_idx; + } while ((!m_filter_func(m_grid, m_idx)) && (m_idx < m_grid.size())); + + return *this; + } + + inline self_type& operator--() + { + do + { + --m_idx; + } while ((!m_filter_func(m_grid, m_idx)) && (m_idx > 0)); + + return *this; + } + + inline reference operator*() const + { + return m_idx; + } + + private: + mutable value_type m_idx; + + const G& m_grid; + filter_func_type m_filter_func; + + template + friend bool operator==(const grid_node_index_iterator<_G>&, + const grid_node_index_iterator<_G>&); + }; + + template + inline bool operator==(const grid_node_index_iterator& lhs, + const grid_node_index_iterator& rhs) + { + return lhs.m_idx == rhs.m_idx; + } + } + + + /** + * STL-compatible, immutable, virtual container for iterating through grid node + * indices. + * + * Currently only implements a bidirectional iterator. + * + * @tparam G The grid type. + */ + template + class grid_nodes_indices + { + public: + using filter_func_type = std::function; + using iterator = detail::grid_node_index_iterator; + + grid_nodes_indices(const G& grid, filter_func_type func = nullptr) + : m_grid(grid) + { + if (!func) + { + m_filter_func = [](const G&, typename G::size_type) { return true; }; + } + else + { + m_filter_func = func; + } + } + + inline iterator begin() const + { + return iterator(m_grid, m_filter_func, 0); + } + + inline iterator end() const + { + return iterator(m_grid, m_filter_func, m_grid.size()); + } + + inline std::reverse_iterator rbegin() const + { + return std::reverse_iterator(end()); + } + + inline std::reverse_iterator rend() const + { + return std::reverse_iterator(begin()); + } + + private: + const G& m_grid; + filter_func_type m_filter_func; + }; + + + /** + * Thin wrapper around iterators of any STL-compatible container. + * + * Only const forward and reverse iterators are exposed. + * + * @tparam C The container type. + * + */ + template + class stl_container_iterator_wrapper + { + public: + using const_iterator_type = typename C::const_iterator; + using reverse_const_iterator_type = std::reverse_iterator; + + stl_container_iterator_wrapper(const C& container) + : m_container(container) + { + } + + inline const_iterator_type begin() const + { + return m_container.begin(); + } + + inline const_iterator_type end() const + { + return m_container.end(); + } + + inline reverse_const_iterator_type rbegin() const + { + return m_container.rbegin(); + } + + inline reverse_const_iterator_type rend() const + { + return m_container.rend(); + } + + private: + const C& m_container; + }; +} +#endif diff --git a/contrib/fastscape/include/fastscapelib/utils/union_find.hpp b/contrib/fastscape/include/fastscapelib/utils/union_find.hpp new file mode 100644 index 00000000000..5c66b7d72ba --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/utils/union_find.hpp @@ -0,0 +1,174 @@ +/** + * @file + * @brief Provides implementation for the classical Union-Find data structure + * + * @author Guillaume Cordonnier + */ + +#pragma once + +#include "utils.hpp" +#include + + +namespace fastscapelib +{ + + namespace detail + { + + /** + * @class union_find + * @brief Union-Find data structure + * + * The union find is a standard data structure for storing and merging equivalence + * classes. Initialy (after construction, or call to clear), the Union Find stores + * size() distincts classes. + * The classes can be merged with a call to merge(), and obtained through the call + * to find(). + * Amortized complexity of Union and Find: optimal O(alpha(m, n)) + */ + template + class union_find + { + public: + /** + * @brief UnionFind Constructor + */ + union_find() + { + } + + /** + * @brief UnionFind Constructor + * @param _size the number of classes + * Complexity O(_size) + */ + union_find(size_t _size) + { + resize(_size); + } + + /** + * @brief clear + * Restore the union-find data structure. The structure holds m_size different size() + * distinct classes + * Complexity O(size()) + */ + void clear() + { + size_t old_size = size(); + parent.clear(); + rank.clear(); + resize(old_size); + } + + /** + * @brief reserve + * Allocate some memory for a given number of class + * @param _size the number of classes + */ + void reserve(size_t _size) + { + parent.reserve(_size); + rank.reserve(_size); + } + + /** + * @brief push_back + * append a new item at the end of the union find structure + * @param c the class of the new item + */ + + void push_back(T c) + { + parent.push_back(c); + rank.push_back(0); + if (c != parent.size() - 1 && !rank[c]) + rank[c] = 1; + } + + /** + * @brief resize + * Resize the internal containers of union find. This only add classes if the new + * size is larger. Else, some elements are removed, but it is possible that the + * class returned by find is bigger than the new size. + * Complexity O(_size - size()) + * @param _size the new size + */ + void resize(size_t _size) + { + // size_t old_size = size(); + + parent.resize(_size); + rank.resize(_size, 0); + + // TODO: this causes seg fault when used from the py bindings + // -> regenerate the whole sequence as a workaround + // std::iota(parent.begin() + old_size, parent.end(), old_size); + std::iota(parent.begin(), parent.end(), 0); + } + + /** + * @brief size + * @return the initial number of elements in the union find structure + */ + size_t size() + { + return parent.size(); + } + + /** + * @brief merge two equivalence class + * The new equivelence class can be represented either by x or y + * @param x class to be merged + * @param y class to be merged + */ + void merge(T x, T y) + { + x = find(x); + y = find(y); + + if (x != y) + { + if (rank[x] < rank[y]) + parent[x] = y; + else + { + parent[y] = x; + if (rank[x] == rank[y]) + rank[x] += 1; + } + } + } + + /** + * @brief find the equivalence class of an element + * @param x the element for which the class is needed + * @return the class representative + */ + T find(T x) + { + // find class + T c = x; + while (c != parent[c]) + c = parent[c]; + + // set class + while (x != parent[x]) + { + T t = parent[x]; + parent[x] = c; + x = t; + } + + // return class + return c; + } + + private: + std::vector parent; + std::vector rank; + }; + } +} diff --git a/contrib/fastscape/include/fastscapelib/utils/utils.hpp b/contrib/fastscape/include/fastscapelib/utils/utils.hpp new file mode 100644 index 00000000000..97b73df0af4 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/utils/utils.hpp @@ -0,0 +1,107 @@ +/** + * @file + * @brief Some utilities used internally. + */ +#ifndef FASTSCAPELIB_UTILS_UTILS_H +#define FASTSCAPELIB_UTILS_UTILS_H + + +#include +#include + +#include "xtensor.hpp" +#include "xtensor/xcontainer.hpp" + + +// type used for array indexers +using index_t = std::ptrdiff_t; + +// type used when working with both shapes or sizes (unsigned) and indexers (signed) +using sshape_t = std::make_signed_t; + +// type used as an alias to xtensor's xexpression +// TODO: use xexpression shaped when it's ready (see xtensor GH #994) +template +using xtensor_t = xt::xexpression; + + +namespace fastscapelib +{ + namespace detail + { + + /** + * @brief Return true if a given (row, col) index is in bounds (false + * otherwise). + */ + template + bool in_bounds(const S& shape, index_t row, index_t col) + { + return (row >= 0 && row < static_cast(shape[0]) && col >= 0 + && col < static_cast(shape[1])); + } + + /** + * @brief Return a pair of the (row, col) coordinate of a node + * in a dem of ncols columns + */ + template + std::pair coords(Node_T node, Node_T ncols) + { + return { node / ncols, node % ncols }; + } + + /** + * @brief Return the node index of a node given its (row, col) coordinate + * in a dem of ncols columns + */ + template + Node_T index(Node_T row, Node_T col, Node_T ncols) + { + return col + row * ncols; + } + + /** + * @brief proxy for flattened view waiting it to be implmented in xtensor + * Unsafe in the genral case, use it as a replacement for maintance help ... + */ + + template + class Flattened2D + { + public: + using value_type = typename Array_T::value_type; + using size_type = typename Array_T::size_type; + + Flattened2D(Array_T& ref) + : _array_ref{ ref } + { + _ncols = ref.shape()[1]; + } + + typename Array_T::value_type operator()(size_type index) + { + auto c = coords(index, _ncols); + return _array_ref(std::get<0>(c), std::get<1>(c)); + } + + typename Array_T::value_type at(size_type index) + { + auto c = coords(index, _ncols); + return _array_ref.at(std::get<0>(c), std::get<1>(c)); + } + + private: + Array_T& _array_ref; + size_type _ncols; + }; + + template + Flattened2D make_flattened(Array_T& ref) + { + return Flattened2D(ref); + } + } +} + +#endif diff --git a/contrib/fastscape/include/fastscapelib/utils/xtensor_utils.hpp b/contrib/fastscape/include/fastscapelib/utils/xtensor_utils.hpp new file mode 100644 index 00000000000..370b1ba826c --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/utils/xtensor_utils.hpp @@ -0,0 +1,73 @@ +/** + * xtensor utils (container tags, etc.) + */ +#ifndef FASTSCAPELIB_UTILS_XTENSOR_UTILS_H +#define FASTSCAPELIB_UTILS_XTENSOR_UTILS_H + + +#include + +#include "xtensor/xarray.hpp" +#include "xtensor/xtensor.hpp" + + +namespace fastscapelib +{ + + /** + * The xtensor selector used by default in Fastscapelib C++ API. + * + * \rst + * + * Fastscapelib template classes specialized with this selector will use + * :cpp:type:`xt::xtensor` or :cpp:type:`xt::xarray` as container types for + * their public array members. + * + * \endrst + * + */ + struct xt_selector + { + }; + + /** + * Used to get the actual xtensor container type from a given selector. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + * @tparam N The number of dimensions (only for static dimension containers) + */ + template + struct xt_container + { + }; + + template + struct xt_container + { + using tensor_type = xt::xtensor; + using array_type = xt::xarray; + }; + + /** + * Alias for the selected (static dimension) xtensor container type. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + * @tparam N The fixed number of dimensions. + */ + template + using xt_tensor_t = typename xt_container::tensor_type; + + /** + * Alias for the selected (dynamic dimension) xtensor container type. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + */ + template + using xt_array_t = typename xt_container::array_type; + +} // namespace fastscapelib + +#endif diff --git a/contrib/fastscape/include/fastscapelib/version.hpp b/contrib/fastscape/include/fastscapelib/version.hpp new file mode 100644 index 00000000000..5622b1b5084 --- /dev/null +++ b/contrib/fastscape/include/fastscapelib/version.hpp @@ -0,0 +1,22 @@ +#ifndef FASTSCAPELIB_CONFIG_HPP +#define FASTSCAPELIB_CONFIG_HPP + +#define FASTSCAPELIB_VERSION_MAJOR 0 +#define FASTSCAPELIB_VERSION_MINOR 1 +#define FASTSCAPELIB_VERSION_PATCH 3 + +#include + +namespace fastscapelib +{ + namespace version + { + constexpr int version_major = FASTSCAPELIB_VERSION_MAJOR; + constexpr int version_minor = FASTSCAPELIB_VERSION_MINOR; + constexpr int version_patch = FASTSCAPELIB_VERSION_PATCH; + static const std::string version_str = std::to_string(version_major) + "." + + std::to_string(version_minor) + "." + + std::to_string(version_patch); + } +} +#endif diff --git a/contrib/xtensor/include/xtensor/xaccessible.hpp b/contrib/xtensor/include/xtensor/xaccessible.hpp new file mode 100644 index 00000000000..9f74eb0b68a --- /dev/null +++ b/contrib/xtensor/include/xtensor/xaccessible.hpp @@ -0,0 +1,347 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_ACCESSIBLE_HPP +#define XTENSOR_ACCESSIBLE_HPP + +#include "xexception.hpp" +#include "xstrides.hpp" +#include "xtensor_forward.hpp" + +namespace xt +{ + /** + * @class xconst_accessible + * @brief Base class for implementation of common expression constant access methods. + * + * The xaccessible class implements constant access methods common to all expressions. + * + * @tparam D The derived type, i.e. the inheriting class for which xconst_accessible + * provides the interface. + */ + template + class xconst_accessible + { + public: + + using derived_type = D; + using inner_types = xcontainer_inner_types; + using reference = typename inner_types::reference; + using const_reference = typename inner_types::const_reference; + using size_type = typename inner_types::size_type; + + size_type size() const noexcept; + size_type dimension() const noexcept; + size_type shape(size_type index) const; + + template + const_reference at(Args... args) const; + + template + disable_integral_t operator[](const S& index) const; + template + const_reference operator[](std::initializer_list index) const; + const_reference operator[](size_type i) const; + + template + const_reference periodic(Args... args) const; + + template + bool in_bounds(Args... args) const; + + const_reference front() const; + const_reference back() const; + + protected: + + xconst_accessible() = default; + ~xconst_accessible() = default; + + xconst_accessible(const xconst_accessible&) = default; + xconst_accessible& operator=(const xconst_accessible&) = default; + + xconst_accessible(xconst_accessible&&) = default; + xconst_accessible& operator=(xconst_accessible&&) = default; + + private: + + const derived_type& derived_cast() const noexcept; + }; + + /** + * @class xaccessible + * @brief Base class for implementation of common expression access methods. + * + * The xaccessible class implements access methods common to all expressions. + * + * @tparam D The derived type, i.e. the inheriting class for which xaccessible + * provides the interface. + */ + template + class xaccessible : public xconst_accessible + { + public: + + using base_type = xconst_accessible; + using derived_type = typename base_type::derived_type; + using reference = typename base_type::reference; + using size_type = typename base_type::size_type; + + template + reference at(Args... args); + + template + disable_integral_t operator[](const S& index); + template + reference operator[](std::initializer_list index); + reference operator[](size_type i); + + template + reference periodic(Args... args); + + reference front(); + reference back(); + + using base_type::at; + using base_type::operator[]; + using base_type::back; + using base_type::front; + using base_type::periodic; + + protected: + + xaccessible() = default; + ~xaccessible() = default; + + xaccessible(const xaccessible&) = default; + xaccessible& operator=(const xaccessible&) = default; + + xaccessible(xaccessible&&) = default; + xaccessible& operator=(xaccessible&&) = default; + + private: + + derived_type& derived_cast() noexcept; + }; + + /************************************ + * xconst_accessible implementation * + ************************************/ + + /** + * Returns the size of the expression. + */ + template + inline auto xconst_accessible::size() const noexcept -> size_type + { + return compute_size(derived_cast().shape()); + } + + /** + * Returns the number of dimensions of the expression. + */ + template + inline auto xconst_accessible::dimension() const noexcept -> size_type + { + return derived_cast().shape().size(); + } + + /** + * Returns the i-th dimension of the expression. + */ + template + inline auto xconst_accessible::shape(size_type index) const -> size_type + { + return derived_cast().shape()[index]; + } + + /** + * Returns a constant reference to the element at the specified position in the expression, + * after dimension and bounds checking. + * @param args a list of indices specifying the position in the expression. Indices + * must be unsigned integers, the number of indices should be equal to the number of dimensions + * of the expression. + * @exception std::out_of_range if the number of argument is greater than the number of dimensions + * or if indices are out of bounds. + */ + template + template + inline auto xconst_accessible::at(Args... args) const -> const_reference + { + check_access(derived_cast().shape(), args...); + return derived_cast().operator()(args...); + } + + /** + * Returns a constant reference to the element at the specified position in the expression. + * @param index a sequence of indices specifying the position in the expression. Indices + * must be unsigned integers, the number of indices in the list should be equal or greater + * than the number of dimensions of the expression. + */ + template + template + inline auto xconst_accessible::operator[](const S& index) const + -> disable_integral_t + { + return derived_cast().element(index.cbegin(), index.cend()); + } + + template + template + inline auto xconst_accessible::operator[](std::initializer_list index) const -> const_reference + { + return derived_cast().element(index.begin(), index.end()); + } + + template + inline auto xconst_accessible::operator[](size_type i) const -> const_reference + { + return derived_cast().operator()(i); + } + + /** + * Returns a constant reference to the element at the specified position in the expression, + * after applying periodicity to the indices (negative and 'overflowing' indices are changed). + * @param args a list of indices specifying the position in the expression. Indices + * must be integers, the number of indices should be equal to the number of dimensions + * of the expression. + */ + template + template + inline auto xconst_accessible::periodic(Args... args) const -> const_reference + { + normalize_periodic(derived_cast().shape(), args...); + return derived_cast()(static_cast(args)...); + } + + /** + * Returns a constant reference to first the element of the expression + */ + template + inline auto xconst_accessible::front() const -> const_reference + { + return *derived_cast().begin(); + } + + /** + * Returns a constant reference to last the element of the expression + */ + template + inline auto xconst_accessible::back() const -> const_reference + { + return *std::prev(derived_cast().end()); + } + + /** + * Returns ``true`` only if the the specified position is a valid entry in the expression. + * @param args a list of indices specifying the position in the expression. + * @return bool + */ + template + template + inline bool xconst_accessible::in_bounds(Args... args) const + { + return check_in_bounds(derived_cast().shape(), args...); + } + + template + inline auto xconst_accessible::derived_cast() const noexcept -> const derived_type& + { + return *static_cast(this); + } + + /****************************** + * xaccessible implementation * + ******************************/ + + /** + * Returns a reference to the element at the specified position in the expression, + * after dimension and bounds checking. + * @param args a list of indices specifying the position in the expression. Indices + * must be unsigned integers, the number of indices should be equal to the number of dimensions + * of the expression. + * @exception std::out_of_range if the number of argument is greater than the number of dimensions + * or if indices are out of bounds. + */ + template + template + inline auto xaccessible::at(Args... args) -> reference + { + check_access(derived_cast().shape(), args...); + return derived_cast().operator()(args...); + } + + /** + * Returns a reference to the element at the specified position in the expression. + * @param index a sequence of indices specifying the position in the expression. Indices + * must be unsigned integers, the number of indices in the list should be equal or greater + * than the number of dimensions of the expression. + */ + template + template + inline auto xaccessible::operator[](const S& index) -> disable_integral_t + { + return derived_cast().element(index.cbegin(), index.cend()); + } + + template + template + inline auto xaccessible::operator[](std::initializer_list index) -> reference + { + return derived_cast().element(index.begin(), index.end()); + } + + template + inline auto xaccessible::operator[](size_type i) -> reference + { + return derived_cast().operator()(i); + } + + /** + * Returns a reference to the element at the specified position in the expression, + * after applying periodicity to the indices (negative and 'overflowing' indices are changed). + * @param args a list of indices specifying the position in the expression. Indices + * must be integers, the number of indices should be equal to the number of dimensions + * of the expression. + */ + template + template + inline auto xaccessible::periodic(Args... args) -> reference + { + normalize_periodic(derived_cast().shape(), args...); + return derived_cast()(args...); + } + + /** + * Returns a reference to the first element of the expression. + */ + template + inline auto xaccessible::front() -> reference + { + return *derived_cast().begin(); + } + + /** + * Returns a reference to the last element of the expression. + */ + template + inline auto xaccessible::back() -> reference + { + return *std::prev(derived_cast().end()); + } + + template + inline auto xaccessible::derived_cast() noexcept -> derived_type& + { + return *static_cast(this); + } + +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xaccumulator.hpp b/contrib/xtensor/include/xtensor/xaccumulator.hpp new file mode 100644 index 00000000000..f884caa1970 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xaccumulator.hpp @@ -0,0 +1,364 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_ACCUMULATOR_HPP +#define XTENSOR_ACCUMULATOR_HPP + +#include +#include +#include +#include + +#include "xexpression.hpp" +#include "xstrides.hpp" +#include "xtensor_config.hpp" +#include "xtensor_forward.hpp" + +namespace xt +{ + +#define DEFAULT_STRATEGY_ACCUMULATORS evaluation_strategy::immediate_type + + namespace detail + { + template + struct accumulator_identity : xtl::identity + { + using value_type = V; + }; + } + + /************** + * accumulate * + **************/ + + template > + struct xaccumulator_functor : public std::tuple + { + using self_type = xaccumulator_functor; + using base_type = std::tuple; + using accumulate_functor_type = ACCUMULATE_FUNC; + using init_functor_type = INIT_FUNC; + using init_value_type = typename init_functor_type::value_type; + + xaccumulator_functor() + : base_type() + { + } + + template + xaccumulator_functor(RF&& accumulate_func) + : base_type(std::forward(accumulate_func), INIT_FUNC()) + { + } + + template + xaccumulator_functor(RF&& accumulate_func, IF&& init_func) + : base_type(std::forward(accumulate_func), std::forward(init_func)) + { + } + }; + + template + auto make_xaccumulator_functor(RF&& accumulate_func) + { + using accumulator_type = xaccumulator_functor>; + return accumulator_type(std::forward(accumulate_func)); + } + + template + auto make_xaccumulator_functor(RF&& accumulate_func, IF&& init_func) + { + using accumulator_type = xaccumulator_functor, std::remove_reference_t>; + return accumulator_type(std::forward(accumulate_func), std::forward(init_func)); + } + + namespace detail + { + template + xarray::value_type> accumulator_impl(F&&, E&&, std::size_t, EVS) + { + static_assert( + !std::is_same::value, + "Lazy accumulators not yet implemented." + ); + } + + template + xarray::value_type> accumulator_impl(F&&, E&&, EVS) + { + static_assert( + !std::is_same::value, + "Lazy accumulators not yet implemented." + ); + } + + template + struct xaccumulator_return_type + { + using type = xarray; + }; + + template + struct xaccumulator_return_type, R> + { + using type = xarray; + }; + + template + struct xaccumulator_return_type, R> + { + using type = xtensor; + }; + + template + struct xaccumulator_return_type, L>, R> + { + using type = xtensor_fixed, L>; + }; + + template + using xaccumulator_return_type_t = typename xaccumulator_return_type::type; + + template + struct fixed_compute_size; + + template + struct xaccumulator_linear_return_type + { + using type = xtensor; + }; + + template + struct xaccumulator_linear_return_type, R> + { + using type = xtensor; + }; + + template + struct xaccumulator_linear_return_type, R> + { + using type = xtensor; + }; + + template + struct xaccumulator_linear_return_type, L>, R> + { + using type = xtensor_fixed>::value>, L>; + }; + + template + using xaccumulator_linear_return_type_t = typename xaccumulator_linear_return_type::type; + + template + inline auto accumulator_init_with_f(F&& f, E& e, std::size_t axis) + { + // this function is the equivalent (but hopefully faster) to (if axis == 1) + // e[:, 0, :, :, ...] = f(e[:, 0, :, :, ...]) + // so that all "first" values are initialized in a first pass + + std::size_t outer_loop_size, inner_loop_size, pos = 0; + std::size_t outer_stride, inner_stride; + + auto set_loop_sizes = [&outer_loop_size, &inner_loop_size](auto first, auto last, std::ptrdiff_t ax) + { + outer_loop_size = std::accumulate( + first, + first + ax, + std::size_t(1), + std::multiplies() + ); + inner_loop_size = std::accumulate( + first + ax + 1, + last, + std::size_t(1), + std::multiplies() + ); + }; + + // Note: add check that strides > 0 + auto set_loop_strides = [&outer_stride, &inner_stride](auto first, auto last, std::ptrdiff_t ax) + { + outer_stride = static_cast(ax == 0 ? 1 : *std::min_element(first, first + ax)); + inner_stride = static_cast( + (ax == std::distance(first, last) - 1) ? 1 : *std::min_element(first + ax + 1, last) + ); + }; + + set_loop_sizes(e.shape().begin(), e.shape().end(), static_cast(axis)); + set_loop_strides(e.strides().begin(), e.strides().end(), static_cast(axis)); + + if (e.layout() == layout_type::column_major) + { + // swap for better memory locality (smaller stride in the inner loop) + std::swap(outer_loop_size, inner_loop_size); + std::swap(outer_stride, inner_stride); + } + + for (std::size_t i = 0; i < outer_loop_size; ++i) + { + pos = i * outer_stride; + for (std::size_t j = 0; j < inner_loop_size; ++j) + { + e.storage()[pos] = f(e.storage()[pos]); + pos += inner_stride; + } + } + } + + template + inline auto accumulator_impl(F&& f, E&& e, std::size_t axis, evaluation_strategy::immediate_type) + { + using init_type = typename F::init_value_type; + using accumulate_functor_type = typename F::accumulate_functor_type; + using expr_value_type = typename std::decay_t::value_type; + // using return_type = std::conditional_t::value, typename + // std::decay_t::value_type, init_type>; + + using return_type = std::decay_t( + )(std::declval(), std::declval()))>; + + using result_type = xaccumulator_return_type_t, return_type>; + + if (axis >= e.dimension()) + { + XTENSOR_THROW(std::runtime_error, "Axis larger than expression dimension in accumulator."); + } + + result_type res = e; // assign + make a copy, we need it anyways + + if (res.shape(axis) != std::size_t(0)) + { + std::size_t inner_stride = static_cast(res.strides()[axis]); + std::size_t outer_stride = 1; // either row- or column-wise (strides.back / strides.front) + std::size_t outer_loop_size = 0; + std::size_t inner_loop_size = 0; + std::size_t init_size = e.shape()[axis] != std::size_t(1) ? std::size_t(1) : std::size_t(0); + + auto set_loop_sizes = + [&outer_loop_size, &inner_loop_size, init_size](auto first, auto last, std::ptrdiff_t ax) + { + outer_loop_size = std::accumulate(first, first + ax, init_size, std::multiplies()); + + inner_loop_size = std::accumulate( + first + ax, + last, + std::size_t(1), + std::multiplies() + ); + }; + + if (result_type::static_layout == layout_type::row_major) + { + set_loop_sizes(res.shape().cbegin(), res.shape().cend(), static_cast(axis)); + } + else + { + set_loop_sizes(res.shape().cbegin(), res.shape().cend(), static_cast(axis + 1)); + std::swap(inner_loop_size, outer_loop_size); + } + + std::size_t pos = 0; + + inner_loop_size = inner_loop_size - inner_stride; + + // activate the init loop if we have an init function other than identity + if (!std::is_same< + std::decay_t, + typename detail::accumulator_identity>::value) + { + accumulator_init_with_f(xt::get<1>(f), res, axis); + } + + pos = 0; + for (std::size_t i = 0; i < outer_loop_size; ++i) + { + for (std::size_t j = 0; j < inner_loop_size; ++j) + { + res.storage()[pos + inner_stride] = xt::get<0>(f + )(res.storage()[pos], res.storage()[pos + inner_stride]); + + pos += outer_stride; + } + pos += inner_stride; + } + } + return res; + } + + template + inline auto accumulator_impl(F&& f, E&& e, evaluation_strategy::immediate_type) + { + using init_type = typename F::init_value_type; + using expr_value_type = typename std::decay_t::value_type; + using accumulate_functor_type = typename F::accumulate_functor_type; + using return_type = std::decay_t( + )(std::declval(), std::declval()))>; + // using return_type = std::conditional_t::value, typename + // std::decay_t::value_type, init_type>; + + using result_type = xaccumulator_return_type_t, return_type>; + + std::size_t sz = e.size(); + auto result = result_type::from_shape({sz}); + + if (sz != std::size_t(0)) + { + auto it = e.template begin(); + result.storage()[0] = xt::get<1>(f)(*it); + ++it; + + for (std::size_t idx = 0; it != e.template end(); ++it) + { + result.storage()[idx + 1] = xt::get<0>(f)(result.storage()[idx], *it); + ++idx; + } + } + return result; + } + } + + /** + * Accumulate and flatten array + * **NOTE** This function is not lazy! + * + * @param f functor to use for accumulation + * @param e xexpression to be accumulated + * @param evaluation_strategy evaluation strategy of the accumulation + * + * @return returns xarray filled with accumulated values + */ + template )> + inline auto accumulate(F&& f, E&& e, EVS evaluation_strategy = EVS()) + { + // Note we need to check is_integral above in order to prohibit EVS = int, and not taking the + // std::size_t overload below! + return detail::accumulator_impl(std::forward(f), std::forward(e), evaluation_strategy); + } + + /** + * Accumulate over axis + * **NOTE** This function is not lazy! + * + * @param f Functor to use for accumulation + * @param e xexpression to accumulate + * @param axis Axis to perform accumulation over + * @param evaluation_strategy evaluation strategy of the accumulation + * + * @return returns xarray filled with accumulated values + */ + template + inline auto accumulate(F&& f, E&& e, std::ptrdiff_t axis, EVS evaluation_strategy = EVS()) + { + std::size_t ax = normalize_axis(e.dimension(), axis); + return detail::accumulator_impl(std::forward(f), std::forward(e), ax, evaluation_strategy); + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xadapt.hpp b/contrib/xtensor/include/xtensor/xadapt.hpp new file mode 100644 index 00000000000..47ddc5b1212 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xadapt.hpp @@ -0,0 +1,921 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_ADAPT_HPP +#define XTENSOR_ADAPT_HPP + +#include +#include +#include +#include + +#include + +#include "xarray.hpp" +#include "xbuffer_adaptor.hpp" +#include "xfixed.hpp" +#include "xtensor.hpp" + +namespace xt +{ + /** + * @defgroup xt_xadapt Adaptors of STL-like containers + */ + + namespace detail + { + template + struct array_size_impl; + + template + struct array_size_impl> + { + static constexpr std::size_t value = N; + }; + + template + using array_size = array_size_impl>; + + template + struct default_allocator_for_ptr + { + using type = std::allocator>>>; + }; + + template + using default_allocator_for_ptr_t = typename default_allocator_for_ptr

::type; + + template + using not_an_array = xtl::negation>; + + template + using not_a_pointer = xtl::negation>; + + template + using not_a_layout = xtl::negation>; + } + +#ifndef IN_DOXYGEN + + /************************** + * xarray_adaptor builder * + **************************/ + + /** + * Constructs an xarray_adaptor of the given stl-like container, + * with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class C, + class SC, + XTL_REQUIRES(detail::not_an_array>, detail::not_a_pointer)> + inline xarray_adaptor, L, std::decay_t> + adapt(C&& container, const SC& shape, layout_type l = L) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + using return_type = xarray_adaptor, L, std::decay_t>; + return return_type(std::forward(container), shape, l); + } + + /** + * Constructs an non-owning xarray_adaptor from a pointer with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the container to adapt + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class C, + class SC, + XTL_REQUIRES(detail::not_an_array>, std::is_pointer>)> + inline auto adapt(C&& pointer, const SC& shape, layout_type l = L) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + using buffer_type = xbuffer_adaptor>; + using return_type = xarray_adaptor>; + std::size_t size = compute_size(shape); + return return_type(buffer_type(pointer, size), shape, l); + } + + /** + * Constructs an xarray_adaptor of the given stl-like container, + * with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param shape the shape of the xarray_adaptor + * @param strides the strides of the xarray_adaptor + */ + template < + class C, + class SC, + class SS, + XTL_REQUIRES(detail::not_an_array>, detail::not_a_layout>)> + inline xarray_adaptor, layout_type::dynamic, std::decay_t> + adapt(C&& container, SC&& shape, SS&& strides) + { + static_assert(!xtl::is_integral>::value, "shape cannot be a integer"); + using return_type = xarray_adaptor, layout_type::dynamic, std::decay_t>; + return return_type( + std::forward(container), + xtl::forward_sequence(shape), + xtl::forward_sequence(strides) + ); + } + + /** + * Constructs an xarray_adaptor of the given dynamically allocated C array, + * with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class P, + class O, + class SC, + class A = detail::default_allocator_for_ptr_t

, + XTL_REQUIRES(detail::not_an_array>)> + inline xarray_adaptor, O, A>, L, SC> adapt( + P&& pointer, + typename A::size_type size, + O ownership, + const SC& shape, + layout_type l = L, + const A& alloc = A() + ) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + (void) ownership; + using buffer_type = xbuffer_adaptor, O, A>; + using return_type = xarray_adaptor; + buffer_type buf(std::forward

(pointer), size, alloc); + return return_type(std::move(buf), shape, l); + } + + /** + * Constructs an xarray_adaptor of the given dynamically allocated C array, + * with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the xarray_adaptor + * @param strides the strides of the xarray_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template < + class P, + class O, + class SC, + class SS, + class A = detail::default_allocator_for_ptr_t

, + XTL_REQUIRES(detail::not_an_array>, detail::not_a_layout>)> + inline xarray_adaptor, O, A>, layout_type::dynamic, std::decay_t> + adapt(P&& pointer, typename A::size_type size, O ownership, SC&& shape, SS&& strides, const A& alloc = A()) + { + static_assert(!xtl::is_integral>::value, "shape cannot be a integer"); + (void) ownership; + using buffer_type = xbuffer_adaptor, O, A>; + using return_type = xarray_adaptor>; + buffer_type buf(std::forward

(pointer), size, alloc); + return return_type( + std::move(buf), + xtl::forward_sequence(shape), + xtl::forward_sequence(strides) + ); + } + + /** + * Constructs an xarray_adaptor of the given C array allocated on the stack, with the + * specified shape and layout. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class T, + std::size_t N, + class SC, + XTL_REQUIRES(detail::not_an_array>)> + inline auto adapt(T (&c_array)[N], const SC& shape, layout_type l = L) + { + return adapt(&c_array[0], N, xt::no_ownership(), shape, l); + } + + /** + * Constructs an xarray_adaptor of the given C array allocated on the stack, with the + * specified shape and stirdes. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the xarray_adaptor + * @param strides the strides of the xarray_adaptor + */ + template < + class T, + std::size_t N, + class SC, + class SS, + XTL_REQUIRES(detail::not_an_array>, detail::not_a_layout>)> + inline auto adapt(T (&c_array)[N], SC&& shape, SS&& strides) + { + return adapt(&c_array[0], N, xt::no_ownership(), std::forward(shape), std::forward(strides)); + } + + /*************************** + * xtensor_adaptor builder * + ***************************/ + + /** + * Constructs a 1-D xtensor_adaptor of the given stl-like container, + * with the specified layout_type. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param l the layout_type of the xtensor_adaptor + */ + template + inline xtensor_adaptor adapt(C&& container, layout_type l = L) + { + const std::array::size_type, 1> shape{container.size()}; + using return_type = xtensor_adaptor, 1, L>; + return return_type(std::forward(container), shape, l); + } + + /** + * Constructs an xtensor_adaptor of the given stl-like container, + * with the specified shape and layout_type. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param shape the shape of the xtensor_adaptor + * @param l the layout_type of the xtensor_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class C, + class SC, + XTL_REQUIRES(detail::is_array>, detail::not_a_pointer)> + inline xtensor_adaptor::value, L> + adapt(C&& container, const SC& shape, layout_type l = L) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + constexpr std::size_t N = detail::array_size::value; + using return_type = xtensor_adaptor, N, L>; + return return_type(std::forward(container), shape, l); + } + + /** + * Constructs an non-owning xtensor_adaptor from a pointer with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to adapt + * @param shape the shape of the xtensor_adaptor + * @param l the layout_type of the xtensor_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class C, + class SC, + XTL_REQUIRES(detail::is_array>, std::is_pointer>)> + inline auto adapt(C&& pointer, const SC& shape, layout_type l = L) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + using buffer_type = xbuffer_adaptor>; + constexpr std::size_t N = detail::array_size::value; + using return_type = xtensor_adaptor; + return return_type(buffer_type(pointer, compute_size(shape)), shape, l); + } + + /** + * Constructs an xtensor_adaptor of the given stl-like container, + * with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param shape the shape of the xtensor_adaptor + * @param strides the strides of the xtensor_adaptor + */ + template < + class C, + class SC, + class SS, + XTL_REQUIRES(detail::is_array>, detail::not_a_layout>)> + inline xtensor_adaptor::value, layout_type::dynamic> + adapt(C&& container, SC&& shape, SS&& strides) + { + static_assert(!xtl::is_integral>::value, "shape cannot be a integer"); + constexpr std::size_t N = detail::array_size::value; + using return_type = xtensor_adaptor, N, layout_type::dynamic>; + return return_type( + std::forward(container), + xtl::forward_sequence(shape), + xtl::forward_sequence(strides) + ); + } + + /** + * Constructs a 1-D xtensor_adaptor of the given dynamically allocated C array, + * with the specified layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param l the layout_type of the xtensor_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template > + inline xtensor_adaptor, O, A>, 1, L> + adapt(P&& pointer, typename A::size_type size, O ownership, layout_type l = L, const A& alloc = A()) + { + (void) ownership; + using buffer_type = xbuffer_adaptor, O, A>; + using return_type = xtensor_adaptor; + buffer_type buf(std::forward

(pointer), size, alloc); + const std::array shape{size}; + return return_type(std::move(buf), shape, l); + } + + /** + * Constructs an xtensor_adaptor of the given dynamically allocated C array, + * with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the xtensor_adaptor + * @param l the layout_type of the xtensor_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class P, + class O, + class SC, + class A = detail::default_allocator_for_ptr_t

, + XTL_REQUIRES(detail::is_array>)> + inline xtensor_adaptor, O, A>, detail::array_size::value, L> + adapt( + P&& pointer, + typename A::size_type size, + O ownership, + const SC& shape, + layout_type l = L, + const A& alloc = A() + ) + { + static_assert(!xtl::is_integral::value, "shape cannot be a integer"); + (void) ownership; + using buffer_type = xbuffer_adaptor, O, A>; + constexpr std::size_t N = detail::array_size::value; + using return_type = xtensor_adaptor; + buffer_type buf(std::forward

(pointer), size, alloc); + return return_type(std::move(buf), shape, l); + } + + /** + * Constructs an xtensor_adaptor of the given dynamically allocated C array, + * with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the xtensor_adaptor + * @param strides the strides of the xtensor_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template < + class P, + class O, + class SC, + class SS, + class A = detail::default_allocator_for_ptr_t

, + XTL_REQUIRES(detail::is_array>, detail::not_a_layout>)> + inline xtensor_adaptor, O, A>, detail::array_size::value, layout_type::dynamic> + adapt(P&& pointer, typename A::size_type size, O ownership, SC&& shape, SS&& strides, const A& alloc = A()) + { + static_assert(!xtl::is_integral>::value, "shape cannot be a integer"); + (void) ownership; + using buffer_type = xbuffer_adaptor, O, A>; + constexpr std::size_t N = detail::array_size::value; + using return_type = xtensor_adaptor; + buffer_type buf(std::forward

(pointer), size, alloc); + return return_type( + std::move(buf), + xtl::forward_sequence(shape), + xtl::forward_sequence(strides) + ); + } + + /** + * Constructs an xtensor_adaptor of the given C array allocated on the stack, with the + * specified shape and layout. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class T, + std::size_t N, + class SC, + XTL_REQUIRES(detail::is_array>)> + inline auto adapt(T (&c_array)[N], const SC& shape, layout_type l = L) + { + return adapt(&c_array[0], N, xt::no_ownership(), shape, l); + } + + /** + * Constructs an xtensor_adaptor of the given C array allocated on the stack, with the + * specified shape and strides. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the xarray_adaptor + * @param strides the strides of the xarray_adaptor + */ + template < + class T, + std::size_t N, + class SC, + class SS, + XTL_REQUIRES(detail::is_array>, detail::not_a_layout>)> + inline auto adapt(T (&c_array)[N], SC&& shape, SS&& strides) + { + return adapt(&c_array[0], N, xt::no_ownership(), std::forward(shape), std::forward(strides)); + } + + /** + * Constructs an non-owning xtensor_fixed_adaptor from a pointer with the + * specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to adapt + * @param shape the shape of the xtensor_fixed_adaptor + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class C, + std::size_t... X, + XTL_REQUIRES(std::is_pointer>)> + inline auto adapt(C&& pointer, const fixed_shape& /*shape*/) + { + using buffer_type = xbuffer_adaptor>; + using return_type = xfixed_adaptor, L>; + return return_type(buffer_type(pointer, detail::fixed_compute_size>::value)); + } + + template + inline auto adapt(C&& ptr, const T (&shape)[N]) + { + using shape_type = std::array; + return adapt(std::forward(ptr), xtl::forward_sequence(shape)); + } + +#else // IN_DOXYGEN + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * from the given stl-like container or pointer, with the specified shape and layout. + * If the adaptor is built from a pointer, it does not take its ownership. + * + * @ingroup xt_xadapt + * @param container the container or pointer to adapt + * @param shape the shape of the adaptor + * @param l the layout_type of the adaptor + */ + template + inline auto adapt(C&& container, const SC& shape, layout_type l = L); + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * from the given stl-like container with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param shape the shape of the adaptor + * @param strides the strides of the adaptor + */ + template + inline auto adapt(C&& container, SC&& shape, SS&& strides); + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * of the given dynamically allocated C array, with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the adaptor + * @param l the layout_type of the adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template > + inline auto adapt( + P&& pointer, + typename A::size_type size, + O ownership, + const SC& shape, + layout_type l = L, + const A& alloc = A() + ); + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * of the given dynamically allocated C array, with the specified shape and strides. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param shape the shape of the adaptor + * @param strides the strides of the adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template > + inline auto + adapt(P&& pointer, typename A::size_type size, O ownership, SC&& shape, SS&& strides, const A& alloc = A()); + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * of the given C array allocated on the stack, with the specified shape and layout. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the adaptor + * @param l the layout_type of the adaptor + */ + template + inline auto adapt(T (&c_array)[N], const SC& shape, layout_type l = L); + + /** + * Constructs: + * - an xarray_adaptor if SC is not an array type + * - an xtensor_adaptor if SC is an array type + * + * of the given C array allocated on the stack, with the + * specified shape and strides. + * + * @ingroup xt_xadapt + * @param c_array the C array allocated on the stack + * @param shape the shape of the adaptor + * @param strides the strides of the adaptor + */ + template + inline auto adapt(T (&c_array)[N], SC&& shape, SS&& strides); + + /** + * Constructs an non-owning xtensor_fixed_adaptor from a pointer with the + * specified shape and layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to adapt + * @param shape the shape of the xtensor_fixed_adaptor + */ + template + inline auto adapt(C&& pointer, const fixed_shape& /*shape*/); + + /** + * Constructs a 1-D xtensor_adaptor of the given stl-like container, + * with the specified layout_type. + * + * @ingroup xt_xadapt + * @param container the container to adapt + * @param l the layout_type of the xtensor_adaptor + */ + template + inline xtensor_adaptor adapt(C&& container, layout_type l = L); + + /** + * Constructs a 1-D xtensor_adaptor of the given dynamically allocated C array, + * with the specified layout. + * + * @ingroup xt_xadapt + * @param pointer the pointer to the beginning of the dynamic array + * @param size the size of the dynamic array + * @param ownership indicates whether the adaptor takes ownership of the array. + * Possible values are ``no_ownership()`` or ``acquire_ownership()`` + * @param l the layout_type of the xtensor_adaptor + * @param alloc the allocator used for allocating / deallocating the dynamic array + */ + template > + inline xtensor_adaptor, O, A>, 1, L> + adapt(P&& pointer, typename A::size_type size, O ownership, layout_type l = L, const A& alloc = A()); + +#endif // IN_DOXYGEN + + /***************************** + * smart_ptr adapter builder * + *****************************/ + + /** + * Adapt a smart pointer to a typed memory block (unique_ptr or shared_ptr) + * + * \code{.cpp} + * #include + * #include + * + * std::shared_ptr sptr(new double[8], std::default_delete()); + * sptr.get()[2] = 321.; + * std::vector shape = {4, 2}; + * auto xptr = adapt_smart_ptr(sptr, shape); + * xptr(1, 3) = 123.; + * std::cout << xptr; + * \endcode + * + * @ingroup xt_xadapt + * @param smart_ptr a smart pointer to a memory block of T[] + * @param shape The desired shape + * @param l The desired memory layout + * + * @return xarray_adaptor for memory + */ + template >)> + auto adapt_smart_ptr(P&& smart_ptr, const SC& shape, layout_type l = L) + { + using buffer_adaptor = xbuffer_adaptor>; + return xarray_adaptor>( + buffer_adaptor(smart_ptr.get(), compute_size(shape), std::forward

(smart_ptr)), + shape, + l + ); + } + + /** + * Adapt a smart pointer (shared_ptr or unique_ptr) + * + * This function allows to automatically adapt a shared or unique pointer to + * a given shape and operate naturally on it. Memory will be automatically + * handled by the smart pointer implementation. + * + * \code{.cpp} + * #include + * #include + * + * struct Buffer { + * Buffer(std::vector& buf) : m_buf(buf) {} + * ~Buffer() { std::cout << "deleted" << std::endl; } + * std::vector m_buf; + * }; + * + * auto data = std::vector{1,2,3,4,5,6,7,8}; + * auto shared_buf = std::make_shared(data); + * auto unique_buf = std::make_unique(data); + * + * std::cout << shared_buf.use_count() << std::endl; + * { + * std::vector shape = {2, 4}; + * auto obj = adapt_smart_ptr(shared_buf.get()->m_buf.data(), + * shape, shared_buf); + * // Use count increased to 2 + * std::cout << shared_buf.use_count() << std::endl; + * std::cout << obj << std::endl; + * } + * // Use count reset to 1 + * std::cout << shared_buf.use_count() << std::endl; + * + * { + * std::vector shape = {2, 4}; + * auto obj = adapt_smart_ptr(unique_buf.get()->m_buf.data(), + * shape, std::move(unique_buf)); + * std::cout << obj << std::endl; + * } + * \endcode + * + * @ingroup xt_xadapt + * @param data_ptr A pointer to a typed data block (e.g. double*) + * @param shape The desired shape + * @param smart_ptr A smart pointer to move or copy, in order to manage memory + * @param l The desired memory layout + * + * @return xarray_adaptor on the memory + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class P, + class SC, + class D, + XTL_REQUIRES(detail::not_an_array>, detail::not_a_layout>)> + auto adapt_smart_ptr(P&& data_ptr, const SC& shape, D&& smart_ptr, layout_type l = L) + { + using buffer_adaptor = xbuffer_adaptor>; + + return xarray_adaptor>( + buffer_adaptor(data_ptr, compute_size(shape), std::forward(smart_ptr)), + shape, + l + ); + } + + /** + * Adapt a smart pointer to a typed memory block (unique_ptr or shared_ptr) + * + * \code{.cpp} + * #include + * #include + * + * std::shared_ptr sptr(new double[8], std::default_delete()); + * sptr.get()[2] = 321.; + * auto xptr = adapt_smart_ptr(sptr, {4, 2}); + * xptr(1, 3) = 123.; + * std::cout << xptr; + * \endcode + * + * @ingroup xt_xadapt + * @param smart_ptr a smart pointer to a memory block of T[] + * @param shape The desired shape + * @param l The desired memory layout + * + * @return xtensor_adaptor for memory + */ + template + auto adapt_smart_ptr(P&& smart_ptr, const I (&shape)[N], layout_type l = L) + { + using buffer_adaptor = xbuffer_adaptor>; + std::array fshape = xtl::forward_sequence, decltype(shape)>( + shape + ); + return xtensor_adaptor( + buffer_adaptor(smart_ptr.get(), compute_size(fshape), std::forward

(smart_ptr)), + std::move(fshape), + l + ); + } + + /** + * Adapt a smart pointer (shared_ptr or unique_ptr) + * + * This function allows to automatically adapt a shared or unique pointer to + * a given shape and operate naturally on it. Memory will be automatically + * handled by the smart pointer implementation. + * + * \code{.cpp} + * #include + * #include + * + * struct Buffer { + * Buffer(std::vector& buf) : m_buf(buf) {} + * ~Buffer() { std::cout << "deleted" << std::endl; } + * std::vector m_buf; + * }; + * + * auto data = std::vector{1,2,3,4,5,6,7,8}; + * auto shared_buf = std::make_shared(data); + * auto unique_buf = std::make_unique(data); + * + * std::cout << shared_buf.use_count() << std::endl; + * { + * auto obj = adapt_smart_ptr(shared_buf.get()->m_buf.data(), + * {2, 4}, shared_buf); + * // Use count increased to 2 + * std::cout << shared_buf.use_count() << std::endl; + * std::cout << obj << std::endl; + * } + * // Use count reset to 1 + * std::cout << shared_buf.use_count() << std::endl; + * + * { + * auto obj = adapt_smart_ptr(unique_buf.get()->m_buf.data(), + * {2, 4}, std::move(unique_buf)); + * std::cout << obj << std::endl; + * } + * \endcode + * + * @ingroup xt_xadapt + * @param data_ptr A pointer to a typed data block (e.g. double*) + * @param shape The desired shape + * @param smart_ptr A smart pointer to move or copy, in order to manage memory + * @param l The desired memory layout + * + * @return xtensor_adaptor on the memory + */ + template < + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class P, + class I, + std::size_t N, + class D, + XTL_REQUIRES(detail::not_a_layout>)> + auto adapt_smart_ptr(P&& data_ptr, const I (&shape)[N], D&& smart_ptr, layout_type l = L) + { + using buffer_adaptor = xbuffer_adaptor>; + std::array fshape = xtl::forward_sequence, decltype(shape)>( + shape + ); + + return xtensor_adaptor( + buffer_adaptor(data_ptr, compute_size(fshape), std::forward(smart_ptr)), + std::move(fshape), + l + ); + } + + /** + * @brief xtensor adaptor for a pointer. + * + * Construct for example with: + * + * \code{.cpp} + * #include + * + * std::array shape = {2, 2}; + * std::vector data = {1, 2, 3, 4}; + * + * xt::xtensor_pointer a = xt::adapt(data.data(), 4, xt::no_ownership(), shape); + * \endcode + * + * @ingroup xt_xadapt + * @tparam T The data type (e.g. ``double``). + * @tparam N The number of dimensions. + * @tparam L The xt::layout_type() of the xtensor. + */ + template + using xtensor_pointer = xtensor_adaptor< + xbuffer_adaptor, xt::no_ownership, detail::default_allocator_for_ptr_t>, + N, + L>; + + /** + * @brief xarray adaptor for a pointer. + * + * Construct for example with: + * + * \code{.cpp} + * #include + * + * std::vector data(4, 0); + * xt::svector shape({2, 2}); + * + * xt::xarray_pointer a = xt::adapt(data.data(), data.size(), xt::no_ownership(), shape); + * \endcode + * + * @ingroup xt_xadapt + * @tparam T The data type (e.g. ``double``). + * @tparam L The xt::layout_type() of the xarray. + * @tparam SC The shape container type (e.g. ``xt::svector``). Default matches + * xt::adapt(P&&, typename A::size_type, O, const SC&, layout_type, const A& alloc) + */ + template < + class T, + layout_type L = XTENSOR_DEFAULT_LAYOUT, + class SC = XTENSOR_DEFAULT_SHAPE_CONTAINER(T, std::allocator, std::allocator)> + using xarray_pointer = xarray_adaptor< + xbuffer_adaptor, xt::no_ownership, detail::default_allocator_for_ptr_t>, + L, + SC>; +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xarray.hpp b/contrib/xtensor/include/xtensor/xarray.hpp new file mode 100644 index 00000000000..996daeb9d18 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xarray.hpp @@ -0,0 +1,667 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_ARRAY_HPP +#define XTENSOR_ARRAY_HPP + +#include +#include +#include + +#include + +#include "xbuffer_adaptor.hpp" +#include "xcontainer.hpp" +#include "xsemantic.hpp" + +namespace xt +{ + + /******************************** + * xarray_container declaration * + ********************************/ + + namespace extension + { + template + struct xarray_container_base; + + template + struct xarray_container_base + { + using type = xtensor_empty_base; + }; + + template + using xarray_container_base_t = typename xarray_container_base::type; + } + + template + struct xcontainer_inner_types> + { + using storage_type = EC; + using reference = inner_reference_t; + using const_reference = typename storage_type::const_reference; + using size_type = typename storage_type::size_type; + using shape_type = SC; + using strides_type = get_strides_t; + using backstrides_type = get_strides_t; + using inner_shape_type = shape_type; + using inner_strides_type = strides_type; + using inner_backstrides_type = backstrides_type; + using temporary_type = xarray_container; + static constexpr layout_type layout = L; + }; + + template + struct xiterable_inner_types> + : xcontainer_iterable_types> + { + }; + + /** + * @class xarray_container + * @brief Dense multidimensional container with tensor semantic. + * + * The xarray_container class implements a dense multidimensional container + * with tensor semantic. + * + * @tparam EC The type of the container holding the elements. + * @tparam L The layout_type of the container. + * @tparam SC The type of the containers holding the shape and the strides. + * @tparam Tag The expression tag. + * @sa xarray, xstrided_container, xcontainer + */ + template + class xarray_container : public xstrided_container>, + public xcontainer_semantic>, + public extension::xarray_container_base_t + { + public: + + using self_type = xarray_container; + using base_type = xstrided_container; + using semantic_base = xcontainer_semantic; + using extension_base = extension::xarray_container_base_t; + using storage_type = typename base_type::storage_type; + using allocator_type = typename base_type::allocator_type; + using value_type = typename base_type::value_type; + using reference = typename base_type::reference; + using const_reference = typename base_type::const_reference; + using pointer = typename base_type::pointer; + using const_pointer = typename base_type::const_pointer; + using shape_type = typename base_type::shape_type; + using inner_shape_type = typename base_type::inner_shape_type; + using strides_type = typename base_type::strides_type; + using backstrides_type = typename base_type::backstrides_type; + using inner_strides_type = typename base_type::inner_strides_type; + using inner_backstrides_type = typename base_type::inner_backstrides_type; + using temporary_type = typename semantic_base::temporary_type; + using expression_tag = Tag; + static constexpr std::size_t rank = SIZE_MAX; + + xarray_container(); + explicit xarray_container(const shape_type& shape, layout_type l = L); + explicit xarray_container(const shape_type& shape, const_reference value, layout_type l = L); + explicit xarray_container(const shape_type& shape, const strides_type& strides); + explicit xarray_container(const shape_type& shape, const strides_type& strides, const_reference value); + explicit xarray_container(storage_type&& storage, inner_shape_type&& shape, inner_strides_type&& strides); + + xarray_container(const value_type& t); + xarray_container(nested_initializer_list_t t); + xarray_container(nested_initializer_list_t t); + xarray_container(nested_initializer_list_t t); + xarray_container(nested_initializer_list_t t); + xarray_container(nested_initializer_list_t t); + + template + static xarray_container from_shape(S&& s); + + ~xarray_container() = default; + + xarray_container(const xarray_container&) = default; + xarray_container& operator=(const xarray_container&) = default; + + xarray_container(xarray_container&&) = default; + xarray_container& operator=(xarray_container&&) = default; + + template + explicit xarray_container(xtensor_container&& rhs); + template + xarray_container& operator=(xtensor_container&& rhs); + + template + xarray_container(const xexpression& e); + + template + xarray_container& operator=(const xexpression& e); + + private: + + storage_type m_storage; + + storage_type& storage_impl() noexcept; + const storage_type& storage_impl() const noexcept; + + friend class xcontainer>; + }; + + /****************************** + * xarray_adaptor declaration * + ******************************/ + + namespace extension + { + template + struct xarray_adaptor_base; + + template + struct xarray_adaptor_base + { + using type = xtensor_empty_base; + }; + + template + using xarray_adaptor_base_t = typename xarray_adaptor_base::type; + } + + template + struct xcontainer_inner_types> + { + using storage_type = std::remove_reference_t; + using reference = inner_reference_t; + using const_reference = typename storage_type::const_reference; + using size_type = typename storage_type::size_type; + using shape_type = SC; + using strides_type = get_strides_t; + using backstrides_type = get_strides_t; + using inner_shape_type = shape_type; + using inner_strides_type = strides_type; + using inner_backstrides_type = backstrides_type; + using temporary_type = xarray_container, L, SC, Tag>; + static constexpr layout_type layout = L; + }; + + template + struct xiterable_inner_types> + : xcontainer_iterable_types> + { + }; + + /** + * @class xarray_adaptor + * @brief Dense multidimensional container adaptor with + * tensor semantic. + * + * The xarray_adaptor class implements a dense multidimensional + * container adaptor with tensor semantic. It is used to provide + * a multidimensional container semantic and a tensor semantic to + * stl-like containers. + * + * @tparam EC The closure for the container type to adapt. + * @tparam L The layout_type of the adaptor. + * @tparam SC The type of the containers holding the shape and the strides. + * @tparam Tag The expression tag. + * @sa xstrided_container, xcontainer + */ + template + class xarray_adaptor : public xstrided_container>, + public xcontainer_semantic>, + public extension::xarray_adaptor_base_t + { + public: + + using container_closure_type = EC; + + using self_type = xarray_adaptor; + using base_type = xstrided_container; + using semantic_base = xcontainer_semantic; + using extension_base = extension::xarray_adaptor_base_t; + using storage_type = typename base_type::storage_type; + using allocator_type = typename base_type::allocator_type; + using shape_type = typename base_type::shape_type; + using strides_type = typename base_type::strides_type; + using backstrides_type = typename base_type::backstrides_type; + using temporary_type = typename semantic_base::temporary_type; + using expression_tag = Tag; + static constexpr std::size_t rank = SIZE_MAX; + + xarray_adaptor(storage_type&& storage); + xarray_adaptor(const storage_type& storage); + + template + xarray_adaptor(D&& storage, const shape_type& shape, layout_type l = L); + + template + xarray_adaptor(D&& storage, const shape_type& shape, const strides_type& strides); + + ~xarray_adaptor() = default; + + xarray_adaptor(const xarray_adaptor&) = default; + xarray_adaptor& operator=(const xarray_adaptor&); + + xarray_adaptor(xarray_adaptor&&) = default; + xarray_adaptor& operator=(xarray_adaptor&&); + xarray_adaptor& operator=(temporary_type&&); + + template + xarray_adaptor& operator=(const xexpression& e); + + template + void reset_buffer(P&& pointer, S&& size); + + private: + + container_closure_type m_storage; + + storage_type& storage_impl() noexcept; + const storage_type& storage_impl() const noexcept; + + friend class xcontainer>; + }; + + /*********************************** + * xarray_container implementation * + ***********************************/ + + /** + * @name Constructors + */ + //@{ + /** + * Allocates an uninitialized xarray_container that holds 0 element. + */ + template + inline xarray_container::xarray_container() + : base_type() + , m_storage(1, value_type()) + { + } + + /** + * Allocates an uninitialized xarray_container with the specified shape and + * layout_type. + * @param shape the shape of the xarray_container + * @param l the layout_type of the xarray_container + */ + template + inline xarray_container::xarray_container(const shape_type& shape, layout_type l) + : base_type() + { + base_type::resize(shape, l); + } + + /** + * Allocates an xarray_container with the specified shape and layout_type. Elements + * are initialized to the specified value. + * @param shape the shape of the xarray_container + * @param value the value of the elements + * @param l the layout_type of the xarray_container + */ + template + inline xarray_container::xarray_container( + const shape_type& shape, + const_reference value, + layout_type l + ) + : base_type() + { + base_type::resize(shape, l); + std::fill(m_storage.begin(), m_storage.end(), value); + } + + /** + * Allocates an uninitialized xarray_container with the specified shape and strides. + * @param shape the shape of the xarray_container + * @param strides the strides of the xarray_container + */ + template + inline xarray_container::xarray_container(const shape_type& shape, const strides_type& strides) + : base_type() + { + base_type::resize(shape, strides); + } + + /** + * Allocates an uninitialized xarray_container with the specified shape and strides. + * Elements are initialized to the specified value. + * @param shape the shape of the xarray_container + * @param strides the strides of the xarray_container + * @param value the value of the elements + */ + template + inline xarray_container::xarray_container( + const shape_type& shape, + const strides_type& strides, + const_reference value + ) + : base_type() + { + base_type::resize(shape, strides); + std::fill(m_storage.begin(), m_storage.end(), value); + } + + /** + * Allocates an xarray_container that holds a single element initialized to the + * specified value. + * @param t the value of the element + */ + template + inline xarray_container::xarray_container(const value_type& t) + : base_type() + { + base_type::resize(xt::shape(t), true); + nested_copy(m_storage.begin(), t); + } + + /** + * Allocates an xarray_container by moving specified data, shape and strides + * + * @param storage the data for the xarray_container + * @param shape the shape of the xarray_container + * @param strides the strides of the xarray_container + */ + template + inline xarray_container::xarray_container( + storage_type&& storage, + inner_shape_type&& shape, + inner_strides_type&& strides + ) + : base_type(std::move(shape), std::move(strides)) + , m_storage(std::move(storage)) + { + } + + //@} + + /** + * @name Constructors from initializer list + */ + //@{ + /** + * Allocates a one-dimensional xarray_container. + * @param t the elements of the xarray_container + */ + template + inline xarray_container::xarray_container(nested_initializer_list_t t) + : base_type() + { + base_type::resize(xt::shape(t)); + constexpr auto tmp = layout_type::row_major; + L == tmp ? nested_copy(m_storage.begin(), t) : nested_copy(this->template begin(), t); + } + + /** + * Allocates a two-dimensional xarray_container. + * @param t the elements of the xarray_container + */ + template + inline xarray_container::xarray_container(nested_initializer_list_t t) + : base_type() + { + base_type::resize(xt::shape(t)); + constexpr auto tmp = layout_type::row_major; + L == tmp ? nested_copy(m_storage.begin(), t) : nested_copy(this->template begin(), t); + } + + /** + * Allocates a three-dimensional xarray_container. + * @param t the elements of the xarray_container + */ + template + inline xarray_container::xarray_container(nested_initializer_list_t t) + : base_type() + { + base_type::resize(xt::shape(t)); + constexpr auto tmp = layout_type::row_major; + L == tmp ? nested_copy(m_storage.begin(), t) : nested_copy(this->template begin(), t); + } + + /** + * Allocates a four-dimensional xarray_container. + * @param t the elements of the xarray_container + */ + template + inline xarray_container::xarray_container(nested_initializer_list_t t) + : base_type() + { + base_type::resize(xt::shape(t)); + constexpr auto tmp = layout_type::row_major; + L == tmp ? nested_copy(m_storage.begin(), t) : nested_copy(this->template begin(), t); + } + + /** + * Allocates a five-dimensional xarray_container. + * @param t the elements of the xarray_container + */ + template + inline xarray_container::xarray_container(nested_initializer_list_t t) + : base_type() + { + base_type::resize(xt::shape(t)); + constexpr auto tmp = layout_type::row_major; + L == tmp ? nested_copy(m_storage.begin(), t) : nested_copy(this->template begin(), t); + } + + //@} + + /** + * Allocates and returns an xarray_container with the specified shape. + * @param s the shape of the xarray_container + */ + template + template + inline xarray_container xarray_container::from_shape(S&& s) + { + shape_type shape = xtl::forward_sequence(s); + return self_type(shape); + } + + template + template + inline xarray_container::xarray_container(xtensor_container&& rhs) + : base_type( + inner_shape_type(rhs.shape().cbegin(), rhs.shape().cend()), + inner_strides_type(rhs.strides().cbegin(), rhs.strides().cend()), + inner_backstrides_type(rhs.backstrides().cbegin(), rhs.backstrides().cend()), + std::move(rhs.layout()) + ) + , m_storage(std::move(rhs.storage())) + { + } + + template + template + inline xarray_container& + xarray_container::operator=(xtensor_container&& rhs) + { + this->shape_impl().assign(rhs.shape().cbegin(), rhs.shape().cend()); + this->strides_impl().assign(rhs.strides().cbegin(), rhs.strides().cend()); + this->backstrides_impl().assign(rhs.backstrides().cbegin(), rhs.backstrides().cend()); + this->mutable_layout() = rhs.layout(); + m_storage = std::move(rhs.storage()); + return *this; + } + + /** + * @name Extended copy semantic + */ + //@{ + /** + * The extended copy constructor. + */ + template + template + inline xarray_container::xarray_container(const xexpression& e) + : base_type() + { + // Avoids unintialized data because of (m_shape == shape) condition + // in resize (called by assign), which is always true when dimension == 0. + if (e.derived_cast().dimension() == 0) + { + detail::resize_data_container(m_storage, std::size_t(1)); + } + semantic_base::assign(e); + } + + /** + * The extended assignment operator. + */ + template + template + inline auto xarray_container::operator=(const xexpression& e) -> self_type& + { + return semantic_base::operator=(e); + } + + //@} + + template + inline auto xarray_container::storage_impl() noexcept -> storage_type& + { + return m_storage; + } + + template + inline auto xarray_container::storage_impl() const noexcept -> const storage_type& + { + return m_storage; + } + + /****************** + * xarray_adaptor * + ******************/ + + /** + * @name Constructors + */ + //@{ + /** + * Constructs an xarray_adaptor of the given stl-like container. + * @param storage the container to adapt + */ + template + inline xarray_adaptor::xarray_adaptor(storage_type&& storage) + : base_type() + , m_storage(std::move(storage)) + { + } + + /** + * Constructs an xarray_adaptor of the given stl-like container. + * @param storage the container to adapt + */ + template + inline xarray_adaptor::xarray_adaptor(const storage_type& storage) + : base_type() + , m_storage(storage) + { + } + + /** + * Constructs an xarray_adaptor of the given stl-like container, + * with the specified shape and layout_type. + * @param storage the container to adapt + * @param shape the shape of the xarray_adaptor + * @param l the layout_type of the xarray_adaptor + */ + template + template + inline xarray_adaptor::xarray_adaptor(D&& storage, const shape_type& shape, layout_type l) + : base_type() + , m_storage(std::forward(storage)) + { + base_type::resize(shape, l); + } + + /** + * Constructs an xarray_adaptor of the given stl-like container, + * with the specified shape and strides. + * @param storage the container to adapt + * @param shape the shape of the xarray_adaptor + * @param strides the strides of the xarray_adaptor + */ + template + template + inline xarray_adaptor::xarray_adaptor( + D&& storage, + const shape_type& shape, + const strides_type& strides + ) + : base_type() + , m_storage(std::forward(storage)) + { + base_type::resize(shape, strides); + } + + //@} + + template + inline auto xarray_adaptor::operator=(const xarray_adaptor& rhs) -> self_type& + { + base_type::operator=(rhs); + m_storage = rhs.m_storage; + return *this; + } + + template + inline auto xarray_adaptor::operator=(xarray_adaptor&& rhs) -> self_type& + { + base_type::operator=(std::move(rhs)); + m_storage = rhs.m_storage; + return *this; + } + + template + inline auto xarray_adaptor::operator=(temporary_type&& rhs) -> self_type& + { + base_type::shape_impl() = std::move(const_cast(rhs.shape())); + base_type::strides_impl() = std::move(const_cast(rhs.strides())); + base_type::backstrides_impl() = std::move(const_cast(rhs.backstrides())); + m_storage = std::move(rhs.storage()); + return *this; + } + + /** + * @name Extended copy semantic + */ + //@{ + /** + * The extended assignment operator. + */ + template + template + inline auto xarray_adaptor::operator=(const xexpression& e) -> self_type& + { + return semantic_base::operator=(e); + } + + //@} + + template + inline auto xarray_adaptor::storage_impl() noexcept -> storage_type& + { + return m_storage; + } + + template + inline auto xarray_adaptor::storage_impl() const noexcept -> const storage_type& + { + return m_storage; + } + + template + template + inline void xarray_adaptor::reset_buffer(P&& pointer, S&& size) + { + return m_storage.reset_data(std::forward

(pointer), std::forward(size)); + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xassign.hpp b/contrib/xtensor/include/xtensor/xassign.hpp new file mode 100644 index 00000000000..d30dd6dc9a0 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xassign.hpp @@ -0,0 +1,1367 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_ASSIGN_HPP +#define XTENSOR_ASSIGN_HPP + +#include +#include +#include +#include + +#include +#include + +#include "xexpression.hpp" +#include "xfunction.hpp" +#include "xiterator.hpp" +#include "xstrides.hpp" +#include "xtensor_config.hpp" +#include "xtensor_forward.hpp" +#include "xutils.hpp" + +#if defined(XTENSOR_USE_TBB) +#include +#endif + +namespace xt +{ + + /******************** + * Assign functions * + ********************/ + + template + void assign_data(xexpression& e1, const xexpression& e2, bool trivial); + + template + void assign_xexpression(xexpression& e1, const xexpression& e2); + + template + void computed_assign(xexpression& e1, const xexpression& e2); + + template + void scalar_computed_assign(xexpression& e1, const E2& e2, F&& f); + + template + void assert_compatible_shape(const xexpression& e1, const xexpression& e2); + + template + void strided_assign(E1& e1, const E2& e2, std::false_type /*disable*/); + + template + void strided_assign(E1& e1, const E2& e2, std::true_type /*enable*/); + + /************************ + * xexpression_assigner * + ************************/ + + template + class xexpression_assigner_base; + + template <> + class xexpression_assigner_base + { + public: + + template + static void assign_data(xexpression& e1, const xexpression& e2, bool trivial); + }; + + template + class xexpression_assigner : public xexpression_assigner_base + { + public: + + using base_type = xexpression_assigner_base; + + template + static void assign_xexpression(E1& e1, const E2& e2); + + template + static void computed_assign(xexpression& e1, const xexpression& e2); + + template + static void scalar_computed_assign(xexpression& e1, const E2& e2, F&& f); + + template + static void assert_compatible_shape(const xexpression& e1, const xexpression& e2); + + private: + + template + static bool resize(E1& e1, const E2& e2); + + template + static bool resize(E1& e1, const xfunction& e2); + }; + + /******************** + * stepper_assigner * + ********************/ + + template + class stepper_assigner + { + public: + + using lhs_iterator = typename E1::stepper; + using rhs_iterator = typename E2::const_stepper; + using shape_type = typename E1::shape_type; + using index_type = xindex_type_t; + using size_type = typename lhs_iterator::size_type; + using difference_type = typename lhs_iterator::difference_type; + + stepper_assigner(E1& e1, const E2& e2); + + void run(); + + void step(size_type i); + void step(size_type i, size_type n); + void reset(size_type i); + + void to_end(layout_type); + + private: + + E1& m_e1; + + lhs_iterator m_lhs; + rhs_iterator m_rhs; + + index_type m_index; + }; + + /******************* + * linear_assigner * + *******************/ + + template + class linear_assigner + { + public: + + template + static void run(E1& e1, const E2& e2); + }; + + template <> + class linear_assigner + { + public: + + template + static void run(E1& e1, const E2& e2); + + private: + + template + static void run_impl(E1& e1, const E2& e2, std::true_type); + + template + static void run_impl(E1& e1, const E2& e2, std::false_type); + }; + + /************************* + * strided_loop_assigner * + *************************/ + + namespace strided_assign_detail + { + struct loop_sizes_t + { + bool can_do_strided_assign; + bool is_row_major; + std::size_t inner_loop_size; + std::size_t outer_loop_size; + std::size_t cut; + std::size_t dimension; + }; + } + + template + class strided_loop_assigner + { + public: + + using loop_sizes_t = strided_assign_detail::loop_sizes_t; + // is_row_major, inner_loop_size, outer_loop_size, cut + template + static void run(E1& e1, const E2& e2, const loop_sizes_t& loop_sizes); + template + static loop_sizes_t get_loop_sizes(E1& e1, const E2& e2); + template + static void run(E1& e1, const E2& e2); + }; + + /*********************************** + * Assign functions implementation * + ***********************************/ + + template + inline void assign_data(xexpression& e1, const xexpression& e2, bool trivial) + { + using tag = xexpression_tag_t; + xexpression_assigner::assign_data(e1, e2, trivial); + } + + template + inline void assign_xexpression(xexpression& e1, const xexpression& e2) + { + xtl::mpl::static_if::value>( + [&](auto self) + { + self(e2).derived_cast().assign_to(e1); + }, + /*else*/ + [&](auto /*self*/) + { + using tag = xexpression_tag_t; + xexpression_assigner::assign_xexpression(e1, e2); + } + ); + } + + template + inline void computed_assign(xexpression& e1, const xexpression& e2) + { + using tag = xexpression_tag_t; + xexpression_assigner::computed_assign(e1, e2); + } + + template + inline void scalar_computed_assign(xexpression& e1, const E2& e2, F&& f) + { + using tag = xexpression_tag_t; + xexpression_assigner::scalar_computed_assign(e1, e2, std::forward(f)); + } + + template + inline void assert_compatible_shape(const xexpression& e1, const xexpression& e2) + { + using tag = xexpression_tag_t; + xexpression_assigner::assert_compatible_shape(e1, e2); + } + + /*************************************** + * xexpression_assigner implementation * + ***************************************/ + + namespace detail + { + template + constexpr bool linear_static_layout() + { + // A row_major or column_major container with a dimension <= 1 is computed as + // layout any, leading to some performance improvements, for example when + // assigning a col-major vector to a row-major vector etc + return compute_layout( + select_layout::value, + select_layout::value + ) + != layout_type::dynamic; + } + + template + inline auto is_linear_assign(const E1& e1, const E2& e2) + -> std::enable_if_t::value, bool> + { + return (E1::contiguous_layout && E2::contiguous_layout && linear_static_layout()) + || (e1.is_contiguous() && e2.has_linear_assign(e1.strides())); + } + + template + inline auto is_linear_assign(const E1&, const E2&) -> std::enable_if_t::value, bool> + { + return false; + } + + template + inline bool linear_dynamic_layout(const E1& e1, const E2& e2) + { + return e1.is_contiguous() && e2.is_contiguous() + && compute_layout(e1.layout(), e2.layout()) != layout_type::dynamic; + } + + template + struct has_step_leading : std::false_type + { + }; + + template + struct has_step_leading().step_leading())>> : std::true_type + { + }; + + template + struct use_strided_loop + { + static constexpr bool stepper_deref() + { + return std::is_reference::value; + } + + static constexpr bool value = has_strides::value + && has_step_leading::value && stepper_deref(); + }; + + template + struct use_strided_loop> + { + static constexpr bool value = true; + }; + + template + struct use_strided_loop> + { + static constexpr bool value = xtl::conjunction>...>::value; + }; + + /** + * Considering the assignment LHS = RHS, if the requested value type used for + * loading simd from RHS is not complex while LHS value_type is complex, + * the assignment fails. The reason is that SIMD batches of complex values cannot + * be implicitly instantiated from batches of scalar values. + * Making the constructor implicit does not fix the issue since in the end, + * the assignment is done with vec.store(buffer) where vec is a batch of scalars + * and buffer an array of complex. SIMD batches of scalars do not provide overloads + * of store that accept buffer of complex values and that SHOULD NOT CHANGE. + * Load and store overloads must accept SCALAR BUFFERS ONLY. + * Therefore, the solution is to explicitly force the instantiation of complex + * batches in the assignment mechanism. A common situation that triggers this + * issue is: + * xt::xarray rhs = { 1, 2, 3 }; + * xt::xarray> lhs = rhs; + */ + template + struct conditional_promote_to_complex + { + static constexpr bool cond = xtl::is_gen_complex::value && !xtl::is_gen_complex::value; + // Alternative: use std::complex or xcomplex depending on T1 + using type = std::conditional_t; + }; + + template + using conditional_promote_to_complex_t = typename conditional_promote_to_complex::type; + } + + template + class xassign_traits + { + private: + + using e1_value_type = typename E1::value_type; + using e2_value_type = typename E2::value_type; + + template + using is_bool = std::is_same; + + static constexpr bool is_bool_conversion() + { + return is_bool::value && !is_bool::value; + } + + static constexpr bool contiguous_layout() + { + return E1::contiguous_layout && E2::contiguous_layout; + } + + static constexpr bool convertible_types() + { + return std::is_convertible::value && !is_bool_conversion(); + } + + static constexpr bool use_xsimd() + { + return xt_simd::simd_traits::size > 1; + } + + template + static constexpr bool simd_size_impl() + { + return xt_simd::simd_traits::size > 1 || (is_bool::value && use_xsimd()); + } + + static constexpr bool simd_size() + { + return simd_size_impl() && simd_size_impl(); + } + + static constexpr bool simd_interface() + { + return has_simd_interface() + && has_simd_interface(); + } + + public: + + // constexpr methods instead of constexpr data members avoid the need of definitions at namespace + // scope of these data members (since they are odr-used). + + static constexpr bool simd_assign() + { + return convertible_types() && simd_size() && simd_interface(); + } + + static constexpr bool linear_assign(const E1& e1, const E2& e2, bool trivial) + { + return trivial && detail::is_linear_assign(e1, e2); + } + + static constexpr bool strided_assign() + { + return detail::use_strided_loop::value && detail::use_strided_loop::value; + } + + static constexpr bool simd_linear_assign() + { + return contiguous_layout() && simd_assign(); + } + + static constexpr bool simd_strided_assign() + { + return strided_assign() && simd_assign(); + } + + static constexpr bool simd_linear_assign(const E1& e1, const E2& e2) + { + return simd_assign() && detail::linear_dynamic_layout(e1, e2); + } + + using e2_requested_value_type = std:: + conditional_t::value, typename E2::bool_load_type, e2_value_type>; + using requested_value_type = detail::conditional_promote_to_complex_t; + }; + + template + inline void xexpression_assigner_base::assign_data( + xexpression& e1, + const xexpression& e2, + bool trivial + ) + { + E1& de1 = e1.derived_cast(); + const E2& de2 = e2.derived_cast(); + using traits = xassign_traits; + + bool linear_assign = traits::linear_assign(de1, de2, trivial); + constexpr bool simd_assign = traits::simd_assign(); + constexpr bool simd_linear_assign = traits::simd_linear_assign(); + constexpr bool simd_strided_assign = traits::simd_strided_assign(); + if (linear_assign) + { + if (simd_linear_assign || traits::simd_linear_assign(de1, de2)) + { + // Do not use linear_assigner here since it will make the compiler + // instantiate this branch even if the runtime condition is false, resulting + // in compilation error for expressions that do not provide a SIMD interface. + // simd_assign is true if simd_linear_assign() or simd_linear_assign(de1, de2) + // is true. + linear_assigner::run(de1, de2); + } + else + { + linear_assigner::run(de1, de2); + } + } + else if (simd_strided_assign) + { + strided_loop_assigner::run(de1, de2); + } + else + { + stepper_assigner(de1, de2).run(); + } + } + + template + template + inline void xexpression_assigner::assign_xexpression(E1& e1, const E2& e2) + { + bool trivial_broadcast = resize(e1.derived_cast(), e2.derived_cast()); + base_type::assign_data(e1, e2, trivial_broadcast); + } + + template + template + inline void xexpression_assigner::computed_assign(xexpression& e1, const xexpression& e2) + { + using shape_type = typename E1::shape_type; + using comperator_type = std::greater; + + using size_type = typename E1::size_type; + + E1& de1 = e1.derived_cast(); + const E2& de2 = e2.derived_cast(); + + size_type dim2 = de2.dimension(); + shape_type shape = uninitialized_shape(dim2); + + bool trivial_broadcast = de2.broadcast_shape(shape, true); + + auto&& de1_shape = de1.shape(); + if (dim2 > de1.dimension() + || std::lexicographical_compare( + shape.begin(), + shape.end(), + de1_shape.begin(), + de1_shape.end(), + comperator_type() + )) + { + typename E1::temporary_type tmp(shape); + base_type::assign_data(tmp, e2, trivial_broadcast); + de1.assign_temporary(std::move(tmp)); + } + else + { + base_type::assign_data(e1, e2, trivial_broadcast); + } + } + + template + template + inline void xexpression_assigner::scalar_computed_assign(xexpression& e1, const E2& e2, F&& f) + { + E1& d = e1.derived_cast(); + using size_type = typename E1::size_type; + auto dst = d.storage().begin(); + for (size_type i = d.size(); i > 0; --i) + { + *dst = f(*dst, e2); + ++dst; + } + } + + template + template + inline void + xexpression_assigner::assert_compatible_shape(const xexpression& e1, const xexpression& e2) + { + const E1& de1 = e1.derived_cast(); + const E2& de2 = e2.derived_cast(); + if (!broadcastable(de2.shape(), de1.shape())) + { + throw_broadcast_error(de2.shape(), de1.shape()); + } + } + + namespace detail + { + template + struct static_trivial_broadcast; + + template + struct static_trivial_broadcast + { + static constexpr bool value = detail::promote_index::shape_type...>::value; + }; + + template + struct static_trivial_broadcast + { + static constexpr bool value = false; + }; + } + + template + template + inline bool xexpression_assigner::resize(E1& e1, const E2& e2) + { + // If our RHS is not a xfunction, we know that the RHS is at least potentially trivial + // We check the strides of the RHS in detail::is_trivial_broadcast to see if they match up! + // So we can skip a shape copy and a call to broadcast_shape(...) + e1.resize(e2.shape()); + return true; + } + + template + template + inline bool xexpression_assigner::resize(E1& e1, const xfunction& e2) + { + return xtl::mpl::static_if::shape_type>::value>( + [&](auto /*self*/) + { + /* + * If the shape of the xfunction is statically known, we can compute the broadcast triviality + * at compile time plus we can resize right away. + */ + // resize in case LHS is not a fixed size container. If it is, this is a NOP + e1.resize(typename xfunction::shape_type{}); + return detail::static_trivial_broadcast< + detail::is_fixed::shape_type>::value, + CT...>::value; + }, + /* else */ + [&](auto /*self*/) + { + using index_type = xindex_type_t; + using size_type = typename E1::size_type; + size_type size = e2.dimension(); + index_type shape = uninitialized_shape(size); + bool trivial_broadcast = e2.broadcast_shape(shape, true); + e1.resize(std::move(shape)); + return trivial_broadcast; + } + ); + } + + /*********************************** + * stepper_assigner implementation * + ***********************************/ + + template + struct is_narrowing_conversion + { + using argument_type = std::decay_t; + using result_type = std::decay_t; + + static const bool value = xtl::is_arithmetic::value + && (sizeof(result_type) < sizeof(argument_type) + || (xtl::is_integral::value + && std::is_floating_point::value)); + }; + + template + struct has_sign_conversion + { + using argument_type = std::decay_t; + using result_type = std::decay_t; + + static const bool value = xtl::is_signed::value != xtl::is_signed::value; + }; + + template + struct has_assign_conversion + { + using argument_type = std::decay_t; + using result_type = std::decay_t; + + static const bool value = is_narrowing_conversion::value + || has_sign_conversion::value; + }; + + template + inline stepper_assigner::stepper_assigner(E1& e1, const E2& e2) + : m_e1(e1) + , m_lhs(e1.stepper_begin(e1.shape())) + , m_rhs(e2.stepper_begin(e1.shape())) + , m_index(xtl::make_sequence(e1.shape().size(), size_type(0))) + { + } + + template + inline void stepper_assigner::run() + { + using tmp_size_type = typename E1::size_type; + using argument_type = std::decay_t; + using result_type = std::decay_t; + constexpr bool needs_cast = has_assign_conversion::value; + + tmp_size_type s = m_e1.size(); + for (tmp_size_type i = 0; i < s; ++i) + { + *m_lhs = conditional_cast(*m_rhs); + stepper_tools::increment_stepper(*this, m_index, m_e1.shape()); + } + } + + template + inline void stepper_assigner::step(size_type i) + { + m_lhs.step(i); + m_rhs.step(i); + } + + template + inline void stepper_assigner::step(size_type i, size_type n) + { + m_lhs.step(i, n); + m_rhs.step(i, n); + } + + template + inline void stepper_assigner::reset(size_type i) + { + m_lhs.reset(i); + m_rhs.reset(i); + } + + template + inline void stepper_assigner::to_end(layout_type l) + { + m_lhs.to_end(l); + m_rhs.to_end(l); + } + + /********************************** + * linear_assigner implementation * + **********************************/ + + template + template + inline void linear_assigner::run(E1& e1, const E2& e2) + { + using lhs_align_mode = xt_simd::container_alignment_t; + constexpr bool is_aligned = std::is_same::value; + using rhs_align_mode = std::conditional_t; + using e1_value_type = typename E1::value_type; + using e2_value_type = typename E2::value_type; + using value_type = typename xassign_traits::requested_value_type; + using simd_type = xt_simd::simd_type; + using size_type = typename E1::size_type; + size_type size = e1.size(); + constexpr size_type simd_size = simd_type::size; + constexpr bool needs_cast = has_assign_conversion::value; + + size_type align_begin = is_aligned ? 0 : xt_simd::get_alignment_offset(e1.data(), size, simd_size); + size_type align_end = align_begin + ((size - align_begin) & ~(simd_size - 1)); + + for (size_type i = 0; i < align_begin; ++i) + { + e1.data_element(i) = conditional_cast(e2.data_element(i)); + } + +#if defined(XTENSOR_USE_TBB) + if (size >= XTENSOR_TBB_THRESHOLD) + { + tbb::static_partitioner ap; + tbb::parallel_for( + align_begin, + align_end, + simd_size, + [&e1, &e2](size_t i) + { + e1.template store_simd( + i, + e2.template load_simd(i) + ); + }, + ap + ); + } + else + { + for (size_type i = align_begin; i < align_end; i += simd_size) + { + e1.template store_simd(i, e2.template load_simd(i)); + } + } +#elif defined(XTENSOR_USE_OPENMP) + if (size >= size_type(XTENSOR_OPENMP_TRESHOLD)) + { +#pragma omp parallel for default(none) shared(align_begin, align_end, e1, e2) +#ifndef _WIN32 + for (size_type i = align_begin; i < align_end; i += simd_size) + { + e1.template store_simd(i, e2.template load_simd(i)); + } +#else + for (auto i = static_cast(align_begin); i < static_cast(align_end); + i += static_cast(simd_size)) + { + size_type ui = static_cast(i); + e1.template store_simd(ui, e2.template load_simd(ui)); + } +#endif + } + else + { + for (size_type i = align_begin; i < align_end; i += simd_size) + { + e1.template store_simd(i, e2.template load_simd(i)); + } + } +#else + for (size_type i = align_begin; i < align_end; i += simd_size) + { + e1.template store_simd(i, e2.template load_simd(i)); + } +#endif + for (size_type i = align_end; i < size; ++i) + { + e1.data_element(i) = conditional_cast(e2.data_element(i)); + } + } + + template + inline void linear_assigner::run(E1& e1, const E2& e2) + { + using is_convertible = std:: + is_convertible::value_type, typename std::decay_t::value_type>; + // If the types are not compatible, this function is still instantiated but never called. + // To avoid compilation problems in effectively unused code trivial_assigner_run_impl is + // empty in this case. + run_impl(e1, e2, is_convertible()); + } + + template + inline void linear_assigner::run_impl(E1& e1, const E2& e2, std::true_type /*is_convertible*/) + { + using value_type = typename E1::value_type; + using size_type = typename E1::size_type; + auto src = linear_begin(e2); + auto dst = linear_begin(e1); + size_type n = e1.size(); +#if defined(XTENSOR_USE_TBB) + tbb::static_partitioner sp; + tbb::parallel_for( + std::ptrdiff_t(0), + static_cast(n), + [&](std::ptrdiff_t i) + { + *(dst + i) = static_cast(*(src + i)); + }, + sp + ); +#elif defined(XTENSOR_USE_OPENMP) + if (n >= XTENSOR_OPENMP_TRESHOLD) + { +#pragma omp parallel for default(none) shared(src, dst, n) + for (std::ptrdiff_t i = std::ptrdiff_t(0); i < static_cast(n); i++) + { + *(dst + i) = static_cast(*(src + i)); + } + } + else + { + for (; n > size_type(0); --n) + { + *dst = static_cast(*src); + ++src; + ++dst; + } + } +#else + for (; n > size_type(0); --n) + { + *dst = static_cast(*src); + ++src; + ++dst; + } +#endif + } + + template + inline void linear_assigner::run_impl(E1&, const E2&, std::false_type /*is_convertible*/) + { + XTENSOR_PRECONDITION(false, "Internal error: linear_assigner called with unrelated types."); + } + + /**************************************** + * strided_loop_assigner implementation * + ****************************************/ + + namespace strided_assign_detail + { + template + struct idx_tools; + + template <> + struct idx_tools + { + template + static void next_idx(T& outer_index, T& outer_shape) + { + auto i = outer_index.size(); + for (; i > 0; --i) + { + if (outer_index[i - 1] + 1 >= outer_shape[i - 1]) + { + outer_index[i - 1] = 0; + } + else + { + outer_index[i - 1]++; + break; + } + } + } + + template + static void nth_idx(size_t n, T& outer_index, const T& outer_shape) + { + dynamic_shape stride_sizes; + xt::resize_container(stride_sizes, outer_shape.size()); + // compute strides + using size_type = typename T::size_type; + for (size_type i = outer_shape.size(); i > 0; i--) + { + stride_sizes[i - 1] = (i == outer_shape.size()) ? 1 : stride_sizes[i] * outer_shape[i]; + } + + // compute index + for (size_type i = 0; i < outer_shape.size(); i++) + { + auto d_idx = n / stride_sizes[i]; + outer_index[i] = d_idx; + n -= d_idx * stride_sizes[i]; + } + } + }; + + template <> + struct idx_tools + { + template + static void next_idx(T& outer_index, T& outer_shape) + { + using size_type = typename T::size_type; + size_type i = 0; + auto sz = outer_index.size(); + for (; i < sz; ++i) + { + if (outer_index[i] + 1 >= outer_shape[i]) + { + outer_index[i] = 0; + } + else + { + outer_index[i]++; + break; + } + } + } + + template + static void nth_idx(size_t n, T& outer_index, const T& outer_shape) + { + dynamic_shape stride_sizes; + xt::resize_container(stride_sizes, outer_shape.size()); + + using size_type = typename T::size_type; + + // compute required strides + for (size_type i = 0; i < outer_shape.size(); i++) + { + stride_sizes[i] = (i == 0) ? 1 : stride_sizes[i - 1] * outer_shape[i - 1]; + } + + // compute index + for (size_type i = outer_shape.size(); i > 0;) + { + i--; + auto d_idx = n / stride_sizes[i]; + outer_index[i] = d_idx; + n -= d_idx * stride_sizes[i]; + } + } + }; + + template + struct check_strides_functor + { + using strides_type = S; + + check_strides_functor(const S& strides) + : m_cut(L == layout_type::row_major ? 0 : strides.size()) + , m_strides(strides) + { + } + + template + std::enable_if_t operator()(const T& el) + { + // All dimenions less than var have differing strides + auto var = check_strides_overlap::get(m_strides, el.strides()); + if (var > m_cut) + { + m_cut = var; + } + return m_cut; + } + + template + std::enable_if_t operator()(const T& el) + { + auto var = check_strides_overlap::get(m_strides, el.strides()); + // All dimensions >= var have differing strides + if (var < m_cut) + { + m_cut = var; + } + return m_cut; + } + + template + std::size_t operator()(const xt::xscalar& /*el*/) + { + return m_cut; + } + + template + std::size_t operator()(const xt::xfunction& xf) + { + xt::for_each(*this, xf.arguments()); + return m_cut; + } + + private: + + std::size_t m_cut; + const strides_type& m_strides; + }; + + template ::value || !possible, bool> = true> + loop_sizes_t get_loop_sizes(const E1& e1, const E2&) + { + return {false, true, 1, e1.size(), e1.dimension(), e1.dimension()}; + } + + template ::value && possible, bool> = true> + loop_sizes_t get_loop_sizes(const E1& e1, const E2& e2) + { + using shape_value_type = typename E1::shape_type::value_type; + bool is_row_major = true; + + // Try to find a row-major scheme first, where the outer loop is on the first N = `cut` + // dimensions, and the inner loop is + is_row_major = true; + auto is_zero = [](auto i) + { + return i == 0; + }; + auto&& strides = e1.strides(); + auto it_bwd = std::find_if_not(strides.rbegin(), strides.rend(), is_zero); + bool de1_row_contiguous = it_bwd != strides.rend() && *it_bwd == 1; + auto it_fwd = std::find_if_not(strides.begin(), strides.end(), is_zero); + bool de1_col_contiguous = it_fwd != strides.end() && *it_fwd == 1; + if (de1_row_contiguous) + { + is_row_major = true; + } + else if (de1_col_contiguous) + { + is_row_major = false; + } + else + { + // No strided loop possible. + return {false, true, 1, e1.size(), e1.dimension(), e1.dimension()}; + } + + // Cut is the number of dimensions in the outer loop + std::size_t cut = 0; + + if (is_row_major) + { + auto csf = check_strides_functor(e1.strides()); + cut = csf(e2); + // This makes that only one dimension will be treated in the inner loop. + if (cut < e1.strides().size() - 1) + { + // Only make the inner loop go over one dimension by default for now + cut = e1.strides().size() - 1; + } + } + else if (!is_row_major) + { + auto csf = check_strides_functor(e1.strides() + ); + cut = csf(e2); + if (cut > 1) + { + // Only make the inner loop go over one dimension by default for now + cut = 1; + } + } // can't reach here because this would have already triggered the fallback + + std::size_t outer_loop_size = static_cast(std::accumulate( + e1.shape().begin(), + e1.shape().begin() + static_cast(cut), + shape_value_type(1), + std::multiplies{} + )); + std::size_t inner_loop_size = static_cast(std::accumulate( + e1.shape().begin() + static_cast(cut), + e1.shape().end(), + shape_value_type(1), + std::multiplies{} + )); + + if (!is_row_major) + { + std::swap(outer_loop_size, inner_loop_size); + } + + return {inner_loop_size > 1, is_row_major, inner_loop_size, outer_loop_size, cut, e1.dimension()}; + } + } + + template + template + inline strided_assign_detail::loop_sizes_t strided_loop_assigner::get_loop_sizes(E1& e1, const E2& e2) + { + return strided_assign_detail::get_loop_sizes(e1, e2); + } + +#define strided_parallel_assign + + template + template + inline void strided_loop_assigner::run(E1& e1, const E2& e2, const loop_sizes_t& loop_sizes) + { + bool is_row_major = loop_sizes.is_row_major; + std::size_t inner_loop_size = loop_sizes.inner_loop_size; + std::size_t outer_loop_size = loop_sizes.outer_loop_size; + std::size_t cut = loop_sizes.cut; + + + // TODO can we get rid of this and use `shape_type`? + dynamic_shape idx, max_shape; + + if (is_row_major) + { + xt::resize_container(idx, cut); + max_shape.assign(e1.shape().begin(), e1.shape().begin() + static_cast(cut)); + } + else + { + xt::resize_container(idx, e1.shape().size() - cut); + max_shape.assign(e1.shape().begin() + static_cast(cut), e1.shape().end()); + } + + // add this when we have std::array index! + // std::fill(idx.begin(), idx.end(), 0); + using e1_value_type = typename E1::value_type; + using e2_value_type = typename E2::value_type; + constexpr bool needs_cast = has_assign_conversion::value; + using value_type = typename xassign_traits::requested_value_type; + using simd_type = std::conditional_t< + std::is_same::value, + xt_simd::simd_bool_type, + xt_simd::simd_type>; + + std::size_t simd_size = inner_loop_size / simd_type::size; + std::size_t simd_rest = inner_loop_size % simd_type::size; + + auto fct_stepper = e2.stepper_begin(e1.shape()); + auto res_stepper = e1.stepper_begin(e1.shape()); + + // TODO in 1D case this is ambiguous -- could be RM or CM. + // Use default layout to make decision + std::size_t step_dim = 0; + if (!is_row_major) // row major case + { + step_dim = cut; + } +#if defined(XTENSOR_USE_OPENMP) && defined(strided_parallel_assign) + if (outer_loop_size >= XTENSOR_OPENMP_TRESHOLD / inner_loop_size) + { + std::size_t first_step = true; +#pragma omp parallel for schedule(static) firstprivate(first_step, fct_stepper, res_stepper, idx) + for (std::size_t ox = 0; ox < outer_loop_size; ++ox) + { + if (first_step) + { + is_row_major + ? strided_assign_detail::idx_tools::nth_idx(ox, idx, max_shape) + : strided_assign_detail::idx_tools::nth_idx(ox, idx, max_shape); + + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + res_stepper.step(i + step_dim, idx[i]); + } + first_step = false; + } + + for (std::size_t i = 0; i < simd_size; ++i) + { + res_stepper.template store_simd(fct_stepper.template step_simd()); + } + for (std::size_t i = 0; i < simd_rest; ++i) + { + *(res_stepper) = conditional_cast(*(fct_stepper)); + res_stepper.step_leading(); + fct_stepper.step_leading(); + } + + // next unaligned index + is_row_major + ? strided_assign_detail::idx_tools::next_idx(idx, max_shape) + : strided_assign_detail::idx_tools::next_idx(idx, max_shape); + + fct_stepper.to_begin(); + + // need to step E1 as well if not contigous assign (e.g. view) + if (!E1::contiguous_layout) + { + res_stepper.to_begin(); + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + res_stepper.step(i + step_dim, idx[i]); + } + } + else + { + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + } + } + } + } + else + { +#elif defined(strided_parallel_assign) && defined(XTENSOR_USE_TBB) + if (outer_loop_size > XTENSOR_TBB_THRESHOLD / inner_loop_size) + { + tbb::static_partitioner sp; + tbb::parallel_for( + tbb::blocked_range(0ul, outer_loop_size), + [&e1, &e2, is_row_major, step_dim, simd_size, simd_rest, &max_shape, &idx_ = idx]( + const tbb::blocked_range& r + ) + { + auto idx = idx_; + auto fct_stepper = e2.stepper_begin(e1.shape()); + auto res_stepper = e1.stepper_begin(e1.shape()); + std::size_t first_step = true; + // #pragma omp parallel for schedule(static) firstprivate(first_step, fct_stepper, + // res_stepper, idx) + for (std::size_t ox = r.begin(); ox < r.end(); ++ox) + { + if (first_step) + { + is_row_major + ? strided_assign_detail::idx_tools::nth_idx(ox, idx, max_shape) + : strided_assign_detail::idx_tools::nth_idx( + ox, + idx, + max_shape + ); + + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + res_stepper.step(i + step_dim, idx[i]); + } + first_step = false; + } + + for (std::size_t i = 0; i < simd_size; ++i) + { + res_stepper.template store_simd(fct_stepper.template step_simd()); + } + for (std::size_t i = 0; i < simd_rest; ++i) + { + *(res_stepper) = conditional_cast(*(fct_stepper)); + res_stepper.step_leading(); + fct_stepper.step_leading(); + } + + // next unaligned index + is_row_major + ? strided_assign_detail::idx_tools::next_idx(idx, max_shape) + : strided_assign_detail::idx_tools::next_idx(idx, max_shape); + + fct_stepper.to_begin(); + + // need to step E1 as well if not contigous assign (e.g. view) + if (!E1::contiguous_layout) + { + res_stepper.to_begin(); + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + res_stepper.step(i + step_dim, idx[i]); + } + } + else + { + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + } + } + } + }, + sp + ); + } + else + { + +#endif + for (std::size_t ox = 0; ox < outer_loop_size; ++ox) + { + for (std::size_t i = 0; i < simd_size; ++i) + { + res_stepper.store_simd(fct_stepper.template step_simd()); + } + for (std::size_t i = 0; i < simd_rest; ++i) + { + *(res_stepper) = conditional_cast(*(fct_stepper)); + res_stepper.step_leading(); + fct_stepper.step_leading(); + } + + is_row_major + ? strided_assign_detail::idx_tools::next_idx(idx, max_shape) + : strided_assign_detail::idx_tools::next_idx(idx, max_shape); + + fct_stepper.to_begin(); + + // need to step E1 as well if not contigous assign (e.g. view) + if (!E1::contiguous_layout) + { + res_stepper.to_begin(); + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + res_stepper.step(i + step_dim, idx[i]); + } + } + else + { + for (std::size_t i = 0; i < idx.size(); ++i) + { + fct_stepper.step(i + step_dim, idx[i]); + } + } + } +#if (defined(XTENSOR_USE_OPENMP) || defined(XTENSOR_USE_TBB)) && defined(strided_parallel_assign) + } +#endif + } + + template <> + template + inline void strided_loop_assigner::run(E1& e1, const E2& e2) + { + strided_assign_detail::loop_sizes_t loop_sizes = strided_loop_assigner::get_loop_sizes(e1, e2); + if (loop_sizes.can_do_strided_assign) + { + run(e1, e2, loop_sizes); + } + else + { + // trigger the fallback assigner + stepper_assigner(e1, e2).run(); + } + } + + template <> + template + inline void strided_loop_assigner::run(E1& /*e1*/, const E2& /*e2*/, const loop_sizes_t&) + { + } + + template <> + template + inline void strided_loop_assigner::run(E1& e1, const E2& e2) + { + // trigger the fallback assigner + stepper_assigner(e1, e2).run(); + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xaxis_iterator.hpp b/contrib/xtensor/include/xtensor/xaxis_iterator.hpp new file mode 100644 index 00000000000..953206aafd2 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xaxis_iterator.hpp @@ -0,0 +1,349 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_AXIS_ITERATOR_HPP +#define XTENSOR_AXIS_ITERATOR_HPP + +#include "xstrided_view.hpp" + +namespace xt +{ + + /****************** + * xaxis_iterator * + ******************/ + + /** + * @class xaxis_iterator + * @brief Class for iteration over (N-1)-dimensional slices, where + * N is the dimension of the underlying expression + * + * If N is the number of dimensions of an expression, the xaxis_iterator + * iterates over (N-1)-dimensional slices oriented along the specified axis. + * + * @tparam CT the closure type of the \ref xexpression + */ + template + class xaxis_iterator + { + public: + + using self_type = xaxis_iterator; + + using xexpression_type = std::decay_t; + using size_type = typename xexpression_type::size_type; + using difference_type = typename xexpression_type::difference_type; + using shape_type = typename xexpression_type::shape_type; + using value_type = xstrided_view; + using reference = std::remove_reference_t>; + using pointer = xtl::xclosure_pointer>>; + + using iterator_category = std::forward_iterator_tag; + + template + xaxis_iterator(CTA&& e, size_type axis); + template + xaxis_iterator(CTA&& e, size_type axis, size_type index, size_type offset); + + self_type& operator++(); + self_type operator++(int); + + reference operator*() const; + pointer operator->() const; + + bool equal(const self_type& rhs) const; + + private: + + using storing_type = xtl::ptr_closure_type_t; + mutable storing_type p_expression; + size_type m_index; + size_type m_add_offset; + value_type m_sv; + + template + std::enable_if_t::value, T> get_storage_init(CTA&& e) const; + + template + std::enable_if_t::value, T> get_storage_init(CTA&& e) const; + }; + + template + bool operator==(const xaxis_iterator& lhs, const xaxis_iterator& rhs); + + template + bool operator!=(const xaxis_iterator& lhs, const xaxis_iterator& rhs); + + template + auto axis_begin(E&& e); + + template + auto axis_begin(E&& e, typename std::decay_t::size_type axis); + + template + auto axis_end(E&& e); + + template + auto axis_end(E&& e, typename std::decay_t::size_type axis); + + /********************************* + * xaxis_iterator implementation * + *********************************/ + + namespace detail + { + template + auto derive_xstrided_view( + CT&& e, + typename std::decay_t::size_type axis, + typename std::decay_t::size_type offset + ) + { + using xexpression_type = std::decay_t; + using shape_type = typename xexpression_type::shape_type; + using strides_type = typename xexpression_type::strides_type; + + const auto& e_shape = e.shape(); + shape_type shape(e_shape.size() - 1); + auto nxt = std::copy(e_shape.cbegin(), e_shape.cbegin() + axis, shape.begin()); + std::copy(e_shape.cbegin() + axis + 1, e_shape.end(), nxt); + + const auto& e_strides = e.strides(); + strides_type strides(e_strides.size() - 1); + auto nxt_strides = std::copy(e_strides.cbegin(), e_strides.cbegin() + axis, strides.begin()); + std::copy(e_strides.cbegin() + axis + 1, e_strides.end(), nxt_strides); + + return strided_view(std::forward(e), std::move(shape), std::move(strides), offset, e.layout()); + } + } + + template + template + inline std::enable_if_t::value, T> xaxis_iterator::get_storage_init(CTA&& e) const + { + return &e; + } + + template + template + inline std::enable_if_t::value, T> xaxis_iterator::get_storage_init(CTA&& e) const + { + return e; + } + + /** + * @name Constructors + */ + //@{ + /** + * Constructs an xaxis_iterator + * + * @param e the expression to iterate over + * @param axis the axis to iterate over taking N-1 dimensional slices + */ + template + template + inline xaxis_iterator::xaxis_iterator(CTA&& e, size_type axis) + : xaxis_iterator(std::forward(e), axis, 0, e.data_offset()) + { + } + + /** + * Constructs an xaxis_iterator starting at specified index and offset + * + * @param e the expression to iterate over + * @param axis the axis to iterate over taking N-1 dimensional slices + * @param index the starting index for the iterator + * @param offset the starting offset for the iterator + */ + template + template + inline xaxis_iterator::xaxis_iterator(CTA&& e, size_type axis, size_type index, size_type offset) + : p_expression(get_storage_init(std::forward(e))) + , m_index(index) + , m_add_offset(static_cast(e.strides()[axis])) + , m_sv(detail::derive_xstrided_view(std::forward(e), axis, offset)) + { + } + + //@} + + /** + * @name Increment + */ + //@{ + /** + * Increments the iterator to the next position and returns it. + */ + template + inline auto xaxis_iterator::operator++() -> self_type& + { + m_sv.set_offset(m_sv.data_offset() + m_add_offset); + ++m_index; + return *this; + } + + /** + * Makes a copy of the iterator, increments it to the next + * position, and returns the copy. + */ + template + inline auto xaxis_iterator::operator++(int) -> self_type + { + self_type tmp(*this); + ++(*this); + return tmp; + } + + //@} + + /** + * @name Reference + */ + //@{ + /** + * Returns the strided view at the current iteration position + * + * @return a strided_view + */ + template + inline auto xaxis_iterator::operator*() const -> reference + { + return m_sv; + } + + /** + * Returns a pointer to the strided view at the current iteration position + * + * @return a pointer to a strided_view + */ + template + inline auto xaxis_iterator::operator->() const -> pointer + { + return xtl::closure_pointer(operator*()); + } + + //@} + + /* + * @name Comparisons + */ + //@{ + /** + * Checks equality of the xaxis_slice_iterator and \c rhs. + * + * @param + * @return true if the iterators are equivalent, false otherwise + */ + template + inline bool xaxis_iterator::equal(const self_type& rhs) const + { + return p_expression == rhs.p_expression && m_index == rhs.m_index + && m_sv.data_offset() == rhs.m_sv.data_offset(); + } + + /** + * Checks equality of the iterators. + * + * @return true if the iterators are equivalent, false otherwise + */ + template + inline bool operator==(const xaxis_iterator& lhs, const xaxis_iterator& rhs) + { + return lhs.equal(rhs); + } + + /** + * Checks inequality of the iterators + * @return true if the iterators are different, true otherwise + */ + template + inline bool operator!=(const xaxis_iterator& lhs, const xaxis_iterator& rhs) + { + return !(lhs == rhs); + } + + //@} + + /** + * @name Iterators + */ + //@{ + /** + * Returns an iterator to the first element of the expression for axis 0 + * + * @param e the expession to iterate over + * @return an instance of xaxis_iterator + */ + template + inline auto axis_begin(E&& e) + { + using return_type = xaxis_iterator>; + return return_type(std::forward(e), 0); + } + + /** + * Returns an iterator to the first element of the expression for the specified axis + * + * @param e the expession to iterate over + * @param axis the axis to iterate over + * @return an instance of xaxis_iterator + */ + template + inline auto axis_begin(E&& e, typename std::decay_t::size_type axis) + { + using return_type = xaxis_iterator>; + return return_type(std::forward(e), axis); + } + + /** + * Returns an iterator to the element following the last element of + * the expression for axis 0 + * + * @param e the expession to iterate over + * @return an instance of xaxis_iterator + */ + template + inline auto axis_end(E&& e) + { + using size_type = typename std::decay_t::size_type; + using return_type = xaxis_iterator>; + return return_type( + std::forward(e), + 0, + e.shape()[0], + static_cast(e.strides()[0]) * e.shape()[0] + ); + } + + /** + * Returns an iterator to the element following the last element of + * the expression for the specified axis + * + * @param e the expression to iterate over + * @param axis the axis to iterate over + * @return an instance of xaxis_iterator + */ + template + inline auto axis_end(E&& e, typename std::decay_t::size_type axis) + { + using size_type = typename std::decay_t::size_type; + using return_type = xaxis_iterator>; + return return_type( + std::forward(e), + axis, + e.shape()[axis], + static_cast(e.strides()[axis]) * e.shape()[axis] + ); + } + + //@} +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xaxis_slice_iterator.hpp b/contrib/xtensor/include/xtensor/xaxis_slice_iterator.hpp new file mode 100644 index 00000000000..8a9814df625 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xaxis_slice_iterator.hpp @@ -0,0 +1,367 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_AXIS_SLICE_ITERATOR_HPP +#define XTENSOR_AXIS_SLICE_ITERATOR_HPP + +#include "xstrided_view.hpp" + +namespace xt +{ + + /** + * @class xaxis_slice_iterator + * @brief Class for iteration over one-dimensional slices + * + * The xaxis_slice_iterator iterates over one-dimensional slices + * oriented along the specified axis + * + * @tparam CT the closure type of the \ref xexpression + */ + template + class xaxis_slice_iterator + { + public: + + using self_type = xaxis_slice_iterator; + + using xexpression_type = std::decay_t; + using size_type = typename xexpression_type::size_type; + using difference_type = typename xexpression_type::difference_type; + using shape_type = typename xexpression_type::shape_type; + using strides_type = typename xexpression_type::strides_type; + using value_type = xstrided_view; + using reference = std::remove_reference_t>; + using pointer = xtl::xclosure_pointer>>; + + using iterator_category = std::forward_iterator_tag; + + template + xaxis_slice_iterator(CTA&& e, size_type axis); + template + xaxis_slice_iterator(CTA&& e, size_type axis, size_type index, size_type offset); + + self_type& operator++(); + self_type operator++(int); + + reference operator*() const; + pointer operator->() const; + + bool equal(const self_type& rhs) const; + + private: + + using storing_type = xtl::ptr_closure_type_t; + mutable storing_type p_expression; + size_type m_index; + size_type m_offset; + size_type m_axis_stride; + size_type m_lower_shape; + size_type m_upper_shape; + size_type m_iter_size; + bool m_is_target_axis; + value_type m_sv; + + template + std::enable_if_t::value, T> get_storage_init(CTA&& e) const; + + template + std::enable_if_t::value, T> get_storage_init(CTA&& e) const; + }; + + template + bool operator==(const xaxis_slice_iterator& lhs, const xaxis_slice_iterator& rhs); + + template + bool operator!=(const xaxis_slice_iterator& lhs, const xaxis_slice_iterator& rhs); + + template + auto xaxis_slice_begin(E&& e); + + template + auto xaxis_slice_begin(E&& e, typename std::decay_t::size_type axis); + + template + auto xaxis_slice_end(E&& e); + + template + auto xaxis_slice_end(E&& e, typename std::decay_t::size_type axis); + + /*************************************** + * xaxis_slice_iterator implementation * + ***************************************/ + + template + template + inline std::enable_if_t::value, T> + xaxis_slice_iterator::get_storage_init(CTA&& e) const + { + return &e; + } + + template + template + inline std::enable_if_t::value, T> + xaxis_slice_iterator::get_storage_init(CTA&& e) const + { + return e; + } + + /** + * @name Constructors + */ + //@{ + /** + * Constructs an xaxis_slice_iterator + * + * @param e the expression to iterate over + * @param axis the axis to iterate over taking one dimensional slices + */ + template + template + inline xaxis_slice_iterator::xaxis_slice_iterator(CTA&& e, size_type axis) + : xaxis_slice_iterator(std::forward(e), axis, 0, e.data_offset()) + { + } + + /** + * Constructs an xaxis_slice_iterator starting at specified index and offset + * + * @param e the expression to iterate over + * @param axis the axis to iterate over taking one dimensional slices + * @param index the starting index for the iterator + * @param offset the starting offset for the iterator + */ + template + template + inline xaxis_slice_iterator::xaxis_slice_iterator(CTA&& e, size_type axis, size_type index, size_type offset) + : p_expression(get_storage_init(std::forward(e))) + , m_index(index) + , m_offset(offset) + , m_axis_stride(static_cast(e.strides()[axis]) * (e.shape()[axis] - 1u)) + , m_lower_shape(0) + , m_upper_shape(0) + , m_iter_size(0) + , m_is_target_axis(false) + , m_sv(strided_view( + std::forward(e), + std::forward({e.shape()[axis]}), + std::forward({e.strides()[axis]}), + offset, + e.layout() + )) + { + if (e.layout() == layout_type::row_major) + { + m_is_target_axis = axis == e.dimension() - 1; + m_lower_shape = std::accumulate( + e.shape().begin() + axis + 1, + e.shape().end(), + size_t(1), + std::multiplies<>() + ); + m_iter_size = std::accumulate(e.shape().begin() + 1, e.shape().end(), size_t(1), std::multiplies<>()); + } + else + { + m_is_target_axis = axis == 0; + m_lower_shape = std::accumulate( + e.shape().begin(), + e.shape().begin() + axis, + size_t(1), + std::multiplies<>() + ); + m_iter_size = std::accumulate(e.shape().begin(), e.shape().end() - 1, size_t(1), std::multiplies<>()); + } + m_upper_shape = m_lower_shape + m_axis_stride; + } + + //@} + + /** + * @name Increment + */ + //@{ + /** + * Increments the iterator to the next position and returns it. + */ + template + inline auto xaxis_slice_iterator::operator++() -> self_type& + { + ++m_index; + ++m_offset; + auto index_compare = (m_offset % m_iter_size); + if (m_is_target_axis || (m_upper_shape >= index_compare && index_compare >= m_lower_shape)) + { + m_offset += m_axis_stride; + } + m_sv.set_offset(m_offset); + return *this; + } + + /** + * Makes a copy of the iterator, increments it to the next + * position, and returns the copy. + */ + template + inline auto xaxis_slice_iterator::operator++(int) -> self_type + { + self_type tmp(*this); + ++(*this); + return tmp; + } + + //@} + + /** + * @name Reference + */ + //@{ + /** + * Returns the strided view at the current iteration position + * + * @return a strided_view + */ + template + inline auto xaxis_slice_iterator::operator*() const -> reference + { + return m_sv; + } + + /** + * Returns a pointer to the strided view at the current iteration position + * + * @return a pointer to a strided_view + */ + template + inline auto xaxis_slice_iterator::operator->() const -> pointer + { + return xtl::closure_pointer(operator*()); + } + + //@} + + /* + * @name Comparisons + */ + //@{ + /** + * Checks equality of the xaxis_slice_iterator and \c rhs. + * + * @return true if the iterators are equivalent, false otherwise + */ + template + inline bool xaxis_slice_iterator::equal(const self_type& rhs) const + { + return p_expression == rhs.p_expression && m_index == rhs.m_index; + } + + /** + * Checks equality of the iterators. + * + * @return true if the iterators are equivalent, false otherwise + */ + template + inline bool operator==(const xaxis_slice_iterator& lhs, const xaxis_slice_iterator& rhs) + { + return lhs.equal(rhs); + } + + /** + * Checks inequality of the iterators + * @return true if the iterators are different, true otherwise + */ + template + inline bool operator!=(const xaxis_slice_iterator& lhs, const xaxis_slice_iterator& rhs) + { + return !(lhs == rhs); + } + + //@} + + /** + * @name Iterators + */ + //@{ + /** + * Returns an iterator to the first element of the expression for axis 0 + * + * @param e the expession to iterate over + * @return an instance of xaxis_slice_iterator + */ + template + inline auto axis_slice_begin(E&& e) + { + using return_type = xaxis_slice_iterator>; + return return_type(std::forward(e), 0); + } + + /** + * Returns an iterator to the first element of the expression for the specified axis + * + * @param e the expession to iterate over + * @param axis the axis to iterate over + * @return an instance of xaxis_slice_iterator + */ + template + inline auto axis_slice_begin(E&& e, typename std::decay_t::size_type axis) + { + using return_type = xaxis_slice_iterator>; + return return_type(std::forward(e), axis, 0, e.data_offset()); + } + + /** + * Returns an iterator to the element following the last element of + * the expression for axis 0 + * + * @param e the expession to iterate over + * @return an instance of xaxis_slice_iterator + */ + template + inline auto axis_slice_end(E&& e) + { + using return_type = xaxis_slice_iterator>; + return return_type( + std::forward(e), + 0, + std::accumulate(e.shape().begin() + 1, e.shape().end(), size_t(1), std::multiplies<>()), + e.size() + ); + } + + /** + * Returns an iterator to the element following the last element of + * the expression for the specified axis + * + * @param e the expression to iterate over + * @param axis the axis to iterate over + * @return an instance of xaxis_slice_iterator + */ + template + inline auto axis_slice_end(E&& e, typename std::decay_t::size_type axis) + { + using return_type = xaxis_slice_iterator>; + auto index_sum = std::accumulate( + e.shape().begin(), + e.shape().begin() + axis, + size_t(1), + std::multiplies<>() + ); + return return_type( + std::forward(e), + axis, + std::accumulate(e.shape().begin() + axis + 1, e.shape().end(), index_sum, std::multiplies<>()), + e.size() + axis + ); + } + + //@} +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xblockwise_reducer.hpp b/contrib/xtensor/include/xtensor/xblockwise_reducer.hpp new file mode 100644 index 00000000000..1a709bae577 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xblockwise_reducer.hpp @@ -0,0 +1,532 @@ +#ifndef XTENSOR_XBLOCKWISE_REDUCER_HPP +#define XTENSOR_XBLOCKWISE_REDUCER_HPP + +#include "xblockwise_reducer_functors.hpp" +#include "xmultiindex_iterator.hpp" +#include "xreducer.hpp" +#include "xshape.hpp" +#include "xtl/xclosure.hpp" +#include "xtl/xsequence.hpp" + +namespace xt +{ + + template + class xblockwise_reducer + { + public: + + using self_type = xblockwise_reducer; + using raw_options_type = std::decay_t; + using keep_dims = xtl::mpl::contains; + using xexpression_type = std::decay_t; + using shape_type = typename xreducer_shape_type, keep_dims>::type; + using functor_type = F; + using value_type = typename functor_type::value_type; + using input_shape_type = typename xexpression_type::shape_type; + using input_chunk_index_type = filter_fixed_shape_t; + using input_grid_strides = filter_fixed_shape_t; + using axes_type = X; + using chunk_shape_type = filter_fixed_shape_t; + + + template + xblockwise_reducer(E&& e, BS&& block_shape, XX&& axes, OO&& options, FF&& functor); + + const input_shape_type& input_shape() const; + const axes_type& axes() const; + + std::size_t dimension() const; + + const shape_type& shape() const; + + const chunk_shape_type& chunk_shape() const; + + template + void assign_to(R& result) const; + + private: + + using mapping_type = filter_fixed_shape_t; + using input_chunked_view_type = xchunked_view&>; + using input_const_chunked_iterator_type = typename input_chunked_view_type::const_chunk_iterator; + using input_chunk_range_type = std::array, 2>; + + template + void assign_to_chunk(CI& result_chunk_iter) const; + + template + input_chunk_range_type compute_input_chunk_range(CI& result_chunk_iter) const; + + input_const_chunked_iterator_type get_input_chunk_iter(input_chunk_index_type input_chunk_index) const; + void init_shapes(); + + CT m_e; + xchunked_view&> m_e_chunked_view; + axes_type m_axes; + raw_options_type m_options; + functor_type m_functor; + shape_type m_result_shape; + chunk_shape_type m_result_chunk_shape; + mapping_type m_mapping; + input_grid_strides m_input_grid_strides; + }; + + template + template + xblockwise_reducer::xblockwise_reducer(E&& e, BS&& block_shape, XX&& axes, OO&& options, FF&& functor) + : m_e(std::forward(e)) + , m_e_chunked_view(m_e, std::forward(block_shape)) + , m_axes(std::forward(axes)) + , m_options(std::forward(options)) + , m_functor(std::forward(functor)) + , m_result_shape() + , m_result_chunk_shape() + , m_mapping() + , m_input_grid_strides() + { + init_shapes(); + resize_container(m_input_grid_strides, m_e.dimension()); + std::size_t stride = 1; + + for (std::size_t i = m_input_grid_strides.size(); i != 0; --i) + { + m_input_grid_strides[i - 1] = stride; + stride *= m_e_chunked_view.grid_shape()[i - 1]; + } + } + + template + inline auto xblockwise_reducer::input_shape() const -> const input_shape_type& + { + return m_e.shape(); + } + + template + inline auto xblockwise_reducer::axes() const -> const axes_type& + { + return m_axes; + } + + template + inline std::size_t xblockwise_reducer::dimension() const + { + return m_result_shape.size(); + } + + template + inline auto xblockwise_reducer::shape() const -> const shape_type& + { + return m_result_shape; + } + + template + inline auto xblockwise_reducer::chunk_shape() const -> const chunk_shape_type& + { + return m_result_chunk_shape; + } + + template + template + inline void xblockwise_reducer::assign_to(R& result) const + { + auto result_chunked_view = as_chunked(result, m_result_chunk_shape); + for (auto chunk_iter = result_chunked_view.chunk_begin(); chunk_iter != result_chunked_view.chunk_end(); + ++chunk_iter) + { + assign_to_chunk(chunk_iter); + } + } + + template + auto xblockwise_reducer::get_input_chunk_iter(input_chunk_index_type input_chunk_index) const + -> input_const_chunked_iterator_type + { + std::size_t chunk_linear_index = 0; + for (std::size_t i = 0; i < m_e_chunked_view.dimension(); ++i) + { + chunk_linear_index += input_chunk_index[i] * m_input_grid_strides[i]; + } + return input_const_chunked_iterator_type(m_e_chunked_view, std::move(input_chunk_index), chunk_linear_index); + } + + template + template + void xblockwise_reducer::assign_to_chunk(CI& result_chunk_iter) const + { + auto result_chunk_view = *result_chunk_iter; + auto reduction_variable = m_functor.reduction_variable(result_chunk_view); + + // get the range of input chunks we need to compute the desired ouput chunk + auto range = compute_input_chunk_range(result_chunk_iter); + + // iterate over input chunk (indics) + auto first = true; + // std::for_each(std::get<0>(range), std::get<1>(range), [&](auto && input_chunk_index) + auto iter = std::get<0>(range); + while (iter != std::get<1>(range)) + { + const auto& input_chunk_index = *iter; + // get input chunk iterator from chunk index + auto chunked_input_iter = this->get_input_chunk_iter(input_chunk_index); + auto input_chunk_view = *chunked_input_iter; + + // compute the per block result + auto block_res = m_functor.compute(input_chunk_view, m_axes, m_options); + + // merge + m_functor.merge(block_res, first, result_chunk_view, reduction_variable); + first = false; + ++iter; + } + + // finalize (ie smth like normalization) + m_functor.finalize(reduction_variable, result_chunk_view, *this); + } + + template + template + auto xblockwise_reducer::compute_input_chunk_range(CI& result_chunk_iter) const + -> input_chunk_range_type + { + auto input_chunks_begin = xtl::make_sequence(m_e_chunked_view.dimension(), 0); + auto input_chunks_end = xtl::make_sequence(m_e_chunked_view.dimension()); + + XTENSOR_ASSERT(input_chunks_begin.size() == m_e_chunked_view.dimension()); + XTENSOR_ASSERT(input_chunks_end.size() == m_e_chunked_view.dimension()); + + std::copy( + m_e_chunked_view.grid_shape().begin(), + m_e_chunked_view.grid_shape().end(), + input_chunks_end.begin() + ); + + const auto& chunk_index = result_chunk_iter.chunk_index(); + for (std::size_t result_ax_index = 0; result_ax_index < m_result_shape.size(); ++result_ax_index) + { + if (m_result_shape[result_ax_index] != 1) + { + const auto input_ax_index = m_mapping[result_ax_index]; + input_chunks_begin[input_ax_index] = chunk_index[result_ax_index]; + input_chunks_end[input_ax_index] = chunk_index[result_ax_index] + 1; + } + } + return input_chunk_range_type{ + multiindex_iterator_begin(input_chunks_begin, input_chunks_end), + multiindex_iterator_end(input_chunks_begin, input_chunks_end)}; + } + + template + void xblockwise_reducer::init_shapes() + { + const auto& shape = m_e.shape(); + const auto dimension = m_e.dimension(); + const auto& block_shape = m_e_chunked_view.chunk_shape(); + if (xtl::mpl::contains::value) + { + resize_container(m_result_shape, dimension); + resize_container(m_result_chunk_shape, dimension); + resize_container(m_mapping, dimension); + for (std::size_t i = 0; i < dimension; ++i) + { + m_mapping[i] = i; + if (std::find(m_axes.begin(), m_axes.end(), i) == m_axes.end()) + { + // i not in m_axes! + m_result_shape[i] = shape[i]; + m_result_chunk_shape[i] = block_shape[i]; + } + else + { + m_result_shape[i] = 1; + m_result_chunk_shape[i] = 1; + } + } + } + else + { + const auto result_dim = dimension - m_axes.size(); + resize_container(m_result_shape, result_dim); + resize_container(m_result_chunk_shape, result_dim); + resize_container(m_mapping, result_dim); + + for (std::size_t i = 0, idx = 0; i < dimension; ++i) + { + if (std::find(m_axes.begin(), m_axes.end(), i) == m_axes.end()) + { + // i not in axes! + m_result_shape[idx] = shape[i]; + m_result_chunk_shape[idx] = block_shape[i]; + m_mapping[idx] = i; + ++idx; + } + } + } + } + + template + inline auto blockwise_reducer(E&& e, CS&& chunk_shape, A&& axes, O&& raw_options, FF&& functor) + { + using functor_type = std::decay_t; + using closure_type = xtl::const_closure_type_t; + using axes_type = std::decay_t; + + return xblockwise_reducer( + std::forward(e), + std::forward(chunk_shape), + std::forward(axes), + std::forward(raw_options), + std::forward(functor) + ); + } + + namespace blockwise + { + +#define XTENSOR_BLOCKWISE_REDUCER_FUNC(FNAME, FUNCTOR) \ + template < \ + class T = void, \ + class E, \ + class BS, \ + class X, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(xtl::negation>, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, X&& axes, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::forward(axes), \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template < \ + class T = void, \ + class E, \ + class BS, \ + class X, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(xtl::is_integral>)> \ + auto FNAME(E&& e, BS&& block_shape, X axis, O options = O()) \ + { \ + std::array axes{axis}; \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + axes, \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template < \ + class T = void, \ + class E, \ + class BS, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(is_reducer_options, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using axes_type = filter_fixed_shape_t; \ + axes_type axes = xtl::make_sequence(e.dimension()); \ + XTENSOR_ASSERT(axes.size() == e.dimension()); \ + std::iota(axes.begin(), axes.end(), 0); \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(axes), \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template \ + auto FNAME(E&& e, BS&& block_shape, const I(&axes)[N], O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + using axes_type = std::array; \ + auto ax = xt::forward_normalize(e, axes); \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(ax), \ + std::forward(options), \ + functor_type() \ + ); \ + } + XTENSOR_BLOCKWISE_REDUCER_FUNC(sum, xt::detail::blockwise::sum_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(prod, xt::detail::blockwise::prod_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(amin, xt::detail::blockwise::amin_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(amax, xt::detail::blockwise::amax_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(mean, xt::detail::blockwise::mean_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(variance, xt::detail::blockwise::variance_functor) + XTENSOR_BLOCKWISE_REDUCER_FUNC(stddev, xt::detail::blockwise::stddev_functor) + +#undef XTENSOR_BLOCKWISE_REDUCER_FUNC + + +// norm reducers do *not* allow to to pass a template +// parameter to specifiy the internal computation type +#define XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(FNAME, FUNCTOR) \ + template < \ + class E, \ + class BS, \ + class X, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(xtl::negation>, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, X&& axes, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::forward(axes), \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template >)> \ + auto FNAME(E&& e, BS&& block_shape, X axis, O options = O()) \ + { \ + std::array axes{axis}; \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + axes, \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template < \ + class E, \ + class BS, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(is_reducer_options, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using axes_type = filter_fixed_shape_t; \ + axes_type axes = xtl::make_sequence(e.dimension()); \ + XTENSOR_ASSERT(axes.size() == e.dimension()); \ + std::iota(axes.begin(), axes.end(), 0); \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(axes), \ + std::forward(options), \ + functor_type() \ + ); \ + } \ + template \ + auto FNAME(E&& e, BS&& block_shape, const I(&axes)[N], O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + using axes_type = std::array; \ + auto ax = xt::forward_normalize(e, axes); \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(ax), \ + std::forward(options), \ + functor_type() \ + ); \ + } + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l0, xt::detail::blockwise::norm_l0_functor) + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l1, xt::detail::blockwise::norm_l1_functor) + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l2, xt::detail::blockwise::norm_l2_functor) + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_sq, xt::detail::blockwise::norm_sq_functor) + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_linf, xt::detail::blockwise::norm_linf_functor) + +#undef XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC + + +#define XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(FNAME, FUNCTOR) \ + template < \ + class E, \ + class BS, \ + class X, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(xtl::negation>, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, double p, X&& axes, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::forward(axes), \ + std::forward(options), \ + functor_type(p) \ + ); \ + } \ + template >)> \ + auto FNAME(E&& e, BS&& block_shape, double p, X axis, O options = O()) \ + { \ + std::array axes{axis}; \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + axes, \ + std::forward(options), \ + functor_type(p) \ + ); \ + } \ + template < \ + class E, \ + class BS, \ + class O = DEFAULT_STRATEGY_REDUCERS, \ + XTL_REQUIRES(is_reducer_options, xtl::negation>>)> \ + auto FNAME(E&& e, BS&& block_shape, double p, O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using axes_type = filter_fixed_shape_t; \ + axes_type axes = xtl::make_sequence(e.dimension()); \ + XTENSOR_ASSERT(axes.size() == e.dimension()); \ + std::iota(axes.begin(), axes.end(), 0); \ + using functor_type = FUNCTOR; \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(axes), \ + std::forward(options), \ + functor_type(p) \ + ); \ + } \ + template \ + auto FNAME(E&& e, BS&& block_shape, double p, const I(&axes)[N], O options = O()) \ + { \ + using input_expression_type = std::decay_t; \ + using functor_type = FUNCTOR; \ + using axes_type = std::array; \ + auto ax = xt::forward_normalize(e, axes); \ + return blockwise_reducer( \ + std::forward(e), \ + std::forward(block_shape), \ + std::move(ax), \ + std::forward(options), \ + functor_type(p) \ + ); \ + } + + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_lp_to_p, xt::detail::blockwise::norm_lp_to_p_functor); + XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_lp, xt::detail::blockwise::norm_lp_functor); + +#undef XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC + } + +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xblockwise_reducer_functors.hpp b/contrib/xtensor/include/xtensor/xblockwise_reducer_functors.hpp new file mode 100644 index 00000000000..d976f3f02df --- /dev/null +++ b/contrib/xtensor/include/xtensor/xblockwise_reducer_functors.hpp @@ -0,0 +1,503 @@ +#ifndef XTENSOR_XBLOCKWISE_REDUCER_FUNCTORS_HPP +#define XTENSOR_XBLOCKWISE_REDUCER_FUNCTORS_HPP + + +#include +#include +#include +#include + +#include "xarray.hpp" +#include "xbuilder.hpp" +#include "xchunked_array.hpp" +#include "xchunked_assign.hpp" +#include "xchunked_view.hpp" +#include "xexpression.hpp" +#include "xmath.hpp" +#include "xnorm.hpp" +#include "xreducer.hpp" +#include "xtl/xclosure.hpp" +#include "xtl/xsequence.hpp" +#include "xutils.hpp" + +namespace xt +{ + namespace detail + { + namespace blockwise + { + + struct empty_reduction_variable + { + }; + + struct simple_functor_base + { + template + auto reduction_variable(const E&) const + { + return empty_reduction_variable(); + } + + template + void finalize(const MR&, E&, const R&) const + { + } + }; + + template + struct sum_functor : public simple_functor_base + { + using value_type = typename std::decay_t(std::declval>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(input, axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + }; + + template + struct prod_functor : public simple_functor_base + { + using value_type = typename std::decay_t(std::declval>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::prod(input, axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) *= block_result; + } + } + }; + + template + struct amin_functor : public simple_functor_base + { + using value_type = typename std::decay_t(std::declval>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::amin(input, axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) = xt::minimum(block_result, result); + } + } + }; + + template + struct amax_functor : public simple_functor_base + { + using value_type = typename std::decay_t(std::declval>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::amax(input, axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) = xt::maximum(block_result, result); + } + } + }; + + template + struct mean_functor + { + using value_type = typename std::decay_t(std::declval>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(input, axes, options); + } + + template + auto reduction_variable(const E&) const + { + return empty_reduction_variable(); + } + + template + auto merge(const BR& block_result, bool first, E& result, empty_reduction_variable&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + + template + void finalize(const empty_reduction_variable&, E& results, const R& reducer) const + { + const auto& axes = reducer.axes(); + std::decay_t factor = 1; + for (auto a : axes) + { + factor *= reducer.input_shape()[a]; + } + xt::noalias(results) /= static_cast(factor); + } + }; + + template + struct variance_functor + { + using value_type = typename std::decay_t(std::declval>()) + )>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + double weight = 1.0; + for (auto a : axes) + { + weight *= static_cast(input.shape()[a]); + } + + + return std::make_tuple( + xt::variance(input, axes, options), + xt::mean(input, axes, options), + weight + ); + } + + template + auto reduction_variable(const E&) const + { + return std::make_tuple(xarray(), 0.0); + } + + template + auto merge(const BR& block_result, bool first, E& variance_a, MR& mr) const + { + auto& mean_a = std::get<0>(mr); + auto& n_a = std::get<1>(mr); + + const auto& variance_b = std::get<0>(block_result); + const auto& mean_b = std::get<1>(block_result); + const auto& n_b = std::get<2>(block_result); + if (first) + { + xt::noalias(variance_a) = variance_b; + xt::noalias(mean_a) = mean_b; + n_a += n_b; + } + else + { + auto new_mean = (n_a * mean_a + n_b * mean_b) / (n_a + n_b); + auto new_variance = (n_a * variance_a + n_b * variance_b + + n_a * xt::pow(mean_a - new_mean, 2) + + n_b * xt::pow(mean_b - new_mean, 2)) + / (n_a + n_b); + xt::noalias(variance_a) = new_variance; + xt::noalias(mean_a) = new_mean; + n_a += n_b; + } + } + + template + void finalize(const MR&, E&, const R&) const + { + } + }; + + template + struct stddev_functor : public variance_functor + { + template + void finalize(const MR&, E& results, const R&) const + { + xt::noalias(results) = xt::sqrt(results); + } + }; + + template + struct norm_l0_functor : public simple_functor_base + { + using value_type = typename std::decay_t>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::not_equal(input, xt::zeros(input.shape())), axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + }; + + template + struct norm_l1_functor : public simple_functor_base + { + using value_type = typename std::decay_t>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::abs(input), axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + }; + + template + struct norm_l2_functor + { + using value_type = typename std::decay_t>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::square(input), axes, options); + } + + template + auto reduction_variable(const E&) const + { + return empty_reduction_variable(); + } + + template + auto merge(const BR& block_result, bool first, E& result, empty_reduction_variable&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + + template + void finalize(const empty_reduction_variable&, E& results, const R&) const + { + xt::noalias(results) = xt::sqrt(results); + } + }; + + template + struct norm_sq_functor : public simple_functor_base + { + using value_type = typename std::decay_t>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::square(input), axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + }; + + template + struct norm_linf_functor : public simple_functor_base + { + using value_type = typename std::decay_t>()))>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::amax(xt::abs(input), axes, options); + } + + template + auto merge(const BR& block_result, bool first, E& result, MR&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) = xt::maximum(block_result, result); + } + } + }; + + template + class norm_lp_to_p_functor + { + public: + + using value_type = typename std::decay_t< + decltype(xt::norm_lp_to_p(std::declval>(), 1.0))>::value_type; + + norm_lp_to_p_functor(double p) + : m_p(p) + { + } + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::pow(input, m_p), axes, options); + } + + template + auto reduction_variable(const E&) const + { + return empty_reduction_variable(); + } + + template + auto merge(const BR& block_result, bool first, E& result, empty_reduction_variable&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + + template + void finalize(const empty_reduction_variable&, E&, const R&) const + { + } + + private: + + double m_p; + }; + + template + class norm_lp_functor + { + public: + + norm_lp_functor(double p) + : m_p(p) + { + } + + using value_type = typename std::decay_t>(), 1.0) + )>::value_type; + + template + auto compute(const E& input, const A& axes, const O& options) const + { + return xt::sum(xt::pow(input, m_p), axes, options); + } + + template + auto reduction_variable(const E&) const + { + return empty_reduction_variable(); + } + + template + auto merge(const BR& block_result, bool first, E& result, empty_reduction_variable&) const + { + if (first) + { + xt::noalias(result) = block_result; + } + else + { + xt::noalias(result) += block_result; + } + } + + template + void finalize(const empty_reduction_variable&, E& results, const R&) const + { + results = xt::pow(results, 1.0 / m_p); + } + + private: + + double m_p; + }; + + + } + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xbroadcast.hpp b/contrib/xtensor/include/xtensor/xbroadcast.hpp new file mode 100644 index 00000000000..c847970ad97 --- /dev/null +++ b/contrib/xtensor/include/xtensor/xbroadcast.hpp @@ -0,0 +1,459 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_BROADCAST_HPP +#define XTENSOR_BROADCAST_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xaccessible.hpp" +#include "xexpression.hpp" +#include "xiterable.hpp" +#include "xscalar.hpp" +#include "xstrides.hpp" +#include "xtensor_config.hpp" +#include "xutils.hpp" + +namespace xt +{ + + /************* + * broadcast * + *************/ + + template + auto broadcast(E&& e, const S& s); + + template + auto broadcast(E&& e, const I (&s)[L]); + + /************************* + * xbroadcast extensions * + *************************/ + + namespace extension + { + template + struct xbroadcast_base_impl; + + template + struct xbroadcast_base_impl + { + using type = xtensor_empty_base; + }; + + template + struct xbroadcast_base : xbroadcast_base_impl, CT, X> + { + }; + + template + using xbroadcast_base_t = typename xbroadcast_base::type; + } + + /************** + * xbroadcast * + **************/ + + template + class xbroadcast; + + template + struct xiterable_inner_types> + { + using xexpression_type = std::decay_t; + using inner_shape_type = promote_shape_t; + using const_stepper = typename xexpression_type::const_stepper; + using stepper = const_stepper; + }; + + template + struct xcontainer_inner_types> + { + using xexpression_type = std::decay_t; + using reference = typename xexpression_type::const_reference; + using const_reference = typename xexpression_type::const_reference; + using size_type = typename xexpression_type::size_type; + }; + + /***************************** + * linear_begin / linear_end * + *****************************/ + + template + XTENSOR_CONSTEXPR_RETURN auto linear_begin(xbroadcast& c) noexcept + { + return linear_begin(c.expression()); + } + + template + XTENSOR_CONSTEXPR_RETURN auto linear_end(xbroadcast& c) noexcept + { + return linear_end(c.expression()); + } + + template + XTENSOR_CONSTEXPR_RETURN auto linear_begin(const xbroadcast& c) noexcept + { + return linear_begin(c.expression()); + } + + template + XTENSOR_CONSTEXPR_RETURN auto linear_end(const xbroadcast& c) noexcept + { + return linear_end(c.expression()); + } + + /** + * @class xbroadcast + * @brief Broadcasted xexpression to a specified shape. + * + * The xbroadcast class implements the broadcasting of an \ref xexpression + * to a specified shape. xbroadcast is not meant to be used directly, but + * only with the \ref broadcast helper functions. + * + * @tparam CT the closure type of the \ref xexpression to broadcast + * @tparam X the type of the specified shape. + * + * @sa broadcast + */ + template + class xbroadcast : public xsharable_expression>, + public xconst_iterable>, + public xconst_accessible>, + public extension::xbroadcast_base_t + { + public: + + using self_type = xbroadcast; + using xexpression_type = std::decay_t; + using accessible_base = xconst_accessible; + using extension_base = extension::xbroadcast_base_t; + using expression_tag = typename extension_base::expression_tag; + + using inner_types = xcontainer_inner_types; + using value_type = typename xexpression_type::value_type; + using reference = typename inner_types::reference; + using const_reference = typename inner_types::const_reference; + using pointer = typename xexpression_type::const_pointer; + using const_pointer = typename xexpression_type::const_pointer; + using size_type = typename inner_types::size_type; + using difference_type = typename xexpression_type::difference_type; + + using iterable_base = xconst_iterable; + using inner_shape_type = typename iterable_base::inner_shape_type; + using shape_type = inner_shape_type; + + using stepper = typename iterable_base::stepper; + using const_stepper = typename iterable_base::const_stepper; + + using bool_load_type = typename xexpression_type::bool_load_type; + + static constexpr layout_type static_layout = layout_type::dynamic; + static constexpr bool contiguous_layout = false; + + template + xbroadcast(CTA&& e, const S& s); + + template + xbroadcast(CTA&& e, shape_type&& s); + + using accessible_base::size; + const inner_shape_type& shape() const noexcept; + layout_type layout() const noexcept; + bool is_contiguous() const noexcept; + using accessible_base::shape; + + template + const_reference operator()(Args... args) const; + + template + const_reference unchecked(Args... args) const; + + template + const_reference element(It first, It last) const; + + const xexpression_type& expression() const noexcept; + + template + bool broadcast_shape(S& shape, bool reuse_cache = false) const; + + template + bool has_linear_assign(const S& strides) const noexcept; + + template + const_stepper stepper_begin(const S& shape) const noexcept; + template + const_stepper stepper_end(const S& shape, layout_type l) const noexcept; + + template ::value>> + void assign_to(xexpression& e) const; + + template + using rebind_t = xbroadcast; + + template + rebind_t build_broadcast(E&& e) const; + + private: + + CT m_e; + inner_shape_type m_shape; + }; + + /**************************** + * broadcast implementation * + ****************************/ + + /** + * @brief Returns an \ref xexpression broadcasting the given expression to + * a specified shape. + * + * @tparam e the \ref xexpression to broadcast + * @tparam s the specified shape to broadcast. + * + * The returned expression either hold a const reference to \p e or a copy + * depending on whether \p e is an lvalue or an rvalue. + */ + template + inline auto broadcast(E&& e, const S& s) + { + using shape_type = filter_fixed_shape_t>; + using broadcast_type = xbroadcast, shape_type>; + return broadcast_type(std::forward(e), xtl::forward_sequence(s)); + } + + template + inline auto broadcast(E&& e, const I (&s)[L]) + { + using broadcast_type = xbroadcast, std::array>; + using shape_type = typename broadcast_type::shape_type; + return broadcast_type(std::forward(e), xtl::forward_sequence(s)); + } + + /***************************** + * xbroadcast implementation * + *****************************/ + + /** + * @name Constructor + */ + //@{ + /** + * Constructs an xbroadcast expression broadcasting the specified + * \ref xexpression to the given shape + * + * @param e the expression to broadcast + * @param s the shape to apply + */ + template + template + inline xbroadcast::xbroadcast(CTA&& e, const S& s) + : m_e(std::forward(e)) + { + if (s.size() < m_e.dimension()) + { + XTENSOR_THROW(xt::broadcast_error, "Broadcast shape has fewer elements than original expression."); + } + xt::resize_container(m_shape, s.size()); + std::copy(s.begin(), s.end(), m_shape.begin()); + xt::broadcast_shape(m_e.shape(), m_shape); + } + + /** + * Constructs an xbroadcast expression broadcasting the specified + * \ref xexpression to the given shape + * + * @param e the expression to broadcast + * @param s the shape to apply + */ + template + template + inline xbroadcast::xbroadcast(CTA&& e, shape_type&& s) + : m_e(std::forward(e)) + , m_shape(std::move(s)) + { + xt::broadcast_shape(m_e.shape(), m_shape); + } + + //@} + + /** + * @name Size and shape + */ + //@{ + /** + * Returns the shape of the expression. + */ + template + inline auto xbroadcast::shape() const noexcept -> const inner_shape_type& + { + return m_shape; + } + + /** + * Returns the layout_type of the expression. + */ + template + inline layout_type xbroadcast::layout() const noexcept + { + return m_e.layout(); + } + + template + inline bool xbroadcast::is_contiguous() const noexcept + { + return false; + } + + //@} + + /** + * @name Data + */ + //@{ + /** + * Returns a constant reference to the element at the specified position in the expression. + * @param args a list of indices specifying the position in the function. Indices + * must be unsigned integers, the number of indices should be equal or greater than + * the number of dimensions of the expression. + */ + template + template + inline auto xbroadcast::operator()(Args... args) const -> const_reference + { + return m_e(args...); + } + + /** + * Returns a constant reference to the element at the specified position in the expression. + * @param args a list of indices specifying the position in the expression. Indices + * must be unsigned integers, the number of indices must be equal to the number of + * dimensions of the expression, else the behavior is undefined. + * + * @warning This method is meant for performance, for expressions with a dynamic + * number of dimensions (i.e. not known at compile time). Since it may have + * undefined behavior (see parameters), operator() should be preferred whenever + * it is possible. + * @warning This method is NOT compatible with broadcasting, meaning the following + * code has undefined behavior: + * \code{.cpp} + * xt::xarray a = {{0, 1}, {2, 3}}; + * xt::xarray b = {0, 1}; + * auto fd = a + b; + * double res = fd.uncheked(0, 1); + * \endcode + */ + template + template + inline auto xbroadcast::unchecked(Args... args) const -> const_reference + { + return this->operator()(args...); + } + + /** + * Returns a constant reference to the element at the specified position in the expression. + * @param first iterator starting the sequence of indices + * @param last iterator ending the sequence of indices + * The number of indices in the sequence should be equal to or greater + * than the number of dimensions of the function. + */ + template + template + inline auto xbroadcast::element(It, It last) const -> const_reference + { + return m_e.element(last - this->dimension(), last); + } + + /** + * Returns a constant reference to the underlying expression of the broadcast expression. + */ + template + inline auto xbroadcast::expression() const noexcept -> const xexpression_type& + { + return m_e; + } + + //@} + + /** + * @name Broadcasting + */ + //@{ + /** + * Broadcast the shape of the function to the specified parameter. + * @param shape the result shape + * @param reuse_cache parameter for internal optimization + * @return a boolean indicating whether the broadcasting is trivial + */ + template + template + inline bool xbroadcast::broadcast_shape(S& shape, bool) const + { + return xt::broadcast_shape(m_shape, shape); + } + + /** + * Checks whether the xbroadcast can be linearly assigned to an expression + * with the specified strides. + * @return a boolean indicating whether a linear assign is possible + */ + template + template + inline bool xbroadcast::has_linear_assign(const S& strides) const noexcept + { + return this->dimension() == m_e.dimension() + && std::equal(m_shape.cbegin(), m_shape.cend(), m_e.shape().cbegin()) + && m_e.has_linear_assign(strides); + } + + //@} + + template + template + inline auto xbroadcast::stepper_begin(const S& shape) const noexcept -> const_stepper + { + // Could check if (broadcastable(shape, m_shape) + return m_e.stepper_begin(shape); + } + + template + template + inline auto xbroadcast::stepper_end(const S& shape, layout_type l) const noexcept -> const_stepper + { + // Could check if (broadcastable(shape, m_shape) + return m_e.stepper_end(shape, l); + } + + template + template + inline void xbroadcast::assign_to(xexpression& e) const + { + auto& ed = e.derived_cast(); + ed.resize(m_shape); + std::fill(ed.begin(), ed.end(), m_e()); + } + + template + template + inline auto xbroadcast::build_broadcast(E&& e) const -> rebind_t + { + return rebind_t(std::forward(e), inner_shape_type(m_shape)); + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xbuffer_adaptor.hpp b/contrib/xtensor/include/xtensor/xbuffer_adaptor.hpp new file mode 100644 index 00000000000..368da58676b --- /dev/null +++ b/contrib/xtensor/include/xtensor/xbuffer_adaptor.hpp @@ -0,0 +1,1282 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XTENSOR_BUFFER_ADAPTOR_HPP +#define XTENSOR_BUFFER_ADAPTOR_HPP + +#include +#include +#include +#include +#include + +#include + +#include "xstorage.hpp" +#include "xtensor_config.hpp" + +namespace xt +{ + + struct no_ownership + { + }; + + using smart_ownership = no_ownership; + + struct acquire_ownership + { + }; + + template >>> + class xbuffer_adaptor; + + /******************** + * buffer_storage_t * + ********************/ + + namespace detail + { + template + class xbuffer_storage + { + public: + + using self_type = xbuffer_storage; + using allocator_type = A; + using destructor_type = allocator_type; + using allocator_traits = std::allocator_traits; + using value_type = typename allocator_traits::value_type; + using reference = std::conditional_t< + std::is_const>>::value, + const value_type&, + value_type&>; + using const_reference = const value_type&; + using pointer = std::conditional_t< + std::is_const>>::value, + typename allocator_traits::const_pointer, + typename allocator_traits::pointer>; + using const_pointer = typename allocator_traits::const_pointer; + using size_type = typename allocator_traits::size_type; + using difference_type = typename allocator_traits::difference_type; + + xbuffer_storage(); + + template + xbuffer_storage(P&& data, size_type size, const allocator_type& alloc = allocator_type()); + + size_type size() const noexcept; + void resize(size_type size); + + pointer data() noexcept; + const_pointer data() const noexcept; + + void swap(self_type& rhs) noexcept; + + template + void reset_data(P&& data, size_type size) noexcept; + + private: + + pointer p_data; + size_type m_size; + }; + + template + class xbuffer_smart_pointer + { + public: + + using self_type = xbuffer_storage; + using destructor_type = D; + using value_type = std::remove_const_t>>; + using allocator_type = std::allocator; + using allocator_traits = std::allocator_traits; + using reference = std::conditional_t< + std::is_const>>::value, + const value_type&, + value_type&>; + using const_reference = const value_type&; + using pointer = std::conditional_t< + std::is_const>>::value, + typename allocator_traits::const_pointer, + typename allocator_traits::pointer>; + using const_pointer = typename allocator_traits::const_pointer; + using size_type = typename allocator_traits::size_type; + using difference_type = typename allocator_traits::difference_type; + + xbuffer_smart_pointer(); + + template + xbuffer_smart_pointer(P&& data_ptr, size_type size, DT&& destruct); + + size_type size() const noexcept; + void resize(size_type size); + + pointer data() noexcept; + const_pointer data() const noexcept; + + void swap(self_type& rhs) noexcept; + + template + void reset_data(P&& data, size_type size, DT&& destruct) noexcept; + + private: + + pointer p_data; + size_type m_size; + destructor_type m_destruct; + }; + + template + class xbuffer_owner_storage + { + public: + + using self_type = xbuffer_owner_storage; + using allocator_type = A; + using destructor_type = allocator_type; + using allocator_traits = std::allocator_traits; + using value_type = typename allocator_traits::value_type; + using reference = std::conditional_t< + std::is_const>>::value, + const value_type&, + value_type&>; + using const_reference = const value_type&; + using pointer = std::conditional_t< + std::is_const>>::value, + typename allocator_traits::const_pointer, + typename allocator_traits::pointer>; + using const_pointer = typename allocator_traits::const_pointer; + using size_type = typename allocator_traits::size_type; + using difference_type = typename allocator_traits::difference_type; + + xbuffer_owner_storage() = default; + + template + xbuffer_owner_storage(P&& data, size_type size, const allocator_type& alloc = allocator_type()); + + ~xbuffer_owner_storage(); + + xbuffer_owner_storage(const self_type&) = delete; + self_type& operator=(const self_type&); + + xbuffer_owner_storage(self_type&&); + self_type& operator=(self_type&&); + + size_type size() const noexcept; + void resize(size_type size); + + pointer data() noexcept; + const_pointer data() const noexcept; + + allocator_type get_allocator() const noexcept; + + void swap(self_type& rhs) noexcept; + + template + void reset_data(P&& data, size_type size, const allocator_type& alloc = allocator_type()) noexcept; + + private: + + xtl::xclosure_wrapper m_data; + size_type m_size; + bool m_moved_from; + allocator_type m_allocator; + }; + + // Workaround for MSVC2015: using void_t results in some + // template instantiation caching that leads to wrong + // type deduction later in xfunction. + template + struct msvc2015_void + { + using type = void; + }; + + template + using msvc2015_void_t = typename msvc2015_void::type; + + template + struct is_lambda_type : std::false_type + { + }; + + // check if operator() is available + template + struct is_lambda_type> : std::true_type + { + }; + + template + struct self_type + { + using type = T; + }; + + template + struct get_buffer_storage + { + using type = xtl::mpl::eval_if_t< + is_lambda_type, + self_type>, + self_type>>; + }; + + template + struct get_buffer_storage + { + using type = xbuffer_owner_storage; + }; + + template + struct get_buffer_storage, no_ownership> + { + using type = xbuffer_smart_pointer>; + }; + + template + struct get_buffer_storage, no_ownership> + { + using type = xbuffer_smart_pointer>; + }; + + template + using buffer_storage_t = typename get_buffer_storage::type; + } + + /************************ + * xbuffer_adaptor_base * + ************************/ + + template + struct buffer_inner_types; + + template + class xbuffer_adaptor_base + { + public: + + using self_type = xbuffer_adaptor_base; + using derived_type = D; + using inner_types = buffer_inner_types; + using value_type = typename inner_types::value_type; + using reference = typename inner_types::reference; + using const_reference = typename inner_types::const_reference; + using pointer = typename inner_types::pointer; + using const_pointer = typename inner_types::const_pointer; + using size_type = typename inner_types::size_type; + using difference_type = typename inner_types::difference_type; + using iterator = typename inner_types::iterator; + using const_iterator = typename inner_types::const_iterator; + using reverse_iterator = typename inner_types::reverse_iterator; + using const_reverse_iterator = typename inner_types::const_reverse_iterator; + using index_type = typename inner_types::index_type; + + bool empty() const noexcept; + + reference operator[](size_type i); + const_reference operator[](size_type i) const; + + reference front(); + const_reference front() const; + + reference back(); + const_reference back() const; + + iterator begin() noexcept; + iterator end() noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + reverse_iterator rbegin() noexcept; + reverse_iterator rend() noexcept; + + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + derived_type& derived_cast() noexcept; + const derived_type& derived_cast() const noexcept; + + protected: + + xbuffer_adaptor_base() = default; + ~xbuffer_adaptor_base() = default; + + xbuffer_adaptor_base(const self_type&) = default; + self_type& operator=(const self_type&) = default; + + xbuffer_adaptor_base(self_type&&) = default; + self_type& operator=(self_type&&) = default; + }; + + template + bool operator==(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + template + bool operator!=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + template + bool operator<(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + template + bool operator<=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + template + bool operator>(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + template + bool operator>=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs); + + /******************* + * xbuffer_adaptor * + *******************/ + + template + struct buffer_inner_types> + { + using base_type = detail::buffer_storage_t; + using value_type = typename base_type::value_type; + using reference = typename base_type::reference; + using const_reference = typename base_type::const_reference; + using pointer = typename base_type::pointer; + using const_pointer = typename base_type::const_pointer; + using size_type = typename base_type::size_type; + using difference_type = typename base_type::difference_type; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using index_type = size_type; + }; + + template + class xbuffer_adaptor : private detail::buffer_storage_t, + public xbuffer_adaptor_base> + { + public: + + using self_type = xbuffer_adaptor; + using base_type = detail::buffer_storage_t; + using buffer_base_type = xbuffer_adaptor_base; + using allocator_type = typename base_type::allocator_type; + using destructor_type = typename base_type::destructor_type; + using value_type = typename buffer_base_type::value_type; + using reference = typename buffer_base_type::reference; + using const_reference = typename buffer_base_type::const_reference; + using pointer = typename buffer_base_type::pointer; + using const_pointer = typename buffer_base_type::const_pointer; + using size_type = typename buffer_base_type::size_type; + using difference_type = typename buffer_base_type::difference_type; + using iterator = typename buffer_base_type::iterator; + using const_iterator = typename buffer_base_type::const_iterator; + using reverse_iterator = typename buffer_base_type::reverse_iterator; + using const_reverse_iterator = typename buffer_base_type::const_reverse_iterator; + using temporary_type = uvector; + + xbuffer_adaptor() = default; + + using base_type::base_type; + + ~xbuffer_adaptor() = default; + + xbuffer_adaptor(const self_type&) = default; + self_type& operator=(const self_type&) = default; + + xbuffer_adaptor(self_type&&) = default; + xbuffer_adaptor& operator=(self_type&&) = default; + + self_type& operator=(temporary_type&&); + + using base_type::data; + using base_type::reset_data; + using base_type::resize; + using base_type::size; + using base_type::swap; + }; + + template + void swap(xbuffer_adaptor& lhs, xbuffer_adaptor& rhs) noexcept; + + /********************* + * xiterator_adaptor * + *********************/ + + template + class xiterator_adaptor; + + template + struct buffer_inner_types> + { + using traits = std::iterator_traits; + using const_traits = std::iterator_traits; + + using value_type = std::common_type_t; + using reference = typename traits::reference; + using const_reference = typename const_traits::reference; + using pointer = typename traits::pointer; + using const_pointer = typename const_traits::pointer; + using difference_type = std::common_type_t; + using size_type = std::make_unsigned_t; + + using iterator = I; + using const_iterator = CI; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using index_type = difference_type; + }; + + template + class xiterator_adaptor : public xbuffer_adaptor_base> + { + public: + + using self_type = xiterator_adaptor; + using base_type = xbuffer_adaptor_base; + using value_type = typename base_type::value_type; + using allocator_type = std::allocator; + using size_type = typename base_type::size_type; + using iterator = typename base_type::iterator; + using const_iterator = typename base_type::const_iterator; + using temporary_type = uvector; + + xiterator_adaptor() = default; + xiterator_adaptor(I it, CI cit, size_type size); + + ~xiterator_adaptor() = default; + + xiterator_adaptor(const self_type&) = default; + xiterator_adaptor& operator=(const self_type&) = default; + + xiterator_adaptor(self_type&&) = default; + xiterator_adaptor& operator=(self_type&&) = default; + + xiterator_adaptor& operator=(const temporary_type& rhs); + xiterator_adaptor& operator=(temporary_type&& rhs); + + size_type size() const noexcept; + void resize(size_type size); + + iterator data() noexcept; + const_iterator data() const noexcept; + + void swap(self_type& rhs) noexcept; + + private: + + I m_it; + CI m_cit; + size_type m_size; + }; + + template + void swap(xiterator_adaptor& lhs, xiterator_adaptor& rhs) noexcept; + + template + struct is_contiguous_container> : is_contiguous_container + { + }; + + /*************************** + * xiterator_owner_adaptor * + ***************************/ + + template + class xiterator_owner_adaptor; + + template + struct buffer_inner_types> + { + using iterator = typename IG::iterator; + using const_iterator = typename IG::const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + using traits = std::iterator_traits; + using const_traits = std::iterator_traits; + + using value_type = std::common_type_t; + using reference = typename traits::reference; + using const_reference = typename const_traits::reference; + using pointer = typename traits::pointer; + using const_pointer = typename const_traits::pointer; + using difference_type = std::common_type_t; + using size_type = std::make_unsigned_t; + using index_type = difference_type; + }; + + template + class xiterator_owner_adaptor : public xbuffer_adaptor_base> + { + public: + + using self_type = xiterator_owner_adaptor; + using base_type = xbuffer_adaptor_base; + using value_type = typename base_type::value_type; + using allocator_type = std::allocator; + using size_type = typename base_type::size_type; + using iterator = typename base_type::iterator; + using const_iterator = typename base_type::const_iterator; + using temporary_type = uvector; + + xiterator_owner_adaptor(C&& c); + + ~xiterator_owner_adaptor() = default; + + xiterator_owner_adaptor(const self_type&); + xiterator_owner_adaptor& operator=(const self_type&); + + xiterator_owner_adaptor(self_type&&); + xiterator_owner_adaptor& operator=(self_type&&); + + xiterator_owner_adaptor& operator=(const temporary_type& rhs); + xiterator_owner_adaptor& operator=(temporary_type&& rhs); + + size_type size() const noexcept; + void resize(size_type size); + + iterator data() noexcept; + const_iterator data() const noexcept; + + void swap(self_type& rhs) noexcept; + + private: + + void init_iterators(); + + C m_container; + iterator m_it; + const_iterator m_cit; + size_type m_size; + }; + + template + void swap(xiterator_owner_adaptor& lhs, xiterator_owner_adaptor& rhs) noexcept; + + template + struct is_contiguous_container> + : is_contiguous_container + { + }; + + /************************** + * make_xiterator_adaptor * + **************************/ + + template + auto make_xiterator_adaptor(C&& container, IG iterator_getter); + + /************************************ + * temporary_container metafunction * + ************************************/ + + template + struct temporary_container + { + using type = C; + }; + + template + struct temporary_container> + { + using type = typename xbuffer_adaptor::temporary_type; + }; + + template + struct temporary_container> + { + using type = typename xiterator_adaptor::temporary_type; + }; + + template + struct temporary_container> + { + using type = typename xiterator_owner_adaptor::temporary_type; + }; + + template + using temporary_container_t = typename temporary_container::type; + + /********************************** + * xbuffer_storage implementation * + **********************************/ + + namespace detail + { + template + inline xbuffer_storage::xbuffer_storage() + : p_data(nullptr) + , m_size(0) + { + } + + template + template + inline xbuffer_storage::xbuffer_storage(P&& data, size_type size, const allocator_type&) + : p_data(std::forward

(data)) + , m_size(size) + { + } + + template + inline auto xbuffer_storage::size() const noexcept -> size_type + { + return m_size; + } + + template + inline void xbuffer_storage::resize(size_type size) + { + if (size != m_size) + { + XTENSOR_THROW(std::runtime_error, "xbuffer_storage not resizable"); + } + } + + template + inline auto xbuffer_storage::data() noexcept -> pointer + { + return p_data; + } + + template + inline auto xbuffer_storage::data() const noexcept -> const_pointer + { + return p_data; + } + + template + inline void xbuffer_storage::swap(self_type& rhs) noexcept + { + using std::swap; + swap(p_data, rhs.p_data); + swap(m_size, rhs.m_size); + } + + template + template + inline void xbuffer_storage::reset_data(P&& data, size_type size) noexcept + { + p_data = std::forward

(data); + m_size = size; + } + } + + /**************************************** + * xbuffer_owner_storage implementation * + ****************************************/ + + namespace detail + { + template + template + inline xbuffer_owner_storage::xbuffer_owner_storage(P&& data, size_type size, const allocator_type& alloc) + : m_data(std::forward

(data)) + , m_size(size) + , m_moved_from(false) + , m_allocator(alloc) + { + } + + template + inline xbuffer_owner_storage::~xbuffer_owner_storage() + { + if (!m_moved_from) + { + safe_destroy_deallocate(m_allocator, m_data.get(), m_size); + m_size = 0; + } + } + + template + inline auto xbuffer_owner_storage::operator=(const self_type& rhs) -> self_type& + { + using std::swap; + if (this != &rhs) + { + allocator_type al = std::allocator_traits::select_on_container_copy_construction( + rhs.get_allocator() + ); + pointer tmp = safe_init_allocate(al, rhs.m_size); + if (xtrivially_default_constructible::value) + { + std::uninitialized_copy(rhs.m_data.get(), rhs.m_data.get() + rhs.m_size, tmp); + } + else + { + std::copy(rhs.m_data.get(), rhs.m_data.get() + rhs.m_size, tmp); + } + swap(m_data.get(), tmp); + swap(m_allocator, al); + safe_destroy_deallocate(al, tmp, m_size); + m_size = rhs.m_size; + } + return *this; + } + + template + inline xbuffer_owner_storage::xbuffer_owner_storage(self_type&& rhs) + : m_data(std::move(rhs.m_data)) + , m_size(std::move(rhs.m_size)) + , m_moved_from(std::move(rhs.m_moved_from)) + , m_allocator(std::move(rhs.m_allocator)) + { + rhs.m_moved_from = true; + rhs.m_size = 0; + } + + template + inline auto xbuffer_owner_storage::operator=(self_type&& rhs) -> self_type& + { + swap(rhs); + return *this; + } + + template + inline auto xbuffer_owner_storage::size() const noexcept -> size_type + { + return m_size; + } + + template + void xbuffer_owner_storage::resize(size_type size) + { + using std::swap; + if (size != m_size) + { + pointer tmp = safe_init_allocate(m_allocator, size); + swap(m_data.get(), tmp); + swap(m_size, size); + safe_destroy_deallocate(m_allocator, tmp, size); + } + } + + template + inline auto xbuffer_owner_storage::data() noexcept -> pointer + { + return m_data.get(); + } + + template + inline auto xbuffer_owner_storage::data() const noexcept -> const_pointer + { + return m_data.get(); + } + + template + inline auto xbuffer_owner_storage::get_allocator() const noexcept -> allocator_type + { + return allocator_type(m_allocator); + } + + template + inline void xbuffer_owner_storage::swap(self_type& rhs) noexcept + { + using std::swap; + swap(m_data, rhs.m_data); + swap(m_size, rhs.m_size); + swap(m_allocator, rhs.m_allocator); + } + + template + template + inline void + xbuffer_owner_storage::reset_data(P&& data, size_type size, const allocator_type& alloc) noexcept + { + xbuffer_owner_storage tmp(std::forward

(data), size, alloc); + this->swap(tmp); + } + } + + /**************************************** + * xbuffer_smart_pointer implementation * + ****************************************/ + + namespace detail + { + template + template + xbuffer_smart_pointer::xbuffer_smart_pointer(P&& data_ptr, size_type size, DT&& destruct) + : p_data(data_ptr) + , m_size(size) + , m_destruct(std::forward

(destruct)) + { + } + + template + auto xbuffer_smart_pointer::size() const noexcept -> size_type + { + return m_size; + } + + template + void xbuffer_smart_pointer::resize(size_type size) + { + if (m_size != size) + { + XTENSOR_THROW(std::runtime_error, "xbuffer_storage not resizeable"); + } + } + + template + auto xbuffer_smart_pointer::data() noexcept -> pointer + { + return p_data; + } + + template + auto xbuffer_smart_pointer::data() const noexcept -> const_pointer + { + return p_data; + } + + template + void xbuffer_smart_pointer::swap(self_type& rhs) noexcept + { + using std::swap; + swap(p_data, rhs.p_data); + swap(m_size, rhs.m_size); + swap(m_destruct, rhs.m_destruct); + } + + template + template + void xbuffer_smart_pointer::reset_data(P&& data, size_type size, DT&& destruct) noexcept + { + p_data = std::forward

(data); + m_size = size; + m_destruct = destruct; + } + } + + /*************************************** + * xbuffer_adaptor_base implementation * + ***************************************/ + + template + inline bool xbuffer_adaptor_base::empty() const noexcept + { + return derived_cast().size() == size_type(0); + } + + template + inline auto xbuffer_adaptor_base::operator[](size_type i) -> reference + { + return derived_cast().data()[static_cast(i)]; + } + + template + inline auto xbuffer_adaptor_base::operator[](size_type i) const -> const_reference + { + return derived_cast().data()[static_cast(i)]; + } + + template + inline auto xbuffer_adaptor_base::front() -> reference + { + return this->operator[](0); + } + + template + inline auto xbuffer_adaptor_base::front() const -> const_reference + { + return this->operator[](0); + } + + template + inline auto xbuffer_adaptor_base::back() -> reference + { + return this->operator[](derived_cast().size() - 1); + } + + template + inline auto xbuffer_adaptor_base::back() const -> const_reference + { + return this->operator[](derived_cast().size() - 1); + } + + template + inline auto xbuffer_adaptor_base::begin() noexcept -> iterator + { + return derived_cast().data(); + } + + template + inline auto xbuffer_adaptor_base::end() noexcept -> iterator + { + return derived_cast().data() + static_cast(derived_cast().size()); + } + + template + inline auto xbuffer_adaptor_base::begin() const noexcept -> const_iterator + { + return derived_cast().data(); + } + + template + inline auto xbuffer_adaptor_base::end() const noexcept -> const_iterator + { + return derived_cast().data() + static_cast(derived_cast().size()); + } + + template + inline auto xbuffer_adaptor_base::cbegin() const noexcept -> const_iterator + { + return begin(); + } + + template + inline auto xbuffer_adaptor_base::cend() const noexcept -> const_iterator + { + return end(); + } + + template + inline auto xbuffer_adaptor_base::rbegin() noexcept -> reverse_iterator + { + return reverse_iterator(end()); + } + + template + inline auto xbuffer_adaptor_base::rend() noexcept -> reverse_iterator + { + return reverse_iterator(begin()); + } + + template + inline auto xbuffer_adaptor_base::rbegin() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator(end()); + } + + template + inline auto xbuffer_adaptor_base::rend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator(begin()); + } + + template + inline auto xbuffer_adaptor_base::crbegin() const noexcept -> const_reverse_iterator + { + return rbegin(); + } + + template + inline auto xbuffer_adaptor_base::crend() const noexcept -> const_reverse_iterator + { + return rend(); + } + + template + inline auto xbuffer_adaptor_base::derived_cast() noexcept -> derived_type& + { + return *static_cast(this); + } + + template + inline auto xbuffer_adaptor_base::derived_cast() const noexcept -> const derived_type& + { + return *static_cast(this); + } + + template + inline bool operator==(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return lhs.derived_cast().size() == rhs.derived_cast().size() + && std::equal(lhs.begin(), lhs.end(), rhs.begin()); + } + + template + inline bool operator!=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return !(lhs == rhs); + } + + template + inline bool operator<(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return std::lexicographical_compare( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + std::less() + ); + } + + template + inline bool operator<=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return std::lexicographical_compare( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + std::less_equal() + ); + } + + template + inline bool operator>(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return std::lexicographical_compare( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + std::greater() + ); + } + + template + inline bool operator>=(const xbuffer_adaptor_base& lhs, const xbuffer_adaptor_base& rhs) + { + return std::lexicographical_compare( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + std::greater_equal() + ); + } + + /********************************** + * xbuffer_adaptor implementation * + **********************************/ + + template + inline auto xbuffer_adaptor::operator=(temporary_type&& tmp) -> self_type& + { + base_type::resize(tmp.size()); + std::copy(tmp.cbegin(), tmp.cend(), this->begin()); + return *this; + } + + template + inline void swap(xbuffer_adaptor& lhs, xbuffer_adaptor& rhs) noexcept + { + lhs.swap(rhs); + } + + /************************************ + * xiterator_adaptor implementation * + ************************************/ + + template + inline xiterator_adaptor::xiterator_adaptor(I it, CI cit, size_type size) + : m_it(it) + , m_cit(cit) + , m_size(size) + { + } + + template + inline auto xiterator_adaptor::operator=(const temporary_type& rhs) -> self_type& + { + resize(rhs.size()); + std::copy(rhs.cbegin(), rhs.cend(), m_it); + return *this; + } + + template + inline auto xiterator_adaptor::operator=(temporary_type&& rhs) -> self_type& + { + return (*this = rhs); + } + + template + inline auto xiterator_adaptor::size() const noexcept -> size_type + { + return m_size; + } + + template + inline void xiterator_adaptor::resize(size_type size) + { + if (m_size != size) + { + XTENSOR_THROW(std::runtime_error, "xiterator_adaptor not resizeable"); + } + } + + template + inline auto xiterator_adaptor::data() noexcept -> iterator + { + return m_it; + } + + template + inline auto xiterator_adaptor::data() const noexcept -> const_iterator + { + return m_cit; + } + + template + inline void xiterator_adaptor::swap(self_type& rhs) noexcept + { + using std::swap; + swap(m_it, rhs.m_it); + swap(m_cit, rhs.m_cit); + swap(m_size, rhs.m_size); + } + + template + inline void swap(xiterator_adaptor& lhs, xiterator_adaptor& rhs) noexcept + { + lhs.swap(rhs); + } + + /****************************************** + * xiterator_owner_adaptor implementation * + ******************************************/ + + template + inline xiterator_owner_adaptor::xiterator_owner_adaptor(C&& c) + : m_container(std::move(c)) + { + init_iterators(); + } + + template + inline xiterator_owner_adaptor::xiterator_owner_adaptor(const self_type& rhs) + : m_container(rhs.m_container) + { + init_iterators(); + } + + template + inline xiterator_owner_adaptor& xiterator_owner_adaptor::operator=(const self_type& rhs) + { + m_container = rhs.m_container; + init_iterators(); + } + + template + inline xiterator_owner_adaptor::xiterator_owner_adaptor(self_type&& rhs) + : m_container(std::move(rhs.m_container)) + { + init_iterators(); + } + + template + inline xiterator_owner_adaptor& xiterator_owner_adaptor::operator=(self_type&& rhs) + { + m_container = std::move(rhs.m_container); + init_iterators(); + } + + template + inline xiterator_owner_adaptor& xiterator_owner_adaptor::operator=(const temporary_type& rhs) + { + resize(rhs.size()); + std::copy(rhs.cbegin(), rhs.cend(), m_it); + return *this; + } + + template + inline xiterator_owner_adaptor& xiterator_owner_adaptor::operator=(temporary_type&& rhs) + { + return (*this = rhs); + } + + template + inline auto xiterator_owner_adaptor::size() const noexcept -> size_type + { + return m_size; + } + + template + inline void xiterator_owner_adaptor::resize(size_type size) + { + if (m_size != size) + { + XTENSOR_THROW(std::runtime_error, "xiterator_owner_adaptor not resizeable"); + } + } + + template + inline auto xiterator_owner_adaptor::data() noexcept -> iterator + { + return m_it; + } + + template + inline auto xiterator_owner_adaptor::data() const noexcept -> const_iterator + { + return m_cit; + } + + template + inline void xiterator_owner_adaptor::swap(self_type& rhs) noexcept + { + using std::swap; + swap(m_container, rhs.m_container); + init_iterators(); + rhs.init_iterators(); + } + + template + inline void xiterator_owner_adaptor::init_iterators() + { + m_it = IG::begin(m_container); + m_cit = IG::cbegin(m_container); + m_size = IG::size(m_container); + } + + template + inline void swap(xiterator_owner_adaptor& lhs, xiterator_owner_adaptor& rhs) noexcept + { + lhs.swap(rhs); + } + + /***************************************** + * make_xiterator_adaptor implementation * + *****************************************/ + + namespace detail + { + template ::value> + struct xiterator_adaptor_builder + { + using iterator = decltype(IG::begin(std::declval())); + using const_iterator = decltype(IG::cbegin(std::declval())); + using type = xiterator_adaptor; + + inline static type build(C& c) + { + return type(IG::begin(c), IG::cbegin(c), IG::size(c)); + } + }; + + template + struct xiterator_adaptor_builder + { + using type = xiterator_owner_adaptor; + + inline static type build(C&& c) + { + return type(std::move(c)); + } + }; + } + + template + inline auto make_xiterator_adaptor(C&& container, IG) + { + using builder_type = detail::xiterator_adaptor_builder; + return builder_type::build(std::forward(container)); + } +} + +#endif diff --git a/contrib/xtensor/include/xtensor/xbuilder.hpp b/contrib/xtensor/include/xtensor/xbuilder.hpp new file mode 100644 index 00000000000..2302bbb96fd --- /dev/null +++ b/contrib/xtensor/include/xtensor/xbuilder.hpp @@ -0,0 +1,1215 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht * + * Copyright (c) QuantStack * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +/** + * @brief standard mathematical functions for xexpressions + */ + +#ifndef XTENSOR_BUILDER_HPP +#define XTENSOR_BUILDER_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "xbroadcast.hpp" +#include "xfunction.hpp" +#include "xgenerator.hpp" +#include "xoperation.hpp" + +namespace xt +{ + + /******** + * ones * + ********/ + + /** + * Returns an \ref xexpression containing ones of the specified shape. + * @tparam shape the shape of the returned expression. + */ + template + inline auto ones(S shape) noexcept + { + return broadcast(T(1), std::forward(shape)); + } + + template + inline auto ones(const I (&shape)[L]) noexcept + { + return broadcast(T(1), shape); + } + + /********* + * zeros * + *********/ + + /** + * Returns an \ref xexpression containing zeros of the specified shape. + * @tparam shape the shape of the returned expression. + */ + template + inline auto zeros(S shape) noexcept + { + return broadcast(T(0), std::forward(shape)); + } + + template + inline auto zeros(const I (&shape)[L]) noexcept + { + return broadcast(T(0), shape); + } + + /** + * Create a xcontainer (xarray, xtensor or xtensor_fixed) with uninitialized values of + * with value_type T and shape. Selects the best container match automatically + * from the supplied shape. + * + * - ``std::vector`` → ``xarray`` + * - ``std::array`` or ``initializer_list`` → ``xtensor`` + * - ``xshape`` → ``xtensor_fixed>`` + * + * @param shape shape of the new xcontainer + */ + template + inline xarray empty(const S& shape) + { + return xarray::from_shape(shape); + } + + template + inline xtensor empty(const std::array& shape) + { + using shape_type = typename xtensor::shape_type; + return xtensor(xtl::forward_sequence(shape)); + } + + template + inline xtensor empty(const I (&shape)[N]) + { + using shape_type = typename xtensor::shape_type; + return xtensor(xtl::forward_sequence(shape)); + } + + template + inline xtensor_fixed, L> empty(const fixed_shape& /*shape*/) + { + return xtensor_fixed, L>(); + } + + /** + * Create a xcontainer (xarray, xtensor or xtensor_fixed) with uninitialized values of + * the same shape, value type and layout as the input xexpression *e*. + * + * @param e the xexpression from which to extract shape, value type and layout. + */ + template + inline auto empty_like(const xexpression& e) + { + using xtype = temporary_type_t; + auto res = xtype::from_shape(e.derived_cast().shape()); + return res; + } + + /** + * Create a xcontainer (xarray, xtensor or xtensor_fixed), filled with *fill_value* and of + * the same shape, value type and layout as the input xexpression *e*. + * + * @param e the xexpression from which to extract shape, value type and layout. + * @param fill_value the value used to set each element of the returned xcontainer. + */ + template + inline auto full_like(const xexpression& e, typename E::value_type fill_value) + { + using xtype = temporary_type_t; + auto res = xtype::from_shape(e.derived_cast().shape()); + res.fill(fill_value); + return res; + } + + /** + * Create a xcontainer (xarray, xtensor or xtensor_fixed), filled with zeros and of + * the same shape, value type and layout as the input xexpression *e*. + * + * Note: contrary to zeros(shape), this function returns a non-lazy, allocated container! + * Use ``xt::zeros(e.shape());` for a lazy version. + * + * @param e the xexpression from which to extract shape, value type and layout. + */ + template + inline auto zeros_like(const xexpression& e) + { + return full_like(e, typename E::value_type(0)); + } + + /** + * Create a xcontainer (xarray, xtensor or xtensor_fixed), filled with ones and of + * the same shape, value type and layout as the input xexpression *e*. + * + * Note: contrary to ones(shape), this function returns a non-lazy, evaluated container! + * Use ``xt::ones(e.shape());`` for a lazy version. + * + * @param e the xexpression from which to extract shape, value type and layout. + */ + template + inline auto ones_like(const xexpression& e) + { + return full_like(e, typename E::value_type(1)); + } + + namespace detail + { + template + struct get_mult_type_impl + { + using type = T; + }; + + template + struct get_mult_type_impl> + { + using type = R; + }; + + template + using get_mult_type = typename get_mult_type_impl::type; + + // These methods should be private methods of arange_generator, however thi leads + // to ICE on VS2015 + template )> + inline void arange_assign_to(xexpression& e, U start, U, X step, bool) noexcept + { + auto& de = e.derived_cast(); + U value = start; + + for (auto&& el : de.storage()) + { + el = static_cast(value); + value += step; + } + } + + template >)> + inline void arange_assign_to(xexpression& e, U start, U stop, X step, bool endpoint) noexcept + { + auto& buf = e.derived_cast().storage(); + using size_type = decltype(buf.size()); + using mult_type = get_mult_type; + size_type num = buf.size(); + for (size_type i = 0; i < num; ++i) + { + buf[i] = static_cast(start + step * mult_type(i)); + } + if (endpoint && num > 1) + { + buf[num - 1] = static_cast(stop); + } + } + + template + class arange_generator + { + public: + + using value_type = R; + using step_type = S; + + arange_generator(T start, T stop, S step, size_t num_steps, bool endpoint = false) + : m_start(start) + , m_stop(stop) + , m_step(step) + , m_num_steps(num_steps) + , m_endpoint(endpoint) + { + } + + template + inline R operator()(Args... args) const + { + return access_impl(args...); + } + + template + inline R element(It first, It) const + { + return access_impl(*first); + } + + template + inline void assign_to(xexpression& e) const noexcept + { + arange_assign_to(e, m_start, m_stop, m_step, m_endpoint); + } + + private: + + T m_start; + T m_stop; + step_type m_step; + size_t m_num_steps; + bool m_endpoint; // true for setting the last element to m_stop + + template + inline R access_impl(T1 t, Args...) const + { + if (m_endpoint && m_num_steps > 1 && size_t(t) == m_num_steps - 1) + { + return static_cast(m_stop); + } + // Avoids warning when T = char (because char + char => int!) + using mult_type = get_mult_type; + return static_cast(m_start + m_step * mult_type(t)); + } + + inline R access_impl() const + { + return static_cast(m_start); + } + }; + + template + using both_integer = xtl::conjunction, xtl::is_integral>; + + template + using integer_with_signed_integer = xtl::conjunction, xtl::is_signed>; + + template + using integer_with_unsigned_integer = xtl::conjunction, std::is_unsigned>; + + template >)> + inline auto arange_impl(T start, T stop, S step = 1) noexcept + { + std::size_t shape = static_cast(std::ceil((stop - start) / step)); + return detail::make_xgenerator(detail::arange_generator(start, stop, step, shape), {shape}); + } + + template )> + inline auto arange_impl(T start, T stop, S step = 1) noexcept + { + bool empty_cond = (stop - start) / step <= 0; + std::size_t shape = 0; + if (!empty_cond) + { + shape = stop > start ? static_cast((stop - start + step - S(1)) / step) + : static_cast((start - stop - step - S(1)) / -step); + } + return detail::make_xgenerator(detail::arange_generator(start, stop, step, shape), {shape}); + } + + template )> + inline auto arange_impl(T start, T stop, S step = 1) noexcept + { + bool empty_cond = stop <= start; + std::size_t shape = 0; + if (!empty_cond) + { + shape = static_cast((stop - start + step - S(1)) / step); + } + return detail::make_xgenerator(detail::arange_generator(start, stop, step, shape), {shape}); + } + + template + class fn_impl + { + public: + + using value_type = typename F::value_type; + using size_type = std::size_t; + + fn_impl(F&& f) + : m_ft(f) + { + } + + inline value_type operator()() const + { + size_type idx[1] = {0ul}; + return access_impl(std::begin(idx), std::end(idx)); + } + + template + inline value_type operator()(Args... args) const + { + size_type idx[sizeof...(Args)] = {static_cast(args)...}; + return access_impl(std::begin(idx), std::end(idx)); + } + + template + inline value_type element(It first, It last) const + { + return access_impl(first, last); + } + + private: + + F m_ft; + + template + inline value_type access_impl(const It& begin, const It& end) const + { + return m_ft(begin, end); + } + }; + + template + class eye_fn + { + public: + + using value_type = T; + + eye_fn(int k) + : m_k(k) + { + } + + template + inline T operator()(const It& /*begin*/, const It& end) const + { + using lvalue_type = typename std::iterator_traits::value_type; + return *(end - 1) == *(end - 2) + static_cast(m_k) ? T(1) : T(0); + } + + private: + + std::ptrdiff_t m_k; + }; + } + + /** + * Generates an array with ones on the diagonal. + * @param shape shape of the resulting expression + * @param k index of the diagonal. 0 (default) refers to the main diagonal, + * a positive value refers to an upper diagonal, and a negative + * value to a lower diagonal. + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto eye(const std::vector& shape, int k = 0) + { + return detail::make_xgenerator(detail::fn_impl>(detail::eye_fn(k)), shape); + } + + /** + * Generates a (n x n) array with ones on the diagonal. + * @param n length of the diagonal. + * @param k index of the diagonal. 0 (default) refers to the main diagonal, + * a positive value refers to an upper diagonal, and a negative + * value to a lower diagonal. + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto eye(std::size_t n, int k = 0) + { + return eye({n, n}, k); + } + + /** + * Generates numbers evenly spaced within given half-open interval [start, stop). + * @param start start of the interval + * @param stop stop of the interval + * @param step stepsize + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto arange(T start, T stop, S step = 1) noexcept + { + return detail::arange_impl(start, stop, step); + } + + /** + * Generate numbers evenly spaced within given half-open interval [0, stop) + * with a step size of 1. + * @param stop stop of the interval + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto arange(T stop) noexcept + { + return arange(T(0), stop, T(1)); + } + + /** + * Generates @a num_samples evenly spaced numbers over given interval + * @param start start of interval + * @param stop stop of interval + * @param num_samples number of samples (defaults to 50) + * @param endpoint if true, include endpoint (defaults to true) + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto linspace(T start, T stop, std::size_t num_samples = 50, bool endpoint = true) noexcept + { + using fp_type = std::common_type_t; + fp_type step = fp_type(stop - start) / std::fmax(fp_type(1), fp_type(num_samples - (endpoint ? 1 : 0))); + return detail::make_xgenerator( + detail::arange_generator(fp_type(start), fp_type(stop), step, num_samples, endpoint), + {num_samples} + ); + } + + /** + * Generates @a num_samples numbers evenly spaced on a log scale over given interval + * @param start start of interval (pow(base, start) is the first value). + * @param stop stop of interval (pow(base, stop) is the final value, except if endpoint = false) + * @param num_samples number of samples (defaults to 50) + * @param base the base of the log space. + * @param endpoint if true, include endpoint (defaults to true) + * @tparam T value_type of xexpression + * @return xgenerator that generates the values on access + */ + template + inline auto logspace(T start, T stop, std::size_t num_samples, T base = 10, bool endpoint = true) noexcept + { + return pow(std::move(base), linspace(start, stop, num_samples, endpoint)); + } + + namespace detail + { + template + class concatenate_access + { + public: + + using tuple_type = std::tuple; + using size_type = std::size_t; + using value_type = xtl::promote_type_t::value_type...>; + + template + inline value_type access(const tuple_type& t, size_type axis, S index) const + { + auto match = [&index, axis](auto& arr) + { + if (index[axis] >= arr.shape()[axis]) + { + index[axis] -= arr.shape()[axis]; + return false; + } + return true; + }; + + auto get = [&index](auto& arr) + { + return arr[index]; + }; + + size_type i = 0; + for (; i < sizeof...(CT); ++i) + { + if (apply(i, match, t)) + { + break; + } + } + return apply(i, get, t); + } + }; + + template + class stack_access + { + public: + + using tuple_type = std::tuple; + using size_type = std::size_t; + using value_type = xtl::promote_type_t::value_type...>; + + template + inline value_type access(const tuple_type& t, size_type axis, S index) const + { + auto get_item = [&index](auto& arr) + { + return arr[index]; + }; + size_type i = index[axis]; + index.erase(index.begin() + std::ptrdiff_t(axis)); + return apply(i, get_item, t); + } + }; + + template + class vstack_access : private concatenate_access, + private stack_access + { + public: + + using tuple_type = std::tuple; + using size_type = std::size_t; + using value_type = xtl::promote_type_t::value_type...>; + + using concatenate_base = concatenate_access; + using stack_base = stack_access; + + template + inline value_type access(const tuple_type& t, size_type axis, S index) const + { + if (std::get<0>(t).dimension() == 1) + { + return stack_base::access(t, axis, index); + } + else + { + return concatenate_base::access(t, axis, index); + } + } + }; + + template