diff --git a/.github/ci-config.yml b/.github/ci-config.yml new file mode 100644 index 000000000..004e8ab62 --- /dev/null +++ b/.github/ci-config.yml @@ -0,0 +1,5 @@ +dependencies: | + ecmwf/ecbuild + ecmwf/eckit +dependency_branch: develop +parallelism_factor: 8 diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml new file mode 100644 index 000000000..eab4a6cd2 --- /dev/null +++ b/.github/ci-hpc-config.yml @@ -0,0 +1,27 @@ +matrix: + - mpi_on + - mpi_off + +mpi_on: + build: + modules: + - ninja + modules_package: + - atlas:fftw,eigen,openmpi + - eckit:openmpi + dependencies: + - ecmwf/ecbuild@develop + - ecmwf/eckit@develop + parallel: 64 + ntasks: 16 + +mpi_off: + build: + modules: + - ninja + modules_package: + - atlas:fftw,eigen + dependencies: + - ecmwf/ecbuild@develop + - ecmwf/eckit@develop + parallel: 64 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f61efce08..f51e1f050 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,8 +32,10 @@ jobs: build_type: [Release,Debug] name: - linux gnu-10 + - linux gnu-12 + - linux gnu-7 - linux clang-12 - - linux nvhpc-21.9 +# - linux nvhpc-22.11 - linux intel - macos @@ -46,6 +48,26 @@ jobs: compiler_cxx: g++-10 compiler_fc: gfortran-10 caching: true + coverage: true + + - name: linux gnu-12 + os: ubuntu-22.04 + compiler: gnu-12 + compiler_cc: gcc-12 + compiler_cxx: g++-12 + compiler_fc: gfortran-12 + cmake_options: -DENABLE_OMP_CXX=OFF + caching: true + coverage: false + + - name: linux gnu-7 + os: ubuntu-20.04 + compiler: gnu-7 + compiler_cc: gcc-7 + compiler_cxx: g++-7 + compiler_fc: gfortran-7 + caching: true + coverage: false - name: linux clang-12 os: ubuntu-20.04 @@ -54,6 +76,7 @@ jobs: compiler_cxx: clang++-12 compiler_fc: gfortran-10 caching: true + coverage: false - name: linux clang-12 build_type: Release @@ -64,16 +87,18 @@ jobs: compiler_fc: gfortran-10 ctest_options: "-LE mpi" # For now until Checkerboard fixed caching: true - - - name: linux nvhpc-21.9 - os: ubuntu-20.04 - compiler: nvhpc-21.9 - compiler_cc: nvc - compiler_cxx: nvc++ - compiler_fc: nvfortran - cmake_options: -DCMAKE_CXX_FLAGS=--diag_suppress177 - ctest_options: "-LE mpi" # For now until Checkerboard fixed - caching: false + coverage: false + +# - name: linux nvhpc-22.11 +# os: ubuntu-20.04 +# compiler: nvhpc-22.11 +# compiler_cc: nvc +# compiler_cxx: nvc++ +# compiler_fc: nvfortran +# cmake_options: -DCMAKE_CXX_FLAGS=--diag_suppress177 +# ctest_options: "-LE mpi" # For now until Checkerboard fixed +# caching: false +# coverage: false - name : linux intel os: ubuntu-20.04 @@ -82,6 +107,7 @@ jobs: compiler_cxx: icpc compiler_fc: ifort caching: true + coverage: false - name: macos # Xcode compiler requires empty environment variables, so we pass null (~) here @@ -91,6 +117,7 @@ jobs: compiler_cxx: ~ compiler_fc: gfortran-11 caching: true + coverage: false cmake_options: -DMPI_SLOTS=4 runs-on: ${{ matrix.os }} @@ -120,6 +147,9 @@ jobs: brew install libomp else sudo apt-get update + if [[ "${{ matrix.compiler }}" =~ gnu-7 ]]; then + sudo apt-get install gcc-7 g++-7 gfortran-7 + fi sudo apt-get install ninja-build fi @@ -138,7 +168,7 @@ jobs: if: contains( matrix.compiler, 'nvhpc' ) shell: bash -eux {0} run: | - ${ATLAS_TOOLS}/install-nvhpc.sh --prefix ${DEPS_DIR}/nvhpc + ${ATLAS_TOOLS}/install-nvhpc.sh --prefix ${DEPS_DIR}/nvhpc --version 22.11 source ${DEPS_DIR}/nvhpc/env.sh echo "${NVHPC_DIR}/compilers/bin" >> $GITHUB_PATH [ -z ${MPI_HOME+x} ] || echo "MPI_HOME=${MPI_HOME}" >> $GITHUB_ENV @@ -189,7 +219,7 @@ jobs: id: build-test uses: ecmwf-actions/build-package@v2 with: - self_coverage: true + self_coverage: ${{ matrix.coverage }} force_build: true cache_suffix: "${{ matrix.build_type }}-${{ env.CACHE_SUFFIX }}" recreate_cache: ${{ matrix.caching == false }} diff --git a/.github/workflows/check-release-version.yml b/.github/workflows/check-release-version.yml new file mode 100644 index 000000000..9cff1a753 --- /dev/null +++ b/.github/workflows/check-release-version.yml @@ -0,0 +1,11 @@ +name: Check VERSION file + +on: + push: + branches: + - "release/**" + - "hotfix/**" + +jobs: + check_version: + uses: ecmwf-actions/reusable-workflows/.github/workflows/check-release-version.yml@v2 diff --git a/.github/workflows/reusable-ci-hpc.yml b/.github/workflows/reusable-ci-hpc.yml index 744456177..0448889d4 100644 --- a/.github/workflows/reusable-ci-hpc.yml +++ b/.github/workflows/reusable-ci-hpc.yml @@ -21,8 +21,7 @@ jobs: ecbuild ninja --modules-package: | - fftw - eigen + atlas:fftw,eigen --dependencies: | ${{ inputs.eckit || 'ecmwf/eckit@develop' }} --parallel: 64 diff --git a/AUTHORS b/AUTHORS index cdf736ded..23103faf0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,23 +13,31 @@ Thanks for contributions from - Andreas Mueller - Baudouin Raoult - Florian Rathgeber -- Marek Wlasak - Oliver Lomax +- Marek Wlasak - Daniel Holdaway - Daan Degrauwe - Philippe Marguinaud +- Slavko Brdar - Gianmarco Mengaldo +- Dušan Figala +- Benjamin Menetrier - James Hawkes - Mats Hamrud -- Benjamin Menetrier - Rahul Mahajan +- Toby Searle - Olivier Iffrig - Christian Kuehnlein - Iain Russell +- Marco Milan +- Daniel Tipping - Domokos Sármány -- Steven Vahl +- Lorenzo Milazzo +- Francois Hebert +- Yannick Trémolet - Mark J. Olah -- Michael Lange +- Sam Hatfield - Peter Bispham -- Slavko Brdar -- Yannick Trémolet +- Paul Cresswell +- Steven Vahl +- Michael Lange diff --git a/CHANGELOG.md b/CHANGELOG.md index e04f14468..068d2b7fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html ## [Unreleased] +## [0.34.0] - 2023-07-10 +### Added +- Fieldset::metadata (#126) +- Fieldset::adjointHaloExchange +- Field/Fieldset::clone method +- Functions to enable/disable FPE +- Add function to build mesh from imported connectivity data (#135) +- Implement field::for_each capabilities (#139) +- Introduce colon-separated environment variable ATLAS_PLUGIN_PATH to simplify plugin detection +- Introduce atlas::mdspan, contributed from github.com/kokkos/mdspan +- Add function Field::horizontal_dimension() -> std::vector +- Setup horizontal_dimensions() for BlockStructuredColumns fields +- Upgrade the halo exchange procedure for the function space 'PointCloud' (#120) + +### Fixed +- Enable latitude normalisation in KDTree coordinate transform (#140) +- Fix LocalView indexing bug for non-contiguous slices +- C++17 flag public propagation to downstream C++ CMake packages +- Fix cells().global_index() metadata for RegularLonLat grids in StructuredMeshGenerator +- BuildHalo: mark interior added cells as ghost + ## [0.33.0] - 2023-04-03 ### Added - Add support for StructuredPartitionPolygon with halo > 0 @@ -443,6 +464,7 @@ Fix StructuredInterpolation2D with retry for failed stencils ## 0.13.0 - 2018-02-16 [Unreleased]: https://github.com/ecmwf/atlas/compare/master...develop +[0.33.0]: https://github.com/ecmwf/atlas/compare/0.33.0...0.34.0 [0.33.0]: https://github.com/ecmwf/atlas/compare/0.32.1...0.33.0 [0.32.1]: https://github.com/ecmwf/atlas/compare/0.32.0...0.32.1 [0.32.0]: https://github.com/ecmwf/atlas/compare/0.31.1...0.32.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index d44f51625..5d458f867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,12 +38,39 @@ find_package(atlas_io) ################################################################################ # Features that can be enabled / disabled with -DENABLE_ +ecbuild_add_option( FEATURE ATLAS_GRID + DESCRIPTION "Build grid related features" ) + +ecbuild_add_option( FEATURE ATLAS_FIELD + DESCRIPTION "Build field and memory management related features" ) + +ecbuild_add_option( FEATURE ATLAS_FUNCTIONSPACE + DESCRIPTION "Build functionspace related features (mesh, functionspace, parallel)" + CONDITION atlas_HAVE_ATLAS_GRID AND atlas_HAVE_ATLAS_FIELD ) + +ecbuild_add_option( FEATURE ATLAS_INTERPOLATION + DESCRIPTION "Build interpolation related features" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) + +ecbuild_add_option( FEATURE ATLAS_TRANS + DESCRIPTION "Build transform related features" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) + +ecbuild_add_option( FEATURE ATLAS_NUMERICS + DESCRIPTION "Build numerics related features" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) + +ecbuild_add_option( FEATURE ECKIT_DEVELOP + DESCRIPTION "Used to enable new features or API depending on eckit develop branch, not yet in a tagged release" + DEFAULT OFF ) + + include( features/BOUNDSCHECKING ) include( features/FORTRAN ) include( features/MPI ) include( features/OMP ) include( features/FFTW ) -include( features/TRANS ) +include( features/ECTRANS ) include( features/TESSELATION ) include( features/GRIDTOOLS_STORAGE ) include( features/ACC ) diff --git a/VERSION b/VERSION index be386c9ed..85e60ed18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.33.0 +0.34.0 diff --git a/atlas_io/src/atlas_io/Data.h b/atlas_io/src/atlas_io/Data.h index 7431fd7f4..69c51096f 100644 --- a/atlas_io/src/atlas_io/Data.h +++ b/atlas_io/src/atlas_io/Data.h @@ -10,6 +10,8 @@ #pragma once +#include + #include "eckit/io/Buffer.h" namespace atlas { diff --git a/atlas_io/src/atlas_io/Trace.h b/atlas_io/src/atlas_io/Trace.h index f08e1d2bc..43da3f526 100644 --- a/atlas_io/src/atlas_io/Trace.h +++ b/atlas_io/src/atlas_io/Trace.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace eckit { class CodeLocation; @@ -51,6 +52,7 @@ struct TraceHookRegistry { static bool enabled(size_t id) { return instance().enabled_[id]; } static size_t size() { return instance().hooks.size(); } static TraceHookBuilder& hook(size_t id) { return instance().hooks[id]; } + static size_t invalidId() { return std::numeric_limits::max(); } private: TraceHookRegistry() = default; diff --git a/atlas_io/tests/CMakeLists.txt b/atlas_io/tests/CMakeLists.txt index 128f046b9..32ff8b3b8 100644 --- a/atlas_io/tests/CMakeLists.txt +++ b/atlas_io/tests/CMakeLists.txt @@ -28,7 +28,7 @@ foreach( algorithm none bzip2 aec lz4 snappy ) string( TOUPPER ${algorithm} feature ) if( eckit_HAVE_${feature} OR algorithm MATCHES "none" ) ecbuild_add_test( TARGET atlas_io_test_record_COMPRESSION_${algorithm} - COMMAND atlas_test_io_record + COMMAND atlas_io_test_record ARGS --suffix ".${algorithm}" ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ATLAS_IO_COMPRESSION=${algorithm} ) diff --git a/cmake/atlas-import.cmake.in b/cmake/atlas-import.cmake.in index d8af94223..98b7c9bb2 100644 --- a/cmake/atlas-import.cmake.in +++ b/cmake/atlas-import.cmake.in @@ -5,7 +5,7 @@ set( atlas_HAVE_MPI @atlas_HAVE_MPI@ ) set( atlas_HAVE_OMP @atlas_HAVE_OMP@ ) set( atlas_HAVE_OMP_CXX @atlas_HAVE_OMP_CXX@ ) set( atlas_HAVE_OMP_Fortran @atlas_HAVE_OMP_Fortran@ ) -set( atlas_HAVE_TRANS @atlas_HAVE_TRANS@ ) +set( atlas_HAVE_ECTRANS @atlas_HAVE_ECTRANS@ ) set( atlas_HAVE_FORTRAN @atlas_HAVE_FORTRAN@ ) set( atlas_HAVE_EIGEN @atlas_HAVE_EIGEN@ ) set( atlas_HAVE_GRIDTOOLS_STORAGE @atlas_HAVE_GRIDTOOLS_STORAGE@ ) @@ -65,7 +65,7 @@ if( atlas_OMP_COMPONENTS ) endif() ## transi -if( atlas_HAVE_TRANS AND atlas_REQUIRES_PRIVATE_DEPENDENCIES ) +if( atlas_HAVE_ECTRANS AND atlas_REQUIRES_PRIVATE_DEPENDENCIES ) set( transi_DIR @transi_DIR@ ) if( transi_DIR ) find_dependency( transi HINTS ${CMAKE_CURRENT_LIST_DIR}/../transi @transi_DIR@ ) diff --git a/cmake/atlas_compile_flags.cmake b/cmake/atlas_compile_flags.cmake index 9b350d478..5473f870a 100644 --- a/cmake/atlas_compile_flags.cmake +++ b/cmake/atlas_compile_flags.cmake @@ -20,3 +20,8 @@ if( CMAKE_CXX_COMPILER_ID MATCHES Cray ) # directives, ACC directives, or ASM intrinsics. endif() + +if( CMAKE_CXX_COMPILER_ID MATCHES NVHPC ) + ecbuild_add_cxx_flags("--diag_suppress declared_but_not_referenced --display_error_number" NAME atlas_cxx_disable_warnings ) + # For all the variables with side effects (constructor/dectructor functionality) +endif() \ No newline at end of file diff --git a/cmake/features/ACC.cmake b/cmake/features/ACC.cmake index f9b58c1e3..dd6a1de02 100644 --- a/cmake/features/ACC.cmake +++ b/cmake/features/ACC.cmake @@ -1,5 +1,7 @@ ### OpenACC +if( atlas_HAVE_ATLAS_FIELD ) + set( ATLAS_ACC_CAPABLE FALSE ) if( HAVE_CUDA ) if( CMAKE_Fortran_COMPILER_ID MATCHES "PGI|NVHPC" ) @@ -21,3 +23,8 @@ if( atlas_HAVE_ACC ) endif() endif() endif() + +else() + set( HAVE_ACC 0 ) + set( atlas_HAVE_ACC 0 ) +endif() \ No newline at end of file diff --git a/cmake/features/ECTRANS.cmake b/cmake/features/ECTRANS.cmake new file mode 100644 index 000000000..0d3a5355c --- /dev/null +++ b/cmake/features/ECTRANS.cmake @@ -0,0 +1,35 @@ +if( atlas_HAVE_ATLAS_TRANS ) + +### trans ... + +if( NOT DEFINED ENABLE_ECTRANS AND DEFINED ENABLE_TRANS ) + ecbuild_warn("Atlas option ENABLE_TRANS is deprecated in favour of ENABLE_ECTRANS") + set( ENABLE_ECTRANS ${ENABLE_TRANS} ) +endif() +if( NOT DEFINED ATLAS_ENABLE_ECTRANS AND DEFINED ATLAS_ENABLE_TRANS ) + ecbuild_warn("Atlas option ATLAS_ENABLE_TRANS is deprecated in favour of ATLAS_ENABLE_ECTRANS") + set( ATLAS_ENABLE_TRANS ${ENABLE_TRANS} ) +endif() + +set( atlas_HAVE_PACKAGE_ECTRANS 0 ) +if( atlas_HAVE_ATLAS_FUNCTIONSPACE AND (ENABLE_ECTRANS OR NOT DEFINED ENABLE_ECTRANS) ) + find_package( ectrans 1.1 COMPONENTS transi double QUIET ) + if( TARGET transi_dp ) + set( transi_FOUND TRUE ) + if( NOT TARGET transi ) + get_target_property( transi_dp_IMPORTED transi_dp IMPORTED ) + if( transi_dp_IMPORTED ) + set_target_properties( transi_dp PROPERTIES IMPORTED_GLOBAL TRUE) # required for aliasing imports + endif() + add_library( transi ALIAS transi_dp ) + endif() + set( atlas_HAVE_PACKAGE_ECTRANS 1 ) + else() + find_package( transi 0.8 QUIET ) + endif() +endif() +ecbuild_add_option( FEATURE ECTRANS + DESCRIPTION "Support for IFS spectral transforms" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE AND transi_FOUND ) + +endif() \ No newline at end of file diff --git a/cmake/features/EIGEN.cmake b/cmake/features/EIGEN.cmake index c73257ac8..1a38bf8f9 100644 --- a/cmake/features/EIGEN.cmake +++ b/cmake/features/EIGEN.cmake @@ -1,5 +1,7 @@ ### Eigen +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) + ecbuild_add_option( FEATURE EIGEN DESCRIPTION "Use Eigen linear algebra library" REQUIRED_PACKAGES Eigen3 ) @@ -10,3 +12,8 @@ if( HAVE_EIGEN AND NOT TARGET Eigen3::Eigen ) target_include_directories( atlas_eigen3 INTERFACE ${EIGEN3_INCLUDE_DIRS} ) add_library( Eigen3::Eigen ALIAS atlas_eigen3 ) endif() + +else() + set( HAVE_EIGEN 0 ) + set( atlas_HAVE_EIGEN 0 ) +endif() \ No newline at end of file diff --git a/cmake/features/FFTW.cmake b/cmake/features/FFTW.cmake index a913d38c0..14bf0dd64 100644 --- a/cmake/features/FFTW.cmake +++ b/cmake/features/FFTW.cmake @@ -1,5 +1,7 @@ ### FFTW ... +if( atlas_HAVE_ATLAS_TRANS ) + ecbuild_add_option( FEATURE FFTW DESCRIPTION "Support for fftw" REQUIRED_PACKAGES "FFTW COMPONENTS double QUIET" ) @@ -8,3 +10,5 @@ if( NOT HAVE_FFTW ) unset( FFTW_LIBRARIES ) unset( FFTW_INCLUDES ) endif() + +endif() \ No newline at end of file diff --git a/cmake/features/FORTRAN.cmake b/cmake/features/FORTRAN.cmake index 835574dd3..e27b63076 100644 --- a/cmake/features/FORTRAN.cmake +++ b/cmake/features/FORTRAN.cmake @@ -2,7 +2,8 @@ ecbuild_add_option( FEATURE FORTRAN DESCRIPTION "Provide Fortran bindings" - REQUIRED_PACKAGES "fckit VERSION 0.6.2 COMPONENTS ECKIT" ) + REQUIRED_PACKAGES "fckit VERSION 0.6.2 COMPONENTS ECKIT" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) if( atlas_HAVE_FORTRAN ) @@ -29,4 +30,4 @@ if( atlas_HAVE_FORTRAN ) endif() -ecbuild_find_python() +ecbuild_find_python( NO_LIBS ) diff --git a/cmake/features/GRIDTOOLS_STORAGE.cmake b/cmake/features/GRIDTOOLS_STORAGE.cmake index b5e10705f..1eea92a81 100644 --- a/cmake/features/GRIDTOOLS_STORAGE.cmake +++ b/cmake/features/GRIDTOOLS_STORAGE.cmake @@ -1,3 +1,5 @@ +if( atlas_HAVE_ATLAS_FIELD ) + ### GridTools storage module ### GridTools may search for CUDA, which searches for "Threads" @@ -61,3 +63,10 @@ if( atlas_HAVE_GRIDTOOLS_STORAGE ) endif() endif() + +else() + set( HAVE_GRIDTOOLS_STORAGE 1 ) + set( atlas_HAVE_GRIDTOOLS_STORAGE 0 ) + set( ATLAS_GRIDTOOLS_STORAGE_BACKEND_CUDA 0 ) + set( ATLAS_GRIDTOOLS_STORAGE_BACKEND_HOST 0 ) +endif() \ No newline at end of file diff --git a/cmake/features/PROJ.cmake b/cmake/features/PROJ.cmake index ff94d127b..f89404b6a 100644 --- a/cmake/features/PROJ.cmake +++ b/cmake/features/PROJ.cmake @@ -1,3 +1,5 @@ +if( atlas_HAVE_ATLAS_GRID ) + ### Proj # From proj 9 onwards, it is guaranteed that it was built/installed using CMake and the CMake export is available. @@ -54,3 +56,5 @@ if( NOT HAVE_PROJ ) unset( PROJ_LIBRARIES ) unset( PROJ_INCLUDE_DIRS ) endif() + +endif() \ No newline at end of file diff --git a/cmake/features/TESSELATION.cmake b/cmake/features/TESSELATION.cmake index 12b4ebe1c..face9c0e4 100644 --- a/cmake/features/TESSELATION.cmake +++ b/cmake/features/TESSELATION.cmake @@ -1,9 +1,11 @@ +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) ### tesselation ... set(Boost_USE_MULTITHREADED ON ) ecbuild_add_option( FEATURE TESSELATION DESCRIPTION "Support for unstructured mesh generation" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE REQUIRED_PACKAGES "CGAL QUIET" "Boost VERSION 1.45.0 QUIET" ) @@ -29,3 +31,4 @@ if( NOT HAVE_TESSELATION ) unset( CGAL_LIBRARIES ) unset( CGAL_INCLUDE_DIRS ) endif() +endif() \ No newline at end of file diff --git a/cmake/features/TRANS.cmake b/cmake/features/TRANS.cmake deleted file mode 100644 index 4136d0f26..000000000 --- a/cmake/features/TRANS.cmake +++ /dev/null @@ -1,23 +0,0 @@ -### trans ... - -set( atlas_HAVE_ECTRANS 0 ) -if( ENABLE_TRANS OR NOT DEFINED ENABLE_TRANS ) - find_package( ectrans 1.1 COMPONENTS transi double QUIET ) - if( TARGET transi_dp ) - set( transi_FOUND TRUE ) - if( NOT TARGET transi ) - get_target_property( transi_dp_IMPORTED transi_dp IMPORTED ) - if( transi_dp_IMPORTED ) - set_target_properties( transi_dp PROPERTIES IMPORTED_GLOBAL TRUE) # required for aliasing imports - endif() - add_library( transi ALIAS transi_dp ) - endif() - set( atlas_HAVE_ECTRANS 1 ) - else() - find_package( transi 0.8 QUIET ) - endif() -endif() - -ecbuild_add_option( FEATURE TRANS - DESCRIPTION "Support for IFS spectral transforms" - CONDITION transi_FOUND ) diff --git a/doc/example-plugin/CMakeLists.txt b/doc/example-plugin/CMakeLists.txt new file mode 100644 index 000000000..2ba6b85ea --- /dev/null +++ b/doc/example-plugin/CMakeLists.txt @@ -0,0 +1,26 @@ +# (C) Copyright 2023- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation nor +# does it submit to any jurisdiction. + + +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) + +find_package(ecbuild 3.4 REQUIRED HINTS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ecbuild) + +project(atlas-example-plugin LANGUAGES CXX) + +find_package(atlas 0.25.0 REQUIRED) + +atlas_create_plugin( atlas-example-plugin + URL https://atlas-example-plugin + NAMESPACE int.ecmwf ) + +add_subdirectory(src) + +ecbuild_install_project(NAME ${PROJECT_NAME}) + diff --git a/doc/example-plugin/README.md b/doc/example-plugin/README.md new file mode 100644 index 000000000..adffd3df2 --- /dev/null +++ b/doc/example-plugin/README.md @@ -0,0 +1,41 @@ +atlas-example-plugin +==================== + +This is a Atlas plugin that serves as a template. +The plugin can be used to register concrete implementations for abstract Atlas concepts such as: +Grid, MeshGenerator, Partitioner, FunctionSpace, Interpolation method, ... + + +Requirements: +------------- +- atlas ... or greater + + + +Loading of atlas plugins: +------------------------- + +- If your project explicitly links with the plugin library, then nothing needs to be done. + The plugin will be loaded as any explicitly linked library. + +- If this plugin is installed in the same install-prefix as the executable or the eckit library, + then nothing needs to be done. The plugin will be automatically detected and loaded at runtime. + This is the recommended approach. + +- For plugins installed in other locations, use the environment variable ATLAS_PLUGIN_PATH + + export ATLAS_PLUGIN_PATH=/path/to/plugin # colon separated list + + When using older versions of eckit (version <= 1.24.0), instead use + + export PLUGINS_MANIFEST_PATH=/path/to/plugin/share/plugins # colon separated list + export LD_LIBRARY_PATH=/path/to/plugin/lib:$LD_LIBRARY_PATH # use DYLD_LIBRARY_PATH for macOS + +Verify the plugin is loaded using the `atlas` executable: + + atlas --info + +should print a "Plugin" section containing "atlas-example-plugin" with associated version and git sha1. + +To debug plugin detection, set environment variable `export MAIN_DEBUG=1` + diff --git a/doc/example-plugin/VERSION b/doc/example-plugin/VERSION new file mode 100644 index 000000000..6e8bf73aa --- /dev/null +++ b/doc/example-plugin/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/doc/example-plugin/src/CMakeLists.txt b/doc/example-plugin/src/CMakeLists.txt new file mode 100644 index 000000000..02bb1024c --- /dev/null +++ b/doc/example-plugin/src/CMakeLists.txt @@ -0,0 +1,10 @@ +# (C) Copyright 2023- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation nor +# does it submit to any jurisdiction. + +add_subdirectory(atlas-example-plugin) diff --git a/doc/example-plugin/src/atlas-example-plugin/CMakeLists.txt b/doc/example-plugin/src/atlas-example-plugin/CMakeLists.txt new file mode 100644 index 000000000..58caced5a --- /dev/null +++ b/doc/example-plugin/src/atlas-example-plugin/CMakeLists.txt @@ -0,0 +1,26 @@ +# (C) Copyright 2023- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation nor +# does it submit to any jurisdiction. + +configure_file(version.cc.in version.cc) + +ecbuild_add_library( + TARGET atlas-example-plugin + TYPE SHARED + SOURCES + Library.cc + Library.h + version.h + ${CMAKE_CURRENT_BINARY_DIR}/version.cc + HEADER_DESTINATION "include/atlas-example-plugin" + PUBLIC_LIBS atlas + PUBLIC_INCLUDES + $ + $ + $ ) + diff --git a/doc/example-plugin/src/atlas-example-plugin/Library.cc b/doc/example-plugin/src/atlas-example-plugin/Library.cc new file mode 100644 index 000000000..f63dd2d9c --- /dev/null +++ b/doc/example-plugin/src/atlas-example-plugin/Library.cc @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2023- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include "atlas-example-plugin/Library.h" +#include "atlas-example-plugin/version.h" + + +namespace atlas { +namespace example_plugin { + + +REGISTER_LIBRARY( Library ); + + +Library::Library() : Plugin( "atlas-example-plugin" ) {} + + +const Library& Library::instance() { + static Library library; + return library; +} + + +std::string Library::version() const { + return atlas_example_plugin_version(); +} + + +std::string Library::gitsha1( unsigned int count ) const { + std::string sha1 = atlas_example_plugin_git_sha1(); + return sha1.empty() ? "not available" : sha1.substr( 0, std::min( count, 40U ) ); +} + + +void Library::init() { + Plugin::init(); +} + + +} // namespace example_plugin +} // namespace atlas diff --git a/doc/example-plugin/src/atlas-example-plugin/Library.h b/doc/example-plugin/src/atlas-example-plugin/Library.h new file mode 100644 index 000000000..05308912d --- /dev/null +++ b/doc/example-plugin/src/atlas-example-plugin/Library.h @@ -0,0 +1,32 @@ +/* + * (C) Copyright 2023- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#pragma once + +#include "atlas/library/Plugin.h" + +namespace atlas { +namespace example_plugin { + + +class Library : public Plugin { +public: + Library(); + static const Library& instance(); + + std::string version() const override; + std::string gitsha1( unsigned int count ) const override; + void init() override; + +}; + + +} // namespace example_plugin +} // namespace atlas diff --git a/doc/example-plugin/src/atlas-example-plugin/version.cc.in b/doc/example-plugin/src/atlas-example-plugin/version.cc.in new file mode 100644 index 000000000..b34991609 --- /dev/null +++ b/doc/example-plugin/src/atlas-example-plugin/version.cc.in @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2023- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "atlas-example-plugin/version.h" + +#define ATLAS_EXAMPLE_PLUGIN_MAJOR_VERSION @atlas-example-plugin_VERSION_MAJOR@ +#define ATLAS_EXAMPLE_PLUGIN_MINOR_VERSION @atlas-example-plugin_VERSION_MINOR@ +#define ATLAS_EXAMPLE_PLUGIN_PATCH_VERSION @atlas-example-plugin_VERSION_PATCH@ + +const char* atlas_example_plugin_git_sha1() { + return "@atlas-example-plugin_GIT_SHA1@"; +} + +const char* atlas_example_plugin_version() { + return "@atlas-example-plugin_VERSION@"; +} + +const char* atlas_example_plugin_version_str() { + return "@atlas-example-plugin_VERSION_STR@"; +} + +unsigned int atlas_example_plugin_version_int() { + return 10000 * ATLAS_EXAMPLE_PLUGIN_MAJOR_VERSION + 100 * ATLAS_EXAMPLE_PLUGIN_MINOR_VERSION + 1 * ATLAS_EXAMPLE_PLUGIN_PATCH_VERSION; +} diff --git a/doc/example-plugin/src/atlas-example-plugin/version.h b/doc/example-plugin/src/atlas-example-plugin/version.h new file mode 100644 index 000000000..20899b8d4 --- /dev/null +++ b/doc/example-plugin/src/atlas-example-plugin/version.h @@ -0,0 +1,16 @@ +/* + * (C) Copyright 2023- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#pragma once + +const char* atlas_example_plugin_git_sha1(); +const char* atlas_example_plugin_version(); +const char* atlas_example_plugin_version_str(); +unsigned int atlas_example_plugin_version_int(); diff --git a/doc/example-projects/project_hello_world/CMakeLists.txt b/doc/example-projects/project_hello_world/CMakeLists.txt index 81105a6e3..1bccb33d7 100644 --- a/doc/example-projects/project_hello_world/CMakeLists.txt +++ b/doc/example-projects/project_hello_world/CMakeLists.txt @@ -18,6 +18,6 @@ message( STATUS "atlas_DIR ${atlas_DIR}" ) message( STATUS "atlas_HAVE_OMP ${atlas_HAVE_OMP}") message( STATUS "atlas_HAVE_OMP_CXX ${atlas_HAVE_OMP_CXX}") message( STATUS "atlas_HAVE_OMP_Fortran ${atlas_HAVE_OMP_Fortran}") -message( STATUS "atlas_HAVE_TRANS ${atlas_HAVE_TRANS}") +message( STATUS "atlas_HAVE_ECTRANS ${atlas_HAVE_ECTRANS}") message( STATUS "atlas_HAVE_MPI ${atlas_HAVE_MPI}") message( STATUS "ATLAS_LIBRARIES ${ATLAS_LIBRARIES}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4199ca74a..14a7087d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,10 +36,16 @@ else() set( atlas_HAVE_FORTRAN 0 ) endif() -if( atlas_HAVE_TRANS ) - set( atlas_HAVE_TRANS 1 ) +if( atlas_HAVE_ECTRANS ) + set( atlas_HAVE_ECTRANS 1 ) else() - set( atlas_HAVE_TRANS 0 ) + set( atlas_HAVE_ECTRANS 0 ) +endif() + +if( atlas_HAVE_PACKAGE_ECTRANS ) + set( atlas_HAVE_PACKAGE_ECTRANS 1 ) +else() + set( atlas_HAVE_PACKAGE_ECTRANS 0 ) endif() if( atlas_HAVE_FFTW ) @@ -75,6 +81,12 @@ if( CMAKE_BUILD_TYPE MATCHES "Debug" ) set( atlas_BUILD_TYPE_DEBUG 1 ) endif() +if( atlas_HAVE_ECKIT_DEVELOP ) + set( ATLAS_ECKIT_DEVELOP 1 ) +else() + set( ATLAS_ECKIT_DEVELOP 0 ) +endif() + ecbuild_parse_version( ${eckit_VERSION} PREFIX ATLAS_ECKIT ) math( EXPR ATLAS_ECKIT_VERSION_INT "( 10000 * ${ATLAS_ECKIT_VERSION_MAJOR} ) + ( 100 * ${ATLAS_ECKIT_VERSION_MINOR} ) + ${ATLAS_ECKIT_VERSION_PATCH}" ) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index b7cebeec8..2e8eb08ac 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -15,14 +15,17 @@ ecbuild_add_executable( ecbuild_add_executable( TARGET atlas-meshgen SOURCES atlas-meshgen.cc - LIBS atlas ) + LIBS atlas + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) ecbuild_add_executable( TARGET atlas-grids SOURCES atlas-grids.cc - LIBS atlas ) + LIBS atlas + CONDITION atlas_HAVE_ATLAS_GRID ) ecbuild_add_executable( TARGET atlas-gaussian-latitudes SOURCES atlas-gaussian-latitudes.cc - LIBS atlas ) + LIBS atlas + CONDITION atlas_HAVE_ATLAS_GRID ) diff --git a/src/apps/atlas.cc b/src/apps/atlas.cc index 8b7968e35..105788b7b 100644 --- a/src/apps/atlas.cc +++ b/src/apps/atlas.cc @@ -40,6 +40,13 @@ void Version::run() { Log::info() << atlas::Library::instance().information() << std::endl; return; } + else if (Resource("--init", false)) { + Log::info() << "+ atlas::initialize()" << std::endl; + atlas::initialize(); + Log::info() << "+ atlas::finalize()" << std::endl; + atlas::finalize(); + return; + } else if (Resource("--help", false)) { Log::info() << "NAME\n" " atlas - Framework for parallel flexible data structures on " @@ -60,7 +67,10 @@ void Version::run() { " Print short version string 'MAJOR.MINOR.PATCH' \n" "\n" " --info\n" - " Print build configuration anad features\n" + " Print build configuration and features\n" + "\n" + " --init\n" + " Initialise and finalise atlas library, useful for printing debug information (environment ATLAS_DEBUG=1)\n" "\n" "AUTHOR\n" " Written by Willem Deconinck.\n" @@ -70,7 +80,7 @@ void Version::run() { return; } else { - Log::info() << "usage: atlas [--help] [--version] [--git] [--info]" << std::endl; + Log::info() << "usage: atlas [--help] [--version] [--git] [--info] [--init]" << std::endl; } } diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 3ca7f0b55..2f2fc213c 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -25,6 +25,8 @@ install( FILES ### sources list( APPEND atlas_srcs +${CMAKE_CURRENT_BINARY_DIR}/library/git_sha1.h +${CMAKE_CURRENT_BINARY_DIR}/library/defines.h library.h library/config.h library/FloatingPointExceptions.h @@ -51,6 +53,13 @@ runtime/trace/Logging.cc runtime/trace/Logging.h runtime/trace/Timings.h runtime/trace/Timings.cc +runtime/Exception.cc +runtime/Exception.h + +library/detail/BlackMagic.h +library/detail/Debug.h + + parallel/mpi/mpi.cc parallel/mpi/mpi.h parallel/omp/omp.cc @@ -58,6 +67,10 @@ parallel/omp/omp.h parallel/omp/copy.h parallel/omp/fill.h parallel/omp/sort.h + +util/Config.cc +util/Config.h + ) list( APPEND atlas_grid_srcs @@ -125,21 +138,16 @@ grid/StructuredGrid.h grid/UnstructuredGrid.cc grid/UnstructuredGrid.h -grid/Distribution.cc -grid/Distribution.h +util/GaussianLatitudes.cc +util/GaussianLatitudes.h + grid/Spacing.cc grid/Spacing.h -grid/Partitioner.h -grid/Partitioner.cc grid/Iterator.h grid/Iterator.cc -grid/Vertical.h -grid/Vertical.cc grid/Stencil.h grid/StencilComputer.h grid/StencilComputer.cc -grid/StructuredPartitionPolygon.h -grid/StructuredPartitionPolygon.cc grid/Tiles.h grid/Tiles.cc @@ -166,18 +174,6 @@ grid/detail/grid/RegionalVariableResolution.cc grid/detail/grid/Healpix.h grid/detail/grid/Healpix.cc -grid/detail/distribution/DistributionImpl.h -grid/detail/distribution/DistributionImpl.cc -grid/detail/distribution/DistributionArray.cc -grid/detail/distribution/DistributionArray.h -grid/detail/distribution/DistributionFunction.cc -grid/detail/distribution/DistributionFunction.h - -grid/detail/distribution/BandsDistribution.cc -grid/detail/distribution/BandsDistribution.h -grid/detail/distribution/SerialDistribution.cc -grid/detail/distribution/SerialDistribution.h - grid/detail/tiles/Tiles.cc grid/detail/tiles/Tiles.h grid/detail/tiles/TilesFactory.h @@ -187,39 +183,6 @@ grid/detail/tiles/FV3Tiles.h grid/detail/tiles/LFRicTiles.cc grid/detail/tiles/LFRicTiles.h -grid/detail/vertical/VerticalInterface.h -grid/detail/vertical/VerticalInterface.cc - -grid/detail/partitioner/BandsPartitioner.cc -grid/detail/partitioner/BandsPartitioner.h -grid/detail/partitioner/CheckerboardPartitioner.cc -grid/detail/partitioner/CheckerboardPartitioner.h -grid/detail/partitioner/CubedSpherePartitioner.cc -grid/detail/partitioner/CubedSpherePartitioner.h -grid/detail/partitioner/EqualBandsPartitioner.cc -grid/detail/partitioner/EqualBandsPartitioner.h -grid/detail/partitioner/EqualRegionsPartitioner.cc -grid/detail/partitioner/EqualRegionsPartitioner.h -grid/detail/partitioner/MatchingMeshPartitioner.h -grid/detail/partitioner/MatchingMeshPartitioner.cc -grid/detail/partitioner/MatchingMeshPartitionerBruteForce.cc -grid/detail/partitioner/MatchingMeshPartitionerBruteForce.h -grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc -grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.h -grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc -grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.h -grid/detail/partitioner/MatchingMeshPartitionerSphericalPolygon.cc -grid/detail/partitioner/MatchingMeshPartitionerSphericalPolygon.h -grid/detail/partitioner/MatchingFunctionSpacePartitioner.h -grid/detail/partitioner/MatchingFunctionSpacePartitioner.cc -grid/detail/partitioner/MatchingFunctionSpacePartitionerLonLatPolygon.cc -grid/detail/partitioner/MatchingFunctionSpacePartitionerLonLatPolygon.h -grid/detail/partitioner/Partitioner.cc -grid/detail/partitioner/Partitioner.h -grid/detail/partitioner/RegularBandsPartitioner.cc -grid/detail/partitioner/RegularBandsPartitioner.h -grid/detail/partitioner/SerialPartitioner.cc -grid/detail/partitioner/SerialPartitioner.h grid/detail/spacing/Spacing.cc grid/detail/spacing/Spacing.h @@ -288,9 +251,69 @@ grid/detail/pl/classic_gaussian/N1600.cc # TL3199 grid/detail/pl/classic_gaussian/N2000.cc # TL3999 grid/detail/pl/classic_gaussian/N4000.cc # TL7999 grid/detail/pl/classic_gaussian/N8000.cc # TL15999 + +grid/Vertical.h +grid/Vertical.cc + ) -if( atlas_HAVE_TRANS ) -list( APPEND atlas_grid_srcs + +list( APPEND atlas_grid_partitioning_srcs + +grid/StructuredPartitionPolygon.h +grid/StructuredPartitionPolygon.cc + + +grid/Distribution.cc +grid/Distribution.h +grid/Partitioner.h +grid/Partitioner.cc + +grid/detail/distribution/DistributionImpl.h +grid/detail/distribution/DistributionImpl.cc +grid/detail/distribution/DistributionArray.cc +grid/detail/distribution/DistributionArray.h +grid/detail/distribution/DistributionFunction.cc +grid/detail/distribution/DistributionFunction.h + +grid/detail/distribution/BandsDistribution.cc +grid/detail/distribution/BandsDistribution.h +grid/detail/distribution/SerialDistribution.cc +grid/detail/distribution/SerialDistribution.h + +grid/detail/partitioner/BandsPartitioner.cc +grid/detail/partitioner/BandsPartitioner.h +grid/detail/partitioner/CheckerboardPartitioner.cc +grid/detail/partitioner/CheckerboardPartitioner.h +grid/detail/partitioner/CubedSpherePartitioner.cc +grid/detail/partitioner/CubedSpherePartitioner.h +grid/detail/partitioner/EqualBandsPartitioner.cc +grid/detail/partitioner/EqualBandsPartitioner.h +grid/detail/partitioner/EqualRegionsPartitioner.cc +grid/detail/partitioner/EqualRegionsPartitioner.h +grid/detail/partitioner/MatchingMeshPartitioner.h +grid/detail/partitioner/MatchingMeshPartitioner.cc +grid/detail/partitioner/MatchingMeshPartitionerBruteForce.cc +grid/detail/partitioner/MatchingMeshPartitionerBruteForce.h +grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc +grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.h +grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc +grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.h +grid/detail/partitioner/MatchingMeshPartitionerSphericalPolygon.cc +grid/detail/partitioner/MatchingMeshPartitionerSphericalPolygon.h +grid/detail/partitioner/MatchingFunctionSpacePartitioner.h +grid/detail/partitioner/MatchingFunctionSpacePartitioner.cc +grid/detail/partitioner/MatchingFunctionSpacePartitionerLonLatPolygon.cc +grid/detail/partitioner/MatchingFunctionSpacePartitionerLonLatPolygon.h +grid/detail/partitioner/Partitioner.cc +grid/detail/partitioner/Partitioner.h +grid/detail/partitioner/RegularBandsPartitioner.cc +grid/detail/partitioner/RegularBandsPartitioner.h +grid/detail/partitioner/SerialPartitioner.cc +grid/detail/partitioner/SerialPartitioner.h +) + +if( atlas_HAVE_ECTRANS ) +list( APPEND atlas_grid_partitioning_srcs grid/detail/partitioner/TransPartitioner.h grid/detail/partitioner/TransPartitioner.cc ) @@ -310,6 +333,9 @@ if( CGAL_COMPILE_FLAGS ) endif() list( APPEND atlas_mesh_srcs +grid/detail/vertical/VerticalInterface.h # Uses Field +grid/detail/vertical/VerticalInterface.cc # Uses Field + mesh.h mesh/Connectivity.cc mesh/Connectivity.h @@ -323,8 +349,11 @@ mesh/HybridElements.cc mesh/HybridElements.h mesh/Mesh.cc mesh/Mesh.h +mesh/MeshBuilder.cc +mesh/MeshBuilder.h mesh/Nodes.cc mesh/Nodes.h +mesh/IsGhostNode.h mesh/PartitionPolygon.cc mesh/PartitionPolygon.h mesh/detail/MeshImpl.cc @@ -333,6 +362,11 @@ mesh/detail/MeshIntf.cc mesh/detail/MeshIntf.h mesh/detail/PartitionGraph.cc mesh/detail/PartitionGraph.h +mesh/detail/AccumulateFacets.h +mesh/detail/AccumulateFacets.cc + +util/Unique.h +util/Unique.cc mesh/actions/ExtendNodesGlobal.h mesh/actions/ExtendNodesGlobal.cc @@ -480,6 +514,31 @@ functionspace/detail/PointCloudInterface.cc functionspace/detail/CubedSphereStructure.h functionspace/detail/CubedSphereStructure.cc +# for cubedsphere matching mesh partitioner +interpolation/method/cubedsphere/CellFinder.cc +interpolation/method/cubedsphere/CellFinder.h +interpolation/Vector2D.cc +interpolation/Vector2D.h +interpolation/Vector3D.cc +interpolation/Vector3D.h +interpolation/element/Quad2D.h +interpolation/element/Quad2D.cc +interpolation/element/Quad3D.cc +interpolation/element/Quad3D.h +interpolation/element/Triag2D.cc +interpolation/element/Triag2D.h +interpolation/element/Triag3D.cc +interpolation/element/Triag3D.h +interpolation/method/Intersect.cc +interpolation/method/Intersect.h +interpolation/method/Ray.cc # For testing Quad +interpolation/method/Ray.h # For testing Quad + +# for BuildConvexHull3D + +interpolation/method/PointSet.cc +interpolation/method/PointSet.h + ) list( APPEND atlas_numerics_srcs @@ -491,7 +550,9 @@ numerics/fvm/Method.h numerics/fvm/Method.cc numerics/fvm/Nabla.h numerics/fvm/Nabla.cc +) +list( APPEND atlas_trans_srcs trans/Cache.h trans/Cache.cc trans/Trans.h @@ -515,8 +576,9 @@ trans/detail/TransImpl.cc trans/detail/TransInterface.h trans/detail/TransInterface.cc ) -if( atlas_HAVE_TRANS ) -list( APPEND atlas_numerics_srcs + +if( atlas_HAVE_ECTRANS ) +list( APPEND atlas_trans_srcs trans/ifs/LegendreCacheCreatorIFS.h trans/ifs/LegendreCacheCreatorIFS.cc trans/ifs/TransIFS.h @@ -538,20 +600,6 @@ interpolation/Interpolation.cc interpolation/Interpolation.h interpolation/NonLinear.cc interpolation/NonLinear.h -interpolation/Vector2D.cc -interpolation/Vector2D.h -interpolation/Vector3D.cc -interpolation/Vector3D.h -interpolation/element/Quad2D.h -interpolation/element/Quad2D.cc -interpolation/element/Quad3D.cc -interpolation/element/Quad3D.h -interpolation/element/Triag2D.cc -interpolation/element/Triag2D.h -interpolation/element/Triag3D.cc -interpolation/element/Triag3D.h -interpolation/method/Intersect.cc -interpolation/method/Intersect.h interpolation/method/Method.cc interpolation/method/Method.h interpolation/method/MethodFactory.cc @@ -560,12 +608,6 @@ interpolation/method/PointIndex3.cc interpolation/method/PointIndex3.h interpolation/method/PointIndex2.cc interpolation/method/PointIndex2.h -interpolation/method/PointSet.cc -interpolation/method/PointSet.h -interpolation/method/Ray.cc -interpolation/method/Ray.h -interpolation/method/cubedsphere/CellFinder.cc -interpolation/method/cubedsphere/CellFinder.h interpolation/method/cubedsphere/CubedSphereBilinear.cc interpolation/method/cubedsphere/CubedSphereBilinear.h interpolation/method/knn/GridBox.cc @@ -617,7 +659,9 @@ interpolation/nonlinear/Missing.cc interpolation/nonlinear/Missing.h interpolation/nonlinear/NonLinear.cc interpolation/nonlinear/NonLinear.h +) +list( APPEND atlas_linalg_srcs linalg/Indexing.h linalg/Introspection.h linalg/View.h @@ -657,18 +701,17 @@ list( APPEND atlas_array_srcs array.h array_fwd.h array/Array.h +array/ArrayDataStore.cc +array/ArrayDataStore.h array/ArrayIdx.h array/ArrayLayout.h array/ArrayShape.h array/ArraySpec.cc array/ArraySpec.h array/ArrayStrides.h -array/ArrayUtil.cc -array/ArrayUtil.h array/ArrayView.h array/ArrayViewUtil.h array/ArrayViewDefs.h -array/DataType.cc array/DataType.h array/IndexView.h array/LocalView.cc @@ -682,6 +725,7 @@ array/helpers/ArrayAssigner.h array/helpers/ArrayWriter.h array/helpers/ArraySlicer.h array/helpers/ArrayCopier.h +array/helpers/ArrayForEach.h #array/Table.h #array/Table.cc #array/TableView.h @@ -713,26 +757,42 @@ array/native/NativeMakeView.cc ) endif() +list( APPEND atlas_parallel_srcs + parallel/Checksum.cc + parallel/Checksum.h + parallel/GatherScatter.cc + parallel/GatherScatter.h + parallel/HaloExchange.cc + parallel/HaloExchange.h + parallel/HaloAdjointExchangeImpl.h + parallel/HaloExchangeImpl.h + parallel/mpi/Buffer.h +) + list( APPEND atlas_util_srcs -parallel/Checksum.cc -parallel/Checksum.h -parallel/GatherScatter.cc -parallel/GatherScatter.h -parallel/HaloExchange.cc -parallel/HaloExchange.h -parallel/HaloAdjointExchangeImpl.h -parallel/HaloExchangeImpl.h -parallel/mpi/Buffer.h -runtime/Exception.cc -runtime/Exception.h -util/Config.cc -util/Config.h +util/Object.h +util/Object.cc +util/ObjectHandle.h +util/ObjectHandle.cc +util/Factory.h +util/Factory.cc +util/Bitflags.h +util/Checksum.h +util/Checksum.cc +util/MicroDeg.h +util/LonLatMicroDeg.h +util/CoordinateEnums.h +util/PeriodicTransform.h +util/Topology.h +util/Allocate.h +util/Allocate.cc + util/Constants.h util/ConvexSphericalPolygon.cc util/ConvexSphericalPolygon.h +util/DataType.cc +util/DataType.h util/Earth.h -util/GaussianLatitudes.cc -util/GaussianLatitudes.h util/Geometry.cc util/Geometry.h util/KDTree.cc @@ -752,10 +812,10 @@ util/SphericalPolygon.cc util/SphericalPolygon.h util/UnitSphere.h util/vector.h +util/mdspan.h +util/detail/mdspan/mdspan.hpp util/VectorOfAbstract.h -util/detail/BlackMagic.h util/detail/Cache.h -util/detail/Debug.h util/detail/KDTree.h util/function/MDPI_functions.h util/function/MDPI_functions.cc @@ -767,102 +827,6 @@ util/function/VortexRollup.h util/function/VortexRollup.cc ) -list( APPEND atlas_internals_srcs -mesh/detail/AccumulateFacets.h -mesh/detail/AccumulateFacets.cc -util/Object.h -util/Object.cc -util/ObjectHandle.h -util/ObjectHandle.cc -util/Factory.h -util/Factory.cc -util/Bitflags.h -util/Checksum.h -util/Checksum.cc -util/MicroDeg.h -mesh/IsGhostNode.h -util/LonLatMicroDeg.h -util/CoordinateEnums.h -util/PeriodicTransform.h -util/Topology.h -util/Unique.h -util/Unique.cc -util/Allocate.h -util/Allocate.cc -) - -list( APPEND atlas_io_srcs - io/atlas-io.h - io/Data.cc - io/Data.h - io/detail/Base64.cc - io/detail/Base64.h - io/detail/Checksum.h - io/detail/Checksum.cc - io/detail/DataInfo.h - io/detail/DataType.cc - io/detail/DataType.h - io/detail/Decoder.cc - io/detail/Decoder.h - io/detail/Defaults.h - io/detail/Encoder.cc - io/detail/Encoder.h - io/detail/Endian.h - io/detail/Link.cc - io/detail/Link.h - io/detail/ParsedRecord.h - io/detail/RecordInfo.h - io/detail/RecordSections.h - io/detail/Reference.h - io/detail/sfinae.h - io/detail/StaticAssert.h - io/detail/tag.h - io/detail/Time.cc - io/detail/Time.h - io/detail/Type.h - io/detail/TypeTraits.h - io/detail/Version.h - io/Exceptions.cc - io/Exceptions.h - io/FileStream.cc - io/FileStream.h - io/Metadata.cc - io/Metadata.h - io/print/TableFormat.cc - io/print/TableFormat.h - io/print/JSONFormat.cc - io/print/JSONFormat.h - io/print/Bytes.cc - io/print/Bytes.h - io/ReadRequest.cc - io/ReadRequest.h - io/Record.cc - io/Record.h - io/RecordItem.cc - io/RecordItem.h - io/RecordItemReader.cc - io/RecordItemReader.h - io/RecordPrinter.cc - io/RecordPrinter.h - io/RecordReader.cc - io/RecordReader.h - io/RecordWriter.cc - io/RecordWriter.h - io/Session.cc - io/Session.h - io/Stream.cc - io/Stream.h - io/types/array.h - io/types/array/ArrayMetadata.cc - io/types/array/ArrayMetadata.h - io/types/array/ArrayReference.cc - io/types/array/ArrayReference.h - io/types/array/adaptors/StdVectorAdaptor.h - io/types/string.h - io/types/scalar.h - io/types/scalar.cc -) - list( APPEND atlas_io_adaptor_srcs io/ArrayAdaptor.cc @@ -873,40 +837,62 @@ list( APPEND atlas_io_adaptor_srcs ### atlas c++ library -if( NOT atlas_HAVE_TRANS ) - unset( TRANSI_INCLUDE_DIRS ) - unset( TRANSI_LIBRARIES ) +if( NOT atlas_HAVE_ATLAS_GRID ) + unset( atlas_grid_srcs) endif() -if( SHORTCUT_COMPILATION ) - unset( atlas_grid_srcs ) +if( NOT atlas_HAVE_ATLAS_FUNCTIONSPACE ) + unset( atlas_grid_partitioning_srcs ) unset( atlas_mesh_srcs ) - unset( atlas_field_srcs ) unset( atlas_functionspace_srcs ) - unset( atlas_interpolation_srcs ) + unset( atlas_parallel_srcs ) + unset( atlas_output_srcs ) unset( atlas_redistribution_srcs ) + unset( atlas_linalg_srcs ) # only depends on array +endif() + +if( NOT atlas_HAVE_ATLAS_INTERPOLATION ) + unset( atlas_interpolation_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_TRANS ) + unset( atlas_trans_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_NUMERICS ) unset( atlas_numerics_srcs ) - unset( atlas_output_srcs ) - unset( atlas_util_srcs ) - unset( atlas_internals_srcs ) endif() +# +# atlas_src _________ io_adaptor +# | / +# array -------------- linalg +# | \________________________field +# util / +# | / +# grid / ( optional link ) +# | / +# mesh + functionspace + parallel +# ___________________________________________________________________ +# | | | | | +# trans interpolation output numerics redistribution list( APPEND source_list ${atlas_srcs} - ${atlas_array_srcs} + ${atlas_util_srcs} ${atlas_grid_srcs} - ${atlas_mesh_srcs} + ${atlas_array_srcs} ${atlas_field_srcs} + ${atlas_linalg_srcs} + ${atlas_grid_partitioning_srcs} + ${atlas_mesh_srcs} + ${atlas_parallel_srcs} ${atlas_functionspace_srcs} + ${atlas_trans_srcs} ${atlas_interpolation_srcs} ${atlas_redistribution_srcs} ${atlas_numerics_srcs} ${atlas_output_srcs} - ${atlas_util_srcs} ${atlas_io_adaptor_srcs} - ${atlas_internals_srcs} - ${CMAKE_CURRENT_BINARY_DIR}/library/git_sha1.h - ${CMAKE_CURRENT_BINARY_DIR}/library/defines.h ) if( ATLAS_GRIDTOOLS_STORAGE_BACKEND_CUDA ) @@ -924,20 +910,6 @@ atlas_host_device( source_list mesh/Connectivity.cc ) -#ecbuild_add_library( TARGET atlas_io - -# INSTALL_HEADERS ALL -# HEADER_DESTINATION include/atlas_io -# SOURCES ${atlas_io_srcs} -# PUBLIC_LIBS eckit -# PUBLIC_INCLUDES -# $ -# $ - - -#) - - ecbuild_add_library( TARGET atlas AUTO_VERSION @@ -951,7 +923,7 @@ ecbuild_add_library( TARGET atlas PRIVATE_LIBS $<${atlas_HAVE_FORTRAN}:fckit> - $<${atlas_HAVE_TRANS}:transi> + $<${atlas_HAVE_ECTRANS}:transi> $<${atlas_HAVE_ACC}:atlas_acc_support> ${CGAL_LIBRARIES} ${FFTW_LIBRARIES} @@ -981,4 +953,4 @@ ecbuild_add_library( TARGET atlas ) -target_compile_features( atlas PUBLIC cxx_std_11 ) +target_compile_features( atlas PUBLIC cxx_std_17 ) diff --git a/src/atlas/array.h b/src/atlas/array.h index 9fd56c9fc..c2cf7f720 100644 --- a/src/atlas/array.h +++ b/src/atlas/array.h @@ -18,10 +18,10 @@ #pragma once #include "atlas/array/Array.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayShape.h" #include "atlas/array/ArraySpec.h" #include "atlas/array/ArrayStrides.h" -#include "atlas/array/ArrayUtil.h" #include "atlas/array/ArrayView.h" #include "atlas/array/DataType.h" #include "atlas/array/LocalView.h" diff --git a/src/atlas/array/Array.h b/src/atlas/array/Array.h index 80339b911..9a7c22668 100644 --- a/src/atlas/array/Array.h +++ b/src/atlas/array/Array.h @@ -13,12 +13,13 @@ #include #include +#include "atlas/library/config.h" + #include "atlas/util/Object.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/DataType.h" #include "atlas/array_fwd.h" -#include "atlas/library/config.h" namespace atlas { namespace array { @@ -132,6 +133,17 @@ class Array : public util::Object { const ArraySpec& spec() const { return spec_; } + struct CopyPolicy { + enum class Execution { + SERIAL=0, + OMP=1 + }; + bool on_device = false; + Execution execution {Execution::SERIAL}; + }; + virtual void copy(const Array&, const CopyPolicy&) = 0; + void copy(const Array& other) { return copy(other,CopyPolicy{}); } + // -- dangerous methods... You're on your own interpreting the raw data template DATATYPE const* host_data() const { @@ -202,6 +214,8 @@ class ArrayT : public Array { virtual void resize(idx_t size0, idx_t size1, idx_t size2, idx_t size3); virtual void resize(idx_t size0, idx_t size1, idx_t size2, idx_t size3, idx_t size4); + virtual void copy(const Array&, const CopyPolicy&); + virtual array::DataType datatype() const { return array::DataType::create(); } virtual void dump(std::ostream& os) const; diff --git a/src/atlas/array/ArrayUtil.cc b/src/atlas/array/ArrayDataStore.cc similarity index 95% rename from src/atlas/array/ArrayUtil.cc rename to src/atlas/array/ArrayDataStore.cc index 655f65b54..5b4ee9e62 100644 --- a/src/atlas/array/ArrayUtil.cc +++ b/src/atlas/array/ArrayDataStore.cc @@ -10,7 +10,7 @@ #include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/runtime/Exception.h" namespace atlas { diff --git a/src/atlas/array/ArrayUtil.h b/src/atlas/array/ArrayDataStore.h similarity index 100% rename from src/atlas/array/ArrayUtil.h rename to src/atlas/array/ArrayDataStore.h diff --git a/src/atlas/array/ArraySpec.cc b/src/atlas/array/ArraySpec.cc index afa0d4db3..074c23074 100644 --- a/src/atlas/array/ArraySpec.cc +++ b/src/atlas/array/ArraySpec.cc @@ -10,7 +10,7 @@ #include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/library/config.h" #include "atlas/runtime/Exception.h" @@ -154,11 +154,11 @@ const std::vector& ArraySpec::stridesf() const { void ArraySpec::allocate_fortran_specs() { shapef_.resize(rank_); + stridesf_.resize(rank_); for (idx_t j = 0; j < rank_; ++j) { shapef_[j] = shape_[rank_ - 1 - layout_[j]]; + stridesf_[j] = strides_[rank_ -1 - layout_[j]]; } - stridesf_.resize(strides_.size()); - std::reverse_copy(strides_.begin(), strides_.end(), stridesf_.begin()); } } // namespace array diff --git a/src/atlas/array/DataType.h b/src/atlas/array/DataType.h index 016adcb59..f11f060bb 100644 --- a/src/atlas/array/DataType.h +++ b/src/atlas/array/DataType.h @@ -10,405 +10,5 @@ #pragma once -#include +#include "atlas/util/DataType.h" -//------------------------------------------------------------------------------------------------------ - -// For type safety we want to use std::byte for the DataType "BYTE", but it is a C++17 feature. -// Backport std::byte here without any operations -#if __cplusplus >= 201703L -#include -#else -#ifndef STD_BYTE_DEFINED -#define STD_BYTE_DEFINED -namespace std { -#ifdef _CRAYC -struct byte { - unsigned char byte_; -}; -#else -enum class byte : unsigned char -{ -}; -#endif -} // namespace std -#endif -#endif - -//------------------------------------------------------------------------------------------------------ - -namespace atlas { -namespace array { - -class DataType { -public: - typedef long kind_t; - static const kind_t KIND_BYTE = 1; - static const kind_t KIND_INT32 = -4; - static const kind_t KIND_INT64 = -8; - static const kind_t KIND_REAL32 = 4; - static const kind_t KIND_REAL64 = 8; - static const kind_t KIND_UINT64 = -16; - - template - static DataType create(); - - static DataType byte() { return DataType(KIND_BYTE); } - static DataType int32() { return DataType(KIND_INT32); } - static DataType int64() { return DataType(KIND_INT64); } - static DataType real32() { return DataType(KIND_REAL32); } - static DataType real64() { return DataType(KIND_REAL64); } - static DataType uint64() { return DataType(KIND_UINT64); } - - template - static kind_t kind(); - template - static kind_t kind(const DATATYPE&); - - template - static std::string str(); - template - static std::string str(const DATATYPE); - - static kind_t str_to_kind(const std::string&); - static std::string kind_to_str(kind_t); - static bool kind_valid(kind_t); - -private: - static std::string byte_str() { return "byte"; } - static std::string int32_str() { return "int32"; } - static std::string int64_str() { return "int64"; } - static std::string real32_str() { return "real32"; } - static std::string real64_str() { return "real64"; } - static std::string uint64_str() { return "uint64"; } - - [[noreturn]] static void throw_not_recognised(kind_t); - [[noreturn]] static void throw_not_recognised(std::string datatype); - -public: - DataType(const std::string&); - DataType(long); - DataType(const DataType&); - DataType& operator=(const DataType&); - std::string str() const { return kind_to_str(kind_); } - kind_t kind() const { return kind_; } - size_t size() const { return (kind_ == KIND_UINT64) ? 8 : std::abs(kind_); } - - friend bool operator==(DataType dt1, DataType dt2); - friend bool operator!=(DataType dt1, DataType dt2); - friend bool operator==(DataType dt, kind_t kind); - friend bool operator!=(DataType dt, kind_t kind); - friend bool operator==(kind_t kind, DataType dt); - friend bool operator!=(kind_t kind, DataType dt2); - -private: - kind_t kind_; -}; - -template <> -inline std::string DataType::str() { - return byte_str(); -} -template <> -inline std::string DataType::str() { - return byte_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(int) == 4, ""); - return int32_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(int) == 4, ""); - return int32_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(long) == 8, ""); - return int64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(long) == 8, ""); - return int64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(long long) == 8, ""); - return int64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(long long) == 8, ""); - return int64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(float) == 4, ""); - return real32_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(float) == 4, ""); - return real32_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(double) == 8, ""); - return real64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(double) == 8, ""); - return real64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(unsigned long) == 8, ""); - return uint64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(unsigned long) == 8, ""); - return uint64_str(); -} - -template <> -inline std::string DataType::str() { - static_assert(sizeof(unsigned long long) == 8, ""); - return uint64_str(); -} -template <> -inline std::string DataType::str() { - static_assert(sizeof(unsigned long long) == 8, ""); - return uint64_str(); -} -template <> -inline std::string DataType::str(const int&) { - return str(); -} -template <> -inline std::string DataType::str(const long&) { - return str(); -} -template <> -inline std::string DataType::str(const long long&) { - return str(); -} -template <> -inline std::string DataType::str(const unsigned long&) { - return str(); -} -template <> -inline std::string DataType::str(const unsigned long long&) { - return str(); -} -template <> -inline std::string DataType::str(const float&) { - return str(); -} -template <> -inline std::string DataType::str(const double&) { - return str(); -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(std::byte) == 1, ""); - return KIND_BYTE; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(std::byte) == 1, ""); - return KIND_BYTE; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(int) == 4, ""); - return KIND_INT32; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(int) == 4, ""); - return KIND_INT32; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(long) == 8, ""); - return KIND_INT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(long) == 8, ""); - return KIND_INT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(long long) == 8, ""); - return KIND_INT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(long long) == 8, ""); - return KIND_INT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(unsigned long) == 8, ""); - return KIND_UINT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(unsigned long) == 8, ""); - return KIND_UINT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(unsigned long long) == 8, ""); - return KIND_UINT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(unsigned long long) == 8, ""); - return KIND_UINT64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(float) == 4, ""); - return KIND_REAL32; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(float) == 4, ""); - return KIND_REAL32; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(double) == 8, ""); - return KIND_REAL64; -} -template <> -inline DataType::kind_t DataType::kind() { - static_assert(sizeof(double) == 8, ""); - return KIND_REAL64; -} -template <> -inline DataType::kind_t DataType::kind(const int&) { - return kind(); -} -template <> -inline DataType::kind_t DataType::kind(const long&) { - return kind(); -} -template <> -inline DataType::kind_t DataType::kind(const unsigned long&) { - return kind(); -} -template <> -inline DataType::kind_t DataType::kind(const float&) { - return kind(); -} -template <> -inline DataType::kind_t DataType::kind(const double&) { - return kind(); -} - -inline DataType::kind_t DataType::str_to_kind(const std::string& datatype) { - if (datatype == "int32") - return KIND_INT32; - else if (datatype == "int64") - return KIND_INT64; - else if (datatype == "uint64") - return KIND_UINT64; - else if (datatype == "real32") - return KIND_REAL32; - else if (datatype == "real64") - return KIND_REAL64; - else if (datatype == "byte") { - return KIND_BYTE; - } - else { - throw_not_recognised(datatype); - } -} -inline std::string DataType::kind_to_str(kind_t kind) { - switch (kind) { - case KIND_INT32: - return int32_str(); - case KIND_INT64: - return int64_str(); - case KIND_UINT64: - return uint64_str(); - case KIND_REAL32: - return real32_str(); - case KIND_REAL64: - return real64_str(); - case KIND_BYTE: - return byte_str(); - default: - throw_not_recognised(kind); - } -} -inline bool DataType::kind_valid(kind_t kind) { - switch (kind) { - case KIND_BYTE: - case KIND_INT32: - case KIND_INT64: - case KIND_UINT64: - case KIND_REAL32: - case KIND_REAL64: - return true; - default: - return false; - } -} - -inline DataType::DataType(const DataType& other): kind_(other.kind_) {} - -inline DataType& DataType::operator=(const DataType& other) { - kind_ = other.kind_; - return *this; -} - -inline DataType::DataType(const std::string& datatype): kind_(str_to_kind(datatype)) {} - -inline DataType::DataType(long kind): kind_(kind) {} - -inline bool operator==(DataType dt1, DataType dt2) { - return dt1.kind_ == dt2.kind_; -} - -inline bool operator!=(DataType dt1, DataType dt2) { - return dt1.kind_ != dt2.kind_; -} - -inline bool operator==(DataType dt, DataType::kind_t kind) { - return dt.kind_ == kind; -} - -inline bool operator!=(DataType dt, DataType::kind_t kind) { - return dt.kind_ != kind; -} - -inline bool operator==(DataType::kind_t kind, DataType dt) { - return dt.kind_ == kind; -} - -inline bool operator!=(DataType::kind_t kind, DataType dt) { - return dt.kind_ != kind; -} - -template -inline DataType DataType::create() { - return DataType(DataType::kind()); -} - -template -inline DataType make_datatype() { - return DataType(DataType::kind()); -} - -//------------------------------------------------------------------------------------------------------ - -} // namespace array -} // namespace atlas diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index 26b2ced2e..0adae4277 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -17,8 +17,9 @@ #include #include +#include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayViewDefs.h" #include "atlas/array/helpers/ArraySlicer.h" #include "atlas/library/config.h" @@ -100,17 +101,12 @@ class LocalView { public: // -- Constructors - ENABLE_IF_CONST_WITH_NON_CONST(value_type) - LocalView(value_type* data, const idx_t shape[], const idx_t strides[]): data_(data) { - size_ = 1; - for (idx_t j = 0; j < Rank; ++j) { - shape_[j] = shape[j]; - strides_[j] = strides[j]; - size_ *= shape_[j]; - } - } - LocalView(value_type* data, const idx_t shape[], const idx_t strides[]): data_(data) { + template >> + LocalView(const LocalView& other): data_(other.data_), size_(other.size_), shape_(other.shape_), strides_(other.strides_) {} + + template && std::is_integral_v && std::is_integral_v>> + LocalView(ValueTp* data, const Int1 shape[], const Int2 strides[]): data_(data) { size_ = 1; for (idx_t j = 0; j < Rank; ++j) { shape_[j] = shape[j]; @@ -119,18 +115,8 @@ class LocalView { } } - - ENABLE_IF_CONST_WITH_NON_CONST(value_type) - LocalView(value_type* data, const idx_t shape[]): data_(data) { - size_ = 1; - for (int j = Rank - 1; j >= 0; --j) { - shape_[j] = shape[j]; - strides_[j] = size_; - size_ *= shape_[j]; - } - } - - LocalView(value_type* data, const idx_t shape[]): data_(data) { + template && std::is_integral_v>> + LocalView(ValueTp* data, const Int shape[]): data_(data) { size_ = 1; for (int j = Rank - 1; j >= 0; --j) { shape_[j] = shape[j]; @@ -139,26 +125,8 @@ class LocalView { } } - - template ::value && - !std::is_const::value>::type> - LocalView(value_type* data, const ArrayShape& shape): data_(data) { - size_ = 1; - for (int j = Rank - 1; j >= 0; --j) { - shape_[j] = shape[j]; - strides_[j] = size_; - size_ *= shape_[j]; - } - } - - LocalView(value_type* data, const ArrayShape& shape): data_(data) { - size_ = 1; - for (int j = Rank - 1; j >= 0; --j) { - shape_[j] = shape[j]; - strides_[j] = size_; - size_ *= shape_[j]; - } - } + template >> + LocalView(ValueTp* data, const ArrayShape& shape) : LocalView(data,shape.data()) {} ENABLE_IF_CONST_WITH_NON_CONST(value_type) operator const LocalView&() const { @@ -169,28 +137,28 @@ class LocalView { // -- Access methods - template - value_type& operator()(Ints... idx) { + template > + value_type& operator()(Idx... idx) { check_bounds(idx...); return data_[index(idx...)]; } - template - const value_type& operator()(Ints... idx) const { + template > + const value_type& operator()(Idx... idx) const { check_bounds(idx...); return data_[index(idx...)]; } - template - typename std::enable_if<(Rank == 1 && EnableBool), const value_type&>::type operator[](Int idx) const { + template > + const value_type& operator[](Idx idx) const { check_bounds(idx); - return data_[idx]; + return data_[index(idx)]; } - template - typename std::enable_if<(Rank == 1 && EnableBool), value_type&>::type operator[](Int idx) { + template > + value_type& operator[](Idx idx) { check_bounds(idx); - return data_[idx]; + return data_[index(idx)]; } idx_t size() const { return size_; } @@ -205,9 +173,9 @@ class LocalView { return strides_[idx]; } - const idx_t* shape() const { return shape_; } + const idx_t* shape() const { return shape_.data(); } - const idx_t* strides() const { return strides_; } + const idx_t* strides() const { return strides_.data(); } value_type const* data() const { return data_; } @@ -263,7 +231,9 @@ class LocalView { } #else template - void check_bounds(Ints...) const {} + void check_bounds(Ints... idx) const { + static_assert(sizeof...(idx) == Rank, "Expected number of indices is different from rank of array"); + } #endif template @@ -289,15 +259,19 @@ class LocalView { private: // -- Private data + template friend class LocalView; value_type* data_; idx_t size_; - idx_t shape_[Rank]; - idx_t strides_[Rank]; + std::array shape_; + std::array strides_; #undef ENABLE_IF_NON_CONST #undef ENABLE_IF_CONST_WITH_NON_CONST }; +template +using View = LocalView; + } // namespace array } // namespace atlas diff --git a/src/atlas/array/Range.h b/src/atlas/array/Range.h index d2c588a62..2a88d8ee4 100644 --- a/src/atlas/array/Range.h +++ b/src/atlas/array/Range.h @@ -10,6 +10,8 @@ #pragma once +#include + //------------------------------------------------------------------------------ namespace atlas { @@ -75,6 +77,10 @@ class RangeAll : public RangeBase { int end(const View& view, int i) const { return view.shape(i); } + friend std::ostream& operator<<(std::ostream& out, const RangeAll& range) { + out << ":"; + return out; + } }; class RangeDummy : public RangeBase {}; diff --git a/src/atlas/array/gridtools/GridToolsArray.cc b/src/atlas/array/gridtools/GridToolsArray.cc index 5c5a0c5cb..78f9b37f0 100644 --- a/src/atlas/array/gridtools/GridToolsArray.cc +++ b/src/atlas/array/gridtools/GridToolsArray.cc @@ -13,7 +13,7 @@ #include #include "atlas/array.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayView.h" #include "atlas/array/DataType.h" #include "atlas/array/MakeView.h" @@ -67,7 +67,7 @@ class ArrayT_impl { static_assert(sizeof...(UInts) > 0, "1"); auto gt_storage = create_gt_storage::type>(dims...); using data_store_t = typename std::remove_pointer::type; - array_.data_store_ = std::unique_ptr>(gt_storage); + array_.data_store_ = std::make_unique>(gt_storage); array_.spec_ = make_spec(gt_storage, dims...); } @@ -249,7 +249,7 @@ class ArrayT_impl { Array* resized = Array::create(ArrayShape{(idx_t)c...}); - array_initializer::apply(array_, *resized); + array_initializer::apply(array_, *resized); array_.replace(*resized); delete resized; } @@ -258,7 +258,6 @@ class ArrayT_impl { void apply_resize(const ArrayShape& shape, std::integer_sequence) { return resize_variadic(shape[Indices]...); } - private: ArrayT& array_; }; @@ -493,6 +492,11 @@ void ArrayT::resize(idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3, idx_t ArrayT_impl(*this).resize_variadic(dim0, dim1, dim2, dim3, dim4); } +template +void ArrayT::copy(const Array& other, const Array::CopyPolicy&) { + array_initializer::apply(other, *this); +} + template void ArrayT::dump(std::ostream& out) const { switch (rank()) { diff --git a/src/atlas/array/gridtools/GridToolsArrayHelpers.h b/src/atlas/array/gridtools/GridToolsArrayHelpers.h index 105eeee54..cb064fc51 100644 --- a/src/atlas/array/gridtools/GridToolsArrayHelpers.h +++ b/src/atlas/array/gridtools/GridToolsArrayHelpers.h @@ -16,7 +16,7 @@ #include #include "atlas/array.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/DataType.h" #include "atlas/array/gridtools/GridToolsTraits.h" #include "atlas/array_fwd.h" diff --git a/src/atlas/array/gridtools/GridToolsArrayView.h b/src/atlas/array/gridtools/GridToolsArrayView.h index 899646d54..69639c7ab 100644 --- a/src/atlas/array/gridtools/GridToolsArrayView.h +++ b/src/atlas/array/gridtools/GridToolsArrayView.h @@ -16,7 +16,7 @@ #include #include "atlas/array/Array.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayViewDefs.h" #include "atlas/array/LocalView.h" #include "atlas/array/gridtools/GridToolsMakeView.h" diff --git a/src/atlas/array/gridtools/GridToolsDataStore.h b/src/atlas/array/gridtools/GridToolsDataStore.h index 6e277b04e..6758c74ba 100644 --- a/src/atlas/array/gridtools/GridToolsDataStore.h +++ b/src/atlas/array/gridtools/GridToolsDataStore.h @@ -10,7 +10,7 @@ #pragma once -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/gridtools/GridToolsTraits.h" //------------------------------------------------------------------------------ diff --git a/src/atlas/array/gridtools/GridToolsIndexView.cc b/src/atlas/array/gridtools/GridToolsIndexView.cc index 7784d6f55..2e9f37518 100644 --- a/src/atlas/array/gridtools/GridToolsIndexView.cc +++ b/src/atlas/array/gridtools/GridToolsIndexView.cc @@ -21,6 +21,128 @@ namespace atlas { namespace array { +namespace detail { + +template +struct host_device_array { // Copied from GridToolsArrayView.cc + ATLAS_HOST_DEVICE host_device_array(std::initializer_list list) { + size_t i(0); + for (const T v : list) { + data_[i++] = v; + } + } + ATLAS_HOST_DEVICE ~host_device_array() = default; + + ATLAS_HOST_DEVICE const T* data() const { return data_; } + + T operator[](int i) const { return data_[i]; } + + T data_[Rank]; +}; + + +template +struct StoragePropBuilder; // Copied from GridToolsArrayView.cc + +template +struct StoragePropBuilder> { // Copied from GridToolsArrayView.cc + static host_device_array buildStrides(const StorageInfo& storage_info) { + return host_device_array{ + (ArrayStrides::value_type)storage_info.template stride()...}; + } + static host_device_array buildShapes(const StorageInfo& storage_info) { + return host_device_array{storage_info.template total_length()...}; + } +}; + +} + + +template +IndexView::IndexView(const Array& array, bool _device_view): + gt_data_view_(_device_view ? gridtools::make_gt_device_view(array) + : gridtools::make_gt_host_view(array)), + data_store_orig_(&array.data_store()), + array_(&array), + is_device_view_(_device_view) { + if (gt_data_view_.valid()) { + constexpr static unsigned int ndims = data_view_t::data_store_t::storage_info_t::ndims; + + using storage_info_ty = gridtools::storage_traits::storage_info_t<0, ndims>; + using data_store_t = gridtools::storage_traits::data_store_t; + + auto storage_info_ = + *((reinterpret_cast(const_cast(array.storage())))->get_storage_info_ptr()); + + auto stridest = + detail::StoragePropBuilder>::buildStrides( + storage_info_); + auto shapet = + detail::StoragePropBuilder>::buildShapes( + storage_info_); + + + for (int i = 0; i < Rank; ++i) { + strides_[i] = stridest[i]; + shape_[i] = shapet[i]; + } + + size_ = storage_info_.total_length(); + } + else { + std::fill_n(shape_, Rank, 0); + std::fill_n(strides_, Rank, 0); + + size_ = 0; + } +} + + + + // IndexView::IndexView(data_view_t data_view): gt_data_view_(data_view) { + // if (gt_data_view_.valid()) { + // size_ = gt_data_view_.storage_info().total_length(); + // } + // else { + // size_ = 0; + // } + + + + // if (gt_data_view_.valid()) { + // constexpr static unsigned int ndims = data_view_t::data_store_t::storage_info_t::ndims; + + // using storage_info_ty = gridtools::storage_traits::storage_info_t<0, ndims>; + // using data_store_t = gridtools::storage_traits::data_store_t; + + // auto storage_info_ = + // *((reinterpret_cast(const_cast(array.storage())))->get_storage_info_ptr()); + + // auto stridest = + // StoragePropBuilder>::buildStrides( + // storage_info_); + // auto shapet = + // StoragePropBuilder>::buildShapes( + // storage_info_); + + + // for (int i = 0; i < Rank; ++i) { + // strides_[i] = stridest[i]; + // shape_[i] = shapet[i]; + // } + + // size_ = storage_info_.total_length(); + // } + // else { + // std::fill_n(shape_, Rank, 0); + // std::fill_n(strides_, Rank, 0); + + // size_ = 0; + // } + + // } + + //------------------------------------------------------------------------------------------------------ template diff --git a/src/atlas/array/gridtools/GridToolsIndexView.h b/src/atlas/array/gridtools/GridToolsIndexView.h index 8dcba7d7e..212a82908 100644 --- a/src/atlas/array/gridtools/GridToolsIndexView.h +++ b/src/atlas/array/gridtools/GridToolsIndexView.h @@ -11,6 +11,7 @@ #pragma once #include +#include "atlas/array/Array.h" #include "atlas/array/gridtools/GridToolsTraits.h" #include "atlas/library/config.h" @@ -100,13 +101,11 @@ class IndexView { using data_view_t = gridtools::data_view_tt::type, Rank, gridtools::get_access_mode()>; + using value_type = Value; + public: - IndexView(data_view_t data_view): gt_data_view_(data_view) { - if (data_view.valid()) - size_ = gt_data_view_.storage_info().total_length(); - else - size_ = 0; - } + IndexView(data_view_t); + IndexView(const Array&, bool device_view); template ::type> Index ATLAS_HOST_DEVICE operator()(Coords... c) { @@ -121,11 +120,27 @@ class IndexView { idx_t size() const { return size_; } + template + idx_t shape(Int idx) const { + return shape_[idx]; + } + + template + idx_t stride(Int idx) const { + return strides_[idx]; + } + + void dump(std::ostream& os) const; private: data_view_t gt_data_view_; idx_t size_; + idx_t shape_[Rank]; + idx_t strides_[Rank]; + bool is_device_view_; + ArrayDataStore const* data_store_orig_; + Array const* array_; #undef INDEX_REF #undef TO_FORTRAN diff --git a/src/atlas/array/gridtools/GridToolsMakeView.cc b/src/atlas/array/gridtools/GridToolsMakeView.cc index b6e5cfd7f..1d41776d2 100644 --- a/src/atlas/array/gridtools/GridToolsMakeView.cc +++ b/src/atlas/array/gridtools/GridToolsMakeView.cc @@ -135,24 +135,30 @@ ArrayView make_view(const Array& array) { template IndexView make_host_indexview(Array& array) { - using value_t = typename std::remove_const::type; - using storage_info_ty = gridtools::storage_traits::storage_info_t<0, Rank>; - using data_store_t = gridtools::storage_traits::data_store_t; + // using value_t = typename std::remove_const::type; + // using storage_info_ty = gridtools::storage_traits::storage_info_t<0, Rank>; + // using data_store_t = gridtools::storage_traits::data_store_t; - data_store_t* ds = reinterpret_cast(const_cast(array.storage())); + // data_store_t* ds = reinterpret_cast(const_cast(array.storage())); - return IndexView(::gridtools::make_host_view()>(*ds)); + // return IndexView(::gridtools::make_host_view()>(*ds)); + check_metadata(array); + constexpr bool device_view = false; + return IndexView(array, device_view); } template IndexView make_host_indexview(const Array& array) { - using value_t = typename std::remove_const::type; - using storage_info_ty = gridtools::storage_traits::storage_info_t<0, Rank>; - using data_store_t = gridtools::storage_traits::data_store_t; + // using value_t = typename std::remove_const::type; + // using storage_info_ty = gridtools::storage_traits::storage_info_t<0, Rank>; + // using data_store_t = gridtools::storage_traits::data_store_t; - data_store_t* ds = reinterpret_cast(const_cast(array.storage())); + // data_store_t* ds = reinterpret_cast(const_cast(array.storage())); - return IndexView(::gridtools::make_host_view()>(*ds)); + // return IndexView(::gridtools::make_host_view()>(*ds)); + check_metadata(array); + constexpr bool device_view = false; + return IndexView(array, device_view); } @@ -160,14 +166,16 @@ IndexView make_host_indexview(const Array& array) { template IndexView make_indexview(Array& array) { - check_metadata(array); - return make_host_indexview(array); + // check_metadata(array); + // constexpr bool device_view = false; + // return IndexView(array, device_view); + return make_host_indexview(array); + } template IndexView make_indexview(const Array& array) { - check_metadata(array); - return make_host_indexview(array); + return make_host_indexview(array); } } // namespace array diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h new file mode 100644 index 000000000..38d4d6441 --- /dev/null +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -0,0 +1,375 @@ +/* + * (C) Crown Copyright 2023 Met Office + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#pragma once + +#include +#include +#include + +#include "atlas/array/ArrayView.h" +#include "atlas/array/Range.h" +#include "atlas/array/helpers/ArraySlicer.h" +#include "atlas/parallel/omp/omp.h" +#include "atlas/runtime/Exception.h" +#include "atlas/util/Config.h" + +namespace atlas { + +namespace execution { + +// As in C++17 std::execution namespace. Note: unsequenced_policy is a C++20 addition. +class sequenced_policy {}; +class unsequenced_policy {}; +class parallel_unsequenced_policy {}; +class parallel_policy {}; + +// execution policy objects as in C++ std::execution namespace. Note: unseq is a C++20 addition. +inline constexpr sequenced_policy seq{ /*unspecified*/ }; +inline constexpr parallel_policy par{ /*unspecified*/ }; +inline constexpr parallel_unsequenced_policy par_unseq{ /*unspecified*/ }; +inline constexpr unsequenced_policy unseq{ /*unspecified*/ }; + + +// Type names for execution policy (Not in C++ standard) +template +constexpr std::string_view policy_name() { + return "unsupported"; +}; +template <> +constexpr std::string_view policy_name() { + return "sequenced_policy"; +}; +template <> +constexpr std::string_view policy_name() { + return "unsequenced_policy"; +}; +template <> +constexpr std::string_view policy_name() { + return "parallel_unsequenced_policy"; +}; +template <> +constexpr std::string_view policy_name() { + return "parallel_policy"; +}; + +template +constexpr std::string_view policy_name(execution_policy) { + return policy_name>(); +} + +// Type check for execution policy (Not in C++ standard) +template +constexpr auto is_execution_policy() { return + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; +} + +template +constexpr auto demote_policy() { + if constexpr(std::is_same_v) { + return unseq; + } + else if constexpr (std::is_same_v) { + return seq; + } + else { + return ExecutionPolicy{}; + } +} + +template +constexpr auto is_omp_policy() { return + std::is_same_v || + std::is_same_v; +} + + +template +using demote_policy_t = decltype(demote_policy()); + +} // namespace execution + +namespace option { + +// Convert execution_policy objects to a util::Config + +template +util::Config execution_policy() { + return util::Config("execution_policy", execution::policy_name>()); +} + +template +util::Config execution_policy(T) { + return execution_policy>(); +} + +} // namespace option + +namespace array { +namespace helpers { + +namespace detail { + +struct NoMask { + template + constexpr bool operator()(Args...) const { return 0; } +}; + +inline constexpr NoMask no_mask; + +template +constexpr auto tuplePushBack(const std::tuple& tuple, T value) { + return std::tuple_cat(tuple, std::make_tuple(value)); +} + +template +void forEach(idx_t idxMax, const Functor& functor) { + + if constexpr(execution::is_omp_policy()) { + atlas_omp_parallel_for(auto idx = idx_t{}; idx < idxMax; ++idx) { + functor(idx); + } + } + else { + // Simple for-loop for sequenced or unsequenced execution policies. + for (auto idx = idx_t{}; idx < idxMax; ++idx) { + functor(idx); + } + } +} + +template +constexpr auto argPadding() { + if constexpr(NPad > 0) { + return std::tuple_cat(std::make_tuple(Range::all()), + argPadding()); + } + else { + return std::make_tuple(); + } +} + +template +auto makeSlices(const std::tuple& slicerArgs, + ArrayViewTuple&& arrayViews) { + + constexpr auto nb_views = std::tuple_size_v; + + auto&& arrayView = std::get(arrayViews); + using ArrayView = std::decay_t; + + constexpr auto Dim = sizeof...(SlicerArgs); + constexpr auto Rank = ArrayView::rank(); + + static_assert (Dim <= Rank, "Error: number of slicer arguments exceeds the rank of ArrayView."); + const auto paddedArgs = std::tuple_cat(slicerArgs, argPadding()); + + const auto slicer = [&arrayView](const auto&... args) { + return std::make_tuple(arrayView.slice(args...)); + }; + + if constexpr (ViewIdx == nb_views-1) { + return std::apply(slicer, paddedArgs); + } + else { + // recurse + return std::tuple_cat(std::apply(slicer, paddedArgs), + makeSlices(slicerArgs, std::forward(arrayViews))); + } +} + +template +struct ArrayForEachImpl; + +template +struct ArrayForEachImpl { + template + static void apply(ArrayViewTuple&& arrayViews, + const Mask& mask, + const Function& function, + const std::tuple& slicerArgs, + const std::tuple& maskArgs) { + // Iterate over this dimension. + if constexpr(Dim == ItrDim) { + + // Get size of iteration dimenion from first view argument. + const auto idxMax = std::get<0>(arrayViews).shape(ItrDim); + + forEach(idxMax, [&](idx_t idx) { + // Demote parallel execution policy to a non-parallel one in further recursion + ArrayForEachImpl, Dim + 1, ItrDims...>::apply( + std::forward(arrayViews), mask, function, + tuplePushBack(slicerArgs, idx), + tuplePushBack(maskArgs, idx)); + }); + } + // Add a RangeAll to arguments. + else { + ArrayForEachImpl::apply( + std::forward(arrayViews), mask, function, + tuplePushBack(slicerArgs, Range::all()), + maskArgs); + } + } +}; + +template + struct is_applicable : std::false_type {}; + +template +struct is_applicable> : std::is_invocable {}; + +template +inline constexpr bool is_applicable_v = is_applicable::value; + +template +struct ArrayForEachImpl { + + template + static void apply(ArrayViewTuple&& arrayViews, + const Mask& mask, + const Function& function, + const std::tuple& slicerArgs, + const std::tuple& maskArgs) { + + constexpr auto maskPresent = !std::is_same_v; + + if constexpr (maskPresent) { + + constexpr auto invocableMask = std::is_invocable_r_v; + static_assert (invocableMask, + "Cannot invoke mask function with given arguments.\n" + "Make sure you arguments are N integers (or auto...) " + "where N == sizeof...(ItrDims). Function must return an int." + ); + + if (std::apply(mask, maskArgs)) { + return; + } + + } + + auto slices = makeSlices(slicerArgs, std::forward(arrayViews)); + + constexpr auto applicable = is_applicable_v; + static_assert(applicable, "Cannot invoke function with given arguments. " + "Make sure you the arguments are rvalue references (Slice&&) or const references (const Slice&) or regular value (Slice)" ); + std::apply(function, std::move(slices)); + } + +}; +} // namespace detail + +/// brief Array "For-Each" helper struct. +/// +/// detail Iterates over dimensions given in ItrDims. Slices over full range +/// of other dimensions. +/// Note: ItrDims must be given in ascending numerical order. TODO: Static +/// checking for this. +template +struct ArrayForEach { + /// brief Apply "For-Each" method. + /// + /// details Visits all elements indexed by ItrDims and creates a slice from + /// each ArrayView in arrayViews. Slices are sent to function + /// which is executed with the signature f(slice1, slice2,...). + /// Iterations are skipped when mask evaluates to "true" + /// and is executed with signature g(idx_i, idx_j,...), where the idxs + /// are indices of ItrDims. + /// When a config is supplied containing "execution_policy" = + /// "sequenced_policy" (default). All loops are then executed in sequential + /// (row-major) order. + /// With "execution_policy" = "parallel_unsequenced" the first loop is executed + /// using OpenMP. The remaining loops are executed in serial. + /// Note: The lowest ArrayView.rank() must be greater than or equal + /// to the highest dim in ItrDims. TODO: static checking for this. + template + static void apply(const eckit::Parametrisation& conf, + std::tuple&& arrayViews, + const Mask& mask, const Function& function) { + + auto execute = [&](auto execution_policy) { + apply(execution_policy, std::move(arrayViews), mask, function); + }; + + using namespace execution; + std::string execution_policy; + if (conf.get("execution_policy",execution_policy)) { + if (execution_policy == policy_name(par_unseq)) { + execute(par_unseq); + } + else if (execution_policy == policy_name(par)) { + execute(par); + } + else if (execution_policy == policy_name(unseq)) { + execute(unseq); + } + else if (execution_policy == policy_name(seq)) { + execute(seq); + } + else { + throw_Exception("Unrecognized execution policy "+execution_policy, Here()); + } + } + else { + execute(seq); + } + } + + /// brief Apply "For-Each" method. + /// + /// details As above, but Execution policy is determined at compile-time. + template ()>> + static void apply(ExecutionPolicy, std::tuple&& arrayViews, const Mask& mask, const Function& function) { + detail::ArrayForEachImpl::apply( + std::move(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); + } + + /// brief Apply "For-Each" method + /// + /// details Apply ForEach with default execution policy. + template + static void apply(std::tuple&& arrayViews, const Mask& mask, const Function& function) { + apply(std::move(arrayViews), mask, function); + } + + /// brief Apply "For-Each" method + /// + /// details Apply ForEach with run-time determined execution policy and no mask. + template + static void apply(const eckit::Parametrisation& conf, std::tuple&& arrayViews, const Function& function) { + apply(conf, std::move(arrayViews), detail::no_mask, function); + } + + /// brief Apply "For-Each" method + /// + /// details Apply ForEach with compile-time determined execution policy and no mask. + template ()>> + static void apply(ExecutionPolicy executionPolicy, std::tuple&& arrayViews, const Function& function) { + apply(executionPolicy, std::move(arrayViews), detail::no_mask, function); + } + + /// brief Apply "For-Each" method + /// + /// details Apply ForEach with default execution policy and no mask. + template + static void apply(std::tuple&& arrayViews, const Function& function) { + apply(execution::seq, std::move(arrayViews), function); + } + +}; + +} // namespace helpers +} // namespace array +} // namespace atlas diff --git a/src/atlas/array/helpers/ArrayInitializer.h b/src/atlas/array/helpers/ArrayInitializer.h index 1ffe3e258..61c1cccb0 100644 --- a/src/atlas/array/helpers/ArrayInitializer.h +++ b/src/atlas/array/helpers/ArrayInitializer.h @@ -27,7 +27,6 @@ namespace helpers { //------------------------------------------------------------------------------ -template struct array_initializer; template @@ -65,9 +64,45 @@ struct array_initializer_impl { //------------------------------------------------------------------------------ -template struct array_initializer { - static void apply(Array const& orig, Array& array_resized) { + + static void apply(Array const& from, Array& to) { + ATLAS_ASSERT(from.rank() == to.rank()); + switch (from.rank()) { + case 1: + apply_rank<1>(from, to); + break; + case 2: + apply_rank<2>(from, to); + break; + case 3: + apply_rank<3>(from, to); + break; + case 4: + apply_rank<4>(from, to); + break; + case 5: + apply_rank<5>(from, to); + break; + case 6: + apply_rank<6>(from, to); + break; + case 7: + apply_rank<7>(from, to); + break; + case 8: + apply_rank<8>(from, to); + break; + case 9: + apply_rank<9>(from, to); + break; + default: + ATLAS_NOTIMPLEMENTED; + } + } + + template + static void apply_rank(Array const& orig, Array& array_resized) { switch (orig.datatype().kind()) { case DataType::KIND_REAL64: return array_initializer_impl::apply(orig, array_resized); diff --git a/src/atlas/array/helpers/ArraySlicer.h b/src/atlas/array/helpers/ArraySlicer.h index 72c88ada3..611f8a1a3 100644 --- a/src/atlas/array/helpers/ArraySlicer.h +++ b/src/atlas/array/helpers/ArraySlicer.h @@ -26,6 +26,7 @@ template struct Reference { Value& value; operator Value&() { return value; } + operator const Value&() const { return value; } template void operator=(const T a) { value = a; @@ -48,6 +49,21 @@ struct Reference { ++value; return *this; } + template + bool operator==(const T a) { + return value == a; + } + template + bool operator!=(const T a) { + return value != a; + } + friend std::ostream& operator<<(std::ostream& out, const Reference& ref) { + out << ref.value; + return out; + } + constexpr int shape(idx_t) { return 0; } + constexpr int stride(idx_t) { return 0; } + constexpr int rank() { return 0; } }; template diff --git a/src/atlas/array/native/NativeArray.cc b/src/atlas/array/native/NativeArray.cc index 015f1f521..479bf80a6 100644 --- a/src/atlas/array/native/NativeArray.cc +++ b/src/atlas/array/native/NativeArray.cc @@ -11,7 +11,7 @@ #include #include "atlas/array.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/MakeView.h" #include "atlas/array/helpers/ArrayInitializer.h" #include "atlas/array/helpers/ArrayWriter.h" @@ -180,42 +180,18 @@ void ArrayT::resize(const ArrayShape& _shape) { Array* resized = Array::create(_shape); - switch (rank()) { - case 1: - array_initializer<1>::apply(*this, *resized); - break; - case 2: - array_initializer<2>::apply(*this, *resized); - break; - case 3: - array_initializer<3>::apply(*this, *resized); - break; - case 4: - array_initializer<4>::apply(*this, *resized); - break; - case 5: - array_initializer<5>::apply(*this, *resized); - break; - case 6: - array_initializer<6>::apply(*this, *resized); - break; - case 7: - array_initializer<7>::apply(*this, *resized); - break; - case 8: - array_initializer<8>::apply(*this, *resized); - break; - case 9: - array_initializer<9>::apply(*this, *resized); - break; - default: - ATLAS_NOTIMPLEMENTED; - } - + array_initializer::apply(*this,*resized); + replace(*resized); delete resized; } + +template +void ArrayT::copy(const Array& other, const CopyPolicy&) { + array_initializer::apply(other,*this); +} + template void ArrayT::insert(idx_t idx1, idx_t size1) { ArrayShape nshape = shape(); diff --git a/src/atlas/array/native/NativeArrayView.h b/src/atlas/array/native/NativeArrayView.h index b902cbb95..ac0324959 100644 --- a/src/atlas/array/native/NativeArrayView.h +++ b/src/atlas/array/native/NativeArrayView.h @@ -55,7 +55,7 @@ #include #include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayViewDefs.h" #include "atlas/array/LocalView.h" #include "atlas/array/Range.h" @@ -132,16 +132,11 @@ class ArrayView { public: // -- Constructors - ArrayView(const ArrayView& other): - data_(other.data_), size_(other.size_), shape_(other.shape_), strides_(other.strides_) {} + template >> + ArrayView(const ArrayView& other): data_(other.data()), size_(other.size()), shape_(other.shape_), strides_(other.strides_) {} - ENABLE_IF_CONST_WITH_NON_CONST(value_type) - ArrayView(const ArrayView& other): data_(other.data()), size_(other.size()) { - for (idx_t j = 0; j < Rank; ++j) { - shape_[j] = other.shape(j); - strides_[j] = other.stride(j); - } - } + template >> + ArrayView(ArrayView&& other):data_(other.data()), size_(other.size()), shape_(other.shape_), strides_(other.strides_) {} #ifndef DOXYGEN_SHOULD_SKIP_THIS // This constructor should not be used directly, but only through a array::make_view() function. @@ -158,19 +153,18 @@ class ArrayView { ENABLE_IF_CONST_WITH_NON_CONST(value_type) operator const ArrayView&() const { return *(const ArrayView*)(this); } - // -- Access methods /// @brief Multidimensional index operator: view(i,j,k,...) - template + template > value_type& operator()(Idx... idx) { check_bounds(idx...); return data_[index(idx...)]; } /// @brief Multidimensional index operator: view(i,j,k,...) - template - const value_type& operator()(Ints... idx) const { + template > + const value_type& operator()(Idx... idx) const { return data_[index(idx...)]; } @@ -178,8 +172,8 @@ class ArrayView { /// /// Note that this function is only present when Rank == 1 #ifndef DOXYGEN_SHOULD_SKIP_THIS - template - typename std::enable_if<(Rank == 1 && EnableBool), const value_type&>::type operator[](Int idx) const { + template > + const value_type& operator[](Idx idx) const { #else // Doxygen API is cleaner! template @@ -193,12 +187,12 @@ class ArrayView { /// /// Note that this function is only present when Rank == 1 #ifndef DOXYGEN_SHOULD_SKIP_THIS - template - typename std::enable_if<(Rank == 1 && EnableBool), value_type&>::type operator[](Int idx) { + template > + value_type& operator[](Idx idx) { #else // Doxygen API is cleaner! - template - value_type operator[](Int idx) { + template + value_type operator[](Idx idx) { #endif check_bounds(idx); return data_[idx * strides_[0]]; @@ -356,6 +350,8 @@ class ArrayView { // -- Private data + template friend class ArrayView; + value_type* data_; size_t size_; std::array shape_; diff --git a/src/atlas/array/native/NativeDataStore.h b/src/atlas/array/native/NativeDataStore.h index 73ef3b421..3e02cb19c 100644 --- a/src/atlas/array/native/NativeDataStore.h +++ b/src/atlas/array/native/NativeDataStore.h @@ -16,7 +16,7 @@ #include // std::numeric_limits::signaling_NaN #include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/library/Library.h" #include "atlas/library/config.h" #include "atlas/runtime/Exception.h" diff --git a/src/atlas/array/native/NativeIndexView.h b/src/atlas/array/native/NativeIndexView.h index 37b3b2cfe..e9d8a55cb 100644 --- a/src/atlas/array/native/NativeIndexView.h +++ b/src/atlas/array/native/NativeIndexView.h @@ -42,7 +42,7 @@ #include #include -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/library/config.h" //------------------------------------------------------------------------------------------------------ diff --git a/src/atlas/field/Field.cc b/src/atlas/field/Field.cc index fad1ec5db..befefbd0b 100644 --- a/src/atlas/field/Field.cc +++ b/src/atlas/field/Field.cc @@ -11,9 +11,15 @@ #include #include +#include "atlas/library/config.h" + #include "atlas/field/Field.h" #include "atlas/field/detail/FieldImpl.h" +#include "atlas/runtime/Exception.h" + +#if ATLAS_HAVE_FUNCTIONSPACE #include "atlas/functionspace/FunctionSpace.h" +#endif namespace atlas { @@ -57,6 +63,17 @@ array::Array& Field::array() { return get()->array(); } +/// @brief Clone +Field Field::clone(const eckit::Parametrisation& config) const { + Field tmp(get()->name(), get()->datatype(), get()->shape()); + tmp.metadata() = this->metadata(); + tmp.set_functionspace(this->functionspace()); + array::Array::CopyPolicy cp; + // To be set up via config. For now use default, as Array does not yet implement it. + tmp.array().copy(this->array(),cp); + return tmp; +} + // -- Accessors /// @brief Access to raw data @@ -166,11 +183,27 @@ idx_t Field::variables() const { return get()->variables(); } +void Field::set_horizontal_dimension(const std::vector& h_dim) { + get()->set_horizontal_dimension(h_dim); +} + +std::vector Field::horizontal_dimension() const { + return get()->horizontal_dimension(); +} + void Field::set_functionspace(const FunctionSpace& functionspace) { +#if ATLAS_HAVE_FUNCTIONSPACE get()->set_functionspace(functionspace); +#else + throw_Exception("Atlas has been compiled without FunctionSpace support",Here()); +#endif } const FunctionSpace& Field::functionspace() const { +#if ATLAS_HAVE_FUNCTIONSPACE return get()->functionspace(); +#else + throw_Exception("Atlas has been compiled without FunctionSpace support",Here()); +#endif } /// @brief Return the memory footprint of the Field diff --git a/src/atlas/field/Field.h b/src/atlas/field/Field.h index d179a1112..cde616ecb 100644 --- a/src/atlas/field/Field.h +++ b/src/atlas/field/Field.h @@ -16,10 +16,13 @@ #include #include +#include "eckit/config/Parametrisation.h" + #include "atlas/array/ArrayShape.h" #include "atlas/array/DataType.h" #include "atlas/array_fwd.h" #include "atlas/library/config.h" +#include "atlas/util/Config.h" #include "atlas/util/ObjectHandle.h" namespace eckit { @@ -32,6 +35,7 @@ class FieldImpl; } // namespace atlas namespace atlas { namespace util { +class Config; class Metadata; } } // namespace atlas @@ -83,6 +87,9 @@ class Field : DOXYGEN_HIDE(public util::ObjectHandle) { template Field(const std::string& name, DATATYPE* data, const array::ArrayShape&); + /// @brief Deep copy + Field clone(const eckit::Parametrisation& = util::Config()) const; + // -- Conversion /// @brief Implicit conversion to Array @@ -161,6 +168,9 @@ class Field : DOXYGEN_HIDE(public util::ObjectHandle) { void set_variables(idx_t n); idx_t variables() const; + void set_horizontal_dimension(const std::vector&); + std::vector horizontal_dimension() const; + void set_functionspace(const FunctionSpace& functionspace); const FunctionSpace& functionspace() const; diff --git a/src/atlas/field/FieldCreatorIFS.cc b/src/atlas/field/FieldCreatorIFS.cc index c0c9c3609..a15261c0c 100644 --- a/src/atlas/field/FieldCreatorIFS.cc +++ b/src/atlas/field/FieldCreatorIFS.cc @@ -15,7 +15,7 @@ #include "eckit/config/Parametrisation.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/DataType.h" #include "atlas/field/detail/FieldImpl.h" #include "atlas/grid/Grid.h" diff --git a/src/atlas/field/FieldSet.cc b/src/atlas/field/FieldSet.cc index 49a6ba3be..b1c4a461e 100644 --- a/src/atlas/field/FieldSet.cc +++ b/src/atlas/field/FieldSet.cc @@ -11,6 +11,7 @@ #include #include "atlas/field/Field.h" +#include "atlas/field/detail/FieldInterface.h" #include "atlas/field/FieldSet.h" #include "atlas/grid/Grid.h" #include "atlas/runtime/Exception.h" @@ -20,9 +21,35 @@ namespace field { //------------------------------------------------------------------------------------------------------ -FieldSetImpl::FieldSetImpl(const std::string& /*name*/): name_() {} +void FieldSetImpl::FieldObserver::onFieldRename(FieldImpl& field) { + std::string name = field.name(); + for (auto& kv: fieldset_.index_) { + const auto old_name = kv.first; + const auto idx = kv.second; + if (&field == fieldset_.fields_[idx].get()) { + if (name.empty()) { + std::stringstream ss; + ss << fieldset_.name_ << "[" << idx << "]"; + name = ss.str(); + } + fieldset_.index_.erase(old_name); + fieldset_.index_[name] = idx; + return; + } + } + throw_AssertionFailed("Should not be here",Here()); +} + + +FieldSetImpl::FieldSetImpl(const std::string& /*name*/): name_(), field_observer_(*this) {} +FieldSetImpl::~FieldSetImpl() { + clear(); +} void FieldSetImpl::clear() { + for( auto& field : fields_ ) { + field->detachObserver(field_observer_); + } index_.clear(); fields_.clear(); } @@ -37,6 +64,8 @@ Field FieldSetImpl::add(const Field& field) { index_[name.str()] = size(); } fields_.push_back(field); + + field.get()->attachObserver(field_observer_); return field; } @@ -86,6 +115,39 @@ std::vector FieldSetImpl::field_names() const { // C wrapper interfaces to C++ routines extern "C" { +void atlas__FieldSet__data_int_specf(FieldSetImpl* This, char* name, int*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_int_specf(This->field(name).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_long_specf(FieldSetImpl* This, char* name, long*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_long_specf(This->field(name).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_float_specf(FieldSetImpl* This, char* name, float*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_float_specf(This->field(name).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_double_specf(FieldSetImpl* This, char* name, double*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_double_specf(This->field(name).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_int_specf_by_idx(FieldSetImpl* This, int& idx, int*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_int_specf(This->operator[](idx).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_long_specf_by_idx(FieldSetImpl* This, int& idx, long*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_long_specf(This->operator[](idx).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_float_specf_by_idx(FieldSetImpl* This, int& idx, float*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_float_specf(This->operator[](idx).get(), data, rank, shapef, stridesf); +} + +void atlas__FieldSet__data_double_specf_by_idx(FieldSetImpl* This, int& idx, double*& data, int& rank, int*& shapef, int*& stridesf) { + atlas__Field__data_double_specf(This->operator[](idx).get(), data, rank, shapef, stridesf); +} + + FieldSetImpl* atlas__FieldSet__new(char* name) { FieldSetImpl* fset = new FieldSetImpl(std::string(name)); fset->name() = name; @@ -152,6 +214,23 @@ FieldSet::FieldSet(const Field& field): Handle(new Implementation()) { get()->add(field); } +const util::Metadata& FieldSet::metadata() const { + return get()->metadata(); +} + +util::Metadata& FieldSet::metadata() { + return get()->metadata(); +} + +FieldSet FieldSet::clone(const eckit::Parametrisation& config) const { + FieldSet fset; + for (idx_t jj = 0; jj < size(); ++jj) { + fset.add(field(jj).clone(config)); + } + fset.metadata() = metadata(); + return fset; +} + void FieldSet::set_dirty(bool value) const { get()->set_dirty(value); } diff --git a/src/atlas/field/FieldSet.h b/src/atlas/field/FieldSet.h index 46f804a1c..4c7a480de 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -24,11 +24,14 @@ #include "eckit/deprecated.h" +#include "atlas/array_fwd.h" #include "atlas/field/Field.h" #include "atlas/library/config.h" #include "atlas/runtime/Exception.h" +#include "atlas/util/Metadata.h" #include "atlas/util/Object.h" #include "atlas/util/ObjectHandle.h" +#include "atlas/field/detail/FieldImpl.h" namespace atlas { @@ -57,10 +60,24 @@ class FieldSetImpl : public util::Object { template using enable_if_index_t = enable_if_t()>; +private: + + class FieldObserver : public field::FieldObserver { + public: + FieldObserver(FieldSetImpl& fieldset) : fieldset_(fieldset) {} + + private: + void onFieldRename(FieldImpl& field) override; + + private: + FieldSetImpl& fieldset_; + }; + public: // methods /// Constructs an empty FieldSet FieldSetImpl(const std::string& name = "untitled"); + virtual ~FieldSetImpl(); idx_t size() const { return static_cast(fields_.size()); } bool empty() const { return !fields_.size(); } @@ -112,6 +129,9 @@ class FieldSetImpl : public util::Object { const_iterator cbegin() const { return fields_.begin(); } const_iterator cend() const { return fields_.end(); } + const util::Metadata& metadata() const { return metadata_; } + util::Metadata& metadata() { return metadata_; } + void haloExchange(bool on_device = false) const; void adjointHaloExchange(bool on_device = false) const; void set_dirty(bool = true) const; @@ -119,10 +139,12 @@ class FieldSetImpl : public util::Object { protected: // data std::vector fields_; ///< field storage std::string name_; ///< internal name + util::Metadata metadata_; ///< metadata associated with the FieldSet std::map index_; ///< name-to-index map, to refer fields by name -}; -class FieldImpl; + friend class FieldObserver; + FieldObserver field_observer_; +}; // C wrapper interfaces to C++ routines extern "C" { @@ -134,6 +156,22 @@ const char* atlas__FieldSet__name(FieldSetImpl* This); idx_t atlas__FieldSet__size(const FieldSetImpl* This); FieldImpl* atlas__FieldSet__field_by_name(FieldSetImpl* This, char* name); FieldImpl* atlas__FieldSet__field_by_idx(FieldSetImpl* This, idx_t idx); +void atlas__FieldSet__data_int_specf(FieldSetImpl* This, char* name, int*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_long_specf(FieldSetImpl* This, char* name, long*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_float_specf(FieldSetImpl* This, char* name, float*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_double_specf(FieldSetImpl* This, char* name, double*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_int_specf_by_idx(FieldSetImpl* This, int& idx, int*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_long_specf_by_idx(FieldSetImpl* This, int& idx, long*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_float_specf_by_idx(FieldSetImpl* This, int& idx, float*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); +void atlas__FieldSet__data_double_specf_by_idx(FieldSetImpl* This, int& idx, double*& field_data, int& rank, int*& field_shapef, + int*& field_stridesf); void atlas__FieldSet__set_dirty(FieldSetImpl* This, int value); void atlas__FieldSet__halo_exchange(FieldSetImpl* This, int on_device); } @@ -160,6 +198,8 @@ class FieldSet : DOXYGEN_HIDE(public util::ObjectHandle) { FieldSet(const std::string& name); FieldSet(const Field&); + FieldSet clone(const eckit::Parametrisation& config = util::Config()) const; + idx_t size() const { return get()->size(); } bool empty() const { return get()->empty(); } @@ -209,7 +249,12 @@ class FieldSet : DOXYGEN_HIDE(public util::ObjectHandle) { const_iterator cbegin() const { return get()->begin(); } const_iterator cend() const { return get()->end(); } + const util::Metadata& metadata() const; + util::Metadata& metadata(); + void haloExchange(bool on_device = false) const { get()->haloExchange(on_device); } + void adjointHaloExchange(bool on_device = false) const { get()->adjointHaloExchange(on_device); } + void set_dirty(bool = true) const; // Deprecated API diff --git a/src/atlas/field/detail/FieldImpl.cc b/src/atlas/field/detail/FieldImpl.cc index d4a56ad08..78d929f23 100644 --- a/src/atlas/field/detail/FieldImpl.cc +++ b/src/atlas/field/detail/FieldImpl.cc @@ -11,15 +11,18 @@ #include #include +#include "atlas/library/config.h" + #include "atlas/array/MakeView.h" #include "atlas/field/FieldCreator.h" #include "atlas/field/detail/FieldImpl.h" -#include "atlas/functionspace/FunctionSpace.h" #include "atlas/runtime/Exception.h" - - #include "atlas/runtime/Log.h" +#if ATLAS_HAVE_FUNCTIONSPACE +#include "atlas/functionspace/FunctionSpace.h" +#endif + namespace atlas { namespace field { @@ -53,8 +56,11 @@ FieldImpl* FieldImpl::create(const std::string& name, array::Array* array) { // ------------------------------------------------------------------------- -FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, const array::ArrayShape& shape): - functionspace_(new FunctionSpace()) { +FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, const array::ArrayShape& shape) +#if ATLAS_HAVE_FUNCTIONSPACE + :functionspace_(new FunctionSpace()) +#endif +{ array_ = array::Array::create(datatype, shape); array_->attach(); rename(name); @@ -62,8 +68,11 @@ FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, const ar set_variables(0); } -FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, array::ArraySpec&& spec): - functionspace_(new FunctionSpace()) { +FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, array::ArraySpec&& spec) +#if ATLAS_HAVE_FUNCTIONSPACE + :functionspace_(new FunctionSpace()) +#endif +{ array_ = array::Array::create(datatype, std::move(spec)); array_->attach(); rename(name); @@ -72,7 +81,11 @@ FieldImpl::FieldImpl(const std::string& name, array::DataType datatype, array::A } -FieldImpl::FieldImpl(const std::string& name, array::Array* array): functionspace_(new FunctionSpace()) { +FieldImpl::FieldImpl(const std::string& name, array::Array* array) +#if ATLAS_HAVE_FUNCTIONSPACE + :functionspace_(new FunctionSpace()) +#endif +{ array_ = array; array_->attach(); rename(name); @@ -88,12 +101,16 @@ FieldImpl::~FieldImpl() { } delete array_; } +#if ATLAS_HAVE_FUNCTIONSPACE delete functionspace_; +#endif } size_t FieldImpl::footprint() const { size_t size = sizeof(*this); +#if ATLAS_HAVE_FUNCTIONSPACE size += functionspace_->footprint(); +#endif size += array_->footprint(); size += metadata_.footprint(); size += name_.capacity() * sizeof(std::string::value_type); @@ -130,6 +147,13 @@ std::string vector_to_str(const std::vector& t) { } // namespace +void FieldImpl::rename(const std::string& name) { + metadata().set("name", name); + for (FieldObserver* observer : field_observers_) { + observer->onFieldRename(*this); + } +} + const std::string& FieldImpl::name() const { name_ = metadata().get("name"); return name_; @@ -164,29 +188,54 @@ void FieldImpl::insert(idx_t idx1, idx_t size1) { } void FieldImpl::set_functionspace(const FunctionSpace& functionspace) { +#if ATLAS_HAVE_FUNCTIONSPACE *functionspace_ = functionspace; +#else + throw_Exception("Atlas is compiled without FunctionSpace support", Here()); +#endif } const FunctionSpace& FieldImpl::functionspace() const { +#if ATLAS_HAVE_FUNCTIONSPACE return *functionspace_; +#else + throw_Exception("Atlas is compiled without FunctionSpace support", Here()); +#endif } void FieldImpl::haloExchange(bool on_device) const { if (dirty()) { +#if ATLAS_HAVE_FUNCTIONSPACE ATLAS_ASSERT(functionspace()); functionspace().haloExchange(Field(this), on_device); set_dirty(false); +#else + throw_Exception("Atlas is compiled without FunctionSpace support", Here()); +#endif } } void FieldImpl::adjointHaloExchange(bool on_device) const { - { - set_dirty(); +#if ATLAS_HAVE_FUNCTIONSPACE + set_dirty(); + ATLAS_ASSERT(functionspace()); + functionspace().adjointHaloExchange(Field(this), on_device); +#else + throw_Exception("Atlas is compiled without FunctionSpace support", Here()); +#endif +} - ATLAS_ASSERT(functionspace()); - functionspace().adjointHaloExchange(Field(this), on_device); +void FieldImpl::attachObserver(FieldObserver& observer) const { + if (std::find(field_observers_.begin(), field_observers_.end(), &observer) == field_observers_.end()) { + field_observers_.push_back(&observer); } } +void FieldImpl::detachObserver(FieldObserver& observer) const { + field_observers_.erase(std::remove(field_observers_.begin(), field_observers_.end(), &observer), + field_observers_.end()); +} + + // ------------------------------------------------------------------ } // namespace field diff --git a/src/atlas/field/detail/FieldImpl.h b/src/atlas/field/detail/FieldImpl.h index 645975b4a..91de0e17a 100644 --- a/src/atlas/field/detail/FieldImpl.h +++ b/src/atlas/field/detail/FieldImpl.h @@ -20,7 +20,7 @@ #include "atlas/util/Object.h" #include "atlas/array.h" -#include "atlas/array/ArrayUtil.h" +#include "atlas/array/ArrayDataStore.h" #include "atlas/array/DataType.h" #include "atlas/util/Metadata.h" @@ -37,6 +37,10 @@ namespace field { //---------------------------------------------------------------------------------------------------------------------- +class FieldObserver; // Definition below + +//---------------------------------------------------------------------------------------------------------------------- + class FieldImpl : public util::Object { public: // Static methods /// @brief Create field from parametrisation @@ -99,7 +103,7 @@ class FieldImpl : public util::Object { const std::string& name() const; /// @brief Rename this field - void rename(const std::string& name) { metadata().set("name", name); } + void rename(const std::string& name); /// @brief Access to metadata associated to this field const util::Metadata& metadata() const { return metadata_; } @@ -149,6 +153,13 @@ class FieldImpl : public util::Object { idx_t levels() const { return metadata().get("levels"); } idx_t variables() const { return metadata().get("variables"); } + void set_horizontal_dimension(const std::vector& h_dim) { metadata().set("horizontal_dimension", h_dim); } + std::vector horizontal_dimension() const { + std::vector h_dim{0}; + metadata().get("horizontal_dimension", h_dim); + return h_dim; + } + void set_functionspace(const FunctionSpace&); const FunctionSpace& functionspace() const; @@ -198,7 +209,8 @@ class FieldImpl : public util::Object { void haloExchange(bool on_device = false) const; void adjointHaloExchange(bool on_device = false) const; - + void attachObserver(FieldObserver&) const; + void detachObserver(FieldObserver&) const; void callbackOnDestruction(std::function&& f) { callback_on_destruction_.emplace_back(std::move(f)); } private: // methods @@ -209,11 +221,41 @@ class FieldImpl : public util::Object { util::Metadata metadata_; array::Array* array_; FunctionSpace* functionspace_; + mutable std::vector field_observers_; std::vector> callback_on_destruction_; }; //---------------------------------------------------------------------------------------------------------------------- +class FieldObserver { +private: + std::vector registered_fields_; + +public: + void registerField(const FieldImpl& field) { + if (std::find(registered_fields_.begin(), registered_fields_.end(), &field) == registered_fields_.end()) { + registered_fields_.push_back(&field); + field.attachObserver(*this); + } + } + void unregisterField(const FieldImpl& field) { + auto found = std::find(registered_fields_.begin(), registered_fields_.end(), &field); + if (found != registered_fields_.end()) { + registered_fields_.erase(found); + field.detachObserver(*this); + } + } + virtual ~FieldObserver() { + for (auto field : registered_fields_) { + field->detachObserver(*this); + } + } + + virtual void onFieldRename(FieldImpl&) = 0; +}; + +//---------------------------------------------------------------------------------------------------------------------- + template FieldImpl* FieldImpl::create(const std::string& name, const array::ArrayShape& shape) { return create(name, array::DataType::create(), shape); diff --git a/src/atlas/field/detail/FieldInterface.cc b/src/atlas/field/detail/FieldInterface.cc index e3a53b040..33435747d 100644 --- a/src/atlas/field/detail/FieldInterface.cc +++ b/src/atlas/field/detail/FieldInterface.cc @@ -10,11 +10,15 @@ #include +#include "atlas/library/config.h" #include "atlas/field/Field.h" #include "atlas/field/detail/FieldImpl.h" -#include "atlas/functionspace/FunctionSpace.h" +#include "atlas/field/detail/FieldInterface.h" #include "atlas/runtime/Exception.h" +#if ATLAS_HAVE_FUNCTIONSPACE +#include "atlas/functionspace/FunctionSpace.h" +#endif namespace atlas { namespace field { @@ -142,12 +146,20 @@ util::Metadata* atlas__Field__metadata(FieldImpl* This) { int atlas__Field__has_functionspace(FieldImpl* This) { ATLAS_ASSERT(This != nullptr, "Cannot access uninitialised atlas_Field"); +#if ATLAS_HAVE_FUNCTIONSPACE return (This->functionspace() != 0); +#else + return 0; +#endif } const functionspace::FunctionSpaceImpl* atlas__Field__functionspace(FieldImpl* This) { ATLAS_ASSERT(This != nullptr, "Cannot access uninitialised atlas_Field"); +#if ATLAS_HAVE_FUNCTIONSPACE return This->functionspace().get(); +#else + throw_Exception("Atlas is not compiled with FunctionSpace support", Here()); +#endif } void atlas__Field__shapef(FieldImpl* This, int*& shape, int& rank) { @@ -196,10 +208,15 @@ void atlas__Field__set_levels(FieldImpl* This, int levels) { This->set_levels(levels); } +# void atlas__Field__set_functionspace(FieldImpl* This, const functionspace::FunctionSpaceImpl* functionspace) { ATLAS_ASSERT(This != nullptr, "Cannot set functionspace in uninitialised atlas_Field"); ATLAS_ASSERT(functionspace != nullptr, "Cannot set uninitialised atlas_FunctionSpace in atlas_Field"); +#if ATLAS_HAVE_FUNCTIONSPACE This->set_functionspace(functionspace); +#else + throw_Exception("Atlas is not compiled with FunctionSpace support", Here()); +#endif } void atlas__Field__update_device(FieldImpl* This) { diff --git a/src/atlas/field/detail/for_each.h b/src/atlas/field/detail/for_each.h new file mode 100644 index 000000000..a72b55ada --- /dev/null +++ b/src/atlas/field/detail/for_each.h @@ -0,0 +1,249 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#pragma once + +#include +#include +#include + +#include "atlas/field/Field.h" +#include "atlas/array/helpers/ArrayForEach.h" +#include "atlas/runtime/Exception.h" + +namespace atlas { +namespace field { +namespace detail { + +template // primary template +struct function_traits : + public function_traits::type::operator())> { }; + +template +struct function_traits : + function_traits { }; + +template +struct function_traits : + function_traits { }; + +template +struct function_traits { + using result_type = ReturnType; + + template + using arg_t = typename std::tuple_element< + Index, + std::tuple + >::type; + + static constexpr std::size_t arity = sizeof...(Arguments); +}; + +template +using first_argument = typename function_traits::template arg_t<0>; + +template +constexpr bool valid_value_function() { + return std::is_same_v>,std::decay_t>; +} + +template +using function_argument_data_type = typename std::decay_t>::value_type>; + +template +constexpr idx_t function_argument_rank() { + return std::decay_t>::rank(); +} + +template +constexpr bool valid_column_function() { + using value_type = std::decay_t; + return function_argument_rank() == Rank && std::is_same_v,value_type>; +} + +template +void for_each_value_masked_view(std::index_sequence, const Config& config, const Mask& mask, const Function& function, std::tuple&& views ) { + if constexpr (std::is_invocable_r_v) { + array::helpers::ArrayForEach::apply(config,std::move(views),mask,function); + } + else { + ATLAS_THROW_EXCEPTION("Invalid mask function passed"); + } +} + +template +auto make_view_tuple(std::tuple&& fields) { + constexpr auto num_fields = std::tuple_size_v>; + if constexpr (FieldIdx < num_fields) { + return std::tuple_cat(std::make_tuple( + array::make_view(std::get(fields))), + make_view_tuple(std::move(fields))); + } else { + return std::make_tuple(); + } +} + +template +void for_each_value_masked_rank(const Config& config, const Mask& mask, std::tuple&& fields, const Function& function ) { + constexpr auto dims = std::make_index_sequence(); + if constexpr (valid_value_function()) { + return for_each_value_masked_view(dims, config, mask, function, make_view_tuple<0, Value, Rank>(std::move(fields))); + } +} + +template +void for_each_column_masked_view(const Config& config, const Mask& mask, const std::vector& h_dim, const Function& function, std::tuple&& views ) { + using View = std::decay_t>>; + using value_type = typename View::value_type; + constexpr int rank = View::rank(); + + if (h_dim.size()>=rank) { + ATLAS_THROW_EXCEPTION("Cannot extract column for Rank="<= rank) { + ATLAS_THROW_EXCEPTION("Invalid horizontal_dimension " << h_dim[0] << " must less rank " << rank); + } + } + + auto has_duplicates = [](const auto &v) { + for (const auto& i : v) { + for (const auto & j : v) { + if (&i == &j) break; + if (i == j) return true; + } + } + return false; + }; + if (has_duplicates(h_dim)) { + ATLAS_THROW_EXCEPTION("horizontal_dimension contains duplicates"); + } + + if constexpr (rank==1) { + ATLAS_THROW_EXCEPTION("Cannot use for_each_column with Rank=1 fields"); + } + if constexpr (rank==2) { + if constexpr (valid_column_function() && + std::is_invocable_r_v) { + switch (h_dim[0]) { + case 0: array::helpers::ArrayForEach<0>::apply(config,std::move(views),mask,function); return; + case 1: array::helpers::ArrayForEach<1>::apply(config,std::move(views),mask,function); return; + default: break; + } + ATLAS_THROW_EXCEPTION("Not implemented for horizontal_dimension = " << h_dim); + } + ATLAS_THROW_EXCEPTION("Invalid function passed"); + } + if constexpr (rank==3) { + if (h_dim.size() == 1) { + if constexpr (valid_column_function() && + std::is_invocable_r_v) { + switch (h_dim[0]) { + case 0: array::helpers::ArrayForEach<0>::apply(config,std::move(views),mask,function); return; + case 1: array::helpers::ArrayForEach<1>::apply(config,std::move(views),mask,function); return; + case 2: array::helpers::ArrayForEach<2>::apply(config,std::move(views),mask,function); return; + default: break; + } + ATLAS_THROW_EXCEPTION("Not implemented for horizontal_dimension = " << h_dim); + } + ATLAS_THROW_EXCEPTION("Invalid function passed"); + } + else if (h_dim.size() == 2) { + if constexpr (valid_column_function() && + std::is_invocable_r_v) { + switch (h_dim[0]) { + case 0: { + switch (h_dim[1]) { + case 1: array::helpers::ArrayForEach<0,1>::apply(config,std::move(views),mask,function); return; + case 2: array::helpers::ArrayForEach<0,2>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + case 1: { + switch (h_dim[1]) { + case 2: array::helpers::ArrayForEach<1,2>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + } + ATLAS_THROW_EXCEPTION("Not implemented for horizontal_dimension = " << h_dim); + } + ATLAS_THROW_EXCEPTION("Invalid function passed"); + } + ATLAS_THROW_EXCEPTION("Not implemented for h_dim = " << h_dim); + } + if constexpr (rank==4) { + if (h_dim.size() == 1) { + if constexpr (valid_column_function() && + std::is_invocable_r_v) { + switch (h_dim[0]) { + case 0: array::helpers::ArrayForEach<0>::apply(config,std::move(views),mask,function); return; + case 1: array::helpers::ArrayForEach<1>::apply(config,std::move(views),mask,function); return; + case 2: array::helpers::ArrayForEach<2>::apply(config,std::move(views),mask,function); return; + case 3: array::helpers::ArrayForEach<3>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + } + else if (h_dim.size() == 2) { + if constexpr (valid_column_function() && + std::is_invocable_r_v) { + switch (h_dim[0]) { + case 0: { + switch (h_dim[1]) { + case 1: array::helpers::ArrayForEach<0,1>::apply(config,std::move(views),mask,function); return; + case 2: array::helpers::ArrayForEach<0,2>::apply(config,std::move(views),mask,function); return; + case 3: array::helpers::ArrayForEach<0,3>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + case 1: { + switch (h_dim[1]) { + case 2: array::helpers::ArrayForEach<1,2>::apply(config,std::move(views),mask,function); return; + case 3: array::helpers::ArrayForEach<1,3>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + case 2: { + switch (h_dim[1]) { + case 3: array::helpers::ArrayForEach<2,3>::apply(config,std::move(views),mask,function); return; + default: break; + } + } + default: break; + } + ATLAS_THROW_EXCEPTION("Not implemented for horizontal_dimension = " << h_dim); + } + ATLAS_THROW_EXCEPTION("Invalid function passed"); + } + ATLAS_THROW_EXCEPTION("Not implemented for h_dim = " << h_dim); + } + ATLAS_THROW_EXCEPTION("Not implemented for rank="< +void for_each_column_masked_rank(const eckit::Parametrisation& config, const Mask& mask, std::tuple&& fields, const Function& function) { + + constexpr auto num_fields = std::tuple_size_v>; + ATLAS_ASSERT(num_fields == detail::function_traits::arity); + + using value_type = detail::function_argument_data_type; + ATLAS_ASSERT( std::get<0>(fields).datatype() == array::make_datatype() ); + + auto h_dim = std::get<0>(fields).horizontal_dimension(); + ATLAS_ASSERT(Rank == detail::function_argument_rank() + h_dim.size()); + + return for_each_column_masked_view(config, mask, h_dim, function, make_view_tuple<0, value_type, Rank>(std::move(fields))); +} + +} // namespace detail +} // namespace field +} // namespace atlas diff --git a/src/atlas/field/for_each.h b/src/atlas/field/for_each.h new file mode 100644 index 000000000..f54334a6c --- /dev/null +++ b/src/atlas/field/for_each.h @@ -0,0 +1,460 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#pragma once + +#include +#include +#include + +#include "atlas/field/detail/for_each.h" + +#include "atlas/array/Array.h" + +#include "Field.h" +#include "FieldSet.h" +#include "atlas/array/helpers/ArrayForEach.h" +#include "atlas/runtime/Exception.h" + +namespace atlas { +namespace field { + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value_masked( const eckit::Parametrisation& , const Mask& , std::tuple&& , const Function& ) + +template +void for_each_value_masked(const eckit::Parametrisation& config, const Mask& mask, std::tuple&& fields, const Function& function) { + auto field_1 = std::get<0>(fields); + if constexpr (std::is_same_v,atlas::Field>) { + auto h_dim = field_1.horizontal_dimension(); + + ATLAS_ASSERT( mask.datatype() == array::make_datatype() ); + ATLAS_ASSERT( mask.rank() <= h_dim.size() ); + + if (h_dim.size() == 1) { + ATLAS_ASSERT(h_dim[0] == 0); + auto mask_view = array::make_view(mask); + auto mask_wrap = [mask_view](idx_t i, auto&&... args) { return mask_view(i); }; + return for_each_value_masked(config, mask_wrap, std::move(fields), function); + } + else if (h_dim.size() == 2) { + if (mask.rank() == 1) { + auto mask_view_1d = array::make_view(mask); + auto mask_view_shape2d = array::make_shape(field_1.shape(h_dim[0]), field_1.shape(h_dim[1])); + auto mask_view = array::View( mask_view_1d.data(), mask_view_shape2d ); + if( h_dim[0] == 0 && h_dim[1] == 2) { + auto mask_wrap = [mask_view](idx_t i, idx_t /*dummy*/, idx_t j, auto&&... args) { return mask_view(i,j); }; + return for_each_value_masked(config, mask_wrap, std::move(fields), function); + } + else { + ATLAS_ASSERT(h_dim[0] == 0 && h_dim[1] == 1); + auto mask_wrap = [mask_view](idx_t i, idx_t j, auto&&... args) { return mask_view(i,j); }; + return for_each_value_masked(config, mask_wrap, std::move(fields), function); + } + } + else { + auto mask_view = array::make_view(mask); + if( h_dim[0] == 0 && h_dim[1] == 2) { + auto mask_wrap = [mask_view](idx_t i, idx_t /*dummy*/, idx_t j, auto&&... args) { return mask_view(i,j); }; + return for_each_value_masked(config, mask_wrap, std::move(fields), function); + } + else { + ATLAS_ASSERT(h_dim[0] == 0 && h_dim[1] == 1); + auto mask_wrap = [mask_view](idx_t i, idx_t j, auto&&... args) { return mask_view(i,j); }; + return for_each_value_masked(config, mask_wrap, std::move(fields), function); + } + } + } + else { + ATLAS_THROW_EXCEPTION("More than 2 horizontal indices is not yet supported"); + } + } + else { + constexpr auto num_fields = std::tuple_size_v>; + static_assert(num_fields == detail::function_traits::arity,"!"); + using value_type = std::decay_t>; + switch (field_1.rank()) { + case 1: return detail::for_each_value_masked_rank(config,mask,std::move(fields),function); + case 2: return detail::for_each_value_masked_rank(config,mask,std::move(fields),function); + case 3: return detail::for_each_value_masked_rank(config,mask,std::move(fields),function); + case 4: return detail::for_each_value_masked_rank(config,mask,std::move(fields),function); + case 5: return detail::for_each_value_masked_rank(config,mask,std::move(fields),function); + default: ATLAS_THROW_EXCEPTION("Only fields with rank <= 5 are currently supported. Given rank: " << std::get<0>(fields).rank()); + } + } + ATLAS_THROW_EXCEPTION("Invalid function"); +} + +template +void for_each_value_masked(const eckit::Parametrisation& config, const Mask& mask, Field field, const Function& function) { + return for_each_value_masked(config, mask, std::make_tuple(field), function); +} + +template +void for_each_value_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_value_masked(config, mask, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_value_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value_masked(config, mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_value_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value_masked(config, mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value_masked( const ExecutionPolicy&& , const Mask& , std::tuple&& , const Function& ) + +template ()>> +void for_each_value_masked(ExecutionPolicy, const Mask& mask, std::tuple&& fields, const Function& function) { + return for_each_value_masked(option::execution_policy(), mask, std::move(fields), function); +} + +template ()>> +void for_each_value_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field, const Function& function) { + return for_each_value_masked(execution_policy, mask, std::make_tuple(field), function); +} + +template ()>> +void for_each_value_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_value_masked(execution_policy, mask, std::make_tuple(field_1, field_2), function); +} + +template ()>> +void for_each_value_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value_masked(execution_policy, mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template ()>> +void for_each_value_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value_masked(execution_policy, mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value_masked( const Mask& , std::tuple&& , const Function& ) + +template +void for_each_value_masked(const Mask& mask, std::tuple&& fields, const Function& function) { + return for_each_value_masked(util::NoConfig(), mask, std::move(fields), function); +} + +template +void for_each_value_masked(const Mask& mask, Field field, const Function& function) { + return for_each_value_masked(mask, std::make_tuple(field), function); +} + +template +void for_each_value_masked(const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_value_masked(mask, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_value_masked(const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value_masked(mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_value_masked(const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value_masked(mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value( const eckit::Parametrisation& , std::tuple&& , const Function& ) + +template +void for_each_value(const eckit::Parametrisation& config, std::tuple&& fields, const Function& function) { + return for_each_value_masked(config, array::helpers::detail::no_mask, std::move(fields), function); +} + +template +void for_each_value(const eckit::Parametrisation& config, Field field, const Function& function) { + return for_each_value_masked(config, std::make_tuple(field), function); +} + +template +void for_each_value(const eckit::Parametrisation& config, Field field_1, Field field_2, const Function& function) { + return for_each_value_masked(config, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_value(const eckit::Parametrisation& config, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value_masked(config, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_value(const eckit::Parametrisation& config, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value_masked(config, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value( const ExecutionPolicy&& , std::tuple&& , const Function& ) + +template ()>> +void for_each_value(ExecutionPolicy, std::tuple&& fields, const Function& function) { + return for_each_value_masked(option::execution_policy(), array::helpers::detail::no_mask, std::move(fields), function); +} + +template ()>> +void for_each_value(ExecutionPolicy execution_policy, Field field, const Function& function) { + return for_each_value(execution_policy, std::make_tuple(field), function); +} + +template ()>> +void for_each_value(ExecutionPolicy execution_policy, Field field_1, Field field_2, const Function& function) { + return for_each_value(execution_policy, std::make_tuple(field_1, field_2), function); +} + +template ()>> +void for_each_value(ExecutionPolicy execution_policy, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value(execution_policy, std::make_tuple(field_1, field_2, field_3), function); +} + +template ()>> +void for_each_value(ExecutionPolicy execution_policy, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value(execution_policy, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_value( std::tuple&& , const Function& ) + +template +void for_each_value(std::tuple&& fields, const Function& function) { + return for_each_value_masked(util::NoConfig(), array::helpers::detail::no_mask, std::move(fields), function); +} + +template +void for_each_value(Field field, const Function& function) { + return for_each_value(std::make_tuple(field), function); +} + +template +void for_each_value(Field field_1, Field field_2, const Function& function) { + return for_each_value(std::make_tuple(field_1, field_2), function); +} + +template +void for_each_value(Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_value(std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_value(Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_value(std::make_tuple(field_1, field_2, field_3, field_4), function); +} + + + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column_masked( const eckit::Parametrisation& , const Mask& , std::tuple&& , const Function& ) + +template +void for_each_column_masked(const eckit::Parametrisation& config, const Mask& mask, std::tuple&& fields, const Function& function) { + if constexpr (std::is_same_v,atlas::Field>) { + auto field_1 = std::get<0>(fields); + auto h_dim = field_1.horizontal_dimension(); + + ATLAS_ASSERT( mask.datatype() == array::make_datatype() ); + ATLAS_ASSERT( mask.rank() <= h_dim.size() ); + + if (h_dim.size() == 1) { + return for_each_column_masked(config, array::make_view(mask), std::move(fields), function); + } + else if (h_dim.size() == 2) { + if (mask.rank() == 1) { + auto mask_view_1d = array::make_view(mask); + auto mask_view_shape2d = array::make_shape(field_1.shape(h_dim[0]), field_1.shape(h_dim[1])); + auto mask_view = array::View( mask_view_1d.data(), mask_view_shape2d ); + return for_each_column_masked(config, mask_view, std::move(fields), function); + } + else { + return for_each_column_masked(config, array::make_view(mask), std::move(fields), function); + } + } + else { + ATLAS_THROW_EXCEPTION("More than 2 horizontal indices is not yet supported"); + } + } + else { + switch (std::get<0>(fields).rank()) { + case 2: return detail::for_each_column_masked_rank<2>(config, mask, std::move(fields), function); + case 3: return detail::for_each_column_masked_rank<3>(config, mask, std::move(fields), function); + case 4: return detail::for_each_column_masked_rank<4>(config, mask, std::move(fields), function); + } + } + ATLAS_THROW_EXCEPTION("Invalid function"); +} + +template +void for_each_column_masked(const eckit::Parametrisation& config, const Mask& mask, Field field, const Function& function) { + return for_each_column_masked(config, mask, std::make_tuple(field), function); +} + +template +void for_each_column_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_column_masked(config, mask, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_column_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column_masked(config, mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_column_masked(const eckit::Parametrisation& config, const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column_masked(config, mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column_masked( const ExecutionPolicy&& , const Mask& , std::tuple&& , const Function& ) + +template ()>> +void for_each_column_masked(ExecutionPolicy, const Mask& mask, std::tuple&& fields, const Function& function) { + return for_each_column_masked(option::execution_policy(), mask, std::move(fields), function); +} + +template ()>> +void for_each_column_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field, const Function& function) { + return for_each_column_masked(execution_policy, mask, std::make_tuple(field), function); +} + +template ()>> +void for_each_column_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_column_masked(execution_policy, mask, std::make_tuple(field_1, field_2), function); +} + +template ()>> +void for_each_column_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column_masked(execution_policy, mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template ()>> +void for_each_column_masked(ExecutionPolicy execution_policy, const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column_masked(execution_policy, mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column_masked( const Mask& , std::tuple&& , const Function& ) + +template +void for_each_column_masked(const Mask& mask, std::tuple&& fields, const Function& function) { + return for_each_column_masked(util::NoConfig(), mask, std::move(fields), function); +} + +template +void for_each_column_masked(const Mask& mask, Field field, const Function& function) { + return for_each_column_masked(mask, std::make_tuple(field), function); +} + +template +void for_each_column_masked(const Mask& mask, Field field_1, Field field_2, const Function& function) { + return for_each_column_masked(mask, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_column_masked(const Mask& mask, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column_masked(mask, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_column_masked(const Mask& mask, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column_masked(mask, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column( const eckit::Parametrisation& , std::tuple&& , const Function& ) + +template +void for_each_column(const eckit::Parametrisation& config, std::tuple&& fields, const Function& function) { + return for_each_column_masked(config, array::helpers::detail::no_mask, std::move(fields), function); +} + +template +void for_each_column(const eckit::Parametrisation& config, Field field, const Function& function) { + return for_each_column_masked(config, std::make_tuple(field), function); +} + +template +void for_each_column(const eckit::Parametrisation& config, Field field_1, Field field_2, const Function& function) { + return for_each_column_masked(config, std::make_tuple(field_1, field_2), function); +} + +template +void for_each_column(const eckit::Parametrisation& config, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column_masked(config, std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_column(const eckit::Parametrisation& config, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column_masked(config, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column( const ExecutionPolicy&& , std::tuple&& , const Function& ) + +template ()>> +void for_each_column(ExecutionPolicy, std::tuple&& fields, const Function& function) { + return for_each_column_masked(option::execution_policy(), array::helpers::detail::no_mask, std::move(fields), function); +} + +template ()>> +void for_each_column(ExecutionPolicy execution_policy, Field field, const Function& function) { + return for_each_column(execution_policy, std::make_tuple(field), function); +} + +template ()>> +void for_each_column(ExecutionPolicy execution_policy, Field field_1, Field field_2, const Function& function) { + return for_each_column(execution_policy, std::make_tuple(field_1, field_2), function); +} + +template ()>> +void for_each_column(ExecutionPolicy execution_policy, Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column(execution_policy, std::make_tuple(field_1, field_2, field_3), function); +} + +template ()>> +void for_each_column(ExecutionPolicy execution_policy, Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column(execution_policy, std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ +// void for_each_column( std::tuple&& , const Function& ) + +template +void for_each_column(std::tuple&& fields, const Function& function) { + return for_each_column_masked(util::NoConfig(), array::helpers::detail::no_mask, std::move(fields), function); +} + +template +void for_each_column(Field field, const Function& function) { + return for_each_column(std::make_tuple(field), function); +} + +template +void for_each_column(Field field_1, Field field_2, const Function& function) { + return for_each_column(std::make_tuple(field_1, field_2), function); +} + +template +void for_each_column(Field field_1, Field field_2, Field field_3, const Function& function) { + return for_each_column(std::make_tuple(field_1, field_2, field_3), function); +} + +template +void for_each_column(Field field_1, Field field_2, Field field_3, Field field_4, const Function& function) { + return for_each_column(std::make_tuple(field_1, field_2, field_3, field_4), function); +} + +//------------------------------------------------------------------------------------------------------------------------------------------------ + +} // namespace field +} // namespace atlas diff --git a/src/atlas/functionspace/BlockStructuredColumns.h b/src/atlas/functionspace/BlockStructuredColumns.h index f4a2f3c93..6f30ae314 100644 --- a/src/atlas/functionspace/BlockStructuredColumns.h +++ b/src/atlas/functionspace/BlockStructuredColumns.h @@ -42,8 +42,6 @@ class BlockStructuredColumns : public FunctionSpace { bool valid() const { return functionspace_; } idx_t size() const { return functionspace_->size(); } -// idx_t sizeOwned() const { return functionspace_->sizeOwned(); } -// idx_t sizeHalo() const { return functionspace_->sizeHalo(); } idx_t levels() const { return functionspace_->levels(); } const Vertical& vertical() const { return functionspace_->vertical(); } @@ -54,10 +52,6 @@ class BlockStructuredColumns : public FunctionSpace { std::string checksum(const Field&) const; idx_t index(idx_t blk, idx_t rof) const { return functionspace_->index(blk, rof); } -// idx_t i_begin(idx_t j) const { return functionspace_->i_begin(j); } -// idx_t i_end(idx_t j) const { return functionspace_->i_end(j); } -// idx_t j_begin() const { return functionspace_->j_begin(); } -// idx_t j_end() const { return functionspace_->j_end(); } idx_t k_begin() const { return functionspace_->k_begin(); } idx_t k_end() const { return functionspace_->k_end(); } idx_t nproma() const { return functionspace_->nproma(); } diff --git a/src/atlas/functionspace/PointCloud.cc b/src/atlas/functionspace/PointCloud.cc index 6fd4fafef..1e6da5a58 100644 --- a/src/atlas/functionspace/PointCloud.cc +++ b/src/atlas/functionspace/PointCloud.cc @@ -1,5 +1,6 @@ /* - * (C) Copyright 2013 ECMWF. + * (C) Copyright 2013 ECMWF + * (C) Crown Copyright 2023 Met Office * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. @@ -9,6 +10,9 @@ */ +#include +#include + #include "atlas/functionspace/PointCloud.h" #include "atlas/array.h" #include "atlas/field/Field.h" @@ -21,6 +25,10 @@ #include "atlas/runtime/Exception.h" #include "atlas/util/CoordinateEnums.h" #include "atlas/util/Metadata.h" +#include "atlas/util/Point.h" +#include "atlas/util/Unique.h" + +#include "eckit/mpi/Comm.h" #if ATLAS_HAVE_FORTRAN #define REMOTE_IDX_BASE 1 @@ -58,18 +66,13 @@ PointCloud::PointCloud(const std::vector& points) { PointCloud::PointCloud(const Field& lonlat): lonlat_(lonlat) {} -PointCloud::PointCloud(const Field& lonlat, const Field& ghost): lonlat_(lonlat), ghost_(ghost) {} +PointCloud::PointCloud(const Field& lonlat, const Field& ghost): lonlat_(lonlat), ghost_(ghost) { + setupHaloExchange(); +} PointCloud::PointCloud(const FieldSet & flds): lonlat_(flds["lonlat"]), - ghost_(flds["ghost"]), remote_index_(flds["remote_index"]), partition_(flds["partition"]) -{ - ATLAS_ASSERT(ghost_.size() == remote_index_.size()); - ATLAS_ASSERT(ghost_.size() == partition_.size()); - halo_exchange_.reset(new parallel::HaloExchange()); - halo_exchange_->setup(array::make_view( partition_).data(), - array::make_view(remote_index_).data(), - REMOTE_IDX_BASE, - ghost_.size()); + ghost_(flds["ghost"]), remote_index_(flds["remote_index"]), partition_(flds["partition"]) { + setupHaloExchange(); } PointCloud::PointCloud(const Grid& grid) { @@ -136,12 +139,10 @@ std::string PointCloud::config_name(const eckit::Configuration& config) const { return name; } - const parallel::HaloExchange& PointCloud::halo_exchange() const { return *halo_exchange_; } - void PointCloud::set_field_metadata(const eckit::Configuration& config, Field& field) const { field.set_functionspace(this); @@ -168,7 +169,6 @@ void PointCloud::set_field_metadata(const eckit::Configuration& config, Field& f } } - Field PointCloud::createField(const eckit::Configuration& options) const { Field field(config_name(options), config_datatype(options), config_spec(options)); set_field_metadata(options, field); @@ -303,6 +303,135 @@ void PointCloud::haloExchange(const Field& field, bool on_device) const { haloExchange(fieldset, on_device); } +void PointCloud::setupHaloExchange(){ + const eckit::mpi::Comm& comm = atlas::mpi::comm(); + std::size_t mpi_rank = comm.rank(); + const std::size_t mpi_size = comm.size(); + + if (not partition_ and not remote_index_) { + + auto lonlat_v = array::make_view(lonlat_); + // data structure containing a flag to identify the 'ghost points'; + // 0={is not a ghost point}, 1={is a ghost point} + auto is_ghost = array::make_view(ghost_); + + std::vector opoints_local; + std::vector gpoints_local; + std::vector lonlat_u; + std::vector opoints_local_u; + + for (idx_t i = 0; i < lonlat_v.shape(0); ++i){ + lonlat_u.emplace_back(util::unique_lonlat(lonlat_v(i, XX), lonlat_v(i, YY))); + } + + idx_t j {0}; + for (idx_t i = 0; i < is_ghost.shape(0); ++i) { + PointXY loc(lonlat_v(j, XX), lonlat_v(j, YY)); + if (is_ghost(i)) { + gpoints_local.emplace_back(loc); + } else { + opoints_local.emplace_back(loc); + opoints_local_u.emplace_back(util::unique_lonlat(loc.x(), loc.y())); + } + ++j; + } + + std::vector coords_gp_local; + coords_gp_local.reserve(gpoints_local.size() * 2); + + for (auto& gp : gpoints_local) { + coords_gp_local.push_back(gp[XX]); + coords_gp_local.push_back(gp[YY]); + } + + eckit::mpi::Buffer buffers_rec(mpi_size); + + comm.allGatherv(coords_gp_local.begin(), coords_gp_local.end(), buffers_rec); + + std::vector gpoints_global; + + for (std::size_t pe = 0; pe < mpi_size; ++pe) { + for (std::size_t j = 0; j < buffers_rec.counts[pe]/2; ++j) { + PointXY loc_gp(*(buffers_rec.begin() + buffers_rec.displs[pe] + 2 * j + XX), + *(buffers_rec.begin() + buffers_rec.displs[pe] + 2 * j + YY)); + gpoints_global.emplace_back(loc_gp); + } + } + + std::vector gpoints_global_u; + for (atlas::PointXY& loc : gpoints_global) { + gpoints_global_u.emplace_back(util::unique_lonlat(loc.x(), loc.y())); + } + + std::vector partition_ids_gp_global(gpoints_global.size(), -1); + std::vector remote_index_gp_global(gpoints_global.size(), -1); + + std::vector::iterator iter_xy_gp_01; + + for (std::size_t idx = 0; idx < gpoints_global_u.size(); ++idx) { + iter_xy_gp_01 = std::find(opoints_local_u.begin(), + opoints_local_u.end(), gpoints_global_u.at(idx)); + if (iter_xy_gp_01 != opoints_local_u.end()) { + std::size_t ridx = std::distance(opoints_local_u.begin(), iter_xy_gp_01); + partition_ids_gp_global.at(idx) = mpi_rank; + remote_index_gp_global.at(idx) = ridx; + } + } + + comm.allReduceInPlace(partition_ids_gp_global.begin(), + partition_ids_gp_global.end(), eckit::mpi::max()); + comm.allReduceInPlace(remote_index_gp_global.begin(), + remote_index_gp_global.end(), eckit::mpi::max()); + + std::vector partition_ids_local(lonlat_v.shape(0), -1); + std::vector remote_index_local(lonlat_v.shape(0), -1); + + idx_t idx_loc {0}; + std::vector::iterator iter_xy_gp_02; + + for (idx_t i = 0; i < lonlat_v.shape(0); ++i){ + iter_xy_gp_02 = std::find(gpoints_global_u.begin(), + gpoints_global_u.end(), lonlat_u.at(i)); + if (iter_xy_gp_02 != gpoints_global_u.end()) { + std::size_t idx_gp = std::distance(gpoints_global_u.begin(), iter_xy_gp_02); + partition_ids_local[idx_loc] = partition_ids_gp_global[idx_gp]; + remote_index_local[idx_loc] = remote_index_gp_global[idx_gp]; + } else { + partition_ids_local[idx_loc] = mpi_rank; + remote_index_local[idx_loc] = idx_loc; + } + ++idx_loc; + } + + partition_ = Field("partition", array::make_datatype(), + array::make_shape(partition_ids_local.size())); + + auto partitionv = array::make_view(partition_); + for (idx_t i = 0; i < partitionv.shape(0); ++i) { + partitionv(i) = partition_ids_local.at(i); + } + + remote_index_ = Field("remote_index", array::make_datatype(), + array::make_shape(remote_index_local.size())); + + auto remote_indexv = array::make_indexview(remote_index_); + for (idx_t i = 0; i < remote_indexv.shape(0); ++i) { + remote_indexv(i) = remote_index_local.at(i); + } + + } + + ATLAS_ASSERT(ghost_.size() == remote_index_.size()); + ATLAS_ASSERT(ghost_.size() == partition_.size()); + + halo_exchange_.reset(new parallel::HaloExchange()); + halo_exchange_->setup(array::make_view(partition_).data(), + array::make_view(remote_index_).data(), + REMOTE_IDX_BASE, + ghost_.size()); + +} + void PointCloud::adjointHaloExchange(const FieldSet& fieldset, bool on_device) const { if (halo_exchange_) { for (idx_t f = 0; f < fieldset.size(); ++f) { @@ -339,11 +468,14 @@ void PointCloud::adjointHaloExchange(const Field& field, bool) const { PointCloud::PointCloud(const FunctionSpace& functionspace): FunctionSpace(functionspace), functionspace_(dynamic_cast(get())) {} -PointCloud::PointCloud(const Field& points): - FunctionSpace(new detail::PointCloud(points)), functionspace_(dynamic_cast(get())) {} +PointCloud::PointCloud(const Field& field): + FunctionSpace(new detail::PointCloud(field)), functionspace_(dynamic_cast(get())) {} -PointCloud::PointCloud(const FieldSet& points): - FunctionSpace(new detail::PointCloud(points)), functionspace_(dynamic_cast(get())) {} +PointCloud::PointCloud(const Field& field1, const Field& field2): + FunctionSpace(new detail::PointCloud(field1, field2)), functionspace_(dynamic_cast(get())) {} + +PointCloud::PointCloud(const FieldSet& fset): + FunctionSpace(new detail::PointCloud(fset)), functionspace_(dynamic_cast(get())) {} PointCloud::PointCloud(const std::vector& points): FunctionSpace(new detail::PointCloud(points)), functionspace_(dynamic_cast(get())) {} diff --git a/src/atlas/functionspace/PointCloud.h b/src/atlas/functionspace/PointCloud.h index 03258f4bc..376388cac 100644 --- a/src/atlas/functionspace/PointCloud.h +++ b/src/atlas/functionspace/PointCloud.h @@ -1,5 +1,6 @@ /* - * (C) Copyright 2013 ECMWF. + * (C) Copyright 2013 ECMWF + * (C) Crown Copyright 2023 Met Office * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. @@ -8,6 +9,7 @@ * nor does it submit to any jurisdiction. */ + #pragma once #include @@ -43,9 +45,7 @@ class PointCloud : public functionspace::FunctionSpaceImpl { PointCloud(const std::vector&); PointCloud(const Field& lonlat); PointCloud(const Field& lonlat, const Field& ghost); - - PointCloud(const FieldSet&); // assuming lonlat ghost ridx and partition present. - + PointCloud(const FieldSet&); // assuming lonlat ghost ridx and partition present PointCloud(const Grid&); ~PointCloud() override {} std::string type() const override { return "PointCloud"; } @@ -55,6 +55,7 @@ class PointCloud : public functionspace::FunctionSpaceImpl { Field lonlat() const override { return lonlat_; } const Field& vertical() const { return vertical_; } Field ghost() const override; + Field remote_index() const override { return remote_index_; } virtual idx_t size() const override { return lonlat_.shape(0); } using FunctionSpaceImpl::createField; @@ -148,7 +149,6 @@ class PointCloud : public functionspace::FunctionSpaceImpl { void set_field_metadata(const eckit::Configuration& config, Field& field) const; - private: Field lonlat_; Field vertical_; @@ -157,6 +157,9 @@ class PointCloud : public functionspace::FunctionSpaceImpl { Field partition_; std::unique_ptr halo_exchange_; idx_t levels_{0}; + + void setupHaloExchange(); + }; //------------------------------------------------------------------------------------------------------ @@ -169,11 +172,12 @@ class PointCloud : public FunctionSpace { public: PointCloud(const FunctionSpace&); PointCloud(const Field& points); + PointCloud(const Field&, const Field&); PointCloud(const FieldSet& flds); PointCloud(const std::vector&); PointCloud(const std::vector&); PointCloud(const std::initializer_list>&); - PointCloud(const Grid& grid); + PointCloud(const Grid&); operator bool() const { return valid(); } bool valid() const { return functionspace_; } diff --git a/src/atlas/functionspace/Spectral.cc b/src/atlas/functionspace/Spectral.cc index f5d401a1e..e737aba9e 100644 --- a/src/atlas/functionspace/Spectral.cc +++ b/src/atlas/functionspace/Spectral.cc @@ -178,19 +178,6 @@ Spectral::Spectral(const int truncation, const eckit::Configuration& config): config.get("levels", nb_levels_); } -Spectral::Spectral(const trans::Trans& trans, const eckit::Configuration& config): - nb_levels_(0), truncation_(trans.truncation()), parallelisation_([&trans, this]() -> Parallelisation* { -#if ATLAS_HAVE_TRANS - const auto* trans_ifs = dynamic_cast(trans.get()); - if (trans_ifs) { - return new Parallelisation(trans_ifs->trans_); - } -#endif - return new Parallelisation(truncation_); - }()) { - config.get("levels", nb_levels_); -} - Spectral::~Spectral() = default; std::string Spectral::distribution() const { diff --git a/src/atlas/functionspace/Spectral.h b/src/atlas/functionspace/Spectral.h index 0932c2261..68c5ff966 100644 --- a/src/atlas/functionspace/Spectral.h +++ b/src/atlas/functionspace/Spectral.h @@ -63,8 +63,6 @@ class Spectral : public functionspace::FunctionSpaceImpl { Spectral(const int truncation, const eckit::Configuration& = util::NoConfig()); - Spectral(const trans::Trans&, const eckit::Configuration& = util::NoConfig()); - ~Spectral() override; std::string type() const override { return "Spectral"; } diff --git a/src/atlas/functionspace/detail/BlockStructuredColumns.cc b/src/atlas/functionspace/detail/BlockStructuredColumns.cc index a291e0294..c18b1756d 100644 --- a/src/atlas/functionspace/detail/BlockStructuredColumns.cc +++ b/src/atlas/functionspace/detail/BlockStructuredColumns.cc @@ -156,7 +156,6 @@ void rev_block_copy(const Field loc, Field sloc, const functionspace::detail::Bl } } - void transpose_nonblocked_to_blocked(const Field& nonblocked, Field& blocked, const functionspace::detail::BlockStructuredColumns& fs) { auto kind = nonblocked.datatype().kind(); if (kind == array::DataType::kind()) { @@ -264,6 +263,13 @@ Field BlockStructuredColumns::createField(const eckit::Configuration& options) c Field field(structuredcolumns_->config_name(options), structuredcolumns_->config_datatype(options), config_spec(options)); structuredcolumns_->set_field_metadata(options, field); field.set_functionspace(this); + + bool global = false; + options.get("global", global); + if (not global) { + field.set_horizontal_dimension({0,field.rank()-1}); + } + return field; } diff --git a/src/atlas/functionspace/detail/StructuredColumns.h b/src/atlas/functionspace/detail/StructuredColumns.h index 392ed577c..8236afca9 100644 --- a/src/atlas/functionspace/detail/StructuredColumns.h +++ b/src/atlas/functionspace/detail/StructuredColumns.h @@ -141,7 +141,7 @@ class StructuredColumns : public FunctionSpaceImpl { Field lonlat() const override { return field_xy_; } Field xy() const { return field_xy_; } - Field z() const { return vertical().z(); } + Field z() const { return field_z_; } Field partition() const { return field_partition_; } Field global_index() const override { return field_global_index_; } Field remote_index() const override { @@ -222,6 +222,7 @@ class StructuredColumns : public FunctionSpaceImpl { mutable util::PartitionPolygons all_polygons_; Field field_xy_; + Field field_z_; Field field_partition_; Field field_global_index_; mutable Field field_remote_index_; @@ -317,6 +318,7 @@ class StructuredColumns : public FunctionSpaceImpl { friend struct BlockStructuredColumnsFortranAccess; Map2to1 ij2gp_; + friend class BlockStructuredColumns; void setup(const grid::Distribution& distribution, const eckit::Configuration& config); friend class BlockStructuredColumns; diff --git a/src/atlas/functionspace/detail/StructuredColumns_setup.cc b/src/atlas/functionspace/detail/StructuredColumns_setup.cc index cb348da60..12a08e6de 100644 --- a/src/atlas/functionspace/detail/StructuredColumns_setup.cc +++ b/src/atlas/functionspace/detail/StructuredColumns_setup.cc @@ -574,6 +574,13 @@ void StructuredColumns::setup(const grid::Distribution& distribution, const ecki field_index_j_ = Field("index_j", array::make_datatype(), array::make_shape(size_halo_)); field_xy_ = Field("xy", array::make_datatype(), array::make_shape(size_halo_, 2)); + field_z_ = Field("z", array::make_datatype(), array::make_shape(vertical_.size())); + { + auto z = array::make_view(field_z_); + for (idx_t k = 0; k < z.size(); ++k) { + z(k) = vertical_[k]; + } + } auto xy = array::make_view(field_xy_); auto part = array::make_view(field_partition_); auto ghost = array::make_view(field_ghost_); diff --git a/src/atlas/grid/Partitioner.cc b/src/atlas/grid/Partitioner.cc index 22cebf9e8..87512a80f 100644 --- a/src/atlas/grid/Partitioner.cc +++ b/src/atlas/grid/Partitioner.cc @@ -39,6 +39,9 @@ namespace { detail::partitioner::Partitioner* partitioner_from_config(const std::string& type, const Partitioner::Config& config) { long partitions = mpi::size(); config.get("partitions", partitions); + if (partitions==1) { + return Factory::build("serial"); + } return Factory::build(type, partitions, config); } detail::partitioner::Partitioner* partitioner_from_config(const Partitioner::Config& config) { diff --git a/src/atlas/grid/StructuredPartitionPolygon.h b/src/atlas/grid/StructuredPartitionPolygon.h index 1ba6a6874..5157a4e43 100644 --- a/src/atlas/grid/StructuredPartitionPolygon.h +++ b/src/atlas/grid/StructuredPartitionPolygon.h @@ -46,7 +46,7 @@ class StructuredPartitionPolygon : public util::PartitionPolygon { PointsXY xy() const override; PointsLonLat lonlat() const override; - const RectangularDomain& inscribedDomain() const override { return inscribed_domain_; } + const RectangleXY& inscribedDomain() const override { return inscribed_domain_; } void allGather(util::PartitionPolygons&) const override; @@ -64,7 +64,7 @@ class StructuredPartitionPolygon : public util::PartitionPolygon { private: PointsXY points_; PointsXY inner_bounding_box_; - RectangularDomain inscribed_domain_; + RectangleXY inscribed_domain_; const functionspace::FunctionSpaceImpl& fs_; idx_t halo_; }; diff --git a/src/atlas/grid/Vertical.cc b/src/atlas/grid/Vertical.cc index fee8396a0..cf844f86b 100644 --- a/src/atlas/grid/Vertical.cc +++ b/src/atlas/grid/Vertical.cc @@ -56,16 +56,6 @@ idx_t get_levels(const util::Config& config) { Vertical::Vertical(const util::Config& config): Vertical(get_levels(config), linspace(0., 1., get_levels(config), true), config) {} -Field Vertical::z() const { - auto zfield = Field("z", array::make_datatype(), array::make_shape(size())); - auto zview = array::make_view(zfield); - for (idx_t k = 0; k < size(); ++k) { - zview(k) = z_[k]; - } - return zfield; -} - - std::ostream& operator<<(std::ostream& os, const Vertical& v) { os << v.z_; return os; diff --git a/src/atlas/grid/Vertical.h b/src/atlas/grid/Vertical.h index 584a4b0db..755a0c839 100644 --- a/src/atlas/grid/Vertical.h +++ b/src/atlas/grid/Vertical.h @@ -18,8 +18,6 @@ namespace atlas { -class Field; - //--------------------------------------------------------------------------------------------------------------------- class Vertical { @@ -32,8 +30,6 @@ class Vertical { Vertical(const util::Config& config = util::NoConfig()); - Field z() const; - public: idx_t k_begin() const { return k_begin_; } idx_t k_end() const { return k_end_; } diff --git a/src/atlas/grid/detail/grid/CubedSphere.h b/src/atlas/grid/detail/grid/CubedSphere.h index 69d29b004..0f1df4803 100644 --- a/src/atlas/grid/detail/grid/CubedSphere.h +++ b/src/atlas/grid/detail/grid/CubedSphere.h @@ -16,7 +16,6 @@ #include "eckit/types/Types.h" -#include "atlas/array.h" #include "atlas/grid/Spacing.h" #include "atlas/grid/Tiles.h" #include "atlas/grid/detail/grid/Grid.h" diff --git a/src/atlas/grid/detail/grid/Structured.cc b/src/atlas/grid/detail/grid/Structured.cc index 02b26384e..a3f42f665 100644 --- a/src/atlas/grid/detail/grid/Structured.cc +++ b/src/atlas/grid/detail/grid/Structured.cc @@ -655,10 +655,7 @@ Grid::Config Structured::meshgenerator() const { Grid::Config Structured::partitioner() const { Config config; - if (mpi::size() == 1) { - config.set("type", "serial"); - } - else if (reduced()) { + if (reduced()) { config.set("type", "equal_regions"); } else { diff --git a/src/atlas/grid/detail/grid/Unstructured.cc b/src/atlas/grid/detail/grid/Unstructured.cc index bfa999b8d..f5047a756 100644 --- a/src/atlas/grid/detail/grid/Unstructured.cc +++ b/src/atlas/grid/detail/grid/Unstructured.cc @@ -19,12 +19,9 @@ #include "eckit/utils/Hash.h" #include "atlas/array/ArrayView.h" -#include "atlas/field/Field.h" #include "atlas/grid/Iterator.h" #include "atlas/grid/detail/grid/GridBuilder.h" #include "atlas/grid/detail/grid/GridFactory.h" -#include "atlas/mesh/Mesh.h" -#include "atlas/mesh/Nodes.h" #include "atlas/option.h" #include "atlas/runtime/Exception.h" #include "atlas/runtime/Log.h" @@ -40,22 +37,6 @@ namespace { static GridFactoryBuilder __register_Unstructured(Unstructured::static_type()); } - -Unstructured::Unstructured(const Mesh& m): Grid(), points_(new std::vector(m.nodes().size())) { - util::Config config_domain; - config_domain.set("type", "global"); - domain_ = Domain(config_domain); - - auto xy = array::make_view(m.nodes().xy()); - std::vector& p = *points_; - const idx_t npts = static_cast(p.size()); - - for (idx_t n = 0; n < npts; ++n) { - p[n].assign(xy(n, XX), xy(n, YY)); - } -} - - namespace { class Normalise { public: @@ -159,6 +140,23 @@ Unstructured::Unstructured(std::initializer_list initializer_list): domain_ = GlobalDomain(); } +Unstructured::Unstructured(size_t N, const double x[], const double y[], size_t xstride, size_t ystride): + Grid(), points_(new std::vector(N)) { + util::Config config_domain; + config_domain.set("type", "global"); + domain_ = Domain(config_domain); + + std::vector& p = *points_; + const idx_t npts = static_cast(p.size()); + + for (idx_t n = 0; n < npts; ++n) { + p[n].assign(x[n*xstride], y[n*ystride]); + } +} + +Unstructured::Unstructured(size_t N, const double xy[]): + Unstructured(N,xy,xy+1,2,2) {} + Unstructured::~Unstructured() = default; Grid::uid_t Unstructured::name() const { diff --git a/src/atlas/grid/detail/grid/Unstructured.h b/src/atlas/grid/detail/grid/Unstructured.h index e5a07848d..9c830d22e 100644 --- a/src/atlas/grid/detail/grid/Unstructured.h +++ b/src/atlas/grid/detail/grid/Unstructured.h @@ -20,14 +20,11 @@ #include #include +#include "atlas/util/mdspan.h" #include "atlas/grid/detail/grid/Grid.h" #include "atlas/runtime/Exception.h" #include "atlas/util/Point.h" -namespace atlas { -class Mesh; -} - namespace atlas { namespace grid { namespace detail { @@ -147,8 +144,17 @@ class Unstructured : public Grid { /// Constructor taking a list of points (makes copy) Unstructured(const std::vector& pts); - /// Constructor taking a mesh - Unstructured(const Mesh& m); + /// Constructor taking a list of points (makes copy) + Unstructured(size_t N, const double x[], const double y[], size_t xstride = 1, size_t ystride = 1); + + /// Constructor taking a list of points (makes copy) + Unstructured(size_t N, const double xy[]); + + /// Constructor taking a mdspan (makes copy) + /// First dimension is number of points, second dimension is coordinate. First X (lon), then Y (lat) + template + Unstructured(atlas::mdspan xy): + Unstructured(xy.extent(0), &xy(0,0), &xy(0,1), xy.stride(1), xy.stride(1)) {} /// Constructor from initializer list Unstructured(std::initializer_list); diff --git a/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc b/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc index 4bba80e98..74a605866 100644 --- a/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc +++ b/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerCubedSphere.cc @@ -24,7 +24,7 @@ void MatchingMeshPartitionerCubedSphere::partition(const Grid& grid, int partiti const auto N = CubedSphereGrid(prePartitionedMesh_.grid()).N(); const auto epsilon = 2. * std::numeric_limits::epsilon() * N; const auto edgeEpsilon = epsilon; - const size_t listSize = 4; + const size_t listSize = 8; // Loop over grid and set partioning[]. auto lonlatIt = grid.lonlat().begin(); diff --git a/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc b/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc index 0f9d003c0..799872a97 100644 --- a/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc +++ b/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc @@ -17,7 +17,6 @@ #include "eckit/log/Bytes.h" -#include "atlas/array.h" #include "atlas/grid/detail/spacing/gaussian/Latitudes.h" #include "atlas/grid/detail/spacing/gaussian/N.h" #include "atlas/library/config.h" @@ -27,12 +26,6 @@ #include "atlas/util/Constants.h" #include "atlas/util/CoordinateEnums.h" -//using eckit::ConcreteBuilderT0; -//using eckit::Factory; - -using atlas::array::Array; -using atlas::array::ArrayView; -using atlas::array::make_view; namespace atlas { namespace grid { @@ -111,7 +104,8 @@ void legpol_newton_iteration(size_t kn, const double pfn[], double px, double& p // PXN : new abscissa (Newton iteration) (out) // PXMOD : PXN-PX (out) - double zdlx, zdlk, zdlldn, zdlxn, zdlmod; + double zdlx, zdlk, zdlldn, zdlxn; + double zdlmod = 0; size_t ik; size_t kodd = kn % 2; // mod(kn,2) @@ -134,7 +128,9 @@ void legpol_newton_iteration(size_t kn, const double pfn[], double px, double& p ++ik; } // Newton method - zdlmod = -zdlk / zdlldn; + if( zdlldn != 0 ) { + zdlmod = -zdlk / zdlldn; + } zdlxn = zdlx + zdlmod; pxn = zdlxn; pxmod = zdlmod; diff --git a/src/atlas/grid/detail/vertical/VerticalInterface.cc b/src/atlas/grid/detail/vertical/VerticalInterface.cc index eed73a152..da9e6f5e2 100644 --- a/src/atlas/grid/detail/vertical/VerticalInterface.cc +++ b/src/atlas/grid/detail/vertical/VerticalInterface.cc @@ -36,8 +36,12 @@ field::FieldImpl* atlas__Vertical__z(const Vertical* This) { ATLAS_ASSERT(This != nullptr); field::FieldImpl* field; { - Field f = This->z(); - field = f.get(); + auto zfield = Field("z", array::make_datatype(), array::make_shape(This->size())); + auto zview = array::make_view(zfield); + for (idx_t k = 0; k < zview.size(); ++k) { + zview(k) = (*This)[k]; + } + field = zfield.get(); field->attach(); } field->detach(); diff --git a/src/atlas/interpolation/Vector2D.h b/src/atlas/interpolation/Vector2D.h index 14432bcd1..ee6cc50eb 100644 --- a/src/atlas/interpolation/Vector2D.h +++ b/src/atlas/interpolation/Vector2D.h @@ -14,13 +14,33 @@ #if ATLAS_HAVE_EIGEN +#if defined(__NVCOMPILER) +# if !defined(ATLAS_START_WARNINGS_SUPPRESSION) +# define ATLAS_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) +# define ATLAS_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) +# endif +# if !defined(ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS) +# define ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS _Pragma( "diag_suppress 68" ) +# endif +#else +# if !defined(ATLAS_START_WARNINGS_SUPPRESSION) +# define ATLAS_START_WARNINGS_SUPPRESSION +# define ATLAS_STOP_WARNINGS_SUPPRESSION +# endif +# if !defined(ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS) +# define ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS +# endif +#endif + #define EIGEN_NO_AUTOMATIC_RESIZING //#define EIGEN_DONT_ALIGN //#define EIGEN_DONT_VECTORIZE +ATLAS_START_WARNINGS_SUPPRESSION +ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS #include #include - +ATLAS_STOP_WARNINGS_SUPPRESSION #else #include diff --git a/src/atlas/interpolation/Vector3D.h b/src/atlas/interpolation/Vector3D.h index 0b325e5b8..d642dcf4c 100644 --- a/src/atlas/interpolation/Vector3D.h +++ b/src/atlas/interpolation/Vector3D.h @@ -14,13 +14,33 @@ #if ATLAS_HAVE_EIGEN +#if defined(__NVCOMPILER) +# ifndef ATLAS_START_WARNINGS_SUPPRESSION +# define ATLAS_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) +# define ATLAS_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) +# endif +# ifndef ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS +# define ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS _Pragma( "diag_suppress 68" ) +# endif +#else +# ifndef ATLAS_START_WARNINGS_SUPPRESSION +# define ATLAS_START_WARNINGS_SUPPRESSION +# define ATLAS_STOP_WARNINGS_SUPPRESSION +# endif +# ifndef ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS +# define ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS +# endif +#endif + #define EIGEN_NO_AUTOMATIC_RESIZING //#define EIGEN_DONT_ALIGN //#define EIGEN_DONT_VECTORIZE +ATLAS_START_WARNINGS_SUPPRESSION +ATLAS_SUPPRESS_INTEGER_SIGN_CHANGE_WARNINGS #include #include - +ATLAS_STOP_WARNINGS_SUPPRESSION #else #include diff --git a/src/atlas/interpolation/method/PointSet.h b/src/atlas/interpolation/method/PointSet.h index 17b5318ed..fbd909935 100644 --- a/src/atlas/interpolation/method/PointSet.h +++ b/src/atlas/interpolation/method/PointSet.h @@ -72,6 +72,31 @@ class PointSet { } } + void list_unique_points(std::vector& opts) { + ATLAS_TRACE("Finding unique points"); + + ATLAS_ASSERT(opts.empty()); + + opts.reserve(npts_); + + for (PointIndex3::iterator i = tree_->begin(); i != tree_->end(); ++i) { + Point p(i->point()); + size_t ip = i->payload(); + // std::cout << "point " << ip << " " << p << std::endl; + size_t uidx = unique(p, ip); + if (ip == uidx) { + opts.push_back(ip); + // std::cout << "----> UNIQ " << ip << std::endl; + } + else { + // std::cout << "----> DUP " << ip << " -> " << uidx << + // std::endl; + } + // ++show_progress; + } + } + + size_t unique(const Point& p, size_t idx = std::numeric_limits::max()) { DupStore_t::iterator dit = duplicates_.find(idx); if (dit != duplicates_.end()) { diff --git a/src/atlas/interpolation/method/cubedsphere/CellFinder.cc b/src/atlas/interpolation/method/cubedsphere/CellFinder.cc index 17f67f7d9..9912d16f1 100644 --- a/src/atlas/interpolation/method/cubedsphere/CellFinder.cc +++ b/src/atlas/interpolation/method/cubedsphere/CellFinder.cc @@ -73,7 +73,7 @@ CellFinder::Cell CellFinder::getCell(const PointLonLat& lonlat, size_t listSize, break; } - const auto t = cellTijView(i); + const auto t = cellTijView(i,0); const auto row = nodeConnectivity.row(i); if (row.size() == 4) { diff --git a/src/atlas/interpolation/method/structured/kernels/CubicHorizontalKernel.h b/src/atlas/interpolation/method/structured/kernels/CubicHorizontalKernel.h index da5235d6e..b61b0cc51 100644 --- a/src/atlas/interpolation/method/structured/kernels/CubicHorizontalKernel.h +++ b/src/atlas/interpolation/method/structured/kernels/CubicHorizontalKernel.h @@ -144,8 +144,8 @@ class CubicHorizontalKernel { auto& weights_j = weights.weights_j; weights_j[0] = (dl2 * dl3 * dl4) / dcl1; -#if defined(_CRAYC) && ATLAS_BUILD_TYPE_RELEASE - // prevents FE_INVALID somehow (tested with Cray 8.7) +#if defined(_CRAYC) || defined(__NVCOMPILER) && ATLAS_BUILD_TYPE_RELEASE + // prevents FE_INVALID somehow (tested with Cray 8.7, nvhpc-22.11) ATLAS_ASSERT(!std::isnan(weights_j[0])); #endif weights_j[1] = (dl1 * dl3 * dl4) / dcl2; diff --git a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc index b87c17987..d1ea5addf 100644 --- a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc +++ b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc @@ -8,8 +8,10 @@ * nor does it submit to any jurisdiction. */ +#include #include #include +#include // for mkdir #include "ConservativeSphericalPolygonInterpolation.h" @@ -22,6 +24,7 @@ #include "atlas/mesh/actions/BuildNode2CellConnectivity.h" #include "atlas/meshgenerator.h" #include "atlas/parallel/mpi/mpi.h" +#include "atlas/parallel/omp/omp.h" #include "atlas/runtime/Exception.h" #include "atlas/runtime/Log.h" #include "atlas/runtime/Trace.h" @@ -31,6 +34,8 @@ #include "eckit/log/Bytes.h" +#define PRINT_BAD_POLYGONS 0 + namespace atlas { namespace interpolation { namespace method { @@ -40,8 +45,99 @@ using util::ConvexSphericalPolygon; namespace { +template +std::string to_json(const It& begin, const It& end, int precision = 0) { + std::stringstream ss; + ss << "[\n"; + size_t size = std::distance(begin,end); + size_t c=0; + for( auto it = begin; it != end; ++it, ++c ) { + ss << " " << it->json(precision); + if( c < size-1 ) { + ss << ",\n"; + } + } + ss << "\n]"; + return ss.str(); +} + +template +std::string to_json(const ConvexSphericalPolygonContainer& polygons, int precision = 0) { + return to_json(polygons.begin(),polygons.end(),precision); +} + + MethodBuilder __builder("conservative-spherical-polygon"); +template +std::string polygons_to_json(const It& begin, const It& end, int precision = 0) { + std::stringstream ss; + ss << "[\n"; + size_t size = std::distance(begin,end); + size_t c=0; + for( auto it = begin; it != end; ++it, ++c ) { + ss << " " << it->json(precision); + if( c < size-1 ) { + ss << ",\n"; + } + } + ss << "\n]"; + return ss.str(); +} + +template +std::string polygons_to_json(const ConvexSphericalPolygonContainer& polygons, int precision = 0) { + return polygons_to_json(polygons.begin(),polygons.end(),precision); +} + +void dump_polygons_to_json( const ConvexSphericalPolygon& t_csp, + double pointsSameEPS, + const std::vector>& source_polygons, + const std::vector& source_polygons_considered_indices, + const std::string folder, + const std::string name) { + std::vector csp_arr{ t_csp }; + std::vector csp_arr_intersecting {t_csp}; + std::vector intersections; + int count = 1; + for( auto& s_idx : source_polygons_considered_indices ) { + auto s_csp = std::get<0>(source_polygons[s_idx]); + csp_arr.emplace_back( s_csp ); + std::fstream file_plg_debug(folder + name + "_" + std::to_string(count++) + ".debug", std::ios::out); + ConvexSphericalPolygon iplg = t_csp.intersect(s_csp, &file_plg_debug, pointsSameEPS); + file_plg_debug.close(); + if (iplg) { + if( iplg.area() > 0. ) { + csp_arr_intersecting.emplace_back( s_csp ); + intersections.emplace_back( iplg ); + } + } + } + double tot = 0.; + Log::info().indent(); + std::fstream file_info(folder + name + ".info", std::ios::out); + file_info << "List of intersection weights: " << std::endl; + count = 1; + for( auto& iplg : intersections ) { + csp_arr.emplace_back( iplg ); + csp_arr_intersecting.emplace_back( iplg ); + tot += iplg.area() / t_csp.area(); + file_info << "\t" << count++ << ": " << iplg.area() / t_csp.area() << std::endl; + } + file_info << std::endl << name + ": " << 100. * tot << " % covered."<< std::endl << std::endl; + file_info << "Target polygon + candidate source polygons + " << intersections.size() << " intersections in file:" << std::endl; + file_info << "\t" << folder + name + ".candidates\n" << std::endl; + std::fstream file_plg(folder + name + ".candidates", std::ios::out); + file_plg << polygons_to_json(csp_arr, 16); + file_plg.close(); + file_info << "Target polygon + intersecting source polygon + " << intersections.size() << " intersections in file:" << std::endl; + file_info << "\t" << folder + name + ".intersections\n" << std::endl; + file_plg.open(folder + name + ".intersections", std::ios::out); + file_plg << polygons_to_json(csp_arr_intersecting, 16); + file_plg.close(); + Log::info().unindent(); +} + constexpr double unit_sphere_area() { // 4*pi*r^2 with r=1 return 4. * M_PI; @@ -49,7 +145,7 @@ constexpr double unit_sphere_area() { template size_t memory_of(const std::vector& vector) { - return sizeof(T) * vector.size(); + return sizeof(T) * vector.capacity(); } template size_t memory_of(const std::vector>& vector_of_vector) { @@ -66,7 +162,7 @@ size_t memory_of( for (const auto& params : vector_of_params) { mem += memory_of(params.cell_idx); mem += memory_of(params.centroids); - mem += memory_of(params.src_weights); + mem += memory_of(params.weights); mem += memory_of(params.tgt_weights); } return mem; @@ -108,9 +204,6 @@ void sort_and_accumulate_triplets(std::vector& triplets) } } - -} // namespace - int inside_vertices(const ConvexSphericalPolygon& plg1, const ConvexSphericalPolygon& plg2, int& pout) { int points_in = 0; pout = 0; @@ -132,6 +225,8 @@ int inside_vertices(const ConvexSphericalPolygon& plg1, const ConvexSphericalPol return points_in; } +} // namespace + ConservativeSphericalPolygonInterpolation::ConservativeSphericalPolygonInterpolation(const Config& config): Method(config) { config.get("validate", validate_ = false); @@ -321,8 +416,9 @@ std::vector ConservativeSphericalPolygonInterpolation::get_node_neighbour // Create polygons for cell-centred data. Here, the polygons are mesh cells ConservativeSphericalPolygonInterpolation::CSPolygonArray -ConservativeSphericalPolygonInterpolation::get_polygons_celldata(Mesh& mesh) const { +ConservativeSphericalPolygonInterpolation::get_polygons_celldata(FunctionSpace fs) const { CSPolygonArray cspolygons; + auto mesh = extract_mesh(fs); const idx_t n_cells = mesh.cells().size(); cspolygons.resize(n_cells); const auto& cell2node = mesh.cells().node_connectivity(); @@ -331,7 +427,11 @@ ConservativeSphericalPolygonInterpolation::get_polygons_celldata(Mesh& mesh) con const auto& cell_flags = array::make_view(mesh.cells().flags()); const auto& cell_part = array::make_view(mesh.cells().partition()); std::vector pts_ll; + const int fs_halo = functionspace::CellColumns(fs).halo().size(); for (idx_t cell = 0; cell < n_cells; ++cell) { + if( cell_halo(cell) > fs_halo ) { + continue; + } int halo_type = cell_halo(cell); const idx_t n_nodes = cell2node.cols(cell); pts_ll.clear(); @@ -354,12 +454,13 @@ ConservativeSphericalPolygonInterpolation::get_polygons_celldata(Mesh& mesh) con // (cell_centre, edge_centre, cell_vertex, edge_centre) // additionally, subcell-to-node and node-to-subcells mapping are computed ConservativeSphericalPolygonInterpolation::CSPolygonArray -ConservativeSphericalPolygonInterpolation::get_polygons_nodedata(Mesh& mesh, std::vector& csp2node, +ConservativeSphericalPolygonInterpolation::get_polygons_nodedata(FunctionSpace fs, std::vector& csp2node, std::vector>& node2csp, std::array& errors) const { CSPolygonArray cspolygons; csp2node.clear(); node2csp.clear(); + auto mesh = extract_mesh(fs); node2csp.resize(mesh.nodes().size()); const auto nodes_ll = array::make_view(mesh.nodes().lonlat()); const auto& cell2node = mesh.cells().node_connectivity(); @@ -376,9 +477,19 @@ ConservativeSphericalPolygonInterpolation::get_polygons_nodedata(Mesh& mesh, std eckit::geometry::Sphere::convertSphericalToCartesian(1., p_ll, p_xyz); return p_xyz; }; + const auto node_halo = array::make_view(mesh.nodes().halo()); + const auto node_ghost = array::make_view(mesh.nodes().ghost()); + const auto node_flags = array::make_view(mesh.nodes().flags()); + const auto node_part = array::make_view(mesh.nodes().partition()); + idx_t cspol_id = 0; // subpolygon enumeration errors = {0., 0.}; // over/undershoots in creation of subpolygons + const int fs_halo = functionspace::NodeColumns(fs).halo().size(); + for (idx_t cell = 0; cell < mesh.cells().size(); ++cell) { + if( cell_halo(cell) > fs_halo ) { + continue; + } ATLAS_ASSERT(cell < cell2node.rows()); const idx_t n_nodes = cell2node.cols(cell); ATLAS_ASSERT(n_nodes > 2); @@ -409,6 +520,7 @@ ConservativeSphericalPolygonInterpolation::get_polygons_nodedata(Mesh& mesh, std PointLonLat cell_ll = xyz2ll(cell_mid); double loc_csp_area_shoot = ConvexSphericalPolygon(pts_ll).area(); // get ConvexSphericalPolygon for each valid edge + int halo_type; for (int inode = 0; inode < pts_idx.size(); inode++) { int inode_n = next_index(inode, pts_idx.size()); idx_t node = cell2node(cell, inode); @@ -429,18 +541,22 @@ ConservativeSphericalPolygonInterpolation::get_polygons_nodedata(Mesh& mesh, std subpol_pts_ll[1] = xyz2ll(iedge_mid); subpol_pts_ll[2] = pts_ll[inode_n]; subpol_pts_ll[3] = xyz2ll(jedge_mid); - int halo_type = cell_halo(cell); - if (util::Bitflags::view(cell_flags(cell)).check(util::Topology::PERIODIC) and - cell_part(cell) == mpi::rank()) { + halo_type = node_halo(node_n); + + if (util::Bitflags::view(node_flags(node_n)).check(util::Topology::PERIODIC) and + node_part(node_n) == mpi::rank()) { halo_type = -1; } + ConvexSphericalPolygon cspi(subpol_pts_ll); loc_csp_area_shoot -= cspi.area(); cspolygons.emplace_back(cspi, halo_type); cspol_id++; } - errors[0] += std::abs(loc_csp_area_shoot); - errors[1] = std::max(std::abs(loc_csp_area_shoot), errors[1]); + if (halo_type == 0) { + errors[0] += std::abs(loc_csp_area_shoot); + errors[1] = std::max(std::abs(loc_csp_area_shoot), errors[1]); + } } ATLAS_TRACE_MPI(ALLREDUCE) { mpi::comm().allReduceInPlace(&errors[0], 1, eckit::mpi::sum()); @@ -465,7 +581,7 @@ void ConservativeSphericalPolygonInterpolation::do_setup_impl(const Grid& src_gr tgt_fs_ = functionspace::CellColumns(tgt_mesh_, option::halo(0)); } else { - tgt_fs_ = functionspace::NodeColumns(tgt_mesh_, option::halo(0)); + tgt_fs_ = functionspace::NodeColumns(tgt_mesh_, option::halo(1)); } } sharable_data_->tgt_fs_ = tgt_fs_; @@ -561,48 +677,68 @@ void ConservativeSphericalPolygonInterpolation::do_setup(const FunctionSpace& sr tgt_mesh_ = extract_mesh(tgt_fs_); { - // we need src_halo_size >= 2, whereas tgt_halo_size >= 0 is enough - int src_halo_size = 0; - src_mesh_.metadata().get("halo", src_halo_size); - ATLAS_ASSERT(src_halo_size > 1); + // we need src_halo_size >= 2, tgt_halo_size >= 0 for CellColumns + // if target is NodeColumns, we need: + // tgt_halo_size >= 1 and + // src_halo_size large enough to cover the the target halo cells in the first row + int halo_size = 0; + src_mesh_.metadata().get("halo", halo_size); + if (halo_size < 2) { + Log::info() << "WARNING The halo size on source mesh should be at least 2.\n"; + } + if (not tgt_cell_data_) { + Log::info() << "WARNING The source cells should cover the first row of the target halos.\n"; + } + halo_size = 0; + tgt_mesh_.metadata().get("halo", halo_size); + if (not tgt_cell_data_ and halo_size == 0) { + Log::info() << "WARNING The halo size on target mesh should be at least 1 for the target NodeColumns.\n"; + } } CSPolygonArray src_csp; CSPolygonArray tgt_csp; - std::array errors = {0., 0.}; if (compute_cache) { + std::array errors = {0., 0.}; ATLAS_TRACE("Get source polygons"); StopWatch stopwatch; stopwatch.start(); if (src_cell_data_) { - src_csp = get_polygons_celldata(src_mesh_); + src_csp = get_polygons_celldata(src_fs_); } else { src_csp = - get_polygons_nodedata(src_mesh_, sharable_data_->src_csp2node_, sharable_data_->src_node2csp_, errors); + get_polygons_nodedata(src_fs_, sharable_data_->src_csp2node_, sharable_data_->src_node2csp_, errors); } stopwatch.stop(); sharable_data_->timings.source_polygons_assembly = stopwatch.elapsed(); - } - remap_stat_.errors[Statistics::Errors::SRC_PLG_L1] = errors[0]; - remap_stat_.errors[Statistics::Errors::SRC_PLG_LINF] = errors[1]; - if (compute_cache) { + remap_stat_.counts[Statistics::Counts::SRC_PLG] = src_csp.size(); + remap_stat_.errors[Statistics::Errors::SRC_SUBPLG_L1] = errors[0]; + remap_stat_.errors[Statistics::Errors::SRC_SUBPLG_LINF] = errors[1]; + + errors = {0., 0.}; ATLAS_TRACE("Get target polygons"); - StopWatch stopwatch; stopwatch.start(); if (tgt_cell_data_) { - tgt_csp = get_polygons_celldata(tgt_mesh_); + tgt_csp = get_polygons_celldata(tgt_fs_); } else { tgt_csp = - get_polygons_nodedata(tgt_mesh_, sharable_data_->tgt_csp2node_, sharable_data_->tgt_node2csp_, errors); + get_polygons_nodedata(tgt_fs_, sharable_data_->tgt_csp2node_, sharable_data_->tgt_node2csp_, errors); } stopwatch.stop(); sharable_data_->timings.target_polygons_assembly = stopwatch.elapsed(); + remap_stat_.counts[Statistics::Counts::TGT_PLG] = tgt_csp.size(); + remap_stat_.errors[Statistics::Errors::TGT_SUBPLG_L1] = errors[0]; + remap_stat_.errors[Statistics::Errors::TGT_SUBPLG_LINF] = errors[1]; + } + else { + remap_stat_.counts[Statistics::Counts::SRC_PLG] = -1111; + remap_stat_.errors[Statistics::Errors::SRC_SUBPLG_L1] = -1111.; + remap_stat_.errors[Statistics::Errors::SRC_SUBPLG_LINF] = -1111.; + remap_stat_.counts[Statistics::Counts::TGT_PLG] = -1111; + remap_stat_.errors[Statistics::Errors::TGT_SUBPLG_L1] = -1111.; + remap_stat_.errors[Statistics::Errors::TGT_SUBPLG_LINF] = -1111.; } - remap_stat_.counts[Statistics::Counts::SRC_PLG] = src_csp.size(); - remap_stat_.counts[Statistics::Counts::TGT_PLG] = tgt_csp.size(); - remap_stat_.errors[Statistics::Errors::TGT_PLG_L1] = errors[0]; - remap_stat_.errors[Statistics::Errors::TGT_PLG_LINF] = errors[1]; n_spoints_ = src_fs_.size(); n_tpoints_ = tgt_fs_.size(); @@ -611,82 +747,124 @@ void ConservativeSphericalPolygonInterpolation::do_setup(const FunctionSpace& sr intersect_polygons(src_csp, tgt_csp); auto& src_points_ = sharable_data_->src_points_; - auto& tgt_points_ = sharable_data_->tgt_points_; src_points_.resize(n_spoints_); - tgt_points_.resize(n_tpoints_); sharable_data_->src_areas_.resize(n_spoints_); auto& src_areas_v = sharable_data_->src_areas_; - if (src_cell_data_) { - for (idx_t spt = 0; spt < n_spoints_; ++spt) { - const auto& s_csp = std::get<0>(src_csp[spt]); - src_points_[spt] = s_csp.centroid(); - src_areas_v[spt] = s_csp.area(); - } - } - else { - auto& src_node2csp_ = sharable_data_->src_node2csp_; - const auto lonlat = array::make_view(src_mesh_.nodes().lonlat()); - for (idx_t spt = 0; spt < n_spoints_; ++spt) { - if (src_node2csp_[spt].size() == 0) { - // this is a node to which no subpolygon is associated - // maximal twice per mesh we end here, and that is only when mesh has nodes on poles - auto p = PointLonLat{lonlat(spt, 0), lonlat(spt, 1)}; - eckit::geometry::Sphere::convertSphericalToCartesian(1., p, src_points_[spt]); - } - else { - // .. in the other case, start computing the barycentre - src_points_[spt] = PointXYZ{0., 0., 0.}; + { + ATLAS_TRACE("Store src_areas and src_point"); + if (src_cell_data_) { + for (idx_t spt = 0; spt < n_spoints_; ++spt) { + const auto& s_csp = std::get<0>(src_csp[spt]); + src_points_[spt] = s_csp.centroid(); + src_areas_v[spt] = s_csp.area(); } - src_areas_v[spt] = 0.; - for (idx_t isubcell = 0; isubcell < src_node2csp_[spt].size(); ++isubcell) { - idx_t subcell = src_node2csp_[spt][isubcell]; - const auto& s_csp = std::get<0>(src_csp[subcell]); - src_areas_v[spt] += s_csp.area(); - src_points_[spt] = src_points_[spt] + PointXYZ::mul(s_csp.centroid(), s_csp.area()); + } + else { + auto& src_node2csp_ = sharable_data_->src_node2csp_; + const auto lonlat = array::make_view(src_mesh_.nodes().lonlat()); + for (idx_t spt = 0; spt < n_spoints_; ++spt) { + if (src_node2csp_[spt].size() == 0) { + // this is a node to which no subpolygon is associated + // maximal twice per mesh we end here, and that is only when mesh has nodes on poles + auto p = PointLonLat{lonlat(spt, 0), lonlat(spt, 1)}; + eckit::geometry::Sphere::convertSphericalToCartesian(1., p, src_points_[spt]); + } + else { + // .. in the other case, start computing the barycentre + src_points_[spt] = PointXYZ{0., 0., 0.}; + } + src_areas_v[spt] = 0.; + for (idx_t isubcell = 0; isubcell < src_node2csp_[spt].size(); ++isubcell) { + idx_t subcell = src_node2csp_[spt][isubcell]; + const auto& s_csp = std::get<0>(src_csp[subcell]); + src_areas_v[spt] += s_csp.area(); + src_points_[spt] = src_points_[spt] + PointXYZ::mul(s_csp.centroid(), s_csp.area()); + } + double src_point_norm = PointXYZ::norm(src_points_[spt]); + if (src_point_norm == 0.) { + ATLAS_DEBUG_VAR(src_point_norm); + ATLAS_DEBUG_VAR(src_points_[spt]); + ATLAS_DEBUG_VAR(src_node2csp_[spt].size()); + for (idx_t isubcell = 0; isubcell < src_node2csp_[spt].size(); ++isubcell) { + idx_t subcell = src_node2csp_[spt][isubcell]; + ATLAS_DEBUG_VAR(subcell); + const auto& s_csp = std::get<0>(src_csp[subcell]); + s_csp.print(Log::info()); + Log::info() << std::endl; + src_areas_v[spt] += s_csp.area(); + ATLAS_DEBUG_VAR(s_csp.area()); + ATLAS_DEBUG_VAR(s_csp.centroid()); + src_points_[spt] = src_points_[spt] + PointXYZ::mul(s_csp.centroid(), s_csp.area()); + } + Log::info().flush(); + // something went wrong, improvise + src_point_norm = 1.; + } + src_points_[spt] = PointXYZ::div(src_points_[spt], src_point_norm); } - double src_point_norm = PointXYZ::norm(src_points_[spt]); - ATLAS_ASSERT(src_point_norm > 0.); - src_points_[spt] = PointXYZ::div(src_points_[spt], src_point_norm); } } + auto& tgt_points_ = sharable_data_->tgt_points_; + tgt_points_.resize(n_tpoints_); sharable_data_->tgt_areas_.resize(n_tpoints_); auto& tgt_areas_v = sharable_data_->tgt_areas_; - if (tgt_cell_data_) { - for (idx_t tpt = 0; tpt < n_tpoints_; ++tpt) { - const auto& t_csp = std::get<0>(tgt_csp[tpt]); - tgt_points_[tpt] = t_csp.centroid(); - tgt_areas_v[tpt] = t_csp.area(); - } - } - else { - auto& tgt_node2csp_ = sharable_data_->tgt_node2csp_; - const auto lonlat = array::make_view(tgt_mesh_.nodes().lonlat()); - for (idx_t tpt = 0; tpt < n_tpoints_; ++tpt) { - if (tgt_node2csp_[tpt].size() == 0) { - // this is a node to which no subpolygon is associated - // maximal twice per mesh we end here, and that is only when mesh has nodes on poles - auto p = PointLonLat{lonlat(tpt, 0), lonlat(tpt, 1)}; - eckit::geometry::Sphere::convertSphericalToCartesian(1., p, tgt_points_[tpt]); - } - else { - // .. in the other case, start computing the barycentre - tgt_points_[tpt] = PointXYZ{0., 0., 0.}; + { + ATLAS_TRACE("Store src_areas and src_point"); + if (tgt_cell_data_) { + for (idx_t tpt = 0; tpt < n_tpoints_; ++tpt) { + const auto& t_csp = std::get<0>(tgt_csp[tpt]); + tgt_points_[tpt] = t_csp.centroid(); + tgt_areas_v[tpt] = t_csp.area(); } - tgt_areas_v[tpt] = 0.; - for (idx_t isubcell = 0; isubcell < tgt_node2csp_[tpt].size(); ++isubcell) { - idx_t subcell = tgt_node2csp_[tpt][isubcell]; - const auto& t_csp = std::get<0>(tgt_csp[subcell]); - tgt_areas_v[tpt] += t_csp.area(); - tgt_points_[tpt] = tgt_points_[tpt] + PointXYZ::mul(t_csp.centroid(), t_csp.area()); + } + else { + auto& tgt_node2csp_ = sharable_data_->tgt_node2csp_; + const auto lonlat = array::make_view(tgt_mesh_.nodes().lonlat()); + for (idx_t tpt = 0; tpt < n_tpoints_; ++tpt) { + if (tgt_node2csp_[tpt].size() == 0) { + // this is a node to which no subpolygon is associated + // maximal twice per mesh we end here, and that is only when mesh has nodes on poles + auto p = PointLonLat{lonlat(tpt, 0), lonlat(tpt, 1)}; + eckit::geometry::Sphere::convertSphericalToCartesian(1., p, tgt_points_[tpt]); + } + else { + // .. in the other case, start computing the barycentre + tgt_points_[tpt] = PointXYZ{0., 0., 0.}; + } + tgt_areas_v[tpt] = 0.; + for (idx_t isubcell = 0; isubcell < tgt_node2csp_[tpt].size(); ++isubcell) { + idx_t subcell = tgt_node2csp_[tpt][isubcell]; + const auto& t_csp = std::get<0>(tgt_csp[subcell]); + tgt_areas_v[tpt] += t_csp.area(); + tgt_points_[tpt] = tgt_points_[tpt] + PointXYZ::mul(t_csp.centroid(), t_csp.area()); + } + double tgt_point_norm = PointXYZ::norm(tgt_points_[tpt]); + ATLAS_ASSERT(tgt_point_norm > 0.); + if (tgt_point_norm == 0.) { + ATLAS_DEBUG_VAR(tgt_point_norm); + ATLAS_DEBUG_VAR(tgt_points_[tpt]); + ATLAS_DEBUG_VAR(tgt_node2csp_[tpt].size()); + for (idx_t isubcell = 0; isubcell < tgt_node2csp_[tpt].size(); ++isubcell) { + idx_t subcell = tgt_node2csp_[tpt][isubcell]; + ATLAS_DEBUG_VAR(subcell); + const auto& t_csp = std::get<0>(tgt_csp[subcell]); + t_csp.print(Log::info()); + Log::info() << std::endl; + tgt_areas_v[tpt] += t_csp.area(); + ATLAS_DEBUG_VAR(t_csp.area()); + ATLAS_DEBUG_VAR(t_csp.centroid()); + tgt_points_[tpt] = tgt_points_[tpt] + PointXYZ::mul(t_csp.centroid(), t_csp.area()); + } + Log::info().flush(); + // something went wrong, improvise + tgt_point_norm = 1.; + } + tgt_points_[tpt] = PointXYZ::div(tgt_points_[tpt], tgt_point_norm); } - double tgt_point_norm = PointXYZ::norm(tgt_points_[tpt]); - ATLAS_ASSERT(tgt_point_norm > 0.); - tgt_points_[tpt] = PointXYZ::div(tgt_points_[tpt], tgt_point_norm); } } } - if (not matrix_free_) { StopWatch stopwatch; stopwatch.start(); @@ -718,30 +896,6 @@ void ConservativeSphericalPolygonInterpolation::do_setup(const FunctionSpace& sr } } -namespace { -// needed for intersect_polygons only, merely for detecting duplicate points -struct ComparePointXYZ { - bool operator()(const PointXYZ& f, const PointXYZ& s) const { - // eps = ConvexSphericalPolygon::EPS which is the threshold when two points are "same" - double eps = 1e4 * std::numeric_limits::epsilon(); - if (f[0] < s[0] - eps) { - return true; - } - else if (std::abs(f[0] - s[0]) < eps) { - if (f[1] < s[1] - eps) { - return true; - } - else if (std::abs(f[1] - s[1]) < eps) { - if (f[2] < s[2] - eps) { - return true; - } - } - } - return false; - } -}; -} // namespace - void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolygonArray& src_csp, const CSPolygonArray& tgt_csp) { ATLAS_TRACE(); @@ -751,8 +905,10 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg util::KDTree kdt_search; kdt_search.reserve(tgt_csp.size()); double max_tgtcell_rad = 0.; + const int tgt_halo_intersection_depth = (tgt_cell_data_ ? 0 : 1); // if target NodeColumns, one target halo required for subcells around target nodes for (idx_t jcell = 0; jcell < tgt_csp.size(); ++jcell) { - if (std::get<1>(tgt_csp[jcell]) == 0) { + auto tgt_halo_type = std::get<1>(tgt_csp[jcell]); + if (tgt_halo_type <= tgt_halo_intersection_depth) { // and tgt_halo_type != -1) { const auto& t_csp = std::get<0>(tgt_csp[jcell]); kdt_search.insert(t_csp.centroid(), jcell); max_tgtcell_rad = std::max(max_tgtcell_rad, t_csp.radius()); @@ -767,18 +923,38 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg StopWatch stopwatch_polygon_intersections; stopwatch_src_already_in.start(); - std::set src_cent; - auto polygon_point = [](const ConvexSphericalPolygon& pol) { - PointXYZ p{0., 0., 0.}; - for (int i = 0; i < pol.size(); i++) { - p = p + pol[i]; - } - p /= pol.size(); - return p; + + + // needed for intersect_polygons only, merely for detecting duplicate points + // Treshold at which points are considered same + double compare_pointxyz_eps = 1.e8 * std::numeric_limits::epsilon(); + if (::getenv("ATLAS_COMPAREPOINTXYZ_EPS_FACTOR")) { + compare_pointxyz_eps = std::atof(::getenv("ATLAS_COMPAREPOINTXYZ_EPS_FACTOR")) * std::numeric_limits::epsilon(); + } + + auto compare_pointxyz = [eps=compare_pointxyz_eps] (const PointXYZ& f, const PointXYZ& s) -> bool { + if (f[0] < s[0] - eps) { + return true; + } + else if (std::abs(f[0] - s[0]) < eps) { + if (f[1] < s[1] - eps) { + return true; + } + else if (std::abs(f[1] - s[1]) < eps) { + if (f[2] < s[2] - eps) { + return true; + } + } + } + return false; }; + + std::set src_cent(compare_pointxyz); auto src_already_in = [&](const PointXYZ& halo_cent) { if (src_cent.find(halo_cent) == src_cent.end()) { - src_cent.insert(halo_cent); + atlas_omp_critical{ + src_cent.insert(halo_cent); + } return false; } return true; @@ -807,79 +983,114 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg tgt_iparam.resize(tgt_csp.size()); } + // the worst target polygon coverage for analysis of intersection + std::pair worst_tgt_overcover; + std::pair worst_tgt_undercover; + worst_tgt_overcover.first = -1; + worst_tgt_overcover.second = -1.; + worst_tgt_undercover.first = -1; + worst_tgt_undercover.second = M_PI; + + // NOTE: polygon vertex points at distance < pointsSameEPS will be replaced with one point + constexpr double pointsSameEPS = 5.e6 * std::numeric_limits::epsilon(); + eckit::Channel blackhole; - eckit::ProgressTimer progress("Intersecting polygons ", src_csp.size(), " cell", double(10), - src_csp.size() > 50 ? Log::info() : blackhole); - for (idx_t scell = 0; scell < src_csp.size(); ++scell, ++progress) { - stopwatch_src_already_in.start(); - if (src_already_in(polygon_point(std::get<0>(src_csp[scell])))) { + eckit::ProgressTimer progress("Intersecting polygons ", src_csp.size() / atlas_omp_get_max_threads(), " (cell/thread)", double(10), + src_csp.size() / atlas_omp_get_max_threads() > 50 ? Log::info() : blackhole); + atlas_omp_parallel_for (idx_t scell = 0; scell < src_csp.size(); ++scell) { + if ( atlas_omp_get_thread_num() == 0 ) { + ++progress; + stopwatch_src_already_in.start(); + } + bool already_in = src_already_in((std::get<0>(src_csp[scell]).centroid())); + if ( atlas_omp_get_thread_num() == 0 ) { stopwatch_src_already_in.stop(); - continue; } - stopwatch_src_already_in.stop(); - - const auto& s_csp = std::get<0>(src_csp[scell]); - const double s_csp_area = s_csp.area(); - double src_cover_area = 0.; - - stopwatch_kdtree_search.start(); - auto tgt_cells = kdt_search.closestPointsWithinRadius(s_csp.centroid(), s_csp.radius() + max_tgtcell_rad); - stopwatch_kdtree_search.stop(); - for (idx_t ttcell = 0; ttcell < tgt_cells.size(); ++ttcell) { - auto tcell = tgt_cells[ttcell].payload(); - const auto& t_csp = std::get<0>(tgt_csp[tcell]); - stopwatch_polygon_intersections.start(); - ConvexSphericalPolygon csp_i = s_csp.intersect(t_csp); - double csp_i_area = csp_i.area(); - stopwatch_polygon_intersections.stop(); - if (validate_) { - // check zero area intersections with inside_vertices - int pout; - if (inside_vertices(s_csp, t_csp, pout) > 2 && csp_i.area() < 3e-16) { - dump_intersection(s_csp, tgt_csp, tgt_cells); + if (not already_in) { + const auto& s_csp = std::get<0>(src_csp[scell]); + const double s_csp_area = s_csp.area(); + double src_cover_area = 0.; + + stopwatch_kdtree_search.start(); + auto tgt_cells = kdt_search.closestPointsWithinRadius(s_csp.centroid(), s_csp.radius() + max_tgtcell_rad); + stopwatch_kdtree_search.stop(); + for (idx_t ttcell = 0; ttcell < tgt_cells.size(); ++ttcell) { + auto tcell = tgt_cells[ttcell].payload(); + const auto& t_csp = std::get<0>(tgt_csp[tcell]); + if( atlas_omp_get_thread_num() == 0 ) { + stopwatch_polygon_intersections.start(); + } + ConvexSphericalPolygon csp_i = s_csp.intersect(t_csp, nullptr, pointsSameEPS); + double csp_i_area = csp_i.area(); + if( atlas_omp_get_thread_num() == 0 ) { + stopwatch_polygon_intersections.stop(); } - } - if (csp_i_area > 0.) { if (validate_) { - tgt_iparam[tcell].cell_idx.emplace_back(scell); - tgt_iparam[tcell].tgt_weights.emplace_back(csp_i_area); + int pout; + // TODO: this can be removed soon + if (inside_vertices(s_csp, t_csp, pout) > 2 && csp_i.area() < 3e-16) { + dump_intersection("Zero area intersections with inside_vertices", s_csp, tgt_csp, tgt_cells); + } + // TODO: assuming intersector search works fine, this should be move under "if (csp_i_area > 0)" + atlas_omp_critical { + tgt_iparam[tcell].cell_idx.emplace_back(scell); + tgt_iparam[tcell].tgt_weights.emplace_back(csp_i_area); + } } - src_iparam_[scell].cell_idx.emplace_back(tcell); - src_iparam_[scell].src_weights.emplace_back(csp_i_area); - double target_weight = csp_i_area / t_csp.area(); - src_iparam_[scell].tgt_weights.emplace_back(target_weight); - src_iparam_[scell].centroids.emplace_back(csp_i.centroid()); - src_cover_area += csp_i_area; - ATLAS_ASSERT(target_weight < 1.1); - ATLAS_ASSERT(csp_i_area / s_csp_area < 1.1); - } - } - const double src_cover_err = std::abs(s_csp_area - src_cover_area); - const double src_cover_err_percent = 100. * src_cover_err / s_csp_area; - if (src_cover_err_percent > 0.1 and std::get<1>(src_csp[scell]) == 0) { - // HACK: source cell at process boundary will not be covered by target cells, skip them - // TODO: mark these source cells beforehand and compute error in them among the processes - - if (validate_) { - if (mpi::size() == 1) { - Log::info() << "WARNING src cell covering error : " << src_cover_err_percent << "%\n"; - dump_intersection(s_csp, tgt_csp, tgt_cells); + if (csp_i_area > 0) { + src_iparam_[scell].cell_idx.emplace_back(tcell); + src_iparam_[scell].weights.emplace_back(csp_i_area); + double target_weight = csp_i_area / t_csp.area(); + src_iparam_[scell].tgt_weights.emplace_back(target_weight); // TODO: tgt_weights vector should be removed for the sake of highres + if (order_ == 2 or not matrix_free_ or not matrixAllocated()) { + src_iparam_[scell].centroids.emplace_back(csp_i.centroid()); + } + src_cover_area += csp_i_area; + + if (validate_) { + // this check is concerned with accuracy of polygon intersections + if (target_weight > 1.1) { + dump_intersection("Intersection larger than target", s_csp, tgt_csp, tgt_cells); + } + if (csp_i_area / s_csp_area > 1.1) { + dump_intersection("Intersection larger than source", s_csp, tgt_csp, tgt_cells); + } + } } } - area_coverage[TOTAL_SRC] += src_cover_err; - area_coverage[MAX_SRC] = std::max(area_coverage[MAX_SRC], src_cover_err); - } - if (src_iparam_[scell].cell_idx.size() == 0) { - num_pol[SRC_NONINTERSECT]++; - } - if (normalise_intersections_ && src_cover_err_percent < 1.) { - double wfactor = s_csp.area() / (src_cover_area > 0. ? src_cover_area : 1.); - for (idx_t i = 0; i < src_iparam_[scell].src_weights.size(); i++) { - src_iparam_[scell].src_weights[i] *= wfactor; - src_iparam_[scell].tgt_weights[i] *= wfactor; + const double src_cover_err = std::abs(s_csp_area - src_cover_area); + const double src_cover_err_percent = 100. * src_cover_err / s_csp_area; + if (src_cover_err_percent > 0.1 and std::get<1>(src_csp[scell]) == 0) { + // NOTE: source cell at process boundary will not be covered by target cells, skip them + // TODO: mark these source cells beforehand and compute error in them among the processes + if (validate_ and mpi::size() == 1) { + dump_intersection("Source cell not exactly covered", s_csp, tgt_csp, tgt_cells); + if (statistics_intersection_) { + atlas_omp_critical{ + area_coverage[TOTAL_SRC] += src_cover_err; + area_coverage[MAX_SRC] = std::max(area_coverage[MAX_SRC], src_cover_err); + } + } + } } - } - num_pol[SRC_TGT_INTERSECT] += src_iparam_[scell].src_weights.size(); + if (src_iparam_[scell].cell_idx.size() == 0 and statistics_intersection_) { + atlas_omp_critical{ + num_pol[SRC_NONINTERSECT]++; + } + } + if (normalise_intersections_ && src_cover_err_percent < 1.) { + double wfactor = s_csp.area() / (src_cover_area > 0. ? src_cover_area : 1.); + for (idx_t i = 0; i < src_iparam_[scell].weights.size(); i++) { + src_iparam_[scell].weights[i] *= wfactor; + src_iparam_[scell].tgt_weights[i] *= wfactor; + } + } + if (statistics_intersection_) { + atlas_omp_critical{ + num_pol[SRC_TGT_INTERSECT] += src_iparam_[scell].weights.size(); + } + } + } // already in } timings.polygon_intersections = stopwatch_polygon_intersections.elapsed(); timings.target_kdtree_search = stopwatch_kdtree_search.elapsed(); @@ -888,48 +1099,151 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg num_pol[TGT] = tgt_csp.size(); ATLAS_TRACE_MPI(ALLREDUCE) { mpi::comm().allReduceInPlace(num_pol.data(), num_pol.size(), eckit::mpi::sum()); - mpi::comm().allReduceInPlace(area_coverage.data(), area_coverage.size(), eckit::mpi::max()); } remap_stat_.counts[Statistics::Counts::INT_PLG] = num_pol[SRC_TGT_INTERSECT]; remap_stat_.counts[Statistics::Counts::UNCVR_SRC] = num_pol[SRC_NONINTERSECT]; - remap_stat_.errors[Statistics::Errors::GEO_L1] = area_coverage[TOTAL_SRC]; - remap_stat_.errors[Statistics::Errors::GEO_LINF] = area_coverage[MAX_SRC]; - - double geo_err_l1 = 0.; - double geo_err_linf = 0.; - for (idx_t scell = 0; scell < src_csp.size(); ++scell) { - const int cell_flag = std::get<1>(src_csp[scell]); - if (cell_flag == -1 or cell_flag > 0) { - // skip periodic & halo cells - continue; + + const std::string polygon_intersection_folder = "polygon_intersection/"; + if (validate_ && mpi::rank() == 0) { + if (mkdir(polygon_intersection_folder.c_str(), 0777) != 0) { + Log::info() << "WARNING Polygon intersection relevant information in is the folder \e[1mpolygon_intersection\e[0m." << std::endl; } - double diff_cell = std::get<0>(src_csp[scell]).area(); - for (idx_t icell = 0; icell < src_iparam_[scell].src_weights.size(); ++icell) { - diff_cell -= src_iparam_[scell].src_weights[icell]; + else { + Log::info() << "WARNING Could not create the folder \e[1mpolygon_intersection\e[0m." << std::endl; } - geo_err_l1 += std::abs(diff_cell); - geo_err_linf = std::max(geo_err_linf, std::abs(diff_cell)); } - ATLAS_TRACE_MPI(ALLREDUCE) { - mpi::comm().allReduceInPlace(geo_err_l1, eckit::mpi::sum()); - mpi::comm().allReduceInPlace(geo_err_linf, eckit::mpi::max()); - } - remap_stat_.errors[Statistics::Errors::GEO_L1] = geo_err_l1 / unit_sphere_area(); - remap_stat_.errors[Statistics::Errors::GEO_LINF] = geo_err_linf; if (validate_) { + double geo_err_l1 = 0.; + double geo_err_linf = -1.; + for (idx_t scell = 0; scell < src_csp.size(); ++scell) { + if (std::get<1>(src_csp[scell]) != 0 ) { + // skip periodic & halo cells + continue; + } + double diff_cell = std::get<0>(src_csp[scell]).area(); + for (idx_t icell = 0; icell < src_iparam_[scell].weights.size(); ++icell) { + diff_cell -= src_iparam_[scell].weights[icell]; + } + geo_err_l1 += std::abs(diff_cell); + geo_err_linf = std::max(geo_err_linf, std::abs(diff_cell)); + } + ATLAS_TRACE_MPI(ALLREDUCE) { + mpi::comm().allReduceInPlace(geo_err_l1, eckit::mpi::sum()); + mpi::comm().allReduceInPlace(geo_err_linf, eckit::mpi::max()); + } + if (mpi::size() == 1) { + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_L1] = geo_err_l1 / unit_sphere_area(); + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_LINF] = geo_err_linf; + } + else { + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_L1] = -1111.; + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_LINF] = -1111.; + } + + std::fstream polygon_intersection_info("polygon_intersection", std::ios::out); + + geo_err_l1 = 0.; + geo_err_linf = -1.; for (idx_t tcell = 0; tcell < tgt_csp.size(); ++tcell) { + if (std::get<1>(tgt_csp[tcell]) != 0) { + // skip periodic & halo cells + continue; + } const auto& t_csp = std::get<0>(tgt_csp[tcell]); + + double tgt_cover_area = 0.; const auto& tiparam = tgt_iparam[tcell]; +#if 0 + // dump polygons in json format + if( tcell == 27609 ) { + dump_polygons_to_json(t_csp, src_csp, tiparam.cell_idx, 1.e-16); + } +#endif for (idx_t icell = 0; icell < tiparam.cell_idx.size(); ++icell) { tgt_cover_area += tiparam.tgt_weights[icell]; } - const double tgt_cover_err_percent = 100. * std::abs(t_csp.area() - tgt_cover_area) / t_csp.area(); - if (tgt_cover_err_percent > 0.1 and std::get<1>(tgt_csp[tcell]) == 0) { - Log::info() << "WARNING tgt cell covering error : " << tgt_cover_err_percent << " %\n"; - dump_intersection(t_csp, src_csp, tiparam.cell_idx); + /* + // TODO: normalise to target cell + double normm = t_csp.area() / (tgt_cover_area > 0. ? tgt_cover_area : t_csp.area()); + for (idx_t icell = 0; icell < tiparam.cell_idx.size(); ++icell) { + idx_t scell = tiparam.cell_idx[icell]; + auto siparam = src_iparam_[scell]; + size_t tgt_intersectors = siparam.cell_idx.size(); + for (idx_t sicell = 0; sicell < tgt_intersectors; sicell++ ) { + if (siparam.cell_idx[icell] == tcell) {; + siparam.weights[icell] *= normm; + siparam.tgt_weights[icell] *= normm; + } + } } + */ + double diff_cell = tgt_cover_area - t_csp.area(); + geo_err_l1 += std::abs(diff_cell); + geo_err_linf = std::max(geo_err_linf, std::abs(diff_cell)); + const double tgt_cover_err = 100. * diff_cell / t_csp.area(); + if (worst_tgt_overcover.second < tgt_cover_err) { + worst_tgt_overcover.second = tgt_cover_err;; + worst_tgt_overcover.first = tcell; + } + if (worst_tgt_undercover.second > tgt_cover_err) { + worst_tgt_undercover.second = tgt_cover_err;; + worst_tgt_undercover.first = tcell; + } + if (std::abs(tgt_cover_err) > 0.5) { + PointLonLat centre_ll; + eckit::geometry::Sphere::convertCartesianToSpherical(1., t_csp.centroid(), centre_ll); + polygon_intersection_info << "WARNING tgt cell " << tcell << " over-covering: \e[1m" << tgt_cover_err << "\e[0m %, cell-centre: " + << centre_ll <<"\n"; + polygon_intersection_info << "source indices: " << tiparam.cell_idx << std::endl; + dump_intersection("Target cell not exaclty covered", t_csp, src_csp, tiparam.cell_idx); + //dump_polygons_to_json(t_csp, src_csp, tiparam.cell_idx, "bad_polygon", 1.e-16); + } + } + ATLAS_TRACE_MPI(ALLREDUCE) { + mpi::comm().allReduceInPlace(geo_err_l1, eckit::mpi::sum()); + mpi::comm().allReduceInPlace(geo_err_linf, eckit::mpi::max()); + } + remap_stat_.errors[Statistics::Errors::TGT_INTERSECTPLG_L1] = geo_err_l1 / unit_sphere_area(); + remap_stat_.errors[Statistics::Errors::TGT_INTERSECTPLG_LINF] = geo_err_linf; + } + else { + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_L1] = -1111.; + remap_stat_.errors[Statistics::Errors::SRC_INTERSECTPLG_LINF] = -1111.; + remap_stat_.errors[Statistics::Errors::TGT_INTERSECTPLG_L1] = -1111.; + remap_stat_.errors[Statistics::Errors::TGT_INTERSECTPLG_LINF] = -1111.; + } + + if (validate_) { + std::vector first(mpi::comm().size()); + std::vector second(mpi::comm().size()); + ATLAS_TRACE_MPI(ALLGATHER) { + mpi::comm().allGather(worst_tgt_overcover.first, first.begin(), first.end()); + mpi::comm().allGather(worst_tgt_overcover.second, second.begin(), second.end()); + } + auto max_over = std::max_element(second.begin(), second.end()); + auto rank_over = std::distance(second.begin(), max_over); + Log::info() << "WARNING The worst target polygon over-coveraging: \e[1m" + << *max_over + << "\e[0m %. For details, check the file: worst_target_cell_overcover.info " << std::endl; + if (rank_over == mpi::rank()) { + auto tcell = worst_tgt_overcover.first; + dump_polygons_to_json(std::get<0>(tgt_csp[tcell]), pointsSameEPS, src_csp, tgt_iparam[tcell].cell_idx, polygon_intersection_folder, "worst_target_cell_overcover"); + } + ATLAS_TRACE_MPI(ALLGATHER) { + mpi::comm().allGather(worst_tgt_undercover.first, first.begin(), first.end()); + mpi::comm().allGather(worst_tgt_undercover.second, second.begin(), second.end()); + } + auto min_under = std::min_element(second.begin(), second.end()); + auto rank_under = std::distance(second.begin(), min_under); + Log::info() << "WARNING The worst target polygon under-coveraging: \e[1m" + << *min_under + << "\e[0m %. For details, check the file: worst_target_cell_undercover.info " << std::endl; + + if (rank_under == mpi::rank()) { + auto tcell = worst_tgt_undercover.first; + dump_polygons_to_json(std::get<0>(tgt_csp[tcell]), pointsSameEPS, src_csp, tgt_iparam[tcell].cell_idx, polygon_intersection_folder, "worst_target_cell_undercover"); } } } @@ -943,7 +1257,7 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_1 // determine the size of array of triplets used to define the sparse matrix if (src_cell_data_) { for (idx_t scell = 0; scell < n_spoints_; ++scell) { - triplets_size += src_iparam_[scell].centroids.size(); + triplets_size += src_iparam_[scell].cell_idx.size(); } } else { @@ -962,7 +1276,7 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_1 if (src_cell_data_ && tgt_cell_data_) { for (idx_t scell = 0; scell < n_spoints_; ++scell) { const auto& iparam = src_iparam_[scell]; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; triplets.emplace_back(tcell, scell, iparam.tgt_weights[icell]); } @@ -974,8 +1288,9 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_1 for (idx_t isubcell = 0; isubcell < src_node2csp_[snode].size(); ++isubcell) { const idx_t subcell = src_node2csp_[snode][isubcell]; const auto& iparam = src_iparam_[subcell]; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; + ATLAS_ASSERT(tcell < n_tpoints_); triplets.emplace_back(tcell, snode, iparam.tgt_weights[icell]); } } @@ -985,11 +1300,15 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_1 auto& tgt_csp2node_ = data_->tgt_csp2node_; for (idx_t scell = 0; scell < n_spoints_; ++scell) { const auto& iparam = src_iparam_[scell]; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; + if (tnode >= n_tpoints_) { + Log::info() << "tnode, n_tpoints = " << tnode << ", " << n_tpoints_ << std::endl; + ATLAS_ASSERT(false); + } double inv_node_weight = (tgt_areas_v[tnode] > 0. ? 1. / tgt_areas_v[tnode] : 0.); - triplets.emplace_back(tnode, scell, iparam.src_weights[icell] * inv_node_weight); + triplets.emplace_back(tnode, scell, iparam.weights[icell] * inv_node_weight); } } } @@ -1000,16 +1319,42 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_1 for (idx_t isubcell = 0; isubcell < src_node2csp_[snode].size(); ++isubcell) { const idx_t subcell = src_node2csp_[snode][isubcell]; const auto& iparam = src_iparam_[subcell]; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; + ATLAS_ASSERT(tnode < n_tpoints_); double inv_node_weight = (tgt_areas_v[tnode] > 0. ? 1. / tgt_areas_v[tnode] : 0.); - triplets.emplace_back(tnode, snode, iparam.src_weights[icell] * inv_node_weight); + triplets.emplace_back(tnode, snode, iparam.weights[icell] * inv_node_weight); + if( tnode >= n_tpoints_) { + Log::info() << tnode << " = tnode, " << n_tpoints_ << " = n_tpoints\n";; + ATLAS_ASSERT(false); + } } } } } sort_and_accumulate_triplets(triplets); // Very expensive!!! (90% of this routine). We need to avoid it + +// if (validate_) { +// std::vector weight_sum(n_tpoints_); +// for( auto& triplet : triplets ) { +// weight_sum[triplet.row()] += triplet.value(); +// } +// if (order_ == 1) { +// // first order should not give overshoots +// double eps = 1e4 * std::numeric_limits::epsilon(); +// for( auto& triplet : triplets ) { +// if (triplet.value() > 1. + eps or triplet.value() < -eps) { +// Log::info() << "target point " << triplet.row() << " weight: " << triplet.value() << std::endl; +// } +// } +// } +// for( size_t row=0; row < n_tpoints_; ++row ) { +// if (std::abs(weight_sum[row] - 1.) > 1e-11) { +// Log::info() << "target weight in row " << row << " differs from 1 by " << std::abs(weight_sum[row] - 1.) << std::endl; +// } +// } +// } return Matrix(n_tpoints_, n_spoints_, triplets); } @@ -1026,28 +1371,30 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 const auto src_halo = array::make_view(src_mesh_.cells().halo()); for (idx_t scell = 0; scell < n_spoints_; ++scell) { const auto nb_cells = get_cell_neighbours(src_mesh_, scell); - triplets_size += (2 * nb_cells.size() + 1) * src_iparam_[scell].centroids.size(); + triplets_size += (2 * nb_cells.size() + 1) * src_iparam_[scell].cell_idx.size(); } triplets.reserve(triplets_size); for (idx_t scell = 0; scell < n_spoints_; ++scell) { - const auto nb_cells = get_cell_neighbours(src_mesh_, scell); const auto& iparam = src_iparam_[scell]; - if (iparam.centroids.size() == 0 && not src_halo(scell)) { + if (iparam.cell_idx.size() == 0 && not src_halo(scell)) { continue; } /* // better conservation after Kritsikis et al. (2017) + // NOTE: ommited here at cost of conservation due to more involved implementation in parallel + // TEMP: use barycentre computed based on the source polygon's vertices, rather then + // the numerical barycentre based on barycentres of the intersections with target cells. + // TODO: for a given source cell, collect the centroids of all its intersections with target cells + // to compute the numerical barycentre of the cell bases on intersection. PointXYZ Cs = {0., 0., 0.}; - for ( idx_t icell = 0; icell < iparam.centroids.size(); ++icell ) { - Cs = Cs + PointXYZ::mul( iparam.centroids[icell], iparam.src_weights[icell] ); + for ( idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell ) { + Cs = Cs + PointXYZ::mul( iparam.centroids[icell], iparam.weights[icell] ); } - const double Cs_norm = PointXYZ::norm( Cs ); - ATLAS_ASSERT( Cs_norm > 0. ); - Cs = PointXYZ::div( Cs, Cs_norm ); */ const PointXYZ& Cs = src_points_[scell]; // compute gradient from cells double dual_area_inv = 0.; std::vector Rsj; + const auto nb_cells = get_cell_neighbours(src_mesh_, scell); Rsj.resize(nb_cells.size()); for (idx_t j = 0; j < nb_cells.size(); ++j) { idx_t nj = next_index(j, nb_cells.size()); @@ -1071,15 +1418,15 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 } // assemble the matrix std::vector Aik; - Aik.resize(iparam.centroids.size()); - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + Aik.resize(iparam.cell_idx.size()); + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { const PointXYZ& Csk = iparam.centroids[icell]; const PointXYZ Csk_Cs = Csk - Cs; Aik[icell] = Csk_Cs - PointXYZ::mul(Cs, PointXYZ::dot(Cs, Csk_Cs)); Aik[icell] = PointXYZ::mul(Aik[icell], iparam.tgt_weights[icell] * dual_area_inv); } if (tgt_cell_data_) { - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { const idx_t tcell = iparam.cell_idx[icell]; for (idx_t j = 0; j < nb_cells.size(); ++j) { idx_t nj = next_index(j, nb_cells.size()); @@ -1093,11 +1440,11 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 } else { auto& tgt_csp2node_ = data_->tgt_csp2node_; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; double inv_node_weight = (tgt_areas_v[tnode] > 0.) ? 1. / tgt_areas_v[tnode] : 0.; - double csp2node_coef = iparam.src_weights[icell] / iparam.tgt_weights[icell] * inv_node_weight; + double csp2node_coef = iparam.weights[icell] / iparam.tgt_weights[icell] * inv_node_weight; for (idx_t j = 0; j < nb_cells.size(); ++j) { idx_t nj = next_index(j, nb_cells.size()); idx_t sj = nb_cells[j]; @@ -1118,7 +1465,7 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 const auto nb_nodes = get_node_neighbours(src_mesh_, snode); for (idx_t isubcell = 0; isubcell < src_node2csp_[snode].size(); ++isubcell) { idx_t subcell = src_node2csp_[snode][isubcell]; - triplets_size += (2 * nb_nodes.size() + 1) * src_iparam_[subcell].centroids.size(); + triplets_size += (2 * nb_nodes.size() + 1) * src_iparam_[subcell].cell_idx.size(); } } triplets.reserve(triplets_size); @@ -1130,14 +1477,14 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 for ( idx_t isubcell = 0; isubcell < src_node2csp_[snode].size(); ++isubcell ) { idx_t subcell = src_node2csp_[snode][isubcell]; const auto& iparam = src_iparam_[subcell]; - for ( idx_t icell = 0; icell < iparam.centroids.size(); ++icell ) { - Cs = Cs + PointXYZ::mul( iparam.centroids[icell], iparam.src_weights[icell] ); + for ( idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell ) { + Cs = Cs + PointXYZ::mul( iparam.centroids[icell], iparam.weights[icell] ); } + const double Cs_norm = PointXYZ::norm( Cs ); + ATLAS_ASSERT( Cs_norm > 0. ); + Cs = PointXYZ::div( Cs, Cs_norm ); } - const double Cs_norm = PointXYZ::norm( Cs ); - ATLAS_ASSERT( Cs_norm > 0. ); - Cs = PointXYZ::div( Cs, Cs_norm ); -*/ + */ const PointXYZ& Cs = src_points_[snode]; // compute gradient from nodes double dual_area_inv = 0.; @@ -1168,19 +1515,19 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 for (idx_t isubcell = 0; isubcell < src_node2csp_[snode].size(); ++isubcell) { idx_t subcell = src_node2csp_[snode][isubcell]; const auto& iparam = src_iparam_[subcell]; - if (iparam.centroids.size() == 0) { + if (iparam.cell_idx.size() == 0) { continue; } std::vector Aik; - Aik.resize(iparam.centroids.size()); - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + Aik.resize(iparam.cell_idx.size()); + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { const PointXYZ& Csk = iparam.centroids[icell]; const PointXYZ Csk_Cs = Csk - Cs; Aik[icell] = Csk_Cs - PointXYZ::mul(Cs, PointXYZ::dot(Cs, Csk_Cs)); Aik[icell] = PointXYZ::mul(Aik[icell], iparam.tgt_weights[icell] * dual_area_inv); } if (tgt_cell_data_) { - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { const idx_t tcell = iparam.cell_idx[icell]; for (idx_t j = 0; j < nb_nodes.size(); ++j) { idx_t nj = next_index(j, nb_nodes.size()); @@ -1194,11 +1541,11 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 } else { auto& tgt_csp2node_ = data_->tgt_csp2node_; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; double inv_node_weight = (tgt_areas_v[tnode] > 1e-15) ? 1. / tgt_areas_v[tnode] : 0.; - double csp2node_coef = iparam.src_weights[icell] / iparam.tgt_weights[icell] * inv_node_weight; + double csp2node_coef = iparam.weights[icell] / iparam.tgt_weights[icell] * inv_node_weight; for (idx_t j = 0; j < nb_nodes.size(); ++j) { idx_t nj = next_index(j, nb_nodes.size()); idx_t sj = nb_nodes[j]; @@ -1218,6 +1565,16 @@ eckit::linalg::SparseMatrix ConservativeSphericalPolygonInterpolation::compute_2 return Matrix(n_tpoints_, n_spoints_, triplets); } +void ConservativeSphericalPolygonInterpolation::do_execute(const FieldSet& src_fields, FieldSet& tgt_fields, + Metadata& metadata) const { + std::vector md_array; + md_array.resize( src_fields.size() ); + for (int i = 0; i < src_fields.size(); i++) { // TODO: memory-wise should we openmp this? + do_execute(src_fields[i], tgt_fields[i], md_array[i]); + } + metadata = md_array[0]; // TODO: reduce metadata of a set of variables to a single metadata of a variable? +} + void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_field, Field& tgt_field, Metadata& metadata) const { ATLAS_TRACE("ConservativeMethod::do_execute()"); @@ -1245,8 +1602,8 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel } for (idx_t scell = 0; scell < src_vals.size(); ++scell) { const auto& iparam = src_iparam_[scell]; - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { - tgt_vals(iparam.cell_idx[icell]) += iparam.src_weights[icell] * src_vals(scell); + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { + tgt_vals(iparam.cell_idx[icell]) += iparam.weights[icell] * src_vals(scell); } } for (idx_t tcell = 0; tcell < tgt_vals.size(); ++tcell) { @@ -1260,16 +1617,13 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel } else if (order_ == 2) { if (matrix_free_) { - ATLAS_TRACE("matrix_free_order_2"); - const auto& src_iparam_ = data_->src_iparam_; - const auto& tgt_areas_v = data_->tgt_areas_; - if (not src_cell_data_ or not tgt_cell_data_) { ATLAS_NOTIMPLEMENTED; } - + ATLAS_TRACE("matrix_free_order_2"); + const auto& src_iparam_ = data_->src_iparam_; + const auto& tgt_areas_v = data_->tgt_areas_; auto& src_points_ = data_->src_points_; - const auto src_vals = array::make_view(src_field); auto tgt_vals = array::make_view(tgt_field); const auto halo = array::make_view(src_mesh_.cells().halo()); @@ -1277,55 +1631,42 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel tgt_vals(tcell) = 0.; } for (idx_t scell = 0; scell < src_vals.size(); ++scell) { - if (halo(scell)) { + const auto& iparam = src_iparam_[scell]; + if (iparam.cell_idx.size() == 0 or halo(scell)) { continue; } - const auto& iparam = src_iparam_[scell]; - const PointXYZ& P = src_points_[scell]; + const PointXYZ& Cs = src_points_[scell]; PointXYZ grad = {0., 0., 0.}; PointXYZ src_barycenter = {0., 0., 0.}; - auto src_neighbour_cells = get_cell_neighbours(src_mesh_, scell); - double dual_area = 0.; - for (idx_t nb_id = 0; nb_id < src_neighbour_cells.size(); ++nb_id) { - idx_t nnb_id = next_index(nb_id, src_neighbour_cells.size()); - idx_t ncell = src_neighbour_cells[nb_id]; - idx_t nncell = src_neighbour_cells[nnb_id]; - const auto& Pn = src_points_[ncell]; - const auto& Pnn = src_points_[nncell]; - if (ncell != scell && nncell != scell) { - double val = 0.5 * (src_vals(ncell) + src_vals(nncell)) - src_vals(scell); - auto csp = ConvexSphericalPolygon({Pn, Pnn, P}); - if (csp.area() < std::numeric_limits::epsilon()) { - csp = ConvexSphericalPolygon({Pn, P, Pnn}); - } - auto NsNsj = ConvexSphericalPolygon::GreatCircleSegment(P, Pn); - val *= (NsNsj.inLeftHemisphere(Pnn, -1e-16) ? -1 : 1); - dual_area += std::abs(csp.area()); - grad = grad + PointXYZ::mul(PointXYZ::cross(Pn, Pnn), val); - } - else if (ncell != scell) { - ATLAS_NOTIMPLEMENTED; - //double val = 0.5 * ( src_vals( ncell ) - src_vals( scell ) ); - //grad = grad + PointXYZ::mul( PointXYZ::cross( Pn, P ), val ); + auto nb_cells = get_cell_neighbours(src_mesh_, scell); + double dual_area_inv = 0.; + for (idx_t j = 0; j < nb_cells.size(); ++j) { + idx_t nj = next_index(j, nb_cells.size()); + idx_t sj = nb_cells[j]; + idx_t nsj = nb_cells[nj]; + const auto& Csj = src_points_[sj]; + const auto& Cnsj = src_points_[nsj]; + double val = 0.5 * (src_vals(nj) + src_vals(nsj)) - src_vals(j); + if (ConvexSphericalPolygon::GreatCircleSegment(Cs, Csj).inLeftHemisphere(Cnsj, -1e-16)) { + dual_area_inv += ConvexSphericalPolygon({Cs, Csj, Cnsj}).area(); } - else if (nncell != scell) { - ATLAS_NOTIMPLEMENTED; - //double val = 0.5 * ( src_vals( nncell ) - src_vals( scell ) ); - //grad = grad + PointXYZ::mul( PointXYZ::cross( P, Pnn ), val ); + else { + //val *= -1; + dual_area_inv += ConvexSphericalPolygon({Cs, Cnsj, Csj}).area(); } + grad = grad + PointXYZ::mul(PointXYZ::cross(Csj, Cnsj), val); } - if (dual_area > std::numeric_limits::epsilon()) { - grad = PointXYZ::div(grad, dual_area); - } - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { - src_barycenter = src_barycenter + PointXYZ::mul(iparam.centroids[icell], iparam.src_weights[icell]); + dual_area_inv = ((dual_area_inv > 0.) ? 1. / dual_area_inv : 0.); + grad = PointXYZ::mul(grad, dual_area_inv); + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { + src_barycenter = src_barycenter + PointXYZ::mul(iparam.centroids[icell], iparam.weights[icell]); } src_barycenter = PointXYZ::div(src_barycenter, PointXYZ::norm(src_barycenter)); grad = grad - PointXYZ::mul(src_barycenter, PointXYZ::dot(grad, src_barycenter)); ATLAS_ASSERT(std::abs(PointXYZ::dot(grad, src_barycenter)) < 1e-14); - for (idx_t icell = 0; icell < iparam.centroids.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.cell_idx.size(); ++icell) { tgt_vals(iparam.cell_idx[icell]) += - iparam.src_weights[icell] * + iparam.weights[icell] * (src_vals(scell) + PointXYZ::dot(grad, iparam.centroids[icell] - src_barycenter)); } } @@ -1340,6 +1681,10 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel } stopwatch.stop(); + { + ATLAS_TRACE("halo exchange target"); + tgt_field.haloExchange(); + } auto remap_stat = remap_stat_; if (statistics_conservation_) { @@ -1369,7 +1714,7 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel else { auto& src_node2csp_ = data_->src_node2csp_; for (idx_t spt = 0; spt < src_vals.size(); ++spt) { - if (src_node_ghost(spt) or src_areas_v[spt] < 1e-14) { + if (src_node_halo(spt) or src_node_ghost(spt)) { continue; } err_remap_cons += src_vals(spt) * src_areas_v[spt]; @@ -1386,24 +1731,28 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel } else { for (idx_t tpt = 0; tpt < tgt_vals.size(); ++tpt) { - if (tgt_node_ghost(tpt)) { + if (tgt_node_halo(tpt) or tgt_node_ghost(tpt)) { continue; } err_remap_cons -= tgt_vals(tpt) * tgt_areas_v[tpt]; } } ATLAS_TRACE_MPI(ALLREDUCE) { mpi::comm().allReduceInPlace(&err_remap_cons, 1, eckit::mpi::sum()); } - remap_stat.errors[Statistics::Errors::REMAP_CONS] = std::sqrt(std::abs(err_remap_cons) / unit_sphere_area()); + remap_stat.errors[Statistics::Errors::REMAP_CONS] = err_remap_cons / unit_sphere_area(); metadata.set("conservation_error", remap_stat.errors[Statistics::Errors::REMAP_CONS]); } if (statistics_intersection_) { - metadata.set("polygons.source", remap_stat.counts[Statistics::SRC_PLG]); - metadata.set("polygons.target", remap_stat.counts[Statistics::TGT_PLG]); - metadata.set("polygons.intersections", remap_stat.counts[Statistics::INT_PLG]); + metadata.set("polygons.source", remap_stat.counts[Statistics::Counts::SRC_PLG]); + metadata.set("polygons.target", remap_stat.counts[Statistics::Counts::TGT_PLG]); + metadata.set("polygons.intersections", remap_stat.counts[Statistics::Counts::INT_PLG]); metadata.set("polygons.uncovered_source", remap_stat.counts[Statistics::UNCVR_SRC]); - metadata.set("source_area_error.L1", remap_stat.errors[Statistics::Errors::GEO_L1]); - metadata.set("source_area_error.Linf", remap_stat.errors[Statistics::Errors::GEO_LINF]); + if (validate_) { + metadata.set("source_area_error.L1", remap_stat.errors[Statistics::Errors::SRC_INTERSECTPLG_L1]); + metadata.set("source_area_error.Linf", remap_stat.errors[Statistics::Errors::SRC_INTERSECTPLG_LINF]); + metadata.set("target_area_error.L1", remap_stat.errors[Statistics::Errors::TGT_INTERSECTPLG_L1]); + metadata.set("target_area_error.Linf", remap_stat.errors[Statistics::Errors::TGT_INTERSECTPLG_LINF]); + } } if (statistics_intersection_ || statistics_conservation_) { @@ -1430,15 +1779,16 @@ void ConservativeSphericalPolygonInterpolation::do_execute(const Field& src_fiel metadata.set("memory.src_node2csp", memory_of(data_->src_node2csp_)); metadata.set("memory.tgt_node2csp", memory_of(data_->tgt_node2csp_)); metadata.set("memory.src_iparam", memory_of(data_->src_iparam_)); - - tgt_field.set_dirty(); } void ConservativeSphericalPolygonInterpolation::print(std::ostream& out) const { out << "ConservativeMethod{"; out << "order:" << order_; - out << ", source:" << (src_cell_data_ ? "cells" : "nodes"); - out << ", target:" << (tgt_cell_data_ ? "cells" : "nodes"); + int halo = 0; + src_mesh_.metadata().get("halo", halo); + out << ", source:" << (src_cell_data_ ? "cells(" : "nodes(") << src_mesh_.grid().name() << ",halo=" << halo << ")"; + tgt_mesh_.metadata().get("halo", halo); + out << ", target:" << (tgt_cell_data_ ? "cells(" : "nodes(") << tgt_mesh_.grid().name() << ",halo=" << halo << ")"; out << ", normalise_intersections:" << normalise_intersections_; out << ", matrix_free:" << matrix_free_; out << ", statistics.intersection:" << statistics_intersection_; @@ -1506,7 +1856,7 @@ void ConservativeSphericalPolygonInterpolation::setup_stat() const { remap_stat_.tgt_area_sum = src_tgt_sums[1]; geo_create_err = std::abs(src_tgt_sums[0] - src_tgt_sums[1]) / unit_sphere_area(); - remap_stat_.errors[Statistics::Errors::GEO_DIFF] = geo_create_err; + remap_stat_.errors[Statistics::Errors::SRCTGT_INTERSECTPLG_DIFF] = geo_create_err; } Field ConservativeSphericalPolygonInterpolation::Statistics::diff(const Interpolation& interpolation, @@ -1543,29 +1893,34 @@ Field ConservativeSphericalPolygonInterpolation::Statistics::diff(const Interpol double diff = src_vals(spt) * src_areas_v[spt]; const auto& iparam = src_iparam_[spt]; if (tgt_cell_data_) { - for (idx_t icell = 0; icell < iparam.src_weights.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.weights.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; if (tgt_cell_halo(tcell) < 1) { - diff -= tgt_vals(iparam.cell_idx[icell]) * iparam.src_weights[icell]; + diff -= tgt_vals(tcell) * iparam.weights[icell]; } } } else { - for (idx_t icell = 0; icell < iparam.src_weights.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.weights.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; - if (tgt_node_halo(tnode) < 1) { - diff -= tgt_vals(tnode) * iparam.src_weights[icell]; + if (not tgt_node_ghost(tnode)) { + diff -= tgt_vals(tnode) * iparam.weights[icell]; } } } - diff_vals(spt) = std::abs(diff) / src_areas_v[spt]; + if (src_areas_v[spt] > 0.) { + diff_vals(spt) = diff / src_areas_v[spt]; + } + else { + Log::info() << " at cell " << spt << " cell-area: " << src_areas_v[spt] << std::endl; + diff_vals(spt) = std::numeric_limits::max(); + } } } else { for (idx_t spt = 0; spt < src_vals.size(); ++spt) { - if (src_node_ghost(spt) or src_areas_v[spt] < 1e-14) { - diff_vals(spt) = 0.; + if (src_node_ghost(spt) or src_node_halo(spt) != 0) { continue; } double diff = src_vals(spt) * src_areas_v[spt]; @@ -1573,38 +1928,50 @@ Field ConservativeSphericalPolygonInterpolation::Statistics::diff(const Interpol for (idx_t subcell = 0; subcell < node2csp.size(); ++subcell) { const auto& iparam = src_iparam_[node2csp[subcell]]; if (tgt_cell_data_) { - for (idx_t icell = 0; icell < iparam.src_weights.size(); ++icell) { - diff -= tgt_vals(iparam.cell_idx[icell]) * iparam.src_weights[icell]; + for (idx_t icell = 0; icell < iparam.weights.size(); ++icell) { + idx_t tcell = iparam.cell_idx[icell]; + if (tgt_cell_halo(tcell) < 1) { + diff -= tgt_vals(tcell) * iparam.weights[icell]; + } } } else { - for (idx_t icell = 0; icell < iparam.src_weights.size(); ++icell) { + for (idx_t icell = 0; icell < iparam.weights.size(); ++icell) { idx_t tcell = iparam.cell_idx[icell]; idx_t tnode = tgt_csp2node_[tcell]; - diff -= tgt_vals(tnode) * iparam.src_weights[icell]; + if (not tgt_node_ghost(tnode)) { + diff -= tgt_vals(tnode) * iparam.weights[icell]; + } } } } - diff_vals(spt) = std::abs(diff) / src_areas_v[spt]; + if (src_areas_v[spt] > 0.) { + diff_vals(spt) = diff / src_areas_v[spt]; + } + else { + diff_vals(spt) = std::numeric_limits::max(); + Log::info() << " at cell " << spt << " cell-area: " << src_areas_v[spt] << std::endl; + } } } return diff; } -void ConservativeSphericalPolygonInterpolation::Statistics::accuracy(const Interpolation& interpolation, - const Field target, - std::function func) { +ConservativeSphericalPolygonInterpolation::Metadata ConservativeSphericalPolygonInterpolation::Statistics::accuracy(const Interpolation& interpolation, + const Field target, + std::function func) { auto tgt_vals = array::make_view(target); auto cachable_data_ = ConservativeSphericalPolygonInterpolation::Cache(interpolation).get(); auto tgt_mesh_ = extract_mesh(cachable_data_->src_fs_); auto tgt_cell_data_ = extract_mesh(cachable_data_->tgt_fs_); const auto tgt_cell_halo = array::make_view(tgt_mesh_.cells().halo()); const auto tgt_node_ghost = array::make_view(tgt_mesh_.nodes().ghost()); + const auto tgt_node_halo = array::make_view(tgt_mesh_.nodes().halo()); const auto& tgt_areas_v = cachable_data_->tgt_areas_; + auto& tgt_points_ = cachable_data_->tgt_points_; double err_remap_l2 = 0.; double err_remap_linf = 0.; - auto& tgt_points_ = cachable_data_->tgt_points_; if (tgt_cell_data_) { size_t ncells = std::min(tgt_vals.size(), tgt_mesh_.cells().size()); for (idx_t tpt = 0; tpt < ncells; ++tpt) { @@ -1623,7 +1990,7 @@ void ConservativeSphericalPolygonInterpolation::Statistics::accuracy(const Inter else { size_t nnodes = std::min(tgt_vals.size(), tgt_mesh_.nodes().size()); for (idx_t tpt = 0; tpt < nnodes; ++tpt) { - if (tgt_node_ghost(tpt)) { + if (tgt_node_ghost(tpt) or tgt_node_halo(tpt)) { continue; } auto p = tgt_points_[tpt]; @@ -1638,77 +2005,71 @@ void ConservativeSphericalPolygonInterpolation::Statistics::accuracy(const Inter mpi::comm().allReduceInPlace(&err_remap_l2, 1, eckit::mpi::sum()); mpi::comm().allReduceInPlace(&err_remap_linf, 1, eckit::mpi::max()); } - this->errors[Statistics::Errors::REMAP_L2] = std::sqrt(err_remap_l2 / unit_sphere_area()); - this->errors[Statistics::Errors::REMAP_LINF] = err_remap_linf; + errors[Statistics::Errors::REMAP_L2] = std::sqrt(err_remap_l2 / unit_sphere_area()); + errors[Statistics::Errors::REMAP_LINF] = err_remap_linf; + Metadata metadata; + metadata.set("errors.REMAP_L2", errors[Statistics::Errors::REMAP_L2]); + metadata.set("errors.REMAP_LINF", errors[Statistics::Errors::REMAP_LINF]); + return metadata; } -auto debug_intersection = [](const ConvexSphericalPolygon& plg_1, const ConvexSphericalPolygon& plg_2, - const ConvexSphericalPolygon& iplg, const ConvexSphericalPolygon& jplg) { - const double intersection_comm_err = std::abs(iplg.area() - jplg.area()) / (plg_1.area() > 0 ? plg_1.area() : 1.); - Log::info().indent(); - if (intersection_comm_err > 1e-6) { - Log::info() << "PLG_1 : " << std::setprecision(10) << plg_1 << "\n"; - Log::info() << "area(PLG_1) : " << plg_1.area() << "\n"; - Log::info() << "PLG_2 :" << plg_2 << "\n"; - Log::info() << "PLG_12 : " << iplg << "\n"; - Log::info() << "PLG_21 : " << jplg << "\n"; - Log::info() << "area(PLG_12 - PLG_21) : " << intersection_comm_err << "\n"; - Log::info() << "area(PLG_21) : " << jplg.area() << "\n"; - //ATLAS_ASSERT( false, "SRC.intersect.TGT =/= TGT.intersect.SRC."); - } - int pout; - int pin = inside_vertices(plg_1, plg_2, pout); - if (pin > 2 && iplg.area() < 3e-16) { - Log::info() << " pin : " << pin << ", pout :" << pout << ", total vertices : " << plg_2.size() << "\n"; - Log::info() << "PLG_2 :" << plg_2 << "\n"; - Log::info() << "PLG_12 : " << iplg << "\n"; - Log::info() << "area(PLG_12) : " << iplg.area() << "\n\n"; - //ATLAS_ASSERT( false, "SRC must intersect TGT." ); - } - Log::info().unindent(); -}; - -void ConservativeSphericalPolygonInterpolation::dump_intersection(const ConvexSphericalPolygon& plg_1, +void ConservativeSphericalPolygonInterpolation::dump_intersection(const std::string msg, + const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, const std::vector& plg_2_idx_array) const { +#if PRINT_BAD_POLYGONS double plg_1_coverage = 0.; + std::vector int_areas; + int_areas.resize(plg_2_idx_array.size()); for (int i = 0; i < plg_2_idx_array.size(); ++i) { const auto plg_2_idx = plg_2_idx_array[i]; const auto& plg_2 = std::get<0>(plg_2_array[plg_2_idx]); - auto iplg = plg_1.intersect(plg_2); - auto jplg = plg_2.intersect(plg_1); - debug_intersection(plg_1, plg_2, iplg, jplg); + auto iplg = plg_1.intersect(plg_2, false, 1.e5 * std::numeric_limits::epsilon()); plg_1_coverage += iplg.area(); + int_areas[i] = iplg.area(); } - Log::info().indent(); if (std::abs(plg_1.area() - plg_1_coverage) > 0.01 * plg_1.area()) { - Log::info() << "Polygon coverage incomplete. Printing polygons." << std::endl; - Log::info() << "Polygon 1 : "; + Log::info().indent(); + Log::info() << msg << ", Polygon_1.area: " << plg_1.area() << ", covered: " << plg_1_coverage << std::endl; + Log::info() << "Polygon_1 : "; + Log::info().precision(18); plg_1.print(Log::info()); Log::info() << std::endl << "Printing " << plg_2_idx_array.size() << " covering polygons -->" << std::endl; Log::info().indent(); + plg_1_coverage = plg_1.area(); for (int i = 0; i < plg_2_idx_array.size(); ++i) { const auto plg_2_idx = plg_2_idx_array[i]; const auto& plg_2 = std::get<0>(plg_2_array[plg_2_idx]); - Log::info() << "Polygon " << i + 1 << " : "; plg_2.print(Log::info()); + Log::info() << "\ninters:"; + plg_1_coverage -= int_areas[i]; + auto iplg = plg_1.intersect(plg_2, false, 1.e5 * std::numeric_limits::epsilon()); + Log::info().indent(); + iplg.print(Log::info()); + Log::info() << ", inters.-area: " << iplg.area() << ", plg1-area left: " << plg_1_coverage << std::endl; + Log::info().unindent(); Log::info() << std::endl; } + Log::info() << std::endl; + Log::info().unindent(); Log::info().unindent(); } - Log::info().unindent(); +#endif } template -void ConservativeSphericalPolygonInterpolation::dump_intersection(const ConvexSphericalPolygon& plg_1, +void ConservativeSphericalPolygonInterpolation::dump_intersection(const std::string msg, + const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, const TargetCellsIDs& plg_2_idx_array) const { +#if PRINT_BAD_POLYGONS std::vector idx_array; idx_array.resize(plg_2_idx_array.size()); for (int i = 0; i < plg_2_idx_array.size(); ++i) { idx_array[i] = plg_2_idx_array[i].payload(); } - dump_intersection(plg_1, plg_2_array, idx_array); + dump_intersection(msg, plg_1, plg_2_array, idx_array); +#endif } ConservativeSphericalPolygonInterpolation::Cache::Cache(std::shared_ptr entry): @@ -1749,20 +2110,16 @@ void ConservativeSphericalPolygonInterpolation::Data::print(std::ostream& out) c } void ConservativeSphericalPolygonInterpolation::Statistics::fillMetadata(Metadata& metadata) { - // counts - metadata.set("counts.SRC_PLG", counts[SRC_PLG]); - metadata.set("counts.TGT_PLG", counts[TGT_PLG]); - metadata.set("counts.INT_PLG", counts[INT_PLG]); - metadata.set("counts.UNCVR_SRC", counts[UNCVR_SRC]); - // errors - metadata.set("errors.SRC_PLG_L1", errors[SRC_PLG_L1]); - metadata.set("errors.SRC_PLG_LINF", errors[SRC_PLG_LINF]); - metadata.set("errors.TGT_PLG_L1", errors[TGT_PLG_L1]); - metadata.set("errors.TGT_PLG_LINF", errors[TGT_PLG_LINF]); - metadata.set("errors.GEO_L1", errors[GEO_L1]); - metadata.set("errors.GEO_LINF", errors[GEO_LINF]); - metadata.set("errors.GEO_DIFF", errors[GEO_DIFF]); + metadata.set("errors.SRC_SUBPLG_L1", errors[SRC_SUBPLG_L1]); + metadata.set("errors.SRC_SUBPLG_LINF", errors[SRC_SUBPLG_LINF]); + metadata.set("errors.TGT_SUBPLG_L1", errors[TGT_SUBPLG_L1]); + metadata.set("errors.TGT_SUBPLG_LINF", errors[TGT_SUBPLG_LINF]); + metadata.set("errors.SRC_INTERSECTPLG_L1", errors[SRC_INTERSECTPLG_L1]); + metadata.set("errors.SRC_INTERSECTPLG_LINF", errors[SRC_INTERSECTPLG_LINF]); + metadata.set("errors.TGT_INTERSECTPLG_L1", errors[TGT_INTERSECTPLG_L1]); + metadata.set("errors.TGT_INTERSECTPLG_LINF", errors[TGT_INTERSECTPLG_LINF]); + metadata.set("errors.SRCTGT_INTERSECTPLG_DIFF", errors[SRCTGT_INTERSECTPLG_DIFF]); metadata.set("errors.REMAP_CONS", errors[REMAP_CONS]); metadata.set("errors.REMAP_L2", errors[REMAP_L2]); metadata.set("errors.REMAP_LINF", errors[REMAP_LINF]); @@ -1774,20 +2131,16 @@ ConservativeSphericalPolygonInterpolation::Statistics::Statistics() { } ConservativeSphericalPolygonInterpolation::Statistics::Statistics(const Metadata& metadata): Statistics() { - // counts - metadata.get("counts.SRC_PLG", counts[SRC_PLG]); - metadata.get("counts.TGT_PLG", counts[TGT_PLG]); - metadata.get("counts.INT_PLG", counts[INT_PLG]); - metadata.get("counts.UNCVR_SRC", counts[UNCVR_SRC]); - // errors - metadata.get("errors.SRC_PLG_L1", errors[SRC_PLG_L1]); - metadata.get("errors.SRC_PLG_LINF", errors[SRC_PLG_LINF]); - metadata.get("errors.TGT_PLG_L1", errors[TGT_PLG_L1]); - metadata.get("errors.TGT_PLG_LINF", errors[TGT_PLG_LINF]); - metadata.get("errors.GEO_L1", errors[GEO_L1]); - metadata.get("errors.GEO_LINF", errors[GEO_LINF]); - metadata.get("errors.GEO_DIFF", errors[GEO_DIFF]); + metadata.get("errors.SRC_SUBPLG_L1", errors[SRC_SUBPLG_L1]); + metadata.get("errors.SRC_SUBPLG_LINF", errors[SRC_SUBPLG_LINF]); + metadata.get("errors.TGT_SUBPLG_L1", errors[TGT_SUBPLG_L1]); + metadata.get("errors.TGT_SUBPLG_LINF", errors[TGT_SUBPLG_LINF]); + metadata.get("errors.SRC_INTERSECTPLG_L1", errors[SRC_INTERSECTPLG_L1]); + metadata.get("errors.SRC_INTERSECTPLG_LINF", errors[SRC_INTERSECTPLG_LINF]); + metadata.get("errors.TGT_INTERSECTPLG_L1", errors[TGT_INTERSECTPLG_L1]); + metadata.get("errors.TGT_INTERSECTPLG_LINF", errors[TGT_INTERSECTPLG_LINF]); + metadata.get("errors.SRCTGT_INTERSECTPLG_DIFF", errors[SRCTGT_INTERSECTPLG_DIFF]); metadata.get("errors.REMAP_CONS", errors[REMAP_CONS]); metadata.get("errors.REMAP_L2", errors[REMAP_L2]); metadata.get("errors.REMAP_LINF", errors[REMAP_LINF]); diff --git a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.h b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.h index bcab9fdba..3dd79099b 100644 --- a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.h +++ b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.h @@ -25,10 +25,8 @@ class ConservativeSphericalPolygonInterpolation : public Method { struct InterpolationParameters { // one polygon intersection std::vector cell_idx; // target cells used for intersection std::vector centroids; // intersection cell centroids - std::vector src_weights; // intersection cell areas - - // TODO: tgt_weights can be computed on the fly - std::vector tgt_weights; // (intersection cell areas) / (target cell area) + std::vector weights; // intersection cell areas + std::vector tgt_weights; // (intersection cell areas) / (target cell area) // TODO: tgt_weights vector should be removed for the sake of highres }; private: @@ -55,7 +53,6 @@ class ConservativeSphericalPolygonInterpolation : public Method { std::vector> src_node2csp_; std::vector> tgt_node2csp_; - // Timings struct Timings { double source_polygons_assembly{0}; @@ -102,18 +99,20 @@ class ConservativeSphericalPolygonInterpolation : public Method { std::array counts; enum Errors { - SRC_PLG_L1 = 0, // index, over/undershoot in source subpolygon creation - SRC_PLG_LINF, - TGT_PLG_L1, // index, over/untershoot in target subpolygon creation - TGT_PLG_LINF, - GEO_L1, // index, cumulative area mismatch in polygon intersections - GEO_LINF, // index, like GEO_L1 but in L_infinity norm - GEO_DIFF, // index, difference in earth area coverages + SRC_SUBPLG_L1 = 0, // index, \sum_{cell of mesh} {cell.area - \sum_{subpol of cell} subpol.area} + SRC_SUBPLG_LINF, // index, \max_-||- + TGT_SUBPLG_L1, // see above + TGT_SUBPLG_LINF, // see above + SRC_INTERSECTPLG_L1, // index, \sum_{cell of src-mesh} {cell.area - \sum_{intersect of cell} intersect.area} + SRC_INTERSECTPLG_LINF, // index, \max_-||- + TGT_INTERSECTPLG_L1, // see above + TGT_INTERSECTPLG_LINF, // see above + SRCTGT_INTERSECTPLG_DIFF, // index, 1/(unit_sphere.area) ( \sum_{scell} scell.area - \sum{tcell} tcell.area ) REMAP_CONS, // index, error in mass conservation REMAP_L2, // index, error accuracy for given analytical function REMAP_LINF // index, like REMAP_L2 but in L_infinity norm }; - std::array errors; + std::array errors; double tgt_area_sum; double src_area_sum; @@ -123,8 +122,8 @@ class ConservativeSphericalPolygonInterpolation : public Method { Statistics(); Statistics(const Metadata&); - void accuracy(const Interpolation& interpolation, const Field target, - std::function func); + Metadata accuracy(const Interpolation& interpolation, const Field target, + std::function func); // compute difference field of source and target mass @@ -139,6 +138,7 @@ class ConservativeSphericalPolygonInterpolation : public Method { void do_setup(const FunctionSpace& src_fs, const FunctionSpace& tgt_fs) override; void do_setup(const Grid& src_grid, const Grid& tgt_grid, const interpolation::Cache&) override; void do_execute(const Field& src_field, Field& tgt_field, Metadata&) const override; + void do_execute(const FieldSet& src_fields, FieldSet& tgt_fields, Metadata&) const override; void print(std::ostream& out) const override; @@ -160,17 +160,17 @@ class ConservativeSphericalPolygonInterpolation : public Method { void intersect_polygons(const CSPolygonArray& src_csp, const CSPolygonArray& tgt_scp); Matrix compute_1st_order_matrix(); Matrix compute_2nd_order_matrix(); - void dump_intersection(const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, + void dump_intersection(const std::string, const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, const std::vector& plg_2_idx_array) const; template - void dump_intersection(const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, + void dump_intersection(const std::string, const ConvexSphericalPolygon& plg_1, const CSPolygonArray& plg_2_array, const TargetCellsIDs& plg_2_idx_array) const; std::vector sort_cell_edges(Mesh& mesh, idx_t cell_id) const; std::vector sort_node_edges(Mesh& mesh, idx_t cell_id) const; - std::vector get_cell_neighbours(Mesh& mesh, idx_t jcell) const; - std::vector get_node_neighbours(Mesh& mesh, idx_t jcell) const; - CSPolygonArray get_polygons_celldata(Mesh& mesh) const; - CSPolygonArray get_polygons_nodedata(Mesh& mesh, std::vector& csp2node, + std::vector get_cell_neighbours(Mesh&, idx_t jcell) const; + std::vector get_node_neighbours(Mesh&, idx_t jcell) const; + CSPolygonArray get_polygons_celldata(FunctionSpace) const; + CSPolygonArray get_polygons_nodedata(FunctionSpace, std::vector& csp2node, std::vector>& node2csp, std::array& errors) const; diff --git a/src/atlas/library/FloatingPointExceptions.cc b/src/atlas/library/FloatingPointExceptions.cc index 2c447367e..6e3d943c0 100644 --- a/src/atlas/library/FloatingPointExceptions.cc +++ b/src/atlas/library/FloatingPointExceptions.cc @@ -16,6 +16,7 @@ #include "eckit/config/LibEcKit.h" #include "eckit/config/Resource.h" +#include "eckit/runtime/Main.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" @@ -42,7 +43,6 @@ static int atlas_feenableexcept(int excepts) { #endif } -#ifdef UNUSED static int atlas_fedisableexcept(int excepts) { #if ATLAS_HAVE_FEDISABLEEXCEPT return ::fedisableexcept(excepts); @@ -50,7 +50,6 @@ static int atlas_fedisableexcept(int excepts) { return 0; #endif } -#endif namespace atlas { namespace library { @@ -275,7 +274,9 @@ void enable_floating_point_exceptions() { // Above trick with "tmp" is what avoids the Cray 8.6 compiler bug } else { - floating_point_exceptions = eckit::Resource>("atlasFPE", {"false"}); + if (eckit::Main::ready()) { + floating_point_exceptions = eckit::Resource>("atlasFPE", {"false"}); + } } { bool _enable = false; @@ -316,8 +317,56 @@ void enable_floating_point_exceptions() { } } +bool enable_floating_point_exception(int except) { + auto check_flag = [](int flags, int bits) -> bool { return (flags & bits) == bits; }; + int previous = atlas_feenableexcept(except); + return !check_flag(previous,except); +} + +bool disable_floating_point_exception(int except) { + auto check_flag = [](int flags, int bits) -> bool { return (flags & bits) == bits; }; + int previous = atlas_fedisableexcept(except); + return !check_flag(previous,except); +} + +bool enable_floating_point_exception(const std::string& floating_point_exception) { + auto it = str_to_except.find(floating_point_exception); + if (it == str_to_except.end()) { + throw eckit::UserError( + floating_point_exception + " is not a valid floating point exception code. " + "Valid codes: [FE_INVALID,FE_INEXACT,FE_DIVBYZERO,FE_OVERFLOW,FE_ALL_EXCEPT]", + Here()); + } + return enable_floating_point_exception(it->second); +} + +bool disable_floating_point_exception(const std::string& floating_point_exception) { + auto it = str_to_except.find(floating_point_exception); + if (it == str_to_except.end()) { + throw eckit::UserError( + floating_point_exception + " is not a valid floating point exception code. " + "Valid codes: [FE_INVALID,FE_INEXACT,FE_DIVBYZERO,FE_OVERFLOW,FE_ALL_EXCEPT]", + Here()); + } + return disable_floating_point_exception(it->second); +} + + void enable_atlas_signal_handler() { - if (eckit::Resource("atlasSignalHandler;$ATLAS_SIGNAL_HANDLER", false)) { + bool enable = false; + if (::getenv("ATLAS_SIGNAL_HANDLER")) { + std::string env(::getenv("ATLAS_SIGNAL_HANDLER")); + bool tmp = eckit::Translator()(env); + enable = tmp; + // Above trick with "tmp" is what avoids the Cray 8.6 compiler bug + } + else { + if (eckit::Main::ready()) { + enable = eckit::Resource("atlasSignalHandler", false); + } + } + + if (enable) { Signals::instance().setSignalHandlers(); } } diff --git a/src/atlas/library/FloatingPointExceptions.h b/src/atlas/library/FloatingPointExceptions.h index 57e93ccfe..a8a9c9acf 100644 --- a/src/atlas/library/FloatingPointExceptions.h +++ b/src/atlas/library/FloatingPointExceptions.h @@ -12,9 +12,11 @@ #include #include #include +#include namespace atlas { namespace library { + // ------------------------------------------------------------------------------------ /* @brief Enable floating point exceptions @@ -30,6 +32,26 @@ namespace library { */ void enable_floating_point_exceptions(); +/* @brief Enable floating point exception + * + * Valid codes: + * FE_INVALID, FE_DIVBYZERO, FE_OVERFLOW, FE_UNDERFLOW, FE_INEXACT, FE_ALL_EXCEPT + * + * @return false when the exception was already enabled, true when change was made + */ +bool enable_floating_point_exception(const std::string&); +bool enable_floating_point_exception(int); + +/* @brief Disable floating point exception + * + * Valid codes: + * FE_INVALID, FE_DIVBYZERO, FE_OVERFLOW, FE_UNDERFLOW, FE_INEXACT, FE_ALL_EXCEPT + * + * @return false when the exception was already disabled, true when change was made + */ +bool disable_floating_point_exception(const std::string&); +bool disable_floating_point_exception(int); + // ------------------------------------------------------------------------------------ /* @brief Enable atlas signal handler for all signals diff --git a/src/atlas/library/Library.cc b/src/atlas/library/Library.cc index 86c7f1075..0ae5c565c 100644 --- a/src/atlas/library/Library.cc +++ b/src/atlas/library/Library.cc @@ -26,6 +26,7 @@ #include "eckit/system/SystemInfo.h" #include "eckit/types/Types.h" #include "eckit/utils/Translator.h" +#include "eckit/system/LibraryManager.h" #if ATLAS_ECKIT_HAVE_ECKIT_585 #include "eckit/linalg/LinearAlgebraDense.h" @@ -88,6 +89,11 @@ std::string str(const eckit::system::Library& lib) { return ss.str(); } +std::string getEnv(const std::string& env, const std::string& default_value = "") { + char const* val = ::getenv(env.c_str()); + return val == nullptr ? default_value : std::string(val); +} + bool getEnv(const std::string& env, bool default_value) { if (::getenv(env.c_str())) { return eckit::Translator()(::getenv(env.c_str())); @@ -114,7 +120,7 @@ static void add_tokens(std::vector& tokens, const std::string& str, }; static void init_data_paths(std::vector& data_paths) { - ATLAS_ASSERT(eckit::Main::instance().ready()); + ATLAS_ASSERT(eckit::Main::ready()); add_tokens(data_paths, eckit::LibResource("atlas-data-path;$ATLAS_DATA_PATH", ""), ":"); add_tokens(data_paths, "~atlas/share", ":"); } @@ -152,7 +158,23 @@ Library::Library(): trace_(getEnv("ATLAS_TRACE", false)), trace_memory_(getEnv("ATLAS_TRACE_MEMORY", false)), trace_barriers_(getEnv("ATLAS_TRACE_BARRIERS", false)), - trace_report_(getEnv("ATLAS_TRACE_REPORT", false)) {} + trace_report_(getEnv("ATLAS_TRACE_REPORT", false)), + atlas_io_trace_hook_(::atlas::io::TraceHookRegistry::invalidId()) { + std::string ATLAS_PLUGIN_PATH = getEnv("ATLAS_PLUGIN_PATH"); +#if ATLAS_ECKIT_VERSION_AT_LEAST(1, 25, 0) || ATLAS_ECKIT_DEVELOP + eckit::system::LibraryManager::addPluginSearchPath(ATLAS_PLUGIN_PATH); +#else + if (ATLAS_PLUGIN_PATH.size()) { + std::cout << "WARNING: atlas::Library discovered environment variable ATLAS_PLUGIN_PATH. " + << "Currently used version of eckit (" << eckit_version_str() << " [" << eckit_git_sha1() << "]) " + << "does not support adding plugin search paths. " + << "When using latest eckit develop branch, please rebuild Atlas with " + << "CMake argument -DENABLE_ECKIT_DEVELOP=ON\n" + << "Alternatively, use combination of environment variables 'PLUGINS_MANIFEST_PATH' " + << "and 'LD_LIBRARY_PATH (for UNIX) / DYLD_LIBRARY_PATH (for macOS)' (colon-separated lists)\n" << std::endl; + } +#endif +} void Library::registerPlugin(eckit::system::Plugin& plugin) { plugins_.push_back(&plugin); @@ -173,7 +195,6 @@ std::string Library::cachePath() const { } void Library::registerDataPath(const std::string& path) { - ATLAS_DEBUG_VAR(path); if (data_paths_.empty()) { init_data_paths(data_paths_); } @@ -183,7 +204,7 @@ void Library::registerDataPath(const std::string& path) { std::string Library::dataPath() const { if (data_paths_.empty()) { - ATLAS_THROW_EXCEPTION("Attempted to access atlas::Library function before atlas was initialized"); + init_data_paths(data_paths_); } std::vector paths = data_paths_; auto join = [](const std::vector& v, const std::string& sep) -> std::string { @@ -254,6 +275,8 @@ void Library::initialise(int argc, char** argv) { void Library::initialise(const eckit::Parametrisation& config) { + ATLAS_ASSERT(eckit::Main::ready()); + if (initialized_) { return; } @@ -339,7 +362,10 @@ void Library::initialise() { } void Library::finalise() { - atlas::io::TraceHookRegistry::disable(atlas_io_trace_hook_); + if( atlas_io_trace_hook_ != atlas::io::TraceHookRegistry::invalidId() ) { + atlas::io::TraceHookRegistry::disable(atlas_io_trace_hook_); + atlas_io_trace_hook_ = atlas::io::TraceHookRegistry::invalidId(); + } if (ATLAS_HAVE_TRACE && trace_report_) { Log::info() << atlas::Trace::report() << std::endl; @@ -449,7 +475,7 @@ void Library::Information::print(std::ostream& out) const { bool feature_fortran(ATLAS_HAVE_FORTRAN); bool feature_OpenMP(ATLAS_HAVE_OMP); - bool feature_Trans(ATLAS_HAVE_TRANS); + bool feature_ecTrans(ATLAS_HAVE_ECTRANS); bool feature_FFTW(ATLAS_HAVE_FFTW); bool feature_Eigen(ATLAS_HAVE_EIGEN); bool feature_Tesselation(ATLAS_HAVE_TESSELATION); @@ -473,7 +499,7 @@ void Library::Information::print(std::ostream& out) const { << " OpenMP : " << str(feature_OpenMP) << '\n' << " BoundsChecking : " << str(feature_BoundsChecking) << '\n' << " Init_sNaN : " << str(feature_Init_sNaN) << '\n' - << " Trans : " << str(feature_Trans) << '\n' + << " ecTrans : " << str(feature_ecTrans) << '\n' << " FFTW : " << str(feature_FFTW) << '\n' << " Eigen : " << str(feature_Eigen) << '\n' << " MKL : " << str(feature_MKL()) << '\n' diff --git a/src/atlas/library/Library.h b/src/atlas/library/Library.h index e911ee3a7..f0cc16df8 100644 --- a/src/atlas/library/Library.h +++ b/src/atlas/library/Library.h @@ -104,7 +104,7 @@ class Library : public eckit::system::Library { private: std::vector plugins_; - std::vector data_paths_; + mutable std::vector data_paths_; size_t atlas_io_trace_hook_; }; diff --git a/src/atlas/library/config.h b/src/atlas/library/config.h index 4b43fe9c8..d684c3716 100644 --- a/src/atlas/library/config.h +++ b/src/atlas/library/config.h @@ -54,5 +54,4 @@ typedef gidx_t uidx_t; #define ATLAS_ECKIT_HAVE_ECKIT_585 0 #endif - } // namespace atlas diff --git a/src/atlas/library/defines.h.in b/src/atlas/library/defines.h.in index 5056fafc8..51d55a733 100644 --- a/src/atlas/library/defines.h.in +++ b/src/atlas/library/defines.h.in @@ -32,13 +32,15 @@ #define ATLAS_HAVE_GRIDTOOLS_STORAGE @atlas_HAVE_GRIDTOOLS_STORAGE@ #define ATLAS_GRIDTOOLS_STORAGE_BACKEND_HOST @ATLAS_GRIDTOOLS_STORAGE_BACKEND_HOST@ #define ATLAS_GRIDTOOLS_STORAGE_BACKEND_CUDA @ATLAS_GRIDTOOLS_STORAGE_BACKEND_CUDA@ -#define ATLAS_HAVE_TRANS @atlas_HAVE_TRANS@ -#define ATLAS_HAVE_ECTRANS @atlas_HAVE_ECTRANS@ +#define ATLAS_HAVE_TRANS @atlas_HAVE_ECTRANS@ +#define ATLAS_HAVE_ECTRANS @atlas_HAVE_PACKAGE_ECTRANS@ #define ATLAS_HAVE_FEENABLEEXCEPT @atlas_HAVE_FEENABLEEXCEPT@ #define ATLAS_HAVE_FEDISABLEEXCEPT @atlas_HAVE_FEDISABLEEXCEPT@ #define ATLAS_BUILD_TYPE_DEBUG @atlas_BUILD_TYPE_DEBUG@ #define ATLAS_BUILD_TYPE_RELEASE @atlas_BUILD_TYPE_RELEASE@ #define ATLAS_ECKIT_VERSION_INT @ATLAS_ECKIT_VERSION_INT@ +#define ATLAS_ECKIT_DEVELOP @ATLAS_ECKIT_DEVELOP@ +#define ATLAS_HAVE_FUNCTIONSPACE @atlas_HAVE_ATLAS_FUNCTIONSPACE@ #ifdef __CUDACC__ #define ATLAS_HOST_DEVICE __host__ __device__ diff --git a/src/atlas/util/detail/BlackMagic.h b/src/atlas/library/detail/BlackMagic.h similarity index 100% rename from src/atlas/util/detail/BlackMagic.h rename to src/atlas/library/detail/BlackMagic.h diff --git a/src/atlas/util/detail/Debug.h b/src/atlas/library/detail/Debug.h similarity index 100% rename from src/atlas/util/detail/Debug.h rename to src/atlas/library/detail/Debug.h diff --git a/src/atlas/mesh/MeshBuilder.cc b/src/atlas/mesh/MeshBuilder.cc new file mode 100644 index 000000000..454e53d3e --- /dev/null +++ b/src/atlas/mesh/MeshBuilder.cc @@ -0,0 +1,144 @@ +/* + * (C) Copyright 2023 UCAR. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include "atlas/mesh/MeshBuilder.h" + +#include "atlas/array/MakeView.h" +#include "atlas/mesh/ElementType.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/Nodes.h" +#include "atlas/parallel/mpi/mpi.h" +#include "atlas/util/CoordinateEnums.h" + +namespace atlas { +namespace mesh { + +//---------------------------------------------------------------------------------------------------------------------- + +Mesh MeshBuilder::operator()(const std::vector& lons, const std::vector& lats, + const std::vector& ghosts, const std::vector& global_indices, + const std::vector& remote_indices, const idx_t remote_index_base, + const std::vector& partitions, + const std::vector>& tri_boundary_nodes, + const std::vector& tri_global_indices, + const std::vector>& quad_boundary_nodes, + const std::vector& quad_global_indices) const { + const size_t nb_nodes = global_indices.size(); + const size_t nb_tris = tri_global_indices.size(); + const size_t nb_quads = quad_global_indices.size(); + + ATLAS_ASSERT(nb_nodes == lons.size()); + ATLAS_ASSERT(nb_nodes == lats.size()); + ATLAS_ASSERT(nb_nodes == ghosts.size()); + ATLAS_ASSERT(nb_nodes == remote_indices.size()); + ATLAS_ASSERT(nb_nodes == partitions.size()); + ATLAS_ASSERT(nb_tris == tri_boundary_nodes.size()); + ATLAS_ASSERT(nb_quads == quad_boundary_nodes.size()); + + return operator()(nb_nodes, lons.data(), lats.data(), ghosts.data(), global_indices.data(), remote_indices.data(), + remote_index_base, partitions.data(), nb_tris, + reinterpret_cast(tri_boundary_nodes.data()), tri_global_indices.data(), nb_quads, + reinterpret_cast(quad_boundary_nodes.data()), quad_global_indices.data()); +} + +Mesh MeshBuilder::operator()(size_t nb_nodes, const double lons[], const double lats[], const int ghosts[], + const gidx_t global_indices[], const idx_t remote_indices[], const idx_t remote_index_base, + const int partitions[], size_t nb_tris, const gidx_t tri_boundary_nodes[], + const gidx_t tri_global_indices[], size_t nb_quads, const gidx_t quad_boundary_nodes[], + const gidx_t quad_global_indices[]) const { + Mesh mesh{}; + + // Populate node data + + mesh.nodes().resize(nb_nodes); + auto xy = array::make_view(mesh.nodes().xy()); + auto lonlat = array::make_view(mesh.nodes().lonlat()); + auto ghost = array::make_view(mesh.nodes().ghost()); + auto gidx = array::make_view(mesh.nodes().global_index()); + auto ridx = array::make_indexview(mesh.nodes().remote_index()); + auto partition = array::make_view(mesh.nodes().partition()); + auto halo = array::make_view(mesh.nodes().halo()); + + for (size_t i = 0; i < nb_nodes; ++i) { + xy(i, size_t(XX)) = lons[i]; + xy(i, size_t(YY)) = lats[i]; + // Identity projection, therefore (lon,lat) = (x,y) + lonlat(i, size_t(LON)) = lons[i]; + lonlat(i, size_t(LAT)) = lats[i]; + ghost(i) = ghosts[i]; + gidx(i) = global_indices[i]; + ridx(i) = remote_indices[i] - remote_index_base; + partition(i) = partitions[i]; + } + halo.assign(0); + + // Populate cell/element data + + // First, count how many cells of each type are on this processor + // Then optimize away the element type if globally nb_tris or nb_quads is zero + size_t sum_nb_tris = 0; + atlas::mpi::comm().allReduce(nb_tris, sum_nb_tris, eckit::mpi::sum()); + const bool add_tris = (sum_nb_tris > 0); + + size_t sum_nb_quads = 0; + atlas::mpi::comm().allReduce(nb_quads, sum_nb_quads, eckit::mpi::sum()); + const bool add_quads = (sum_nb_quads > 0); + + if (add_tris) { + mesh.cells().add(new mesh::temporary::Triangle(), nb_tris); + } + if (add_quads) { + mesh.cells().add(new mesh::temporary::Quadrilateral(), nb_quads); + } + + atlas::mesh::HybridElements::Connectivity& node_connectivity = mesh.cells().node_connectivity(); + auto cells_part = array::make_view(mesh.cells().partition()); + auto cells_gidx = array::make_view(mesh.cells().global_index()); + + // Find position of idx inside global_indices + const auto position_of = [&nb_nodes, &global_indices](const gidx_t idx) { + const auto& it = std::find(global_indices, global_indices + nb_nodes, idx); + ATLAS_ASSERT(it != global_indices + nb_nodes); + return std::distance(global_indices, it); + }; + + size_t idx = 0; + if (add_tris) { + idx_t buffer[3]; + for (size_t tri = 0; tri < nb_tris; ++tri) { + for (size_t i = 0; i < 3; ++i) { + buffer[i] = position_of(tri_boundary_nodes[3 * tri + i]); + } + node_connectivity.set(idx, buffer); + cells_gidx(idx) = tri_global_indices[tri]; + idx++; + } + } + if (add_quads) { + idx_t buffer[4]; + for (size_t quad = 0; quad < nb_quads; ++quad) { + for (size_t i = 0; i < 4; ++i) { + buffer[i] = position_of(quad_boundary_nodes[4 * quad + i]); + } + node_connectivity.set(idx, buffer); + cells_gidx(idx) = quad_global_indices[quad]; + idx++; + } + } + + ATLAS_ASSERT(idx == nb_tris + nb_quads); + + cells_part.assign(atlas::mpi::comm().rank()); + + return mesh; +} + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace mesh +} // namespace atlas diff --git a/src/atlas/mesh/MeshBuilder.h b/src/atlas/mesh/MeshBuilder.h new file mode 100644 index 000000000..5f2f42127 --- /dev/null +++ b/src/atlas/mesh/MeshBuilder.h @@ -0,0 +1,75 @@ +/* + * (C) Copyright 2023 UCAR. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#pragma once + +#include "atlas/mesh/Mesh.h" +#include "atlas/util/Config.h" + +#include +#include + +namespace atlas { +namespace mesh { + +//----------------------------------------------------------------------------- + +/** + * \brief Construct a Mesh by importing external connectivity data + * + * Given a list of nodes and corresponding cell-to-node connectivity, sets up a Mesh. Not all Mesh + * fields are initialized, but enough are to build halos and construct a NodeColumns FunctionSpace. + * + * Some limitations of the current implementation (but not inherent): + * - can only set up triangle and quad cells. + * - cannot import halos, i.e., cells owned by other MPI tasks; halos can still be subsequently + * computed by calling the BuildMesh action. + * - cannot import node-to-cell connectivity information. + */ +class MeshBuilder { +public: + MeshBuilder(const eckit::Configuration& = util::NoConfig()) {} + + /** + * \brief C-interface to construct a Mesh from external connectivity data + * + * The inputs lons, lats, ghosts, global_indices, remote_indices, and partitions are vectors of + * size nb_nodes, ranging over the nodes locally owned by (or in the ghost nodes of) the MPI + * task. The global index is a uniform labeling of the nodes across all MPI tasks; the remote + * index is a remote_index_base-based vector index for the node on its owning task. + * + * The tri/quad connectivities (boundary_nodes and global_indices) are vectors ranging over the + * cells owned by the MPI task. Each cell is defined by a list of nodes defining its boundary; + * note that each boundary node must be locally known (whether as an owned of ghost node on the + * MPI task), in other words, must be an element of the node global_indices. The boundary nodes + * are ordered node-varies-fastest, element-varies-slowest order. The cell global index is, + * here also, a uniform labeling over the of the cells across all MPI tasks. + */ + Mesh operator()(size_t nb_nodes, const double lons[], const double lats[], const int ghosts[], + const gidx_t global_indices[], const idx_t remote_indices[], const idx_t remote_index_base, + const int partitions[], size_t nb_tris, const gidx_t tri_boundary_nodes[], + const gidx_t tri_global_indices[], size_t nb_quads, const gidx_t quad_boundary_nodes[], + const gidx_t quad_global_indices[]) const; + + /** + * \brief C++-interface to construct a Mesh from external connectivity data + * + * Provides a wrapper to the C-interface using STL containers. + */ + Mesh operator()(const std::vector& lons, const std::vector& lats, const std::vector& ghosts, + const std::vector& global_indices, const std::vector& remote_indices, + const idx_t remote_index_base, const std::vector& partitions, + const std::vector>& tri_boundary_nodes, + const std::vector& tri_global_indices, + const std::vector>& quad_boundary_nodes, + const std::vector& quad_global_indices) const; +}; + +//----------------------------------------------------------------------------- + +} // namespace mesh +} // namespace atlas diff --git a/src/atlas/mesh/actions/BuildConvexHull3D.cc b/src/atlas/mesh/actions/BuildConvexHull3D.cc index a0712cdee..1aeb0cf9e 100644 --- a/src/atlas/mesh/actions/BuildConvexHull3D.cc +++ b/src/atlas/mesh/actions/BuildConvexHull3D.cc @@ -64,6 +64,13 @@ namespace actions { //---------------------------------------------------------------------------------------------------------------------- +BuildConvexHull3D::BuildConvexHull3D(const eckit::Parametrisation& config) { + config.get("remove_duplicate_points", remove_duplicate_points_ = true); + config.get("reshuffle", reshuffle_ = true); +} + +//---------------------------------------------------------------------------------------------------------------------- + #if ATLAS_HAVE_TESSELATION static Polyhedron_3* create_convex_hull_from_points(const std::vector& pts) { diff --git a/src/atlas/mesh/actions/BuildConvexHull3D.h b/src/atlas/mesh/actions/BuildConvexHull3D.h index b05b58fbc..65bb68353 100644 --- a/src/atlas/mesh/actions/BuildConvexHull3D.h +++ b/src/atlas/mesh/actions/BuildConvexHull3D.h @@ -10,6 +10,10 @@ #pragma once +#include "eckit/config/Parametrisation.h" + +#include "atlas/util/Config.h" + namespace atlas { class Mesh; @@ -20,7 +24,11 @@ namespace actions { /// Creates a 3D convex-hull on the mesh points class BuildConvexHull3D { public: + BuildConvexHull3D(const eckit::Parametrisation& = util::NoConfig()); void operator()(Mesh&) const; +private: + bool remove_duplicate_points_; + bool reshuffle_; }; } // namespace actions diff --git a/src/atlas/mesh/actions/BuildHalo.cc b/src/atlas/mesh/actions/BuildHalo.cc index c83195fa9..f0ed02925 100644 --- a/src/atlas/mesh/actions/BuildHalo.cc +++ b/src/atlas/mesh/actions/BuildHalo.cc @@ -576,8 +576,10 @@ class BuildHaloHelper { << "-----\n"; idx_t n(0); for (idx_t jpart = 0; jpart < mpi_size; ++jpart) { - for (auto g : node_glb_idx[jpart]) { - os << std::setw(4) << n++ << " : " << g << "\n"; + for (idx_t jnode = 0; jnode < node_glb_idx[jpart].size(); ++jnode ) { + auto g = node_glb_idx[jpart][jnode]; + auto p = node_part[jpart][jnode]; + os << std::setw(4) << n++ << " : [ p" << p << " ] " << g << "\n"; } } os << std::flush; @@ -704,7 +706,6 @@ class BuildHaloHelper { typename NodeContainer::const_iterator it; for (it = nodes_uid.begin(); it != nodes_uid.end(); ++it, ++jnode) { uid_t uid = *it; - auto found = uid2node.find(uid); if (found != uid2node.end()) // Point exists inside domain { @@ -751,6 +752,7 @@ class BuildHaloHelper { for (idx_t jnode = 0; jnode < elem_nodes->cols(ielem); ++jnode) { buf.elem_nodes_id[p][jelemnode++] = compute_uid((*elem_nodes)(ielem, jnode)); } + Topology::set(buf.elem_flags[p][jelem], Topology::GHOST); } } @@ -868,7 +870,9 @@ class BuildHaloHelper { for (idx_t jpart = 0; jpart < mpi_size; ++jpart) { const idx_t nb_nodes_on_part = static_cast(buf.node_glb_idx[jpart].size()); for (idx_t n = 0; n < nb_nodes_on_part; ++n) { - double crd[] = {buf.node_xy[jpart][n * 2 + XX], buf.node_xy[jpart][n * 2 + YY]}; + std::array crd{buf.node_xy[jpart][n * 2 + XX], buf.node_xy[jpart][n * 2 + YY]}; + mesh.projection().xy2lonlat(crd.data()); + if (not node_already_exists(util::unique_lonlat(crd))) { rfn_idx[jpart].push_back(n); } @@ -921,7 +925,7 @@ class BuildHaloHelper { if (found != uid2node.end()) { int other = found->second; std::stringstream msg; - msg << "New node with uid " << uid << ":\n" + msg << "New node loc " << loc_idx << " with uid " << uid << ":\n" << glb_idx(loc_idx) << "(" << xy(loc_idx, XX) << "," << xy(loc_idx, YY) << ")\n"; msg << "Existing already loc " << other << " : " << glb_idx(other) << "(" << xy(other, XX) << "," << xy(other, YY) << ")\n"; diff --git a/src/atlas/mesh/actions/ExtendNodesGlobal.cc b/src/atlas/mesh/actions/ExtendNodesGlobal.cc index 43292ae2f..51b38299c 100644 --- a/src/atlas/mesh/actions/ExtendNodesGlobal.cc +++ b/src/atlas/mesh/actions/ExtendNodesGlobal.cc @@ -66,6 +66,9 @@ void ExtendNodesGlobal::operator()(const Grid& grid, Mesh& mesh) const { array::ArrayView xy = array::make_view(nodes.xy()); array::ArrayView lonlat = array::make_view(nodes.lonlat()); array::ArrayView gidx = array::make_view(nodes.global_index()); + array::ArrayView ghost = array::make_view(nodes.ghost()); + array::ArrayView partition = array::make_view(nodes.partition()); + array::ArrayView flags = array::make_view(nodes.flags()); for (idx_t i = 0; i < nb_extension_pts; ++i) { const idx_t n = nb_real_pts + i; @@ -82,6 +85,10 @@ void ExtendNodesGlobal::operator()(const Grid& grid, Mesh& mesh) const { lonlat(n, LON) = pLL.lon(); lonlat(n, LAT) = pLL.lat(); gidx(n) = n + 1; + ghost(n) = false; + partition(n) = 0; + util::Topology::view(flags(n)).reset(util::Topology::NONE); + // util::Topology::view(flags(n)).set(util::Topology::GHOST); } } diff --git a/src/atlas/mesh/actions/WriteLoadBalanceReport.cc b/src/atlas/mesh/actions/WriteLoadBalanceReport.cc index 875d2314d..ba3ea752e 100644 --- a/src/atlas/mesh/actions/WriteLoadBalanceReport.cc +++ b/src/atlas/mesh/actions/WriteLoadBalanceReport.cc @@ -21,6 +21,7 @@ #include "atlas/mesh/actions/WriteLoadBalanceReport.h" #include "atlas/parallel/mpi/mpi.h" #include "atlas/runtime/Exception.h" +#include "atlas/library/FloatingPointExceptions.h" using atlas::mesh::IsGhostNode; @@ -80,6 +81,8 @@ void write_load_balance_report(const Mesh& mesh, std::ostream& ofs) { mpi::comm().gather(nghost, nb_ghost_nodes, root); } + bool disabled_fpe = library::disable_floating_point_exception(FE_INVALID); + for (idx_t p = 0; p < npart; ++p) { if (nb_owned_nodes[p]) { ghost_ratio_nodes[p] = static_cast(nb_ghost_nodes[p]) / static_cast(nb_owned_nodes[p]); @@ -88,6 +91,9 @@ void write_load_balance_report(const Mesh& mesh, std::ostream& ofs) { ghost_ratio_nodes[p] = -1; } } + if (disabled_fpe) { + library::enable_floating_point_exception(FE_INVALID); + } } bool has_edges = mesh.edges().size(); diff --git a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc index be86d606a..641c07694 100644 --- a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc @@ -8,6 +8,8 @@ * nor does it submit to any jurisdiction. */ +#include + #include "eckit/utils/Hash.h" #include "atlas/array/ArrayView.h" @@ -27,6 +29,8 @@ #include "atlas/projection/Projection.h" #include "atlas/runtime/Log.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/parallel/mpi/mpi.h" +#include "atlas/mesh/ElementType.h" using atlas::Mesh; @@ -37,7 +41,11 @@ namespace meshgenerator { DelaunayMeshGenerator::DelaunayMeshGenerator() = default; -DelaunayMeshGenerator::DelaunayMeshGenerator(const eckit::Parametrisation&) {} +DelaunayMeshGenerator::DelaunayMeshGenerator(const eckit::Parametrisation& p) { + p.get("part",part_=mpi::rank()); + p.get("reshuffle",reshuffle_=true); + p.get("remove_duplicate_points",remove_duplicate_points_=true); +} DelaunayMeshGenerator::~DelaunayMeshGenerator() = default; @@ -48,57 +56,311 @@ void DelaunayMeshGenerator::hash(eckit::Hash& h) const { } void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& dist, Mesh& mesh) const { - if (dist.nb_partitions() > 1) { - Log::warning() << "Delaunay triangulation does not support a GridDistribution" - "with more than 1 partition" - << std::endl; - ATLAS_NOTIMPLEMENTED; - /// TODO: Read mesh on 1 MPI task, and distribute according to - /// GridDistribution - /// HINT: use atlas/actions/DistributeMesh - } - else { - generate(grid, mesh); + + auto build_global_mesh = [&](Mesh& mesh) { + idx_t nb_nodes = grid.size(); + mesh.nodes().resize(nb_nodes); + auto xy = array::make_view(mesh.nodes().xy()); + auto lonlat = array::make_view(mesh.nodes().lonlat()); + auto ghost = array::make_view(mesh.nodes().ghost()); + auto gidx = array::make_view(mesh.nodes().global_index()); + auto part = array::make_view(mesh.nodes().partition()); + auto flags = array::make_view(mesh.nodes().flags()); + + size_t jnode{0}; + Projection projection = grid.projection(); + PointLonLat Pll; + for (PointXY Pxy : grid.xy()) { + xy(jnode, size_t(XX)) = Pxy.x(); + xy(jnode, size_t(YY)) = Pxy.y(); + + Pll = projection.lonlat(Pxy); + lonlat(jnode, size_t(LON)) = Pll.lon(); + lonlat(jnode, size_t(LAT)) = Pll.lat(); + + part(jnode) = dist.partition(jnode); + ghost(jnode) = part(jnode) != part_; + + gidx(jnode) = jnode + 1; + + if (ghost(jnode)) { + util::Topology::view(flags(jnode)).set(util::Topology::GHOST); + } + + ++jnode; + } + mesh::actions::BuildXYZField()(mesh); + mesh::actions::ExtendNodesGlobal()(grid,mesh); ///< does nothing if global domain + mesh::actions::BuildConvexHull3D()(mesh); + + auto cells_gidx = array::make_view( mesh.cells().global_index() ); + for (idx_t jelem=0; jelem(global_mesh.nodes().xy()); + auto g_lonlat = array::make_view(global_mesh.nodes().lonlat()); + auto g_ghost = array::make_view(global_mesh.nodes().ghost()); + auto g_gidx = array::make_view(global_mesh.nodes().global_index()); + auto g_part = array::make_view(global_mesh.nodes().partition()); - setGrid(mesh, g, "serial"); -} + size_t owned_nodes_count = dist.nb_pts()[part_]; -void DelaunayMeshGenerator::createNodes(const Grid& grid, Mesh& mesh) const { - idx_t nb_nodes = grid.size(); - mesh.nodes().resize(nb_nodes); + std::vector owned_nodes; + owned_nodes.reserve(1.4*owned_nodes_count); + for (size_t jnode=0; jnode < global_mesh.nodes().size(); ++ jnode) { + if (g_ghost(jnode) == 0) { + owned_nodes.emplace_back(jnode); + } + } + auto& g_node_connectivity = global_mesh.cells().node_connectivity(); + std::set ghost_nodes; + std::vector owned_elements; + owned_elements.reserve(1.4*owned_nodes_count); + std::set element_nodes_uncertainty; + std::set element_uncertainty; + constexpr idx_t OWNED = -1; + constexpr idx_t GHOST = -2; + constexpr idx_t UNCERTAIN = -3; + constexpr idx_t CERTAIN = -4; - auto xy = array::make_view(mesh.nodes().xy()); - auto lonlat = array::make_view(mesh.nodes().lonlat()); - auto ghost = array::make_view(mesh.nodes().ghost()); - auto gidx = array::make_view(mesh.nodes().global_index()); + auto elem_node_partition = [&](idx_t jelem, idx_t jnode) -> int { + return g_part(g_node_connectivity(jelem,jnode)); + }; - size_t jnode{0}; - Projection projection = grid.projection(); - PointLonLat Pll; - for (PointXY Pxy : grid.xy()) { - xy(jnode, size_t(XX)) = Pxy.x(); - xy(jnode, size_t(YY)) = Pxy.y(); + auto get_elem_ownership = [&](idx_t jelem) -> int { + int p0 = elem_node_partition(jelem,0); + int p1 = elem_node_partition(jelem,1); + int p2 = elem_node_partition(jelem,2); + if (p0 != part_ && p1 != part_ && p2 != part_) { + return GHOST; + } + if ((p0 == p1 || p0 == p2) && p0 == part_) { + return OWNED; + } + else if (p1 == p2 && p1 == part_) { + return OWNED; + } + else if ( p0 == p1 || p0 == p2 || p1 == p2 ) { + return CERTAIN; + } + return UNCERTAIN; + }; - Pll = projection.lonlat(Pxy); - lonlat(jnode, size_t(LON)) = Pll.lon(); - lonlat(jnode, size_t(LAT)) = Pll.lat(); + auto get_elem_part = [&](idx_t jelem) -> int { + int p0 = elem_node_partition(jelem,0); + int p1 = elem_node_partition(jelem,1); + int p2 = elem_node_partition(jelem,2); + if (p0 == p1 || p0 == p2) { + return p0; + } + else if (p1 == p2) { + return p1; + } + return UNCERTAIN; + }; - ghost(jnode) = false; - gidx(jnode) = jnode + 1; + auto collect_element = [&](idx_t jelem){ + owned_elements.emplace_back(jelem); + for (idx_t j=0; j<3; ++j) { + if (elem_node_partition(jelem,j) != part_) { + ghost_nodes.insert(g_node_connectivity(jelem,j)); + } + } + }; - ++jnode; - } + for (idx_t jelem=0; jelem> node2element; + for (idx_t jelem=0; jelemsecond.emplace_back(jelem); + } + } + } + } + // Log::info() << "node2element" << std::endl; + // for( auto& pair: node2element ) { + // idx_t jnode = pair.first; + // auto& elems = pair.second; + // // Log::info() << jnode << " : " << elems << std::endl; + // } + + auto get_elem_edge = [&](idx_t jelem, idx_t jedge) { + if (jedge == 0) { + return std::array{ + g_node_connectivity(jelem,0), + g_node_connectivity(jelem,1), + }; + } + else if(jedge == 1) { + return std::array{ + g_node_connectivity(jelem,1), + g_node_connectivity(jelem,2), + }; + } + else if(jedge == 2) { + return std::array{ + g_node_connectivity(jelem,2), + g_node_connectivity(jelem,0), + }; + } + return std::array{-1,-1}; + }; + + auto get_elem_neighbours = [&](idx_t jelem) -> std::array { + std::array elem_neighbours{-1,-1,-1}; + idx_t jneighbour=0; + for (idx_t jedge=0; jedge<3; ++jedge) { + auto edge = get_elem_edge(jelem,jedge); + // Log::info() << "jelem,jedge " << jelem << "," << jedge << " : " << edge << " p: " << g_part(edge[0]) << " " << g_part(edge[1]) << std::endl; + auto& elem_candidates = node2element.at(edge[0]); + for (auto& ielem : elem_candidates) { + for (idx_t iedge=0; iedge<3; ++iedge) { + auto candidate_edge = get_elem_edge(ielem,iedge); + if ( edge[0] == candidate_edge[1] && edge[1] == candidate_edge[0] ) { + elem_neighbours[jneighbour++] = ielem; + goto next_neighbour; + } + } + } + next_neighbour:; + } + return elem_neighbours; + }; + + for( idx_t jelem : element_uncertainty ) { + auto elem_neighbours = get_elem_neighbours(jelem); + idx_t e0 = elem_neighbours[0] >= 0 ? get_elem_part(elem_neighbours[0]) : UNCERTAIN; + idx_t e1 = elem_neighbours[1] >= 0 ? get_elem_part(elem_neighbours[1]) : UNCERTAIN; + idx_t e2 = elem_neighbours[2] >= 0 ? get_elem_part(elem_neighbours[2]) : UNCERTAIN; + + idx_t elem_part = UNCERTAIN; + if (e0 == e1 || e0 == e2) { + elem_part = e0; + } + else if (e1 == e2) { + elem_part = e1; + } + else if (e0 != UNCERTAIN) { + elem_part = e0; + } + else if (e1 != UNCERTAIN) { + elem_part = e1; + } + else if (e2 != UNCERTAIN) { + elem_part = e2; + } + if (elem_part == part_) { + collect_element(jelem); + } + } + } + + size_t nb_nodes = owned_nodes.size() + ghost_nodes.size(); + + mesh.nodes().resize(nb_nodes); + auto xy = array::make_view(mesh.nodes().xy()); + auto lonlat = array::make_view(mesh.nodes().lonlat()); + auto ghost = array::make_view(mesh.nodes().ghost()); + auto gidx = array::make_view(mesh.nodes().global_index()); + auto part = array::make_view(mesh.nodes().partition()); + auto halo = array::make_view(mesh.nodes().halo()); + auto flags = array::make_view(mesh.nodes().flags()); + halo.assign(0.); + std::unordered_map from_gnode; + for (idx_t jnode=0; jnode(mesh.cells().global_index()); + auto cell_part = array::make_view(mesh.cells().partition()); + + for (idx_t jelem=0; jelem triag_nodes { + from_gnode[g_node_connectivity(gelem,0)], + from_gnode[g_node_connectivity(gelem,1)], + from_gnode[g_node_connectivity(gelem,2)] + }; + node_connectivity.set(jelem, triag_nodes.data()); + cell_gidx(jelem) = gelem+1; + cell_part(jelem) = part_; + } + }; + + extract_mesh_partition(global_mesh, mesh); + + setGrid(mesh, grid, dist.type()); +} + +void DelaunayMeshGenerator::generate(const Grid& g, Mesh& mesh) const { + generate( g, grid::Distribution{g}, mesh); } namespace { diff --git a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.h b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.h index 661d71408..f397e6408 100644 --- a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.h +++ b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.h @@ -39,7 +39,9 @@ class DelaunayMeshGenerator : public MeshGenerator::Implementation { virtual void generate(const Grid&, const grid::Distribution&, Mesh&) const override; virtual void generate(const Grid&, Mesh&) const override; - void createNodes(const Grid&, Mesh&) const; + int part_; + bool remove_duplicate_points_; + bool reshuffle_; }; //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc index 8547f59f1..24388e0bf 100644 --- a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc @@ -1273,8 +1273,8 @@ void StructuredMeshGenerator::generate_mesh(const StructuredGrid& rg, const grid nodes[1] = tmp; }; - bool regular_cells_glb_idx = atlas::RegularLonLatGrid(rg); - if( options.getBool("triangulate") || y_numbering > 0) { + bool regular_cells_glb_idx = rg.regular(); + if (options.getBool("triangulate") || y_numbering > 0 || rg.y().front() != 90. || rg.y().back() != -90.) { regular_cells_glb_idx = false; } @@ -1580,7 +1580,11 @@ void StructuredMeshGenerator::generate_mesh(const StructuredGrid& rg, const grid } } } - if (not regular_cells_glb_idx) { + if (regular_cells_glb_idx) { + mesh.cells().global_index().metadata().set("min", 1); + mesh.cells().global_index().metadata().set("max", rg.nx(0) * (rg.ny()-1) ); + } + else { generateGlobalElementNumbering(mesh); } } diff --git a/src/atlas/option/Options.cc b/src/atlas/option/Options.cc index 0bad47b32..10e901278 100644 --- a/src/atlas/option/Options.cc +++ b/src/atlas/option/Options.cc @@ -26,15 +26,15 @@ halo::halo(size_t size) { set("halo", size); } -datatype::datatype(array::DataType::kind_t kind) { +datatype::datatype(DataType::kind_t kind) { set("datatype", kind); } datatype::datatype(const std::string& str) { - set("datatype", array::DataType::str_to_kind(str)); + set("datatype", DataType::str_to_kind(str)); } -datatype::datatype(array::DataType dtype) { +datatype::datatype(DataType dtype) { set("datatype", dtype.kind()); } diff --git a/src/atlas/option/Options.h b/src/atlas/option/Options.h index 04d11d72a..7416c851f 100644 --- a/src/atlas/option/Options.h +++ b/src/atlas/option/Options.h @@ -10,7 +10,8 @@ #pragma once -#include "atlas/array/DataType.h" +#include "atlas/util/DataType.h" + #include "atlas/util/Config.h" // ---------------------------------------------------------------------------- @@ -72,9 +73,9 @@ class datatypeT : public util::Config { class datatype : public util::Config { public: - datatype(array::DataType::kind_t); + datatype(DataType::kind_t); datatype(const std::string&); - datatype(array::DataType); + datatype(DataType); }; // ---------------------------------------------------------------------------- @@ -120,7 +121,7 @@ class pole_edges : public util::Config { template datatypeT::datatypeT() { - set("datatype", array::DataType::kind()); + set("datatype", DataType::kind()); } } // namespace option diff --git a/src/atlas/parallel/mpi/Statistics.h b/src/atlas/parallel/mpi/Statistics.h index 7905dbedd..95543989b 100644 --- a/src/atlas/parallel/mpi/Statistics.h +++ b/src/atlas/parallel/mpi/Statistics.h @@ -19,7 +19,7 @@ #if ATLAS_HAVE_TRACE -#include "atlas/util/detail/BlackMagic.h" +#include "atlas/library/detail/BlackMagic.h" #undef ATLAS_TRACE_MPI #define ATLAS_TRACE_MPI(...) ATLAS_TRACE_MPI_(::atlas::mpi::Trace, Here(), __VA_ARGS__) diff --git a/src/atlas/projection/detail/CubedSphereEquiAnglProjection.h b/src/atlas/projection/detail/CubedSphereEquiAnglProjection.h index 7c2631f74..510d29e1a 100644 --- a/src/atlas/projection/detail/CubedSphereEquiAnglProjection.h +++ b/src/atlas/projection/detail/CubedSphereEquiAnglProjection.h @@ -7,7 +7,6 @@ #pragma once -#include "atlas/array.h" #include "atlas/domain.h" #include "atlas/projection/Jacobian.h" #include "atlas/projection/detail/CubedSphereProjectionBase.h" diff --git a/src/atlas/projection/detail/CubedSphereEquiDistProjection.h b/src/atlas/projection/detail/CubedSphereEquiDistProjection.h index 60480b909..6776fc39c 100644 --- a/src/atlas/projection/detail/CubedSphereEquiDistProjection.h +++ b/src/atlas/projection/detail/CubedSphereEquiDistProjection.h @@ -7,7 +7,6 @@ #pragma once -#include "atlas/array.h" #include "atlas/domain.h" #include "atlas/projection/detail/CubedSphereProjectionBase.h" #include "atlas/projection/detail/ProjectionImpl.h" diff --git a/src/atlas/projection/detail/VariableResolutionProjection.cc b/src/atlas/projection/detail/VariableResolutionProjection.cc index b8cf952e8..d3d17ab82 100644 --- a/src/atlas/projection/detail/VariableResolutionProjection.cc +++ b/src/atlas/projection/detail/VariableResolutionProjection.cc @@ -450,17 +450,9 @@ double VariableResolutionProjectionT::general_stretch_inv(const double * simply using delta_high */ - double distance_to_inner; ///< distance from point to reg. grid double delta_add; ///< additional part in stretch different from internal high resolution double new_ratio = new_ratio_[L_long ? 1 : 0]; - if (point_st < inner_start) { - distance_to_inner = inner_start - point_st; - } - else if (point_st > inner_end) { - distance_to_inner = point_st - inner_end; - } - /* * number of high resolution points intervals, that are not * in the internal regular grid on one side, diff --git a/src/atlas/runtime/Exception.h b/src/atlas/runtime/Exception.h index c1d2dc288..5220c835a 100644 --- a/src/atlas/runtime/Exception.h +++ b/src/atlas/runtime/Exception.h @@ -53,7 +53,7 @@ inline void Assert(bool success, const char* code, const std::string& msg, const } // namespace detail } // namespace atlas -#include "atlas/util/detail/BlackMagic.h" +#include "atlas/library/detail/BlackMagic.h" #define ATLAS_NOTIMPLEMENTED ::atlas::throw_NotImplemented(Here()) diff --git a/src/atlas/runtime/Log.h b/src/atlas/runtime/Log.h index a9bd35b37..91babf256 100644 --- a/src/atlas/runtime/Log.h +++ b/src/atlas/runtime/Log.h @@ -35,7 +35,7 @@ std::string backtrace(); } // namespace atlas #include -#include "atlas/util/detail/BlackMagic.h" +#include "atlas/library/detail/BlackMagic.h" #include "eckit/log/CodeLocation.h" namespace atlas { diff --git a/src/atlas/runtime/Trace.h b/src/atlas/runtime/Trace.h index 1fd22ce03..9d7cf38cf 100644 --- a/src/atlas/runtime/Trace.h +++ b/src/atlas/runtime/Trace.h @@ -85,7 +85,7 @@ class Trace : public runtime::trace::TraceT { #if ATLAS_HAVE_TRACE -#include "atlas/util/detail/BlackMagic.h" +#include "atlas/library/detail/BlackMagic.h" #undef ATLAS_TRACE #undef ATLAS_TRACE_SCOPE diff --git a/src/atlas/util/Config.h b/src/atlas/util/Config.h index 97a736872..6c9e9a42b 100644 --- a/src/atlas/util/Config.h +++ b/src/atlas/util/Config.h @@ -11,6 +11,7 @@ #pragma once #include #include +#include #include "eckit/config/LocalConfiguration.h" #include "eckit/log/JSON.h" @@ -44,6 +45,9 @@ class Config : public eckit::LocalConfiguration { template Config(const std::string& name, const ValueT& value); + Config(const std::string& name, const std::string_view value) : + Config(name, std::string(value)) {} + template Config(const std::string& name, std::initializer_list&& value); diff --git a/src/atlas/util/ConvexSphericalPolygon.cc b/src/atlas/util/ConvexSphericalPolygon.cc index fe266f87f..40b3584c7 100644 --- a/src/atlas/util/ConvexSphericalPolygon.cc +++ b/src/atlas/util/ConvexSphericalPolygon.cc @@ -19,9 +19,9 @@ #include "atlas/util/ConvexSphericalPolygon.h" #include "atlas/util/CoordinateEnums.h" #include "atlas/util/NormaliseLongitude.h" +#include "atlas/library/FloatingPointExceptions.h" -#define DEBUG_OUTPUT 0 -#define DEBUG_OUTPUT_DETAIL 0 +// #define DEBUG_OUTPUT 1 namespace atlas { namespace util { @@ -32,14 +32,6 @@ namespace { constexpr double EPS = std::numeric_limits::epsilon(); constexpr double EPS2 = EPS * EPS; -constexpr double TOL = 1.e4 * EPS; // two points considered "same" -constexpr double TOL2 = TOL * TOL; - -enum IntersectionType -{ - NO_INTERSECT = -100, - OVERLAP -}; double distance2(const PointXYZ& p1, const PointXYZ& p2) { double dx = p2[0] - p1[0]; @@ -52,38 +44,35 @@ double norm2(const PointXYZ& p) { return p[0] * p[0] + p[1] * p[1] + p[2] * p[2]; } -bool approx_eq(const double& v1, const double& v2, const double& tol) { - return std::abs(v1 - v2) < tol; +bool approx_eq(const double& v1, const double& v2) { + return std::abs(v1 - v2) <= EPS; } -bool approx_eq(const PointXYZ& v1, const PointXYZ& v2, const double& tol) { +bool approx_eq(const PointXYZ& v1, const PointXYZ& v2) { //return approx_eq( v1[0], v2[0], t ) && approx_eq( v1[1], v2[1], t ) && approx_eq( v1[2], v2[2], t ); - return distance2(v1, v2) < tol * tol; -} - -bool approx_eq_null(const PointXYZ& v1, const double& tol) { - //return approx_eq( v1[0], 0., t ) && approx_eq( v1[1], 0., t ) && approx_eq( v1[2], 0., t ); - double n = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]; - return n < tol * tol; + return distance2(v1, v2) <= EPS2; } void lonlat2xyz(const PointLonLat& lonlat, PointXYZ& xyz) { eckit::geometry::Sphere::convertSphericalToCartesian(1., lonlat, xyz); } -void xyz2lonlat(const PointXYZ xyz, PointLonLat& lonlat) { +void xyz2lonlat(const PointXYZ& xyz, PointLonLat& lonlat) { eckit::geometry::Sphere::convertCartesianToSpherical(1., xyz, lonlat); } -double norm_max(const PointXYZ& p, const PointXYZ& q) { - double n01 = std::max(std::abs(p[0] - q[0]), std::abs(p[1] - q[1])); - return std::max(n01, std::abs(p[2] - q[2])); +PointLonLat xyz2lonlat(const PointXYZ& xyz) { + PointLonLat lonlat; + eckit::geometry::Sphere::convertCartesianToSpherical(1., xyz, lonlat); + return lonlat; } -template +#if 0 +// NOTE: StackVector is not used +template struct StackVector { private: - using Wrapped = std::array; + using Wrapped = std::array; public: using reference = typename Wrapped::reference; @@ -101,7 +90,7 @@ struct StackVector { #endif void push_back(const T& value) { wrapped_[size_++] = value; - ATLAS_ASSERT(size_ < ConvexSphericalPolygon::MAX_SIZE); + ATLAS_ASSERT(size_ < MAX_SIZE); } size_t size() const { return size_; } @@ -109,38 +98,7 @@ struct StackVector { size_t size_{0}; Wrapped wrapped_; }; - -struct PolygonEdgeIntersection { - static constexpr int BEGIN = 1; - static constexpr int END = 2; - static constexpr int INSIDE = 3; - - PolygonEdgeIntersection(const ConvexSphericalPolygon& polygon, int edge_index, const PointXYZ& point) { - auto matches = [](const PointXYZ& p1, const PointXYZ& p2) { - return (distance2(p1, p2) < 1e-16); - // We would like this to be TOL2 instead, but that gives bad results - }; - - ATLAS_ASSERT(edge_index >= 0); - ATLAS_ASSERT(edge_index < polygon.size()); - - const int node_index = edge_index; - - if (matches(point, polygon[node_index])) { - location = BEGIN; - } - else if (matches(point, polygon[polygon.next(node_index)])) { - location = END; - } - else { - location = INSIDE; - } - } - bool isPointAtBegin() const { return location == BEGIN; } - bool isPointAtEnd() const { return location == END; } - bool isPointInside() const { return location == INSIDE; } - int location; -}; +#endif } // namespace @@ -153,13 +111,14 @@ ConvexSphericalPolygon::ConvexSphericalPolygon(const PointLonLat points[], size_ size_t isp = 1; for (size_t i = 1; i < size_ - 1; ++i) { lonlat2xyz(points[i], sph_coords_[isp]); - if (approx_eq(sph_coords_[isp], sph_coords_[isp - 1], TOL)) { +// Log::info() << " d : " << PointXYZ::distance(sph_coords_[isp], sph_coords_[isp - 1]) << std::endl; + if (approx_eq(sph_coords_[isp], sph_coords_[isp - 1])) { continue; } ++isp; } lonlat2xyz(points[size_ - 1], sph_coords_[isp]); - if (approx_eq(sph_coords_[isp], sph_coords_[0], TOL) or approx_eq(sph_coords_[isp], sph_coords_[isp - 1], TOL)) { + if (approx_eq(sph_coords_[isp], sph_coords_[0]) or approx_eq(sph_coords_[isp], sph_coords_[isp - 1])) { } else { ++isp; @@ -178,13 +137,13 @@ ConvexSphericalPolygon::ConvexSphericalPolygon(const PointXYZ points[], size_t s size_t isp = 1; for (size_t i = 1; i < size_ - 1; ++i) { sph_coords_[isp] = points[i]; - if (approx_eq(sph_coords_[isp], sph_coords_[isp - 1], TOL)) { + if (approx_eq(sph_coords_[isp], sph_coords_[isp - 1])) { continue; } ++isp; } sph_coords_[isp] = points[size_ - 1]; - if (approx_eq(sph_coords_[isp], sph_coords_[0], TOL) or approx_eq(sph_coords_[isp], sph_coords_[isp - 1], TOL)) { + if (approx_eq(sph_coords_[isp], sph_coords_[0]) or approx_eq(sph_coords_[isp], sph_coords_[isp - 1])) { } else { ++isp; @@ -196,74 +155,8 @@ ConvexSphericalPolygon::ConvexSphericalPolygon(const PointXYZ points[], size_t s } } - -void ConvexSphericalPolygon::simplify() { - ATLAS_ASSERT(size_ < MAX_SIZE); - if (size_ < 3) { - size_ = 0; - valid_ = false; - return; - } - idx_t isp = 0; - idx_t i = 0; - idx_t j; - idx_t k; - bool search_3pts = true; - auto& points = sph_coords_; - for (; i < size_ && search_3pts; ++i) { - const PointXYZ& P0 = points[i]; - for (j = i + 1; j < size_ && search_3pts; ++j) { - const PointXYZ& P1 = points[j]; - if (approx_eq(P0, P1, 1.e-10)) { - continue; - } - for (k = j + 1; k < size_ && search_3pts; ++k) { - const PointXYZ& P2 = points[k]; - if (approx_eq(P1, P2, TOL) or approx_eq(P0, P2, TOL)) { - continue; - } - if (GreatCircleSegment{P0, P1}.inLeftHemisphere(P2, -EPS)) { - sph_coords_[isp++] = P0; - sph_coords_[isp++] = P1; - sph_coords_[isp++] = P2; - search_3pts = false; - } - } - } - } - if (search_3pts) { - valid_ = false; - size_ = 0; - return; - } - for (; k < size_ - 1; ++k) { - if (approx_eq(points[k], sph_coords_[isp - 1], TOL) or - (not GreatCircleSegment{sph_coords_[isp - 2], sph_coords_[isp - 1]}.inLeftHemisphere(points[k], -EPS))) { - continue; - } - sph_coords_[isp] = points[k]; - isp++; - } - const PointXYZ& Pl2 = sph_coords_[isp - 2]; - const PointXYZ& Pl1 = sph_coords_[isp - 1]; - const PointXYZ& P0 = sph_coords_[0]; - const PointXYZ& P = points[size_ - 1]; - if ((not approx_eq(P, P0, EPS)) and (not approx_eq(P, Pl1, EPS)) and - GreatCircleSegment{Pl2, Pl1}.inLeftHemisphere(P, -EPS) and - GreatCircleSegment{Pl1, P}.inLeftHemisphere(P0, -EPS)) { - sph_coords_[isp] = P; - ++isp; - } - size_ = isp; - valid_ = size_ > 2; - - computed_area_ = false; - computed_radius_ = false; - computed_centroid_ = false; -} - void ConvexSphericalPolygon::compute_centroid() const { - const auto triangles = triangulate(radius()); + const auto triangles = triangulate(); area_ = triangles.area(); computed_area_ = true; @@ -288,9 +181,9 @@ bool ConvexSphericalPolygon::validate() { int nni = next(ni); const PointXYZ& P = sph_coords_[i]; const PointXYZ& nextP = sph_coords_[ni]; - ATLAS_ASSERT(std::abs(PointXYZ::dot(P, P) - 1.) < 10. * EPS); - ATLAS_ASSERT(not approx_eq(P, PointXYZ::mul(nextP, -1.), TOL)); - valid_ = valid_ && GreatCircleSegment{P, nextP}.inLeftHemisphere(sph_coords_[nni], -EPS); + ATLAS_ASSERT(std::abs(PointXYZ::dot(P, P) - 1.) < 10 * EPS); + ATLAS_ASSERT(not approx_eq(P, PointXYZ::mul(nextP, -1.))); + valid_ = valid_ && GreatCircleSegment{P, nextP}.inLeftHemisphere(sph_coords_[nni], -0.5*EPS); } } return valid_; @@ -300,8 +193,16 @@ bool ConvexSphericalPolygon::equals(const ConvexSphericalPolygon& plg, const dou if (size_ == 0 and plg.size_ == 0) { return true; } - if ((not plg.valid_) || (not valid_) || size_ != plg.size()) { - Log::info() << " ConvexSphericalPolygon::equals == not compatible\n"; + if (not valid_) { + Log::info() << " ConvexSphericalPolygon::equals : this polygon is not valid\n"; + return false; + } + if (not plg.valid_) { + Log::info() << " ConvexSphericalPolygon::equals : other polygon passed as argument is not valid\n"; + return false; + } + if (size_ != plg.size()) { + Log::info() << " ConvexSphericalPolygon::equals : incompatible sizes: " << size_ << " != " << plg.size() << "\n"; return false; } int offset = 0; @@ -313,7 +214,7 @@ bool ConvexSphericalPolygon::equals(const ConvexSphericalPolygon& plg, const dou } } if (offset == size_) { - Log::info() << "ConvexSphericalPolygon::equals == no point equal\n"; + Log::info() << "ConvexSphericalPolygon::equals : no point equal\n"; return false; } @@ -321,82 +222,38 @@ bool ConvexSphericalPolygon::equals(const ConvexSphericalPolygon& plg, const dou int idx = (offset + j) % size_; auto dist2 = distance2(plg.sph_coords_[j], sph_coords_[idx]); if (dist2 > le2) { - Log::info() << " ConvexSphericalPolygon::equals == point distance " << std::sqrt(dist2) << "\n"; + Log::info() << " ConvexSphericalPolygon::equals : point distance " << std::sqrt(dist2) << " < " << le2 << "(= "<< deg_prec << " deg)" << "\n"; return false; } } return true; } -// note: unit sphere! -// I. Todhunter (1886), Paragr. 99 -ConvexSphericalPolygon::SubTriangles ConvexSphericalPolygon::triangulate(const double cell_radius) const { +// cf. Folke Eriksson, "On the Measure of Solid Angles", Mathematics Magazine, Vol. 63, No. 3, pp. 184-187 (1990) +ConvexSphericalPolygon::SubTriangles ConvexSphericalPolygon::triangulate() const { SubTriangles triangles; if (size_ < 3) { return triangles; } - size_t itri{0}; - if (cell_radius < 1.e-6) { // plane area - for (int i = 1; i < size_ - 1; i++) { - const PointXYZ pl = sph_coords_[i] - sph_coords_[0]; - const PointXYZ pr = sph_coords_[i + 1] - sph_coords_[0]; - triangles[itri].centroid = PointXYZ::normalize(sph_coords_[0] + sph_coords_[i] + sph_coords_[i + 1]); - triangles[itri].area = 0.5 * PointXYZ::norm(PointXYZ::cross(pl, pr)); - ++itri; - } - } - else { // spherical area - const PointXYZ& a = sph_coords_[0]; - for (size_t i = 1; i < size_ - 1; i++) { - const PointXYZ& b = sph_coords_[i]; - const PointXYZ& c = sph_coords_[i + 1]; - auto ab = PointXYZ::cross(a, b); - auto bc = PointXYZ::cross(b, c); - auto ca = PointXYZ::cross(c, a); - const double ab_norm = PointXYZ::norm(ab); - const double bc_norm = PointXYZ::norm(bc); - const double ca_norm = PointXYZ::norm(ca); - if (ab_norm < EPS or bc_norm < EPS or ca_norm < EPS) { - continue; - } - double abc = -PointXYZ::dot(ab, bc) / (ab_norm * bc_norm); - double bca = -PointXYZ::dot(bc, ca) / (bc_norm * ca_norm); - double cab = -PointXYZ::dot(ca, ab) / (ca_norm * ab_norm); - if (abc <= -1.) { - abc = M_PI; - } - else if (abc < 1.) { - abc = std::acos(abc); - } - else { - abc = 0.; - } - if (bca <= -1.) { - bca = M_PI; - } - else if (bca < 1.) { - bca = std::acos(bca); - } - else { - bca = 0.; - } - if (cab <= -1.) { - cab = M_PI; - } - else if (cab < 1.) { - cab = std::acos(cab); - } - else { - cab = 0.; - } - triangles[itri].centroid = PointXYZ::normalize(a + b + c); - triangles[itri].area = abc + bca + cab - M_PI; - ++itri; - } + const PointXYZ& a = sph_coords_[0]; + for (size_t i = 1; i < size_ - 1; i++) { + const PointXYZ& b = sph_coords_[i]; + const PointXYZ& c = sph_coords_[i + 1]; + triangles[itri].centroid = PointXYZ::normalize(a + b + c); +// if (PointXYZ::distance(a, b) + PointXYZ::distance(b, c) < 1e-10) { +// triangles[itri].area = 0.5 * PointXYZ::norm(PointXYZ::cross(b - a, c - b)); +// } +// else { + auto abc = PointXYZ::dot(a, b) + PointXYZ::dot(b, c) + PointXYZ::dot(c, a); + auto a_bc = PointXYZ::dot(a, PointXYZ::cross(b, c)); + triangles[itri].area = 2. * std::atan(std::abs(a_bc) / (1. + abc)); +// } + ++itri; } triangles.size() = itri; return triangles; + } @@ -408,95 +265,93 @@ double ConvexSphericalPolygon::SubTriangles::area() const { return area; } -// @return lowest point id of this polygon's segment intersecting [s1,s2)) -int ConvexSphericalPolygon::intersect(const int start, const GreatCircleSegment& s, PointXYZ& I) const { - for (int i = start; i < size_; i++) { - const int id0 = i; - const int id1 = next(i); - const GreatCircleSegment p(sph_coords_[id0], sph_coords_[id1]); - I = s.intersect(p); - if (I[0] == 0 && I[1] == 0 && I[2] == 0) { - // intersection not on [p1,p2) - continue; - } - if (I[0] == 1 && I[1] == 1) { - return OVERLAP; - } - return id0; - } - return NO_INTERSECT; -} - - -void ConvexSphericalPolygon::clip(const GreatCircleSegment& great_circle) { +void ConvexSphericalPolygon::clip(const GreatCircleSegment& great_circle, std::ostream* out, double pointsSameEPS) { ATLAS_ASSERT(valid_); - ATLAS_ASSERT(distance2(great_circle.first(), great_circle.second()) > TOL2); - + ATLAS_ASSERT(not approx_eq(great_circle.first(), great_circle.second())); + std::vector clipped_sph_coords; + clipped_sph_coords.reserve(ConvexSphericalPolygon::MAX_SIZE); auto invalidate_this_polygon = [&]() { size_ = 0; valid_ = false; area_ = 0.; }; - - // Count and mark all vertices to be possibly considered in clipped polygon - StackVector vertex_in(size_); - int num_vertices_in = 0; +#if DEBUG_OUTPUT + int add_point_num = 0; +#endif + bool first_in = great_circle.inLeftHemisphere(sph_coords_[0], -1.5 * EPS, out); for (int i = 0; i < size_; i++) { - vertex_in[i] = great_circle.inLeftHemisphere(sph_coords_[i], -10. * TOL); - num_vertices_in += vertex_in[i] ? 1 : 0; - } - - PointXYZ i1; - const int f1 = intersect(0, great_circle, i1); - const bool segment_only_touches_last_point = (f1 == size_ - 1); - if (f1 == OVERLAP || f1 == NO_INTERSECT || segment_only_touches_last_point) { - if (num_vertices_in < 3) { - invalidate_this_polygon(); - } - return; - } - PolygonEdgeIntersection intersection_1(*this, f1, i1); - - PointXYZ i2; // second intersection point - auto start2 = [&]() { return f1 + 1 + (intersection_1.isPointAtEnd() ? 1 : 0); }; - const int f2 = intersect(start2(), great_circle, i2); - if (f2 == OVERLAP || f2 == NO_INTERSECT) { - if (num_vertices_in < 3) { - invalidate_this_polygon(); + int in = (i+1) % size_; + bool second_in = great_circle.inLeftHemisphere(sph_coords_[in], -1.5 * EPS, out); +#if DEBUG_OUTPUT + if (out) { + out->precision(18); + *out << " ** first: " << xyz2lonlat(sph_coords_[i]) << ", in ? " << first_in << std::endl; + *out << " second: " << xyz2lonlat(sph_coords_[in]) << ", in ? " << second_in << std::endl; } - return; - } - PolygonEdgeIntersection intersection_2(*this, f2, i2); - - // Create new vector of clipped coordinates - StackVector clipped_sph_coords; - { - auto keep_vertex = [&](int index) { clipped_sph_coords.push_back(sph_coords_[index]); }; - auto insert_point = [&](const PointXYZ& p) { clipped_sph_coords.push_back(p); }; - - for (int i = 0; i <= f1; i++) { - if (vertex_in[i]) { - keep_vertex(i); +#endif + if (first_in and second_in) { + clipped_sph_coords.emplace_back(sph_coords_[in]); +#if DEBUG_OUTPUT + if (out) { + *out << " ((" << ++add_point_num << ")) both_in add second: " << xyz2lonlat(sph_coords_[in]) << std::endl; } +#endif } - if ((not vertex_in[f1] and intersection_1.isPointAtBegin()) or - (not vertex_in[next(f1)] and intersection_1.isPointAtEnd()) or intersection_1.isPointInside()) { - insert_point(i1); + else if (not first_in and not second_in) { + // continue to update first_in } - for (int i = f1 + 1; i <= f2; i++) { - if (vertex_in[i]) { - keep_vertex(i); + else { + const GreatCircleSegment segment(sph_coords_[i], sph_coords_[in]); + PointXYZ ip = great_circle.intersect(segment, out, pointsSameEPS); +#if DEBUG_OUTPUT + if (out) { + *out << " ip : " << xyz2lonlat(ip) << std::endl; } - } - if ((not vertex_in[f2] and intersection_2.isPointAtBegin()) or - (not vertex_in[next(f2)] and intersection_2.isPointAtEnd()) or intersection_2.isPointInside()) { - insert_point(i2); - } - for (int i = f2 + 1; i < size_; i++) { - if (vertex_in[i]) { - keep_vertex(i); +#endif + if (ip[0] == 1 and ip[1] == 1 and ip[2] == 1) { + // consider the segments parallel +#if DEBUG_OUTPUT + if (out) { + *out << " ((" << ++add_point_num << ")) ip=(1,1,1) add second: " + << xyz2lonlat(sph_coords_[in]) << std::endl; + } +#endif + clipped_sph_coords.emplace_back(sph_coords_[in]); + first_in = second_in; + continue; + } + if (second_in) { + int inn = (in+1) % size_; + const GreatCircleSegment segment_n(sph_coords_[in], sph_coords_[inn]); + if (segment.inLeftHemisphere(ip, -1.5 * EPS, out) and + segment_n.inLeftHemisphere(ip, -1.5 * EPS, out) and + (PointXYZ::distance(ip, sph_coords_[in]) > pointsSameEPS)) { + clipped_sph_coords.emplace_back(ip); +#if DEBUG_OUTPUT + if (out) { + *out << " ((" << ++add_point_num << ")) second_in add ip: " << xyz2lonlat(ip) << std::endl; + } +#endif + } + clipped_sph_coords.emplace_back(sph_coords_[in]); +#if DEBUG_OUTPUT + if (out) { + *out << " ((" << ++add_point_num << ")) second_in add second: " << xyz2lonlat(sph_coords_[in]) << std::endl; + } +#endif + } + else { + if (PointXYZ::distance(ip, sph_coords_[i]) > pointsSameEPS) { + clipped_sph_coords.emplace_back(ip); +#if DEBUG_OUTPUT + if (out) { + *out << " ((" << ++add_point_num << ")) first_in add ip: " << xyz2lonlat(ip) << std::endl; + } +#endif + } } } + first_in = second_in; } // Update polygon @@ -516,22 +371,68 @@ void ConvexSphericalPolygon::clip(const GreatCircleSegment& great_circle) { // intersect a polygon with this polygon // @param[in] pol clipping polygon // @param[out] intersecting polygon -ConvexSphericalPolygon ConvexSphericalPolygon::intersect(const ConvexSphericalPolygon& plg) const { - ConvexSphericalPolygon intersection = *this; +ConvexSphericalPolygon ConvexSphericalPolygon::intersect(const ConvexSphericalPolygon& plg, std::ostream* out, double pointsSameEPS) const { + + bool fpe_disabled = atlas::library::disable_floating_point_exception(FE_INVALID); + auto restore_fpe = [fpe_disabled] { + if (fpe_disabled) { + atlas::library::enable_floating_point_exception(FE_INVALID); + } + }; + + // the larger area polygon is the intersector + ConvexSphericalPolygon intersection; + ConvexSphericalPolygon intersector; + std::string intor_id = "P1"; + std::string inted_id = "P2"; + + bool this_intersector = (area() > plg.area()); + if (approx_eq(area(), plg.area())) { + PointXYZ dc = centroid() - plg.centroid(); + if (dc[0] > 0. or (dc[0] == 0. and (dc[1] > 0. or (dc[1] == 0. and dc[2] > 0.)))) { + this_intersector = true; + } + } + if (this_intersector) { + intersector = *this; + intersection = plg; + } + else { + intor_id = "P2"; + inted_id = "P1"; + intersector = plg; + intersection = *this; + } +#if DEBUG_OUTPUT + if (out) { + *out << inted_id << " : "; + print(*out); + *out << std::endl << intor_id << " : "; + plg.print(*out); + *out << std::endl; + } +#endif if (intersection.valid_) { - for (size_t i = 0; i < plg.size_; i++) { - const PointXYZ& s1 = plg.sph_coords_[i]; - const PointXYZ& s2 = plg.sph_coords_[(i != plg.size_ - 1) ? i + 1 : 0]; - intersection.clip(GreatCircleSegment(s1, s2)); + for (size_t i = 0; i < intersector.size_; i++) { + const PointXYZ& s1 = intersector.sph_coords_[i]; + const PointXYZ& s2 = intersector.sph_coords_[(i != intersector.size_ - 1) ? i + 1 : 0]; +#if DEBUG_OUTPUT + if (out) { + *out << std::endl << "Clip with [" << intor_id << "_" << i << ", " << intor_id << "_" + << (i+1) % intersector.size_ << "]" << std::endl; + } +#endif + intersection.clip(GreatCircleSegment(s1, s2), out, pointsSameEPS); if (not intersection.valid_) { + restore_fpe(); return intersection; } } } - intersection.simplify(); intersection.computed_area_ = false; intersection.computed_radius_ = false; intersection.computed_centroid_ = false; + restore_fpe(); return intersection; } @@ -548,72 +449,64 @@ void ConvexSphericalPolygon::print(std::ostream& out) const { out << "}"; } +std::string ConvexSphericalPolygon::json(int precision) const { + std::stringstream ss; + if( precision ) { + ss.precision(16); + } + ss << "["; + for (size_t i = 0; i < size(); ++i) { + if (i > 0) { + ss << ","; + } + PointLonLat ip_ll; + xyz2lonlat(sph_coords_[i], ip_ll); + ss << "[" << ip_ll.lon() << "," << ip_ll.lat() << "]"; + } + ss << "]"; + return ss.str(); +} + + double ConvexSphericalPolygon::compute_radius() const { double radius{0.}; if (valid_) { - PointXYZ centroid; - centroid = sph_coords_[0]; - size_t isp = 1; - for (size_t i = 1; i < size_; ++i) { - if (approx_eq(sph_coords_[isp], sph_coords_[isp - 1], TOL)) { - continue; - } - centroid = centroid + sph_coords_[isp]; - ++isp; + if (not computed_centroid_) { + compute_centroid(); } - centroid = PointXYZ::div(centroid, PointXYZ::norm(centroid)); - for (size_t i = 0; i < size_; ++i) { - radius = std::max(radius, PointXYZ::distance(sph_coords_[i], centroid)); + radius = std::max(radius, PointXYZ::distance(sph_coords_[i], centroid_)); } } return radius; } -bool ConvexSphericalPolygon::GreatCircleSegment::contains(const PointXYZ& p) const { - /* - * @brief Point-on-segment test on great circle segments - * @param[in] P given point in (x,y,z) coordinates - * @return - */ - constexpr double eps_large = 1.e3 * EPS; - - // Case where p is one of the endpoints - double pp1n2 = distance2(p, p1_); - double pp2n2 = distance2(p, p2_); - if (pp1n2 < EPS2 or pp2n2 < EPS2) { - return true; - } - - PointXYZ p12 = cross(); - double p12n2 = norm2(p12); - double p12n = std::sqrt(p12n2); - p12 /= p12n; - if (std::abs(PointXYZ::dot(p, p12)) > eps_large) { - return false; - } - double pp = PointXYZ::distance(p1_, p2_); - double pp1 = PointXYZ::distance(p, p1_); - double pp2 = PointXYZ::distance(p, p2_); - return (std::min(pp - pp1, pp - pp2) > -eps_large); -} - -PointXYZ ConvexSphericalPolygon::GreatCircleSegment::intersect(const GreatCircleSegment& p) const { +// 'this' great circle's intersection with the segment 'p': [p.first(), p.second()) +PointXYZ ConvexSphericalPolygon::GreatCircleSegment::intersect(const GreatCircleSegment& p, std::ostream* out, double pointsSameEPS) const { const auto& s = *this; PointXYZ sp = PointXYZ::cross(s.cross(), p.cross()); double sp_norm = PointXYZ::norm(sp); - if (sp_norm > EPS) { + bool gcircles_distinct = (sp_norm > EPS); +#if DEBUG_OUTPUT + if (out) { + *out << " Great circles distinct ? " << sp_norm << " > " << EPS << " ? " + << gcircles_distinct << std::endl; + } +#endif + if (gcircles_distinct) { sp /= sp_norm; - if (p.contains(sp)) { + auto sp_2 = sp * -1.; + double d = distance2(p.first(), p.second()); + double d1 = std::max(distance2(sp, p.first()), distance2(sp, p.second())); + double d2 = std::max(distance2(sp_2, p.first()), distance2(sp_2, p.second())); + if (d1 < d2) { return sp; } - sp *= -1.; - if (p.contains(sp)) { - return sp; + else { + return sp_2; } - return PointXYZ(0, 0, 0); } else { return PointXYZ(1, 1, 1); diff --git a/src/atlas/util/ConvexSphericalPolygon.h b/src/atlas/util/ConvexSphericalPolygon.h index 9a5783fa0..7b3338fd5 100644 --- a/src/atlas/util/ConvexSphericalPolygon.h +++ b/src/atlas/util/ConvexSphericalPolygon.h @@ -16,6 +16,9 @@ #include "atlas/util/Point.h" #include "atlas/util/Polygon.h" +#include "atlas/runtime/Log.h" + +#define DEBUG_OUTPUT 1 namespace atlas { namespace util { @@ -31,17 +34,20 @@ class ConvexSphericalPolygon { class GreatCircleSegment { public: GreatCircleSegment(const PointXYZ& p1, const PointXYZ& p2): p1_(p1), p2_(p2), cross_(PointXYZ::cross(p1, p2)) {} - bool contains(const PointXYZ&) const; - - PointXYZ intersect(const GreatCircleSegment&) const; - // Hemisphere is defined by segment when walking from first() to second() - // Positive offset: distance into left hemisphere, e.g. to exclude segment itself with tolerance - // Negative offset: distance into right hemisphere, e.g. to include segment with tolerance - bool inLeftHemisphere(const PointXYZ& P, const double offset = 0.) const { - return (PointXYZ::dot(cross(), P) > offset); + // For a given segment, the "left" hemisphere is defined on the left of the segment when walking from first() to second() + inline bool inLeftHemisphere(const PointXYZ& P, const double offset = 0., std::ostream* out = NULL) const { +#if DEBUG_OUTPUT + if (out) { + *out << " inLeftHemi: " <= " << offset << " ? " + << (PointXYZ::dot(cross(), P) >= offset) << std::endl; + } +#endif + return (PointXYZ::dot(cross(), P) >= offset); // has to have = included } + PointXYZ intersect(const GreatCircleSegment&, std::ostream* f = NULL, double pointsSameEPS = std::numeric_limits::epsilon()) const; + const PointXYZ& first() const { return p1_; } const PointXYZ& second() const { return p2_; } @@ -96,7 +102,7 @@ class ConvexSphericalPolygon { return radius_; } - ConvexSphericalPolygon intersect(const ConvexSphericalPolygon& pol) const; + ConvexSphericalPolygon intersect(const ConvexSphericalPolygon& pol, std::ostream* f = nullptr, double pointsEqualEPS = std::numeric_limits::epsilon()) const; /* * @brief check if two spherical polygons area equal @@ -107,6 +113,8 @@ class ConvexSphericalPolygon { void print(std::ostream&) const; + std::string json(int precision = 0) const; + friend std::ostream& operator<<(std::ostream& out, const ConvexSphericalPolygon& p) { p.print(out); return out; @@ -136,29 +144,15 @@ class ConvexSphericalPolygon { double compute_radius() const; - SubTriangles triangulate(const double radius) const; + SubTriangles triangulate() const; - void clip(const GreatCircleSegment&); + void clip(const GreatCircleSegment&, std::ostream* f = nullptr, double pointsSameEPS = std::numeric_limits::epsilon()); /* * @return true:polygon is convex */ bool validate(); - /* - * @brief Segment-sph_polygon intersection - * @param[in] s1, s2 segment endpoints in (x,y,z) coordinates - * @param[in] start start with polygon segments [pol[start],pol[start+1]],... - * @param[out] ip intersection point or nullptr - * @return -1: overlap with one of polygon edges, - * 0: no_intersect, - * 1 + (id of this polygon's segment intersecting [s1,s2)): otherwise - */ - int intersect(const int start, const GreatCircleSegment&, PointXYZ& ip) const; - - /// Makes the polygon convex and skips consequtive node that is too close to previous - void simplify(); - private: std::array sph_coords_; mutable PointXYZ centroid_; diff --git a/src/atlas/array/DataType.cc b/src/atlas/util/DataType.cc similarity index 100% rename from src/atlas/array/DataType.cc rename to src/atlas/util/DataType.cc diff --git a/src/atlas/util/DataType.h b/src/atlas/util/DataType.h new file mode 100644 index 000000000..c8a6a3f8f --- /dev/null +++ b/src/atlas/util/DataType.h @@ -0,0 +1,420 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#pragma once + +#include + +//------------------------------------------------------------------------------------------------------ + +// For type safety we want to use std::byte for the DataType "BYTE", but it is a C++17 feature. +// Backport std::byte here without any operations +#if __cplusplus >= 201703L +#include +#else +#ifndef STD_BYTE_DEFINED +#define STD_BYTE_DEFINED +namespace std { +#ifdef _CRAYC +struct byte { + unsigned char byte_; +}; +#else +enum class byte : unsigned char +{ +}; +#endif +} // namespace std +#endif +#endif + +//------------------------------------------------------------------------------------------------------ + +namespace atlas { +namespace array { +class DataType { +public: + typedef long kind_t; + static constexpr kind_t KIND_BYTE = 1; + static constexpr kind_t KIND_INT32 = -4; + static constexpr kind_t KIND_INT64 = -8; + static constexpr kind_t KIND_REAL32 = 4; + static constexpr kind_t KIND_REAL64 = 8; + static constexpr kind_t KIND_UINT64 = -16; + + template + static DataType create(); + + static DataType byte() { return DataType(KIND_BYTE); } + static DataType int32() { return DataType(KIND_INT32); } + static DataType int64() { return DataType(KIND_INT64); } + static DataType real32() { return DataType(KIND_REAL32); } + static DataType real64() { return DataType(KIND_REAL64); } + static DataType uint64() { return DataType(KIND_UINT64); } + + template + static constexpr kind_t kind(); + template + static constexpr kind_t kind(const DATATYPE&); + + template + static std::string str(); + template + static std::string str(const DATATYPE); + + static kind_t str_to_kind(const std::string&); + static std::string kind_to_str(kind_t); + static bool kind_valid(kind_t); + +private: + static std::string byte_str() { return "byte"; } + static std::string int32_str() { return "int32"; } + static std::string int64_str() { return "int64"; } + static std::string real32_str() { return "real32"; } + static std::string real64_str() { return "real64"; } + static std::string uint64_str() { return "uint64"; } + + [[noreturn]] static void throw_not_recognised(kind_t); + [[noreturn]] static void throw_not_recognised(std::string datatype); + +public: + DataType(const std::string&); + DataType(long); + DataType(const DataType&); + DataType& operator=(const DataType&); + std::string str() const { return kind_to_str(kind_); } + kind_t kind() const { return kind_; } + size_t size() const { return (kind_ == KIND_UINT64) ? 8 : std::abs(kind_); } + + friend bool operator==(DataType dt1, DataType dt2); + friend bool operator!=(DataType dt1, DataType dt2); + friend bool operator==(DataType dt, kind_t kind); + friend bool operator!=(DataType dt, kind_t kind); + friend bool operator==(kind_t kind, DataType dt); + friend bool operator!=(kind_t kind, DataType dt2); + +private: + kind_t kind_; +}; + +template <> +inline std::string DataType::str() { + return byte_str(); +} +template <> +inline std::string DataType::str() { + return byte_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(int) == 4, ""); + return int32_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(int) == 4, ""); + return int32_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(long) == 8, ""); + return int64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(long) == 8, ""); + return int64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(long long) == 8, ""); + return int64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(long long) == 8, ""); + return int64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(float) == 4, ""); + return real32_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(float) == 4, ""); + return real32_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(double) == 8, ""); + return real64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(double) == 8, ""); + return real64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(unsigned long) == 8, ""); + return uint64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(unsigned long) == 8, ""); + return uint64_str(); +} + +template <> +inline std::string DataType::str() { + static_assert(sizeof(unsigned long long) == 8, ""); + return uint64_str(); +} +template <> +inline std::string DataType::str() { + static_assert(sizeof(unsigned long long) == 8, ""); + return uint64_str(); +} +template <> +inline std::string DataType::str(const int&) { + return str(); +} +template <> +inline std::string DataType::str(const long&) { + return str(); +} +template <> +inline std::string DataType::str(const long long&) { + return str(); +} +template <> +inline std::string DataType::str(const unsigned long&) { + return str(); +} +template <> +inline std::string DataType::str(const unsigned long long&) { + return str(); +} +template <> +inline std::string DataType::str(const float&) { + return str(); +} +template <> +inline std::string DataType::str(const double&) { + return str(); +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(std::byte) == 1, ""); + return KIND_BYTE; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(std::byte) == 1, ""); + return KIND_BYTE; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(int) == 4, ""); + return KIND_INT32; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(int) == 4, ""); + return KIND_INT32; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(long) == 8, ""); + return KIND_INT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(long) == 8, ""); + return KIND_INT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(long long) == 8, ""); + return KIND_INT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(long long) == 8, ""); + return KIND_INT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(unsigned long) == 8, ""); + return KIND_UINT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(unsigned long) == 8, ""); + return KIND_UINT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(unsigned long long) == 8, ""); + return KIND_UINT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(unsigned long long) == 8, ""); + return KIND_UINT64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(float) == 4, ""); + return KIND_REAL32; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(float) == 4, ""); + return KIND_REAL32; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(double) == 8, ""); + return KIND_REAL64; +} +template <> +inline constexpr DataType::kind_t DataType::kind() { + static_assert(sizeof(double) == 8, ""); + return KIND_REAL64; +} +template <> +inline constexpr DataType::kind_t DataType::kind(const int&) { + return kind(); +} +template <> +inline constexpr DataType::kind_t DataType::kind(const long&) { + return kind(); +} +template <> +inline constexpr DataType::kind_t DataType::kind(const unsigned long&) { + return kind(); +} +template <> +inline constexpr DataType::kind_t DataType::kind(const float&) { + return kind(); +} +template <> +inline constexpr DataType::kind_t DataType::kind(const double&) { + return kind(); +} + +inline DataType::kind_t DataType::str_to_kind(const std::string& datatype) { + if (datatype == "int32") + return KIND_INT32; + else if (datatype == "int64") + return KIND_INT64; + else if (datatype == "uint64") + return KIND_UINT64; + else if (datatype == "real32") + return KIND_REAL32; + else if (datatype == "real64") + return KIND_REAL64; + else if (datatype == "byte") { + return KIND_BYTE; + } + else { + throw_not_recognised(datatype); + } +} +inline std::string DataType::kind_to_str(kind_t kind) { + switch (kind) { + case KIND_INT32: + return int32_str(); + case KIND_INT64: + return int64_str(); + case KIND_UINT64: + return uint64_str(); + case KIND_REAL32: + return real32_str(); + case KIND_REAL64: + return real64_str(); + case KIND_BYTE: + return byte_str(); + default: + throw_not_recognised(kind); + } +} +inline bool DataType::kind_valid(kind_t kind) { + switch (kind) { + case KIND_BYTE: + case KIND_INT32: + case KIND_INT64: + case KIND_UINT64: + case KIND_REAL32: + case KIND_REAL64: + return true; + default: + return false; + } +} + +inline DataType::DataType(const DataType& other): kind_(other.kind_) {} + +inline DataType& DataType::operator=(const DataType& other) { + kind_ = other.kind_; + return *this; +} + +inline DataType::DataType(const std::string& datatype): kind_(str_to_kind(datatype)) {} + +inline DataType::DataType(long kind): kind_(kind) {} + +inline bool operator==(DataType dt1, DataType dt2) { + return dt1.kind_ == dt2.kind_; +} + +inline bool operator!=(DataType dt1, DataType dt2) { + return dt1.kind_ != dt2.kind_; +} + +inline bool operator==(DataType dt, DataType::kind_t kind) { + return dt.kind_ == kind; +} + +inline bool operator!=(DataType dt, DataType::kind_t kind) { + return dt.kind_ != kind; +} + +inline bool operator==(DataType::kind_t kind, DataType dt) { + return dt.kind_ == kind; +} + +inline bool operator!=(DataType::kind_t kind, DataType dt) { + return dt.kind_ != kind; +} + +template +inline DataType DataType::create() { + return DataType(DataType::kind()); +} + +template +inline DataType make_datatype() { + return DataType(DataType::kind()); +} + +//------------------------------------------------------------------------------------------------------ +} // namespace array + +using DataType= array::DataType; + +template +inline DataType make_datatype() { + return DataType(DataType::kind()); +} + +} // namespace atlas diff --git a/src/atlas/util/Geometry.cc b/src/atlas/util/Geometry.cc index 4196e5e02..7d8e07797 100644 --- a/src/atlas/util/Geometry.cc +++ b/src/atlas/util/Geometry.cc @@ -11,11 +11,28 @@ #include "eckit/geometry/Point2.h" #include "eckit/geometry/Point3.h" +#include "atlas/library/config.h" #include "atlas/runtime/Exception.h" #include "atlas/util/Geometry.h" namespace atlas { +namespace geometry { +namespace detail { +void GeometrySphere::lonlat2xyz(const Point2& lonlat, Point3& xyz) const { +#if ATLAS_ECKIT_VERSION_AT_LEAST(1, 24, 0) + Sphere::convertSphericalToCartesian(radius_, lonlat, xyz, 0., true); +#else + Sphere::convertSphericalToCartesian(radius_, lonlat, xyz); +#endif +} +void GeometrySphere::xyz2lonlat(const Point3& xyz, Point2& lonlat) const { + Sphere::convertCartesianToSpherical(radius_, xyz, lonlat); +} + +} // namespace detail +} // namespace geometry + extern "C" { // ------------------------------------------------------------------ // C wrapper interfaces to C++ routines diff --git a/src/atlas/util/Geometry.h b/src/atlas/util/Geometry.h index 2566ee685..3880645e8 100644 --- a/src/atlas/util/Geometry.h +++ b/src/atlas/util/Geometry.h @@ -71,12 +71,8 @@ class GeometrySphere : public GeometryBase { public: GeometrySphere(double radius): radius_(radius) {} - void lonlat2xyz(const Point2& lonlat, Point3& xyz) const override { - Sphere::convertSphericalToCartesian(radius_, lonlat, xyz); - } - void xyz2lonlat(const Point3& xyz, Point2& lonlat) const override { - Sphere::convertCartesianToSpherical(radius_, xyz, lonlat); - } + void lonlat2xyz(const Point2& lonlat, Point3& xyz) const override; + void xyz2lonlat(const Point3& xyz, Point2& lonlat) const override; double distance(const Point2& p1, const Point2& p2) const override { return Sphere::distance(radius_, p1, p2); } double distance(const Point3& p1, const Point3& p2) const override { return Sphere::distance(radius_, p1, p2); } double radius() const override { return radius_; } diff --git a/src/atlas/util/Metadata.cc b/src/atlas/util/Metadata.cc index 1882fed2c..c24e6c897 100644 --- a/src/atlas/util/Metadata.cc +++ b/src/atlas/util/Metadata.cc @@ -118,6 +118,18 @@ void Metadata::broadcast(Metadata& dest, idx_t root) const { } } + +Metadata& Metadata::set(const eckit::LocalConfiguration& other) { + eckit::Value& root = const_cast(get()); + auto& other_root = other.get(); + std::vector other_keys; + eckit::fromValue(other_keys, other_root.keys()); + for (auto& key : other_keys) { + root[key] = other_root[key]; + } + return *this; +} + Metadata::Metadata(const eckit::Value& value): eckit::LocalConfiguration(value) {} // ------------------------------------------------------------------ diff --git a/src/atlas/util/Metadata.h b/src/atlas/util/Metadata.h index f700e48d0..c697e13fa 100644 --- a/src/atlas/util/Metadata.h +++ b/src/atlas/util/Metadata.h @@ -35,6 +35,8 @@ class Metadata : public eckit::LocalConfiguration { return *this; } + Metadata& set(const eckit::LocalConfiguration&); + using eckit::LocalConfiguration::get; template diff --git a/src/atlas/util/Polygon.cc b/src/atlas/util/Polygon.cc index dc5b38872..21677254b 100644 --- a/src/atlas/util/Polygon.cc +++ b/src/atlas/util/Polygon.cc @@ -16,9 +16,7 @@ #include "eckit/types/FloatCompare.h" -#include "atlas/array.h" #include "atlas/domain/Domain.h" -#include "atlas/field/Field.h" #include "atlas/projection/Projection.h" #include "atlas/runtime/Exception.h" #include "atlas/util/CoordinateEnums.h" @@ -117,15 +115,14 @@ void Polygon::print(std::ostream& s) const { s << '}'; } -const RectangularDomain& PartitionPolygon::inscribedDomain() const { - static RectangularDomain inscribed; +const PartitionPolygon::RectangleXY& PartitionPolygon::inscribedDomain() const { + static RectangleXY inscribed; return inscribed; } //------------------------------------------------------------------------------------------------------ - -PolygonCoordinates::PolygonCoordinates(const Polygon& poly, const atlas::Field& coordinates, bool removeAlignedPoints) { +PolygonCoordinates::PolygonCoordinates(const Polygon& poly, const double x[], const double y[], size_t xstride, size_t ystride, bool removeAlignedPoints) { ATLAS_ASSERT(poly.size() > 2); ATLAS_ASSERT(poly.front() == poly.back()); @@ -136,15 +133,14 @@ PolygonCoordinates::PolygonCoordinates(const Polygon& poly, const atlas::Field& coordinates_.clear(); coordinates_.reserve(poly.size()); - auto coord = array::make_view(coordinates); - coordinatesMin_ = Point2(coord(poly[0], XX), coord(poly[0], YY)); + coordinatesMin_ = Point2(x[poly[0]], y[poly[0]]); coordinatesMax_ = coordinatesMin_; size_t nb_removed_points_due_to_alignment = 0; for (size_t i = 0; i < poly.size(); ++i) { - Point2 A(coord(poly[i], XX), coord(poly[i], YY)); + Point2 A(x[poly[i*xstride]], y[poly[i*ystride]]); coordinatesMin_ = Point2::componentsMin(coordinatesMin_, A); coordinatesMax_ = Point2::componentsMax(coordinatesMax_, A); diff --git a/src/atlas/util/Polygon.h b/src/atlas/util/Polygon.h index 3dcce8581..eef0daede 100644 --- a/src/atlas/util/Polygon.h +++ b/src/atlas/util/Polygon.h @@ -21,24 +21,18 @@ #include #include +#include "atlas/util/mdspan.h" + #include "atlas/library/config.h" #include "atlas/util/Config.h" #include "atlas/util/Object.h" #include "atlas/util/Point.h" #include "atlas/util/VectorOfAbstract.h" -#include "atlas/projection/Projection.h" // for ExplicitPartitionPolygon - namespace eckit { class PathName; } -namespace atlas { -class Field; -class RectangularDomain; -class Projection; -} // namespace atlas - namespace atlas { namespace util { @@ -100,9 +94,25 @@ class PartitionPolygon : public Polygon, util::Object { using Polygon::Polygon; using PointsXY = std::vector; using PointsLonLat = std::vector; + class RectangleXY { + public: + RectangleXY() = default; + RectangleXY(const std::array& x, const std::array& y) : + xmin_{x[0]}, xmax_{x[1]}, ymin_{y[0]}, ymax_{y[1]} {} + + double xmin() const { return xmin_; } + double xmax() const { return xmax_; } + double ymin() const { return ymin_; } + double ymax() const { return ymax_; } + private: + double xmin_{std::numeric_limits::max()}; + double xmax_{std::numeric_limits::max()}; + double ymin_{std::numeric_limits::max()}; + double ymax_{std::numeric_limits::max()}; + }; /// @brief Return inscribed rectangular domain (not rotated) - virtual const RectangularDomain& inscribedDomain() const; + virtual const RectangleXY& inscribedDomain() const; /// @brief Return value of halo virtual idx_t halo() const { return 0; } @@ -130,9 +140,9 @@ class PartitionPolygon : public Polygon, util::Object { class ExplicitPartitionPolygon : public util::PartitionPolygon { public: explicit ExplicitPartitionPolygon(PointsXY&& points): - ExplicitPartitionPolygon(std::move(points), RectangularDomain()) {} + ExplicitPartitionPolygon(std::move(points), RectangleXY()) {} - explicit ExplicitPartitionPolygon(PointsXY&& points, const RectangularDomain& inscribed): + explicit ExplicitPartitionPolygon(PointsXY&& points, const RectangleXY& inscribed): points_(std::move(points)), inscribed_(inscribed) { setup(compute_edges(points_.size())); } @@ -142,7 +152,7 @@ class ExplicitPartitionPolygon : public util::PartitionPolygon { void allGather(util::PartitionPolygons&) const override; - const RectangularDomain& inscribedDomain() const override { return inscribed_; } + const RectangleXY& inscribedDomain() const override { return inscribed_; } private: static util::Polygon::edge_set_t compute_edges(idx_t points_size); @@ -150,7 +160,7 @@ class ExplicitPartitionPolygon : public util::PartitionPolygon { private: std::vector points_; - RectangularDomain inscribed_; + RectangleXY inscribed_; }; // namespace util //------------------------------------------------------------------------------------------------------ @@ -167,7 +177,11 @@ class PolygonCoordinates { using Vector = VectorOfAbstract; // -- Constructors - PolygonCoordinates(const Polygon&, const atlas::Field& coordinates, bool removeAlignedPoints); + PolygonCoordinates(const Polygon&, const double x[], const double y[], size_t xstride, size_t ystride, bool removeAlignedPoints); + + template + PolygonCoordinates(const Polygon& poly, atlas::mdspan coordinates, bool removeAlignedPoints): + PolygonCoordinates(poly, &coordinates(0,0), &coordinates(0,1), coordinates.stride(1), coordinates.stride(1), removeAlignedPoints ) {} template PolygonCoordinates(const PointContainer& points); diff --git a/src/atlas/util/PolygonXY.cc b/src/atlas/util/PolygonXY.cc index 71735becb..5ce46c56d 100644 --- a/src/atlas/util/PolygonXY.cc +++ b/src/atlas/util/PolygonXY.cc @@ -81,8 +81,8 @@ double compute_inner_radius_squared(const PointContainer& points, const PointLon //------------------------------------------------------------------------------------------------------ PolygonXY::PolygonXY(const PartitionPolygon& partition_polygon): PolygonCoordinates(partition_polygon.xy(), true) { - RectangularLonLatDomain inscribed = partition_polygon.inscribedDomain(); - if (inscribed) { + const auto& inscribed = partition_polygon.inscribedDomain(); + if( inscribed.xmin() != inscribed.xmax() && inscribed.ymin() != inscribed.ymax() ) { inner_coordinatesMin_ = {inscribed.xmin(), inscribed.ymin()}; inner_coordinatesMax_ = {inscribed.xmax(), inscribed.ymax()}; } diff --git a/src/atlas/util/detail/mdspan/LICENSE b/src/atlas/util/detail/mdspan/LICENSE new file mode 100644 index 000000000..6572cc2db --- /dev/null +++ b/src/atlas/util/detail/mdspan/LICENSE @@ -0,0 +1,238 @@ + ************************************************************************ + + Kokkos v. 4.0 + Copyright (2022) National Technology & Engineering + Solutions of Sandia, LLC (NTESS). + + Under the terms of Contract DE-NA0003525 with NTESS, + the U.S. Government retains certain rights in this software. + + + ============================================================================== + Kokkos is under the Apache License v2.0 with LLVM Exceptions: + ============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS Apache 2.0 + + ---- LLVM Exceptions to the Apache 2.0 License ---- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into an Object form of such source code, you + may redistribute such embedded portions in such Object form without complying + with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + + In addition, if you combine or link compiled forms of this Software with + software that is licensed under the GPLv2 ("Combined Software") and if a + court of competent jurisdiction determines that the patent provision (Section + 3), the indemnity provision (Section 9) or other Section of the License + conflicts with the conditions of the GPLv2, you may retroactively and + prospectively choose to deem waived or otherwise exclude such Section(s) of + the License, but only in their entirety and only with respect to the Combined + Software. + + ============================================================================== + Software from third parties included in Kokkos: + ============================================================================== + + Kokkos contains third party software which is under different license + terms. All such code will be identified clearly using at least one of two + mechanisms: + 1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or + 2) It will contain specific license and restriction terms at the top of every + file. + + + THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Questions? Contact: + Christian R. Trott (crtrott@sandia.gov) and + Damien T. Lebrun-Grandie (lebrungrandt@ornl.gov) + + ************************************************************************ diff --git a/src/atlas/util/detail/mdspan/README b/src/atlas/util/detail/mdspan/README new file mode 100644 index 000000000..e1a309576 --- /dev/null +++ b/src/atlas/util/detail/mdspan/README @@ -0,0 +1,9 @@ +File: mdspan_impl.hpp +License: See LICENSE file in this directory + +Contributed from: +Repository: https://github.com/kokkos/mdpsan +Branch: single-header +Commit: https://github.com/kokkos/mdspan/tree/e6aa6fd5db4a51cb9c7ee35a15f4876d16d989a9 +Date: June 9 2023 + diff --git a/src/atlas/util/detail/mdspan/mdspan.hpp b/src/atlas/util/detail/mdspan/mdspan.hpp new file mode 100644 index 000000000..495564d0f --- /dev/null +++ b/src/atlas/util/detail/mdspan/mdspan.hpp @@ -0,0 +1,4725 @@ +#ifndef _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ +#define _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/mdarray +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE std +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental +#endif + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/mdspan +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE std +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental +#endif + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/mdspan/mdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#ifndef MDSPAN_HPP_ +#define MDSPAN_HPP_ + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental +#endif + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/default_accessor.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/macros.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/config.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#ifndef __has_include +# define __has_include(x) 0 +#endif + +#if __has_include() +# include +#else +# include +# include +#endif + +#ifdef _MSVC_LANG +#define _MDSPAN_CPLUSPLUS _MSVC_LANG +#else +#define _MDSPAN_CPLUSPLUS __cplusplus +#endif + +#define MDSPAN_CXX_STD_14 201402L +#define MDSPAN_CXX_STD_17 201703L +#define MDSPAN_CXX_STD_20 202002L + +#define MDSPAN_HAS_CXX_14 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14) +#define MDSPAN_HAS_CXX_17 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_17) +#define MDSPAN_HAS_CXX_20 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_20) + +static_assert(_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later."); + +#ifndef _MDSPAN_COMPILER_CLANG +# if defined(__clang__) +# define _MDSPAN_COMPILER_CLANG __clang__ +# endif +#endif + +#if !defined(_MDSPAN_COMPILER_MSVC) && !defined(_MDSPAN_COMPILER_MSVC_CLANG) +# if defined(_MSC_VER) +# if !defined(_MDSPAN_COMPILER_CLANG) +# define _MDSPAN_COMPILER_MSVC _MSC_VER +# else +# define _MDSPAN_COMPILER_MSVC_CLANG _MSC_VER +# endif +# endif +#endif + +#ifndef _MDSPAN_COMPILER_INTEL +# ifdef __INTEL_COMPILER +# define _MDSPAN_COMPILER_INTEL __INTEL_COMPILER +# endif +#endif + +#ifndef _MDSPAN_COMPILER_APPLECLANG +# ifdef __apple_build_version__ +# define _MDSPAN_COMPILER_APPLECLANG __apple_build_version__ +# endif +#endif + +#ifndef _MDSPAN_HAS_CUDA +# if defined(__CUDACC__) +# define _MDSPAN_HAS_CUDA __CUDACC__ +# endif +#endif + +#ifndef _MDSPAN_HAS_HIP +# if defined(__HIPCC__) +# define _MDSPAN_HAS_HIP __HIPCC__ +# endif +#endif + +#ifndef _MDSPAN_HAS_SYCL +# if defined(SYCL_LANGUAGE_VERSION) +# define _MDSPAN_HAS_SYCL SYCL_LANGUAGE_VERSION +# endif +#endif + +#ifndef __has_cpp_attribute +# define __has_cpp_attribute(x) 0 +#endif + +#ifndef _MDSPAN_PRESERVE_STANDARD_LAYOUT +// Preserve standard layout by default, but we're not removing the old version +// that turns this off until we're sure this doesn't have an unreasonable cost +// to the compiler or optimizer. +# define _MDSPAN_PRESERVE_STANDARD_LAYOUT 1 +#endif + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +# if ((__has_cpp_attribute(no_unique_address) >= 201803L) && \ + (!defined(__NVCC__) || MDSPAN_HAS_CXX_20) && \ + (!defined(_MDSPAN_COMPILER_MSVC) || MDSPAN_HAS_CXX_20)) +# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define _MDSPAN_NO_UNIQUE_ADDRESS [[no_unique_address]] +# else +# define _MDSPAN_NO_UNIQUE_ADDRESS +# endif +#endif + +// NVCC older than 11.6 chokes on the no-unique-address-emulation +// so just pretend to use it (to avoid the full blown EBO workaround +// which NVCC also doesn't like ...), and leave the macro empty +#ifndef _MDSPAN_NO_UNIQUE_ADDRESS +# if defined(__NVCC__) +# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define _MDSPAN_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS +# endif +# define _MDSPAN_NO_UNIQUE_ADDRESS +#endif + +// AMDs HIP compiler seems to have issues with concepts +// it pretends concepts exist, but doesn't ship +#ifndef __HIPCC__ +#ifndef _MDSPAN_USE_CONCEPTS +# if defined(__cpp_concepts) && __cpp_concepts >= 201507L +# define _MDSPAN_USE_CONCEPTS 1 +# endif +#endif +#endif + +#ifndef _MDSPAN_USE_FOLD_EXPRESSIONS +# if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \ + || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_FOLD_EXPRESSIONS 1 +# endif +#endif + +#ifndef _MDSPAN_USE_INLINE_VARIABLES +# if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \ + || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_INLINE_VARIABLES 1 +# endif +#endif + +#ifndef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS +# if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \ + || !MDSPAN_HAS_CXX_17) +# if !(defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1 +# endif +# endif +#endif + +#ifndef _MDSPAN_USE_VARIABLE_TEMPLATES +# if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \ + || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_VARIABLE_TEMPLATES 1 +# endif +#endif // _MDSPAN_USE_VARIABLE_TEMPLATES + +#ifndef _MDSPAN_USE_CONSTEXPR_14 +# if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \ + || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \ + && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700)) +# define _MDSPAN_USE_CONSTEXPR_14 1 +# endif +#endif + +#ifndef _MDSPAN_USE_INTEGER_SEQUENCE +# if defined(_MDSPAN_COMPILER_MSVC) +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# endif +# endif +#endif +#ifndef _MDSPAN_USE_INTEGER_SEQUENCE +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \ + || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \ + /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \ + || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__)) + // several compilers lie about integer_sequence working properly unless the C++14 standard is used +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making + // integer_sequence work +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# endif +#endif + +#ifndef _MDSPAN_USE_RETURN_TYPE_DEDUCTION +# if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \ + || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14) +# define _MDSPAN_USE_RETURN_TYPE_DEDUCTION 1 +# endif +#endif + +#ifndef _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +# if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ >= 11 && __CUDACC_VER_MINOR__ >= 7)) && \ + ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \ + (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17)) +# define _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 +# endif +#endif + +#ifndef _MDSPAN_USE_STANDARD_TRAIT_ALIASES +# if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \ + || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14) +# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 +# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 +# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 +# endif +#endif + +#ifndef _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND +# ifdef __GNUC__ +# if __GNUC__ < 9 +# define _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1 +# endif +# endif +#endif + +#ifndef MDSPAN_CONDITIONAL_EXPLICIT +# if MDSPAN_HAS_CXX_20 && !defined(_MDSPAN_COMPILER_MSVC) +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND) +# else +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) +# endif +#endif + +#ifndef MDSPAN_USE_BRACKET_OPERATOR +# if defined(__cpp_multidimensional_subscript) +# define MDSPAN_USE_BRACKET_OPERATOR 1 +# else +# define MDSPAN_USE_BRACKET_OPERATOR 0 +# endif +#endif + +#ifndef MDSPAN_USE_PAREN_OPERATOR +# if !MDSPAN_USE_BRACKET_OPERATOR +# define MDSPAN_USE_PAREN_OPERATOR 1 +# else +# define MDSPAN_USE_PAREN_OPERATOR 0 +# endif +#endif + +#if MDSPAN_USE_BRACKET_OPERATOR +# define __MDSPAN_OP(mds,...) mds[__VA_ARGS__] +// Corentins demo compiler for subscript chokes on empty [] call, +// though I believe the proposal supports it? +#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR +# define __MDSPAN_OP0(mds) mds.accessor().access(mds.data_handle(),0) +#else +# define __MDSPAN_OP0(mds) mds[] +#endif +# define __MDSPAN_OP1(mds, a) mds[a] +# define __MDSPAN_OP2(mds, a, b) mds[a,b] +# define __MDSPAN_OP3(mds, a, b, c) mds[a,b,c] +# define __MDSPAN_OP4(mds, a, b, c, d) mds[a,b,c,d] +# define __MDSPAN_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e] +# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f] +#else +# define __MDSPAN_OP(mds,...) mds(__VA_ARGS__) +# define __MDSPAN_OP0(mds) mds() +# define __MDSPAN_OP1(mds, a) mds(a) +# define __MDSPAN_OP2(mds, a, b) mds(a,b) +# define __MDSPAN_OP3(mds, a, b, c) mds(a,b,c) +# define __MDSPAN_OP4(mds, a, b, c, d) mds(a,b,c,d) +# define __MDSPAN_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e) +# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f) +#endif +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/config.hpp + +#include // std::is_void + +#ifndef _MDSPAN_HOST_DEVICE +# if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) +# define _MDSPAN_HOST_DEVICE __host__ __device__ +# else +# define _MDSPAN_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_FORCE_INLINE_FUNCTION +# ifdef _MDSPAN_COMPILER_MSVC // Microsoft compilers +# define MDSPAN_FORCE_INLINE_FUNCTION __forceinline _MDSPAN_HOST_DEVICE +# else +# define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) _MDSPAN_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_INLINE_FUNCTION +# define MDSPAN_INLINE_FUNCTION inline _MDSPAN_HOST_DEVICE +#endif + +#ifndef MDSPAN_FUNCTION +# define MDSPAN_FUNCTION _MDSPAN_HOST_DEVICE +#endif + +#ifdef _MDSPAN_HAS_HIP +# define MDSPAN_DEDUCTION_GUIDE _MDSPAN_HOST_DEVICE +#else +# define MDSPAN_DEDUCTION_GUIDE +#endif + +// In CUDA defaulted functions do not need host device markup +#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED +# define MDSPAN_INLINE_FUNCTION_DEFAULTED +#endif + +//============================================================================== +// {{{1 + +#define MDSPAN_PP_COUNT(...) \ + _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE( \ + _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \ + ) + +#define _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ +#define _MDSPAN_PP_INTERNAL_EXPAND(x) x +#define _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE(...) \ + _MDSPAN_PP_INTERNAL_EXPAND( \ + _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ + __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \ + 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ + 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, \ + 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, \ + 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, \ + 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \ + ) \ + ) +# define _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ + _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, \ + _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \ + _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \ + _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \ + _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, \ + _70, count, ...) count \ + /**/ + +#define MDSPAN_PP_STRINGIFY_IMPL(x) #x +#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x) + +#define MDSPAN_PP_CAT_IMPL(x, y) x ## y +#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y) + +#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__) + +#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__ +#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__ + +#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) +#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE) + +// end Preprocessor helpers }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +// These compatibility macros don't help with partial ordering, but they should do the trick +// for what we need to do with concepts in mdspan +#ifdef _MDSPAN_USE_CONCEPTS +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \ + /**/ +#else +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0> +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_TEMPLATE_REQUIRES( \ + class __function_requires_ignored=void, \ + (std::is_void<__function_requires_ignored>::value && REQ) \ + ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \ + /**/ +#endif + +#if defined(_MDSPAN_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_CAT( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\ + (__VA_ARGS__), \ + ) \ + /**/ +#else +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_EVAL( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \ + __VA_ARGS__ \ + ) \ + /**/ +#endif + +#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \ + template end Concept emulation }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#ifdef _MDSPAN_USE_INLINE_VARIABLES +# define _MDSPAN_INLINE_VARIABLE inline +#else +# define _MDSPAN_INLINE_VARIABLE +#endif + +// end inline variables }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION +# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +#else +# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> std::remove_cv_t> \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> decltype(BODY) \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } + +#endif + +// end Return type deduction }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +struct __mdspan_enable_fold_comma { }; + +#ifdef _MDSPAN_USE_FOLD_EXPRESSIONS +# define _MDSPAN_FOLD_AND(...) ((__VA_ARGS__) && ...) +# define _MDSPAN_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...) +# define _MDSPAN_FOLD_OR(...) ((__VA_ARGS__) || ...) +# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__)) +# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__)) +# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__)) +# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__)) +# define _MDSPAN_FOLD_COMMA(...) ((__VA_ARGS__), ...) +#else + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +namespace __fold_compatibility_impl { + +// We could probably be more clever here, but at the (small) risk of losing some compiler understanding. For the +// few operations we need, it's not worth generalizing over the operation + +#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_and_impl() { + return true; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_and_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) && __fold_compatibility_impl::__fold_right_and_impl((Args&&)args...); +} + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_or_impl() { + return false; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_or_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) || __fold_compatibility_impl::__fold_right_or_impl((Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_left_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return __fold_compatibility_impl::__fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) = __fold_compatibility_impl::__fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_plus_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) + __fold_compatibility_impl::__fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_times_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) * __fold_compatibility_impl::__fold_right_times_impl((Arg2&&)arg2, (Args&&)args...); +} + +#else + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_and_impl_; +template <> +struct __fold_right_and_impl_<> { + using __rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl() noexcept { + return true; + } +}; +template +struct __fold_right_and_impl_ { + using __next_t = __fold_right_and_impl_; + using __rv = decltype(std::declval() && std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) && __next_t::__impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_and_impl_::__rv +__fold_right_and_impl(Args&&... args) { + return __fold_right_and_impl_::__impl((Args&&)args...); +} + +// end right and }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_or_impl_; +template <> +struct __fold_right_or_impl_<> { + using __rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl() noexcept { + return false; + } +}; +template +struct __fold_right_or_impl_ { + using __next_t = __fold_right_or_impl_; + using __rv = decltype(std::declval() || std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) || __next_t::__impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_or_impl_::__rv +__fold_right_or_impl(Args&&... args) { + return __fold_right_or_impl_::__impl((Args&&)args...); +} + +// end right or }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_plus_impl_; +template +struct __fold_right_plus_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_plus_impl_ { + using __next_t = __fold_right_plus_impl_; + using __rv = decltype(std::declval() + std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) + __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_plus_impl_::__rv +__fold_right_plus_impl(Args&&... args) { + return __fold_right_plus_impl_::__impl((Args&&)args...); +} + +// end right plus }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_times_impl_; +template +struct __fold_right_times_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_times_impl_ { + using __next_t = __fold_right_times_impl_; + using __rv = decltype(std::declval() * std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) * __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_times_impl_::__rv +__fold_right_times_impl(Args&&... args) { + return __fold_right_times_impl_::__impl((Args&&)args...); +} + +// end right times }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_assign_impl_; +template +struct __fold_right_assign_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_assign_impl_ { + using __next_t = __fold_right_assign_impl_; + using __rv = decltype(std::declval() = std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) = __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_assign_impl_::__rv +__fold_right_assign_impl(Args&&... args) { + return __fold_right_assign_impl_::__impl((Args&&)args...); +} + +// end right assign }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_left_assign_impl_; +template +struct __fold_left_assign_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_left_assign_impl_ { + using __assign_result_t = decltype(std::declval() = std::declval()); + using __next_t = __fold_left_assign_impl_<__assign_result_t, Args...>; + using __rv = typename __next_t::__rv; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return __next_t::__impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_left_assign_impl_::__rv +__fold_left_assign_impl(Args&&... args) { + return __fold_left_assign_impl_::__impl((Args&&)args...); +} + +// end left assign }}}2 +//------------------------------------------------------------------------------ + +#endif + + +template +constexpr __mdspan_enable_fold_comma __fold_comma_impl(Args&&... args) noexcept { return { }; } + +template +struct __bools; + +} // __fold_compatibility_impl + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +# define _MDSPAN_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_and_impl((__VA_ARGS__)...) +# define _MDSPAN_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_or_impl((__VA_ARGS__)...) +# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_left_assign_impl(INIT, (__VA_ARGS__)...) +# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_assign_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_times_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_plus_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_comma_impl((__VA_ARGS__)...) + +# define _MDSPAN_FOLD_AND_TEMPLATE(...) \ + _MDSPAN_TRAIT(std::is_same, __fold_compatibility_impl::__bools<(__VA_ARGS__)..., true>, __fold_compatibility_impl::__bools) + +#endif + +// end fold expressions }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_VARIABLE_TEMPLATES +# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__> +#else +# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value +#endif + +// end Variable template compatibility }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_CONSTEXPR_14 +# define _MDSPAN_CONSTEXPR_14 constexpr +// Workaround for a bug (I think?) in EDG frontends +# ifdef __EDG__ +# define _MDSPAN_CONSTEXPR_14_DEFAULTED +# else +# define _MDSPAN_CONSTEXPR_14_DEFAULTED constexpr +# endif +#else +# define _MDSPAN_CONSTEXPR_14 +# define _MDSPAN_CONSTEXPR_14_DEFAULTED +#endif + +// end Pre-C++14 constexpr }}}1 +//============================================================================== +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/macros.hpp + +#include // size_t + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct default_accessor { + + using offset_policy = default_accessor; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = ElementType*; + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[]) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr default_accessor(default_accessor) noexcept {} + + MDSPAN_INLINE_FUNCTION + constexpr data_handle_type + offset(data_handle_type p, size_t i) const noexcept { + return p + i; + } + + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference access(data_handle_type p, size_t i) const noexcept { + return p[i]; + } + +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/default_accessor.hpp +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/full_extent_t.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct full_extent_t { explicit full_extent_t() = default; }; + +_MDSPAN_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ }; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/full_extent_t.hpp +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/mdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_right.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/trait_backports.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER +#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ +#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ + + +#include +#include // integer_sequence + +//============================================================================== +// {{{1 + +#ifdef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +#if _MDSPAN_USE_VARIABLE_TEMPLATES +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define _MDSPAN_BACKPORT_TRAIT(TRAIT) \ + template _MDSPAN_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT::value; + +_MDSPAN_BACKPORT_TRAIT(is_assignable) +_MDSPAN_BACKPORT_TRAIT(is_constructible) +_MDSPAN_BACKPORT_TRAIT(is_convertible) +_MDSPAN_BACKPORT_TRAIT(is_default_constructible) +_MDSPAN_BACKPORT_TRAIT(is_trivially_destructible) +_MDSPAN_BACKPORT_TRAIT(is_same) +_MDSPAN_BACKPORT_TRAIT(is_empty) +_MDSPAN_BACKPORT_TRAIT(is_void) + +#undef _MDSPAN_BACKPORT_TRAIT + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif // _MDSPAN_USE_VARIABLE_TEMPLATES + +#endif // _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +// end Variable template trait backports (e.g., is_void_v) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(_MDSPAN_USE_INTEGER_SEQUENCE) || !_MDSPAN_USE_INTEGER_SEQUENCE + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct integer_sequence { + static constexpr size_t size() noexcept { return sizeof...(Vals); } + using value_type = T; +}; + +template +using index_sequence = std::integer_sequence; + +namespace __detail { + +template +struct __make_int_seq_impl; + +template +struct __make_int_seq_impl> +{ + using type = integer_sequence; +}; + +template +struct __make_int_seq_impl< + T, N, I, integer_sequence +> : __make_int_seq_impl> +{ }; + +} // end namespace __detail + +template +using make_integer_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using make_index_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using index_sequence_for = make_index_sequence; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end integer sequence (ugh...) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(_MDSPAN_USE_STANDARD_TRAIT_ALIASES) || !_MDSPAN_USE_STANDARD_TRAIT_ALIASES + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define _MDSPAN_BACKPORT_TRAIT_ALIAS(TRAIT) \ + template using TRAIT##_t = typename TRAIT::type; + +_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_cv) +_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_reference) + +template +using enable_if_t = typename enable_if<_B, _T>::type; + +#undef _MDSPAN_BACKPORT_TRAIT_ALIAS + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end standard trait aliases }}}1 +//============================================================================== + +#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/trait_backports.hpp +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/extents.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#if defined(__cpp_lib_span) +#include +#endif + +#include // size_t +#include // numeric_limits + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +#if defined(__cpp_lib_span) +using std::dynamic_extent; +#else +_MDSPAN_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits::max(); +#endif +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//============================================================================================================== +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp + +#ifdef __cpp_lib_span +#include +#endif +#include + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Function used to check compatibility of extents in converting constructor +// can't be a private member function for some reason. +template +static constexpr std::integral_constant __check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +// This helper prevents ICE's on MSVC. +template +struct __compare_extent_compatible : std::integral_constant +{}; + +template +static constexpr std::integral_constant< + bool, _MDSPAN_FOLD_AND(__compare_extent_compatible::value)> +__check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +// ------------------------------------------------------------------ +// ------------ static_array ---------------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides an array of static values with get +// function and operator []. + +// Implementation of Static Array with recursive implementation of get. +template struct static_array_impl; + +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t r) { + if (r == R) + return FirstExt; + else + return static_array_impl::get(r); + } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { +#if MDSPAN_HAS_CXX_17 + if constexpr (r == R) + return FirstExt; + else + return static_array_impl::template get(); +#else + get(r); +#endif + } +}; + +// End the recursion +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return FirstExt; } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { + return FirstExt; + } +}; + +// Don't start recursion if size 0 +template struct static_array_impl<0, T> { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return T(); } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { + return T(); + } +}; + +// Static array, provides get(), get(r) and operator[r] +template struct static_array: + public static_array_impl<0, T, Values...> { + +public: + using value_type = T; + + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return sizeof...(Values); } +}; + + +// ------------------------------------------------------------------ +// ------------ index_sequence_scan --------------------------------- +// ------------------------------------------------------------------ + +// index_sequence_scan takes compile time values and provides get(r) +// and get() which return the sum of the first r-1 values. + +// Recursive implementation for get +template struct index_sequence_scan_impl; + +template +struct index_sequence_scan_impl { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + if (r > R) + return FirstVal + index_sequence_scan_impl::get(r); + else + return 0; + } +}; + +template +struct index_sequence_scan_impl { +#if defined(__NVCC__) || defined(__NVCOMPILER) + // NVCC warns about pointless comparison with 0 for R==0 and r being const + // evaluatable and also 0. + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + return static_cast(R) > static_cast(r) ? FirstVal : 0; + } +#else + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } +#endif +}; +template <> struct index_sequence_scan_impl<0> { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t) { return 0; } +}; + +// ------------------------------------------------------------------ +// ------------ possibly_empty_array ------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides get function and operator [], and +// has a specialization for the size 0 case. +// This is needed to make the maybe_static_array be truly empty, for +// all static values. + +template struct possibly_empty_array { + T vals[N]; + MDSPAN_INLINE_FUNCTION + constexpr T &operator[](size_t r) { return vals[r]; } + MDSPAN_INLINE_FUNCTION + constexpr const T &operator[](size_t r) const { return vals[r]; } +}; + +template struct possibly_empty_array { + MDSPAN_INLINE_FUNCTION + constexpr T operator[](size_t) { return T(); } + MDSPAN_INLINE_FUNCTION + constexpr const T operator[](size_t) const { return T(); } +}; + +// ------------------------------------------------------------------ +// ------------ maybe_static_array ---------------------------------- +// ------------------------------------------------------------------ + +// array like class which has a mix of static and runtime values but +// only stores the runtime values. +// The type of the static and the runtime values can be different. +// The position of a dynamic value is indicated through a tag value. +template +struct maybe_static_array { + + static_assert(std::is_convertible::value, "maybe_static_array: TStatic must be convertible to TDynamic"); + static_assert(std::is_convertible::value, "maybe_static_array: TDynamic must be convertible to TStatic"); + +private: + // Static values member + using static_vals_t = static_array; + constexpr static size_t m_size = sizeof...(Values); + constexpr static size_t m_size_dynamic = + _MDSPAN_FOLD_PLUS_RIGHT((Values == dyn_tag), 0); + + // Dynamic values member + _MDSPAN_NO_UNIQUE_ADDRESS possibly_empty_array + m_dyn_vals; + + // static mapping of indices to the position in the dynamic values array + using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; +public: + + // two types for static and dynamic values + using value_type = TDynamic; + using static_value_type = TStatic; + // tag value indicating dynamic value + constexpr static static_value_type tag_value = dyn_tag; + + constexpr maybe_static_array() = default; + + // constructor for all static values + // TODO: add precondition check? + MDSPAN_TEMPLATE_REQUIRES(class... Vals, + /* requires */ ((m_size_dynamic == 0) && + (sizeof...(Vals) > 0))) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} + + // constructors from dynamic values only + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) == + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{static_cast(vals)...} {} + + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N == 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &) : m_dyn_vals{} {} + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } +#endif + + // constructors from all values + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) != + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{} { + static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); + TDynamic values[m_size]{static_cast(vals)...}; + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = values[r]; + } +// Precondition check +#ifdef _MDSPAN_DEBUG + else { + assert(values[r] == static_cast(static_val)); + } +#endif + } + } + + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + static_assert((N == m_size), "Invalid number of values."); +// Precondition check +#ifdef _MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +// Precondition check +#ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + static_assert((N == m_size) || (m_size == dynamic_extent)); +#ifdef _MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +#ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } +#endif + + // access functions + MDSPAN_INLINE_FUNCTION + constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); } + + MDSPAN_INLINE_FUNCTION + constexpr TDynamic value(size_t r) const { + TStatic static_val = static_vals_t::get(r); + return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)] + : static_cast(static_val); + } + MDSPAN_INLINE_FUNCTION + constexpr TDynamic operator[](size_t r) const { return value(r); } + + + // observers + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return m_size; } + MDSPAN_INLINE_FUNCTION + constexpr static size_t size_dynamic() { return m_size_dynamic; } +}; + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +// ------------------------------------------------------------------ +// ------------ extents --------------------------------------------- +// ------------------------------------------------------------------ + +// Class to describe the extents of a multi dimensional array. +// Used by mdspan, mdarray and layout mappings. +// See ISO C++ standard [mdspan.extents] + +template class extents { +public: + // typedefs for integral types used + using index_type = IndexType; + using size_type = std::make_unsigned_t; + using rank_type = size_t; + + static_assert(std::is_integral::value && !std::is_same::value, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type"); +private: + constexpr static rank_type m_rank = sizeof...(Extents); + constexpr static rank_type m_rank_dynamic = + _MDSPAN_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0); + + // internal storage type using maybe_static_array + using vals_t = + detail::maybe_static_array; + _MDSPAN_NO_UNIQUE_ADDRESS vals_t m_vals; + +public: + // [mdspan.extents.obs], observers of multidimensional index space + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank() noexcept { return m_rank; } + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } + MDSPAN_INLINE_FUNCTION + constexpr static size_t static_extent(rank_type r) noexcept { + return vals_t::static_value(r); + } + + // [mdspan.extents.cons], constructors + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr extents() noexcept = default; + + // Construction from just dynamic or all values. + // Precondition check is deferred to maybe_static_array constructor + MDSPAN_TEMPLATE_REQUIRES( + class... OtherIndexTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, OtherIndexTypes, + index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, + OtherIndexTypes) /* && ... */) && + (sizeof...(OtherIndexTypes) == m_rank || + sizeof...(OtherIndexTypes) == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + constexpr explicit extents(OtherIndexTypes... dynvals) noexcept + : m_vals(static_cast(dynvals)...) {} + + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + ( + _MDSPAN_TRAIT(std::is_convertible, OtherIndexType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, + OtherIndexType) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::array &exts) noexcept + : m_vals(std::move(exts)) {} + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + (_MDSPAN_TRAIT(std::is_convertible, OtherIndexType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, OtherIndexType) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::span &exts) noexcept + : m_vals(std::move(exts)) {} +#endif + +private: + // Function to construct extents storage from other extents. + // With C++ 17 the first two variants could be collapsed using if constexpr + // in which case you don't need all the requires clauses. + // in C++ 14 mode that doesn't work due to infinite recursion + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return __construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values..., + exts.extent(R)); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent))) + MDSPAN_INLINE_FUNCTION + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return __construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values...); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &, + DynamicValues... dynamic_values) noexcept { + return vals_t{static_cast(dynamic_values)...}; + } + +public: + + // Converting constructor from other extents specializations + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t... OtherExtents, + /* requires */ + ( + /* multi-stage check to protect from invalid pack expansion when sizes + don't match? */ + decltype(detail::__check_compatible_extents( + std::integral_constant{}, + std::integer_sequence{}, + std::integer_sequence{}))::value)) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) && + (OtherExtents == dynamic_extent)) || + ...) || + (std::numeric_limits::max() < + std::numeric_limits::max())) + constexpr extents(const extents &other) noexcept + : m_vals(__construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), other)) {} + + // Comparison operator + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const extents &lhs, + const extents &rhs) noexcept { + bool value = true; + for (size_type r = 0; r < m_rank; r++) + value &= rhs.extent(r) == lhs.extent(r); + return value; + } + +#if !(MDSPAN_HAS_CXX_20) + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(extents const &lhs, + extents const &rhs) noexcept { + return !(lhs == rhs); + } +#endif +}; + +// Recursive helper classes to implement dextents alias for extents +namespace detail { + +template > +struct __make_dextents; + +template +struct __make_dextents< + IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = typename __make_dextents< + IndexType, Rank - 1, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents>::type; +}; + +template +struct __make_dextents< + IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; +}; + +} // end namespace detail + +// [mdspan.extents.dextents], alias template +template +using dextents = typename detail::__make_dextents::type; + +// Deduction guide for extents +#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +template +extents(IndexTypes...) + -> extents; +#endif + +// Helper type traits for identifying a class as extents. +namespace detail { + +template struct __is_extents : ::std::false_type {}; + +template +struct __is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> + : ::std::true_type {}; + +template +#if MDSPAN_HAS_CXX_17 +inline +#else +static +#endif +constexpr bool __is_extents_v = __is_extents::value; + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/extents.hpp +#include +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_stride.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/compressed_pair.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/no_unique_address.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +//============================================================================== + +template +struct __no_unique_address_emulation { + using __stored_type = _T; + _T __v; + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { + return __v; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { + return __v; + } +}; + +// Empty case +// This doesn't work if _T is final, of course, but we're not using anything +// like that currently. That kind of thing could be added pretty easily though +template +struct __no_unique_address_emulation< + _T, _Disambiguator, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && + // If the type isn't trivially destructible, its destructor + // won't be called at the right time, so don't use this + // specialization + _MDSPAN_TRAIT(std::is_trivially_destructible, _T)>> : +#ifdef _MDSPAN_COMPILER_MSVC + // MSVC doesn't allow you to access public static member functions of a type + // when you *happen* to privately inherit from that type. + protected +#else + // But we still want this to be private if possible so that we don't accidentally + // access members of _T directly rather than calling __ref() first, which wouldn't + // work if _T happens to be stateful and thus we're using the unspecialized definition + // of __no_unique_address_emulation above. + private +#endif + _T { + using __stored_type = _T; + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { + return *static_cast<_T const *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { + return *static_cast<_T *>(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation( + __no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation( + __no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & + operator=(__no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & + operator=(__no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__no_unique_address_emulation() noexcept = default; + + // Explicitly make this not a reference so that the copy or move + // constructor still gets called. + MDSPAN_INLINE_FUNCTION + explicit constexpr __no_unique_address_emulation(_T const& __v) noexcept : _T(__v) {} + MDSPAN_INLINE_FUNCTION + explicit constexpr __no_unique_address_emulation(_T&& __v) noexcept : _T(::std::move(__v)) {} +}; + +//============================================================================== + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/no_unique_address.hpp +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// For no unique address emulation, this is the case taken when neither are empty. +// For real `[[no_unique_address]]`, this case is always taken. +template struct __compressed_pair { + _MDSPAN_NO_UNIQUE_ADDRESS _T __t_val; + _MDSPAN_NO_UNIQUE_ADDRESS _U __u_val; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__first() noexcept { return __t_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__first() const noexcept { + return __t_val; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _U &__second() noexcept { return __u_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _U const &__second() const noexcept { + return __u_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() noexcept = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_TLike &&__t, _ULike &&__u) + : __t_val((_TLike &&) __t), __u_val((_ULike &&) __u) {} +}; + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +// First empty. +template +struct __compressed_pair< + _T, _U, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && !_MDSPAN_TRAIT(std::is_empty, _U)>> + : private _T { + _U __u_val; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__first() noexcept { + return *static_cast<_T *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__first() const noexcept { + return *static_cast<_T const *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _U &__second() noexcept { return __u_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _U const &__second() const noexcept { + return __u_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() noexcept = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_TLike &&__t, _ULike &&__u) + : _T((_TLike &&) __t), __u_val((_ULike &&) __u) {} +}; + +// Second empty. +template +struct __compressed_pair< + _T, _U, + std::enable_if_t> + : private _U { + _T __t_val; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__first() noexcept { return __t_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__first() const noexcept { + return __t_val; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _U &__second() noexcept { + return *static_cast<_U *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _U const &__second() const noexcept { + return *static_cast<_U const *>(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() noexcept = default; + + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_TLike &&__t, _ULike &&__u) + : _U((_ULike &&) __u), __t_val((_TLike &&) __t) {} +}; + +// Both empty. +template +struct __compressed_pair< + _T, _U, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && _MDSPAN_TRAIT(std::is_empty, _U)>> + // We need to use the __no_unique_address_emulation wrapper here to avoid + // base class ambiguities. +#ifdef _MDSPAN_COMPILER_MSVC +// MSVC doesn't allow you to access public static member functions of a type +// when you *happen* to privately inherit from that type. + : protected __no_unique_address_emulation<_T, 0>, + protected __no_unique_address_emulation<_U, 1> +#else + : private __no_unique_address_emulation<_T, 0>, + private __no_unique_address_emulation<_U, 1> +#endif +{ + using __first_base_t = __no_unique_address_emulation<_T, 0>; + using __second_base_t = __no_unique_address_emulation<_U, 1>; + + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__first() noexcept { + return this->__first_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__first() const noexcept { + return this->__first_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _U &__second() noexcept { + return this->__second_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _U const &__second() const noexcept { + return this->__second_base_t::__ref(); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() noexcept = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_TLike &&__t, _ULike &&__u) noexcept + : __first_base_t(_T((_TLike &&) __t)), + __second_base_t(_U((_ULike &&) __u)) + { } +}; + +#endif // !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/compressed_pair.hpp + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +#endif + +#include +#include +#include +#ifdef __cpp_lib_span +#include +#endif +#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts) +# include +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct layout_left { + template + class mapping; +}; +struct layout_right { + template + class mapping; +}; + +namespace detail { + template + constexpr bool __is_mapping_of = + std::is_same, Mapping>::value; + +#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 +# if !defined(__cpp_lib_concepts) + namespace internal { + namespace detail { + template + concept __same_as = std::is_same_v<_Tp, _Up>; + } // namespace detail + template + concept __same_as = detail::__same_as && detail::__same_as; + } // namespace internal +# endif + + template + concept __layout_mapping_alike = requires { + requires __is_extents::value; +#if defined(__cpp_lib_concepts) + { M::is_always_strided() } -> std::same_as; + { M::is_always_exhaustive() } -> std::same_as; + { M::is_always_unique() } -> std::same_as; +#else + { M::is_always_strided() } -> internal::__same_as; + { M::is_always_exhaustive() } -> internal::__same_as; + { M::is_always_unique() } -> internal::__same_as; +#endif + std::bool_constant::value; + std::bool_constant::value; + std::bool_constant::value; + }; +#endif +} // namespace detail + +struct layout_stride { + template + class mapping +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : private detail::__no_unique_address_emulation< + detail::__compressed_pair< + Extents, + std::array + > + > +#endif + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_stride; + + // This could be a `requires`, but I think it's better and clearer as a `static_assert`. + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + + private: + + //---------------------------------------------------------------------------- + + using __strides_storage_t = std::array; + using __member_pair_t = detail::__compressed_pair; + +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + _MDSPAN_NO_UNIQUE_ADDRESS __member_pair_t __members; +#else + using __base_t = detail::__no_unique_address_emulation<__member_pair_t>; +#endif + + MDSPAN_FORCE_INLINE_FUNCTION constexpr __strides_storage_t const& + __strides_storage() const noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__second(); +#else + return this->__base_t::__ref().__second(); +#endif + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 __strides_storage_t& + __strides_storage() noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__second(); +#else + return this->__base_t::__ref().__second(); +#endif + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT( static_cast(extents().extent(Idx)), 1 ); + } + + //---------------------------------------------------------------------------- + + template + friend class mapping; + + //---------------------------------------------------------------------------- + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct __deduction_workaround; + + template + struct __deduction_workaround> + { + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _eq_impl(mapping const& self, mapping const& other) noexcept { + return _MDSPAN_FOLD_AND((self.stride(Idxs) == other.stride(Idxs)) /* && ... */) + && _MDSPAN_FOLD_AND((self.extents().extent(Idxs) == other.extents().extent(Idxs)) /* || ... */); + } + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _not_eq_impl(mapping const& self, mapping const& other) noexcept { + return _MDSPAN_FOLD_OR((self.stride(Idxs) != other.stride(Idxs)) /* || ... */) + || _MDSPAN_FOLD_OR((self.extents().extent(Idxs) != other.extents().extent(Idxs)) /* || ... */); + } + + template + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept { + return _MDSPAN_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0); + } + + MDSPAN_INLINE_FUNCTION + static constexpr size_t _req_span_size_impl(mapping const& self) noexcept { + // assumes no negative strides; not sure if I'm allowed to assume that or not + return __impl::_call_op_impl(self, (self.extents().template __extent() - 1)...) + 1; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const OtherMapping& map) { + return __strides_storage_t{static_cast(map.stride(Idxs))...}; + } + + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t& fill_strides(const __strides_storage_t& s) { + return s; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const std::array& s) { + return __strides_storage_t{static_cast(s[Idxs])...}; + } + +#ifdef __cpp_lib_span + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const std::span& s) { + return __strides_storage_t{static_cast(s[Idxs])...}; + } +#endif + + template + MDSPAN_INLINE_FUNCTION + static constexpr size_t __return_zero() { return 0; } + + template + MDSPAN_INLINE_FUNCTION + static constexpr typename Mapping::index_type + __OFFSET(const Mapping& m) { return m(__return_zero()...); } + }; + + // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. + using __impl = __deduction_workaround>; + + + //---------------------------------------------------------------------------- + +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(__member_pair_t&& __m) : __members(::std::move(__m)) {} +#else + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(__base_t&& __b) : __base_t(::std::move(__b)) {} +#endif + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + extents_type const& e, + std::array const& s + ) noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + e, __strides_storage_t(__impl::fill_strides(s)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + extents_type const& e, + std::span const& s + ) noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + e, __strides_storage_t(__impl::fill_strides(s)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } +#endif // __cpp_lib_span + +#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + detail::__is_mapping_of && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::__layout_mapping_alike && + _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible::value) && + (detail::__is_mapping_of || + detail::__is_mapping_of || + detail::__is_mapping_of) + ) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + other.extents(), __strides_storage_t(__impl::fill_strides(other)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - other.stride(i) > 0 is true for all i in the range [0, rank_ ). + * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]). + * - OFFSET(other) == 0 + */ + } + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED + mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__first(); +#else + return this->__base_t::__ref().__first(); +#endif + }; + + MDSPAN_INLINE_FUNCTION + constexpr std::array< index_type, extents_type::rank() > strides() const noexcept { + return __strides_storage(); + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type span_size = 1; + for(unsigned r = 0; r < extents_type::rank(); r++) { + // Return early if any of the extents are zero + if(extents().extent(r)==0) return 0; + span_size += ( static_cast(extents().extent(r) - 1 ) * __strides_storage()[r]); + } + return span_size; + } + + + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ ( + sizeof...(Indices) == Extents::rank() && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, Indices, index_type) /*&& ...*/ ) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Indices) /*&& ...*/) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr index_type operator()(Indices... idxs) const noexcept { + return static_cast(__impl::_call_op_impl(*this, static_cast(idxs)...)); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return false; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 bool is_exhaustive() const noexcept { + return required_span_size() == __get_size(extents(), std::make_index_sequence()); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type r) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + return __strides_storage()[r]; + } + +#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::__is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::__layout_mapping_alike && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept { + bool strides_match = true; + for(rank_type r = 0; r < extents_type::rank(); r++) + strides_match = strides_match && (x.stride(r) == y.stride(r)); + return (x.extents() == y.extents()) && + (__impl::__OFFSET(y)== static_cast(0)) && + strides_match; + } + + // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return __impl::_eq_impl(lhs, rhs); + } + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::__is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept { + return not (x == y); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return __impl::_not_eq_impl(lhs, rhs); + } +#endif + + }; +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_stride.hpp + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== +template +class layout_right::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right; + private: + + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct __rank_count {}; + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + index_type offset, __rank_count, const I& i, Indices... idx) const { + return __compute_offset(offset * __extents.extent(r) + i,__rank_count(), idx...); + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const { + return __compute_offset(i,__rank_count<1,extents_type::rank()>(),idx...); + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(size_t offset, __rank_count) const { + return static_cast(offset); + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + _MDSPAN_HOST_DEVICE + constexpr mapping(extents_type const& __exts) noexcept + :__extents(__exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_left::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + #if !defined(_MDSPAN_HAS_CUDA) && !defined(_MDSPAN_HAS_HIP) && !defined(NDEBUG) + index_type stride = 1; + for(rank_type r=__extents.rank(); r>0; r--) { + if(stride != static_cast(other.stride(r-1))) { + // Note this throw will lead to a terminate if triggered since this function is marked noexcept + throw std::runtime_error("Assigning layout_stride to layout_right with invalid strides."); + } + stride *= __extents.extent(r-1); + } + #endif + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return __extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r != extents_type::rank(); ++r) value*=__extents.extent(r); + return value; + } + + //-------------------------------------------------------------------------------- + + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ ( + (sizeof...(Indices) == extents_type::rank()) && + _MDSPAN_FOLD_AND( + (_MDSPAN_TRAIT(std::is_convertible, Indices, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Indices)) + ) + ) + ) + _MDSPAN_HOST_DEVICE + constexpr index_type operator()(Indices... idxs) const noexcept { + return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return true; } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return true; } + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=extents_type::rank()-1; r>i; r--) value*=__extents.extent(r); + return value; + } + + template + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + template + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT((Idx>N? __extents.template __extent():1),1); + } + template + constexpr index_type __stride() const noexcept { + return __get_stride(__extents, std::make_index_sequence()); + } + +private: + _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; + +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_right.hpp + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +template < + class ElementType, + class Extents, + class LayoutPolicy = layout_right, + class AccessorPolicy = default_accessor +> +class mdspan +{ +private: + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct __deduction_workaround; + + template + struct __deduction_workaround> + { + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + size_t __size(mdspan const& __self) noexcept { + return _MDSPAN_FOLD_TIMES_RIGHT((__self.__mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1)); + } + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + bool __empty(mdspan const& __self) noexcept { + return (__self.rank()>0) && _MDSPAN_FOLD_OR((__self.__mapping_ref().extents().extent(Idxs)==index_type(0))); + } + template + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + ReferenceType __callop(mdspan const& __self, const std::array& indices) noexcept { + return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); + } + }; + +public: + + //-------------------------------------------------------------------------------- + // Domain and codomain types + + using extents_type = Extents; + using layout_type = LayoutPolicy; + using accessor_type = AccessorPolicy; + using mapping_type = typename layout_type::template mapping; + using element_type = ElementType; + using value_type = std::remove_cv_t; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using data_handle_type = typename accessor_type::data_handle_type; + using reference = typename accessor_type::reference; + + MDSPAN_INLINE_FUNCTION static constexpr size_t rank() noexcept { return extents_type::rank(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t rank_dynamic() noexcept { return extents_type::rank_dynamic(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } + MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return __mapping_ref().extents().extent(r); }; + +private: + + // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. + using __impl = __deduction_workaround>; + + using __map_acc_pair_t = detail::__compressed_pair; + +public: + + //-------------------------------------------------------------------------------- + // [mdspan.basic.cons], mdspan constructors, assignment, and destructor + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default; +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() + requires( + // nvhpc has a bug where using just rank_dynamic() here doesn't work ... + (extents_type::rank_dynamic() > 0) && + _MDSPAN_TRAIT(std::is_default_constructible, data_handle_type) && + _MDSPAN_TRAIT(std::is_default_constructible, mapping_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) = default; +#endif + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default; + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && + ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents) + // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here? + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(static_cast(std::move(dynamic_extents))...)), accessor_type())) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) && + ((N == rank()) || (N == rank_dynamic())) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const std::array& dynamic_extents) + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type())) + { } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) && + ((N == rank()) || (N == rank_dynamic())) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, std::span dynamic_extents) + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type())) + { } +#endif + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const extents_type& exts), , + /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type)) + ) : __members(std::move(p), __map_acc_pair_t(mapping_type(exts), accessor_type())) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const mapping_type& m), , + /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type)) + ) : __members(std::move(p), __map_acc_pair_t(m, accessor_type())) + { } + + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a) + : __members(std::move(p), __map_acc_pair_t(m, a)) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping) && + _MDSPAN_TRAIT(std::is_constructible, accessor_type, OtherAccessor) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(const mdspan& other) + : __members(other.__ptr_ref(), __map_acc_pair_t(other.__mapping_ref(), other.__accessor_ref())) + { + static_assert(_MDSPAN_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction"); + static_assert(_MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction"); + /* + * TODO: Check precondition + * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true. + */ + } + + /* Might need this on NVIDIA? + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~mdspan() = default; + */ + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default; + + + //-------------------------------------------------------------------------------- + // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element + + #if MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && + (rank() == sizeof...(SizeTypes)) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](SizeTypes... indices) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); + } + #endif + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](const std::array< SizeType, rank()>& indices) const + { + return __impl::template __callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](std::span indices) const + { + return __impl::template __callop(*this, indices); + } + #endif // __cpp_lib_span + + #if !MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class Index, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, Index, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Index) && + extents_type::rank() == 1 + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](Index idx) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(idx)))); + } + #endif + + #if MDSPAN_USE_PAREN_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(SizeTypes... indices) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(const std::array& indices) const + { + return __impl::template __callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, SizeType, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeType) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(std::span indices) const + { + return __impl::template __callop(*this, indices); + } + #endif // __cpp_lib_span + #endif // MDSPAN_USE_PAREN_OPERATOR + + MDSPAN_INLINE_FUNCTION constexpr size_t size() const noexcept { + return __impl::__size(*this); + }; + + MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept { + return __impl::__empty(*this); + }; + + MDSPAN_INLINE_FUNCTION + friend constexpr void swap(mdspan& x, mdspan& y) noexcept { + // can't call the std::swap inside on HIP + #if !defined(_MDSPAN_HAS_HIP) && !defined(_MDSPAN_HAS_CUDA) + using std::swap; + swap(x.__ptr_ref(), y.__ptr_ref()); + swap(x.__mapping_ref(), y.__mapping_ref()); + swap(x.__accessor_ref(), y.__accessor_ref()); + #else + mdspan tmp = y; + y = x; + x = tmp; + #endif + } + + //-------------------------------------------------------------------------------- + // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space + + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return __mapping_ref().extents(); }; + MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return __ptr_ref(); }; + MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return __mapping_ref(); }; + MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return __accessor_ref(); }; + + //-------------------------------------------------------------------------------- + // [mdspan.basic.obs], mdspan observers of the mapping + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return mapping_type::is_always_unique(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return mapping_type::is_always_exhaustive(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return mapping_type::is_always_strided(); }; + + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return __mapping_ref().is_unique(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return __mapping_ref().is_exhaustive(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return __mapping_ref().is_strided(); }; + MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return __mapping_ref().stride(r); }; + +private: + + detail::__compressed_pair __members{}; + + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 data_handle_type& __ptr_ref() noexcept { return __members.__first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& __ptr_ref() const noexcept { return __members.__first(); } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 mapping_type& __mapping_ref() noexcept { return __members.__second().__first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& __mapping_ref() const noexcept { return __members.__second().__first(); } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 accessor_type& __accessor_ref() noexcept { return __members.__second().__second(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& __accessor_ref() const noexcept { return __members.__second().__second(); } + + template + friend class mdspan; + +}; + +#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +MDSPAN_TEMPLATE_REQUIRES( + class ElementType, class... SizeTypes, + /* requires */ _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_integral, SizeTypes) /* && ... */) && + (sizeof...(SizeTypes) > 0) +) +MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...) + -> mdspan>; + +MDSPAN_TEMPLATE_REQUIRES( + class Pointer, + (_MDSPAN_TRAIT(std::is_pointer, std::remove_reference_t)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan>, extents>; + +MDSPAN_TEMPLATE_REQUIRES( + class CArray, + (_MDSPAN_TRAIT(std::is_array, CArray) && (std::rank_v == 1)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan, extents>>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array&) + -> mdspan>; + +#ifdef __cpp_lib_span +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span) + -> mdspan>; +#endif + +// This one is necessary because all the constructors take `data_handle_type`s, not +// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which +// seems to throw off automatic deduction guides. +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents&) + -> mdspan>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&) + -> mdspan; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&) + -> mdspan; +#endif + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/mdspan.hpp +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_left.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== + +template +class layout_left::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left; + private: + + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct __rank_count {}; + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count, const I& i, Indices... idx) const { + return __compute_offset(__rank_count(), idx...) * + __extents.extent(r) + i; + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count, const I& i) const { + return i; + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + _MDSPAN_HOST_DEVICE + constexpr mapping(extents_type const& __exts) noexcept + :__extents(__exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_right::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + #if !defined(_MDSPAN_HAS_CUDA) && !defined(_MDSPAN_HAS_HIP) && !defined(NDEBUG) + index_type stride = 1; + for(rank_type r=0; r<__extents.rank(); r++) { + if(stride != static_cast(other.stride(r))) { + // Note this throw will lead to a terminate if triggered since this function is marked noexcept + throw std::runtime_error("Assigning layout_stride to layout_left with invalid strides."); + } + stride *= __extents.extent(r); + } + #endif + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return __extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r(), static_cast(idxs)...); + } + + + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return true; } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return true; } + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=0; r + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + template + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT((Idx():1),1); + } + template + constexpr index_type __stride() const noexcept { + return __get_stride(__extents, std::make_index_sequence()); + } + +private: + _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; + +}; + + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p0009_bits/layout_left.hpp +#if MDSPAN_HAS_CXX_17 +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/strided_slice.hpp + +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +namespace { + template + struct __mdspan_is_integral_constant: std::false_type {}; + + template + struct __mdspan_is_integral_constant>: std::true_type {}; +} +// Slice Specifier allowing for strides and compile time extent +template +struct strided_slice { + using offset_type = OffsetType; + using extent_type = ExtentType; + using stride_type = StrideType; + + OffsetType offset; + ExtentType extent; + StrideType stride; + + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); +}; + +} // MDSPAN_IMPL_PROPOSED_NAMESPACE +} // MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/strided_slice.hpp +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { +namespace detail { + +// Mapping from submapping ranks to srcmapping ranks +// InvMapRank is an index_sequence, which we build recursively +// to contain the mapped indices. +// end of recursion specialization containing the final index_sequence +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence) { + return std::index_sequence(); +} + +// specialization reducing rank by one (i.e., integral slice specifier) +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence, Slice, + SliceSpecifiers... slices) { + using next_idx_seq_t = std::conditional_t, + std::index_sequence, + std::index_sequence>; + + return inv_map_rank(std::integral_constant(), next_idx_seq_t(), + slices...); +} + +// Helper for identifying strided_slice +template struct is_strided_slice : std::false_type {}; + +template +struct is_strided_slice< + strided_slice> : std::true_type {}; + +// first_of(slice): getting begin of slice specifier range +MDSPAN_TEMPLATE_REQUIRES( + class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral first_of(const Integral &i) { + return i; +} + +MDSPAN_INLINE_FUNCTION +constexpr std::integral_constant +first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) { + return std::integral_constant(); +} + +MDSPAN_TEMPLATE_REQUIRES( + class Slice, + /* requires */(std::is_convertible_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr auto first_of(const Slice &i) { + return std::get<0>(i); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +first_of(const strided_slice &r) { + return r.offset; +} + +// last_of(slice): getting end of slice specifier range +// We need however not just the slice but also the extents +// of the original view and which rank from the extents. +// This is needed in the case of slice being full_extent_t. +MDSPAN_TEMPLATE_REQUIRES( + size_t k, class Extents, class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral + last_of(std::integral_constant, const Extents &, const Integral &i) { + return i; +} + +MDSPAN_TEMPLATE_REQUIRES( + size_t k, class Extents, class Slice, + /* requires */(std::is_convertible_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr auto last_of(std::integral_constant, const Extents &, + const Slice &i) { + return std::get<1>(i); +} + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +template +MDSPAN_INLINE_FUNCTION +constexpr auto last_of(std::integral_constant, const Extents &ext, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) { + if constexpr (Extents::static_extent(k) == dynamic_extent) { + return ext.extent(k); + } else { + return std::integral_constant(); + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + // Even with CUDA_ARCH protection this thing warns about calling host function + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +last_of(std::integral_constant, const Extents &, + const strided_slice &r) { + return r.extent; +} + +// get stride of slices +template +MDSPAN_INLINE_FUNCTION +constexpr auto stride_of(const T &) { + return std::integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto +stride_of(const strided_slice &r) { + return r.stride; +} + +// divide which can deal with integral constant preservation +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const T0 &v0, const T1 &v1) { + return IndexT(v0) / IndexT(v1); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const std::integral_constant &, + const std::integral_constant &) { + // cutting short division by zero + // this is used for strided_slice with zero extent/stride + return std::integral_constant(); +} + +// multiply which can deal with integral constant preservation +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const T0 &v0, const T1 &v1) { + return IndexT(v0) * IndexT(v1); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const std::integral_constant &, + const std::integral_constant &) { + return std::integral_constant(); +} + +// compute new static extent from range, preserving static knowledge +template struct StaticExtentFromRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromRange, + std::integral_constant> { + constexpr static size_t value = val1 - val0; +}; + +// compute new static extent from strided_slice, preserving static +// knowledge +template struct StaticExtentFromStridedRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromStridedRange, + std::integral_constant> { + constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; +}; + +// creates new extents through recursive calls to next_extent member function +// next_extent has different overloads for different types of stride specifiers +template +struct extents_constructor { + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */(!std::is_convertible_v && + !is_strided_slice::value) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &sl, + SlicesAndExtents... slices_and_extents) { + constexpr size_t new_static_extent = StaticExtentFromRange< + decltype(first_of(std::declval())), + decltype(last_of(std::integral_constant(), + std::declval(), + std::declval()))>::value; + + using next_t = + extents_constructor; + using index_t = typename Extents::index_type; + return next_t::next_extent( + ext, slices_and_extents..., + index_t(last_of(std::integral_constant(), ext, + sl)) - + index_t(first_of(sl))); + } + + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */ (std::is_convertible_v) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &, + SlicesAndExtents... slices_and_extents) { + using next_t = extents_constructor; + return next_t::next_extent(ext, slices_and_extents...); + } + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto + next_extent(const Extents &ext, + const strided_slice &r, + SlicesAndExtents... slices_and_extents) { + using index_t = typename Extents::index_type; + using new_static_extent_t = + StaticExtentFromStridedRange; + if constexpr (new_static_extent_t::value == dynamic_extent) { + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., + r.extent > 0 ? 1 + divide(r.extent - 1, r.stride) : 0); + } else { + constexpr size_t new_static_extent = new_static_extent_t::value; + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., index_t(divide(ExtentType(), StrideType()))); + } + } +}; + +template +struct extents_constructor<0, Extents, NewStaticExtents...> { + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &, NewExtents... new_exts) { + return extents( + new_exts...); + } +}; + +} // namespace detail + +// submdspan_extents creates new extents given src extents and submdspan slice +// specifiers +template +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_extents(const extents &src_exts, + SliceSpecifiers... slices) { + + using ext_t = extents; + return detail::extents_constructor::next_extent( + src_exts, slices...); +} +} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include +#include +#include +#include // index_sequence + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { +//****************************************** +// Return type of submdspan_mapping overloads +//****************************************** +template struct mapping_offset { + Mapping mapping; + size_t offset; +}; +} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE + +namespace detail { +using MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::first_of; +using MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::stride_of; +using MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::inv_map_rank; + +// constructs sub strides +template +MDSPAN_INLINE_FUNCTION +constexpr auto +construct_sub_strides(const SrcMapping &src_mapping, + std::index_sequence, + const std::tuple &slices_stride_factor) { + using index_type = typename SrcMapping::index_type; + return std::array{ + (static_cast(src_mapping.stride(InvMapIdxs)) * + static_cast(std::get(slices_stride_factor)))...}; +} +} // namespace detail + +//********************************** +// layout_left submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_left +template +struct preserve_layout_left_mapping; + +template +struct preserve_layout_left_mapping, SubRank, + SliceSpecifiers...> { + constexpr static bool value = + // Preserve layout for rank 0 + (SubRank == 0) || + ( + // Slice specifiers up to subrank need to be full_extent_t - except + // for the last one which could also be tuple but not a strided index + // range slice specifiers after subrank are integrals + ((Idx > SubRank - 1) || // these are only integral slice specifiers + (std::is_same_v) || + ((Idx == SubRank - 1) && + std::is_convertible_v>)) && + ...); +}; +} // namespace detail + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +// Actual submdspan mapping call +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan_mapping(const layout_left::mapping &src_mapping, + SliceSpecifiers... slices) { + using MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan_extents; + using MDSPAN_IMPL_PROPOSED_NAMESPACE::mapping_offset; + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(src_mapping.extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // figure out sub layout type + constexpr bool preserve_layout = detail::preserve_layout_left_mapping< + decltype(std::make_index_sequence()), dst_ext_t::rank(), + SliceSpecifiers...>::value; + using dst_layout_t = + std::conditional_t; + using dst_mapping_t = typename dst_layout_t::template mapping; + + if constexpr (std::is_same_v) { + // layout_left case + return mapping_offset{ + dst_mapping_t(dst_ext), + static_cast(src_mapping(detail::first_of(slices)...))}; + } else { + // layout_stride case + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + return mapping_offset{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + src_mapping, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) + std::tuple{detail::stride_of(slices)...})), + #else + std::tuple{detail::stride_of(slices)...})), + #endif + static_cast(src_mapping(detail::first_of(slices)...))}; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +//********************************** +// layout_right submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_right +template +struct preserve_layout_right_mapping; + +template +struct preserve_layout_right_mapping, SubRank, + SliceSpecifiers...> { + constexpr static size_t SrcRank = sizeof...(SliceSpecifiers); + constexpr static bool value = + // Preserve layout for rank 0 + (SubRank == 0) || + ( + // The last subrank slice specifiers need to be full_extent_t - except + // for the srcrank-subrank one which could also be tuple but not a + // strided index range slice specifiers before srcrank-subrank are + // integrals + ((Idx < + SrcRank - SubRank) || // these are only integral slice specifiers + (std::is_same_v) || + ((Idx == SrcRank - SubRank) && + std::is_convertible_v>)) && + ...); +}; +} // namespace detail + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan_mapping(const layout_right::mapping &src_mapping, + SliceSpecifiers... slices) { + using MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan_extents; + using MDSPAN_IMPL_PROPOSED_NAMESPACE::mapping_offset; + + // get sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(src_mapping.extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // determine new layout type + constexpr bool preserve_layout = detail::preserve_layout_right_mapping< + decltype(std::make_index_sequence()), dst_ext_t::rank(), + SliceSpecifiers...>::value; + using dst_layout_t = + std::conditional_t; + using dst_mapping_t = typename dst_layout_t::template mapping; + + if constexpr (std::is_same_v) { + // layout_right case + return mapping_offset{ + dst_mapping_t(dst_ext), + static_cast(src_mapping(detail::first_of(slices)...))}; + } else { + // layout_stride case + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + return mapping_offset{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + src_mapping, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) + std::tuple{detail::stride_of(slices)...})), + #else + std::tuple{detail::stride_of(slices)...})), + #endif + static_cast(src_mapping(detail::first_of(slices)...))}; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +//********************************** +// layout_stride submdspan_mapping +//********************************* +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan_mapping(const layout_stride::mapping &src_mapping, + SliceSpecifiers... slices) { + using MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan_extents; + using MDSPAN_IMPL_PROPOSED_NAMESPACE::mapping_offset; + auto dst_ext = submdspan_extents(src_mapping.extents(), slices...); + using dst_ext_t = decltype(dst_ext); + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + using dst_mapping_t = typename layout_stride::template mapping; + return mapping_offset{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + src_mapping, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) + std::tuple(detail::stride_of(slices)...))), +#else + std::tuple(detail::stride_of(slices)...))), +#endif + static_cast(src_mapping(detail::first_of(slices)...))}; +} +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan(const mdspan &src, + SliceSpecifiers... slices) { + const auto sub_mapping_offset = submdspan_mapping(src.mapping(), slices...); + // NVCC has a problem with the deduction so lets figure out the type + using sub_mapping_t = std::remove_cv_t; + using sub_extents_t = typename sub_mapping_t::extents_type; + using sub_layout_t = typename sub_mapping_t::layout_type; + using sub_accessor_t = typename AccessorPolicy::offset_policy; + return mdspan( + src.accessor().offset(src.data_handle(), sub_mapping_offset.offset), + sub_mapping_offset.mapping, + sub_accessor_t(src.accessor())); +} +} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p2630_bits/submdspan.hpp +#endif + +#endif // MDSPAN_HPP_ +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/mdspan/mdspan.hpp + +// backward compatibility import into experimental +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_left; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_right; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::default_accessor; + } +} +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/mdspan +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/mdspan/mdarray.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#ifndef MDARRAY_HPP_ +#define MDARRAY_HPP_ + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental +#endif + +//BEGIN_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p1684_bits/mdarray.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +namespace { + template + struct size_of_extents; + + template + struct size_of_extents> { + constexpr static size_t value() { + size_t size = 1; + for(size_t r=0; r::rank(); r++) + size *= extents::static_extent(r); + return size; + } + }; +} + +namespace { + template + struct container_is_array : std::false_type { + template + static constexpr C construct(const M& m) { return C(m.required_span_size()); } + }; + template + struct container_is_array> : std::true_type { + template + static constexpr std::array construct(const M&) { return std::array(); } + }; +} + +template < + class ElementType, + class Extents, + class LayoutPolicy = layout_right, + class Container = std::vector +> +class mdarray { +private: + static_assert(::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::__is_extents_v, + MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + +public: + + //-------------------------------------------------------------------------------- + // Domain and codomain types + + using extents_type = Extents; + using layout_type = LayoutPolicy; + using container_type = Container; + using mapping_type = typename layout_type::template mapping; + using element_type = ElementType; + using mdspan_type = mdspan; + using const_mdspan_type = mdspan; + using value_type = std::remove_cv_t; + using index_type = typename Extents::index_type; + using size_type = typename Extents::size_type; + using rank_type = typename Extents::rank_type; + using pointer = typename container_type::pointer; + using reference = typename container_type::reference; + using const_pointer = typename container_type::const_pointer; + using const_reference = typename container_type::const_reference; + +public: + + //-------------------------------------------------------------------------------- + // [mdspan.basic.cons], mdspan constructors, assignment, and destructor + +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr), + mdarray, (), , + /* requires */ (extents_type::rank_dynamic()!=0)) {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray() requires(extents_type::rank_dynamic()!=0) = default; +#endif + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(const mdarray&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(mdarray&&) = default; + + // Constructors for container types constructible from a size + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_TRAIT( std::is_constructible, extents_type, SizeTypes...) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type) && + (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) && + (extents_type::rank()>0 || extents_type::rank_dynamic()==0) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdarray(SizeTypes... dynamic_extents) + : map_(extents_type(dynamic_extents...)), ctr_(container_is_array::construct(map_)) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const extents_type& exts), , + /* requires */ ((_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(container_is_array::construct(map_)) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const mapping_type& m), , + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) + ) : map_(m), ctr_(container_is_array::construct(map_)) + { } + + // Constructors from container + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_TRAIT( std::is_constructible, extents_type, SizeTypes...) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdarray(const container_type& ctr, SizeTypes... dynamic_extents) + : map_(extents_type(dynamic_extents...)), ctr_(ctr) + { assert(ctr.size() >= static_cast(map_.required_span_size())); } + + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const container_type& ctr, const extents_type& exts), , + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(ctr) + { assert(ctr.size() >= static_cast(map_.required_span_size())); } + + constexpr mdarray(const container_type& ctr, const mapping_type& m) + : map_(m), ctr_(ctr) + { assert(ctr.size() >= static_cast(map_.required_span_size())); } + + + // Constructors from container + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_TRAIT( std::is_constructible, extents_type, SizeTypes...) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdarray(container_type&& ctr, SizeTypes... dynamic_extents) + : map_(extents_type(dynamic_extents...)), ctr_(std::move(ctr)) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (container_type&& ctr, const extents_type& exts), , + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(std::move(ctr)) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + constexpr mdarray(container_type&& ctr, const mapping_type& m) + : map_(m), ctr_(std::move(ctr)) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer, + /* requires */ ( + _MDSPAN_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping) && + _MDSPAN_TRAIT( std::is_constructible, container_type, OtherContainer) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mdarray& other) + : map_(other.mapping()), ctr_(other.container()) + { + static_assert( std::is_constructible::value, ""); + } + + // Constructors for container types constructible from a size and allocator + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const extents_type& exts, const Alloc& a) + : map_(exts), ctr_(map_.required_span_size(), a) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mapping_type& map, const Alloc& a) + : map_(map), ctr_(map_.required_span_size(), a) + { } + + // Constructors for container types constructible from a container and allocator + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, container_type, Alloc) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const container_type& ctr, const extents_type& exts, const Alloc& a) + : map_(exts), ctr_(ctr, a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const container_type& ctr, const mapping_type& map, const Alloc& a) + : map_(map), ctr_(ctr, a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, container_type, Alloc) && + _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(container_type&& ctr, const extents_type& exts, const Alloc& a) + : map_(exts), ctr_(std::move(ctr), a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(container_type&& ctr, const mapping_type& map, const Alloc& a) + : map_(map), ctr_(std::move(ctr), a) + { assert(ctr_.size() >= map_.required_span_size()); } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer, class Alloc, + /* requires */ ( + _MDSPAN_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping) && + _MDSPAN_TRAIT( std::is_constructible, container_type, OtherContainer, Alloc) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mdarray& other, const Alloc& a) + : map_(other.mapping()), ctr_(other.container(), a) + { + static_assert( std::is_constructible::value, ""); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (const mdarray&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (mdarray&&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~mdarray() = default; + + //-------------------------------------------------------------------------------- + // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element + + #if MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator[](SizeTypes... indices) const noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](SizeTypes... indices) noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + #endif + +#if 0 + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator[](const std::array& indices) const noexcept + { + return __impl::template __callop(*this, indices); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](const std::array& indices) noexcept + { + return __impl::template __callop(*this, indices); + } +#endif + + + #if MDSPAN_USE_PAREN_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator()(SizeTypes... indices) const noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(SizeTypes... indices) noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + +#if 0 + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator()(const std::array& indices) const noexcept + { + return __impl::template __callop(*this, indices); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(const std::array& indices) noexcept + { + return __impl::template __callop(*this, indices); + } +#endif + #endif + + MDSPAN_INLINE_FUNCTION constexpr pointer data() noexcept { return ctr_.data(); }; + MDSPAN_INLINE_FUNCTION constexpr const_pointer data() const noexcept { return ctr_.data(); }; + MDSPAN_INLINE_FUNCTION constexpr container_type& container() noexcept { return ctr_; }; + MDSPAN_INLINE_FUNCTION constexpr const container_type& container() const noexcept { return ctr_; }; + + //-------------------------------------------------------------------------------- + // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space + + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank() noexcept { return extents_type::rank(); } + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank_dynamic() noexcept { return extents_type::rank_dynamic(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return map_.extents(); }; + MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return map_.extents().extent(r); }; + MDSPAN_INLINE_FUNCTION constexpr index_type size() const noexcept { +// return __impl::__size(*this); + return ctr_.size(); + }; + + + //-------------------------------------------------------------------------------- + // [mdspan.basic.obs], mdspan observers of the mapping + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return mapping_type::is_always_unique(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return mapping_type::is_always_exhaustive(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return mapping_type::is_always_strided(); }; + + MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return map_; }; + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return map_.is_unique(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return map_.is_exhaustive(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return map_.is_strided(); }; + MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return map_.stride(r); }; + + // Converstion to mdspan + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, + class OtherLayoutType, class OtherAccessorType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_assignable, mdspan_type, + mdspan) + ) + ) + constexpr operator mdspan () { + return mdspan_type(data(), map_); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, + class OtherLayoutType, class OtherAccessorType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_assignable, const_mdspan_type, + mdspan) + ) + ) + constexpr operator mdspan () const { + return const_mdspan_type(data(), map_); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherAccessorType = default_accessor, + /* requires */ ( + _MDSPAN_TRAIT(std::is_assignable, mdspan_type, + mdspan) + ) + ) + constexpr mdspan + to_mdspan(const OtherAccessorType& a = default_accessor()) { + return mdspan(data(), map_, a); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherAccessorType = default_accessor, + /* requires */ ( + _MDSPAN_TRAIT(std::is_assignable, const_mdspan_type, + mdspan) + ) + ) + constexpr mdspan + to_mdspan(const OtherAccessorType& a = default_accessor()) const { + return mdspan(data(), map_, a); + } + +private: + mapping_type map_; + container_type ctr_; + + template + friend class mdarray; +}; + + +} // end namespace MDSPAN_IMPL_PROPOSED_NAMESPACE +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/__p1684_bits/mdarray.hpp + +#endif // MDARRAY_HPP_ +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/mdspan/mdarray.hpp +//END_FILE_INCLUDE: /home/runner/work/mdspan/mdspan/include/experimental/mdarray +#endif // _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ + diff --git a/src/atlas/util/mdspan.h b/src/atlas/util/mdspan.h new file mode 100644 index 000000000..097df1c68 --- /dev/null +++ b/src/atlas/util/mdspan.h @@ -0,0 +1,4 @@ + +#define MDSPAN_IMPL_STANDARD_NAMESPACE atlas +#include "atlas/util/detail/mdspan/mdspan.hpp" + diff --git a/src/atlas_f/CMakeLists.txt b/src/atlas_f/CMakeLists.txt index f015676a5..f01da0392 100644 --- a/src/atlas_f/CMakeLists.txt +++ b/src/atlas_f/CMakeLists.txt @@ -127,18 +127,28 @@ generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/functionspace/EdgeColumns.h generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/functionspace/detail/PointCloudInterface.h MODULE atlas_functionspace_PointCloud_c_binding OUTPUT functionspace_PointCloud_c_binding.f90) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Nabla.h) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Nabla.h) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Method.h ) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/fvm/Method.h - MODULE atlas_fvm_method_c_binding - OUTPUT fvm_method_c_binding.f90 ) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/interpolation/Interpolation.h - MODULE atlas_interpolation_c_binding - OUTPUT interpolation_c_binding.f90 ) -generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/trans/detail/TransInterface.h - MODULE atlas_trans_c_binding - OUTPUT trans_c_binding.f90 ) + +if( atlas_HAVE_ATLAS_NUMERICS ) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Nabla.h) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Nabla.h) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/Method.h ) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/numerics/fvm/Method.h + MODULE atlas_fvm_method_c_binding + OUTPUT fvm_method_c_binding.f90 ) +endif() + +if( atlas_HAVE_ATLAS_INTERPOLATION ) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/interpolation/Interpolation.h + MODULE atlas_interpolation_c_binding + OUTPUT interpolation_c_binding.f90 ) +endif() + +if( atlas_HAVE_ATLAS_TRANS ) + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/trans/detail/TransInterface.h + MODULE atlas_trans_c_binding + OUTPUT trans_c_binding.f90 ) +endif() + generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/util/Allocate.h) generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/util/Metadata.h) generate_fortran_bindings(FORTRAN_BINDINGS ../atlas/util/Config.h) @@ -153,6 +163,33 @@ generate_fortran_bindings(FORTRAN_BINDINGS internals/Library.h) generate_fortran_bindings(FORTRAN_BINDINGS runtime/atlas_trace.h MODULE atlas_trace_c_binding OUTPUT atlas_trace_c_binding.f90 ) +list( APPEND atlas_trans_srcs + trans/atlas_Trans_module.F90 +) + +list( APPEND atlas_interpolation_srcs + interpolation/atlas_Interpolation_module.F90 +) + +list( APPEND atlas_numerics_srcs + numerics/atlas_Method_module.F90 + numerics/atlas_fvm_module.F90 + numerics/atlas_Nabla_module.F90 +) + +if( NOT atlas_HAVE_ATLAS_TRANS ) + unset( atlas_trans_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_INTERPOLATION ) + unset( atlas_interpolation_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_NUMERICS ) + unset( atlas_numerics_srcs ) +endif() + + ### atlas fortran lib ecbuild_add_library( TARGET atlas_f AUTO_VERSION @@ -175,7 +212,7 @@ ecbuild_add_library( TARGET atlas_f functionspace/atlas_functionspace_BlockStructuredColumns_module.F90 functionspace/atlas_functionspace_Spectral_module.F90 functionspace/atlas_functionspace_PointCloud_module.F90 - field/atlas_FieldSet_module.F90 + field/atlas_FieldSet_module.fypp field/atlas_State_module.F90 field/atlas_Field_module.fypp grid/atlas_Grid_module.F90 @@ -192,21 +229,19 @@ ecbuild_add_library( TARGET atlas_f mesh/atlas_Elements_module.F90 mesh/atlas_ElementType_module.F90 mesh/atlas_mesh_actions_module.F90 - numerics/atlas_Method_module.F90 - numerics/atlas_fvm_module.F90 - numerics/atlas_Nabla_module.F90 - interpolation/atlas_Interpolation_module.F90 parallel/atlas_GatherScatter_module.fypp parallel/atlas_Checksum_module.fypp parallel/atlas_HaloExchange_module.fypp projection/atlas_Projection_module.F90 - trans/atlas_Trans_module.F90 internals/atlas_read_file.h internals/atlas_read_file.cc internals/Library.h internals/Library.cc runtime/atlas_trace.cc runtime/atlas_Trace_module.F90 + ${atlas_trans_srcs} + ${atlas_interpolation_srcs} + ${atlas_numerics_srcs} PUBLIC_LIBS $ diff --git a/src/atlas_f/atlas_f.h.in b/src/atlas_f/atlas_f.h.in index fd0867af9..cea48ee0e 100644 --- a/src/atlas_f/atlas_f.h.in +++ b/src/atlas_f/atlas_f.h.in @@ -17,11 +17,14 @@ #define ATLAS_HAVE_OMP @atlas_HAVE_OMP_Fortran@ -#define ATLAS_HAVE_TRANS @atlas_HAVE_TRANS@ #define ATLAS_HAVE_ACC @atlas_HAVE_ACC@ #define ATLAS_BITS_GLOBAL @ATLAS_BITS_GLOBAL@ #define ATLAS_BITS_LOCAL @ATLAS_BITS_LOCAL@ +#define ATLAS_HAVE_TRANS @atlas_HAVE_ATLAS_TRANS@ +#define ATLAS_HAVE_INTERPOLATION @atlas_HAVE_ATLAS_INTERPOLATION@ +#define ATLAS_HAVE_NUMERICS @atlas_HAVE_ATLAS_NUMERICS@ + #define ATLAS_FINAL FCKIT_FINAL #ifndef PGIBUG_ATLAS_197 diff --git a/src/atlas_f/atlas_module.F90 b/src/atlas_f/atlas_module.F90 index 8931791df..daff7976e 100644 --- a/src/atlas_f/atlas_module.F90 +++ b/src/atlas_f/atlas_module.F90 @@ -63,8 +63,10 @@ module atlas_module & atlas_mesh_Nodes use atlas_HaloExchange_module, only: & & atlas_HaloExchange +#if ATLAS_HAVE_INTERPOLATION use atlas_Interpolation_module, only: & & atlas_Interpolation +#endif use atlas_GatherScatter_module, only: & & atlas_GatherScatter use atlas_Checksum_module, only: & @@ -102,8 +104,10 @@ module atlas_module & atlas_RotatedLonLatProjection, & & atlas_LambertConformalConicProjection, & & atlas_RotatedSchmidtProjection +#if ATLAS_HAVE_TRANS use atlas_Trans_module, only : & & atlas_Trans +#endif use atlas_kinds_module, only: & & ATLAS_KIND_GIDX, & & ATLAS_KIND_IDX, & @@ -117,12 +121,14 @@ module atlas_module & atlas_MatchingMeshPartitioner ! Deprecated ! use atlas_MatchingPartitioner instead use atlas_MeshGenerator_module, only: & & atlas_MeshGenerator +#ifdef ATLAS_HAVE_NUMERICS use atlas_Method_module, only: & & atlas_Method use atlas_fvm_module, only: & & atlas_fvm_Method use atlas_Nabla_module, only: & & atlas_Nabla +#endif use atlas_mesh_actions_module, only: & & atlas_build_parallel_fields, & & atlas_build_nodes_parallel_fields, & diff --git a/src/atlas_f/field/atlas_FieldSet_module.F90 b/src/atlas_f/field/atlas_FieldSet_module.fypp similarity index 65% rename from src/atlas_f/field/atlas_FieldSet_module.F90 rename to src/atlas_f/field/atlas_FieldSet_module.fypp index a8a09457c..e337c958c 100644 --- a/src/atlas_f/field/atlas_FieldSet_module.F90 +++ b/src/atlas_f/field/atlas_FieldSet_module.fypp @@ -8,9 +8,13 @@ #include "atlas/atlas_f.h" +#:include "atlas/atlas_f.fypp" +#:include "internals/atlas_generics.fypp" + module atlas_FieldSet_module use fckit_owned_object_module, only: fckit_owned_object +use atlas_field_module, only: atlas_field, array_c_to_f use atlas_kinds_module, only : ATLAS_KIND_IDX implicit none @@ -53,10 +57,33 @@ module atlas_FieldSet_module procedure, public :: set_dirty procedure, public :: halo_exchange +#:for rank in ranks +#:for dtype in dtypes + procedure, private :: access_data_${dtype}$_r${rank}$_by_name + procedure, private :: access_data_${dtype}$_r${rank}$_by_idx + procedure, private :: access_data_${dtype}$_r${rank}$_slice_by_name + procedure, private :: access_data_${dtype}$_r${rank}$_slice_by_idx +#:endfor +#:endfor + + generic, public :: data => & +#:for rank in ranks +#:for dtype in dtypes + & access_data_${dtype}$_r${rank}$_by_name, & + & access_data_${dtype}$_r${rank}$_by_idx, & + & access_data_${dtype}$_r${rank}$_slice_by_name, & + & access_data_${dtype}$_r${rank}$_slice_by_idx, & +#:endfor +#:endfor + & dummy + + procedure, private :: dummy + #if FCKIT_FINAL_NOT_INHERITING final :: atlas_FieldSet__final_auto #endif procedure, public :: has_field => has ! deprecated ! + END TYPE atlas_FieldSet !------------------------------------------------------------------------------ @@ -74,6 +101,86 @@ module atlas_FieldSet_module ! ----------------------------------------------------------------------------- ! FieldSet routines +#:for rank in ranks +#:for dtype,ftype,ctype in types +subroutine access_data_${dtype}$_r${rank}$_by_name(this, name, field) + use fckit_c_interop_module, only: c_str + use atlas_fieldset_c_binding + use, intrinsic :: iso_c_binding, only : c_ptr, c_int, c_long, c_float, c_double + class(atlas_FieldSet), intent(in) :: this + character(len=*), intent(in) :: name + ${ftype}$, pointer, intent(inout) :: field(${dim[rank]}$) + type(c_ptr) :: field_cptr + type(c_ptr) :: shape_cptr + type(c_ptr) :: strides_cptr + integer(c_int) :: rank + call atlas__FieldSet__data_${ctype}$_specf(this%CPTR_PGIBUG_A, c_str(name), field_cptr, rank, shape_cptr, strides_cptr) + call array_c_to_f(field_cptr, rank, shape_cptr, strides_cptr, field) +end subroutine +subroutine access_data_${dtype}$_r${rank}$_by_idx(this, idx, field) + use fckit_c_interop_module, only: c_str + use atlas_fieldset_c_binding + use, intrinsic :: iso_c_binding, only : c_ptr, c_int, c_long, c_float, c_double + class(atlas_FieldSet), intent(in) :: this + integer, intent(in) :: idx + ${ftype}$, pointer, intent(inout) :: field(${dim[rank]}$) + type(c_ptr) :: field_cptr + type(c_ptr) :: shape_cptr + type(c_ptr) :: strides_cptr + integer(c_int) :: rank + call atlas__FieldSet__data_${ctype}$_specf_by_idx(this%CPTR_PGIBUG_A, idx-1, field_cptr, rank, shape_cptr, strides_cptr) + call array_c_to_f(field_cptr, rank, shape_cptr, strides_cptr, field) +end subroutine +subroutine access_data_${dtype}$_r${rank}$_slice_by_name(this, name, slice, iblk) + use fckit_c_interop_module, only: c_str + use atlas_fieldset_c_binding + use, intrinsic :: iso_c_binding, only : c_ptr, c_int, c_long, c_float, c_double + class(atlas_FieldSet), intent(in) :: this + character(len=*), intent(in) :: name +#:if rank > 1 + ${ftype}$, pointer, intent(inout) :: slice(${dimr[rank]}$) +#:else + ${ftype}$, pointer, intent(inout) :: slice +#:endif + integer, intent(in) :: iblk + ${ftype}$, pointer :: field(${dim[rank]}$) + call access_data_${dtype}$_r${rank}$_by_name(this, c_str(name), field) +#:if rank > 1 + slice => field(${dimr[rank]}$, iblk) +#:else + slice => field(iblk) +#:endif +end subroutine +subroutine access_data_${dtype}$_r${rank}$_slice_by_idx(this, idx, slice, iblk) + use fckit_c_interop_module, only: c_str + use atlas_fieldset_c_binding + use, intrinsic :: iso_c_binding, only : c_ptr, c_int, c_long, c_float, c_double + class(atlas_FieldSet), intent(in) :: this + integer, intent(in) :: idx +#:if rank > 1 + ${ftype}$, pointer, intent(inout) :: slice(${dimr[rank]}$) +#:else + ${ftype}$, pointer, intent(inout) :: slice +#:endif + integer, intent(in) :: iblk + ${ftype}$, pointer :: field(${dim[rank]}$) + call access_data_${dtype}$_r${rank}$_by_idx(this, idx, field) +#:if rank > 1 + slice => field(${dimr[rank]}$, iblk) +#:else + slice => field(iblk) +#:endif +end subroutine +!------------------------------------------------------------------------------- + +#:endfor +#:endfor +subroutine dummy(this) + use atlas_fieldset_c_binding + class(atlas_FieldSet), intent(in) :: this + FCKIT_SUPPRESS_UNUSED(this) +end subroutine + function atlas_FieldSet__cptr(cptr) result(fieldset) use, intrinsic :: iso_c_binding, only: c_ptr type(atlas_FieldSet) :: fieldset diff --git a/src/atlas_f/field/atlas_Field_module.fypp b/src/atlas_f/field/atlas_Field_module.fypp index 6f859a5b2..68cc5f04a 100644 --- a/src/atlas_f/field/atlas_Field_module.fypp +++ b/src/atlas_f/field/atlas_Field_module.fypp @@ -23,6 +23,7 @@ public :: atlas_real public :: atlas_integer public :: atlas_logical public :: atlas_data_type +public :: array_c_to_f private @@ -75,6 +76,7 @@ contains #:for dtype in dtypes procedure, private :: access_data_${dtype}$_r${rank}$ procedure, private :: access_data_${dtype}$_r${rank}$_shape + procedure, private :: access_data_${dtype}$_r${rank}$_slice #:endfor #:endfor @@ -83,6 +85,7 @@ contains #:for dtype in dtypes & access_data_${dtype}$_r${rank}$, & & access_data_${dtype}$_r${rank}$_shape, & + & access_data_${dtype}$_r${rank}$_slice, & #:endfor #:endfor & dummy @@ -135,7 +138,6 @@ interface array_c_to_f end interface !------------------------------------------------------------------------------- - private :: fckit_owned_object private :: atlas_Config @@ -161,6 +163,7 @@ subroutine array_c_to_f_${dtype}$_r${rank}$(array_cptr,rank,shape_cptr,strides_c integer :: j if( rank /= ${rank}$ ) then + write(0,*) rank, "!=", ${rank}$ @:ATLAS_ABORT("Rank mismatch") endif @@ -199,6 +202,29 @@ subroutine access_data_${dtype}$_r${rank}$(this, field) call atlas__Field__data_${ctype}$_specf(this%CPTR_PGIBUG_A, field_cptr, rank, shape_cptr, strides_cptr) call array_c_to_f(field_cptr,rank,shape_cptr,strides_cptr, field) end subroutine +subroutine access_data_${dtype}$_r${rank}$_slice(this, slice, iblk) + use atlas_field_c_binding + use, intrinsic :: iso_c_binding, only : c_ptr, c_int, c_long, c_float, c_double + class(atlas_Field), intent(in) :: this + ${ftype}$, pointer :: field(${dim[rank]}$) +#:if rank > 1 + ${ftype}$, pointer, intent(inout) :: slice(${dimr[rank]}$) +#:else + ${ftype}$, pointer, intent(inout) :: slice +#:endif + integer, intent(in) :: iblk + type(c_ptr) :: field_cptr + type(c_ptr) :: shape_cptr + type(c_ptr) :: strides_cptr + integer(c_int) :: rank + call atlas__Field__data_${ctype}$_specf(this%CPTR_PGIBUG_A, field_cptr, rank, shape_cptr, strides_cptr) + call array_c_to_f(field_cptr, rank, shape_cptr, strides_cptr, field) +#:if rank > 1 + slice => field(${dimr[rank]}$, iblk) +#:else + slice => field(iblk) +#:endif +end subroutine !------------------------------------------------------------------------------- diff --git a/src/atlas_f/functionspace/atlas_functionspace_Spectral_module.F90 b/src/atlas_f/functionspace/atlas_functionspace_Spectral_module.F90 index ad5f50caf..ab8492ddf 100644 --- a/src/atlas_f/functionspace/atlas_functionspace_Spectral_module.F90 +++ b/src/atlas_f/functionspace/atlas_functionspace_Spectral_module.F90 @@ -15,7 +15,9 @@ module atlas_functionspace_Spectral_module use atlas_functionspace_module, only : atlas_FunctionSpace use atlas_Field_module, only: atlas_Field use atlas_FieldSet_module, only: atlas_FieldSet +#if ATLAS_HAVE_TRANS use atlas_Trans_module, only: atlas_Trans +#endif use atlas_Config_module, only: atlas_Config implicit none @@ -25,8 +27,10 @@ module atlas_functionspace_Spectral_module private :: atlas_FunctionSpace private :: atlas_Field private :: atlas_FieldSet -private :: atlas_Trans private :: atlas_Config +#if ATLAS_HAVE_TRANS +private :: atlas_Trans +#endif public :: atlas_functionspace_Spectral @@ -80,7 +84,9 @@ module atlas_functionspace_Spectral_module interface atlas_functionspace_Spectral module procedure atlas_functionspace_Spectral__cptr module procedure atlas_functionspace_Spectral__config +#if ATLAS_HAVE_TRANS module procedure atlas_functionspace_Spectral__trans +#endif end interface !------------------------------------------------------------------------------ @@ -114,6 +120,7 @@ function atlas_functionspace_Spectral__config(truncation,levels) result(this) call this%return() end function +#if ATLAS_HAVE_TRANS function atlas_functionspace_Spectral__trans(trans,levels) result(this) use atlas_functionspace_spectral_c_binding type(atlas_functionspace_Spectral) :: this @@ -131,6 +138,7 @@ function atlas_functionspace_Spectral__trans(trans,levels) result(this) call this%return() end function +#endif subroutine gather_field(this,local,global) use atlas_functionspace_spectral_c_binding diff --git a/src/atlas_f/internals/atlas_generics.fypp b/src/atlas_f/internals/atlas_generics.fypp index 78d78c353..d99c7f565 100644 --- a/src/atlas_f/internals/atlas_generics.fypp +++ b/src/atlas_f/internals/atlas_generics.fypp @@ -10,6 +10,7 @@ #:set ranks = [1,2,3,4] #:set dim = ['',':',':,:',':,:,:',':,:,:,:',':,:,:,:,:'] +#:set dimr = ['','',':',':,:',':,:,:',':,:,:,:'] #:set ftypes = ['integer(c_int)','integer(c_long)','real(c_float)','real(c_double)', 'logical'] #:set ctypes = ['int','long','float','double', 'int'] diff --git a/src/sandbox/interpolation/atlas-conservative-interpolation.cc b/src/sandbox/interpolation/atlas-conservative-interpolation.cc index 48b6e71eb..6e5199674 100644 --- a/src/sandbox/interpolation/atlas-conservative-interpolation.cc +++ b/src/sandbox/interpolation/atlas-conservative-interpolation.cc @@ -8,19 +8,6 @@ * nor does it submit to any jurisdiction. */ - -// TODO: -// ----- -// * Fix abort encountered with -// mpirun -np 4 atlas-conservative-interpolation --source.grid=O20 --target.grid=H8 --order=2 -// -// QUESTIONS: -// ---------- -// * Why sqrt in ConservativeSphericalPolygon in line -// remap_stat.errors[Statistics::Errors::REMAP_CONS] = std::sqrt(std::abs(err_remap_cons) / unit_sphere_area()); -// used to compute conservation_error - - #include #include #include @@ -35,6 +22,8 @@ #include "atlas/array/MakeView.h" #include "atlas/field.h" #include "atlas/grid.h" +#include "atlas/grid/Spacing.h" +#include "atlas/grid/detail/spacing/CustomSpacing.h" #include "atlas/interpolation/Interpolation.h" #include "atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.h" #include "atlas/mesh.h" @@ -73,13 +62,15 @@ class AtlasParallelInterpolation : public AtlasTool { add_option(new eckit::option::Separator("Source/Target options")); add_option(new SimpleOption("source.grid", "source gridname")); + add_option(new SimpleOption("source.partitioner", "source partitioner name (spherical-polygon, lonlat-polygon, brute-force)")); add_option(new SimpleOption("target.grid", "target gridname")); + add_option(new SimpleOption("target.partitioner", "target partitioner name (equal_regions, regular_bands, equal_bands)")); add_option(new SimpleOption("source.functionspace", "source functionspace, to override source grid default")); add_option(new SimpleOption("target.functionspace", "target functionspace, to override target grid default")); add_option(new SimpleOption("source.halo", "default=2")); - add_option(new SimpleOption("target.halo", "default=0")); + add_option(new SimpleOption("target.halo", "default=0 for CellColumns and 1 for NodeColumns")); add_option(new eckit::option::Separator("Interpolation options")); add_option(new SimpleOption("order", "Interpolation order. Supported: 1, 2 (default=1)")); @@ -182,8 +173,22 @@ std::function get_init(const AtlasTool::Args& args) } int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { - auto src_grid = Grid{args.getString("source.grid", "H16")}; - auto tgt_grid = Grid{args.getString("target.grid", "H32")}; + auto get_grid = [](std::string grid_name) { + int grid_number = std::atoi( grid_name.substr(1, grid_name.size()).c_str() ); + if (grid_name.at(0) == 'P') { + Log::info() << "P-grid number: " << grid_number << std::endl; + ATLAS_ASSERT(grid_number > 3); + std::vector y = {90, 89.9999, 0, -90}; + auto xspace = StructuredGrid::XSpace( grid::LinearSpacing(0, 360, grid_number, false) ); + auto yspace = StructuredGrid::YSpace( new grid::spacing::CustomSpacing( y.size(), y.data() ) ); + return StructuredGrid(xspace, yspace); + } + else { + return StructuredGrid{grid_name}; + } + }; + auto src_grid = get_grid(args.getString("source.grid", "H32")); + auto tgt_grid = get_grid(args.getString("target.grid", "H32")); auto create_functionspace = [&](Mesh& mesh, int halo, std::string type) -> FunctionSpace { if (type.empty()) { @@ -199,13 +204,13 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { return functionspace::CellColumns(mesh, option::halo(halo)); } else if (type == "NodeColumns") { - return functionspace::NodeColumns(mesh, option::halo(halo)); + return functionspace::NodeColumns(mesh, option::halo(std::max(1,halo))); } ATLAS_THROW_EXCEPTION("FunctionSpace " << type << " is not recognized."); }; timers.target_setup.start(); - auto tgt_mesh = Mesh{tgt_grid}; + auto tgt_mesh = Mesh{tgt_grid, grid::Partitioner(args.getString("target.partitioner", "regular_bands"))}; auto tgt_functionspace = create_functionspace(tgt_mesh, args.getLong("target.halo", 0), args.getString("target.functionspace", "")); auto tgt_field = tgt_functionspace.createField(); @@ -213,8 +218,8 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { timers.source_setup.start(); auto src_meshgenerator = - MeshGenerator{src_grid.meshgenerator() | option::halo(2) | util::Config("pole_elements", "pentagons")}; - auto src_partitioner = grid::MatchingPartitioner{tgt_mesh}; + MeshGenerator{src_grid.meshgenerator() | option::halo(2) | util::Config("pole_elements", "")}; + auto src_partitioner = grid::MatchingPartitioner{tgt_mesh, util::Config("partitioner",args.getString("source.partitioner", "spherical-polygon"))}; auto src_mesh = src_meshgenerator.generate(src_grid, src_partitioner); auto src_functionspace = create_functionspace(src_mesh, args.getLong("source.halo", 2), args.getString("source.functionspace", "")); @@ -230,14 +235,14 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { for (idx_t n = 0; n < lonlat.shape(0); ++n) { src_view(n) = f(PointLonLat{lonlat(n, LON), lonlat(n, LAT)}); } - src_field.set_dirty(false); + src_field.set_dirty(true); timers.initial_condition.start(); } - timers.interpolation_setup.start(); auto interpolation = Interpolation(option::type("conservative-spherical-polygon") | args, src_functionspace, tgt_functionspace); + Log::info() << interpolation << std::endl; timers.interpolation_setup.stop(); @@ -251,11 +256,13 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { using Statistics = interpolation::method::ConservativeSphericalPolygonInterpolation::Statistics; Statistics stats(metadata); if (args.getBool("statistics.accuracy", false)) { - stats.accuracy(interpolation, tgt_field, get_init(args)); + metadata.set( stats.accuracy(interpolation, tgt_field, get_init(args) ) ); } if (args.getBool("statistics.conservation", false)) { // compute difference field src_conservation_field = stats.diff(interpolation, src_field, tgt_field); + src_conservation_field.set_dirty(true); + src_conservation_field.haloExchange(); } } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 469aa77ab..767432d99 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -108,23 +108,41 @@ if( HAVE_CUDA ) set( ATLAS_TEST_ENVIRONMENT "ATLAS_RUN_NGPUS=1" ) endif() -add_subdirectory( acc ) -add_subdirectory( array ) add_subdirectory( util ) add_subdirectory( runtime ) -add_subdirectory( parallel ) -add_subdirectory( field ) -add_subdirectory( projection ) -add_subdirectory( grid ) -add_subdirectory( mesh ) -add_subdirectory( functionspace ) -add_subdirectory( io ) -add_subdirectory( output ) -add_subdirectory( numerics ) -add_subdirectory( trans ) -add_subdirectory( interpolation ) -add_subdirectory( linalg ) -add_subdirectory( redistribution ) + +if (atlas_HAVE_ATLAS_GRID) + add_subdirectory( projection ) + add_subdirectory( grid ) +endif() + +if (atlas_HAVE_ATLAS_FIELD) + add_subdirectory( acc ) + add_subdirectory( array ) + add_subdirectory( field ) +endif() + +if (atlas_HAVE_ATLAS_FUNCTIONSPACE) + add_subdirectory( parallel ) + add_subdirectory( mesh ) + add_subdirectory( functionspace ) + add_subdirectory( io ) + add_subdirectory( output ) + add_subdirectory( redistribution ) +endif() + +if (atlas_HAVE_ATLAS_TRANS) + add_subdirectory( trans ) +endif() + +if (atlas_HAVE_ATLAS_INTERPOLATION) + add_subdirectory( interpolation ) + add_subdirectory( linalg ) +endif() + +if (atlas_HAVE_ATLAS_NUMERICS) + add_subdirectory( numerics ) +endif() add_subdirectory( acceptance_tests ) add_subdirectory( export_tests ) diff --git a/src/tests/acceptance_tests/CMakeLists.txt b/src/tests/acceptance_tests/CMakeLists.txt index a9971c8a6..af3d19e10 100644 --- a/src/tests/acceptance_tests/CMakeLists.txt +++ b/src/tests/acceptance_tests/CMakeLists.txt @@ -10,6 +10,7 @@ ecbuild_add_executable( TARGET atlas-atest-mgrids SOURCES atest_mgrids.cc LIBS atlas + CONDITION atlas_HAVE_ATLAS_NUMERICS ) if( HAVE_TESTS ) @@ -23,7 +24,7 @@ set( HAVE_ACCEPTANCE_TESTS_LARGE ON ) function( atlas_atest_mgrids category case nprocs ) - if( HAVE_TRANS ) + if( atlas_HAVE_ECTRANS ) set( PARTITIONER "--partitioner=trans" ) endif() @@ -65,6 +66,7 @@ function( atlas_atest_mgrids category case nprocs ) endforeach() endfunction() +if( atlas_HAVE_ATLAS_NUMERICS ) if( HAVE_ACCEPTANCE_TESTS_SMALL ) unset( cases ) list( APPEND cases @@ -92,6 +94,6 @@ if( HAVE_ACCEPTANCE_TESTS_LARGE ) atlas_atest_mgrids( large ${case} "${nprocs}" ) endforeach() endif() - +endif() endif() diff --git a/src/tests/array/CMakeLists.txt b/src/tests/array/CMakeLists.txt index 3afbb4832..3a5583223 100644 --- a/src/tests/array/CMakeLists.txt +++ b/src/tests/array/CMakeLists.txt @@ -13,6 +13,12 @@ ecbuild_add_test( TARGET atlas_test_array_slicer ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +ecbuild_add_test( TARGET atlas_test_array_foreach + SOURCES test_array_foreach.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_test( TARGET atlas_test_array SOURCES test_array.cc LIBS atlas @@ -45,6 +51,11 @@ ecbuild_add_test( TARGET atlas_test_array_view_util ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +ecbuild_add_test( TARGET atlas_test_array_indexview + SOURCES test_indexview.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) if( CMAKE_BUILD_TYPE MATCHES "DEBUG" ) set ( CMAKE_NVCC_FLAGS "-G" ) diff --git a/src/tests/array/test_array_foreach.cc b/src/tests/array/test_array_foreach.cc new file mode 100644 index 000000000..f8c5b0b4a --- /dev/null +++ b/src/tests/array/test_array_foreach.cc @@ -0,0 +1,428 @@ +/* + * (C) Crown Copyright 2023 Met Office + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include +#include + +#include "atlas/array.h" +#include "atlas/array/MakeView.h" +#include "atlas/array/helpers/ArrayForEach.h" +#include "atlas/array/helpers/ArraySlicer.h" +#include "atlas/util/Config.h" + +#include "tests/AtlasTestEnvironment.h" + +using namespace atlas::array; +using namespace atlas::array::helpers; + +namespace atlas { +namespace test { + +CASE("test_array_foreach_1_view") { + + const auto arr = ArrayT(2, 3); + const auto view = make_view(arr); + + // Test slice shapes. + + const auto loopFunctorDim0 = [](auto&& slice) { + EXPECT_EQ(slice.rank(), 1); + EXPECT_EQ(slice.shape(0), 3); + }; + ArrayForEach<0>::apply(std::tie(view), loopFunctorDim0); + + const auto loopFunctorDim1 = [](auto&& slice) { + EXPECT_EQ(slice.rank(), 1); + EXPECT_EQ(slice.shape(0), 2); + }; + ArrayForEach<1>::apply(std::tie(view), loopFunctorDim1); + + // Test that slice resolves to double. + + const auto loopFunctorDimAll = [](auto&& slice) { + static_assert(std::is_convertible_v); + }; + ArrayForEach<0, 1>::apply(std::tie(view), loopFunctorDimAll); + + // Test ghost functionality. + + auto ghost = ArrayT(2); + auto ghostView = make_view(ghost); + ghostView.assign({0, 1}); + + auto count = int {}; + const auto countNonGhosts = [&count](auto&&...) { ++count; }; + ArrayForEach<0>::apply(execution::seq, std::tie(view), ghostView, countNonGhosts); + EXPECT_EQ(count, 1); + + count = 0; + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { + // Wrap ghostView to use correct number of indices. + return ghostView(idx); + }; + ArrayForEach<0, 1>::apply(execution::seq, std::tie(view), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 3); +} + +CASE("test_array_foreach_2_views") { + + const auto arr1 = ArrayT(2, 3); + const auto view1 = make_view(arr1); + + const auto arr2 = ArrayT(2, 3, 4); + const auto view2 = make_view(arr2); + + // Test slice shapes. + + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 3); + + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 3); + EXPECT_EQ(slice2.shape(1), 4); + }; + ArrayForEach<0>::apply(std::tie(view1, view2), loopFunctorDim0); + + const auto loopFunctorDim1 = [](auto&& slice1, auto&& slice2) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 2); + + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 2); + EXPECT_EQ(slice2.shape(1), 4); + }; + ArrayForEach<1>::apply(std::tie(view1, view2), loopFunctorDim1); + + // Test that slice resolves to double. + + const auto loopFunctorDimAll = [](auto&& slice2) { + static_assert(std::is_convertible_v); + }; + ArrayForEach<0, 1, 2>::apply(std::tie(view2), loopFunctorDimAll); + + // Test ghost functionality. + + auto ghost = ArrayT(2); + auto ghostView = make_view(ghost); + ghostView.assign({0, 1}); + + auto count = int {}; + const auto countNonGhosts = [&count](auto&&...) { ++count; }; + ArrayForEach<0>::apply(execution::seq, std::tie(view2), ghostView, countNonGhosts); + EXPECT_EQ(count, 1); + + count = 0; + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { + // Wrap ghostView to use correct number of indices. + return ghostView(idx); + }; + ArrayForEach<0, 1>::apply(execution::seq, std::tie(view2), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 3); + + count = 0; + ArrayForEach<0, 1, 2>::apply(execution::seq, std::tie(view2), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 12); +} + +CASE("test_array_foreach_3_views") { + + const auto arr1 = ArrayT(2, 3); + const auto view1 = make_view(arr1); + + const auto arr2 = ArrayT(2, 3, 4); + const auto view2 = make_view(arr2); + + const auto arr3 = ArrayT(2, 3, 4, 5); + const auto view3 = make_view(arr3); + + // Test slice shapes. + + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2, auto&& slice3) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 3); + + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 3); + EXPECT_EQ(slice2.shape(1), 4); + + EXPECT_EQ(slice3.rank(), 3); + EXPECT_EQ(slice3.shape(0), 3); + EXPECT_EQ(slice3.shape(1), 4); + EXPECT_EQ(slice3.shape(2), 5); + }; + ArrayForEach<0>::apply(std::tie(view1, view2, view3), loopFunctorDim0); + + const auto loopFunctorDim1 = [](auto&& slice1, auto&& slice2, auto&& slice3) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 2); + + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 2); + EXPECT_EQ(slice2.shape(1), 4); + + EXPECT_EQ(slice3.rank(), 3); + EXPECT_EQ(slice3.shape(0), 2); + EXPECT_EQ(slice3.shape(1), 4); + EXPECT_EQ(slice3.shape(2), 5); + }; + ArrayForEach<1>::apply(std::tie(view1, view2, view3), loopFunctorDim1); + + // Test that slice resolves to double. + + const auto loopFunctorDimAll = [](auto&& slice3) { + static_assert(std::is_convertible_v); + }; + ArrayForEach<0, 1, 2, 3>::apply(std::tie(view3), loopFunctorDimAll); + + // Test ghost functionality. + + auto ghost = ArrayT(2); + auto ghostView = make_view(ghost); + ghostView.assign({0, 1}); + + auto count = int {}; + const auto countNonGhosts = [&count](auto&&...) { ++count; }; + ArrayForEach<0>::apply(execution::seq, std::tie(view3), ghostView, countNonGhosts); + EXPECT_EQ(count, 1); + + count = 0; + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { + // Wrap ghostView to use correct number of indices. + return ghostView(idx); + }; + ArrayForEach<0, 1>::apply(execution::seq, std::tie(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 3); + + count = 0; + ArrayForEach<0, 1, 2>::apply(execution::seq, std::tie(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 12); + + count = 0; + ArrayForEach<0, 1, 2, 3>::apply(execution::seq, std::tie(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 60); +} + + +CASE("test_array_foreach_forwarding") { + + const auto arr1 = ArrayT(2, 3); + const auto view1 = make_view(arr1); + + auto arr2 = ArrayT(2, 3, 4); + auto view2 = make_view(arr2); + + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2) { + EXPECT_EQUAL(slice1.rank(), 1); + EXPECT_EQUAL(slice1.shape(0), 3); + + EXPECT_EQUAL(slice2.rank(), 2); + EXPECT_EQUAL(slice2.shape(0), 3); + EXPECT_EQUAL(slice2.shape(1), 4); + }; + + ArrayForEach<0>::apply(std::make_tuple(view1, view2), loopFunctorDim0); + ArrayForEach<0>::apply(std::tie(view1, view2), loopFunctorDim0); + ArrayForEach<0>::apply(std::forward_as_tuple(view1, view2), loopFunctorDim0); +} + +CASE("test_array_foreach_data_integrity") { + + auto arr1 = ArrayT(200, 3); + auto view1 = make_view(arr1); + + auto arr2 = ArrayT(200, 3, 4); + auto view2 = make_view(arr2); + + for (auto idx = size_t{}; idx < arr1.size(); ++idx) { + static_cast(arr1.data())[idx] = idx; + } + + for (auto idx = size_t{}; idx < arr2.size(); ++idx) { + static_cast(arr2.data())[idx] = idx; + } + + const auto scaleDataDim0 = [](auto&& slice1, auto&& slice2) { + + static_assert(std::is_convertible_v); + slice1 *= 2.; + + const auto scaleDataDim1 = [](auto&& slice) { + + static_assert(std::is_convertible_v); + slice *= 3.; + }; + ArrayForEach<0>::apply(execution::seq, std::tie(slice2), scaleDataDim1); + }; + ArrayForEach<0, 1>::apply(std::tie(view1, view2), scaleDataDim0); + + for (auto idx = size_t{}; idx < arr1.size(); ++idx) { + EXPECT_EQ(static_cast(arr1.data())[idx], 2. * idx); + } + + for (auto idx = size_t{}; idx < arr2.size(); ++idx) { + EXPECT_EQ(static_cast(arr2.data())[idx], 3. * idx); + } +} + +template +double timeLoop(const IterationMethod& iterationMethod, int num_iter, int num_first, + const Operation& operation, double baseline, const std::string& output) { + double time{0}; + for (int i=0; i{stop - start}; + if (i>=num_first) { + time += duration.count(); + } + } + time /= double(num_iter); + Log::info() << "Elapsed time: " + output + "= " << time << "s"; + if (baseline != 0) { + Log::info() << "\t; relative to baseline : " << 100.*time/baseline << "%"; + } + Log::info() << std::endl; + return time; +} + +CASE("test_array_foreach_performance") { + int ni = 50000; + int nj = 100; + int num_iter = 20; + int num_first = 3; + + if( ATLAS_ARRAYVIEW_BOUNDS_CHECKING ) { + ni = 5000; + nj = 20; + num_iter = 1; + num_first = 0; + Log::info() << "WARNING: Following timings contain very expensive bounds checking. Compile with -DENABLE_BOUNDSCHECKING=OFF for fair comparison" << std::endl; + } + + + + auto arr1 = ArrayT(ni, nj); + auto view1 = make_view(arr1); + + auto arr2 = ArrayT(ni, nj); + auto view2 = make_view(arr2); + + for (auto idx = size_t{}; idx < arr2.size(); ++idx) { + static_cast(arr2.data())[idx] = 2 * idx + 1; + } + + auto arr3 = ArrayT(ni, nj); + auto view3 = make_view(arr2); + + + for (auto idx = size_t{}; idx < arr3.size(); ++idx) { + static_cast(arr3.data())[idx] = 3 * idx + 1; + } + + const auto add = [](double& a1, const double& a2, + const double& a3) { a1 = a2 + a3; }; + + const auto trig = [](double& a1, const double& a2, + const double& a3) { a1 = std::sin(a2) + std::cos(a3); }; + + const auto rawPointer = [&](const auto& operation) { + const size_t size = arr1.size(); + auto* p1 = view1.data(); + const auto* p2 = view2.data(); + const auto* p3 = view3.data(); + for (size_t idx = 0; idx < size; ++idx) { + operation(p1[idx], p2[idx], p3[idx]); + } + }; + + const auto ijLoop = [&](const auto& operation) { + const idx_t ni = view1.shape(0); + const idx_t nj = view1.shape(1); + for (idx_t i = 0; i < ni; ++i) { + for (idx_t j = 0; j < nj; ++j) { + operation(view1(i, j), view2(i, j), view3(i, j)); + } + } + }; + + const auto jiLoop = [&](const auto& operation) { + const idx_t ni = view1.shape(0); + const idx_t nj = view1.shape(1); + for (idx_t j = 0; j < nj; ++j) { + for (idx_t i = 0; i < ni; ++i) { + operation(view1(i, j), view2(i, j), view3(i, j)); + } + } + }; + + const auto forEachCol = [&](const auto& operation) { + const auto function = [&](auto&& slice1, auto&& slice2, auto&& slice3) { + const idx_t size = slice1.shape(0); + for (idx_t idx = 0; idx < size; ++idx) { + operation(slice1(idx), slice2(idx), slice3(idx)); + } + }; + ArrayForEach<0>::apply(execution::seq, std::tie(view1, view2, view3), function); + }; + + const auto forEachLevel = [&](const auto& operation) { + const auto function = [&](auto&& slice1, auto&& slice2, auto&& slice3) { + const idx_t size = slice1.shape(0); + for (idx_t idx = 0; idx < size; ++idx) { + operation(slice1(idx), slice2(idx), slice3(idx)); + } + }; + ArrayForEach<1>::apply(execution::seq, std::tie(view1, view2, view3), function); + }; + + const auto forEachAll = [&](const auto& operation) { + ArrayForEach<0, 1>::apply(execution::seq, std::tie(view1, view2, view3), operation); + }; + + const auto forEachNested = [&](const auto& operation) { + const auto function = [&](auto&& slice1, auto&& slice2, auto&& slice3) { + ArrayForEach<0>::apply(execution::seq, std::tie(slice1, slice2, slice3), operation); + }; + ArrayForEach<0>::apply(execution::seq, std::tie(view1, view2, view3), function); + }; + + const auto forEachConf = [&](const auto& operation) { + const auto function = [&](auto&& slice1, auto&& slice2, auto&& slice3) { + ArrayForEach<0>::apply(option::execution_policy(execution::seq), std::tie(slice1, slice2, slice3), operation); + }; + ArrayForEach<0>::apply(option::execution_policy(execution::seq), std::tie(view1, view2, view3), function); + }; + + double baseline; + baseline = timeLoop(rawPointer, num_iter, num_first, add, 0, "Addition; raw pointer "); + timeLoop(ijLoop, num_iter, num_first, add, baseline, "Addition; for loop (i, j) "); + timeLoop(jiLoop, num_iter, num_first, add, baseline, "Addition; for loop (j, i) "); + timeLoop(forEachCol, num_iter, num_first, add, baseline, "Addition; for each (columns) "); + timeLoop(forEachLevel, num_iter, num_first, add, baseline, "Addition; for each (levels) "); + timeLoop(forEachAll, num_iter, num_first, add, baseline, "Addition; for each (all elements) "); + timeLoop(forEachNested, num_iter, num_first, add, baseline, "Addition; for each (nested) "); + timeLoop(forEachConf, num_iter, num_first, add, baseline, "Addition; for each (nested, config) "); + Log::info() << std::endl; + + num_first = 2; + num_iter = 5; + baseline = timeLoop(rawPointer, num_iter, num_first, trig, 0, "Trig ; raw pointer "); + timeLoop(ijLoop, num_iter, num_first, trig, baseline, "Trig ; for loop (i, j) "); + timeLoop(jiLoop, num_iter, num_first, trig, baseline, "Trig ; for loop (j, i) "); + timeLoop(forEachCol, num_iter, num_first, trig, baseline, "Trig ; for each (columns) "); + timeLoop(forEachLevel, num_iter, num_first, trig, baseline, "Trig ; for each (levels) "); + timeLoop(forEachAll, num_iter, num_first, trig, baseline, "Trig ; for each (all elements) "); + timeLoop(forEachNested, num_iter, num_first, trig, baseline, "Trig ; for each (nested) "); + timeLoop(forEachConf, num_iter, num_first, trig, baseline, "Trig ; for each (nested, config) "); +} + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { return atlas::test::run(argc, argv); } diff --git a/src/tests/util/test_indexview.cc b/src/tests/array/test_indexview.cc similarity index 100% rename from src/tests/util/test_indexview.cc rename to src/tests/array/test_indexview.cc diff --git a/src/tests/field/CMakeLists.txt b/src/tests/field/CMakeLists.txt index 93e025b24..64a59da78 100644 --- a/src/tests/field/CMakeLists.txt +++ b/src/tests/field/CMakeLists.txt @@ -6,12 +6,25 @@ # granted to it by virtue of its status as an intergovernmental organisation nor # does it submit to any jurisdiction. +ecbuild_add_test( TARGET atlas_test_fieldset + SOURCES test_fieldset.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_test( TARGET atlas_test_field_missingvalue SOURCES test_field_missingvalue.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +ecbuild_add_test( TARGET atlas_test_field_foreach + SOURCES test_field_foreach.cc + LIBS atlas + OMP 4 + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + if( HAVE_FCTEST ) add_fctest( TARGET atlas_fctest_field diff --git a/src/tests/field/fctest_field.F90 b/src/tests/field/fctest_field.F90 index a0264f7e4..8ea98e09d 100644 --- a/src/tests/field/fctest_field.F90 +++ b/src/tests/field/fctest_field.F90 @@ -172,7 +172,7 @@ module fcta_Field_fixture implicit none type(atlas_Field) :: field real(c_double), pointer :: view(:,:,:) - field = atlas_Field("field_2",atlas_real(c_double),(/3,5,10/),alignment=4) + field = atlas_Field("field_3",atlas_real(c_double),(/3,5,10/),alignment=4) call field%data(view) FCTEST_CHECK_EQUAL( size(view,1) , 3 ) FCTEST_CHECK_EQUAL( size(view,2) , 5 ) @@ -183,6 +183,72 @@ module fcta_Field_fixture FCTEST_CHECK_EQUAL( field%stride(1), 1 ) FCTEST_CHECK_EQUAL( field%stride(2), 4 ) FCTEST_CHECK_EQUAL( field%stride(3), 4*5 ) + call field%final() +END_TEST + +TEST( test_fieldset_slice ) + implicit none + type(atlas_FieldSet) :: fieldset + type(atlas_Field) :: field + integer, pointer :: view1d(:), view3d(:,:,:) + integer, pointer :: slice0d, slice2d(:,:) + + ! slicing of a three-dimensional field + field = atlas_Field("field_4",atlas_integer(),(/3,5,10/)) + call field%data(view3d) + view3d(1,2,3) = 123 + call field%data(slice2d,3) + FCTEST_CHECK_EQUAL( size(slice2d,1) , 3 ) + FCTEST_CHECK_EQUAL( size(slice2d,2) , 5 ) + FCTEST_CHECK_EQUAL( slice2d(1,2), 123 ) + slice2d(1,2) = slice2d(1,2) * 2 + call field%data(view3d) + FCTEST_CHECK_EQUAL( view3d(1,2,3), 246 ) + + ! slicing of a one-dimensional field + field = atlas_Field("field_5",atlas_integer(),(/3/)) + call field%data(view1d) + view1d(2) = 123 + call field%data(slice0d,2) + FCTEST_CHECK_EQUAL( slice0d, 123 ) + slice0d = slice0d * 2 + call field%data(view1d) + FCTEST_CHECK_EQUAL( view1d(2), 246 ) + call field%final() + + ! slicing of a three-dimensional field through a fieldset by name and by idx + fieldset = atlas_FieldSet() + field = atlas_Field("field_6",atlas_integer(),(/3,5,10/)) + call fieldset%add(field) + field = atlas_Field("field_7",atlas_integer(),(/3/)) + call fieldset%add(field) + call fieldset%data("field_6", view3d) + view3d(1,2,3) = 122 + call fieldset%data(1, view3d) + view3d(1,2,3) = 123 + call fieldset%data("field_6", slice2d, 3) + slice2d(1,2) = slice2d(1,2) + 1 + call fieldset%data(1, slice2d, 3) + slice2d(1,2) = slice2d(1,2) - 1 + FCTEST_CHECK_EQUAL( size(slice2d,1) , 3 ) + FCTEST_CHECK_EQUAL( size(slice2d,2) , 5 ) + FCTEST_CHECK_EQUAL( slice2d(1,2), 123 ) + slice2d(1,2) = slice2d(1,2) * 2 + call fieldset%data('field_6', view3d) + FCTEST_CHECK_EQUAL( view3d(1,2,3), 246 ) + + ! slicing of a one-dimensional field through a fieldset by name and by idx + call fieldset%data('field_7', view1d) + view1d(2) = 122 + call fieldset%data(2, view1d) + view1d(2) = 123 + call field%data(slice0d, 2) + FCTEST_CHECK_EQUAL( slice0d, 123 ) + slice0d = slice0d * 2 + call field%data(view1d) + FCTEST_CHECK_EQUAL( view1d(2), 246 ) + call field%final() + call fieldset%final() END_TEST ! ----------------------------------------------------------------------------- diff --git a/src/tests/field/test_field_foreach.cc b/src/tests/field/test_field_foreach.cc new file mode 100644 index 000000000..8187df9ae --- /dev/null +++ b/src/tests/field/test_field_foreach.cc @@ -0,0 +1,639 @@ +/* + * (C) Crown Copyright 2023 Met Office + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include "tests/AtlasTestEnvironment.h" + +#include "atlas/field/for_each.h" +#include "atlas/option.h" + +using namespace atlas::array; +using namespace atlas::array::helpers; + +namespace atlas { +namespace test { + +auto split_index_3d = [] (int idx, auto shape) { + int i = idx / (shape[2]*shape[1]); + int j = (idx - i * (shape[2] * shape[1]))/shape[2]; + int k = idx - i * (shape[2]*shape[1]) - j*shape[2]; + return std::make_tuple(i,j,k); +}; + +CASE( "test field::for_each_value" ) { + Field f("name", array::make_datatype(), array::make_shape({4,2,8})); + + int v=0; + field::for_each_value(f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + x = 100*i + 10*j + k; + v++; + }); + + v = 0; + field::for_each_value(f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + double expected = 100*i + 10*j + k; + EXPECT_EQ(x, expected); + ++v; + }); +} + + +CASE( "test field::for_each_value_masked; horizontal_dimension {0} (implicit)" ) { + Field f("name", array::make_datatype(), array::make_shape({4,2,8})); + + array::make_view(f).assign(0.); + + Field ghost("ghost", array::make_datatype(), array::make_shape(4) ); + array::make_view(ghost).assign(0); + array::make_view(ghost)(2) = 1; + + field::for_each_value_masked(ghost, f, [&](double& x) { + x = 1.; + }); + + int v = 0; + field::for_each_value(f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + double expected = 1.; + if (i==2) { + expected = 0.; + } + EXPECT_EQ(x, expected); + ++v; + }); +} + +CASE( "test field::for_each_value_masked; horizontal_dimension {0,2} mask_rank1" ) { + Field f("name", array::make_datatype(), array::make_shape({4,2,8})); + f.set_horizontal_dimension({0,2}); + + array::make_view(f).assign(0.); + + Field ghost("ghost", array::make_datatype(), array::make_shape(f.shape(0)*f.shape(2)) ); + array::make_view(ghost).assign(0); + array::make_view(ghost)(2*f.shape(2)+3) = 1; + + field::for_each_value_masked(ghost, f, [&](double& x) { + x = 1.; + }); + + int v = 0; + field::for_each_value(f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + double expected = 1.; + if (i==2 && k==3) { + expected = 0.; + } + EXPECT_EQ(x, expected); + ++v; + }); +} + +CASE( "test field::for_each_value_masked; horizontal_dimension {0,2} mask_rank2" ) { + Field f("name", array::make_datatype(), array::make_shape({4,2,8})); + f.set_horizontal_dimension({0,2}); + + array::make_view(f).assign(0.); + + Field ghost("ghost", array::make_datatype(), array::make_shape(f.shape(0),f.shape(2)) ); + array::make_view(ghost).assign(0); + array::make_view(ghost)(2,3) = 1; + + field::for_each_value_masked(ghost, f, [&](double& x) { + x = 1.; + }); + + int v = 0; + field::for_each_value(f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + double expected = 1.; + if (i==2 && k==3) { + expected = 0.; + } + EXPECT_EQ(x, expected); + ++v; + }); +} + +CASE( "test field::for_each_column" ) { + Field f("name", array::make_datatype(), array::make_shape({6,4,2})); + + int v=0; + field::for_each_value(execution::seq, f, [&](double& x) { + auto [i,j,k] = split_index_3d(v,f.shape()); + x = 100*i + 10*j + k; + v++; + }); + + auto print_column_1d = [&](const array::View& column) { + Log::info() << ""; + for( idx_t jlev=0; jlev& column) { + Log::info() << ""; + for( idx_t jlev=0; jlev visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 1, // + 10, 11, // + 20, 21, // + 30, 31, // + 100, 101, // + 110, 111, // + 120, 121, // + 130, 131, // + 200, 201, // + 210, 211, // + 220, 221, // + 230, 231, // + 300, 301, // + 310, 311, // + 320, 321, // + 330, 331, // + 400, 401, // + 410, 411, // + 420, 421, // + 430, 431, // + 500, 501, // + 510, 511, // + 520, 521, // + 530, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } + SECTION( "horizontal_dimension = {0,2}") { + f.set_horizontal_dimension({0,2}); + + std::vector visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 10, 20, 30, // + 1, 11, 21, 31, // + 100, 110, 120, 130, // + 101, 111, 121, 131, // + 200, 210, 220, 230, // + 201, 211, 221, 231, // + 300, 310, 320, 330, // + 301, 311, 321, 331, // + 400, 410, 420, 430, // + 401, 411, 421, 431, // + 500, 510, 520, 530, // + 501, 511, 521, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } + SECTION( "horizontal_dimension = {1,2}") { + f.set_horizontal_dimension({1,2}); + + std::vector visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 100, 200, 300, 400, 500, // + 1, 101, 201, 301, 401, 501, // + 10, 110, 210, 310, 410, 510, // + 11, 111, 211, 311, 411, 511, // + 20, 120, 220, 320, 420, 520, // + 21, 121, 221, 321, 421, 521, // + 30, 130, 230, 330, 430, 530, // + 31, 131, 231, 331, 431, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } + + SECTION( "horizontal_dimension = {0}") { + f.set_horizontal_dimension({0}); + + std::vector visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 1, // + 10, 11, // + 20, 21, // + 30, 31, // + 100, 101, // + 110, 111, // + 120, 121, // + 130, 131, // + 200, 201, // + 210, 211, // + 220, 221, // + 230, 231, // + 300, 301, // + 310, 311, // + 320, 321, // + 330, 331, // + 400, 401, // + 410, 411, // + 420, 421, // + 430, 431, // + 500, 501, // + 510, 511, // + 520, 521, // + 530, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } + + SECTION( "horizontal_dimension = {1}") { + f.set_horizontal_dimension({1}); + + std::vector visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 1, // + 100, 101, // + 200, 201, // + 300, 301, // + 400, 401, // + 500, 501, // + 10, 11, // + 110, 111, // + 210, 211, // + 310, 311, // + 410, 411, // + 510, 511, // + 20, 21, // + 120, 121, // + 220, 221, // + 320, 321, // + 420, 421, // + 520, 521, // + 30, 31, // + 130, 131, // + 230, 231, // + 330, 331, // + 430, 431, // + 530, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } + + SECTION( "horizontal_dimension = {2}") { + f.set_horizontal_dimension({2}); + + std::vector visited_values; + field::for_each_column(f, [&visited_values](const array::View& column) { + for( idx_t jlev=0; jlev expected_values = { + 0, 10, 20, 30, // + 100, 110, 120, 130, // + 200, 210, 220, 230, // + 300, 310, 320, 330, // + 400, 410, 420, 430, // + 500, 510, 520, 530, // + 1, 11, 21, 31, // + 101, 111, 121, 131, // + 201, 211, 221, 231, // + 301, 311, 321, 331, // + 401, 411, 421, 431, // + 501, 511, 521, 531 // + }; + EXPECT_EQ( visited_values, expected_values ); + } +} + +CASE( "test field::for_each_column multiple" ) { + Field f1("f1", array::make_datatype(),array::make_shape({6,4,2})); + Field f2("f2", array::make_datatype(),array::make_shape({6,4,2})); + Field f3("f2", array::make_datatype(),array::make_shape({6,4,2})); + + int v=0; + field::for_each_value(f1, f2, [&](double& x1, double& x2) { + auto [i,j,k] = split_index_3d(v,f1.shape()); + x1 = 100*i + 10*j + k; + x2 = 2*x1; + v++; + }); + + auto check_result = [&]() { + auto view = array::make_view(f3); + for (idx_t i=0; i(f3); + for (idx_t i=0; i mask) { + auto h_dim = f1.horizontal_dimension(); + ATLAS_ASSERT(h_dim.size() == mask.size()); + std::vector h_shape; + for (auto h: h_dim) { + h_shape.emplace_back(f1.shape(h)); + } + + size_t h_size = 1; + for (auto h: h_shape) { + h_size *= h; + } + + Field ghost("ghost", array::make_datatype(), array::make_shape(h_size)); + auto ghost_v = array::make_view(ghost); + ghost_v.assign(0); + if( mask.size() == 1 ) { + ghost_v(mask[0]) = 1; + } + if( mask.size() == 2 ) { + ghost_v(h_shape[1]*mask[0] + mask[1]) = 1; + } + if( mask.size() == 3 ) { + ghost_v(h_shape[2]*h_shape[1]*mask[0] + h_shape[1]*mask[1] + mask[2]) = 1; + } + return ghost; + }; + + SECTION( "for_each_column; horizontal_dimension = {0,1}") { + f1.set_horizontal_dimension({0,1}); + array::make_view(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column(f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column_masked(ghost, + f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(f3).assign(0.); + field::for_each_column_masked(ghost, f1, f2, f3, []( + array::View&& c1, + array::View&& c2, + array::View&& c3) { + for( idx_t jlev=0; jlev(),array::make_shape({ni,nj,nk})); + Field f2("f2", array::make_datatype(),array::make_shape({ni,nj,nk})); + Field f3("f2", array::make_datatype(),array::make_shape({ni,nj,nk})); + + int v=0; + field::for_each_value(f1, f2, [&](double& x1, double& x2) { + auto [i,j,k] = split_index_3d(v,f1.shape()); + x1 = 100*i + 10*j + k; + x2 = 2*x1; + v++; + }); + + auto time_function = [](const auto& function) -> double { + runtime::trace::StopWatch stopwatch; + for (size_t j=0; j<5; ++j) { + function(); + } + size_t N=10; + stopwatch.start(); + for (size_t j=0; j(f1); + auto v2 = array::make_view(f2); + auto v3 = array::make_view(f3); + for (size_t i=0; i(), array::make_shape(10,4))); + auto field_1 = fieldset.add(Field("1", make_datatype(), array::make_shape(10,5))); + auto field_2 = fieldset.add(Field("2", make_datatype(), array::make_shape(10,6))); + + EXPECT(fieldset.has("0")); + EXPECT(fieldset.has("1")); + EXPECT(fieldset.has("2")); + + field_0.rename("field_0"); + field_1.rename("field_1"); + field_2.rename("field_2"); + + EXPECT(fieldset.has("field_0")); + EXPECT(fieldset.has("field_1")); + EXPECT(fieldset.has("field_2")); + EXPECT(!fieldset.has("0")); + EXPECT(!fieldset.has("1")); + EXPECT(!fieldset.has("2")); + + EXPECT_EQ(fieldset.field(0).name(),"field_0"); + EXPECT_EQ(fieldset.field(1).name(),"field_1"); + EXPECT_EQ(fieldset.field(2).name(),"field_2"); + + EXPECT_EQ(fieldset.field("field_0").shape(1),4); + EXPECT_EQ(fieldset.field("field_1").shape(1),5); + EXPECT_EQ(fieldset.field("field_2").shape(1),6); + + field_1.rename(""); + EXPECT(!fieldset.has("field_1")); + EXPECT_EQ(fieldset.field(1).name(),std::string("")); + EXPECT(fieldset.has("[1]")); + +} + +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/functionspace/CMakeLists.txt b/src/tests/functionspace/CMakeLists.txt index 47a1fff31..0859497ee 100644 --- a/src/tests/functionspace/CMakeLists.txt +++ b/src/tests/functionspace/CMakeLists.txt @@ -8,7 +8,7 @@ if( HAVE_FCTEST ) - if( NOT HAVE_TRANS ) + if( NOT atlas_HAVE_ECTRANS ) set( transi_HAVE_MPI 1 ) set( ectrans_HAVE_MPI 1 ) endif() @@ -92,6 +92,22 @@ ecbuild_add_test( TARGET atlas_test_pointcloud CONDITION eckit_HAVE_MPI AND NOT HAVE_GRIDTOOLS_STORAGE ) +ecbuild_add_test( TARGET atlas_test_pointcloud_he_2PE + SOURCES test_pointcloud_haloexchange_2PE.cc + LIBS atlas + MPI 2 + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION eckit_HAVE_MPI AND NOT HAVE_GRIDTOOLS_STORAGE +) + +ecbuild_add_test( TARGET atlas_test_pointcloud_he_3PE + SOURCES test_pointcloud_haloexchange_3PE.cc + LIBS atlas + MPI 3 + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION eckit_HAVE_MPI AND NOT HAVE_GRIDTOOLS_STORAGE +) + ecbuild_add_test( TARGET atlas_test_reduced_halo SOURCES test_reduced_halo.cc LIBS atlas diff --git a/src/tests/functionspace/fctest_blockstructuredcolumns.F90 b/src/tests/functionspace/fctest_blockstructuredcolumns.F90 index 708f6e60e..6d69bef50 100644 --- a/src/tests/functionspace/fctest_blockstructuredcolumns.F90 +++ b/src/tests/functionspace/fctest_blockstructuredcolumns.F90 @@ -7,6 +7,7 @@ ! This File contains Unit Tests for testing the ! C++ / Fortran Interfaces to the State Datastructure +! ! @author Willem Deconinck ! @author Slavko Brdar diff --git a/src/tests/functionspace/test_blockstructuredcolumns.cc b/src/tests/functionspace/test_blockstructuredcolumns.cc index 953dd9bd2..f100c0a20 100644 --- a/src/tests/functionspace/test_blockstructuredcolumns.cc +++ b/src/tests/functionspace/test_blockstructuredcolumns.cc @@ -49,6 +49,9 @@ void run_scatter_gather(const Grid& grid, const BlockStructuredColumns& fs, int auto field = fs.createField(glb_field); + EXPECT_EQ(glb_field.horizontal_dimension(), (std::vector{0})); + EXPECT_EQ(field.horizontal_dimension(), (std::vector{0,3})); + fs.scatter(glb_field, field); auto glb_field_2 = fs.createField(glb_field , option::global(atlas::mpi::comm().size()-1)); @@ -179,7 +182,7 @@ CASE("test_BlockStructuredColumns") { } SECTION("test_BlockStructuredColumns scatter/gather") { - auto fs = functionspace::StructuredColumns(grid, config); + auto fs = functionspace::BlockStructuredColumns(grid, config); run_scatter_gather(grid, fs, nlev, nvar); run_scatter_gather(grid, fs, nlev, nvar); run_scatter_gather(grid, fs, nlev, nvar); diff --git a/src/tests/functionspace/test_pointcloud.cc b/src/tests/functionspace/test_pointcloud.cc index 4115f593c..af43bb4d2 100644 --- a/src/tests/functionspace/test_pointcloud.cc +++ b/src/tests/functionspace/test_pointcloud.cc @@ -1,13 +1,15 @@ /* - * (C) Copyright 2013 ECMWF. + * (C) Copyright 2013 ECMWF + * (C) Crown Copyright 2023 Met Office * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation - * nor does it submit to any jurisdiction. + * */ + +#include + #include "atlas/array.h" #include "atlas/field.h" #include "atlas/functionspace/PointCloud.h" @@ -157,178 +159,6 @@ CASE("test_createField") { EXPECT_EQ(f3.shape(2), 5); } -//----------------------------------------------------------------------------- - -double innerproductwithhalo(const atlas::Field& f1, const atlas::Field& f2) { - long sum(0); - - auto view1 = atlas::array::make_view(f1); - auto view2 = atlas::array::make_view(f2); - - for (atlas::idx_t jn = 0; jn < f1.shape(0); ++jn) { - for (atlas::idx_t jl = 0; jl < f1.levels(); ++jl) { - sum += view1(jn, jl) * view2(jn, jl); - } - } - - atlas::mpi::comm().allReduceInPlace(sum, eckit::mpi::sum()); - return sum; -} - - -CASE("test_createFieldSet") { - // Here is some ascii art to describe the test. - // Remote index is the same for both PEs in this case - // - // _1____0__ - // 1| 0 1 | 0 - // 3| 2 3 | 2 - // --------- - // 3 2 - // - // Point order (locally is also the same) - // - // _4____5__ - // 11| 0 1 | 6 - // 10| 2 3 | 7 - // --------- - // 9 8 - // - // Partition index - // PE 0: PE 1: - // - // _1____1__ _0____0__ - // 1| 0 0 | 1 0| 1 1 | 0 - // 1| 0 0 | 1 0| 1 1 | 0 - // --------- --------- - // 1 1 0 0 - // - // Initial Values - // PE 0: PE 1: - // - // _0____0__ _0____0__ - // 0|10 11 | 0 0|20 21 | 0 - // 0|12 13 | 0 0|22 23 | 0 - // --------- --------- - // 0 0 0 0 - // - // Values after halo exchange - // PE 0: PE 1: - // - // 21___20__ 11___10__ - // 21|10 11 | 20 11|20 21 | 10 - // 23|12 13 | 22 13|22 23 | 12 - // --------- --------- - // 23 22 13 12 - // - // Values after halo exchange and adjoint halo exchange - // PE 0: PE 1: - // - // _0____0__ _0___0__ - // 0|30 33 | 0 0|60 63 | 0 - // 0|36 39 | 0 0|66 69 | 0 - // --------- --------- - // 0 0 0 0 - - double tolerance(1e-16); - Field lonlat("lonlat", array::make_datatype(), array::make_shape(12, 2)); - Field ghost("ghost", array::make_datatype(), array::make_shape(12)); - Field remote_index("remote_index", array::make_datatype(), array::make_shape(12)); - Field partition("partition", array::make_datatype(), array::make_shape(12)); - - auto lonlatv = array::make_view(lonlat); - auto ghostv = array::make_view(ghost); - auto remote_indexv = array::make_indexview(remote_index); - auto partitionv = array::make_view(partition); - - ghostv.assign({ 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1}); - - remote_indexv.assign({0, 1, 2, 3, 1, 0, 0, 2, 2, 3, 3, 1}); - - - if (atlas::mpi::rank() == 0) { - // center followed by clockwise halo starting from top left - lonlatv.assign({-45.0, 45.0, - 45.0, 45.0, - -45.0, -45.0, - -45.0, 45.0, // center - 225.0, 45.0, 135.0, 45.0, // up - 135.0, 45.0, 135.0, -45.0, //left - 135.0, -45.0, 135.0, 45.0, // down - 135.0, 45.0, 225.0, 45.0}); // right - - partitionv.assign({0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1}); - - - - } else if (atlas::mpi::rank() == 1) { - // center followed by clockwise halo starting from top left - lonlatv.assign({135.0, 45.0, 225.0, 45.0, 135.0, -45.0, 135.0, 45.0, // center - 45.0, 45.0, -45.0, 45.0, // up - -45.0, 45.0, -45.0, -45.0, // left - -45.0, -45.0, -45.0, 45.0, // down - -45.0, 45.0, 45.0, 45.0}); // right - - partitionv.assign({1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0}); - - } - - atlas::FieldSet fset; - fset.add(lonlat); - fset.add(ghost); - fset.add(remote_index); - fset.add(partition); - - auto fs2 = functionspace::PointCloud(fset); - Field f1 = fs2.createField(option::name("f1") | option::levels(2)); - Field f2 = fs2.createField(option::name("f2") | option::levels(2)); - auto f1v = array::make_view(f1); - auto f2v = array::make_view(f2); - - f1v.assign(0.0); - f2v.assign(0.0); - for (idx_t i = 0; i < f2v.shape(0); ++i) { - for (idx_t l = 0; l < f2v.shape(1); ++l) { - auto ghostv2 = array::make_view(fs2.ghost()); - if (ghostv2(i) == 0) { - f1v(i, l) = (atlas::mpi::rank() +1) * 10.0 + i; - f2v(i, l) = f1v(i, l); - } - } - } - - f2.haloExchange(); - - // adjoint test - double sum1 = innerproductwithhalo(f2, f2); - - f2.adjointHaloExchange(); - - double sum2 = innerproductwithhalo(f1, f2); - - atlas::mpi::comm().allReduceInPlace(sum1, eckit::mpi::sum()); - atlas::mpi::comm().allReduceInPlace(sum2, eckit::mpi::sum()); - EXPECT(std::abs(sum1 - sum2)/ std::abs(sum1) < tolerance); - atlas::Log::info() << "adjoint test passed :: " - << "sum1 " << sum1 << " sum2 " << sum2 << " normalised difference " - << std::abs(sum1 - sum2)/ std::abs(sum1) << std::endl; - - // In this case the effect of the halo exchange followed by the adjoint halo exchange - // multiples the values by a factor of 3 (see pictures above) - for (idx_t i = 0; i < f2v.shape(0); ++i) { - for (idx_t l = 0; l < f2v.shape(1); ++l) { - EXPECT( std::abs(f2v(i, l) - 3.0 * f1v(i, l)) < tolerance); - } - } - atlas::Log::info() << "values from halo followed by halo adjoint are as expected " - << std::endl; -} - - - //----------------------------------------------------------------------------- } // namespace test diff --git a/src/tests/functionspace/test_pointcloud_haloexchange_2PE.cc b/src/tests/functionspace/test_pointcloud_haloexchange_2PE.cc new file mode 100644 index 000000000..35a93b268 --- /dev/null +++ b/src/tests/functionspace/test_pointcloud_haloexchange_2PE.cc @@ -0,0 +1,285 @@ +/* + * (C) Copyright 2023 ECMWF + * (C) Crown Copyright 2023 Met Office + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + */ + + +#include "atlas/array.h" +#include "atlas/field.h" +#include "atlas/functionspace/PointCloud.h" +#include "atlas/option.h" +#include "atlas/parallel/mpi/mpi.h" + +#include "tests/AtlasTestEnvironment.h" + +using namespace eckit; +using namespace atlas::functionspace; +using namespace atlas::util; + +namespace atlas { +namespace test { + + +double innerproductwithhalo(const Field& f1, const Field& f2) { + long sum(0); + + auto view1 = array::make_view(f1); + auto view2 = array::make_view(f2); + + for (idx_t jn = 0; jn < f1.shape(0); ++jn) { + for (idx_t jl = 0; jl < f1.levels(); ++jl) { + sum += view1(jn, jl) * view2(jn, jl); + } + } + + atlas::mpi::comm().allReduceInPlace(sum, eckit::mpi::sum()); + return sum; +} + + +CASE("test_halo_exchange_01") { + // Here is some ascii art to describe the test. + // Remote index is the same for both PEs in this case + // + // _1____0__ + // 1| 0 1 | 0 + // 3| 2 3 | 2 + // --------- + // 3 2 + // + // Point order (locally is also the same) + // + // _4____5__ + // 11| 0 1 | 6 + // 10| 2 3 | 7 + // --------- + // 9 8 + // + // Partition index + // PE 0: PE 1: + // + // _1____1__ _0____0__ + // 1| 0 0 | 1 0| 1 1 | 0 + // 1| 0 0 | 1 0| 1 1 | 0 + // --------- --------- + // 1 1 0 0 + // + // Initial Values + // PE 0: PE 1: + // + // _0____0__ _0____0__ + // 0|10 11 | 0 0|20 21 | 0 + // 0|12 13 | 0 0|22 23 | 0 + // --------- --------- + // 0 0 0 0 + // + // Values after halo exchange + // PE 0: PE 1: + // + // 21___20__ 11___10__ + // 21|10 11 | 20 11|20 21 | 10 + // 23|12 13 | 22 13|22 23 | 12 + // --------- --------- + // 23 22 13 12 + // + // Values after halo exchange and adjoint halo exchange + // PE 0: PE 1: + // + // _0____0__ _0___0__ + // 0|30 33 | 0 0|60 63 | 0 + // 0|36 39 | 0 0|66 69 | 0 + // --------- --------- + // 0 0 0 0 + + double tolerance(1e-16); + Field lonlat("lonlat", array::make_datatype(), array::make_shape(12, 2)); + Field ghost("ghost", array::make_datatype(), array::make_shape(12)); + Field remote_index("remote_index", array::make_datatype(), array::make_shape(12)); + Field partition("partition", array::make_datatype(), array::make_shape(12)); + + auto lonlatv = array::make_view(lonlat); + auto ghostv = array::make_view(ghost); + auto remote_indexv = array::make_indexview(remote_index); + auto partitionv = array::make_view(partition); + + ghostv.assign({ 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1}); + + remote_indexv.assign({0, 1, 2, 3, 1, 0, 0, 2, 2, 3, 3, 1}); + + + if (atlas::mpi::rank() == 0) { + // center followed by clockwise halo starting from top left + lonlatv.assign({-45.0, 45.0, 45.0, 45.0, // center, first row + -45.0, -45.0, 45.0, -45.0, // center, second row + 225.0, 45.0, 135.0, 45.0, // up + 135.0, 45.0, 135.0, -45.0, // right + 135.0, -45.0, 225.0, -45.0, // down + 225.0, -45.0, 225.0, 45.0}); // left + + partitionv.assign({0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1}); + + + + } else if (atlas::mpi::rank() == 1) { + // center followed by clockwise halo starting from top left + lonlatv.assign({135.0, 45.0, 225.0, 45.0, + 135.0, -45.0, 225.0, -45.0, + 45.0, 45.0, -45.0, 45.0, + -45.0, 45.0, -45.0, -45.0, + -45.0, -45.0, 45.0, -45.0, + 45.0, -45.0, 45.0, 45.0}); + + partitionv.assign({1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0}); + + } + + FieldSet fset; + fset.add(lonlat); + fset.add(ghost); + fset.add(remote_index); + fset.add(partition); + + auto fs2 = functionspace::PointCloud(fset); + Field f1 = fs2.createField(option::name("f1") | option::levels(2)); + Field f2 = fs2.createField(option::name("f2") | option::levels(2)); + auto f1v = array::make_view(f1); + auto f2v = array::make_view(f2); + + f1v.assign(0.0); + f2v.assign(0.0); + for (idx_t i = 0; i < f2v.shape(0); ++i) { + for (idx_t l = 0; l < f2v.shape(1); ++l) { + auto ghostv2 = array::make_view(fs2.ghost()); + if (ghostv2(i) == 0) { + f1v(i, l) = (atlas::mpi::rank() +1) * 10.0 + i; + f2v(i, l) = f1v(i, l); + } + } + } + + f2.haloExchange(); + + // adjoint test + double sum1 = innerproductwithhalo(f2, f2); + + f2.adjointHaloExchange(); + + double sum2 = innerproductwithhalo(f1, f2); + + atlas::mpi::comm().allReduceInPlace(sum1, eckit::mpi::sum()); + atlas::mpi::comm().allReduceInPlace(sum2, eckit::mpi::sum()); + EXPECT(std::abs(sum1 - sum2)/ std::abs(sum1) < tolerance); + atlas::Log::info() << "adjoint test passed :: " + << "sum1 " << sum1 << " sum2 " << sum2 << " normalised difference " + << std::abs(sum1 - sum2)/ std::abs(sum1) << std::endl; + + // In this case the effect of the halo exchange followed by the adjoint halo exchange + // multiples the values by a factor of 3 (see pictures above) + for (idx_t i = 0; i < f2v.shape(0); ++i) { + for (idx_t l = 0; l < f2v.shape(1); ++l) { + EXPECT( std::abs(f2v(i, l) - 3.0 * f1v(i, l)) < tolerance); + } + } + atlas::Log::info() << "values from halo followed by halo adjoint are as expected " + << std::endl; +} + + +CASE("test_halo_exchange_02") { + + double tolerance(1e-16); + + Field lonlat("lonlat", array::make_datatype(), array::make_shape(12, 2)); + Field ghost("ghost", array::make_datatype(), array::make_shape(12)); + + auto lonlatv = array::make_view(lonlat); + auto ghostv = array::make_view(ghost); + + if (atlas::mpi::rank() == 0) { + // center followed by clockwise halo starting from top left + lonlatv.assign({-45.0, 45.0, 45.0, 45.0, // center, first row + -45.0, -45.0, 45.0, -45.0, // center, second row + 225.0, 45.0, 135.0, 45.0, // up + 135.0, 45.0, 135.0, -45.0, // right + 135.0, -45.0, 225.0, -45.0, // down + 225.0, -45.0, 225.0, 45.0}); // left + + ghostv.assign({ 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1}); + + } else if (atlas::mpi::rank() == 1) { + // center followed by clockwise halo starting from top left + lonlatv.assign({135.0, 45.0, 225.0, 45.0, + 135.0, -45.0, 225.0, -45.0, + 45.0, 45.0, -45.0, 45.0, + -45.0, 45.0, -45.0, -45.0, + -45.0, -45.0, 45.0, -45.0, + 45.0, -45.0, 45.0, 45.0}); + + ghostv.assign({ 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1}); + } + + + auto pcfs = functionspace::PointCloud(lonlat, ghost); + + Field f1 = pcfs.createField(option::name("f1") | option::levels(2)); + Field f2 = pcfs.createField(option::name("f2") | option::levels(2)); + auto f1v = array::make_view(f1); + auto f2v = array::make_view(f2); + + f1v.assign(0.0); + f2v.assign(0.0); + + for (idx_t i = 0; i < f2v.shape(0); ++i) { + for (idx_t l = 0; l < f2v.shape(1); ++l) { + auto ghostv2 = array::make_view(pcfs.ghost()); + if (ghostv2(i) == 0) { + f1v(i, l) = (atlas::mpi::rank() +1) * 10.0 + i; + f2v(i, l) = f1v(i, l); + } + } + } + + f2.haloExchange(); + + // adjoint test + double sum1 = innerproductwithhalo(f2, f2); + + f2.adjointHaloExchange(); + + double sum2 = innerproductwithhalo(f1, f2); + + atlas::mpi::comm().allReduceInPlace(sum1, eckit::mpi::sum()); + atlas::mpi::comm().allReduceInPlace(sum2, eckit::mpi::sum()); + EXPECT(std::abs(sum1 - sum2)/ std::abs(sum1) < tolerance); + atlas::Log::info() << "adjoint test passed :: " + << "sum1 " << sum1 << " sum2 " << sum2 << " normalised difference " + << std::abs(sum1 - sum2)/ std::abs(sum1) << std::endl; + + // In this case the effect of the halo exchange followed by the adjoint halo exchange + // multiples the values by a factor of 3 (see pictures above) + for (idx_t i = 0; i < f2v.shape(0); ++i) { + for (idx_t l = 0; l < f2v.shape(1); ++l) { + EXPECT( std::abs(f2v(i, l) - 3.0 * f1v(i, l)) < tolerance); + } + } + atlas::Log::info() << "values from halo followed by halo adjoint are as expected " + << std::endl; + +} + + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/functionspace/test_pointcloud_haloexchange_3PE.cc b/src/tests/functionspace/test_pointcloud_haloexchange_3PE.cc new file mode 100644 index 000000000..b3add4cf4 --- /dev/null +++ b/src/tests/functionspace/test_pointcloud_haloexchange_3PE.cc @@ -0,0 +1,130 @@ +/* + * (C) Copyright 2023 ECMWF + * (C) Crown Copyright 2023 Met Office + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * + */ + + +#include + +#include "atlas/array.h" +#include "atlas/field.h" +#include "atlas/functionspace/PointCloud.h" +#include "atlas/option.h" +#include "atlas/parallel/mpi/mpi.h" + +#include "tests/AtlasTestEnvironment.h" + +using namespace eckit; +using namespace atlas::functionspace; +using namespace atlas::util; + +namespace atlas { +namespace test { + + +CASE("test_halo_exchange_01") { + + // + // ++ point order ++ + // + // PE 0 PE 1 + // _________________ + // | 0 1 || 0 1 | + // | 3 2 || 3 2 | + // ------------------ + // ------------------ + // | 0 1 2 3 | + // | 7 6 5 4 | + // ------------------ + // PE 2 + // + // 'own points': clockwise starting from top left + // + + + // number of points (own + ghost) + int no_points; + if (atlas::mpi::rank() == 0) { + no_points = 8; + } else if (atlas::mpi::rank() == 1) { + no_points = 8; + } else if (atlas::mpi::rank() == 2) { + no_points = 12; + } + + Field lonlat("lonlat", array::make_datatype(), + array::make_shape(no_points, 2)); + // ghost points flags: 0={is not a ghost point}, 1={is a ghost point} + Field gpoints("ghost", array::make_datatype(), + array::make_shape(no_points)); + + auto lonlatv = array::make_view(lonlat); + auto gpointsv = array::make_view(gpoints); + + if (atlas::mpi::rank() == 0) { + // own points: clockwise starting from top left + // halo points: clockwise starting from top left + lonlatv.assign({0.0, 1.0, 1.0, 1.0, // center, first row [own] + 1.0, -1.0, 0.0, -1.0, // center, second row [own] + 2.0, 1.0, 2.0, -1.0, // right [ghost] + 1.0, -2.0, 0.0, -2.0}); // down [ghost] + + gpointsv.assign({0, 0, 0, 0, + 1, 1, 1, 1}); + + } else if (atlas::mpi::rank() == 1) { + lonlatv.assign({2.0, 1.0, 3.0, 1.0, // center, first row [own] + 3.0, -1.0, 2.0, -1.0, // center, second row [own] + 3.0, -2.0, 2.0, -2.0, // down [ghost] + 1.0, -1.0, 1.0, 1.0}); // left [ghost] + + gpointsv.assign({0, 0, 0, 0, + 1, 1, 1, 1}); + + } else if (atlas::mpi::rank() == 2) { + lonlatv.assign({0.0, -2.0, 1.0, -2.0, 2.0, -2.0, 3.0, -2.0, // center, first row [own] + 3.0, -3.0, 2.0, -3.0, 1.0, -3.0, 0.0, -3.0, // center, second row [own] + 0.0, -1.0, 1.0, -1.0, 2.0, -1.0, 3.0, -1.0}); // top [ghost] + + gpointsv.assign({0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1}); + + } + + + // function space + auto pcfs = functionspace::PointCloud(lonlat, gpoints); + + // remote indexes (reference) + std::vector remote_idxs_ref; + if (atlas::mpi::rank() == 0) { + remote_idxs_ref = {0, 1, 2, 3, 0, 3, 1, 0}; + } else if (atlas::mpi::rank() == 1) { + remote_idxs_ref = {0, 1, 2, 3, 3, 2, 2, 1}; + } else if (atlas::mpi::rank() == 2) { + remote_idxs_ref = {0, 1, 2, 3, 4, 5, 6, 7, 3, 2, 3, 2}; + } + + // remote indexes + auto remote_index = pcfs.remote_index(); + + EXPECT(remote_index.size() == (lonlat.size()/2));; + + auto remote_indexv = array::make_indexview(remote_index); + for (idx_t i = 0; i < remote_indexv.shape(0); ++i) { + EXPECT(remote_indexv(i) == remote_idxs_ref.at(i));; + } + +} + + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/grid/CMakeLists.txt b/src/tests/grid/CMakeLists.txt index 3fd83edd2..8ca6b6276 100644 --- a/src/tests/grid/CMakeLists.txt +++ b/src/tests/grid/CMakeLists.txt @@ -6,6 +6,7 @@ # granted to it by virtue of its status as an intergovernmental organisation nor # does it submit to any jurisdiction. +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) if( HAVE_FCTEST ) foreach(test fctest_griddistribution @@ -19,24 +20,31 @@ if( HAVE_FCTEST ) endforeach() add_fctest( TARGET atlas_fctest_unstructuredgrid SOURCES fctest_unstructuredgrid.F90 LINKER_LANGUAGE Fortran LIBS atlas_f CONDITION HAVE_TESSELATION ) endif() +endif() foreach(test test_domain - test_field test_grid_iterator - test_grids test_stretchedrotatedgaussian test_grid_cropping test_vertical test_spacing - test_state test_largegrid test_grid_hash - test_cubedsphere) - + ) ecbuild_add_test( TARGET atlas_${test} SOURCES ${test}.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +endforeach() +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) +foreach(test + test_field + test_grids + test_state + test_cubedsphere + ) + ecbuild_add_test( TARGET atlas_${test} SOURCES ${test}.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) endforeach() +endif() set( _WITH_MPI ) if( eckit_HAVE_MPI ) @@ -48,6 +56,7 @@ ecbuild_add_test( TARGET atlas_test_distribution_regular_bands SOURCES test_distribution_regular_bands.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) diff --git a/src/tests/grid/test_field.cc b/src/tests/grid/test_field.cc index f54929968..a9607c582 100644 --- a/src/tests/grid/test_field.cc +++ b/src/tests/grid/test_field.cc @@ -95,6 +95,38 @@ CASE("test_implicit_conversion") { TakeArray cta(f); } +CASE("test_clone") { + Field org("origin", array::make_datatype(), array::make_shape(10, 2)); + array::ArrayView orgv = array::make_view(org); + double zz = 0.0; + for (size_t ii = 0; ii < org.shape()[0]; ++ii) { + for (size_t jj = 0; jj < org.shape()[1]; ++jj) { + zz += 1.0; + orgv(ii, jj) = zz; + } + } + + Field dst = org.clone(); + + for (size_t ii = 0; ii < org.shape()[0]; ++ii) { + for (size_t jj = 0; jj < org.shape()[1]; ++jj) { + orgv(ii, jj) = -999.999; + } + } + + EXPECT(dst.rank() == 2); + EXPECT(dst.shape()[0] == 10); + EXPECT(dst.shape()[1] == 2); + array::ArrayView dstv = array::make_view(dst); + zz = 0.0; + for (size_t ii = 0; ii < dst.shape()[0]; ++ii) { + for (size_t jj = 0; jj < dst.shape()[1]; ++jj) { + zz += 1.0; + EXPECT(dstv(ii, jj) == zz); + } + } +} + CASE("test_wrap_rawdata_through_array") { std::vector rawdata(20, 8.); util::ObjectHandle array(array::Array::wrap(rawdata.data(), array::make_shape(10, 2))); @@ -132,12 +164,12 @@ CASE("test_field_aligned") { EXPECT_EQ(field.strides()[2], 1); }; SECTION("field(name,datatype,spec)") { - Field field("name", make_datatype(), ArraySpec{make_shape(10, 5, 3), ArrayAlignment(4)}); + Field field("name", array::make_datatype(), ArraySpec{make_shape(10, 5, 3), ArrayAlignment(4)}); check_field(field); } SECTION("field(config)") { Field field(util::Config("creator", "ArraySpec") | // - util::Config("datatype", make_datatype().str()) | // + util::Config("datatype", array::make_datatype().str()) | // option::shape({10, 5, 3}) | // option::alignment(4)); check_field(field); diff --git a/src/tests/grid/test_stretchedrotatedgaussian.cc b/src/tests/grid/test_stretchedrotatedgaussian.cc index 88c4368a3..66ea4c7de 100644 --- a/src/tests/grid/test_stretchedrotatedgaussian.cc +++ b/src/tests/grid/test_stretchedrotatedgaussian.cc @@ -1,7 +1,6 @@ #include #include -#include "atlas/array.h" #include "atlas/grid.h" #include "atlas/option.h" #include "atlas/util/Config.h" diff --git a/src/tests/interpolation/CMakeLists.txt b/src/tests/interpolation/CMakeLists.txt index 24eb949cc..596729209 100644 --- a/src/tests/interpolation/CMakeLists.txt +++ b/src/tests/interpolation/CMakeLists.txt @@ -13,12 +13,6 @@ ecbuild_add_test( TARGET atlas_test_Quad3D ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) -ecbuild_add_test( TARGET atlas_test_interpolation_conservative - SOURCES test_interpolation_conservative.cc - LIBS atlas - ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} -) - ecbuild_add_test( TARGET atlas_test_Quad2D SOURCES test_Quad2D.cc LIBS atlas @@ -31,6 +25,14 @@ ecbuild_add_test( TARGET atlas_test_Triag2D ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +if( atlas_HAVE_ATLAS_INTERPOLATION ) + +ecbuild_add_test( TARGET atlas_test_interpolation_conservative + SOURCES test_interpolation_conservative.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_test( TARGET atlas_test_interpolation_finite_element SOURCES test_interpolation_finite_element.cc LIBS atlas @@ -119,3 +121,5 @@ ecbuild_add_test( TARGET atlas_test_interpolation_cubedsphere CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 6 ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) + +endif() \ No newline at end of file diff --git a/src/tests/interpolation/CubicInterpolationPrototype.h b/src/tests/interpolation/CubicInterpolationPrototype.h index 9888bb500..33715a1a4 100644 --- a/src/tests/interpolation/CubicInterpolationPrototype.h +++ b/src/tests/interpolation/CubicInterpolationPrototype.h @@ -138,7 +138,7 @@ class CubicVerticalInterpolation { double d3 = z - zvec[3]; w[0] = (d1 * d2 * d3) / dc0; -#if defined(_CRAYC) && ATLAS_BUILD_TYPE_RELEASE +#if defined(_CRAYC) || defined(__NVCOMPILER) && ATLAS_BUILD_TYPE_RELEASE // prevents FE_INVALID somehow (tested with Cray 8.7) ATLAS_ASSERT(!std::isnan(w[0])); #endif @@ -256,7 +256,7 @@ class CubicHorizontalInterpolation { auto& weights_j = weights.weights_j; weights_j[0] = (dl2 * dl3 * dl4) / dcl1; -#if defined(_CRAYC) && ATLAS_BUILD_TYPE_RELEASE +#if defined(_CRAYC) || defined(__NVCOMPILER) && ATLAS_BUILD_TYPE_RELEASE // prevents FE_INVALID somehow (tested with Cray 8.7) ATLAS_ASSERT(!std::isnan(weights_j[0])); #endif diff --git a/src/tests/interpolation/test_interpolation_conservative.cc b/src/tests/interpolation/test_interpolation_conservative.cc index 92b426ff0..5cfde0448 100644 --- a/src/tests/interpolation/test_interpolation_conservative.cc +++ b/src/tests/interpolation/test_interpolation_conservative.cc @@ -25,6 +25,7 @@ #include "atlas/meshgenerator.h" #include "atlas/option.h" #include "atlas/util/Config.h" +#include "atlas/util/function/VortexRollup.h" #include "tests/AtlasTestEnvironment.h" @@ -36,17 +37,26 @@ using ConservativeMethod = interpolation::method::ConservativeSphericalPolygonIn using Statistics = ConservativeMethod::Statistics; void do_remapping_test(Grid src_grid, Grid tgt_grid, std::function func, - Statistics& remap_stat_1, Statistics& remap_stat_2) { + Statistics& remap_stat_1, Statistics& remap_stat_2, bool src_cell_data, bool tgt_cell_data) { + std::string src_data_type = (src_cell_data ? "CellColumns(" : "NodeColumns("); + std::string tgt_data_type = (tgt_cell_data ? "CellColumns(" : "NodeColumns("); + Log::info() << "+-----------------------\n"; + Log::info() << src_data_type << src_grid.name() << ") --> " << tgt_data_type << tgt_grid.name() <<")\n"; + Log::info() << "+-----------------------\n"; Log::info().indent(); + // setup conservative remap: compute weights, polygon intersection, etc util::Config config("type", "conservative-spherical-polygon"); config.set("order", 1); config.set("validate", true); config.set("statistics.intersection", true); config.set("statistics.conservation", true); + config.set("src_cell_data", src_cell_data); + config.set("tgt_cell_data", tgt_cell_data); auto conservative_interpolation = Interpolation(config, src_grid, tgt_grid); Log::info() << conservative_interpolation << std::endl; + Log::info() << std::endl; // create source field from analytic function "func" const auto& src_fs = conservative_interpolation.source(); @@ -85,6 +95,7 @@ void do_remapping_test(Grid src_grid, Grid tgt_grid, std::function 1st order constructing new matrix"); @@ -95,14 +106,16 @@ void do_remapping_test(Grid src_grid, Grid tgt_grid, std::function 1st order matrix-free"); cfg.set("matrix_free", true); cfg.set("order", 1); auto interpolation = Interpolation(cfg, src_grid, tgt_grid, cache); Log::info() << interpolation << std::endl; interpolation.execute(src_field, tgt_field); + Log::info() << std::endl; } auto cache_2 = interpolation::Cache{}; { @@ -113,14 +126,16 @@ void do_remapping_test(Grid src_grid, Grid tgt_grid, std::function 2nd order matrix-free"); cfg.set("matrix_free", true); cfg.set("order", 2); auto interpolation = Interpolation(cfg, src_grid, tgt_grid, cache); Log::info() << interpolation << std::endl; interpolation.execute(src_field, tgt_field); + Log::info() << std::endl; } { ATLAS_TRACE("cached -> 2nd order using cached matrix"); @@ -129,10 +144,10 @@ void do_remapping_test(Grid src_grid, Grid tgt_grid, std::function tol) { - auto improvement = [](double& e, double& r) { return 100. * (r - e) / r; }; + //auto improvement = [](double& e, double& r) { return 100. * (r - e) / r; }; + auto improvement = [](double& e, double& r) { return r - e; }; double err; // check polygon intersections - err = remap_stat_1.errors[Statistics::Errors::GEO_DIFF]; - Log::info() << "Polygon area computation improvement: " << improvement(err, tol[0]) << " %" << std::endl; + err = remap_stat_1.errors[Statistics::Errors::SRCTGT_INTERSECTPLG_DIFF]; + Log::info() << "Polygon area computation (new < ref) = (" << err << " < " << tol[0] << ")" << std::endl; EXPECT(err < tol[0]); - err = remap_stat_1.errors[Statistics::Errors::GEO_L1]; - Log::info() << "Polygon intersection improvement : " << improvement(err, tol[1]) << " %" << std::endl; + err = remap_stat_1.errors[Statistics::Errors::TGT_INTERSECTPLG_L1]; + Log::info() << "Polygon intersection (new < ref) = (" << err << " < " << tol[1] << ")" << std::endl; EXPECT(err < tol[1]); // check remap accuracy - err = remap_stat_1.errors[Statistics::Errors::REMAP_L2]; - Log::info() << "1st order accuracy improvement : " << improvement(err, tol[2]) << " %" << std::endl; + err = std::abs(remap_stat_1.errors[Statistics::Errors::REMAP_L2]); + Log::info() << "1st order accuracy (new < ref) = (" << err << " < " << tol[2] << ")" << std::endl; EXPECT(err < tol[2]); - err = remap_stat_2.errors[Statistics::Errors::REMAP_L2]; - Log::info() << "2nd order accuracy improvement : " << improvement(err, tol[3]) << " %" << std::endl; + err = std::abs(remap_stat_2.errors[Statistics::Errors::REMAP_L2]); + Log::info() << "2nd order accuracy (new < ref) = (" << err << " < " << tol[3] << ")" << std::endl; EXPECT(err < tol[3]); // check mass conservation - err = remap_stat_1.errors[Statistics::Errors::REMAP_CONS]; - Log::info() << "1st order conservation improvement : " << improvement(err, tol[4]) << " %" << std::endl; + err = std::abs(remap_stat_1.errors[Statistics::Errors::REMAP_CONS]); + Log::info() << "1st order conservation (new < ref) = (" << err << " < " << tol[4] << ")" << std::endl; EXPECT(err < tol[4]); - err = remap_stat_2.errors[Statistics::Errors::REMAP_CONS]; - Log::info() << "2nd order conservation improvement : " << improvement(err, tol[5]) << " %" << std::endl - << std::endl; + err = std::abs(remap_stat_2.errors[Statistics::Errors::REMAP_CONS]); + Log::info() << "2nd order conservation (new < ref) = (" << err << " < " << tol[5] << ")" << std::endl; EXPECT(err < tol[5]); Log::info().unindent(); } CASE("test_interpolation_conservative") { -#if 1 SECTION("analytic constfunc") { auto func = [](const PointLonLat& p) { return 1.; }; Statistics remap_stat_1; Statistics remap_stat_2; - do_remapping_test(Grid("H47"), Grid("H48"), func, remap_stat_1, remap_stat_2); - check(remap_stat_1, remap_stat_2, {1.e-13, 5.e-8, 2.9e-6, 2.9e-6, 5.5e-5, 5.5e-5}); + bool src_cell_data = true; + bool tgt_cell_data = true; + do_remapping_test(Grid("O128"), Grid("H48"), func, remap_stat_1, remap_stat_2, src_cell_data, tgt_cell_data); + check(remap_stat_1, remap_stat_2, {1.0e-13, 1.0e-13, 1.0e-13, 1.0e-13, 1.0e-13, 1.0e-13}); } - SECTION("analytic Y_2^2 as in Jones(1998)") { + SECTION("vortex_rollup") { auto func = [](const PointLonLat& p) { - double cos = std::cos(0.025 * p[0]); - return 2. + cos * cos * std::cos(2 * 0.025 * p[1]); + return util::function::vortex_rollup(p[0], p[1], 0.5); }; Statistics remap_stat_1; Statistics remap_stat_2; - do_remapping_test(Grid("H47"), Grid("H48"), func, remap_stat_1, remap_stat_2); - check(remap_stat_1, remap_stat_2, {1.e-13, 5.e-8, 4.8e-4, 1.1e-4, 8.9e-5, 1.1e-4}); + + bool src_cell_data = true; + bool tgt_cell_data = true; + do_remapping_test(Grid("O32"), Grid("H24"), func, remap_stat_1, remap_stat_2, src_cell_data, tgt_cell_data); + check(remap_stat_1, remap_stat_2, {1.0e-13, 1.0e-12, 3.0e-3, 6.0e-4, 1.0e-15, 1.0e-8}); + + src_cell_data = true; + tgt_cell_data = false; + do_remapping_test(Grid("O32"), Grid("H24"), func, remap_stat_1, remap_stat_2, src_cell_data, tgt_cell_data); + check(remap_stat_1, remap_stat_2, {1.0e-13, 1.0e-12, 3.0e-3, 6.0e-4, 1.0e-15, 1.0e-8}); + + src_cell_data = false; + tgt_cell_data = true; + do_remapping_test(Grid("O32"), Grid("H24"), func, remap_stat_1, remap_stat_2, src_cell_data, tgt_cell_data); + check(remap_stat_1, remap_stat_2, {1.0e-13, 1.0e-12, 3.0e-3, 6.0e-4, 1.0e-15, 1.0e-8}); + + src_cell_data = false; + tgt_cell_data = false; + do_remapping_test(Grid("O32"), Grid("H24"), func, remap_stat_1, remap_stat_2, src_cell_data, tgt_cell_data); + check(remap_stat_1, remap_stat_2, {1.0e-12, 1.0e-12, 3.0e-3, 6.0e-4, 1.0e-15, 1.0e-8}); } -#endif } } // namespace test diff --git a/src/tests/interpolation/test_interpolation_cubedsphere.cc b/src/tests/interpolation/test_interpolation_cubedsphere.cc index 8d77fd461..b2d446ebf 100644 --- a/src/tests/interpolation/test_interpolation_cubedsphere.cc +++ b/src/tests/interpolation/test_interpolation_cubedsphere.cc @@ -10,14 +10,19 @@ #include "atlas/functionspace/CellColumns.h" #include "atlas/functionspace/CubedSphereColumns.h" #include "atlas/functionspace/NodeColumns.h" +#include "atlas/functionspace/PointCloud.h" +#include "atlas/functionspace/StructuredColumns.h" #include "atlas/grid/CubedSphereGrid.h" +#include "atlas/grid/Distribution.h" #include "atlas/grid/Grid.h" +#include "atlas/grid/Iterator.h" #include "atlas/grid/Partitioner.h" #include "atlas/interpolation/Interpolation.h" #include "atlas/mesh/Mesh.h" #include "atlas/meshgenerator/MeshGenerator.h" #include "atlas/output/Gmsh.h" #include "atlas/parallel/mpi/mpi.h" +#include "atlas/redistribution/Redistribution.h" #include "atlas/util/Constants.h" #include "atlas/util/CoordinateEnums.h" #include "atlas/util/function/VortexRollup.h" @@ -28,10 +33,35 @@ namespace atlas { namespace test { +struct CubedSphereInterpolationFixture { + atlas::Grid sourceGrid_ = Grid("CS-LFR-24"); + atlas::Mesh sourceMesh_ = MeshGenerator("cubedsphere_dual").generate(sourceGrid_); + atlas::FunctionSpace sourceFunctionSpace_ = functionspace::NodeColumns(sourceMesh_); + atlas::grid::Partitioner targetPartitioner_ = + grid::MatchingPartitioner(sourceMesh_, util::Config("type", "cubedsphere")); + atlas::Grid targetGrid_ = Grid("O24"); + atlas::Mesh targetMesh_ = MeshGenerator("structured").generate(targetGrid_, targetPartitioner_); + atlas::FunctionSpace targetFunctionSpace_ = functionspace::NodeColumns(targetMesh_); +}; + +void gmshOutput(const std::string& fileName, const FieldSet& fieldSet) { + + + const auto& functionSpace = fieldSet[0].functionspace(); + const auto& mesh = functionspace::NodeColumns(functionSpace).mesh(); + + const auto gmshConfig = + util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); + const auto gmsh = output::Gmsh(fileName, gmshConfig); + gmsh.write(mesh); + gmsh.write(fieldSet, functionSpace); +} + // Return (u, v) field with vortex_rollup as the streamfunction. // This has no physical significance, but it makes a nice swirly field. std::pair vortexField(double lon, double lat) { + // set hLon and hLat step size. const double hLon = 0.0001; const double hLat = 0.0001; @@ -66,10 +96,8 @@ double dotProd(const Field& a, const Field& b) { } CASE("cubedsphere_scalar_interpolation") { - // Create a source cubed sphere grid, mesh and functionspace. - const auto sourceGrid = Grid("CS-LFR-24"); - const auto sourceMesh = MeshGenerator("cubedsphere_dual").generate(sourceGrid); - const auto sourceFunctionspace = functionspace::NodeColumns(sourceMesh); + + const auto fixture = CubedSphereInterpolationFixture{}; //-------------------------------------------------------------------------- // Interpolation test. @@ -77,12 +105,12 @@ CASE("cubedsphere_scalar_interpolation") { // Populate analytic source field. double stDev{}; - auto sourceField = sourceFunctionspace.createField(option::name("test_field")); + auto sourceField = fixture.sourceFunctionSpace_.createField(option::name("test_field")); { - const auto lonlat = array::make_view(sourceFunctionspace.lonlat()); - const auto ghost = array::make_view(sourceFunctionspace.ghost()); + const auto lonlat = array::make_view(fixture.sourceFunctionSpace_.lonlat()); + const auto ghost = array::make_view(fixture.sourceFunctionSpace_.ghost()); auto view = array::make_view(sourceField); - for (idx_t i = 0; i < sourceFunctionspace.size(); ++i) { + for (idx_t i = 0; i < fixture.sourceFunctionSpace_.size(); ++i) { view(i) = util::function::vortex_rollup(lonlat(i, LON), lonlat(i, LAT), 1.); if (!ghost(i)) { stDev += view(i) * view(i); @@ -90,33 +118,26 @@ CASE("cubedsphere_scalar_interpolation") { } } mpi::comm().allReduceInPlace(stDev, eckit::mpi::Operation::SUM); - stDev = std::sqrt(stDev / sourceGrid.size()); - - - // Create target grid, mesh and functionspace. - const auto partitioner = grid::MatchingPartitioner(sourceMesh, util::Config("type", "cubedsphere")); - const auto targetGrid = Grid("O24"); - const auto targetMesh = MeshGenerator("structured").generate(targetGrid, partitioner); - const auto targetFunctionspace = functionspace::NodeColumns(targetMesh); + stDev = std::sqrt(stDev / fixture.sourceGrid_.size()); // Set up interpolation object. const auto scheme = util::Config("type", "cubedsphere-bilinear") | util::Config("adjoint", true); - const auto interp = Interpolation(scheme, sourceFunctionspace, targetFunctionspace); + const auto interp = Interpolation(scheme, fixture.sourceFunctionSpace_, fixture.targetFunctionSpace_); // Interpolate from source to target field. - auto targetField = targetFunctionspace.createField(option::name("test_field")); + auto targetField = fixture.targetFunctionSpace_.createField(option::name("test_field")); interp.execute(sourceField, targetField); targetField.haloExchange(); // Make some diagnostic output fields. - auto errorField = targetFunctionspace.createField(option::name("error_field")); - auto partField = targetFunctionspace.createField(option::name("partition")); + auto errorField = fixture.targetFunctionSpace_.createField(option::name("error_field")); + auto partField = fixture.targetFunctionSpace_.createField(option::name("partition")); { - const auto lonlat = array::make_view(targetFunctionspace.lonlat()); + const auto lonlat = array::make_view(fixture.targetFunctionSpace_.lonlat()); auto targetView = array::make_view(targetField); auto errorView = array::make_view(errorField); auto partView = array::make_view(partField); - for (idx_t i = 0; i < targetFunctionspace.size(); ++i) { + for (idx_t i = 0; i < fixture.targetFunctionSpace_.size(); ++i) { const auto val = util::function::vortex_rollup(lonlat(i, LON), lonlat(i, LAT), 1.); errorView(i) = std::abs((targetView(i) - val) / stDev); partView(i) = mpi::rank(); @@ -124,32 +145,24 @@ CASE("cubedsphere_scalar_interpolation") { } partField.haloExchange(); - // Output source mesh. - const auto gmshConfig = - util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); - const auto sourceGmsh = output::Gmsh("cubedsphere_source.msh", gmshConfig); - sourceGmsh.write(sourceMesh); - sourceGmsh.write(FieldSet(sourceField), sourceFunctionspace); + gmshOutput("cubedsphere_source.msh", FieldSet(sourceField)); - // Output target mesh. - const auto targetGmsh = output::Gmsh("cubedsphere_target.msh", gmshConfig); - targetGmsh.write(targetMesh); auto targetFields = FieldSet{}; targetFields.add(targetField); targetFields.add(errorField); targetFields.add(partField); - targetGmsh.write(targetFields, targetFunctionspace); + gmshOutput("cubedsphere_target.msh", targetFields); //-------------------------------------------------------------------------- // Adjoint test. //-------------------------------------------------------------------------- // Ensure that the adjoint identity relationship holds. - auto targetAdjoint = targetFunctionspace.createField(option::name("target adjoint")); + auto targetAdjoint = fixture.targetFunctionSpace_.createField(option::name("target adjoint")); array::make_view(targetAdjoint).assign(array::make_view(targetField)); targetAdjoint.adjointHaloExchange(); - auto sourceAdjoint = sourceFunctionspace.createField(option::name("source adjoint")); + auto sourceAdjoint = fixture.sourceFunctionSpace_.createField(option::name("source adjoint")); array::make_view(sourceAdjoint).assign(0.); interp.execute_adjoint(sourceAdjoint, targetAdjoint); @@ -160,13 +173,11 @@ CASE("cubedsphere_scalar_interpolation") { } CASE("cubedsphere_wind_interpolation") { - // Create a source cubed sphere grid, mesh and functionspace. - const auto sourceGrid = CubedSphereGrid("CS-LFR-48"); - const auto sourceMesh = MeshGenerator("cubedsphere_dual").generate(sourceGrid); - const auto sourceFunctionspace = functionspace::CubedSphereNodeColumns(sourceMesh); + + const auto fixture = CubedSphereInterpolationFixture{}; // Get projection. - const auto& proj = sourceGrid.cubedSphereProjection(); + const auto& proj = CubedSphereGrid(fixture.sourceGrid_).cubedSphereProjection(); // Set wind transform Jacobian. const auto windTransform = [&](const PointLonLat& lonlat, idx_t t) { @@ -189,12 +200,12 @@ CASE("cubedsphere_wind_interpolation") { // Populate analytic source field. auto sourceFieldSet = FieldSet{}; - sourceFieldSet.add(sourceFunctionspace.createField(option::name("u_orig"))); - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_orig"))); - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_alpha"))); - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_beta"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("u_orig"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_orig"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_alpha"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_beta"))); { - const auto lonlat = array::make_view(sourceFunctionspace.lonlat()); + const auto lonlat = array::make_view(fixture.sourceFunctionSpace_.lonlat()); auto u = array::make_view(sourceFieldSet["u_orig"]); auto v = array::make_view(sourceFieldSet["v_orig"]); auto vAlpha = array::make_view(sourceFieldSet["v_alpha"]); @@ -205,7 +216,8 @@ CASE("cubedsphere_wind_interpolation") { // wind transform. Then the transform is applied to the entire field, // *including* the halo. - sourceFunctionspace.parallel_for(util::Config("include_halo", true), [&](idx_t idx, idx_t t, idx_t i, idx_t j) { + functionspace::CubedSphereNodeColumns(fixture.sourceFunctionSpace_).parallel_for( + util::Config("include_halo", true), [&](idx_t idx, idx_t t, idx_t i, idx_t j) { // Get lonlat const auto ll = PointLonLat(lonlat(idx, LON), lonlat(idx, LAT)); @@ -220,26 +232,20 @@ CASE("cubedsphere_wind_interpolation") { }); } - // Create target grid, mesh and functionspace. - const auto partitioner = grid::MatchingPartitioner(sourceMesh, util::Config("type", "cubedsphere")); - const auto targetGrid = Grid("O48"); - const auto targetMesh = MeshGenerator("structured").generate(targetGrid, partitioner); - const auto targetFunctionspace = functionspace::NodeColumns(targetMesh); - // Set up interpolation object. // Note: We have to disable the source field halo exhange in the // interpolation execute and excute_adjoint methods. If left on, the halo // exchange will corrupt the transformed wind field. const auto scheme = util::Config("type", "cubedsphere-bilinear") | util::Config("adjoint", true) | util::Config("halo_exchange", false); - const auto interp = Interpolation(scheme, sourceFunctionspace, targetFunctionspace); + const auto interp = Interpolation(scheme, fixture.sourceFunctionSpace_, fixture.targetFunctionSpace_); // Make target fields. auto targetFieldSet = FieldSet{}; - targetFieldSet.add(targetFunctionspace.createField(option::name("u_orig"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_orig"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_alpha"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_beta"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("u_orig"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_orig"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_alpha"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_beta"))); // Interpolate from source to target fields. array::make_view(targetFieldSet["v_alpha"]).assign(0.); @@ -247,13 +253,13 @@ CASE("cubedsphere_wind_interpolation") { interp.execute(sourceFieldSet, targetFieldSet); // Make new (u, v) fields from (v_alpha, v_beta) - targetFieldSet.add(targetFunctionspace.createField(option::name("error_field_0"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("error_field_1"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("u_new"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_new"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("error_field_0"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("error_field_1"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("u_new"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_new"))); { - const auto lonlat = array::make_view(targetFunctionspace.lonlat()); - const auto ghost = array::make_view(targetFunctionspace.ghost()); + const auto lonlat = array::make_view(fixture.targetFunctionSpace_.lonlat()); + const auto ghost = array::make_view(fixture.targetFunctionSpace_.ghost()); auto u = array::make_view(targetFieldSet["u_new"]); auto v = array::make_view(targetFieldSet["v_new"]); const auto uOrig = array::make_view(targetFieldSet["u_orig"]); @@ -264,7 +270,7 @@ CASE("cubedsphere_wind_interpolation") { auto error1 = array::make_view(targetFieldSet["error_field_1"]); const auto& tVec = interp.target()->metadata().getIntVector("tile index"); - for (idx_t idx = 0; idx < targetFunctionspace.size(); ++idx) { + for (idx_t idx = 0; idx < fixture.targetFunctionSpace_.size(); ++idx) { if (!ghost(idx)) { const auto ll = PointLonLat(lonlat(idx, LON), lonlat(idx, LAT)); const idx_t t = tVec[idx]; @@ -285,26 +291,16 @@ CASE("cubedsphere_wind_interpolation") { } targetFieldSet.haloExchange(); - // Output source mesh. - const auto gmshConfig = - util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); - const auto sourceGmsh = output::Gmsh("cubedsphere_vec_source.msh", gmshConfig); - sourceGmsh.write(sourceMesh); - sourceGmsh.write(sourceFieldSet, sourceFunctionspace); - - // Output target mesh. - const auto targetGmsh = output::Gmsh("cubedsphere_vec_target.msh", gmshConfig); - targetGmsh.write(targetMesh); - targetGmsh.write(targetFieldSet, targetFunctionspace); - + gmshOutput("cubedsphere_vec_source.msh", sourceFieldSet); + gmshOutput("cubedsphere_vec_target.msh", targetFieldSet); //-------------------------------------------------------------------------- // Adjoint test. //-------------------------------------------------------------------------- // Ensure that the adjoint identity relationship holds. - targetFieldSet.add(targetFunctionspace.createField(option::name("u_adjoint"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_adjoint"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("u_adjoint"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_adjoint"))); array::make_view(targetFieldSet["u_adjoint"]) .assign(array::make_view(targetFieldSet["u_new"])); array::make_view(targetFieldSet["v_adjoint"]) @@ -315,11 +311,11 @@ CASE("cubedsphere_wind_interpolation") { targetFieldSet["v_adjoint"].adjointHaloExchange(); // Adjoint of inverse wind transform. - targetFieldSet.add(targetFunctionspace.createField(option::name("v_alpha_adjoint"))); - targetFieldSet.add(targetFunctionspace.createField(option::name("v_beta_adjoint"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_alpha_adjoint"))); + targetFieldSet.add(fixture.targetFunctionSpace_.createField(option::name("v_beta_adjoint"))); { - const auto lonlat = array::make_view(targetFunctionspace.lonlat()); - const auto ghost = array::make_view(targetFunctionspace.ghost()); + const auto lonlat = array::make_view(fixture.targetFunctionSpace_.lonlat()); + const auto ghost = array::make_view(fixture.targetFunctionSpace_.ghost()); const auto uAdj = array::make_view(targetFieldSet["u_adjoint"]); const auto vAdj = array::make_view(targetFieldSet["v_adjoint"]); auto vAlphaAdj = array::make_view(targetFieldSet["v_alpha_adjoint"]); @@ -327,7 +323,7 @@ CASE("cubedsphere_wind_interpolation") { const auto& tVec = interp.target()->metadata().getIntVector("tile index"); vAlphaAdj.assign(0.); vBetaAdj.assign(0.); - for (idx_t idx = 0; idx < targetFunctionspace.size(); ++idx) { + for (idx_t idx = 0; idx < fixture.targetFunctionSpace_.size(); ++idx) { if (!ghost(idx)) { const auto ll = PointLonLat(lonlat(idx, LON), lonlat(idx, LAT)); const idx_t t = tVec[idx]; @@ -342,18 +338,18 @@ CASE("cubedsphere_wind_interpolation") { } // Adjoint of interpolation. - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_alpha_adjoint"))); - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_beta_adjoint"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_alpha_adjoint"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_beta_adjoint"))); array::make_view(sourceFieldSet["v_alpha_adjoint"]).assign(0.); array::make_view(sourceFieldSet["v_beta_adjoint"]).assign(0.); interp.execute_adjoint(sourceFieldSet["v_alpha_adjoint"], targetFieldSet["v_alpha_adjoint"]); interp.execute_adjoint(sourceFieldSet["v_beta_adjoint"], targetFieldSet["v_beta_adjoint"]); // Adjoint of wind transform. - sourceFieldSet.add(sourceFunctionspace.createField(option::name("u_adjoint"))); - sourceFieldSet.add(sourceFunctionspace.createField(option::name("v_adjoint"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("u_adjoint"))); + sourceFieldSet.add(fixture.sourceFunctionSpace_.createField(option::name("v_adjoint"))); { - const auto lonlat = array::make_view(sourceFunctionspace.lonlat()); + const auto lonlat = array::make_view(fixture.sourceFunctionSpace_.lonlat()); auto uAdj = array::make_view(sourceFieldSet["u_adjoint"]); auto vAdj = array::make_view(sourceFieldSet["v_adjoint"]); uAdj.assign(0.); @@ -361,7 +357,8 @@ CASE("cubedsphere_wind_interpolation") { const auto vAlphaAdj = array::make_view(sourceFieldSet["v_alpha_adjoint"]); const auto vBetaAdj = array::make_view(sourceFieldSet["v_beta_adjoint"]); - sourceFunctionspace.parallel_for(util::Config("include_halo", true), [&](idx_t idx, idx_t t, idx_t i, idx_t j) { + functionspace::CubedSphereNodeColumns(fixture.sourceFunctionSpace_).parallel_for( + util::Config("include_halo", true), [&](idx_t idx, idx_t t, idx_t i, idx_t j) { // Get lonlat const auto ll = PointLonLat(lonlat(idx, LON), lonlat(idx, LAT)); @@ -383,6 +380,81 @@ CASE("cubedsphere_wind_interpolation") { EXPECT_APPROX_EQ(yDotY / xDotXAdj, 1., 1e-14); } +CASE("cubedsphere_node_columns_to_structured_columns") { + + const auto fixture = CubedSphereInterpolationFixture{}; + + // Can't (easily) redistribute directly from a cubedsphere functionspace to a structured columns. + // Solution is to build two intermediate PointClouds functions spaces, and copy fields in and out. + + + const auto targetStructuredColumns = functionspace::StructuredColumns(fixture.targetGrid_, grid::Partitioner("equal_regions")); + const auto& targetCubedSphereParitioner = fixture.targetPartitioner_; + const auto targetNativePartitioner = grid::Partitioner(targetStructuredColumns.distribution()); + + // This should be a PointCloud constructor. + const auto makePointCloud = [](const Grid& grid, const grid::Partitioner partitioner) { + + const auto distribution = grid::Distribution(grid, partitioner); + + auto lonLats = std::vector{}; + auto idx = gidx_t{0}; + for (const auto& lonLat : grid.lonlat()) { + if (distribution.partition(idx++) == mpi::rank()) { + lonLats.emplace_back(lonLat.data()); + } + } + return functionspace::PointCloud(lonLats); + }; + + const auto targetNativePointCloud = makePointCloud(fixture.targetGrid_, targetNativePartitioner); + const auto targetCubedSpherePointCloud = makePointCloud(fixture.targetGrid_, targetCubedSphereParitioner); + + + // Populate analytic source field. + auto sourceField = fixture.sourceFunctionSpace_.createField(option::name("test_field")); + { + const auto lonlat = array::make_view(fixture.sourceFunctionSpace_.lonlat()); + const auto ghost = array::make_view(fixture.sourceFunctionSpace_.ghost()); + auto view = array::make_view(sourceField); + for (idx_t i = 0; i < fixture.sourceFunctionSpace_.size(); ++i) { + if (!ghost(i)) { + view(i) = util::function::vortex_rollup(lonlat(i, LON), lonlat(i, LAT), 1.); + } + } + } + sourceField.haloExchange(); + + // Interpolate from source field to targetCubedSpherePointCloud field. + const auto scheme = util::Config("type", "cubedsphere-bilinear") | util::Config("adjoint", true) | + util::Config("halo_exchange", false); + const auto interp = Interpolation(scheme, fixture.sourceFunctionSpace_, targetCubedSpherePointCloud); + auto targetCubedSphereField = targetCubedSpherePointCloud.createField(option::name("test_field")); + interp.execute(sourceField, targetCubedSphereField); + + // Redistribute from targetCubedSpherePointCloud to targetNativePointCloud + const auto redist = Redistribution(targetCubedSpherePointCloud, targetNativePointCloud); + auto targetNativeField = targetNativePointCloud.createField(option::name("test_field")); + redist.execute(targetCubedSphereField, targetNativeField); + + // copy temp field to target field. + auto targetField = targetStructuredColumns.createField(option::name("test_field")); + array::make_view(targetField).assign(array::make_view(targetNativeField)); + + // Done. Tidy up and write output. + + targetField.haloExchange(); + + gmshOutput("cubedsphere_to_structured_cols_source.msh", FieldSet{sourceField}); + + // gmsh needs a target mesh... + const auto mesh = MeshGenerator("structured").generate(fixture.targetGrid_, targetNativePartitioner); + const auto gmshConfig = + util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); + const auto targetGmsh = output::Gmsh("cubedsphere_to_structured_cols_target.msh", gmshConfig); + targetGmsh.write(mesh); + targetGmsh.write(targetField, functionspace::NodeColumns(mesh)); +} } // namespace test } // namespace atlas diff --git a/src/tests/linalg/CMakeLists.txt b/src/tests/linalg/CMakeLists.txt index 61d304c2f..d060dcc8a 100644 --- a/src/tests/linalg/CMakeLists.txt +++ b/src/tests/linalg/CMakeLists.txt @@ -6,6 +6,8 @@ # granted to it by virtue of its status as an intergovernmental organisation nor # does it submit to any jurisdiction. +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) + ecbuild_add_test( TARGET atlas_test_linalg_sparse SOURCES test_linalg_sparse.cc LIBS atlas @@ -18,3 +20,4 @@ ecbuild_add_test( TARGET atlas_test_linalg_dense ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +endif() \ No newline at end of file diff --git a/src/tests/mesh/CMakeLists.txt b/src/tests/mesh/CMakeLists.txt index 7a495d050..148c966d4 100644 --- a/src/tests/mesh/CMakeLists.txt +++ b/src/tests/mesh/CMakeLists.txt @@ -104,7 +104,7 @@ ecbuild_add_test( TARGET atlas_test_healpixmeshgen ecbuild_add_test( TARGET atlas_test_cubedsphere_meshgen MPI 8 - CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 8 + CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 8 AND atlas_HAVE_ATLAS_INTERPOLATION SOURCES test_cubedsphere_meshgen.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} @@ -114,6 +114,20 @@ if( TEST atlas_test_cubedsphere_meshgen ) set_tests_properties( atlas_test_cubedsphere_meshgen PROPERTIES TIMEOUT 30 ) endif() +ecbuild_add_test( TARGET atlas_test_mesh_builder + SOURCES test_mesh_builder.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + +ecbuild_add_test( TARGET atlas_test_mesh_builder_parallel + MPI 6 + CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 6 + SOURCES test_mesh_builder_parallel.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_executable( TARGET atlas_test_mesh_reorder SOURCES test_mesh_reorder.cc LIBS atlas @@ -130,6 +144,15 @@ ecbuild_add_test( TARGET atlas_test_mesh_reorder_unstructured ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +foreach( test footprint ) + ecbuild_add_test( TARGET atlas_test_${test} + SOURCES test_${test}.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + ) +endforeach() + + atlas_add_cuda_test( TARGET atlas_test_connectivity_kernel @@ -142,4 +165,5 @@ ecbuild_add_test( TARGET atlas_test_pentagon_element SOURCES test_pentagon_element.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION atlas_HAVE_ATLAS_INTERPOLATION ) diff --git a/src/tests/mesh/helper_mesh_builder.h b/src/tests/mesh/helper_mesh_builder.h new file mode 100644 index 000000000..38366a172 --- /dev/null +++ b/src/tests/mesh/helper_mesh_builder.h @@ -0,0 +1,83 @@ +/* + * (C) Copyright 2023 UCAR. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include +#include +#include + +#include "atlas/array/MakeView.h" +#include "atlas/mesh/Elements.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/Nodes.h" + +namespace atlas { +namespace test { +namespace helper { + +void check_mesh_nodes_and_cells(const Mesh& mesh, const std::vector& lons, const std::vector& lats, + const std::vector& ghosts, const std::vector& global_indices, + const std::vector& remote_indices, const idx_t remote_index_base, + const std::vector& partitions, + const std::vector>& tri_boundary_nodes, + const std::vector& tri_global_indices, + const std::vector>& quad_boundary_nodes, + const std::vector& quad_global_indices) { + const auto mesh_xy = array::make_view(mesh.nodes().xy()); + const auto mesh_lonlat = array::make_view(mesh.nodes().lonlat()); + const auto mesh_ghost = array::make_view(mesh.nodes().ghost()); + const auto mesh_gidx = array::make_view(mesh.nodes().global_index()); + const auto mesh_ridx = array::make_indexview(mesh.nodes().remote_index()); + const auto mesh_partition = array::make_view(mesh.nodes().partition()); + const auto mesh_halo = array::make_view(mesh.nodes().halo()); + + EXPECT(mesh.nodes().size() == lons.size()); + for (size_t i = 0; i < mesh.nodes().size(); ++i) { + EXPECT(mesh_xy(i, 0) == lons[i]); + EXPECT(mesh_xy(i, 1) == lats[i]); + EXPECT(mesh_lonlat(i, 0) == lons[i]); + EXPECT(mesh_lonlat(i, 1) == lats[i]); + EXPECT(mesh_ghost(i) == ghosts[i]); + EXPECT(mesh_gidx(i) == global_indices[i]); + EXPECT(mesh_ridx(i) == remote_indices[i] - remote_index_base); + EXPECT(mesh_partition(i) == partitions[i]); + EXPECT(mesh_halo(i) == 0.); + // Don't expect (or test) any node-to-cell connectivities + } + + EXPECT(mesh.cells().nb_types() == 2); + EXPECT(mesh.cells().size() == tri_boundary_nodes.size() + quad_boundary_nodes.size()); + + const auto position_of = [&global_indices](const gidx_t idx) { + const auto& it = std::find(global_indices.begin(), global_indices.end(), idx); + ATLAS_ASSERT(it != global_indices.end()); + return std::distance(global_indices.begin(), it); + }; + + // Check triangle cell-to-node connectivities + EXPECT(mesh.cells().elements(0).size() == tri_boundary_nodes.size()); + EXPECT(mesh.cells().elements(0).nb_nodes() == 3); + for (size_t tri = 0; tri < mesh.cells().elements(0).size(); ++tri) { + for (size_t node = 0; node < mesh.cells().elements(0).nb_nodes(); ++node) { + EXPECT(mesh.cells().elements(0).node_connectivity()(tri, node) == + position_of(tri_boundary_nodes[tri][node])); + } + } + // Check quad cell-to-node connectivities + EXPECT(mesh.cells().elements(1).size() == quad_boundary_nodes.size()); + EXPECT(mesh.cells().elements(1).nb_nodes() == 4); + for (size_t quad = 0; quad < mesh.cells().elements(1).size(); ++quad) { + for (size_t node = 0; node < mesh.cells().elements(1).nb_nodes(); ++node) { + EXPECT(mesh.cells().elements(1).node_connectivity()(quad, node) == + position_of(quad_boundary_nodes[quad][node])); + } + } +} + +} // namespace helper +} // namespace test +} // namespace atlas diff --git a/src/tests/util/test_footprint.cc b/src/tests/mesh/test_footprint.cc similarity index 98% rename from src/tests/util/test_footprint.cc rename to src/tests/mesh/test_footprint.cc index dd6094392..8dbcd23ad 100644 --- a/src/tests/util/test_footprint.cc +++ b/src/tests/mesh/test_footprint.cc @@ -35,7 +35,7 @@ static std::string griduid() { //----------------------------------------------------------------------------- -CASE("test_broadcast_to_self") { +CASE("test_footprint") { array::ArrayT array(10, 2); Log::info() << "array.footprint = " << eckit::Bytes(array.footprint()) << std::endl; diff --git a/src/tests/mesh/test_mesh_builder.cc b/src/tests/mesh/test_mesh_builder.cc new file mode 100644 index 000000000..91f236000 --- /dev/null +++ b/src/tests/mesh/test_mesh_builder.cc @@ -0,0 +1,150 @@ +/* + * (C) Copyright 2023 UCAR. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include +#include +#include + +#include "atlas/array/MakeView.h" +#include "atlas/mesh/Elements.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/MeshBuilder.h" +#include "atlas/mesh/Nodes.h" + +#include "tests/AtlasTestEnvironment.h" +#include "tests/mesh/helper_mesh_builder.h" + +using namespace atlas::mesh; + +//#include "atlas/output/Gmsh.h" +//using namespace atlas::output; + +namespace atlas { +namespace test { + +//----------------------------------------------------------------------------- + +CASE("test_tiny_mesh") { + // small regional grid whose cell-centers are connected as (global nodes and cells): + // + // 1 - 5 ----- 6 + // | 3 \ 1 /2| + // 2 ----- 3 - 4 + // + std::vector lons{{0.0, 0.0, 10.0, 15.0, 5.0, 15.0}}; + std::vector lats{{5.0, 0.0, 0.0, 0.0, 5.0, 5.0}}; + + std::vector ghosts(6, 0); // all points owned + std::vector global_indices(6); + std::iota(global_indices.begin(), global_indices.end(), 1); // 1-based numbering + const idx_t remote_index_base = 0; // 0-based numbering + std::vector remote_indices(6); + std::iota(remote_indices.begin(), remote_indices.end(), remote_index_base); + std::vector partitions(6, 0); // all points on proc 0 + + // triangles + std::vector> tri_boundary_nodes = {{{3, 6, 5}}, {{3, 4, 6}}}; + std::vector tri_global_indices = {1, 2}; + + // quads + std::vector> quad_boundary_nodes = {{{1, 2, 3, 5}}}; + std::vector quad_global_indices = {3}; + + const MeshBuilder mesh_builder{}; + const Mesh mesh = mesh_builder(lons, lats, ghosts, global_indices, remote_indices, remote_index_base, partitions, + tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, quad_global_indices); + + helper::check_mesh_nodes_and_cells(mesh, lons, lats, ghosts, global_indices, remote_indices, remote_index_base, + partitions, tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, + quad_global_indices); + + //Gmsh gmsh("out.msh", util::Config("coordinates", "xyz")); + //gmsh.write(mesh); +} + +CASE("test_cs_c2_mesh_serial") { + // coordinates of C2 lfric cubed-sphere grid: grid("CS-LFR-2"); + std::vector lons = {337.5, 22.5, 337.5, 22.5, // +x + 67.5, 112.5, 67.5, 112.5, // +y + 202.5, 202.5, 157.5, 157.5, // -x + 292.5, 292.5, 247.5, 247.5, // -y + 315, 45, 225, 135, // +z + 315, 225, 45, 135}; // -z + + std::vector lats = {-20.941, -20.941, 20.941, 20.941, // +x + -20.941, -20.941, 20.941, 20.941, // +y + -20.941, 20.941, -20.941, 20.941, // -x + -20.941, 20.941, -20.941, 20.941, // -y + 59.6388, 59.6388, 59.6388, 59.6388, // +z + -59.6388, -59.6388, -59.6388, -59.6388}; // -z + + std::vector ghosts(24, 0); + std::vector global_indices(24); + std::iota(global_indices.begin(), global_indices.end(), 1); + const idx_t remote_index_base = 1; // test with 1-based numbering + std::vector remote_indices(24); + std::iota(remote_indices.begin(), remote_indices.end(), remote_index_base); + std::vector partitions(24, 0); + + // triangles + std::vector> tri_boundary_nodes = {//corners + {{17, 14, 3}}, + {{18, 4, 7}}, + {{20, 8, 12}}, + {{19, 10, 16}}, + {{21, 1, 13}}, + {{23, 5, 2}}, + {{24, 11, 6}}, + {{22, 15, 9}}}; + std::vector tri_global_indices(8); + std::iota(tri_global_indices.begin(), tri_global_indices.end(), 1); + + // quads + std::vector> quad_boundary_nodes = {// faces + {{1, 2, 4, 3}}, + {{5, 6, 8, 7}}, + {{11, 9, 10, 12}}, + {{15, 13, 14, 16}}, + {{17, 18, 20, 19}}, + {{21, 22, 24, 23}}, + // edges between faces + {{2, 5, 7, 4}}, + {{6, 11, 12, 8}}, + {{9, 15, 16, 10}}, + {{13, 1, 3, 14}}, + {{7, 8, 20, 18}}, + {{12, 10, 19, 20}}, + {{16, 14, 17, 19}}, + {{3, 4, 18, 17}}, + {{23, 24, 6, 5}}, + {{24, 22, 9, 11}}, + {{22, 21, 13, 15}}, + {{21, 23, 2, 1}}}; + std::vector quad_global_indices(18); + std::iota(quad_global_indices.begin(), quad_global_indices.end(), 9); // nb_tris + 1 + + const MeshBuilder mesh_builder{}; + const Mesh mesh = mesh_builder(lons, lats, ghosts, global_indices, remote_indices, remote_index_base, partitions, + tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, quad_global_indices); + + helper::check_mesh_nodes_and_cells(mesh, lons, lats, ghosts, global_indices, remote_indices, remote_index_base, + partitions, tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, + quad_global_indices); + + //Gmsh gmsh("out.msh", util::Config("coordinates", "xyz")); + //gmsh.write(mesh); +} + +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/mesh/test_mesh_builder_parallel.cc b/src/tests/mesh/test_mesh_builder_parallel.cc new file mode 100644 index 000000000..fd4e0c7b3 --- /dev/null +++ b/src/tests/mesh/test_mesh_builder_parallel.cc @@ -0,0 +1,173 @@ +/* + * (C) Copyright 2023 UCAR. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + */ + +#include +#include +#include +#include + +#include "atlas/array/MakeView.h" +#include "atlas/mesh/Elements.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/MeshBuilder.h" +#include "atlas/mesh/Nodes.h" +#include "atlas/parallel/mpi/mpi.h" + +#include "tests/AtlasTestEnvironment.h" +#include "tests/mesh/helper_mesh_builder.h" + +using namespace atlas::mesh; + +//#include "atlas/output/Gmsh.h" +//using namespace atlas::output; + +namespace atlas { +namespace test { + +//----------------------------------------------------------------------------- + +CASE("test_cs_c2_mesh_parallel") { + ATLAS_ASSERT(mpi::comm().size() == 6); + const int rank = mpi::comm().rank(); + + // Coordinates of the C2 LFRic cubed-sphere grid: grid("CS-LFR-2"); + const std::vector global_lons = {337.5, 22.5, 337.5, 22.5, // +x + 67.5, 112.5, 67.5, 112.5, // +y + 202.5, 202.5, 157.5, 157.5, // -x + 292.5, 292.5, 247.5, 247.5, // -y + 315, 45, 225, 135, // +z + 315, 225, 45, 135}; // -z + + const std::vector global_lats = {-20.941, -20.941, 20.941, 20.941, // +x + -20.941, -20.941, 20.941, 20.941, // +y + -20.941, 20.941, -20.941, 20.941, // -x + -20.941, 20.941, -20.941, 20.941, // -y + 59.6388, 59.6388, 59.6388, 59.6388, // +z + -59.6388, -59.6388, -59.6388, -59.6388}; // -z + + const std::vector global_partitions = {0, 0, 0, 0, // +x + 1, 1, 1, 1, // +y + 2, 2, 2, 2, // -x + 3, 3, 3, 3, // -y + 4, 4, 4, 4, // +z + 5, 5, 5, 5}; // -z + + const std::vector local_indices = {0, 1, 2, 3, // +x + 0, 1, 2, 3, // +y + 0, 1, 2, 3, // -x + 0, 1, 2, 3, // -y + 0, 1, 2, 3, // +z + 0, 1, 2, 3}; // -z + + std::vector global_indices; + std::vector> tri_boundary_nodes{}; + std::vector tri_global_indices{}; + std::vector> quad_boundary_nodes{}; + std::vector quad_global_indices{}; + + if (rank == 0) { + // global indices for points and cells are 1-based + global_indices = {1, 2, 3, 4, 5, 7, 17, 18}; + tri_boundary_nodes.push_back({{18, 4, 7}}); + tri_global_indices = {2}; + quad_boundary_nodes.push_back({{1, 2, 4, 3}}); + quad_boundary_nodes.push_back({{2, 5, 7, 4}}); + quad_boundary_nodes.push_back({{3, 4, 18, 17}}); + quad_global_indices = {9, 15, 22}; + } + else if (rank == 1) { + global_indices = {5, 6, 7, 8, 11, 12, 18, 20}; + tri_boundary_nodes.push_back({{20, 8, 12}}); + tri_global_indices = {3}; + quad_boundary_nodes.push_back({{5, 6, 8, 7}}); + quad_boundary_nodes.push_back({{6, 11, 12, 8}}); + quad_boundary_nodes.push_back({{7, 8, 20, 18}}); + quad_global_indices = {10, 16, 19}; + } + else if (rank == 2) { + global_indices = {9, 10, 11, 12, 15, 16, 22, 24}; + tri_boundary_nodes.push_back({{22, 15, 9}}); + tri_global_indices = {8}; + quad_boundary_nodes.push_back({{11, 9, 10, 12}}); + quad_boundary_nodes.push_back({{9, 15, 16, 10}}); + quad_boundary_nodes.push_back({{24, 22, 9, 11}}); + quad_global_indices = {11, 17, 24}; + } + else if (rank == 3) { + global_indices = {1, 3, 13, 14, 15, 16, 21, 22}; + tri_boundary_nodes.push_back({{21, 1, 13}}); + tri_global_indices = {5}; + quad_boundary_nodes.push_back({{15, 13, 14, 16}}); + quad_boundary_nodes.push_back({{13, 1, 3, 14}}); + quad_boundary_nodes.push_back({{22, 21, 13, 15}}); + quad_global_indices = {12, 18, 25}; + } + else if (rank == 4) { + global_indices = {3, 10, 12, 14, 16, 17, 18, 19, 20}; + tri_boundary_nodes.push_back({{17, 14, 3}}); + tri_boundary_nodes.push_back({{19, 10, 16}}); + tri_global_indices = {1, 4}; + quad_boundary_nodes.push_back({{17, 18, 20, 19}}); + quad_boundary_nodes.push_back({{12, 10, 19, 20}}); + quad_boundary_nodes.push_back({{16, 14, 17, 19}}); + quad_global_indices = {13, 20, 21}; + } + else { // rank == 5 + global_indices = {1, 2, 5, 6, 11, 21, 22, 23, 24}; + tri_boundary_nodes.push_back({{23, 5, 2}}); + tri_boundary_nodes.push_back({{24, 11, 6}}); + tri_global_indices = {6, 7}; + quad_boundary_nodes.push_back({{21, 22, 24, 23}}); + quad_boundary_nodes.push_back({{23, 24, 6, 5}}); + quad_boundary_nodes.push_back({{21, 23, 2, 1}}); + quad_global_indices = {14, 23, 26}; + } + + // Compute (local subset of) {lons,lats,ghosts,partitions} from (local subset of) global indices + std::vector lons; + std::transform(global_indices.begin(), global_indices.end(), std::back_inserter(lons), + [&global_lons](const gidx_t idx) { return global_lons[idx - 1]; }); + + std::vector lats; + std::transform(global_indices.begin(), global_indices.end(), std::back_inserter(lats), + [&global_lats](const gidx_t idx) { return global_lats[idx - 1]; }); + + std::vector ghosts; + std::transform( + global_indices.begin(), global_indices.end(), std::back_inserter(ghosts), + [&global_partitions, &rank](const gidx_t idx) { return static_cast(global_partitions[idx - 1] != rank); }); + + const idx_t remote_index_base = 0; // 0-based indexing used in local_indices above + std::vector remote_indices; + std::transform(global_indices.begin(), global_indices.end(), std::back_inserter(remote_indices), + [&local_indices](const gidx_t idx) { return static_cast(local_indices[idx - 1]); }); + + std::vector partitions; + std::transform(global_indices.begin(), global_indices.end(), std::back_inserter(partitions), + [&global_partitions](const gidx_t idx) { return global_partitions[idx - 1]; }); + + const MeshBuilder mesh_builder{}; + const Mesh mesh = mesh_builder(lons, lats, ghosts, global_indices, remote_indices, remote_index_base, partitions, + tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, quad_global_indices); + + helper::check_mesh_nodes_and_cells(mesh, lons, lats, ghosts, global_indices, remote_indices, remote_index_base, + partitions, tri_boundary_nodes, tri_global_indices, quad_boundary_nodes, + quad_global_indices); + + //Gmsh gmsh("out.msh", util::Config("coordinates", "xyz")); + //gmsh.write(mesh); +} + +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/output/test_pointcloud_io.cc b/src/tests/output/test_pointcloud_io.cc index 44f99f3ee..193729fd7 100644 --- a/src/tests/output/test_pointcloud_io.cc +++ b/src/tests/output/test_pointcloud_io.cc @@ -34,6 +34,11 @@ namespace { +using mdspan_xy = atlas::mdspan>; +mdspan_xy make_mdspan(const atlas::Field& xy) { + return mdspan_xy{xy.array().host_data(), xy.shape(0), 2 }; +} + namespace test_arrays { const size_t nb_pts = 5; @@ -121,7 +126,7 @@ CASE("read_grid_sample_file") { Mesh mesh = output::detail::PointCloudIO::read("pointcloud.txt"); Log::info() << "Mesh created" << std::endl; - Grid grid(new grid::detail::grid::Unstructured(mesh)); + Grid grid(new grid::detail::grid::Unstructured(make_mdspan(mesh.nodes().xy()))); EXPECT(grid); EXPECT(grid.size() == test_arrays::nb_pts); @@ -137,7 +142,7 @@ CASE("read_grid_sample_file_header_less_rows") { Log::info() << "Creating Mesh..." << std::endl; Mesh mesh = output::detail::PointCloudIO::read("pointcloud.txt"); Log::info() << "Creating Mesh...done" << std::endl; - Grid grid(new grid::detail::grid::Unstructured(mesh)); + Grid grid(new grid::detail::grid::Unstructured(make_mdspan(mesh.nodes().xy()))); EXPECT(grid); EXPECT(grid.size() == test_arrays::nb_pts - 2); @@ -150,7 +155,7 @@ CASE("read_grid_sample_file_header_less_columns_1") { test_write_file("pointcloud.txt", test_arrays::nb_pts, test_arrays::nb_columns - 1); Mesh mesh = output::detail::PointCloudIO::read("pointcloud.txt"); - Grid grid(new grid::detail::grid::Unstructured(mesh)); + Grid grid(new grid::detail::grid::Unstructured(make_mdspan(mesh.nodes().xy()))); EXPECT(grid); EXPECT(grid.size() == test_arrays::nb_pts); @@ -163,7 +168,7 @@ CASE("read_grid_sample_file_header_less_columns_2") { test_write_file("pointcloud.txt", test_arrays::nb_pts, test_arrays::nb_columns - test_arrays::nb_fld); Mesh mesh = output::detail::PointCloudIO::read("pointcloud.txt"); - Grid grid(new grid::detail::grid::Unstructured(mesh)); + Grid grid(new grid::detail::grid::Unstructured(make_mdspan(mesh.nodes().xy()))); EXPECT(grid); EXPECT(grid.size() == test_arrays::nb_pts); @@ -310,7 +315,7 @@ CASE("write_read_write_field") { Log::info() << "Part 2" << std::endl; Mesh mesh = output::detail::PointCloudIO::read("pointcloud.txt"); - Grid grid(new grid::detail::grid::Unstructured(mesh)); + Grid grid(new grid::detail::grid::Unstructured(make_mdspan(mesh.nodes().xy()))); EXPECT(grid); EXPECT(grid.size() == test_vectors::nb_pts); @@ -351,10 +356,10 @@ CASE("write_read_write_field") { EXPECT_NO_THROW(output::detail::PointCloudIO::write("pointcloud_Grid.txt", mesh)); Mesh mesh_from_FieldSet = output::detail::PointCloudIO::read("pointcloud_FieldSet.txt"); - Grid grid_from_FieldSet(new grid::detail::grid::Unstructured(mesh_from_FieldSet)); + Grid grid_from_FieldSet(new grid::detail::grid::Unstructured(make_mdspan(mesh_from_FieldSet.nodes().xy()))); Mesh mesh_from_Grid(output::detail::PointCloudIO::read("pointcloud_Grid.txt")); - Grid grid_from_Grid(new grid::detail::grid::Unstructured(mesh_from_Grid)); + Grid grid_from_Grid(new grid::detail::grid::Unstructured(make_mdspan(mesh_from_Grid.nodes().xy()))); EXPECT(grid_from_FieldSet); EXPECT(grid_from_Grid); diff --git a/src/tests/projection/CMakeLists.txt b/src/tests/projection/CMakeLists.txt index c2c744282..f74eaa8f4 100644 --- a/src/tests/projection/CMakeLists.txt +++ b/src/tests/projection/CMakeLists.txt @@ -9,7 +9,6 @@ foreach(test test_bounding_box test_projection_LAEA - test_projection_variable_resolution test_rotation ) ecbuild_add_test( TARGET atlas_${test} SOURCES ${test}.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) @@ -28,9 +27,13 @@ if( HAVE_FCTEST ) add_fctest( TARGET atlas_fctest_projection SOURCES fctest_projection.F90 LINKER_LANGUAGE Fortran LIBS atlas_f ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) endif() + +ecbuild_add_test( TARGET atlas_test_projection_variable_resolution SOURCES test_projection_variable_resolution.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) + ecbuild_add_test( TARGET atlas_test_cubedsphere_projection MPI 6 - CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 6 + CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 6 AND atlas_HAVE_ATLAS_FUNCTIONSPACE SOURCES test_cubedsphere_projection.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} diff --git a/src/tests/redistribution/CMakeLists.txt b/src/tests/redistribution/CMakeLists.txt index bc7e0d0e6..0a3a5a836 100644 --- a/src/tests/redistribution/CMakeLists.txt +++ b/src/tests/redistribution/CMakeLists.txt @@ -5,6 +5,8 @@ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. # +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) + ecbuild_add_test( TARGET atlas_test_redistribution_structured SOURCES test_redistribution_structured.cc MPI 8 @@ -21,3 +23,4 @@ ecbuild_add_test( TARGET atlas_test_redistribution_generic ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +endif() diff --git a/src/tests/runtime/CMakeLists.txt b/src/tests/runtime/CMakeLists.txt index 26b4dcd87..b96d4feec 100644 --- a/src/tests/runtime/CMakeLists.txt +++ b/src/tests/runtime/CMakeLists.txt @@ -13,6 +13,14 @@ ecbuild_add_test( TARGET atlas_test_trace OMP 2 ) +foreach( test test_library test_library_noargs test_library_init_nofinal test_library_noinit_final ) + ecbuild_add_test( TARGET atlas_${test} + SOURCES ${test}.cc + LIBS atlas + ENVIRONMENT ATLAS_TRACE_REPORT=1 ATLAS_DEBUG=1 + ) +endforeach() + if( HAVE_FCTEST ) add_fctest( TARGET atlas_fctest_trace diff --git a/src/tests/runtime/test_library.cc b/src/tests/runtime/test_library.cc new file mode 100644 index 000000000..f10d085eb --- /dev/null +++ b/src/tests/runtime/test_library.cc @@ -0,0 +1,19 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include +#include + +#include "atlas/library.h" + +int main(int argc, char** argv) { + atlas::initialise(argc,argv); + atlas::finalise(); +} diff --git a/src/tests/runtime/test_library_init_nofinal.cc b/src/tests/runtime/test_library_init_nofinal.cc new file mode 100644 index 000000000..5c52eeff1 --- /dev/null +++ b/src/tests/runtime/test_library_init_nofinal.cc @@ -0,0 +1,23 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include "atlas/library.h" + +int main(int argc, char** argv) { + atlas::initialise(argc,argv); + + atlas::Library::instance().registerDataPath("bogus"); + + std::cout << "atlas::Library::instance().dataPath() : " << atlas::Library::instance().dataPath() << std::endl; + + return 0; +} diff --git a/src/tests/runtime/test_library_noargs.cc b/src/tests/runtime/test_library_noargs.cc new file mode 100644 index 000000000..0e5d6c949 --- /dev/null +++ b/src/tests/runtime/test_library_noargs.cc @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include +#include + +#include "eckit/runtime/Main.h" + +#include "atlas/library.h" + +int main(int argc, char** argv) { + try { + atlas::initialise(); + } + catch (std::exception& e) { + // Attempting to access a non-existent instance of eckit::Main() + + eckit::Main::initialise(argc,argv); + atlas::initialise(); + atlas::finalise(); + return 0; // SUCCESS + } + return 1; // FAILED +} diff --git a/src/tests/runtime/test_library_noinit_final.cc b/src/tests/runtime/test_library_noinit_final.cc new file mode 100644 index 000000000..1adb81dad --- /dev/null +++ b/src/tests/runtime/test_library_noinit_final.cc @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2013 ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include +#include + +#include "eckit/runtime/Main.h" + +#include "atlas/library.h" + +int main(int argc, char** argv) { + + try { + atlas::Library::instance().registerDataPath("bogus"); + } + catch (std::exception& e) { + // Attempting to access a non-existent instance of eckit::Main() + + eckit::Main::initialise(argc,argv); + atlas::Library::instance().registerDataPath("bogus"); + + std::cout << "atlas::Library::instance().dataPath() : " << atlas::Library::instance().dataPath() << std::endl; + + atlas::finalise(); + return 0; + } + return 1; +} diff --git a/src/tests/trans/CMakeLists.txt b/src/tests/trans/CMakeLists.txt index 7d0fd0d0f..f58e8d49c 100644 --- a/src/tests/trans/CMakeLists.txt +++ b/src/tests/trans/CMakeLists.txt @@ -8,7 +8,7 @@ if( HAVE_FCTEST ) - if( atlas_HAVE_TRANS ) + if( atlas_HAVE_ECTRANS ) add_fctest( TARGET atlas_fctest_trans LINKER_LANGUAGE Fortran @@ -20,7 +20,7 @@ if( HAVE_FCTEST ) ) endif() - if( atlas_HAVE_TRANS ) + if( atlas_HAVE_ECTRANS ) add_fctest( TARGET atlas_fctest_trans_invtrans_grad LINKER_LANGUAGE Fortran SOURCES fctest_trans_invtrans_grad.F90 @@ -40,7 +40,7 @@ endif() ecbuild_add_test( TARGET atlas_test_trans MPI 4 SOURCES test_trans.cc - CONDITION atlas_HAVE_TRANS AND eckit_HAVE_MPI AND ( transi_HAVE_MPI OR ectrans_HAVE_MPI ) + CONDITION atlas_HAVE_ECTRANS AND eckit_HAVE_MPI AND ( transi_HAVE_MPI OR ectrans_HAVE_MPI ) LIBS atlas transi ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} # There seems to be a issue with vd2uv raising FE_INVALID, only on specific arch (bamboo-leap42-gnu63) @@ -49,7 +49,7 @@ ecbuild_add_test( TARGET atlas_test_trans ecbuild_add_test( TARGET atlas_test_trans_serial SOURCES test_trans.cc - CONDITION atlas_HAVE_TRANS + CONDITION atlas_HAVE_ECTRANS LIBS atlas transi ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} # There seems to be a issue with vd2uv raising FE_INVALID, only on specific arch (bamboo-leap42-gnu63) @@ -58,7 +58,7 @@ ecbuild_add_test( TARGET atlas_test_trans_serial ecbuild_add_test( TARGET atlas_test_trans_invtrans_grad SOURCES test_trans_invtrans_grad.cc - CONDITION atlas_HAVE_TRANS + CONDITION atlas_HAVE_ECTRANS LIBS atlas transi ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) @@ -66,7 +66,7 @@ ecbuild_add_test( TARGET atlas_test_trans_invtrans_grad # Note: This duplication is needed because transi is a private library of atlas, # and this tests needs access to the transi include directories. # ToDo: Fix this inside the test code so that we don't directly need to include transi headers. -if( atlas_HAVE_TRANS ) +if( atlas_HAVE_ECTRANS ) ecbuild_add_test( TARGET atlas_test_transgeneral SOURCES test_transgeneral.cc LIBS atlas transi @@ -87,5 +87,6 @@ ecbuild_add_test( TARGET atlas_test_trans_localcache SOURCES test_trans_localcache.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ATLAS_TRACE_REPORT=1 + CONDITION atlas_HAVE_ATLAS_TRANS ) diff --git a/src/tests/trans/test_transgeneral.cc b/src/tests/trans/test_transgeneral.cc index 741575ea7..c95522519 100644 --- a/src/tests/trans/test_transgeneral.cc +++ b/src/tests/trans/test_transgeneral.cc @@ -696,13 +696,13 @@ CASE("test_trans_hires") { trans::Trans trans(g, trc, option::type(trans_type)); for (int ivar_in = 2; ivar_in < 3; ivar_in++) { // vorticity, divergence, scalar for (int ivar_out = 2; ivar_out < 3; ivar_out++) { // u, v, scalar - int nb_fld = 1; - if (ivar_out == 2) { - nb_fld = nb_scalar; - } - else { - nb_fld = nb_vordiv; - } + // int nb_fld = 1; + // if (ivar_out == 2) { + // nb_fld = nb_scalar; + // } + // else { + // nb_fld = nb_vordiv; + // } for (int jfld = 0; jfld < 1; jfld++) { // multiple fields int k = 0; for (int m = 0; m <= trc; m++) { // zonal wavenumber diff --git a/src/tests/util/CMakeLists.txt b/src/tests/util/CMakeLists.txt index 6cf229e8e..0566bd3f9 100644 --- a/src/tests/util/CMakeLists.txt +++ b/src/tests/util/CMakeLists.txt @@ -46,7 +46,7 @@ if( HAVE_FCTEST ) ) endif() -foreach( test util earth flags footprint indexview polygon point ) +foreach( test util earth flags polygon point ) ecbuild_add_test( TARGET atlas_test_${test} SOURCES test_${test}.cc LIBS atlas @@ -72,6 +72,7 @@ ecbuild_add_test( TARGET atlas_test_kdtree SOURCES test_kdtree.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} + CONDITION atlas_HAVE_ATLAS_GRID ) ecbuild_add_test( TARGET atlas_test_convexsphericalpolygon diff --git a/src/tests/util/test_convexsphericalpolygon.cc b/src/tests/util/test_convexsphericalpolygon.cc index f4e158855..f7897a29e 100644 --- a/src/tests/util/test_convexsphericalpolygon.cc +++ b/src/tests/util/test_convexsphericalpolygon.cc @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "atlas/util/ConvexSphericalPolygon.h" #include "atlas/util/Geometry.h" @@ -13,6 +15,7 @@ namespace atlas { namespace test { using ConvexSphericalPolygon = util::ConvexSphericalPolygon; +const double EPS = std::numeric_limits::epsilon(); util::ConvexSphericalPolygon make_polygon(const std::initializer_list& list) { return util::ConvexSphericalPolygon{std::vector(list)}; @@ -29,17 +32,112 @@ util::ConvexSphericalPolygon make_polygon() { return util::ConvexSphericalPolygon{}; } + +template +std::string to_json(const It& begin, const It& end, int precision = 0) { + std::stringstream ss; + ss << "[\n"; + size_t size = std::distance(begin,end); + size_t c=0; + for( auto it = begin; it != end; ++it, ++c ) { + ss << " " << it->json(precision); + if( c < size-1 ) { + ss << ",\n"; + } + } + ss << "\n]"; + return ss.str(); +} + +template +std::string to_json(const ConvexSphericalPolygonContainer& polygons, int precision = 0) { + return to_json(polygons.begin(),polygons.end(),precision); +} +std::string to_json(std::initializer_list&& polygons, int precision = 0) { + return to_json(polygons.begin(),polygons.end(),precision); +} + +void check_intersection(const ConvexSphericalPolygon& plg1, const ConvexSphericalPolygon& plg2, const ConvexSphericalPolygon& iplg_sol, double pointsSameEPS = 5.e6 * EPS, std::ostream* out = nullptr) { + auto iplg = plg1.intersect(plg2, out, pointsSameEPS); + Log::info().indent(); + Log::info() << "plg1 area : " << plg1.area() << "\n"; + Log::info() << "plg2 area : " << plg2.area() << "\n"; + Log::info() << "iplg area : " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg1,plg2,iplg},20) << "\n"; + EXPECT(std::min(plg1.area(), plg2.area()) >= iplg.area()); + EXPECT(iplg.equals(iplg_sol, 1.e-8)); + Log::info().unindent(); +} + CASE("test default constructor") { ConvexSphericalPolygon p; EXPECT(bool(p) == false); } +CASE("Size of ConvexSphericalPolygon") { + // This test illustrates that ConvexSphericalPolygon is allocated on the stack completely, + // as sizeof(ConvexSphericalPolygon) includes space for MAX_SIZE coordinates of type PointXYZ + EXPECT(sizeof(PointXYZ) == sizeof(double) * 3); + size_t expected_size = 0; + expected_size += (1 + ConvexSphericalPolygon::MAX_SIZE) * sizeof(PointXYZ); + expected_size += sizeof(size_t); + expected_size += sizeof(bool); + expected_size += 2 * sizeof(double); + EXPECT(sizeof(ConvexSphericalPolygon) >= expected_size); // greater because compiler may add some padding +} + +CASE("analyse intersect") { + const double du = 0.5; + const double dv = 1.1 * EPS; + const double duc = 0.5 * du; + const double sduc = std::sqrt(1. - 0.25 * du * du); + const double dvc = 1. - 0.5 * dv * dv; + const double sdvc = dv * std::sqrt(1. - 0.25 * dv * dv); + PointXYZ s0p0{sduc, -duc, 0.}; + PointXYZ s0p1{sduc, duc, 0.}; + PointXYZ s1p0{dvc * sduc, -dvc * duc, -sdvc}; + PointXYZ s1p1{dvc * sduc, dvc * duc, sdvc}; + + EXPECT_APPROX_EQ(dv, PointXYZ::norm(s0p0 - s1p0), EPS); + EXPECT_APPROX_EQ(du, PointXYZ::norm(s0p0 - s0p1), EPS); + EXPECT_APPROX_EQ(dv, PointXYZ::norm(s0p1 - s1p1), EPS); + + ConvexSphericalPolygon::GreatCircleSegment s1(s0p0, s0p1); + ConvexSphericalPolygon::GreatCircleSegment s2(s1p0, s1p1); + + // analytical solution + PointXYZ Isol{1., 0., 0.}; + + // test "intersection" + PointXYZ I12 = s1.intersect(s2); + PointXYZ I21 = s2.intersect(s1); + EXPECT_APPROX_EQ(std::abs(PointXYZ::norm(I12) - 1.), 0., EPS); + EXPECT_APPROX_EQ(PointXYZ::norm(I12 - Isol), 0., EPS); + EXPECT_APPROX_EQ(std::abs(PointXYZ::norm(I21) - 1.), 0., EPS); + EXPECT_APPROX_EQ(PointXYZ::norm(I21 - Isol), 0., EPS); +} + +CASE("test_json_format") { + auto plg = make_polygon({{0., 45.}, {0., 0.}, {90., 0.}, {90., 45.}}); + EXPECT_EQ(plg.json(), "[[0,45],[0,0],[90,0],[90,45]]"); + + std::vector plg_g = { + make_polygon({{0, 60}, {0, 50}, {40, 60}}), //0 + make_polygon({{0, 60}, {0, 50}, {20, 60}}), + make_polygon({{10, 60}, {10, 50}, {30, 60}}), //3 + }; + Log::info() << to_json(plg_g) << std::endl; + +} + CASE("test_spherical_polygon_area") { auto plg1 = make_polygon({{0., 90.}, {0., 0.}, {90., 0.}}); EXPECT_APPROX_EQ(plg1.area(), M_PI_2); auto plg2 = make_polygon({{0., 45.}, {0., 0.}, {90., 0.}, {90., 45.}}); auto plg3 = make_polygon({{0., 90.}, {0., 45.}, {90., 45.}}); - Log::info() << "area diff: " << plg1.area() - plg2.area() - plg3.area() << std::endl; + Log::info().indent(); + EXPECT(plg1.area() - plg2.area() - plg3.area() <= EPS); + Log::info().unindent(); EXPECT_APPROX_EQ(std::abs(plg1.area() - plg2.area() - plg3.area()), 0, 1e-15); } @@ -50,23 +148,23 @@ CASE("test_spherical_polygon_intersection") { std::array plg_f = {make_polygon({{0, 70}, {0, 60}, {40, 60}, {40, 70}}), make_polygon({{0, 90}, {0, 0}, {40, 0}})}; std::array plg_g = { - make_polygon({{0, 60}, {0, 50}, {40, 60}}), //0 - make_polygon({{0, 60}, {0, 50}, {20, 60}}), - make_polygon({{10, 60}, {10, 50}, {30, 60}}), //3 - make_polygon({{40, 80}, {0, 60}, {40, 60}}), - make_polygon({{0, 80}, {0, 60}, {40, 60}}), //5 - make_polygon({{20, 80}, {0, 60}, {40, 60}}), - make_polygon({{20, 70}, {0, 50}, {40, 50}}), //7 - make_polygon({{0, 90}, {0, 60}, {40, 60}}), - make_polygon({{-10, 80}, {-10, 50}, {50, 80}}), //9 - make_polygon({{0, 80}, {0, 50}, {40, 50}, {40, 80}}), - make_polygon({{0, 65}, {20, 55}, {40, 60}, {20, 65}}), //11 - make_polygon({{20, 65}, {0, 60}, {20, 55}, {40, 60}}), - make_polygon({{10, 63}, {20, 55}, {30, 63}, {20, 65}}), //13 - make_polygon({{20, 75}, {0, 70}, {5, 5}, {10, 0}, {20, 0}, {40, 70}}), - make_polygon({{0, 50}, {0, 40}, {5, 45}}), //15 - make_polygon({{0, 90}, {0, 80}, {20, 0}, {40, 80}}), - make_polygon({{0, 65}, {0, 55}, {40, 65}, {40, 75}}), //17 + make_polygon({{0, 60}, {0, 50}, {40, 60}}), // 0 + make_polygon({{0, 60}, {0, 50}, {20, 60}}), // 1 + make_polygon({{10, 60}, {10, 50}, {30, 60}}), // 2 + make_polygon({{40, 80}, {0, 60}, {40, 60}}), // 3 + make_polygon({{0, 80}, {0, 60}, {40, 60}}), // 4 + make_polygon({{20, 80}, {0, 60}, {40, 60}}), // 5 + make_polygon({{20, 70}, {0, 50}, {40, 50}}), // 6 + make_polygon({{0, 90}, {0, 60}, {40, 60}}), // 7 + make_polygon({{-10, 80}, {-10, 50}, {50, 80}}), // 8 + make_polygon({{0, 80}, {0, 50}, {40, 50}, {40, 80}}), // 9 + make_polygon({{0, 65}, {20, 55}, {40, 60}, {20, 65}}), // 10 + make_polygon({{20, 65}, {0, 60}, {20, 55}, {40, 60}}), // 11 + make_polygon({{10, 63}, {20, 55}, {30, 63}, {20, 65}}), // 12 + make_polygon({{20, 75}, {0, 70}, {5, 5}, {10, 0}, {20, 0}, {40, 70}}), // 13 + make_polygon({{0, 50}, {0, 40}, {5, 45}}), // 14 + make_polygon({{0, 90}, {0, 80}, {20, 0}, {40, 80}}), // 15 + make_polygon({{0, 65}, {0, 55}, {40, 65}, {40, 75}}), // 16 }; std::array plg_i = { make_polygon(), //0 @@ -103,154 +201,208 @@ CASE("test_spherical_polygon_intersection") { make_polygon({{0, 50}, {0, 40}, {5, 45}}), make_polygon({{0, 90}, {0, 80}, {20, 0}, {40, 80}}), //32 make_polygon({{0, 65}, {0, 55}, {40, 65}, {40, 75}})}; + Log::info().indent(); for (int i = 0; i < nplg_f; i++) { for (int j = 0; j < nplg_g; j++) { - Log::info() << "\n(" << i * nplg_g + j << ") Intersecting polygon\n " << plg_f[i] << std::endl; - Log::info() << "with polygon\n " << plg_g[j] << std::endl; - auto plg_fg = plg_f[i].intersect(plg_g[j]); - auto plg_gf = plg_g[j].intersect(plg_f[i]); - Log::info() << "got polygon\n "; - if (plg_fg) { - Log::info() << plg_fg << std::endl; - Log::info() << " " << plg_gf << std::endl; - EXPECT(plg_fg.equals(plg_gf)); - EXPECT(plg_fg.equals(plg_i[i * nplg_g + j], 0.1)); - Log::info() << "instead of polygon\n "; - Log::info() << plg_i[i * nplg_g + j] << std::endl; - } - else { - Log::info() << " empty" << std::endl; - Log::info() << "instead of polygon\n "; - Log::info() << plg_i[i * nplg_g + j] << std::endl; + auto plg_fg = plg_f[i].intersect(plg_g[j], nullptr, 5.e6 * std::numeric_limits::epsilon()); + bool polygons_equal = plg_i[i * nplg_g + j].equals(plg_fg, 0.1); + EXPECT(polygons_equal); + if( not polygons_equal or (i==0 && j==10)) { + Log::info() << "\nIntersected the polygon plg_f["<= expected_size); // greater because compiler may add some padding -} +CASE("test_spherical_polygon_intersection_stretched") { + // plg1 is an octant + const auto plg1 = make_polygon({{0, 90}, {0, 0}, {90, 0}}); + Log::info().precision(20); -CASE("analyse intersect") { - const double EPS = std::numeric_limits::epsilon(); - const double du = 0.5; - const double dv = 1.1 * EPS; - const double duc = 0.5 * du; - const double sduc = std::sqrt(1. - 0.25 * du * du); - const double dvc = 1. - 0.5 * dv * dv; - const double sdvc = dv * std::sqrt(1. - 0.25 * dv * dv); - PointXYZ s0p0{sduc, -duc, 0.}; - PointXYZ s0p1{sduc, duc, 0.}; - PointXYZ s1p0{dvc * sduc, -dvc * duc, -sdvc}; - PointXYZ s1p1{dvc * sduc, dvc * duc, sdvc}; + const double delta = 1.1 * EPS; + const double u = 1. - delta; + const double v = std::sqrt(1 - u * u); + const double w = delta; + const double a = sqrt(1 - w * w); - EXPECT_APPROX_EQ(dv, PointXYZ::norm(s0p0 - s1p0), EPS); - EXPECT_APPROX_EQ(du, PointXYZ::norm(s0p0 - s0p1), EPS); - EXPECT_APPROX_EQ(dv, PointXYZ::norm(s0p1 - s1p1), EPS); + // plg2 is a stretched polygon + std::vector plg2_points = { + PointXYZ{u, v, 0.}, + PointXYZ{a, 0., w }, + PointXYZ{u, -v, 0.}, + PointXYZ{0., 0., -1.}}; + auto plg2 = util::ConvexSphericalPolygon(plg2_points.data(), plg2_points.size()); + EXPECT(plg2.size() == 4); - ConvexSphericalPolygon::GreatCircleSegment s1(s0p0, s0p1); - ConvexSphericalPolygon::GreatCircleSegment s2(s1p0, s1p1); + auto plg11 = plg1.intersect(plg1); + auto plg12 = plg1.intersect(plg2); + auto plg22 = plg2.intersect(plg2); - // analytical solution - PointXYZ Isol{1., 0., 0.}; + EXPECT(plg11.size() == 3); + EXPECT(plg12.size() == 3); + EXPECT(plg22.size() == 4); - // test "intersection" - PointXYZ I = s1.intersect(s2); - EXPECT_APPROX_EQ(std::abs(PointXYZ::norm(I) - 1.), 0., EPS); - EXPECT_APPROX_EQ(PointXYZ::norm(I - Isol), 0., EPS); + // plg3 is the analytical solution + std::vector plg3_points = { + PointXYZ{u, v, 0.}, + PointXYZ{a, 0, w}, + PointXYZ{1, 0, 0.}}; + auto plg3 = util::ConvexSphericalPolygon(plg3_points.data(), plg3_points.size()); + EXPECT(plg3.size() == 3); + EXPECT(plg12.size() == 3); + auto plg_sol = make_polygon({{1.2074e-06, 0.}, {0., 1.3994e-14}, {0., 0.}}); + EXPECT(plg12.equals(plg_sol, 1e-10)); - // test "contains" - EXPECT(s1.contains(Isol) && s2.contains(Isol)); - EXPECT(s1.contains(I) && s2.contains(I)); + Log::info().indent(); + Log::info() << "delta : " << delta << std::endl; + Log::info() << "plg12.area : " << plg12.area() << std::endl; + Log::info() << "exact intersection area : " << plg3.area() << std::endl; + double error_area = plg12.area() - plg3.area(); + EXPECT(error_area < EPS and error_area >= 0.); + Log::info().unindent(); + Log::info().precision(-1); } -CASE("source_covered") { - const double dd = 0.; - const auto csp0 = make_polygon({{0, 90}, {0, 0}, {90, 0}}); - double dcov1 = csp0.area(); // optimal coverage - double dcov2 = dcov1; // intersection-based coverage - double dcov3 = dcov1; // normalized intersection-based coverage - double darea = 0; // commutative area error in intersection: |area(A^B)-area(B^A)| - - double max_tarea = 0.; - double min_tarea = 0.; - double accumulated_tarea = 0.; - - const int n = 900; - const int m = 900; - const double dlat = 90. / n; +CASE("test_lonlat_pole_problem") { + const auto north_octant = make_polygon({{0, 90}, {0, 0}, {90, 0}}); + const double first_lat = 90. - 1.e+12 * EPS; + const int m = 10000; const double dlon = 90. / m; + std::vector csp(m); + Log::info().indent(); + + ATLAS_TRACE_SCOPE("create polygons") + for (int j = 0; j < m; j++) { + csp[j] = + make_polygon(PointLonLat{0, 90}, PointLonLat{dlon * j, first_lat}, PointLonLat{dlon * (j + 1), first_lat}); + } + + double max_area_overshoot = 0.; + int false_zero = 0; + for (int j = 0; j < m; j++) { + auto csp_i = csp[j].intersect(north_octant); + // intersection area should not be larger than its father polygon's + max_area_overshoot = std::max(max_area_overshoot, csp_i.area() - csp[j].area()); + if (csp_i.area() < EPS) { + false_zero++; + } + } + Log::info() << "False zero area intersection: " << false_zero << std::endl; + Log::info() << "Max area overshoot: " << max_area_overshoot << std::endl; + EXPECT(max_area_overshoot <= m * EPS); + EXPECT(false_zero == 0); + Log::info().unindent(); +} + +CASE("test_thin_elements_area") { + const auto north_octant = make_polygon({{0, 90}, {0, 0}, {90, 0}}); + const auto south_octant = make_polygon({{0,0}, {0, -90},{90, 0}}); + const int n = 3; + const int m = 2500; + const double dlat = 180. / n; + const double dlon = 90. / m; + + ATLAS_ASSERT(n > 1); std::vector csp(n * m); - std::vector tgt_area(n * m); - ATLAS_TRACE_SCOPE("1") + + ATLAS_TRACE_SCOPE("create polygons") for (int j = 0; j < m; j++) { csp[j] = make_polygon(PointLonLat{0, 90}, PointLonLat{dlon * j, 90 - dlat}, PointLonLat{dlon * (j + 1), 90 - dlat}); } - ATLAS_TRACE_SCOPE("2") - for (int i = 1; i < n; i++) { + for (int i = 1; i < n - 1; i++) { for (int j = 0; j < m; j++) { csp[i * m + j] = make_polygon( PointLonLat{dlon * j, 90 - dlat * i}, PointLonLat{dlon * j, 90 - dlat * (i + 1)}, PointLonLat{dlon * (j + 1), 90 - dlat * (i + 1)}, PointLonLat{dlon * (j + 1), 90 - dlat * i}); } } - ConvexSphericalPolygon cspi0; - ConvexSphericalPolygon csp0i; + for (int j = 0; j < m; j++) { + csp[(n - 1) * m + j] = + make_polygon(PointLonLat{dlon * j, 90 - dlat * (n-1)}, PointLonLat{dlon * j, -90.}, + PointLonLat{dlon * (j + 1), 90 - dlat * (n-1)}); + } + + double coverage_north = 0.; // north octant coverage by intersections with "csp"s + double coverage_south = 0.; // south octant coverage by intersections with "csp"s + double coverage_norm = 0.; // area sum of intersections in the north octant normalised to sum up to the area of the north octant + double coverage_csp = 0.; // area sum of all "csp"s + double accumulated_tarea = 0.; + std::vector i_north_area(n * m); - ATLAS_TRACE_SCOPE("3") + ATLAS_TRACE_SCOPE("intersect polygons") for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { auto ipoly = i * m + j; - cspi0 = csp[ipoly].intersect(csp0); // intersect small csp[...] with large polygon csp0 - // should be approx csp[...] as well - csp0i = csp0.intersect(csp[ipoly]); // opposite: intersect csp0 with small csp[...] - // should be approx csp[...] as well - double a_csp = csp[ipoly].area(); - double a_cspi0 = cspi0.area(); // should be approx csp[...].area() - double a_csp0i = csp0i.area(); // should approx match a_cspi0 - EXPECT_APPROX_EQ(a_cspi0, a_csp, 1.e-10); - EXPECT_APPROX_EQ(a_csp0i, a_csp, 1.e-10); - darea = std::max(darea, a_cspi0 - a_csp0i); // should remain approx zero - dcov1 -= csp[ipoly].area(); - dcov2 -= a_cspi0; - tgt_area[ipoly] = a_cspi0; - accumulated_tarea += tgt_area[ipoly]; + double a_north_csp_i = csp[ipoly].intersect(north_octant).area(); // intersect narrow csp with the north octant + double a_south_csp_i = csp[ipoly].intersect(south_octant).area(); // intersect narrow csp with the south octant + if (i == 0) { + if (n == 2) { + EXPECT_APPROX_EQ(a_north_csp_i, csp[ipoly].area(), EPS); + } + else { + if (a_north_csp_i > csp[ipoly].area()) { + Log::info() << " error: " << a_north_csp_i - csp[ipoly].area() << std::endl; + } + } + } + coverage_north += a_north_csp_i; + coverage_csp += csp[ipoly].area(); + coverage_south += a_south_csp_i; + i_north_area[ipoly] = a_north_csp_i; } } - // normalize weights - double norm_fac = csp0.area() / accumulated_tarea; - ATLAS_TRACE_SCOPE("4") + // normalise weights of the intersection polygons to sum up to the area of the north octant + double norm_fac = north_octant.area() / coverage_north; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { - tgt_area[i * m + j] *= norm_fac; - dcov3 -= tgt_area[i * m + j]; + coverage_norm += i_north_area[i * m + j] * norm_fac; } } + EXPECT(north_octant.area() - M_PI_2 < EPS); // spherical area error for the north octant + EXPECT(south_octant.area() - M_PI_2 < EPS); // spherical area error for the south octant + EXPECT(north_octant.area() - coverage_north < m * EPS); // error polygon intersec. north + EXPECT(south_octant.area() - coverage_south < m * EPS); // error polygon intersec. north + EXPECT(north_octant.area() - coverage_norm < m * EPS); // error polygon intersec. north + auto err = std::max(north_octant.area() - coverage_north, south_octant.area() - coverage_south); + auto err_normed = north_octant.area() - coverage_norm; + Log::info() << "Octant coverting error : " << err << std::endl; + Log::info() << "Octant coverting error normed : " << err_normed << std::endl; +} - Log::info() << " dlat, dlon : " << dlat << ", " << dlon << "\n"; - Log::info() << " max (commutative_area) : " << darea << "\n"; - Log::info() << " dcov1 : " << dcov1 << "\n"; - Log::info() << " dcov2 : " << dcov2 << "\n"; - Log::info() << " dcov3 : " << dcov3 << "\n"; - Log::info() << " accumulated small polygon area : " << accumulated_tarea << "\n"; - Log::info() << " large polygon area : " << csp0.area() << "\n"; - EXPECT_APPROX_EQ(darea, 0., 1.e-10); - EXPECT_APPROX_EQ(accumulated_tarea, csp0.area(), 1.e-8); +CASE("intesection of epsilon-distorted polygons") { + const double eps = 1e-4; // degrees + const auto plg0 = make_polygon({{42.45283019, 50.48004426}, + {43.33333333, 49.70239033}, + {44.1509434, 50.48004426}, + {43.26923077, 51.2558069}}); + const auto plg1 = make_polygon({{42.45283019, 50.48004426 - eps}, + {43.33333333 + eps, 49.70239033}, + {44.1509434, 50.48004426 + eps}, + {43.26923077 - eps, 51.2558069}}); + const auto iplg_sol = make_polygon({{44.15088878324276, 50.48009332686897}, + {43.68455392823953, 50.89443301919586}, + {43.26918271448949, 51.25576215711414}, + {42.86876285000331, 50.87921047438197}, + {42.45288468219661, 50.47999711267543}, + {42.92307395320301, 50.06869923211562}, + {43.33338148668725, 49.70243705225555}, + {43.72937844034824, 50.08295071539503}}); + check_intersection(plg0, plg1, iplg_sol); } -CASE("edge cases") { +CASE("Edge cases in polygon intersections") { Log::info().precision(20); - SECTION("CS-LFR-256 -> H1280 problem polygon intersection") { + + SECTION("CS-LFR-256 -> H1280") { const auto plg0 = make_polygon({{-23.55468749999994, -41.11286269132660}, {-23.20312500000000, -41.18816845938357}, {-23.20312500000000, -40.83947225425061}, @@ -260,14 +412,15 @@ CASE("edge cases") { {-23.23828125000000, -40.81704944888558}, {-23.27343750000000, -40.77762996221442}}); auto iplg = plg0.intersect(plg1); - auto jplg = plg1.intersect(plg0); - EXPECT_APPROX_EQ(iplg.area(), jplg.area(), 2e-12); // can not take 1e-15 - EXPECT_EQ(iplg.size(), 3); - EXPECT_EQ(jplg.size(), 3); - EXPECT(iplg.equals(jplg, 5.e-7)); - Log::info() << "Intersection area difference: " << std::abs(iplg.area() - jplg.area()) << "\n"; + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},16) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); } - SECTION("CS-LFR-16 -> O32 problem polygon intersection") { + SECTION("CS-LFR-16 -> O32") { const auto plg0 = make_polygon({{174.3750000000001, -16.79832945594544}, {174.3750000000001, -11.19720014633353}, {168.7500000000000, -11.03919441545243}, @@ -276,25 +429,29 @@ CASE("edge cases") { {174.1935483870968, -15.34836475949100}, {177.0967741935484, -15.34836475949100}}); auto iplg = plg0.intersect(plg1); - auto jplg = plg1.intersect(plg0); - EXPECT_APPROX_EQ(iplg.area(), jplg.area(), 1e-13); // can not take 1e-15 - EXPECT_EQ(iplg.size(), 3); - EXPECT_EQ(jplg.size(), 3); - EXPECT(iplg.equals(jplg, 1.e-11)); - Log::info() << "Intersection area difference: " << std::abs(iplg.area() - jplg.area()) << "\n"; + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},16) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); } - SECTION("CS-LFR-2 -> O48 problem polygon intersection") { + SECTION("CS-LFR-2 -> O48") { const auto plg0 = make_polygon({{180, -45}, {180, 0}, {135, 0}, {135, -35.26438968}}); const auto plg1 = make_polygon({{180, -23.31573073}, {180, -25.18098558}, {-177.75, -23.31573073}}); auto iplg = plg0.intersect(plg1); - auto jplg = plg1.intersect(plg0); - EXPECT_APPROX_EQ(iplg.area(), jplg.area(), 1e-15); EXPECT_EQ(iplg.size(), 0); - EXPECT_EQ(jplg.size(), 0); - EXPECT(iplg.equals(jplg, 1.e-12)); - Log::info() << "Intersection area difference: " << std::abs(iplg.area() - jplg.area()) << "\n"; + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},10) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + EXPECT_APPROX_EQ(iplg.area(), 0.); + Log::info().unindent(); } - SECTION("H128->H256 problem polygon intersection") { + SECTION("H128->H256") { const auto plg0 = make_polygon({{42.45283019, 50.48004426}, {43.33333333, 49.70239033}, {44.15094340, 50.48004426}, @@ -304,12 +461,13 @@ CASE("edge cases") { {44.15094340, 50.48004426}, {43.71428571, 50.86815893}}); auto iplg = plg0.intersect(plg1); - auto jplg = plg1.intersect(plg0); - EXPECT_APPROX_EQ(iplg.area(), jplg.area(), 1e-15); - EXPECT_EQ(iplg.size(), 4); - EXPECT_EQ(jplg.size(), 4); - EXPECT(iplg.equals(jplg, 1.e-12)); - Log::info() << "Intersection area difference: " << std::abs(iplg.area() - jplg.area()) << "\n"; + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},10) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); } SECTION("intesection of epsilon-distorted polygons") { @@ -323,13 +481,104 @@ CASE("edge cases") { {44.1509434, 50.48004426 + eps}, {43.26923077 - eps, 51.2558069}}); auto iplg = plg0.intersect(plg1); - auto jplg = plg1.intersect(plg0); - EXPECT_APPROX_EQ(iplg.area(), jplg.area(), 1e-10); EXPECT_EQ(iplg.size(), 8); - EXPECT_EQ(jplg.size(), 8); - EXPECT(iplg.equals(jplg, 1.e-8)); - Log::info() << "Intersection area difference: " << std::abs(iplg.area() - jplg.area()) << "\n"; + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},10) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); + } + SECTION("one") { // TODO: remove, do not know what example + auto plg0 = make_polygon({{0,90},{67.463999999999998636,89.999899999085130275},{67.5,89.999899999085130275}}); + auto plg1 = make_polygon({{72,85.760587120443801723},{90,85.760587120443801723},{-90,85.760587120443801723}}); + auto iplg = plg0.intersect(plg1); + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg},20) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); } + + SECTION("debug") { // TODO: remove, do not know what example + auto plg0 = make_polygon({{168.599999999999994,-6.88524379355500038},{168.561872909699019,-7.16627415158899961},{168.862876254180634,-7.16627415158899961}}); + auto plg1 = make_polygon({{168.84,-6.84},{168.84,-7.2},{169.2,-7.2},{169.2,-6.84}}); + auto plg2 = make_polygon({{168.48,-6.84},{168.48,-7.2},{168.84,-7.2},{168.84,-6.84}}); + auto iplg = plg0.intersect(plg1); + auto iplg2 = plg0.intersect(plg2); + Log::info().indent(); + Log::info() << "source area: " << plg0.area() << "\n"; + Log::info() << "target area: " << plg1.area() << "\n"; + Log::info() << "inters area: " << iplg.area() << "\n"; + Log::info() << "json: \n" << to_json({plg0,plg1,iplg2,iplg,iplg2},20) << "\n"; + EXPECT(std::min(plg0.area(), plg1.area()) >= iplg.area()); + Log::info().unindent(); + } + + + SECTION("debug2") { // TODO: remove, do not know what example + auto plg0 = make_polygon({{168.599999999999994, -6.88524379355500038}, + {168.561872909699019, -7.16627415158899961}, + {168.862876254180634, -7.16627415158899961}}); + auto plg1 = make_polygon({{168.48, -6.84}, + {168.48, -7.20}, + {168.84, -7.20}, + {168.84, -6.84}}); + const auto iplg_sol = make_polygon({{168.600000000000, -6.885243793555000}, + {168.561872909699, -7.166274151589000}, + {168.840000000000, -7.166281023991750}, + {168.840000000000, -7.141837487873564}}); + check_intersection(plg0, plg1, iplg_sol); + } + + SECTION("O320c_O320n_tcell-2524029") { + auto plg0 = make_polygon({{ 16.497973615369710, 89.85157892074884}, + { 0.000000000000000, 89.87355342974176}, + {-54.000000000000010, 89.78487690721863}, + { -9.000000000000002, 89.84788464490568}}); + auto plg1 = make_polygon({{ 36.000000000000000, 89.78487690721863}, + {-54.000000000000010, 89.78487690721863}, + {-36.000000000000000, 89.78487690721863}}); + const auto iplg_sol = make_polygon(); + check_intersection(plg0, plg1, iplg_sol); + } + + SECTION("O128c_F128c_tcell-77536") { + auto plg0 = make_polygon({{157.5,-16.49119584364},{157.5,-17.192948774143},{158.203125,-17.192948774143},{158.203125,-16.49119584364}}); + auto plg1 = make_polygon({{157.7064220183486,-16.49119584364},{157.5,-17.192948774143},{158.3333333333333,-17.192948774143}}); + const auto iplg_sol = make_polygon({{157.7066386314724, -16.49143953797217}, + {157.7063506671565, -16.49143933951490}, + {157.5000000000000, -17.19294877414300}, + {158.2031250000000, -17.19294877414292}, + {158.2031250000000, -17.04778221576889}}); + check_intersection(plg0, plg1, iplg_sol); + } + + SECTION("O128c_F128c_tcell") { + auto plg0 = make_polygon({{135,-10.505756145244},{135,-11.906523334954},{136.40625,-11.906523334954},{136.40625,-10.505756145244}}); + auto plg1 = make_polygon({{135.7377049180328,-10.505756145244},{135,-11.906523334954},{136.5,-11.906523334954}}); + const auto iplg_sol = make_polygon({{135.7381225488238, -10.50652773728132}, + {135.7373006953013, -10.50652782622945}, + {135.0000000000000, -11.90652333495400}, + {136.4062500000000, -11.90652333495396}, + {136.4062500000000, -11.73509894855489}}); + check_intersection(plg0, plg1, iplg_sol); + } + + SECTION("O128c_F128c_tcell-2") { + auto plg0 = make_polygon({{134.296875,-10.877172064989},{134.296875,-11.578925065131},{135,-11.578925065131},{135,-10.877172064989}}); + auto plg1 = make_polygon({{134.6153846153846,-10.877172064989},{134.2241379310345,-11.578925065131},{135,-11.578925065131}}); + const auto iplg_sol = make_polygon({{134.6154929040914, -10.87737018871372}, + {134.6152744739456, -10.87737016536156}, + {134.2968750000000, -11.44875997313061}, + {134.2968749999999, -11.57892506513095}, + {135.0000000000000, -11.57892506513100}}); + check_intersection(plg0, plg1, iplg_sol); + } + } //----------------------------------------------------------------------------- diff --git a/tools/generate-authors.py b/tools/generate-authors.py index 1384bca97..1ee9ecaf4 100755 --- a/tools/generate-authors.py +++ b/tools/generate-authors.py @@ -21,6 +21,10 @@ def real_name(name): "benjaminmenetrier" : "Benjamin Menetrier", "danholdaway" : "Daniel Holdaway", "MarekWlasak" : "Marek Wlasak", + "odlomax" : "Oliver Lomax", + "MO-marcomilan" : "Marco Milan", + "twsearle" : "Toby Searle", + "Dusan Figala" : "Dušan Figala", } if name in alias: return alias[name]