From a46c934706c5eb07d862b6fa8da6361dd3d2311d Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Tue, 4 Apr 2023 17:17:23 +0200 Subject: [PATCH 01/78] Fix package modules input syntax --- .github/workflows/reusable-ci-hpc.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 From 6530e54aeb79ca6f92321c11f50fcca22c5221d8 Mon Sep 17 00:00:00 2001 From: Marek Wlasak Date: Thu, 20 Apr 2023 18:01:50 +0100 Subject: [PATCH 02/78] adding FieldSet adjointHaloExchange to main class. --- src/atlas/field/FieldSet.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/atlas/field/FieldSet.h b/src/atlas/field/FieldSet.h index 46f804a1c..990dbf823 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -210,6 +210,8 @@ class FieldSet : DOXYGEN_HIDE(public util::ObjectHandle) { const_iterator cend() const { return get()->end(); } 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 From 32a177d0542126350bd70f625a7920c6de2ffd35 Mon Sep 17 00:00:00 2001 From: Lorenzo Milazzo <82884956+mo-lormi@users.noreply.github.com> Date: Fri, 21 Apr 2023 19:31:45 +0100 Subject: [PATCH 03/78] add procedure to include metadata in FieldSet (#126) * added procedure to include metadata in FieldSet --- src/atlas/field/FieldSet.cc | 8 ++++++++ src/atlas/field/FieldSet.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/atlas/field/FieldSet.cc b/src/atlas/field/FieldSet.cc index 49a6ba3be..bf8eda526 100644 --- a/src/atlas/field/FieldSet.cc +++ b/src/atlas/field/FieldSet.cc @@ -152,6 +152,14 @@ 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(); +} + 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 990dbf823..6e5a382af 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -27,6 +27,7 @@ #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" @@ -112,6 +113,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,6 +123,7 @@ 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 }; @@ -209,6 +214,9 @@ 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); } From 94bcf3c2959cf1b261d1c9ba7c7b79c060ae8e8d Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Sun, 9 Apr 2023 13:18:27 -0400 Subject: [PATCH 04/78] All partitioners are 'serial' when partitions == 1 --- src/atlas/grid/Partitioner.cc | 3 +++ src/atlas/grid/detail/grid/Structured.cc | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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 { From 48d4a37a71c6f18e736f712741d0b51e3d4c4684 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 21 Apr 2023 13:54:29 -0600 Subject: [PATCH 05/78] Fix gridtools compilation --- src/atlas/array/gridtools/GridToolsArray.cc | 2 +- src/atlas/interpolation/method/cubedsphere/CellFinder.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atlas/array/gridtools/GridToolsArray.cc b/src/atlas/array/gridtools/GridToolsArray.cc index 5c5a0c5cb..08e6bbc2f 100644 --- a/src/atlas/array/gridtools/GridToolsArray.cc +++ b/src/atlas/array/gridtools/GridToolsArray.cc @@ -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...); } 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) { From 85b8190e255fa8f2ae8978709cffc39490b83b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannick=20Tr=C3=A9molet?= <30638944+ytremolet@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:22:41 -0600 Subject: [PATCH 06/78] Added field/fieldset clone method (#124) Co-authored-by Willem Deconinck --- src/atlas/CMakeLists.txt | 4 +- src/atlas/array.h | 2 +- src/atlas/array/Array.h | 15 ++++++- .../array/{ArrayUtil.cc => ArrayDataStore.cc} | 2 +- .../array/{ArrayUtil.h => ArrayDataStore.h} | 0 src/atlas/array/ArraySpec.cc | 2 +- src/atlas/array/LocalView.h | 2 +- src/atlas/array/gridtools/GridToolsArray.cc | 10 +++-- .../array/gridtools/GridToolsArrayHelpers.h | 2 +- .../array/gridtools/GridToolsArrayView.h | 2 +- .../array/gridtools/GridToolsDataStore.h | 2 +- src/atlas/array/helpers/ArrayInitializer.h | 41 ++++++++++++++++-- src/atlas/array/native/NativeArray.cc | 42 ++++--------------- src/atlas/array/native/NativeArrayView.h | 2 +- src/atlas/array/native/NativeDataStore.h | 2 +- src/atlas/array/native/NativeIndexView.h | 2 +- src/atlas/field/Field.cc | 11 +++++ src/atlas/field/Field.h | 7 ++++ src/atlas/field/FieldCreatorIFS.cc | 2 +- src/atlas/field/FieldSet.cc | 8 ++++ src/atlas/field/FieldSet.h | 2 + src/atlas/field/detail/FieldImpl.h | 2 +- src/tests/grid/test_field.cc | 32 ++++++++++++++ 23 files changed, 142 insertions(+), 54 deletions(-) rename src/atlas/array/{ArrayUtil.cc => ArrayDataStore.cc} (95%) rename src/atlas/array/{ArrayUtil.h => ArrayDataStore.h} (100%) diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 3ca7f0b55..a2e7142fb 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -657,14 +657,14 @@ 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 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..86a1ded72 100644 --- a/src/atlas/array/Array.h +++ b/src/atlas/array/Array.h @@ -15,7 +15,7 @@ #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" @@ -132,6 +132,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 +213,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..c521e7a05 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" diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index 26b2ced2e..a420e4854 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -18,7 +18,7 @@ #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" diff --git a/src/atlas/array/gridtools/GridToolsArray.cc b/src/atlas/array/gridtools/GridToolsArray.cc index 08e6bbc2f..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" @@ -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/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/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..6466f23f4 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" 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..0ce02a2ff 100644 --- a/src/atlas/field/Field.cc +++ b/src/atlas/field/Field.cc @@ -57,6 +57,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 diff --git a/src/atlas/field/Field.h b/src/atlas/field/Field.h index d179a1112..2c5bba45d 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 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 bf8eda526..6c87a9c7d 100644 --- a/src/atlas/field/FieldSet.cc +++ b/src/atlas/field/FieldSet.cc @@ -160,6 +160,14 @@ 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)); + } + 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 6e5a382af..4bf5e1ff3 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -165,6 +165,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(); } diff --git a/src/atlas/field/detail/FieldImpl.h b/src/atlas/field/detail/FieldImpl.h index 645975b4a..8beca8a46 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" diff --git a/src/tests/grid/test_field.cc b/src/tests/grid/test_field.cc index f54929968..c7189f6b5 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))); From 7e41eb7de1c617564aa1a98c7544c3948162e7bb Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 21 Apr 2023 14:29:39 -0600 Subject: [PATCH 07/78] Also copy Fieldset metadata (#124) --- src/atlas/field/FieldSet.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atlas/field/FieldSet.cc b/src/atlas/field/FieldSet.cc index 6c87a9c7d..0627ed7bb 100644 --- a/src/atlas/field/FieldSet.cc +++ b/src/atlas/field/FieldSet.cc @@ -165,6 +165,7 @@ FieldSet FieldSet::clone(const eckit::Parametrisation& config) const { for (idx_t jj = 0; jj < size(); ++jj) { fset.add(field(jj).clone(config)); } + fset.metadata() = metadata(); return fset; } From a101f4ae1d6d7b59c68cd15d098db29855f19642 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 21 Apr 2023 15:17:23 -0600 Subject: [PATCH 08/78] Delaunay parallel meshgenerator --- src/atlas/interpolation/method/PointSet.h | 25 +++ src/atlas/mesh/actions/BuildConvexHull3D.cc | 7 + src/atlas/mesh/actions/BuildConvexHull3D.h | 8 + src/atlas/mesh/actions/ExtendNodesGlobal.cc | 4 + .../detail/DelaunayMeshGenerator.cc | 211 ++++++++++++++---- .../detail/DelaunayMeshGenerator.h | 4 +- 6 files changed, 210 insertions(+), 49 deletions(-) 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/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/ExtendNodesGlobal.cc b/src/atlas/mesh/actions/ExtendNodesGlobal.cc index 43292ae2f..e7aec205f 100644 --- a/src/atlas/mesh/actions/ExtendNodesGlobal.cc +++ b/src/atlas/mesh/actions/ExtendNodesGlobal.cc @@ -66,6 +66,8 @@ 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()); for (idx_t i = 0; i < nb_extension_pts; ++i) { const idx_t n = nb_real_pts + i; @@ -82,6 +84,8 @@ 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) = true; + partition(n) = -1; } } diff --git a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc index be86d606a..b3b5a9c7e 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,164 @@ 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 + + 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()); + + 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; + + ++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()); + + size_t owned_nodes_count = dist.nb_pts()[part_]; + + 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); + for (idx_t jelem=0; jelem elem_nodes { + g_node_connectivity(jelem,0), + g_node_connectivity(jelem,1), + g_node_connectivity(jelem,2) + }; + std::array elem_nodes_partition; + for (idx_t j=0; j<3; ++j) { + elem_nodes_partition[j] = g_part(elem_nodes[j]); + } + idx_t elem_partition = -1; + if (elem_nodes_partition[0] >= 0 ) { + elem_partition = elem_nodes_partition[0]; + } + if (elem_nodes_partition[1] == elem_nodes_partition[2] && elem_nodes_partition[1] >= 0) { + elem_partition = elem_nodes_partition[1]; + } + if (elem_partition == part_) { + owned_elements.emplace_back(jelem); + for (idx_t j=0; j<3; ++j) { + if (elem_nodes_partition[j] != elem_partition) { + ghost_nodes.insert(elem_nodes[j]); + } + } + } + } + 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()); + + 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::createNodes(const Grid& grid, Mesh& mesh) const { - 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()); - - 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(); - - ghost(jnode) = false; - - gidx(jnode) = jnode + 1; - - ++jnode; - } +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_; }; //---------------------------------------------------------------------------------------------------------------------- From 8273190e92020fdd21cf7a9ea00b2ea61de4654a Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Sat, 22 Apr 2023 00:58:57 -0600 Subject: [PATCH 09/78] Delaunay parallel meshgenerator with better rule for element partition --- .../detail/DelaunayMeshGenerator.cc | 177 ++++++++++++++++-- 1 file changed, 158 insertions(+), 19 deletions(-) diff --git a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc index b3b5a9c7e..af2bfaed5 100644 --- a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc @@ -122,32 +122,171 @@ void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& std::set ghost_nodes; std::vector owned_elements; owned_elements.reserve(1.4*owned_nodes_count); - for (idx_t jelem=0; jelem elem_nodes { - g_node_connectivity(jelem,0), - g_node_connectivity(jelem,1), - g_node_connectivity(jelem,2) - }; - std::array elem_nodes_partition; + 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 elem_node_partition = [&](idx_t jelem, idx_t jnode) -> int { + return g_part(g_node_connectivity(jelem,jnode)); + }; + + 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; + }; + + 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; + }; + + + auto collect_element = [&](idx_t jelem){ + owned_elements.emplace_back(jelem); for (idx_t j=0; j<3; ++j) { - elem_nodes_partition[j] = g_part(elem_nodes[j]); + if (elem_node_partition(jelem,j) != part_) { + ghost_nodes.insert(g_node_connectivity(jelem,j)); + } } - idx_t elem_partition = -1; - if (elem_nodes_partition[0] >= 0 ) { - elem_partition = elem_nodes_partition[0]; + }; + + for (idx_t jelem=0; jelem= 0) { - elem_partition = elem_nodes_partition[1]; + else if (elem_ownership == UNCERTAIN) { + // all three are different + element_nodes_uncertainty.insert(g_node_connectivity(jelem,0)); + element_nodes_uncertainty.insert(g_node_connectivity(jelem,1)); + element_nodes_uncertainty.insert(g_node_connectivity(jelem,2)); + element_uncertainty.insert(jelem); } - if (elem_partition == part_) { - owned_elements.emplace_back(jelem); - for (idx_t j=0; j<3; ++j) { - if (elem_nodes_partition[j] != elem_partition) { - ghost_nodes.insert(elem_nodes[j]); + } + // Log::info() << "element_uncertainty" << std::endl; + // for( auto& jelem: element_uncertainty ) { + // Log::info() << jelem << std::endl; + // } + + if( element_uncertainty.size() ) { + std::map> 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); From 31bf546c84786d19e6832d4d81ccb9665ac3ff2f Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Sat, 22 Apr 2023 04:08:07 -0600 Subject: [PATCH 10/78] Delaunay parallel meshgenerator for regional grids --- src/atlas/mesh/actions/ExtendNodesGlobal.cc | 9 ++++++--- .../meshgenerator/detail/DelaunayMeshGenerator.cc | 11 +++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/atlas/mesh/actions/ExtendNodesGlobal.cc b/src/atlas/mesh/actions/ExtendNodesGlobal.cc index e7aec205f..51b38299c 100644 --- a/src/atlas/mesh/actions/ExtendNodesGlobal.cc +++ b/src/atlas/mesh/actions/ExtendNodesGlobal.cc @@ -67,7 +67,8 @@ void ExtendNodesGlobal::operator()(const Grid& grid, Mesh& mesh) const { 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 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; @@ -84,8 +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) = true; - partition(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/meshgenerator/detail/DelaunayMeshGenerator.cc b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc index af2bfaed5..1343d6e9f 100644 --- a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc @@ -65,6 +65,7 @@ void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& 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(); @@ -82,6 +83,10 @@ void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& gidx(jnode) = jnode + 1; + if (ghost(jnode)) { + util::Topology::view(flags(jnode)).set(util::Topology::GHOST); + } + ++jnode; } mesh::actions::BuildXYZField()(mesh); @@ -296,7 +301,7 @@ void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& 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 Date: Sat, 22 Apr 2023 04:09:09 -0600 Subject: [PATCH 11/78] Fix BuildHalo for cubed sphere meshed with Delaunay meshgenerator --- src/atlas/mesh/actions/BuildHalo.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/atlas/mesh/actions/BuildHalo.cc b/src/atlas/mesh/actions/BuildHalo.cc index c83195fa9..5682d59f8 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 { @@ -868,7 +869,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 +924,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"; From 841510978d22644833d9f36c92795071dacb6a73 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 27 Apr 2023 16:06:15 +0200 Subject: [PATCH 12/78] Add functions to enable/disable floating point exceptions --- src/atlas/library/FloatingPointExceptions.cc | 37 ++++++++++++++++++-- src/atlas/library/FloatingPointExceptions.h | 22 ++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/atlas/library/FloatingPointExceptions.cc b/src/atlas/library/FloatingPointExceptions.cc index 2c447367e..654c34bfb 100644 --- a/src/atlas/library/FloatingPointExceptions.cc +++ b/src/atlas/library/FloatingPointExceptions.cc @@ -42,7 +42,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 +49,6 @@ static int atlas_fedisableexcept(int excepts) { return 0; #endif } -#endif namespace atlas { namespace library { @@ -316,6 +314,41 @@ 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)) { 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 From b7790327975732d4ce74ec2d63bc6e721f002da0 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 2 May 2023 18:30:59 +0200 Subject: [PATCH 13/78] Support string_view as value in util::Config constructor --- src/atlas/util/Config.h | 4 ++++ 1 file changed, 4 insertions(+) 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); From 1bbbe4982d8955816c6001cdbb3f282c5997eac0 Mon Sep 17 00:00:00 2001 From: Oliver Lomax Date: Tue, 2 May 2023 17:56:45 +0100 Subject: [PATCH 14/78] Add ArrayForEach method which takes advantage of ArraySlicer. (#127) Implement helper class that enables to iterate over dimensions of multiple ArrayViews and applies a template function to each element or slice. To serve as building block to build more high-level API. Co-authored-by: Willem Deconinck --- src/atlas/CMakeLists.txt | 1 + src/atlas/array/helpers/ArrayForEach.h | 307 +++++++++++++++++++ src/tests/array/CMakeLists.txt | 6 + src/tests/array/test_array_foreach.cc | 389 +++++++++++++++++++++++++ 4 files changed, 703 insertions(+) create mode 100644 src/atlas/array/helpers/ArrayForEach.h create mode 100644 src/tests/array/test_array_foreach.cc diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index a2e7142fb..599b5a378 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -682,6 +682,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 diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h new file mode 100644 index 000000000..8f1744d0f --- /dev/null +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -0,0 +1,307 @@ +/* + * (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 +std::string policy_name() { + return "unsupported"; +}; +template <> +std::string policy_name() { + return "sequenced_policy"; +}; +template <> +std::string policy_name() { + return "unsequenced_policy"; +}; +template <> +std::string policy_name() { + return "parallel_unsequenced_policy"; +}; + +template +std::string policy_name(execution_policy) { + return policy_name(); +} + +} // 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 { + +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(std::is_same_v) { + 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 typename View, + typename Value, int Rank, typename... ArrayViews> +auto makeSlices(const std::tuple& slicerArgs, + View& arrayView, ArrayViews&... arrayViews) { + + // "Lambdafy" slicer apply method to work with std::apply. + const auto slicer = [&arrayView](const auto&... args) { + return std::make_tuple(arrayView.slice(args...)); + }; + + // Fill out the remaining slicerArgs with Range::all(). + constexpr auto Dim = sizeof...(SlicerArgs); + const auto paddedArgs = std::tuple_cat(slicerArgs, argPadding()); + + if constexpr(sizeof...(ArrayViews) > 0) { + + // Recurse until all views are sliced. + return std::tuple_cat(std::apply(slicer, paddedArgs), + makeSlices(slicerArgs, arrayViews...)); + } + else { + return std::apply(slicer, paddedArgs); + } +} + +template +struct ArrayForEachImpl; + +template +struct ArrayForEachImpl { + template + static void apply(std::tuple& arrayViews, + const Mask& mask, + const Function& function, + const std::tuple& slicerArgs, + const std::tuple& maskArgs) { + + using namespace detail; + + // 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) { + + // Decay from parallel_unsequenced to unsequenced policy + if constexpr(std::is_same_v) { + ArrayForEachImpl::apply( + arrayViews, mask, function, + tuplePushBack(slicerArgs, idx), + tuplePushBack(maskArgs, idx)); + } + else { + // Retain current execution policy. + ArrayForEachImpl::apply( + arrayViews, mask, function, + tuplePushBack(slicerArgs, idx), + tuplePushBack(maskArgs, idx)); + } + }); + } + // Add a RangeAll to arguments. + else { + ArrayForEachImpl::apply( + arrayViews, mask, function, + tuplePushBack(slicerArgs, Range::all()), + maskArgs); + } + } +}; + +template +struct ArrayForEachImpl { + template + static void apply(std::tuple& arrayViews, + const Mask& mask, + const Function& function, + const std::tuple& slicerArgs, + const std::tuple& maskArgs) { + + // Skip iteration if mask evaluates to true. + if (std::apply(mask, maskArgs)) { + return; + } + + const auto slicerWrapper = [&slicerArgs](auto&... args) { + return makeSlices(slicerArgs, args...); + }; + + auto slices = std::apply(slicerWrapper, arrayViews); + std::apply(function, 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" = + /// "parallel_unsequenced_policy" (default) the first loop is executed + /// using OpenMP. The remaining loops are executed in serial. When + /// "execution_policy" = "sequenced_policy", all loops are executed in + /// sequential (row-major) order. + /// 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, + const std::tuple& arrayViews, + const Mask& mask, const Function& function) { + + auto execute = [&](auto execution_policy) { + // Make a copy of views to simplify constness and forwarding. + auto arrayViewsCopy = arrayViews; + + detail::ArrayForEachImpl::apply( + arrayViewsCopy, mask, function, std::make_tuple(), std::make_tuple()); + }; + + 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 { + execute(par_unseq); + } + } + + template ,int> =0> + static void apply(ExecutionPolicy, const std::tuple& arrayViews, const Mask& mask, const Function& function) { + apply(option::execution_policy(), arrayViews, mask, function); + } + + template + static void apply(const std::tuple& arrayViews, const Mask& mask, const Function& function) { + apply(util::NoConfig(), arrayViews, mask, function); + } + + /// brief Apply "For-Each" method. + /// + /// details Apply "For-Each" without a mask. + template + static void apply(const eckit::Parametrisation& conf, const std::tuple& arrayViews, const Function& function) { + constexpr auto no_mask = [](auto args...) { return 0; }; + apply(conf, arrayViews, no_mask, function); + } + + template ,int> =0> + static void apply(ExecutionPolicy, const std::tuple& arrayViews, const Function& function) { + apply(option::execution_policy(), arrayViews, function); + } + + template + static void apply(const std::tuple& arrayViews, const Function& function) { + apply(util::NoConfig{}, arrayViews, function); + } + +}; + +} // namespace helpers +} // namespace array +} // namespace atlas diff --git a/src/tests/array/CMakeLists.txt b/src/tests/array/CMakeLists.txt index 3afbb4832..9d39f6ea1 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 diff --git a/src/tests/array/test_array_foreach.cc b/src/tests/array/test_array_foreach.cc new file mode 100644 index 000000000..aeda1ea61 --- /dev/null +++ b/src/tests/array/test_array_foreach.cc @@ -0,0 +1,389 @@ +/* + * (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_EQUAL(slice.rank(), 1); + EXPECT_EQUAL(slice.shape(0), 3); + }; + ArrayForEach<0>::apply(std::make_tuple(view), loopFunctorDim0); + + const auto loopFunctorDim1 = [](auto& slice) { + EXPECT_EQUAL(slice.rank(), 1); + EXPECT_EQUAL(slice.shape(0), 2); + }; + ArrayForEach<1>::apply(std::make_tuple(view), loopFunctorDim1); + + // Test that slice resolves to double. + + const auto loopFunctorDimAll = [](auto& slice) { + static_assert(std::is_convertible_v); + }; + ArrayForEach<0, 1>::apply(std::make_tuple(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::make_tuple(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::make_tuple(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_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); + + const auto loopFunctorDim1 = [](auto& slice1, auto& slice2) { + EXPECT_EQUAL(slice1.rank(), 1); + EXPECT_EQUAL(slice1.shape(0), 2); + + EXPECT_EQUAL(slice2.rank(), 2); + EXPECT_EQUAL(slice2.shape(0), 2); + EXPECT_EQUAL(slice2.shape(1), 4); + }; + ArrayForEach<1>::apply(std::make_tuple(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::make_tuple(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::make_tuple(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::make_tuple(view2), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 3); + + count = 0; + ArrayForEach<0, 1, 2>::apply(execution::seq, std::make_tuple(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_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); + + EXPECT_EQUAL(slice3.rank(), 3); + EXPECT_EQUAL(slice3.shape(0), 3); + EXPECT_EQUAL(slice3.shape(1), 4); + EXPECT_EQUAL(slice3.shape(2), 5); + }; + ArrayForEach<0>::apply(std::make_tuple(view1, view2, view3), loopFunctorDim0); + + const auto loopFunctorDim1 = [](auto& slice1, auto& slice2, auto& slice3) { + EXPECT_EQUAL(slice1.rank(), 1); + EXPECT_EQUAL(slice1.shape(0), 2); + + EXPECT_EQUAL(slice2.rank(), 2); + EXPECT_EQUAL(slice2.shape(0), 2); + EXPECT_EQUAL(slice2.shape(1), 4); + + EXPECT_EQUAL(slice3.rank(), 3); + EXPECT_EQUAL(slice3.shape(0), 2); + EXPECT_EQUAL(slice3.shape(1), 4); + EXPECT_EQUAL(slice3.shape(2), 5); + }; + ArrayForEach<1>::apply(std::make_tuple(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::make_tuple(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::make_tuple(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::make_tuple(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 3); + + count = 0; + ArrayForEach<0, 1, 2>::apply(execution::seq, std::make_tuple(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 12); + + count = 0; + ArrayForEach<0, 1, 2, 3>::apply(execution::seq, std::make_tuple(view3), ghostWrap, countNonGhosts); + EXPECT_EQ(count, 60); +} + +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::make_tuple(slice2), scaleDataDim1); + }; + ArrayForEach<0, 1>::apply(std::make_tuple(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& __restrict__ a1, const double& __restrict__ a2, + const double& __restrict__ 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::make_tuple(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::make_tuple(view1, view2, view3), function); + }; + + const auto forEachAll = [&](const auto& operation) { + ArrayForEach<0, 1>::apply(execution::seq, std::make_tuple(view1, view2, view3), operation); + }; + + + + 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) "); + 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) "); +} + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { return atlas::test::run(argc, argv); } From 7d4fac6a5793ae841afd96eaf066be71787f6917 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 2 May 2023 18:31:51 +0200 Subject: [PATCH 15/78] Use constexpr string_view return type for execution::policy_name --- src/atlas/array/helpers/ArrayForEach.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h index 8f1744d0f..f124a1011 100644 --- a/src/atlas/array/helpers/ArrayForEach.h +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "atlas/array/ArrayView.h" #include "atlas/array/Range.h" @@ -37,24 +38,24 @@ inline constexpr unsequenced_policy unseq{ /*unspecified*/ }; // Type names for execution policy (Not in C++ standard) template -std::string policy_name() { +constexpr std::string_view policy_name() { return "unsupported"; }; template <> -std::string policy_name() { +constexpr std::string_view policy_name() { return "sequenced_policy"; }; template <> -std::string policy_name() { +constexpr std::string_view policy_name() { return "unsequenced_policy"; }; template <> -std::string policy_name() { +constexpr std::string_view policy_name() { return "parallel_unsequenced_policy"; }; template -std::string policy_name(execution_policy) { +constexpr std::string_view policy_name(execution_policy) { return policy_name(); } From a6a049b803e6cb5707ee56987f5681b0847314ed Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 3 May 2023 10:02:00 +0200 Subject: [PATCH 16/78] util::Metadata can embed another Metadata via set() --- src/atlas/util/Metadata.cc | 12 ++++++++++++ src/atlas/util/Metadata.h | 2 ++ 2 files changed, 14 insertions(+) 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 From 20094caed6a00b1adbc09692bd1254f1d18ccfe5 Mon Sep 17 00:00:00 2001 From: Slavko Brdar Date: Wed, 8 Mar 2023 11:57:19 +0100 Subject: [PATCH 17/78] New algorithm for ConvexSphericalPolygon intersection; interpolation fails --- src/atlas/util/ConvexSphericalPolygon.cc | 547 +++++++---------- src/atlas/util/ConvexSphericalPolygon.h | 44 +- src/tests/util/test_convexsphericalpolygon.cc | 559 +++++++++++++----- 3 files changed, 643 insertions(+), 507 deletions(-) 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/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); + } + } //----------------------------------------------------------------------------- From 56454e9e542a656f35acc067e7818d61156b600f Mon Sep 17 00:00:00 2001 From: Slavko Brdar Date: Wed, 8 Mar 2023 14:38:32 +0100 Subject: [PATCH 18/78] Fix and diagnose problems with ConservativeSphericalInterpolation --- ...nservativeSphericalPolygonInterpolation.cc | 1083 +++++++++++------ ...onservativeSphericalPolygonInterpolation.h | 42 +- .../atlas-conservative-interpolation.cc | 53 +- .../test_interpolation_conservative.cc | 86 +- 4 files changed, 827 insertions(+), 437 deletions(-) diff --git a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc index b87c17987..863591212 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(); @@ -751,8 +929,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()); @@ -768,17 +948,11 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg 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; - }; 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 +981,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 +1097,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 +1255,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 +1274,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 +1286,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 +1298,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 +1317,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 +1369,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 +1416,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 +1438,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 +1463,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 +1475,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 +1513,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 +1539,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 +1563,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 +1600,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 +1615,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 +1629,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 +1679,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 +1712,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 +1729,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 +1777,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 +1854,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 +1891,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 +1926,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 +1988,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 +2003,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 +2108,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 +2129,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/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/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 From 5233ae580553ed12b29d8680861dd164998baf2e Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 26 Apr 2023 13:51:27 +0200 Subject: [PATCH 19/78] Configurable eps in compare_pointxyz via ATLAS_COMPAREPOINTXYZ_EPS_FACTOR env var New default 1e8*eps instead of 1e4*eps. For previous behaviour: ATLAS_COMPAREPOINTXYZ_EPS_FACTOR=1e4 For high resolution we want to have it to 1e4, for low resolution 1e8 The detection mechanism of source points needs to be replaced with something deterministic that does not rely on comparing points. --- ...nservativeSphericalPolygonInterpolation.cc | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc index 863591212..d1ea5addf 100644 --- a/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc +++ b/src/atlas/interpolation/method/unstructured/ConservativeSphericalPolygonInterpolation.cc @@ -896,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(); @@ -947,7 +923,33 @@ void ConservativeSphericalPolygonInterpolation::intersect_polygons(const CSPolyg StopWatch stopwatch_polygon_intersections; stopwatch_src_already_in.start(); - std::set src_cent; + + + // 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()) { atlas_omp_critical{ From 565c84d9be3e92c8262348bbfda24cc2acb266f2 Mon Sep 17 00:00:00 2001 From: Lorenzo Milazzo <82884956+mo-lormi@users.noreply.github.com> Date: Thu, 4 May 2023 09:02:57 +0100 Subject: [PATCH 20/78] Upgrade the halo exchange procedure for the function space 'PointCloud' (#120) --- src/atlas/functionspace/PointCloud.cc | 168 +++++++++-- src/atlas/functionspace/PointCloud.h | 16 +- src/tests/functionspace/CMakeLists.txt | 16 + src/tests/functionspace/test_pointcloud.cc | 182 +---------- .../test_pointcloud_haloexchange_2PE.cc | 285 ++++++++++++++++++ .../test_pointcloud_haloexchange_3PE.cc | 130 ++++++++ 6 files changed, 597 insertions(+), 200 deletions(-) create mode 100644 src/tests/functionspace/test_pointcloud_haloexchange_2PE.cc create mode 100644 src/tests/functionspace/test_pointcloud_haloexchange_3PE.cc 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/tests/functionspace/CMakeLists.txt b/src/tests/functionspace/CMakeLists.txt index 47a1fff31..b97869098 100644 --- a/src/tests/functionspace/CMakeLists.txt +++ b/src/tests/functionspace/CMakeLists.txt @@ -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/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); +} From 4c38ca0c98d17b221c264551da607494572ac8ef Mon Sep 17 00:00:00 2001 From: Daniel Tipping Date: Tue, 9 May 2023 20:24:45 +0100 Subject: [PATCH 21/78] Add workflow to check release version is correct (#132) * Add workflow to check release version is correct * Check hotfix version is correct --- .github/workflows/check-release-version.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/check-release-version.yml 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 From cad95abf93f91cdf9f8307b29cb21bbe2092e61c Mon Sep 17 00:00:00 2001 From: Oliver Lomax Date: Wed, 10 May 2023 10:08:15 +0100 Subject: [PATCH 22/78] Optimise array::helpers::ForEach for small arrays. (#133) * Optimised ForEach for small arrays. * Tidied up interface * Perfect forwarding of ArrayViews. * Added Doxygen comments. * Stripped out unneeded code. * Removed redundant header. * Simplified ArrayView rank getter. * Addressed reviewer comments. * Typo in function name. --- src/atlas/array/helpers/ArrayForEach.h | 161 +++++++++++++++---------- src/tests/array/test_array_foreach.cc | 109 +++++++++++------ 2 files changed, 169 insertions(+), 101 deletions(-) diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h index f124a1011..429d472d2 100644 --- a/src/atlas/array/helpers/ArrayForEach.h +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -16,7 +15,6 @@ #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 { @@ -59,6 +57,15 @@ 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; +} + } // namespace execution namespace option { @@ -82,6 +89,17 @@ namespace helpers { namespace detail { +template +struct IsTupleImpl : std::false_type {}; + +template +struct IsTupleImpl> : std::true_type {}; + +template +constexpr auto is_tuple() { + return IsTupleImpl::value; +} + template constexpr auto tuplePushBack(const std::tuple& tuple, T value) { return std::tuple_cat(tuple, std::make_tuple(value)); @@ -114,28 +132,29 @@ constexpr auto argPadding() { } } -template typename View, - typename Value, int Rank, typename... ArrayViews> +template auto makeSlices(const std::tuple& slicerArgs, - View& arrayView, ArrayViews&... arrayViews) { + ArrayViewTuple&& arrayViews) { + + if constexpr(ViewIdx < std::tuple_size_v>) { - // "Lambdafy" slicer apply method to work with std::apply. - const auto slicer = [&arrayView](const auto&... args) { - return std::make_tuple(arrayView.slice(args...)); - }; + auto&& arrayView = std::get(arrayViews); + using ArrayView = std::decay_t; - // Fill out the remaining slicerArgs with Range::all(). - constexpr auto Dim = sizeof...(SlicerArgs); - const auto paddedArgs = std::tuple_cat(slicerArgs, argPadding()); + constexpr auto Dim = sizeof...(SlicerArgs); + constexpr auto Rank = ArrayView::rank(); + const auto paddedArgs = + std::tuple_cat(slicerArgs, argPadding()); - if constexpr(sizeof...(ArrayViews) > 0) { + const auto slicer = [&arrayView](const auto&... args) { + return std::make_tuple(arrayView.slice(args...)); + }; - // Recurse until all views are sliced. return std::tuple_cat(std::apply(slicer, paddedArgs), - makeSlices(slicerArgs, arrayViews...)); + makeSlices(slicerArgs, std::forward(arrayViews))); } else { - return std::apply(slicer, paddedArgs); + return std::make_tuple(); } } @@ -144,9 +163,9 @@ struct ArrayForEachImpl; template struct ArrayForEachImpl { - template - static void apply(std::tuple& arrayViews, + static void apply(ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function, const std::tuple& slicerArgs, @@ -165,14 +184,14 @@ struct ArrayForEachImpl { // Decay from parallel_unsequenced to unsequenced policy if constexpr(std::is_same_v) { ArrayForEachImpl::apply( - arrayViews, mask, function, + std::forward(arrayViews), mask, function, tuplePushBack(slicerArgs, idx), tuplePushBack(maskArgs, idx)); } else { // Retain current execution policy. ArrayForEachImpl::apply( - arrayViews, mask, function, + std::forward(arrayViews), mask, function, tuplePushBack(slicerArgs, idx), tuplePushBack(maskArgs, idx)); } @@ -181,7 +200,7 @@ struct ArrayForEachImpl { // Add a RangeAll to arguments. else { ArrayForEachImpl::apply( - arrayViews, mask, function, + std::forward(arrayViews), mask, function, tuplePushBack(slicerArgs, Range::all()), maskArgs); } @@ -190,9 +209,9 @@ struct ArrayForEachImpl { template struct ArrayForEachImpl { - template - static void apply(std::tuple& arrayViews, + static void apply(ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function, const std::tuple& slicerArgs, @@ -203,11 +222,7 @@ struct ArrayForEachImpl { return; } - const auto slicerWrapper = [&slicerArgs](auto&... args) { - return makeSlices(slicerArgs, args...); - }; - - auto slices = std::apply(slicerWrapper, arrayViews); + auto slices = makeSlices(slicerArgs, std::forward(arrayViews)); std::apply(function, slices); } }; @@ -236,69 +251,83 @@ struct ArrayForEach { /// sequential (row-major) order. /// Note: The lowest ArrayView.rank() must be greater than or equal /// to the highest dim in ItrDims. TODO: static checking for this. - template + template ()>> static void apply(const eckit::Parametrisation& conf, - const std::tuple& arrayViews, + ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function) { auto execute = [&](auto execution_policy) { - // Make a copy of views to simplify constness and forwarding. - auto arrayViewsCopy = arrayViews; - - detail::ArrayForEachImpl::apply( - arrayViewsCopy, mask, function, std::make_tuple(), std::make_tuple()); + apply(execution_policy, std::forward(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); - } + 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 { execute(par_unseq); } } - template ,int> =0> - static void apply(ExecutionPolicy, const std::tuple& arrayViews, const Mask& mask, const Function& function) { - apply(option::execution_policy(), arrayViews, mask, function); + /// brief Apply "For-Each" method. + /// + /// details As above, but Execution policy is determined at compile-time. + template ()>, + typename = std::enable_if_t()>> + static void apply(ExecutionPolicy, ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function) { + + detail::ArrayForEachImpl::apply( + std::forward(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); } - template - static void apply(const std::tuple& arrayViews, const Mask& mask, const Function& function) { - apply(util::NoConfig(), arrayViews, mask, function); + /// brief Apply "For-Each" method + /// + /// detials Apply ForEach with default execution policy. + template ()>> + static void apply(ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function) { + apply(std::forward(arrayViews), mask, function); } - /// brief Apply "For-Each" method. + /// brief Apply "For-Each" method /// - /// details Apply "For-Each" without a mask. - template - static void apply(const eckit::Parametrisation& conf, const std::tuple& arrayViews, const Function& function) { + /// detials Apply ForEach with run-time determined execution policy and no mask. + template ()>> + static void apply(const eckit::Parametrisation& conf, ArrayViewTuple&& arrayViews, const Function& function) { constexpr auto no_mask = [](auto args...) { return 0; }; - apply(conf, arrayViews, no_mask, function); + apply(conf, std::forward(arrayViews), no_mask, function); } - template ,int> =0> - static void apply(ExecutionPolicy, const std::tuple& arrayViews, const Function& function) { - apply(option::execution_policy(), arrayViews, function); + /// brief Apply "For-Each" method + /// + /// detials Apply ForEach with compile-time determined execution policy and no mask. + template ()>, + typename = std::enable_if_t()>> + static void apply(ExecutionPolicy executionPolicy, ArrayViewTuple&& arrayViews, const Function& function) { + constexpr auto no_mask = [](auto args...) { return 0; }; + apply(executionPolicy, std::forward(arrayViews), no_mask, function); } - template - static void apply(const std::tuple& arrayViews, const Function& function) { - apply(util::NoConfig{}, arrayViews, function); + /// brief Apply "For-Each" method + /// + /// detials Apply ForEach with default execution policy and no mask. + template ()>> + static void apply(ArrayViewTuple arrayViews, const Function& function) { + apply(execution::par_unseq, std::forward(arrayViews), function); } }; diff --git a/src/tests/array/test_array_foreach.cc b/src/tests/array/test_array_foreach.cc index aeda1ea61..0d14d8e60 100644 --- a/src/tests/array/test_array_foreach.cc +++ b/src/tests/array/test_array_foreach.cc @@ -33,20 +33,20 @@ CASE("test_array_foreach_1_view") { EXPECT_EQUAL(slice.rank(), 1); EXPECT_EQUAL(slice.shape(0), 3); }; - ArrayForEach<0>::apply(std::make_tuple(view), loopFunctorDim0); + ArrayForEach<0>::apply(std::tie(view), loopFunctorDim0); const auto loopFunctorDim1 = [](auto& slice) { EXPECT_EQUAL(slice.rank(), 1); EXPECT_EQUAL(slice.shape(0), 2); }; - ArrayForEach<1>::apply(std::make_tuple(view), loopFunctorDim1); + 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::make_tuple(view), loopFunctorDimAll); + ArrayForEach<0, 1>::apply(std::tie(view), loopFunctorDimAll); // Test ghost functionality. @@ -56,7 +56,7 @@ CASE("test_array_foreach_1_view") { auto count = int {}; const auto countNonGhosts = [&count](auto&...) { ++count; }; - ArrayForEach<0>::apply(execution::seq, std::make_tuple(view), ghostView, countNonGhosts); + ArrayForEach<0>::apply(execution::seq, std::tie(view), ghostView, countNonGhosts); EXPECT_EQ(count, 1); count = 0; @@ -64,7 +64,7 @@ CASE("test_array_foreach_1_view") { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; - ArrayForEach<0, 1>::apply(execution::seq, std::make_tuple(view), ghostWrap, countNonGhosts); + ArrayForEach<0, 1>::apply(execution::seq, std::tie(view), ghostWrap, countNonGhosts); EXPECT_EQ(count, 3); } @@ -86,7 +86,7 @@ CASE("test_array_foreach_2_views") { 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); const auto loopFunctorDim1 = [](auto& slice1, auto& slice2) { EXPECT_EQUAL(slice1.rank(), 1); @@ -96,14 +96,14 @@ CASE("test_array_foreach_2_views") { EXPECT_EQUAL(slice2.shape(0), 2); EXPECT_EQUAL(slice2.shape(1), 4); }; - ArrayForEach<1>::apply(std::make_tuple(view1, view2), loopFunctorDim1); + 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::make_tuple(view2), loopFunctorDimAll); + ArrayForEach<0, 1, 2>::apply(std::tie(view2), loopFunctorDimAll); // Test ghost functionality. @@ -113,7 +113,7 @@ CASE("test_array_foreach_2_views") { auto count = int {}; const auto countNonGhosts = [&count](auto&...) { ++count; }; - ArrayForEach<0>::apply(execution::seq, std::make_tuple(view2), ghostView, countNonGhosts); + ArrayForEach<0>::apply(execution::seq, std::tie(view2), ghostView, countNonGhosts); EXPECT_EQ(count, 1); count = 0; @@ -121,11 +121,11 @@ CASE("test_array_foreach_2_views") { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; - ArrayForEach<0, 1>::apply(execution::seq, std::make_tuple(view2), ghostWrap, countNonGhosts); + 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::make_tuple(view2), ghostWrap, countNonGhosts); + ArrayForEach<0, 1, 2>::apply(execution::seq, std::tie(view2), ghostWrap, countNonGhosts); EXPECT_EQ(count, 12); } @@ -155,7 +155,7 @@ CASE("test_array_foreach_3_views") { EXPECT_EQUAL(slice3.shape(1), 4); EXPECT_EQUAL(slice3.shape(2), 5); }; - ArrayForEach<0>::apply(std::make_tuple(view1, view2, view3), loopFunctorDim0); + ArrayForEach<0>::apply(std::tie(view1, view2, view3), loopFunctorDim0); const auto loopFunctorDim1 = [](auto& slice1, auto& slice2, auto& slice3) { EXPECT_EQUAL(slice1.rank(), 1); @@ -170,14 +170,14 @@ CASE("test_array_foreach_3_views") { EXPECT_EQUAL(slice3.shape(1), 4); EXPECT_EQUAL(slice3.shape(2), 5); }; - ArrayForEach<1>::apply(std::make_tuple(view1, view2, view3), loopFunctorDim1); + 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::make_tuple(view3), loopFunctorDimAll); + ArrayForEach<0, 1, 2, 3>::apply(std::tie(view3), loopFunctorDimAll); // Test ghost functionality. @@ -187,7 +187,7 @@ CASE("test_array_foreach_3_views") { auto count = int {}; const auto countNonGhosts = [&count](auto&...) { ++count; }; - ArrayForEach<0>::apply(execution::seq, std::make_tuple(view3), ghostView, countNonGhosts); + ArrayForEach<0>::apply(execution::seq, std::tie(view3), ghostView, countNonGhosts); EXPECT_EQ(count, 1); count = 0; @@ -195,18 +195,41 @@ CASE("test_array_foreach_3_views") { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; - ArrayForEach<0, 1>::apply(execution::seq, std::make_tuple(view3), ghostWrap, countNonGhosts); + 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::make_tuple(view3), ghostWrap, countNonGhosts); + 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::make_tuple(view3), ghostWrap, countNonGhosts); + 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); @@ -233,9 +256,9 @@ CASE("test_array_foreach_data_integrity") { static_assert(std::is_convertible_v); slice *= 3.; }; - ArrayForEach<0>::apply(execution::seq, std::make_tuple(slice2), scaleDataDim1); + ArrayForEach<0>::apply(execution::seq, std::tie(slice2), scaleDataDim1); }; - ArrayForEach<0, 1>::apply(std::make_tuple(view1, view2), scaleDataDim0); + 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); @@ -345,7 +368,7 @@ CASE("test_array_foreach_performance") { operation(slice1(idx), slice2(idx), slice3(idx)); } }; - ArrayForEach<0>::apply(execution::seq, std::make_tuple(view1, view2, view3), function); + ArrayForEach<0>::apply(execution::seq, std::tie(view1, view2, view3), function); }; const auto forEachLevel = [&](const auto& operation) { @@ -355,32 +378,48 @@ CASE("test_array_foreach_performance") { operation(slice1(idx), slice2(idx), slice3(idx)); } }; - ArrayForEach<1>::apply(execution::seq, std::make_tuple(view1, view2, view3), function); + ArrayForEach<1>::apply(execution::seq, std::tie(view1, view2, view3), function); }; const auto forEachAll = [&](const auto& operation) { - ArrayForEach<0, 1>::apply(execution::seq, std::make_tuple(view1, view2, view3), 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) "); + 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) "); + 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 From a27fc304525607c16cae7a44a2424c328df34d30 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 10 May 2023 13:44:58 +0200 Subject: [PATCH 23/78] Fix compilation of atlas_io due to missing header --- atlas_io/src/atlas_io/Data.h | 2 ++ 1 file changed, 2 insertions(+) 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 { From 2100fc5d1c3a5dd36b2f840e4b5a00a01f4ff38d Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 8 May 2023 11:05:58 +0200 Subject: [PATCH 24/78] constexpr kinds for DataType --- src/atlas/array/DataType.h | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/atlas/array/DataType.h b/src/atlas/array/DataType.h index 016adcb59..9bcd8e396 100644 --- a/src/atlas/array/DataType.h +++ b/src/atlas/array/DataType.h @@ -43,12 +43,12 @@ 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; + 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(); @@ -61,9 +61,9 @@ class DataType { static DataType uint64() { return DataType(KIND_UINT64); } template - static kind_t kind(); + static constexpr kind_t kind(); template - static kind_t kind(const DATATYPE&); + static constexpr kind_t kind(const DATATYPE&); template static std::string str(); @@ -213,103 +213,103 @@ inline std::string DataType::str(const double&) { return str(); } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(std::byte) == 1, ""); return KIND_BYTE; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(std::byte) == 1, ""); return KIND_BYTE; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(int) == 4, ""); return KIND_INT32; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(int) == 4, ""); return KIND_INT32; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(long) == 8, ""); return KIND_INT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(long) == 8, ""); return KIND_INT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(long long) == 8, ""); return KIND_INT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(long long) == 8, ""); return KIND_INT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(unsigned long) == 8, ""); return KIND_UINT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(unsigned long) == 8, ""); return KIND_UINT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(unsigned long long) == 8, ""); return KIND_UINT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(unsigned long long) == 8, ""); return KIND_UINT64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(float) == 4, ""); return KIND_REAL32; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(float) == 4, ""); return KIND_REAL32; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(double) == 8, ""); return KIND_REAL64; } template <> -inline DataType::kind_t DataType::kind() { +inline constexpr DataType::kind_t DataType::kind() { static_assert(sizeof(double) == 8, ""); return KIND_REAL64; } template <> -inline DataType::kind_t DataType::kind(const int&) { +inline constexpr DataType::kind_t DataType::kind(const int&) { return kind(); } template <> -inline DataType::kind_t DataType::kind(const long&) { +inline constexpr DataType::kind_t DataType::kind(const long&) { return kind(); } template <> -inline DataType::kind_t DataType::kind(const unsigned long&) { +inline constexpr DataType::kind_t DataType::kind(const unsigned long&) { return kind(); } template <> -inline DataType::kind_t DataType::kind(const float&) { +inline constexpr DataType::kind_t DataType::kind(const float&) { return kind(); } template <> -inline DataType::kind_t DataType::kind(const double&) { +inline constexpr DataType::kind_t DataType::kind(const double&) { return kind(); } From a748fe7a9c5f446e8076070a0420c0ee3b3e4eed Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 8 May 2023 11:07:06 +0200 Subject: [PATCH 25/78] RangeAll output as ':' --- src/atlas/array/Range.h | 6 ++++++ 1 file changed, 6 insertions(+) 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 {}; From 38f027906b2c71dd39f0688216e07e794ef70dc9 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 8 May 2023 11:10:34 +0200 Subject: [PATCH 26/78] Fix LocalView indexing bug for non-contiguous slices --- src/atlas/array/LocalView.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index a420e4854..5b88c2e2b 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -184,13 +184,13 @@ class LocalView { template typename std::enable_if<(Rank == 1 && EnableBool), const value_type&>::type operator[](Int 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) { check_bounds(idx); - return data_[idx]; + return data_[index(idx)]; } idx_t size() const { return size_; } @@ -263,7 +263,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 From 4ecb0c8e7005e0f8016b5c7ffe36da0666c703dd Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 8 May 2023 11:11:56 +0200 Subject: [PATCH 27/78] Improve array::Reference - It now also has functions like rank(), stride(), shape() - Conversion operators to POD references --- src/atlas/array/helpers/ArraySlicer.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 From 624e759986327d601c84a818c1cf28423926000b Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 9 May 2023 14:24:08 +0200 Subject: [PATCH 28/78] Modernise LocalView --- src/atlas/array/LocalView.h | 69 ++++++++++--------------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index 5b88c2e2b..a355d7568 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -17,6 +17,7 @@ #include #include +#include #include "atlas/array/ArrayDataStore.h" #include "atlas/array/ArrayViewDefs.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,9 +115,8 @@ class LocalView { } } - - ENABLE_IF_CONST_WITH_NON_CONST(value_type) - 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]; @@ -130,35 +125,8 @@ class LocalView { } } - 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]; - } - } - - - 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 { @@ -181,14 +149,14 @@ class LocalView { return data_[index(idx...)]; } - template - typename std::enable_if<(Rank == 1 && EnableBool), const value_type&>::type operator[](Int idx) const { + template + typename std::enable_if_t operator[](Int idx) const { check_bounds(idx); return data_[index(idx)]; } - template - typename std::enable_if<(Rank == 1 && EnableBool), value_type&>::type operator[](Int idx) { + template + typename std::enable_if_t operator[](Int idx) { check_bounds(idx); return data_[index(idx)]; } @@ -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_; } @@ -291,11 +259,12 @@ 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 From 97d8d97840ae0ea2b88e7befaa533aa26570b70d Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 9 May 2023 15:14:35 +0200 Subject: [PATCH 29/78] Modernise NativeArrayView --- src/atlas/array/native/NativeArrayView.h | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/atlas/array/native/NativeArrayView.h b/src/atlas/array/native/NativeArrayView.h index 6466f23f4..55bdbd30c 100644 --- a/src/atlas/array/native/NativeArrayView.h +++ b/src/atlas/array/native/NativeArrayView.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,7 +153,6 @@ 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,...) @@ -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 + typename std::enable_if::type operator[](Int idx) const { #else // Doxygen API is cleaner! template @@ -193,8 +187,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), value_type&>::type operator[](Int idx) { + template + typename std::enable_if::type operator[](Int idx) { #else // Doxygen API is cleaner! template @@ -356,6 +350,8 @@ class ArrayView { // -- Private data + template friend class ArrayView; + value_type* data_; size_t size_; std::array shape_; From a126b901e27ed54af48c317e0120f230772f9f2b Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 11 May 2023 11:10:29 +0200 Subject: [PATCH 30/78] Fix compilation with GridToolsStorage. GridToolsIndexView was missing shape() function. --- .../array/gridtools/GridToolsIndexView.cc | 122 ++++++++++++++++++ .../array/gridtools/GridToolsIndexView.h | 27 +++- .../array/gridtools/GridToolsMakeView.cc | 36 ++++-- 3 files changed, 165 insertions(+), 20 deletions(-) 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 From 695bc1871d375193bfa14afc59fa881cfd63c482 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 11 May 2023 15:57:31 +0200 Subject: [PATCH 31/78] Add CI build config --- .github/ci-config.yml | 6 ++++++ .github/ci-hpc-config.yml | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 .github/ci-config.yml create mode 100644 .github/ci-hpc-config.yml diff --git a/.github/ci-config.yml b/.github/ci-config.yml new file mode 100644 index 000000000..d09b5fa0d --- /dev/null +++ b/.github/ci-config.yml @@ -0,0 +1,6 @@ +self_coverage: true +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..31a57ad89 --- /dev/null +++ b/.github/ci-hpc-config.yml @@ -0,0 +1,9 @@ +build: + modules: + - ecbuild + - ninja + modules_package: + - atlas:fftw,eigen + dependencies: + - ecmwf/eckit@develop + parallel: 64 From e74203618e7c0b425539ac396eabfb7d88a57405 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 11 May 2023 17:59:43 +0200 Subject: [PATCH 32/78] Remove self coverage input --- .github/ci-config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ci-config.yml b/.github/ci-config.yml index d09b5fa0d..004e8ab62 100644 --- a/.github/ci-config.yml +++ b/.github/ci-config.yml @@ -1,4 +1,3 @@ -self_coverage: true dependencies: | ecmwf/ecbuild ecmwf/eckit From df864bdd0e0b578058b878093e997995d1d3a629 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 15 May 2023 11:19:34 +0200 Subject: [PATCH 33/78] Make cxx_std_17 feature PUBLIC --- src/atlas/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 599b5a378..e109d067e 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -982,4 +982,4 @@ ecbuild_add_library( TARGET atlas ) -target_compile_features( atlas PUBLIC cxx_std_11 ) +target_compile_features( atlas PUBLIC cxx_std_17 ) From ad55a1a052b26ef617e705e43064d93c47543795 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 12 May 2023 09:59:53 +0200 Subject: [PATCH 34/78] Create alias array::View := array::LocalView --- src/atlas/array/LocalView.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index a355d7568..32d448911 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -270,5 +270,8 @@ class LocalView { #undef ENABLE_IF_CONST_WITH_NON_CONST }; +template +using View = LocalView; + } // namespace array } // namespace atlas From b2871fce24ff7306bf97e7efbdf6e400d5d5772e Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 15 May 2023 16:20:35 +0200 Subject: [PATCH 35/78] Disable ArrayView / LocalView indexing operator rather than triggering static_assert to enable detection with std::is_invocable --- src/atlas/array/LocalView.h | 16 ++++++++-------- src/atlas/array/native/NativeArrayView.h | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/atlas/array/LocalView.h b/src/atlas/array/LocalView.h index 32d448911..0adae4277 100644 --- a/src/atlas/array/LocalView.h +++ b/src/atlas/array/LocalView.h @@ -137,26 +137,26 @@ 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_t operator[](Int idx) const { + template > + const value_type& operator[](Idx idx) const { check_bounds(idx); return data_[index(idx)]; } - template - typename std::enable_if_t operator[](Int idx) { + template > + value_type& operator[](Idx idx) { check_bounds(idx); return data_[index(idx)]; } diff --git a/src/atlas/array/native/NativeArrayView.h b/src/atlas/array/native/NativeArrayView.h index 55bdbd30c..ac0324959 100644 --- a/src/atlas/array/native/NativeArrayView.h +++ b/src/atlas/array/native/NativeArrayView.h @@ -156,15 +156,15 @@ class ArrayView { // -- 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...)]; } @@ -172,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::type operator[](Int idx) const { + template > + const value_type& operator[](Idx idx) const { #else // Doxygen API is cleaner! template @@ -187,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::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]]; From b47da73891bd05f81b6c78adb21539a5da1d0134 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 8 May 2023 12:09:48 +0200 Subject: [PATCH 36/78] Updates to ArrayForEach - It is now impossible to have non-const references for slice views - Better error message in case arguments don't match function signature --- src/atlas/array/helpers/ArrayForEach.h | 231 ++++++++++++++++--------- src/tests/array/test_array_foreach.cc | 112 ++++++------ 2 files changed, 203 insertions(+), 140 deletions(-) diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h index 429d472d2..27df2109c 100644 --- a/src/atlas/array/helpers/ArrayForEach.h +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -15,6 +15,7 @@ #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 { @@ -51,10 +52,14 @@ 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(); + return policy_name>(); } // Type check for execution policy (Not in C++ standard) @@ -66,6 +71,29 @@ constexpr auto is_execution_policy() { return 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 { @@ -74,12 +102,12 @@ namespace option { template util::Config execution_policy() { - return util::Config("execution_policy", execution::policy_name()); + return util::Config("execution_policy", execution::policy_name>()); } template util::Config execution_policy(T) { - return execution_policy(); + return execution_policy>(); } } // namespace option @@ -89,26 +117,22 @@ namespace helpers { namespace detail { -template -struct IsTupleImpl : std::false_type {}; - -template -struct IsTupleImpl> : std::true_type {}; +struct NoMask { + template + constexpr bool operator()(Args...) const { return 0; } +}; -template -constexpr auto is_tuple() { - return IsTupleImpl::value; -} +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 +template void forEach(idx_t idxMax, const Functor& functor) { - if constexpr(std::is_same_v) { + if constexpr(execution::is_omp_policy()) { atlas_omp_parallel_for(auto idx = idx_t{}; idx < idxMax; ++idx) { functor(idx); } @@ -136,25 +160,26 @@ template auto makeSlices(const std::tuple& slicerArgs, ArrayViewTuple&& arrayViews) { - if constexpr(ViewIdx < std::tuple_size_v>) { + constexpr auto nb_views = std::tuple_size_v; - auto&& arrayView = std::get(arrayViews); - using ArrayView = std::decay_t; + auto&& arrayView = std::get(arrayViews); + using ArrayView = std::decay_t; - constexpr auto Dim = sizeof...(SlicerArgs); - constexpr auto Rank = ArrayView::rank(); - const auto paddedArgs = - std::tuple_cat(slicerArgs, argPadding()); + constexpr auto Dim = sizeof...(SlicerArgs); + constexpr auto Rank = ArrayView::rank(); + const auto paddedArgs = std::tuple_cat(slicerArgs, argPadding()); - const auto slicer = [&arrayView](const auto&... args) { - return std::make_tuple(arrayView.slice(args...)); - }; + const auto slicer = [&arrayView](const auto&... args) { + return std::make_tuple(arrayView.slice(args...)); + }; - return std::tuple_cat(std::apply(slicer, paddedArgs), - makeSlices(slicerArgs, std::forward(arrayViews))); + if constexpr (ViewIdx == nb_views-1) { + return std::apply(slicer, paddedArgs); } else { - return std::make_tuple(); + // recurse + return std::tuple_cat(std::apply(slicer, paddedArgs), + makeSlices(slicerArgs, std::forward(arrayViews))); } } @@ -170,9 +195,6 @@ struct ArrayForEachImpl { const Function& function, const std::tuple& slicerArgs, const std::tuple& maskArgs) { - - using namespace detail; - // Iterate over this dimension. if constexpr(Dim == ItrDim) { @@ -180,21 +202,11 @@ struct ArrayForEachImpl { const auto idxMax = std::get<0>(arrayViews).shape(ItrDim); forEach(idxMax, [&](idx_t idx) { - - // Decay from parallel_unsequenced to unsequenced policy - if constexpr(std::is_same_v) { - ArrayForEachImpl::apply( + // 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)); - } - else { - // Retain current execution policy. - ArrayForEachImpl::apply( - std::forward(arrayViews), mask, function, - tuplePushBack(slicerArgs, idx), - tuplePushBack(maskArgs, idx)); - } }); } // Add a RangeAll to arguments. @@ -205,8 +217,42 @@ struct ArrayForEachImpl { maskArgs); } } + + template + static void apply(ArrayViewTuple&& arrayViews, + const Function& function, + const std::tuple& slicerArgs) { + // 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), function, + tuplePushBack(slicerArgs, idx)); + }); + } + // Add a RangeAll to arguments. + else { + ArrayForEachImpl::apply( + std::forward(arrayViews), function, + tuplePushBack(slicerArgs, Range::all())); + } + } }; +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 { const Function& function, const std::tuple& slicerArgs, const std::tuple& maskArgs) { - - // Skip iteration if mask evaluates to true. - if (std::apply(mask, maskArgs)) { - return; + ATLAS_ASSERT((std::is_invocable_v)); + if constexpr (std::is_invocable_v) { + if (std::apply(mask, maskArgs)) { + return; + } } + apply( std::forward(arrayViews), function, slicerArgs); + } + template + static void apply(ArrayViewTuple&& arrayViews, + const Function& function, + const std::tuple& slicerArgs) { auto slices = makeSlices(slicerArgs, std::forward(arrayViews)); - std::apply(function, slices); + + 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)" ); + if constexpr (applicable) { + std::apply(function, std::move(slices)); + } } + }; } // namespace detail @@ -251,28 +311,33 @@ struct ArrayForEach { /// sequential (row-major) order. /// Note: The lowest ArrayView.rank() must be greater than or equal /// to the highest dim in ItrDims. TODO: static checking for this. - template ()>> + template static void apply(const eckit::Parametrisation& conf, - ArrayViewTuple&& arrayViews, + std::tuple&& arrayViews, const Mask& mask, const Function& function) { auto execute = [&](auto execution_policy) { - apply(execution_policy, std::forward(arrayViews), mask, function); + apply(execution_policy, std::move(arrayViews), mask, function); }; using namespace execution; std::string execution_policy; - if( conf.get("execution_policy",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)) { + } + else if (execution_policy == policy_name(par)) { execute(par); - } else if (execution_policy == policy_name(unseq)) { + } + else if (execution_policy == policy_name(unseq)) { execute(unseq); - } else if (execution_policy == policy_name(seq)) { + } + else if (execution_policy == policy_name(seq)) { execute(seq); } + else { + throw_Exception("Unrecognized execution policy "+execution_policy, Here()); + } } else { execute(par_unseq); @@ -282,52 +347,50 @@ struct ArrayForEach { /// brief Apply "For-Each" method. /// /// details As above, but Execution policy is determined at compile-time. - template ()>, - typename = std::enable_if_t()>> - static void apply(ExecutionPolicy, ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function) { - - detail::ArrayForEachImpl::apply( - std::forward(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); + template ()>> + static void apply(ExecutionPolicy, std::tuple&& arrayViews, const Mask& mask, const Function& function) { + if constexpr (std::is_same_v,detail::NoMask>) { + detail::ArrayForEachImpl::apply( + std::move(arrayViews), function, std::make_tuple()); + } + else { + detail::ArrayForEachImpl::apply( + std::move(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); + } } /// brief Apply "For-Each" method /// - /// detials Apply ForEach with default execution policy. - template ()>> - static void apply(ArrayViewTuple&& arrayViews, const Mask& mask, const Function& function) { - apply(std::forward(arrayViews), mask, function); + /// 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 /// - /// detials Apply ForEach with run-time determined execution policy and no mask. - template ()>> - static void apply(const eckit::Parametrisation& conf, ArrayViewTuple&& arrayViews, const Function& function) { - constexpr auto no_mask = [](auto args...) { return 0; }; - apply(conf, std::forward(arrayViews), no_mask, function); + /// 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 /// - /// detials Apply ForEach with compile-time determined execution policy and no mask. - template ()>, + /// details Apply ForEach with compile-time determined execution policy and no mask. + template ()>> - static void apply(ExecutionPolicy executionPolicy, ArrayViewTuple&& arrayViews, const Function& function) { - constexpr auto no_mask = [](auto args...) { return 0; }; - apply(executionPolicy, std::forward(arrayViews), no_mask, function); + 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 /// - /// detials Apply ForEach with default execution policy and no mask. - template ()>> - static void apply(ArrayViewTuple arrayViews, const Function& function) { - apply(execution::par_unseq, std::forward(arrayViews), function); + /// 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); } }; diff --git a/src/tests/array/test_array_foreach.cc b/src/tests/array/test_array_foreach.cc index 0d14d8e60..f8c5b0b4a 100644 --- a/src/tests/array/test_array_foreach.cc +++ b/src/tests/array/test_array_foreach.cc @@ -29,21 +29,21 @@ CASE("test_array_foreach_1_view") { // Test slice shapes. - const auto loopFunctorDim0 = [](auto& slice) { - EXPECT_EQUAL(slice.rank(), 1); - EXPECT_EQUAL(slice.shape(0), 3); + 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_EQUAL(slice.rank(), 1); - EXPECT_EQUAL(slice.shape(0), 2); + 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) { + const auto loopFunctorDimAll = [](auto&& slice) { static_assert(std::is_convertible_v); }; ArrayForEach<0, 1>::apply(std::tie(view), loopFunctorDimAll); @@ -55,12 +55,12 @@ CASE("test_array_foreach_1_view") { ghostView.assign({0, 1}); auto count = int {}; - const auto countNonGhosts = [&count](auto&...) { ++count; }; + 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&...) { + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; @@ -78,29 +78,29 @@ CASE("test_array_foreach_2_views") { // Test slice shapes. - const auto loopFunctorDim0 = [](auto& slice1, auto& slice2) { - EXPECT_EQUAL(slice1.rank(), 1); - EXPECT_EQUAL(slice1.shape(0), 3); + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 3); - EXPECT_EQUAL(slice2.rank(), 2); - EXPECT_EQUAL(slice2.shape(0), 3); - EXPECT_EQUAL(slice2.shape(1), 4); + 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_EQUAL(slice1.rank(), 1); - EXPECT_EQUAL(slice1.shape(0), 2); + const auto loopFunctorDim1 = [](auto&& slice1, auto&& slice2) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 2); - EXPECT_EQUAL(slice2.rank(), 2); - EXPECT_EQUAL(slice2.shape(0), 2); - EXPECT_EQUAL(slice2.shape(1), 4); + 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) { + const auto loopFunctorDimAll = [](auto&& slice2) { static_assert(std::is_convertible_v); }; ArrayForEach<0, 1, 2>::apply(std::tie(view2), loopFunctorDimAll); @@ -112,12 +112,12 @@ CASE("test_array_foreach_2_views") { ghostView.assign({0, 1}); auto count = int {}; - const auto countNonGhosts = [&count](auto&...) { ++count; }; + 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&...) { + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; @@ -142,39 +142,39 @@ CASE("test_array_foreach_3_views") { // Test slice shapes. - const auto loopFunctorDim0 = [](auto& slice1, auto& slice2, auto& slice3) { - EXPECT_EQUAL(slice1.rank(), 1); - EXPECT_EQUAL(slice1.shape(0), 3); + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2, auto&& slice3) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 3); - EXPECT_EQUAL(slice2.rank(), 2); - EXPECT_EQUAL(slice2.shape(0), 3); - EXPECT_EQUAL(slice2.shape(1), 4); + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 3); + EXPECT_EQ(slice2.shape(1), 4); - EXPECT_EQUAL(slice3.rank(), 3); - EXPECT_EQUAL(slice3.shape(0), 3); - EXPECT_EQUAL(slice3.shape(1), 4); - EXPECT_EQUAL(slice3.shape(2), 5); + 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_EQUAL(slice1.rank(), 1); - EXPECT_EQUAL(slice1.shape(0), 2); + const auto loopFunctorDim1 = [](auto&& slice1, auto&& slice2, auto&& slice3) { + EXPECT_EQ(slice1.rank(), 1); + EXPECT_EQ(slice1.shape(0), 2); - EXPECT_EQUAL(slice2.rank(), 2); - EXPECT_EQUAL(slice2.shape(0), 2); - EXPECT_EQUAL(slice2.shape(1), 4); + EXPECT_EQ(slice2.rank(), 2); + EXPECT_EQ(slice2.shape(0), 2); + EXPECT_EQ(slice2.shape(1), 4); - EXPECT_EQUAL(slice3.rank(), 3); - EXPECT_EQUAL(slice3.shape(0), 2); - EXPECT_EQUAL(slice3.shape(1), 4); - EXPECT_EQUAL(slice3.shape(2), 5); + 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) { + const auto loopFunctorDimAll = [](auto&& slice3) { static_assert(std::is_convertible_v); }; ArrayForEach<0, 1, 2, 3>::apply(std::tie(view3), loopFunctorDimAll); @@ -186,12 +186,12 @@ CASE("test_array_foreach_3_views") { ghostView.assign({0, 1}); auto count = int {}; - const auto countNonGhosts = [&count](auto&...) { ++count; }; + 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&...) { + const auto ghostWrap = [&ghostView](idx_t idx, auto&&...) { // Wrap ghostView to use correct number of indices. return ghostView(idx); }; @@ -216,7 +216,7 @@ CASE("test_array_foreach_forwarding") { auto arr2 = ArrayT(2, 3, 4); auto view2 = make_view(arr2); - const auto loopFunctorDim0 = [](auto& slice1, auto& slice2) { + const auto loopFunctorDim0 = [](auto&& slice1, auto&& slice2) { EXPECT_EQUAL(slice1.rank(), 1); EXPECT_EQUAL(slice1.shape(0), 3); @@ -246,12 +246,12 @@ CASE("test_array_foreach_data_integrity") { static_cast(arr2.data())[idx] = idx; } - const auto scaleDataDim0 = [](auto& slice1, auto& slice2) { + const auto scaleDataDim0 = [](auto&& slice1, auto&& slice2) { static_assert(std::is_convertible_v); slice1 *= 2.; - const auto scaleDataDim1 = [](auto& slice) { + const auto scaleDataDim1 = [](auto&& slice) { static_assert(std::is_convertible_v); slice *= 3.; @@ -325,8 +325,8 @@ CASE("test_array_foreach_performance") { static_cast(arr3.data())[idx] = 3 * idx + 1; } - const auto add = [](double& __restrict__ a1, const double& __restrict__ a2, - const double& __restrict__ a3) { a1 = a2 + a3; }; + 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); }; @@ -362,7 +362,7 @@ CASE("test_array_foreach_performance") { }; const auto forEachCol = [&](const auto& operation) { - const auto function = [&](auto& slice1, auto& slice2, auto& slice3) { + 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)); @@ -372,7 +372,7 @@ CASE("test_array_foreach_performance") { }; const auto forEachLevel = [&](const auto& operation) { - const auto function = [&](auto& slice1, auto& slice2, auto& slice3) { + 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)); @@ -386,14 +386,14 @@ CASE("test_array_foreach_performance") { }; const auto forEachNested = [&](const auto& operation) { - const auto function = [&](auto& slice1, auto& slice2, auto& slice3) { + 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) { + 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); From 5f542427658010ad82b79fbafc167deb489546f3 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 10 May 2023 12:36:01 +0200 Subject: [PATCH 37/78] Change default execution_policy in ArrayForEach from par_unseq to seq It is safer to assume no parallelisation or unsequenced visits --- src/atlas/array/helpers/ArrayForEach.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h index 27df2109c..81c4a5cf6 100644 --- a/src/atlas/array/helpers/ArrayForEach.h +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -305,10 +305,10 @@ struct ArrayForEach { /// 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" = - /// "parallel_unsequenced_policy" (default) the first loop is executed - /// using OpenMP. The remaining loops are executed in serial. When - /// "execution_policy" = "sequenced_policy", all loops are executed in - /// sequential (row-major) order. + /// "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 @@ -340,7 +340,7 @@ struct ArrayForEach { } } else { - execute(par_unseq); + execute(seq); } } From 923943f8bd6b0e907e81c67bf18d1c6e92b7b298 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 15 May 2023 17:06:08 +0200 Subject: [PATCH 38/78] Add function Field::horizontal_dimension() -> std::vector --- src/atlas/field/Field.cc | 8 ++++++++ src/atlas/field/Field.h | 3 +++ src/atlas/field/detail/FieldImpl.h | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/src/atlas/field/Field.cc b/src/atlas/field/Field.cc index 0ce02a2ff..e80e87b95 100644 --- a/src/atlas/field/Field.cc +++ b/src/atlas/field/Field.cc @@ -177,6 +177,14 @@ 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) { get()->set_functionspace(functionspace); } diff --git a/src/atlas/field/Field.h b/src/atlas/field/Field.h index 2c5bba45d..cde616ecb 100644 --- a/src/atlas/field/Field.h +++ b/src/atlas/field/Field.h @@ -168,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/detail/FieldImpl.h b/src/atlas/field/detail/FieldImpl.h index 8beca8a46..a17da998c 100644 --- a/src/atlas/field/detail/FieldImpl.h +++ b/src/atlas/field/detail/FieldImpl.h @@ -149,6 +149,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; From ab63920691882e5d9dea930ce263fff88539d6b5 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 15 May 2023 17:41:57 +0200 Subject: [PATCH 39/78] Setup horizontal_dimensions() for BlockStructuredColumns fields --- src/atlas/functionspace/detail/BlockStructuredColumns.cc | 7 +++++++ src/tests/functionspace/test_blockstructuredcolumns.cc | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/atlas/functionspace/detail/BlockStructuredColumns.cc b/src/atlas/functionspace/detail/BlockStructuredColumns.cc index a291e0294..6041ca1cc 100644 --- a/src/atlas/functionspace/detail/BlockStructuredColumns.cc +++ b/src/atlas/functionspace/detail/BlockStructuredColumns.cc @@ -264,6 +264,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/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); From 43c22a9b85511a5480a06bf0d58d225c465a51e4 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 16 May 2023 14:01:56 +0200 Subject: [PATCH 40/78] Add gnu-7 ci to github actions with github-hosted runners (#136) --- .github/workflows/build.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f61efce08..f10c834fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,7 @@ jobs: build_type: [Release,Debug] name: - linux gnu-10 + - linux gnu-7 - linux clang-12 - linux nvhpc-21.9 - linux intel @@ -46,6 +47,16 @@ jobs: compiler_cxx: g++-10 compiler_fc: gfortran-10 caching: true + coverage: true + + - 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 +65,7 @@ jobs: compiler_cxx: clang++-12 compiler_fc: gfortran-10 caching: true + coverage: false - name: linux clang-12 build_type: Release @@ -64,6 +76,7 @@ jobs: compiler_fc: gfortran-10 ctest_options: "-LE mpi" # For now until Checkerboard fixed caching: true + coverage: false - name: linux nvhpc-21.9 os: ubuntu-20.04 @@ -74,6 +87,7 @@ jobs: 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 +96,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 +106,7 @@ jobs: compiler_cxx: ~ compiler_fc: gfortran-11 caching: true + coverage: false cmake_options: -DMPI_SLOTS=4 runs-on: ${{ matrix.os }} @@ -120,6 +136,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 @@ -189,7 +208,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 }} From 10cad2a1050799a415a5a0b05907084ba27e386e Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 19 May 2023 15:45:06 +0200 Subject: [PATCH 41/78] Add gnu-12 ci to github actions (github-hosted runners) --- .github/workflows/build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f10c834fd..b9f2c0598 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,7 @@ jobs: build_type: [Release,Debug] name: - linux gnu-10 + - linux gnu-12 - linux gnu-7 - linux clang-12 - linux nvhpc-21.9 @@ -49,6 +50,15 @@ jobs: 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 + caching: true + coverage: false + - name: linux gnu-7 os: ubuntu-20.04 compiler: gnu-7 From 4943b57f5afa5af391c2ce2dba4aecde11832488 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Mon, 22 May 2023 09:18:16 +0200 Subject: [PATCH 42/78] Disable GHA "linux gnu-12" OpenMP for CXX as "omp.h" header is not found :( --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9f2c0598..942d57356 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,6 +56,7 @@ jobs: compiler_cc: gcc-12 compiler_cxx: g++-12 compiler_fc: gfortran-12 + cmake_options: -DENABLE_OMP_CXX=OFF caching: true coverage: false From 1859b46337b51ee38523f325a3e9f059fb3a052e Mon Sep 17 00:00:00 2001 From: Francois Hebert Date: Mon, 22 May 2023 02:24:26 -0600 Subject: [PATCH 43/78] Add function to build mesh from imported connectivity data (#135) * Add function to build mesh from imported connectivity data * Review: set remote_index for nodes, but not cells * Review: pass connectivities via std library containers * Update copyright to UCAR * Move shared test code into helper file * Clean output filename * Clean documentation * Clean inconsistent argument name * Update src/atlas/mesh/BuildMeshFromConnectivities.cc Co-authored-by: Willem Deconinck * Code review: correct use of remote_index * Code review: change API to class with op() * Code review: update filenames to better match new class name * Code review: move new code to mesh namespace --- src/atlas/CMakeLists.txt | 2 + src/atlas/mesh/MeshBuilder.cc | 144 +++++++++++++++ src/atlas/mesh/MeshBuilder.h | 75 ++++++++ src/tests/mesh/CMakeLists.txt | 14 ++ src/tests/mesh/helper_mesh_builder.h | 83 +++++++++ src/tests/mesh/test_mesh_builder.cc | 150 ++++++++++++++++ src/tests/mesh/test_mesh_builder_parallel.cc | 173 +++++++++++++++++++ 7 files changed, 641 insertions(+) create mode 100644 src/atlas/mesh/MeshBuilder.cc create mode 100644 src/atlas/mesh/MeshBuilder.h create mode 100644 src/tests/mesh/helper_mesh_builder.h create mode 100644 src/tests/mesh/test_mesh_builder.cc create mode 100644 src/tests/mesh/test_mesh_builder_parallel.cc diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index e109d067e..a67246e14 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -323,6 +323,8 @@ 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/PartitionPolygon.cc 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/tests/mesh/CMakeLists.txt b/src/tests/mesh/CMakeLists.txt index 7a495d050..46b387c70 100644 --- a/src/tests/mesh/CMakeLists.txt +++ b/src/tests/mesh/CMakeLists.txt @@ -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 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/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); +} From 448cf08b9bf804c88a60a68375a391b39ed0de5b Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 25 May 2023 13:50:31 +0200 Subject: [PATCH 44/78] Add nvidia compiler specific HPC build config --- .github/ci-hpc-config.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 31a57ad89..29d1d94b2 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -7,3 +7,14 @@ build: dependencies: - ecmwf/eckit@develop parallel: 64 + +nvidia-22.11: + build: + modules: + - ecbuild + - ninja + modules_package: + - atlas:fftw + dependencies: + - ecmwf/eckit@develop + parallel: 64 From a31a598a99655df3ba7c723c5a696925babe8ba3 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 25 May 2023 15:02:30 +0200 Subject: [PATCH 45/78] Disable floating point signals for tests on nvidia --- .github/ci-hpc-config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 29d1d94b2..12b022355 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -18,3 +18,5 @@ nvidia-22.11: dependencies: - ecmwf/eckit@develop parallel: 64 + env: + - ATLAS_FPE=0 From b7906f0987535d6628957cb05c1e752622c08f29 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 25 May 2023 15:55:57 +0200 Subject: [PATCH 46/78] Use Eigen 3.4 --- .github/ci-hpc-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 12b022355..b6dcae9c4 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -14,7 +14,7 @@ nvidia-22.11: - ecbuild - ninja modules_package: - - atlas:fftw + - atlas:fftw,eigen/3.4.0 dependencies: - ecmwf/eckit@develop parallel: 64 From d69e61aca4fb0a6f976e6a429c34ea7fe471f5b2 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 31 May 2023 13:39:05 +0200 Subject: [PATCH 47/78] Fix bug where DelaunayMeshGenerator with 1 partition was not setting the grid in the mesh (#143) --- src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc index 1343d6e9f..641c07694 100644 --- a/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/DelaunayMeshGenerator.cc @@ -101,6 +101,7 @@ void DelaunayMeshGenerator::generate(const Grid& grid, const grid::Distribution& if( dist.nb_partitions() == 1 ) { build_global_mesh(mesh); + setGrid(mesh, grid, dist.type()); return; } From e2255a08efd4c39505e9c81e79a06a1d08e22799 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 31 May 2023 07:43:29 +0000 Subject: [PATCH 48/78] Avoid and suppress some compiler warnings with nvhpc --- cmake/atlas_compile_flags.cmake | 5 +++++ src/atlas/interpolation/Vector2D.h | 22 ++++++++++++++++++- src/atlas/interpolation/Vector3D.h | 22 ++++++++++++++++++- .../detail/VariableResolutionProjection.cc | 8 ------- src/tests/trans/test_transgeneral.cc | 14 ++++++------ 5 files changed, 54 insertions(+), 17 deletions(-) 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/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/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/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 From 3b82a74ff6ef28ba2c5c09ac986c73c77b7994e9 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 31 May 2023 09:18:09 +0000 Subject: [PATCH 49/78] Update GHA nvidia-21.9 to nvidia-22.11 --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 942d57356..3953d4140 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: - linux gnu-12 - linux gnu-7 - linux clang-12 - - linux nvhpc-21.9 + - linux nvhpc-22.11 - linux intel - macos @@ -89,9 +89,9 @@ jobs: caching: true coverage: false - - name: linux nvhpc-21.9 + - name: linux nvhpc-22.11 os: ubuntu-20.04 - compiler: nvhpc-21.9 + compiler: nvhpc-22.11 compiler_cc: nvc compiler_cxx: nvc++ compiler_fc: nvfortran @@ -168,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 From 9a2def8efbbe273974232b3e6c54e1ab806ae325 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 1 Jun 2023 08:16:54 +0000 Subject: [PATCH 50/78] Workaround compiler bug in nvhpc-22.11-release build --- src/atlas/array/ArraySpec.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atlas/array/ArraySpec.cc b/src/atlas/array/ArraySpec.cc index c521e7a05..074c23074 100644 --- a/src/atlas/array/ArraySpec.cc +++ b/src/atlas/array/ArraySpec.cc @@ -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 From e2b598b4d7f856574f5c9162c79ebb242f06bd64 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Thu, 1 Jun 2023 17:41:19 +0200 Subject: [PATCH 51/78] Add HPC build options matrix --- .github/ci-hpc-config.yml | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index b6dcae9c4..830907725 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -1,20 +1,29 @@ -build: - modules: - - ecbuild - - ninja - modules_package: - - atlas:fftw,eigen - dependencies: - - ecmwf/eckit@develop - parallel: 64 +matrix: + - mpi_on + - mpi_off -nvidia-22.11: +mpi_on: build: modules: - ecbuild - ninja + - openmpi modules_package: - - atlas:fftw,eigen/3.4.0 + - atlas:fftw,eigen + dependencies: + - ecmwf/eckit@develop + parallel: 64 + ntasks: 16 + env: + - ATLAS_FPE=0 + +mpi_off: + build: + modules: + - ecbuild + - ninja + modules_package: + - atlas:fftw,eigen dependencies: - ecmwf/eckit@develop parallel: 64 From 8cbf2436e0e770b476f36d98e5a12ec416f71eb2 Mon Sep 17 00:00:00 2001 From: Dusan Figala Date: Fri, 2 Jun 2023 11:38:16 +0200 Subject: [PATCH 52/78] Optimize modules and dependencies for caching --- .github/ci-hpc-config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 830907725..5cef5e027 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -5,12 +5,12 @@ matrix: mpi_on: build: modules: - - ecbuild - ninja - - openmpi modules_package: - - atlas:fftw,eigen + - atlas:fftw,eigen,openmpi + - eckit:openmpi dependencies: + - ecmwf/ecbuild@develop - ecmwf/eckit@develop parallel: 64 ntasks: 16 @@ -20,11 +20,11 @@ mpi_on: mpi_off: build: modules: - - ecbuild - ninja modules_package: - atlas:fftw,eigen dependencies: + - ecmwf/ecbuild@develop - ecmwf/eckit@develop parallel: 64 env: From 14c53b2677718e5ac0e00547e66111e6578a4e4a Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 2 Jun 2023 12:24:44 +0000 Subject: [PATCH 53/78] Avoid harmless FE_DIVBYZERO with nvhpc build --- src/atlas/grid/detail/spacing/gaussian/Latitudes.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc b/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc index 0f9d003c0..06e030008 100644 --- a/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc +++ b/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc @@ -111,7 +111,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 +135,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; From 84d45e0e55ad3a45932a1257c1f494409cca323d Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 2 Jun 2023 12:27:09 +0000 Subject: [PATCH 54/78] Remove ATLAS_FPE=0 environment variable as not needed anymore now --- .github/ci-hpc-config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 5cef5e027..eab4a6cd2 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -14,8 +14,6 @@ mpi_on: - ecmwf/eckit@develop parallel: 64 ntasks: 16 - env: - - ATLAS_FPE=0 mpi_off: build: @@ -27,5 +25,3 @@ mpi_off: - ecmwf/ecbuild@develop - ecmwf/eckit@develop parallel: 64 - env: - - ATLAS_FPE=0 From 6d4a61d835ea41a637889ce5b811eab71a68055e Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 2 Jun 2023 15:12:30 +0000 Subject: [PATCH 55/78] Avoid harmless FE_INVALID with nvhpc build --- src/atlas/mesh/actions/WriteLoadBalanceReport.cc | 6 ++++++ 1 file changed, 6 insertions(+) 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(); From 650b293e0df8c2011a0a34233cf6e64d9c300ca3 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Fri, 2 Jun 2023 16:47:00 +0000 Subject: [PATCH 56/78] Avoid harmless FE_INVALID with nvhpc build --- .../method/structured/kernels/CubicHorizontalKernel.h | 4 ++-- src/tests/interpolation/CubicInterpolationPrototype.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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 From d0babe3b2e620eb69e1207f05811c9b3b884ab85 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 6 Jun 2023 10:01:52 +0200 Subject: [PATCH 57/78] Implement field::for_each capabilities (#139) * Implement field::for_each_value and field::for_each_column * Workaround for GCC bug 64095. --------- Co-authored-by: odlomax --- src/atlas/array/helpers/ArrayForEach.h | 67 +-- src/atlas/field/detail/for_each.h | 249 ++++++++++ src/atlas/field/for_each.h | 460 ++++++++++++++++++ src/tests/field/CMakeLists.txt | 7 + src/tests/field/test_field_foreach.cc | 633 +++++++++++++++++++++++++ 5 files changed, 1370 insertions(+), 46 deletions(-) create mode 100644 src/atlas/field/detail/for_each.h create mode 100644 src/atlas/field/for_each.h create mode 100644 src/tests/field/test_field_foreach.cc diff --git a/src/atlas/array/helpers/ArrayForEach.h b/src/atlas/array/helpers/ArrayForEach.h index 81c4a5cf6..38d4d6441 100644 --- a/src/atlas/array/helpers/ArrayForEach.h +++ b/src/atlas/array/helpers/ArrayForEach.h @@ -167,6 +167,8 @@ auto makeSlices(const std::tuple& slicerArgs, 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) { @@ -217,31 +219,6 @@ struct ArrayForEachImpl { maskArgs); } } - - template - static void apply(ArrayViewTuple&& arrayViews, - const Function& function, - const std::tuple& slicerArgs) { - // 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), function, - tuplePushBack(slicerArgs, idx)); - }); - } - // Add a RangeAll to arguments. - else { - ArrayForEachImpl::apply( - std::forward(arrayViews), function, - tuplePushBack(slicerArgs, Range::all())); - } - } }; template @@ -255,6 +232,7 @@ inline constexpr bool is_applicable_v = is_applicable::value; template struct ArrayForEachImpl { + template static void apply(ArrayViewTuple&& arrayViews, @@ -262,27 +240,30 @@ struct ArrayForEachImpl { const Function& function, const std::tuple& slicerArgs, const std::tuple& maskArgs) { - ATLAS_ASSERT((std::is_invocable_v)); - if constexpr (std::is_invocable_v) { - if (std::apply(mask, maskArgs)) { - return; - } + + 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; + } + } - apply( std::forward(arrayViews), function, slicerArgs); - } - template - static void apply(ArrayViewTuple&& arrayViews, - const Function& function, - const std::tuple& slicerArgs) { 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)" ); - if constexpr (applicable) { - std::apply(function, std::move(slices)); - } + std::apply(function, std::move(slices)); } }; @@ -350,14 +331,8 @@ struct ArrayForEach { template ()>> static void apply(ExecutionPolicy, std::tuple&& arrayViews, const Mask& mask, const Function& function) { - if constexpr (std::is_same_v,detail::NoMask>) { detail::ArrayForEachImpl::apply( - std::move(arrayViews), function, std::make_tuple()); - } - else { - detail::ArrayForEachImpl::apply( - std::move(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); - } + std::move(arrayViews), mask, function, std::make_tuple(), std::make_tuple()); } /// brief Apply "For-Each" method 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/tests/field/CMakeLists.txt b/src/tests/field/CMakeLists.txt index 93e025b24..e830bd84b 100644 --- a/src/tests/field/CMakeLists.txt +++ b/src/tests/field/CMakeLists.txt @@ -12,6 +12,13 @@ ecbuild_add_test( TARGET atlas_test_field_missingvalue 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/test_field_foreach.cc b/src/tests/field/test_field_foreach.cc new file mode 100644 index 000000000..2aee44bcb --- /dev/null +++ b/src/tests/field/test_field_foreach.cc @@ -0,0 +1,633 @@ +/* + * (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 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; diff --git a/src/atlas/field/FieldSet.h b/src/atlas/field/FieldSet.h index 4bf5e1ff3..83bfc1f15 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -24,6 +24,7 @@ #include "eckit/deprecated.h" +#include "atlas/array_fwd.h" #include "atlas/field/Field.h" #include "atlas/library/config.h" #include "atlas/runtime/Exception.h" @@ -139,6 +140,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); } 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/detail/BlockStructuredColumns.cc b/src/atlas/functionspace/detail/BlockStructuredColumns.cc index 6041ca1cc..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()) { diff --git a/src/atlas/functionspace/detail/StructuredColumns.h b/src/atlas/functionspace/detail/StructuredColumns.h index 392ed577c..8385a5777 100644 --- a/src/atlas/functionspace/detail/StructuredColumns.h +++ b/src/atlas/functionspace/detail/StructuredColumns.h @@ -317,6 +317,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_f/CMakeLists.txt b/src/atlas_f/CMakeLists.txt index f015676a5..29d55ab99 100644 --- a/src/atlas_f/CMakeLists.txt +++ b/src/atlas_f/CMakeLists.txt @@ -175,7 +175,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 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/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/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/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 From 0e00ad91bd8fdaaf1268619260c8488fa2626381 Mon Sep 17 00:00:00 2001 From: Francois Hebert Date: Tue, 6 Jun 2023 06:49:53 -0600 Subject: [PATCH 59/78] Enable latitude normalisation in KDTree coordinate transform (#140) * Enable latitude normalisation in coordinate transform * Add eckit compatibility guard --------- Co-authored-by: Willem Deconinck --- src/atlas/util/Geometry.cc | 17 +++++++++++++++++ src/atlas/util/Geometry.h | 8 ++------ 2 files changed, 19 insertions(+), 6 deletions(-) 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_; } From e4324c8cc4ac9e0e31d583dcc70eca16892c57af Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 8 Jun 2023 18:08:52 +0100 Subject: [PATCH 60/78] Access to atlas::Library::dataPath without initialising --- src/atlas/library/Library.cc | 3 +-- src/atlas/library/Library.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/atlas/library/Library.cc b/src/atlas/library/Library.cc index 86c7f1075..ff8ab5ff2 100644 --- a/src/atlas/library/Library.cc +++ b/src/atlas/library/Library.cc @@ -173,7 +173,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 +182,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 { 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_; }; From 9c54fab67362f984cdb5393a7dae700bdee8728b Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 15 Jun 2023 16:45:55 +0200 Subject: [PATCH 61/78] Fix and test some use cases with / without calling atlas::initialise() --- atlas_io/src/atlas_io/Trace.h | 2 ++ src/atlas/library/FloatingPointExceptions.cc | 20 +++++++++-- src/atlas/library/Library.cc | 12 +++++-- src/tests/runtime/CMakeLists.txt | 8 +++++ src/tests/runtime/test_library.cc | 19 ++++++++++ .../runtime/test_library_init_nofinal.cc | 23 ++++++++++++ src/tests/runtime/test_library_noargs.cc | 31 ++++++++++++++++ .../runtime/test_library_noinit_final.cc | 35 +++++++++++++++++++ 8 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/tests/runtime/test_library.cc create mode 100644 src/tests/runtime/test_library_init_nofinal.cc create mode 100644 src/tests/runtime/test_library_noargs.cc create mode 100644 src/tests/runtime/test_library_noinit_final.cc 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/src/atlas/library/FloatingPointExceptions.cc b/src/atlas/library/FloatingPointExceptions.cc index 654c34bfb..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" @@ -273,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; @@ -350,7 +353,20 @@ bool disable_floating_point_exception(const std::string& floating_point_exceptio 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/Library.cc b/src/atlas/library/Library.cc index ff8ab5ff2..4ae993f28 100644 --- a/src/atlas/library/Library.cc +++ b/src/atlas/library/Library.cc @@ -114,7 +114,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 +152,8 @@ 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()) {} void Library::registerPlugin(eckit::system::Plugin& plugin) { plugins_.push_back(&plugin); @@ -253,6 +254,8 @@ void Library::initialise(int argc, char** argv) { void Library::initialise(const eckit::Parametrisation& config) { + ATLAS_ASSERT(eckit::Main::ready()); + if (initialized_) { return; } @@ -338,7 +341,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; 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; +} From a33bd611e6833fd3dd1ba8dfd24801956022ada0 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 15 Jun 2023 15:26:41 +0200 Subject: [PATCH 62/78] atlas_io: fix test command in standalone build --- atlas_io/tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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} ) From aec10997ceeef155b668fe92284069ee9bf616b0 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 22 Jun 2023 11:22:06 +0200 Subject: [PATCH 63/78] Disable GHA "nvhpc-22.11" as there are random compilation errors, probably related to insufficient disk or memory space --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3953d4140..f51e1f050 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: - linux gnu-12 - linux gnu-7 - linux clang-12 - - linux nvhpc-22.11 +# - linux nvhpc-22.11 - linux intel - macos @@ -89,16 +89,16 @@ jobs: caching: true 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 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 From c938f1089788feb32d3e7cbf4af4ba50bdfb123d Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 10:46:30 +0200 Subject: [PATCH 64/78] Introduce atlas::mdspan, contributed from github.com/kokkos/mdspan --- src/atlas/CMakeLists.txt | 2 + src/atlas/util/detail/mdspan/LICENSE | 238 ++ src/atlas/util/detail/mdspan/README | 9 + src/atlas/util/detail/mdspan/mdspan.hpp | 4725 +++++++++++++++++++++++ src/atlas/util/mdspan.h | 4 + 5 files changed, 4978 insertions(+) create mode 100644 src/atlas/util/detail/mdspan/LICENSE create mode 100644 src/atlas/util/detail/mdspan/README create mode 100644 src/atlas/util/detail/mdspan/mdspan.hpp create mode 100644 src/atlas/util/mdspan.h diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index a67246e14..858f58ac0 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -792,6 +792,8 @@ util/Unique.h util/Unique.cc util/Allocate.h util/Allocate.cc +util/mdspan.h +util/detail/mdspan/mdspan.hpp ) list( APPEND atlas_io_srcs 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" + From 77d25a2e0f9bec6a62779fcc3755aaba32b8a1ea Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 11:23:37 +0200 Subject: [PATCH 65/78] Move files to correct places --- src/atlas/CMakeLists.txt | 5 +- src/atlas/array/DataType.h | 402 +---------------- .../{util => library}/detail/BlackMagic.h | 0 src/atlas/{util => library}/detail/Debug.h | 0 src/atlas/option/Options.cc | 6 +- src/atlas/option/Options.h | 9 +- src/atlas/parallel/mpi/Statistics.h | 2 +- src/atlas/runtime/Exception.h | 2 +- src/atlas/runtime/Log.h | 2 +- src/atlas/runtime/Trace.h | 2 +- src/atlas/{array => util}/DataType.cc | 0 src/atlas/util/DataType.h | 420 ++++++++++++++++++ src/tests/array/CMakeLists.txt | 5 + src/tests/{util => array}/test_indexview.cc | 0 src/tests/grid/test_field.cc | 4 +- src/tests/mesh/CMakeLists.txt | 9 + src/tests/{util => mesh}/test_footprint.cc | 2 +- src/tests/util/CMakeLists.txt | 2 +- 18 files changed, 453 insertions(+), 419 deletions(-) rename src/atlas/{util => library}/detail/BlackMagic.h (100%) rename src/atlas/{util => library}/detail/Debug.h (100%) rename src/atlas/{array => util}/DataType.cc (100%) create mode 100644 src/atlas/util/DataType.h rename src/tests/{util => array}/test_indexview.cc (100%) rename src/tests/{util => mesh}/test_footprint.cc (98%) diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 858f58ac0..a79b4590e 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -670,7 +670,6 @@ array/ArrayStrides.h array/ArrayView.h array/ArrayViewUtil.h array/ArrayViewDefs.h -array/DataType.cc array/DataType.h array/IndexView.h array/LocalView.cc @@ -733,6 +732,8 @@ util/Config.h util/Constants.h util/ConvexSphericalPolygon.cc util/ConvexSphericalPolygon.h +util/DataType.cc +util/DataType.h util/Earth.h util/GaussianLatitudes.cc util/GaussianLatitudes.h @@ -756,9 +757,7 @@ util/SphericalPolygon.h util/UnitSphere.h util/vector.h 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 diff --git a/src/atlas/array/DataType.h b/src/atlas/array/DataType.h index 9bcd8e396..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 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 -} // namespace atlas 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/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/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/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/tests/array/CMakeLists.txt b/src/tests/array/CMakeLists.txt index 9d39f6ea1..3a5583223 100644 --- a/src/tests/array/CMakeLists.txt +++ b/src/tests/array/CMakeLists.txt @@ -51,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/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/grid/test_field.cc b/src/tests/grid/test_field.cc index c7189f6b5..a9607c582 100644 --- a/src/tests/grid/test_field.cc +++ b/src/tests/grid/test_field.cc @@ -164,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/mesh/CMakeLists.txt b/src/tests/mesh/CMakeLists.txt index 46b387c70..e0b04c366 100644 --- a/src/tests/mesh/CMakeLists.txt +++ b/src/tests/mesh/CMakeLists.txt @@ -144,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 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/util/CMakeLists.txt b/src/tests/util/CMakeLists.txt index 6cf229e8e..52ef6fb18 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 From c07caa2c6859baba06a1a099143e88a80f227ea1 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 11:58:37 +0200 Subject: [PATCH 66/78] CMake cleanup --- src/atlas/CMakeLists.txt | 341 +++++++++++++++------------------- src/tests/grid/CMakeLists.txt | 14 +- 2 files changed, 158 insertions(+), 197 deletions(-) diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index a79b4590e..40e9572d4 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 + +) + +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_TRANS ) -list( APPEND atlas_grid_srcs +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 @@ -327,6 +353,7 @@ 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 @@ -335,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 @@ -482,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 @@ -540,20 +597,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 @@ -562,12 +605,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 @@ -619,7 +656,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 @@ -715,28 +754,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 @@ -756,6 +809,8 @@ 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/Cache.h util/detail/KDTree.h @@ -769,104 +824,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 -util/mdspan.h -util/detail/mdspan/mdspan.hpp -) - -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 @@ -897,20 +854,20 @@ endif() list( APPEND source_list ${atlas_srcs} - ${atlas_array_srcs} + ${atlas_util_srcs} ${atlas_grid_srcs} + ${atlas_array_srcs} + ${atlas_linalg_srcs} + ${atlas_grid_partitioning_srcs} ${atlas_mesh_srcs} ${atlas_field_srcs} + ${atlas_parallel_srcs} ${atlas_functionspace_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 ) diff --git a/src/tests/grid/CMakeLists.txt b/src/tests/grid/CMakeLists.txt index 3fd83edd2..161186bcb 100644 --- a/src/tests/grid/CMakeLists.txt +++ b/src/tests/grid/CMakeLists.txt @@ -22,20 +22,24 @@ 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() +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() set( _WITH_MPI ) From c839689873ed362bc7d36807d70ed406a68a07dd Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 12:28:58 +0200 Subject: [PATCH 67/78] Remove interdependencies between structural components --- src/atlas/array/Array.h | 3 +- src/atlas/functionspace/Spectral.cc | 13 ------ src/atlas/functionspace/Spectral.h | 2 - .../functionspace/detail/StructuredColumns.h | 3 +- .../detail/StructuredColumns_setup.cc | 7 ++++ src/atlas/grid/StructuredPartitionPolygon.h | 4 +- src/atlas/grid/Vertical.cc | 10 ----- src/atlas/grid/Vertical.h | 4 -- src/atlas/grid/detail/grid/CubedSphere.h | 1 - src/atlas/grid/detail/grid/Unstructured.cc | 36 ++++++++-------- src/atlas/grid/detail/grid/Unstructured.h | 18 +++++--- .../grid/detail/spacing/gaussian/Latitudes.cc | 7 ---- .../grid/detail/vertical/VerticalInterface.cc | 8 +++- src/atlas/library/config.h | 1 - .../detail/CubedSphereEquiAnglProjection.h | 1 - .../detail/CubedSphereEquiDistProjection.h | 1 - src/atlas/util/Polygon.cc | 14 +++---- src/atlas/util/Polygon.h | 42 ++++++++++++------- src/atlas/util/PolygonXY.cc | 4 +- .../grid/test_stretchedrotatedgaussian.cc | 1 - src/tests/output/test_pointcloud_io.cc | 18 ++++---- 21 files changed, 94 insertions(+), 104 deletions(-) diff --git a/src/atlas/array/Array.h b/src/atlas/array/Array.h index 86a1ded72..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/ArrayDataStore.h" #include "atlas/array/DataType.h" #include "atlas/array_fwd.h" -#include "atlas/library/config.h" namespace atlas { namespace array { 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/StructuredColumns.h b/src/atlas/functionspace/detail/StructuredColumns.h index 8385a5777..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_; 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/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/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/spacing/gaussian/Latitudes.cc b/src/atlas/grid/detail/spacing/gaussian/Latitudes.cc index 06e030008..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 { 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/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/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/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/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/output/test_pointcloud_io.cc b/src/tests/output/test_pointcloud_io.cc index 44f99f3ee..1ce5d9c2a 100644 --- a/src/tests/output/test_pointcloud_io.cc +++ b/src/tests/output/test_pointcloud_io.cc @@ -34,6 +34,10 @@ namespace { +auto make_mdspan(const atlas::Field& xy) { + return atlas::mdspan{ xy.array().host_data(), xy.shape(0), 2 }; +} + namespace test_arrays { const size_t nb_pts = 5; @@ -121,7 +125,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 +141,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 +154,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 +167,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 +314,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 +355,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); From 8845dea0c6ef097e37ca52fd5175a80bec4c5a7c Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 12:03:09 +0200 Subject: [PATCH 68/78] Introduce features to disable compilation of structural components --- CMakeLists.txt | 14 ++++++++++ cmake/features/FFTW.cmake | 1 + cmake/features/FORTRAN.cmake | 5 ++-- cmake/features/TESSELATION.cmake | 1 + cmake/features/TRANS.cmake | 5 ++-- src/apps/CMakeLists.txt | 3 ++- src/atlas/CMakeLists.txt | 31 +++++++++++++++++------ src/atlas/library/defines.h.in | 1 + src/tests/CMakeLists.txt | 31 +++++++++++++---------- src/tests/acceptance_tests/CMakeLists.txt | 4 ++- src/tests/field/test_field_foreach.cc | 12 ++++++--- src/tests/grid/CMakeLists.txt | 5 ++++ src/tests/interpolation/CMakeLists.txt | 16 +++++++----- src/tests/linalg/CMakeLists.txt | 3 +++ src/tests/mesh/CMakeLists.txt | 3 ++- src/tests/numerics/CMakeLists.txt | 2 ++ src/tests/output/test_pointcloud_io.cc | 5 ++-- src/tests/projection/CMakeLists.txt | 7 +++-- src/tests/redistribution/CMakeLists.txt | 3 +++ src/tests/trans/CMakeLists.txt | 1 + 20 files changed, 111 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d44f51625..9626fa298 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,20 @@ 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 grid related features" ) + +ecbuild_add_option( FEATURE ATLAS_FUNCTIONSPACE + DESCRIPTION "Build Array related features" + CONDITION atlas_HAVE_ATLAS_GRID AND atlas_HAVE_ATLAS_FIELD ) + +ecbuild_add_option( FEATURE ATLAS_NUMERICS + DESCRIPTION "Build numerics related features" + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) + include( features/BOUNDSCHECKING ) include( features/FORTRAN ) include( features/MPI ) diff --git a/cmake/features/FFTW.cmake b/cmake/features/FFTW.cmake index a913d38c0..dabf61beb 100644 --- a/cmake/features/FFTW.cmake +++ b/cmake/features/FFTW.cmake @@ -2,6 +2,7 @@ ecbuild_add_option( FEATURE FFTW DESCRIPTION "Support for fftw" + CONDITION atlas_HAVE_ATLAS_NUMERICS REQUIRED_PACKAGES "FFTW COMPONENTS double QUIET" ) if( NOT HAVE_FFTW ) diff --git a/cmake/features/FORTRAN.cmake b/cmake/features/FORTRAN.cmake index 835574dd3..24a4af782 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_NUMERICS ) 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/TESSELATION.cmake b/cmake/features/TESSELATION.cmake index 12b4ebe1c..3da03d1bc 100644 --- a/cmake/features/TESSELATION.cmake +++ b/cmake/features/TESSELATION.cmake @@ -4,6 +4,7 @@ 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" ) diff --git a/cmake/features/TRANS.cmake b/cmake/features/TRANS.cmake index 4136d0f26..dab9839ef 100644 --- a/cmake/features/TRANS.cmake +++ b/cmake/features/TRANS.cmake @@ -1,7 +1,7 @@ ### trans ... set( atlas_HAVE_ECTRANS 0 ) -if( ENABLE_TRANS OR NOT DEFINED ENABLE_TRANS ) +if( atlas_HAVE_ATLAS_NUMERICS AND (ENABLE_TRANS OR NOT DEFINED ENABLE_TRANS) ) find_package( ectrans 1.1 COMPONENTS transi double QUIET ) if( TARGET transi_dp ) set( transi_FOUND TRUE ) @@ -17,7 +17,6 @@ if( ENABLE_TRANS OR NOT DEFINED ENABLE_TRANS ) find_package( transi 0.8 QUIET ) endif() endif() - ecbuild_add_option( FEATURE TRANS DESCRIPTION "Support for IFS spectral transforms" - CONDITION transi_FOUND ) + CONDITION atlas_HAVE_ATLAS_NUMERICS AND transi_FOUND ) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index b7cebeec8..a976d0041 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -15,7 +15,8 @@ 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 diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 40e9572d4..d79a3ad71 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -834,24 +834,39 @@ 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_parallel_srcs ) + unset( atlas_output_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_NUMERICS ) + unset( atlas_linalg_srcs ) # only depends on array unset( atlas_interpolation_srcs ) unset( atlas_redistribution_srcs ) unset( atlas_numerics_srcs ) - unset( atlas_output_srcs ) - unset( atlas_util_srcs ) - unset( atlas_internals_srcs ) endif() +# +# atlas_src _________ io_adaptor +# | / +# array -------------- linalg +# | +# util +# | +# grid +# | +# mesh + field + functionspace + parallel +# _____________________________________________________________________________ +# | | | | +# interpolation redistribution output numerics list( APPEND source_list ${atlas_srcs} ${atlas_util_srcs} diff --git a/src/atlas/library/defines.h.in b/src/atlas/library/defines.h.in index 5056fafc8..46c948615 100644 --- a/src/atlas/library/defines.h.in +++ b/src/atlas/library/defines.h.in @@ -39,6 +39,7 @@ #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_HAVE_FUNCTIONSPACE @atlas_HAVE_ATLAS_FUNCTIONSPACE@ #ifdef __CUDACC__ #define ATLAS_HOST_DEVICE __host__ __device__ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 469aa77ab..6be56f59e 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -108,23 +108,28 @@ 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_FIELD) + add_subdirectory( acc ) + add_subdirectory( array ) +endif() + +if (atlas_HAVE_ATLAS_FUNCTIONSPACE) + add_subdirectory( parallel ) + add_subdirectory( field ) + 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 ) +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..e0f933278 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 ) @@ -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/field/test_field_foreach.cc b/src/tests/field/test_field_foreach.cc index 2aee44bcb..8187df9ae 100644 --- a/src/tests/field/test_field_foreach.cc +++ b/src/tests/field/test_field_foreach.cc @@ -133,7 +133,9 @@ CASE( "test field::for_each_column" ) { 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(), xy.shape(0), 2 }; +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 { 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..3f9c6f60a 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_NUMERICS ) + 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/trans/CMakeLists.txt b/src/tests/trans/CMakeLists.txt index 7d0fd0d0f..c593d0a81 100644 --- a/src/tests/trans/CMakeLists.txt +++ b/src/tests/trans/CMakeLists.txt @@ -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_NUMERICS ) From d6060c852d64093abc05466687b5fca8e1d373eb Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 21 Jun 2023 12:26:46 +0200 Subject: [PATCH 69/78] Field with functionspace optional --- src/apps/CMakeLists.txt | 6 ++- src/atlas/CMakeLists.txt | 3 +- src/atlas/field/Field.cc | 14 ++++++ src/atlas/field/detail/FieldImpl.cc | 58 ++++++++++++++++++------ src/atlas/field/detail/FieldInterface.cc | 19 +++++++- src/tests/CMakeLists.txt | 9 ++-- src/tests/util/CMakeLists.txt | 1 + 7 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index a976d0041..2e8eb08ac 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -21,9 +21,11 @@ ecbuild_add_executable( 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/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index d79a3ad71..6910dd435 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -841,7 +841,6 @@ endif() 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_parallel_srcs ) unset( atlas_output_srcs ) @@ -872,10 +871,10 @@ list( APPEND source_list ${atlas_util_srcs} ${atlas_grid_srcs} ${atlas_array_srcs} + ${atlas_field_srcs} ${atlas_linalg_srcs} ${atlas_grid_partitioning_srcs} ${atlas_mesh_srcs} - ${atlas_field_srcs} ${atlas_parallel_srcs} ${atlas_functionspace_srcs} ${atlas_interpolation_srcs} diff --git a/src/atlas/field/Field.cc b/src/atlas/field/Field.cc index e80e87b95..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 { @@ -186,10 +192,18 @@ std::vector Field::horizontal_dimension() const { } 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/detail/FieldImpl.cc b/src/atlas/field/detail/FieldImpl.cc index d4a56ad08..0ec15afdd 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); @@ -164,27 +181,40 @@ 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(); - - ATLAS_ASSERT(functionspace()); - functionspace().adjointHaloExchange(Field(this), on_device); - } +#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 } // ------------------------------------------------------------------ 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/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 6be56f59e..0feb545b2 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -110,17 +110,20 @@ endif() add_subdirectory( util ) add_subdirectory( runtime ) -add_subdirectory( projection ) -add_subdirectory( grid ) + +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( field ) add_subdirectory( mesh ) add_subdirectory( functionspace ) add_subdirectory( io ) diff --git a/src/tests/util/CMakeLists.txt b/src/tests/util/CMakeLists.txt index 52ef6fb18..0566bd3f9 100644 --- a/src/tests/util/CMakeLists.txt +++ b/src/tests/util/CMakeLists.txt @@ -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 From 7a8e767123d0cde9e757c695610f9c51c666b9c3 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 22 Jun 2023 11:15:43 +0200 Subject: [PATCH 70/78] Further separate compilation features --- CMakeLists.txt | 14 +++- cmake/atlas-import.cmake.in | 4 +- cmake/features/ACC.cmake | 7 ++ cmake/features/ECTRANS.cmake | 35 ++++++++++ cmake/features/EIGEN.cmake | 7 ++ cmake/features/FFTW.cmake | 5 +- cmake/features/FORTRAN.cmake | 2 +- cmake/features/GRIDTOOLS_STORAGE.cmake | 9 +++ cmake/features/PROJ.cmake | 4 ++ cmake/features/TESSELATION.cmake | 2 + cmake/features/TRANS.cmake | 22 ------ .../project_hello_world/CMakeLists.txt | 2 +- src/CMakeLists.txt | 12 +++- src/atlas/CMakeLists.txt | 61 ++++++++-------- src/atlas/library/Library.cc | 4 +- src/atlas/library/defines.h.in | 4 +- src/atlas_f/CMakeLists.txt | 69 ++++++++++++++----- src/atlas_f/atlas_f.h.in | 5 +- src/atlas_f/atlas_module.F90 | 6 ++ .../atlas_functionspace_Spectral_module.F90 | 10 ++- src/tests/CMakeLists.txt | 14 +++- src/tests/acceptance_tests/CMakeLists.txt | 2 +- src/tests/functionspace/CMakeLists.txt | 2 +- src/tests/interpolation/CMakeLists.txt | 2 +- src/tests/linalg/CMakeLists.txt | 2 +- src/tests/mesh/CMakeLists.txt | 4 +- src/tests/numerics/CMakeLists.txt | 2 - src/tests/redistribution/CMakeLists.txt | 2 +- src/tests/trans/CMakeLists.txt | 14 ++-- 29 files changed, 222 insertions(+), 106 deletions(-) create mode 100644 cmake/features/ECTRANS.cmake delete mode 100644 cmake/features/TRANS.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9626fa298..a7d1b4afe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,12 +42,20 @@ ecbuild_add_option( FEATURE ATLAS_GRID DESCRIPTION "Build grid related features" ) ecbuild_add_option( FEATURE ATLAS_FIELD - DESCRIPTION "Build grid related features" ) + DESCRIPTION "Build field and memory management related features" ) ecbuild_add_option( FEATURE ATLAS_FUNCTIONSPACE - DESCRIPTION "Build Array related features" + 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 ) @@ -57,7 +65,7 @@ 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/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/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 dabf61beb..14bf0dd64 100644 --- a/cmake/features/FFTW.cmake +++ b/cmake/features/FFTW.cmake @@ -1,11 +1,14 @@ ### FFTW ... +if( atlas_HAVE_ATLAS_TRANS ) + ecbuild_add_option( FEATURE FFTW DESCRIPTION "Support for fftw" - CONDITION atlas_HAVE_ATLAS_NUMERICS REQUIRED_PACKAGES "FFTW COMPONENTS double QUIET" ) 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 24a4af782..e27b63076 100644 --- a/cmake/features/FORTRAN.cmake +++ b/cmake/features/FORTRAN.cmake @@ -3,7 +3,7 @@ ecbuild_add_option( FEATURE FORTRAN DESCRIPTION "Provide Fortran bindings" REQUIRED_PACKAGES "fckit VERSION 0.6.2 COMPONENTS ECKIT" - CONDITION atlas_HAVE_ATLAS_NUMERICS ) + CONDITION atlas_HAVE_ATLAS_FUNCTIONSPACE ) if( atlas_HAVE_FORTRAN ) 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 3da03d1bc..face9c0e4 100644 --- a/cmake/features/TESSELATION.cmake +++ b/cmake/features/TESSELATION.cmake @@ -1,3 +1,4 @@ +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) ### tesselation ... set(Boost_USE_MULTITHREADED ON ) @@ -30,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 dab9839ef..000000000 --- a/cmake/features/TRANS.cmake +++ /dev/null @@ -1,22 +0,0 @@ -### trans ... - -set( atlas_HAVE_ECTRANS 0 ) -if( atlas_HAVE_ATLAS_NUMERICS AND (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 atlas_HAVE_ATLAS_NUMERICS AND transi_FOUND ) 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..49fcd4f28 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 ) diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index 6910dd435..2f2fc213c 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -312,7 +312,7 @@ grid/detail/partitioner/SerialPartitioner.cc grid/detail/partitioner/SerialPartitioner.h ) -if( atlas_HAVE_TRANS ) +if( atlas_HAVE_ECTRANS ) list( APPEND atlas_grid_partitioning_srcs grid/detail/partitioner/TransPartitioner.h grid/detail/partitioner/TransPartitioner.cc @@ -550,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 @@ -574,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 @@ -844,28 +847,35 @@ if( NOT atlas_HAVE_ATLAS_FUNCTIONSPACE ) unset( atlas_functionspace_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_NUMERICS ) - unset( atlas_linalg_srcs ) # only depends on array +if( NOT atlas_HAVE_ATLAS_INTERPOLATION ) unset( atlas_interpolation_srcs ) - unset( atlas_redistribution_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_TRANS ) + unset( atlas_trans_srcs ) +endif() + +if( NOT atlas_HAVE_ATLAS_NUMERICS ) unset( atlas_numerics_srcs ) endif() # -# atlas_src _________ io_adaptor -# | / +# atlas_src _________ io_adaptor +# | / # array -------------- linalg -# | -# util -# | -# grid -# | -# mesh + field + functionspace + parallel -# _____________________________________________________________________________ -# | | | | -# interpolation redistribution output numerics +# | \________________________field +# util / +# | / +# grid / ( optional link ) +# | / +# mesh + functionspace + parallel +# ___________________________________________________________________ +# | | | | | +# trans interpolation output numerics redistribution list( APPEND source_list ${atlas_srcs} ${atlas_util_srcs} @@ -877,6 +887,7 @@ list( APPEND source_list ${atlas_mesh_srcs} ${atlas_parallel_srcs} ${atlas_functionspace_srcs} + ${atlas_trans_srcs} ${atlas_interpolation_srcs} ${atlas_redistribution_srcs} ${atlas_numerics_srcs} @@ -899,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 @@ -926,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} diff --git a/src/atlas/library/Library.cc b/src/atlas/library/Library.cc index 4ae993f28..ee491ca62 100644 --- a/src/atlas/library/Library.cc +++ b/src/atlas/library/Library.cc @@ -454,7 +454,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); @@ -478,7 +478,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/defines.h.in b/src/atlas/library/defines.h.in index 46c948615..632b5ba19 100644 --- a/src/atlas/library/defines.h.in +++ b/src/atlas/library/defines.h.in @@ -32,8 +32,8 @@ #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@ diff --git a/src/atlas_f/CMakeLists.txt b/src/atlas_f/CMakeLists.txt index 29d55ab99..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 @@ -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/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/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 0feb545b2..767432d99 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -128,11 +128,21 @@ if (atlas_HAVE_ATLAS_FUNCTIONSPACE) add_subdirectory( functionspace ) add_subdirectory( io ) add_subdirectory( output ) - add_subdirectory( numerics ) + add_subdirectory( redistribution ) +endif() + +if (atlas_HAVE_ATLAS_TRANS) add_subdirectory( trans ) +endif() + +if (atlas_HAVE_ATLAS_INTERPOLATION) add_subdirectory( interpolation ) add_subdirectory( linalg ) - add_subdirectory( redistribution ) 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 e0f933278..af3d19e10 100644 --- a/src/tests/acceptance_tests/CMakeLists.txt +++ b/src/tests/acceptance_tests/CMakeLists.txt @@ -24,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() diff --git a/src/tests/functionspace/CMakeLists.txt b/src/tests/functionspace/CMakeLists.txt index b97869098..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() diff --git a/src/tests/interpolation/CMakeLists.txt b/src/tests/interpolation/CMakeLists.txt index 43e896cd7..596729209 100644 --- a/src/tests/interpolation/CMakeLists.txt +++ b/src/tests/interpolation/CMakeLists.txt @@ -25,7 +25,7 @@ ecbuild_add_test( TARGET atlas_test_Triag2D ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) -if( atlas_HAVE_ATLAS_NUMERICS ) +if( atlas_HAVE_ATLAS_INTERPOLATION ) ecbuild_add_test( TARGET atlas_test_interpolation_conservative SOURCES test_interpolation_conservative.cc diff --git a/src/tests/linalg/CMakeLists.txt b/src/tests/linalg/CMakeLists.txt index de9d93c6a..d060dcc8a 100644 --- a/src/tests/linalg/CMakeLists.txt +++ b/src/tests/linalg/CMakeLists.txt @@ -6,7 +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_NUMERICS ) +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) ecbuild_add_test( TARGET atlas_test_linalg_sparse SOURCES test_linalg_sparse.cc diff --git a/src/tests/mesh/CMakeLists.txt b/src/tests/mesh/CMakeLists.txt index c1332e784..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 AND atlas_HAVE_ATLAS_NUMERICS + 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} @@ -165,5 +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_NUMERICS + CONDITION atlas_HAVE_ATLAS_INTERPOLATION ) diff --git a/src/tests/numerics/CMakeLists.txt b/src/tests/numerics/CMakeLists.txt index ff00497e0..87d10a262 100644 --- a/src/tests/numerics/CMakeLists.txt +++ b/src/tests/numerics/CMakeLists.txt @@ -6,7 +6,6 @@ # granted to it by virtue of its status as an intergovernmental organisation nor # does it submit to any jurisdiction. -if( atlas_HAVE_ATLAS_NUMERICS ) ecbuild_add_executable( TARGET atlas_test_fvm_nabla_exe SOURCES test_fvm_nabla.cc LIBS atlas ${OMP_CXX} @@ -50,4 +49,3 @@ if( HAVE_FCTEST) ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) endif() -endif() \ No newline at end of file diff --git a/src/tests/redistribution/CMakeLists.txt b/src/tests/redistribution/CMakeLists.txt index 3f9c6f60a..0a3a5a836 100644 --- a/src/tests/redistribution/CMakeLists.txt +++ b/src/tests/redistribution/CMakeLists.txt @@ -5,7 +5,7 @@ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. # -if( atlas_HAVE_ATLAS_NUMERICS ) +if( atlas_HAVE_ATLAS_FUNCTIONSPACE ) ecbuild_add_test( TARGET atlas_test_redistribution_structured SOURCES test_redistribution_structured.cc diff --git a/src/tests/trans/CMakeLists.txt b/src/tests/trans/CMakeLists.txt index c593d0a81..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,6 +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_NUMERICS + CONDITION atlas_HAVE_ATLAS_TRANS ) From 78e798bf35a263f7261f31fccf9e209e15d7c26f Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 22 Jun 2023 16:09:13 +0200 Subject: [PATCH 71/78] Fix FieldSet::field(std::string) when field has been renamed (fixes #147) --- src/atlas/field/FieldSet.cc | 30 ++++++++++++- src/atlas/field/FieldSet.h | 20 ++++++++- src/atlas/field/detail/FieldImpl.cc | 19 ++++++++ src/atlas/field/detail/FieldImpl.h | 39 +++++++++++++++- src/tests/field/CMakeLists.txt | 6 +++ src/tests/field/test_fieldset.cc | 69 +++++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 src/tests/field/test_fieldset.cc diff --git a/src/atlas/field/FieldSet.cc b/src/atlas/field/FieldSet.cc index 01a0db916..b1c4a461e 100644 --- a/src/atlas/field/FieldSet.cc +++ b/src/atlas/field/FieldSet.cc @@ -21,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(); } @@ -38,6 +64,8 @@ Field FieldSetImpl::add(const Field& field) { index_[name.str()] = size(); } fields_.push_back(field); + + field.get()->attachObserver(field_observer_); return field; } diff --git a/src/atlas/field/FieldSet.h b/src/atlas/field/FieldSet.h index 83bfc1f15..4c7a480de 100644 --- a/src/atlas/field/FieldSet.h +++ b/src/atlas/field/FieldSet.h @@ -31,6 +31,7 @@ #include "atlas/util/Metadata.h" #include "atlas/util/Object.h" #include "atlas/util/ObjectHandle.h" +#include "atlas/field/detail/FieldImpl.h" namespace atlas { @@ -59,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(); } @@ -126,9 +141,10 @@ class FieldSetImpl : public util::Object { 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" { diff --git a/src/atlas/field/detail/FieldImpl.cc b/src/atlas/field/detail/FieldImpl.cc index 0ec15afdd..78d929f23 100644 --- a/src/atlas/field/detail/FieldImpl.cc +++ b/src/atlas/field/detail/FieldImpl.cc @@ -147,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_; @@ -217,6 +224,18 @@ void FieldImpl::adjointHaloExchange(bool on_device) const { #endif } +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 a17da998c..91de0e17a 100644 --- a/src/atlas/field/detail/FieldImpl.h +++ b/src/atlas/field/detail/FieldImpl.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_; } @@ -205,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 @@ -216,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/tests/field/CMakeLists.txt b/src/tests/field/CMakeLists.txt index e830bd84b..64a59da78 100644 --- a/src/tests/field/CMakeLists.txt +++ b/src/tests/field/CMakeLists.txt @@ -6,6 +6,12 @@ # 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 diff --git a/src/tests/field/test_fieldset.cc b/src/tests/field/test_fieldset.cc new file mode 100644 index 000000000..500256cd3 --- /dev/null +++ b/src/tests/field/test_fieldset.cc @@ -0,0 +1,69 @@ +/* + * (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 "atlas/field/Field.h" +#include "atlas/field/FieldSet.h" + +#include "tests/AtlasTestEnvironment.h" + +using namespace std; +using namespace eckit; + +//----------------------------------------------------------------------------- + +namespace atlas { +namespace test { + +//----------------------------------------------------------------------------- + +CASE("test_rename") { + FieldSet fieldset; + auto field_0 = fieldset.add(Field("0", make_datatype(), 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); +} From 5437eb2a7a43c7bff2edcfd867c3cee745ba5680 Mon Sep 17 00:00:00 2001 From: Oliver Lomax Date: Thu, 22 Jun 2023 20:08:59 +0100 Subject: [PATCH 72/78] Interpolate from cubedsphere mesh to structured columns (#146) * Added test fixture. * Added Cubedsphere to StructuredColumns interpolation test. * Added halo exchange. * Changed cubedsphere partitioner "magic number" to match interpolation default. --- .../MatchingMeshPartitionerCubedSphere.cc | 2 +- .../test_interpolation_cubedsphere.cc | 248 +++++++++++------- 2 files changed, 161 insertions(+), 89 deletions(-) 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/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 From be1ab46933b53f307563a1a87a69d1e9aa87b9bb Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 5 Jul 2023 12:13:03 +0200 Subject: [PATCH 73/78] Introduce colon-separated environment variable ATLAS_PLUGIN_PATH to simplify plugin detection --- CMakeLists.txt | 5 +++++ src/CMakeLists.txt | 6 ++++++ src/apps/atlas.cc | 14 ++++++++++++-- src/atlas/library/Library.cc | 23 ++++++++++++++++++++++- src/atlas/library/defines.h.in | 1 + 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7d1b4afe..5d458f867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,11 @@ 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 ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49fcd4f28..14a7087d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,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/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/library/Library.cc b/src/atlas/library/Library.cc index ee491ca62..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())); @@ -153,7 +159,22 @@ Library::Library(): trace_memory_(getEnv("ATLAS_TRACE_MEMORY", false)), trace_barriers_(getEnv("ATLAS_TRACE_BARRIERS", false)), trace_report_(getEnv("ATLAS_TRACE_REPORT", false)), - atlas_io_trace_hook_(::atlas::io::TraceHookRegistry::invalidId()) {} + 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); diff --git a/src/atlas/library/defines.h.in b/src/atlas/library/defines.h.in index 632b5ba19..51d55a733 100644 --- a/src/atlas/library/defines.h.in +++ b/src/atlas/library/defines.h.in @@ -39,6 +39,7 @@ #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__ From 34e22538cf8da9594b254febecb152b5f029abcd Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 5 Jul 2023 12:13:41 +0200 Subject: [PATCH 74/78] Add example-plugin in doc --- doc/example-plugin/CMakeLists.txt | 26 ++++++++++ doc/example-plugin/README.md | 41 +++++++++++++++ doc/example-plugin/VERSION | 1 + doc/example-plugin/src/CMakeLists.txt | 10 ++++ .../src/atlas-example-plugin/CMakeLists.txt | 26 ++++++++++ .../src/atlas-example-plugin/Library.cc | 50 +++++++++++++++++++ .../src/atlas-example-plugin/Library.h | 32 ++++++++++++ .../src/atlas-example-plugin/version.cc.in | 31 ++++++++++++ .../src/atlas-example-plugin/version.h | 16 ++++++ 9 files changed, 233 insertions(+) create mode 100644 doc/example-plugin/CMakeLists.txt create mode 100644 doc/example-plugin/README.md create mode 100644 doc/example-plugin/VERSION create mode 100644 doc/example-plugin/src/CMakeLists.txt create mode 100644 doc/example-plugin/src/atlas-example-plugin/CMakeLists.txt create mode 100644 doc/example-plugin/src/atlas-example-plugin/Library.cc create mode 100644 doc/example-plugin/src/atlas-example-plugin/Library.h create mode 100644 doc/example-plugin/src/atlas-example-plugin/version.cc.in create mode 100644 doc/example-plugin/src/atlas-example-plugin/version.h 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(); From 4919787172e1457cd4c612b3082744233d023473 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 17 May 2023 09:05:00 +0200 Subject: [PATCH 75/78] BuildHalo: mark interior added cells as ghost --- src/atlas/mesh/actions/BuildHalo.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atlas/mesh/actions/BuildHalo.cc b/src/atlas/mesh/actions/BuildHalo.cc index 5682d59f8..f0ed02925 100644 --- a/src/atlas/mesh/actions/BuildHalo.cc +++ b/src/atlas/mesh/actions/BuildHalo.cc @@ -752,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); } } From 3d674759b26e8ce9cfa657a308ea705aba3d0c1f Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Wed, 24 May 2023 09:45:09 +0200 Subject: [PATCH 76/78] Fix cells().global_index() metadata for RegularLonLat grids in StructuredMeshGenerator --- src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc index 8547f59f1..e73a7808a 100644 --- a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc @@ -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); } } From 419dae84d9ae56cd534d60d1cde9b47077b92fae Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 6 Jul 2023 12:55:44 +0200 Subject: [PATCH 77/78] Fix global indexing for meshes from structured grids with pole patch --- src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc index e73a7808a..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; } From 35726eea57791eecd2327767958530e62383e815 Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Thu, 6 Jul 2023 11:32:22 +0200 Subject: [PATCH 78/78] Version 0.34.0 --- AUTHORS | 20 ++++++++++++++------ CHANGELOG.md | 22 ++++++++++++++++++++++ VERSION | 2 +- tools/generate-authors.py | 4 ++++ 4 files changed, 41 insertions(+), 7 deletions(-) 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/VERSION b/VERSION index be386c9ed..85e60ed18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.33.0 +0.34.0 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]