diff --git a/CHANGELOG.md b/CHANGELOG.md index db96451fb..9cbf6840b 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.28.0] - 2021-03-02 +### Added +- Assignment of ArrayView from ArrayView +- Grid "regional_variable_resolution" via a new VariableResolutionProjection +- CubedSphereDualMeshGenerator +- VortexRollup function as analytical field for initialising data +- ConvexSphericalPolygon utility class +- Improve Projection::Jacobian +- Initial implementation for bilinear interpolation for unstructured meshes + +### Changed +- Use new eckit (1.19.0) Sparse and Dense linear algebra API +- General robustness improvements to CubedSphere to using functionspaces with various halos + +### Fixed +- Workarounds to fix compilation with Fujitsu compiler +- Workarounds to avoid Cray compiler problems with certain flag combinations +- CellColumns::haloExchange for meshes with multiple element types +- Computation of HEALPix mesh remote indices. + + ## [0.27.0] - 2021-12-03 ### Added - Adjoint interpolation with some restrictions @@ -320,6 +341,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html ## 0.13.0 - 2018-02-16 [Unreleased]: https://github.com/ecmwf/atlas/compare/master...develop +[0.28.0]: https://github.com/ecmwf/atlas/compare/0.27.0...0.28.0 [0.27.0]: https://github.com/ecmwf/atlas/compare/0.26.0...0.27.0 [0.26.0]: https://github.com/ecmwf/atlas/compare/0.25.0...0.26.0 [0.25.0]: https://github.com/ecmwf/atlas/compare/0.24.1...0.25.0 diff --git a/VERSION b/VERSION index 1b58cc101..697f087f3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.27.0 +0.28.0 diff --git a/doc/example-grids/regional_variable_resolution.yml b/doc/example-grids/regional_variable_resolution.yml new file mode 100644 index 000000000..dc4fa1c0c --- /dev/null +++ b/doc/example-grids/regional_variable_resolution.yml @@ -0,0 +1,29 @@ +# (C) Crown copyright 2022, 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. +# regional projection using variable resolution + +type : "regional_variable_resolution" +progression : 1.13 +inner : + xmin : 351.386944827586319 + ymin : -5.008754172413662 + xend : 366.363355172413776 + yend : 8.665359620690706 + dx : 0.6511482758621128 +outer : + xmin : 348.13120344827576 + xend : 369.6190965517242 + ymin : -8.264495551724226 + yend : 11.92110100000127 + dx : 1 + width: 4. + +check : + size : 1088 + lonlat(first) : [346.9838795150958504,-9.41181948490414122] + lonlat(last) : [370.7664204849036764,13.06842493318118414] + bounding_box(n,w,s,e) : [13.06842493318118414,346.9838795150958504,-9.41181948490414122,370.766420484903676] + uid : aca7861d7ddeb4f2448c57484946d44e + diff --git a/doc/example-grids/update_uid.py b/doc/example-grids/update_uid.py index 125839888..f01bc7465 100755 --- a/doc/example-grids/update_uid.py +++ b/doc/example-grids/update_uid.py @@ -16,7 +16,7 @@ def run_program_get_error(file): from subprocess import Popen, PIPE - p = Popen(command, stdout=PIPE, stderr=PIPE) + p = Popen(command, stdout=PIPE, stderr=PIPE, shell=True) output, error = p.communicate() if p.returncode != 0: return str(error.strip(),'ascii') diff --git a/src/apps/atlas-meshgen.cc b/src/apps/atlas-meshgen.cc index 1608f077b..ccc349f92 100644 --- a/src/apps/atlas-meshgen.cc +++ b/src/apps/atlas-meshgen.cc @@ -125,6 +125,7 @@ class Meshgen2Gmsh : public AtlasTool { std::string key; long halo; bool edges; + bool cells; bool brick; bool stats; bool info; @@ -165,6 +166,7 @@ Meshgen2Gmsh::Meshgen2Gmsh(int argc, char** argv): AtlasTool(argc, argv) { add_option(new Separator("Advanced")); add_option(new SimpleOption("halo", "Halo size")); add_option(new SimpleOption("edges", "Build edge datastructure")); + add_option(new SimpleOption("cells", "Build cells datastructure")); add_option(new SimpleOption("brick", "Build brick dual mesh")); add_option(new SimpleOption("stats", "Write statistics file")); add_option(new SimpleOption("info", "Write Info")); @@ -203,6 +205,8 @@ int Meshgen2Gmsh::execute(const Args& args) { edges = false; args.get("edges", edges); + cells = false; + args.get("cells", cells); stats = false; args.get("stats", stats); info = false; @@ -280,6 +284,7 @@ int Meshgen2Gmsh::execute(const Args& args) { throw; } + if (grid.projection().units() == "degrees") { functionspace::NodeColumns nodes_fs(mesh, option::halo(halo)); } @@ -299,6 +304,10 @@ int Meshgen2Gmsh::execute(const Args& args) { } } + if (cells) { + functionspace::CellColumns cells_fs(mesh, option::halo(halo)); + } + if (stats) { build_statistics(mesh); } diff --git a/src/atlas/CMakeLists.txt b/src/atlas/CMakeLists.txt index cc3ab973a..04fea0bb9 100644 --- a/src/atlas/CMakeLists.txt +++ b/src/atlas/CMakeLists.txt @@ -93,6 +93,8 @@ projection/detail/ProjectionImpl.h projection/detail/ProjectionUtilities.h projection/detail/SchmidtProjection.cc projection/detail/SchmidtProjection.h +projection/detail/VariableResolutionProjection.cc +projection/detail/VariableResolutionProjection.h domain.h domain/Domain.cc @@ -158,6 +160,8 @@ grid/detail/grid/LonLat.h grid/detail/grid/LonLat.cc grid/detail/grid/Regional.h grid/detail/grid/Regional.cc +grid/detail/grid/RegionalVariableResolution.h +grid/detail/grid/RegionalVariableResolution.cc grid/detail/grid/Healpix.h grid/detail/grid/Healpix.cc @@ -332,6 +336,8 @@ mesh/actions/ExtendNodesGlobal.cc mesh/actions/BuildDualMesh.h mesh/actions/BuildCellCentres.cc mesh/actions/BuildCellCentres.h +mesh/actions/Build2DCellCentres.cc +mesh/actions/Build2DCellCentres.h mesh/actions/BuildConvexHull3D.cc mesh/actions/BuildConvexHull3D.h mesh/actions/BuildDualMesh.cc @@ -365,6 +371,8 @@ meshgenerator/MeshGenerator.cc meshgenerator/MeshGenerator.h meshgenerator/detail/CubedSphereMeshGenerator.h meshgenerator/detail/CubedSphereMeshGenerator.cc +meshgenerator/detail/CubedSphereDualMeshGenerator.h +meshgenerator/detail/CubedSphereDualMeshGenerator.cc meshgenerator/detail/NodalCubedSphereMeshGenerator.h meshgenerator/detail/NodalCubedSphereMeshGenerator.cc meshgenerator/detail/DelaunayMeshGenerator.h @@ -525,8 +533,12 @@ 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 @@ -537,12 +549,16 @@ interpolation/method/MethodFactory.cc interpolation/method/MethodFactory.h 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/fe/FiniteElement.cc interpolation/method/fe/FiniteElement.h +interpolation/method/bil/BilinearRemapping.cc +interpolation/method/bil/BilinearRemapping.h interpolation/method/knn/GridBox.cc interpolation/method/knn/GridBox.h interpolation/method/knn/GridBoxAverage.cc @@ -650,6 +666,7 @@ array/helpers/ArrayInitializer.h array/helpers/ArrayAssigner.h array/helpers/ArrayWriter.h array/helpers/ArraySlicer.h +array/helpers/ArrayCopier.h #array/Table.h #array/Table.cc #array/TableView.h @@ -696,6 +713,8 @@ runtime/Exception.h util/Config.cc util/Config.h util/Constants.h +util/ConvexSphericalPolygon.cc +util/ConvexSphericalPolygon.h util/Earth.h util/GaussianLatitudes.cc util/GaussianLatitudes.h @@ -723,6 +742,8 @@ util/detail/BlackMagic.h util/detail/Cache.h util/detail/Debug.h util/detail/KDTree.h +util/function/VortexRollup.h +util/function/VortexRollup.cc ) list( APPEND atlas_internals_srcs diff --git a/src/atlas/array/ArrayView.h b/src/atlas/array/ArrayView.h index b927c4bac..b247fd6ab 100644 --- a/src/atlas/array/ArrayView.h +++ b/src/atlas/array/ArrayView.h @@ -31,6 +31,10 @@ namespace array { EXPLICIT_TEMPLATE_DECLARATION_TYPE_RANK(float, RANK); \ EXPLICIT_TEMPLATE_DECLARATION_TYPE_RANK(double, RANK); +// The Fujitsu compiler complains about missing references with the below +// so we just skip it in that case +#if !defined(__FUJITSU) + // For each RANK in [1..9] EXPLICIT_TEMPLATE_DECLARATION(1) EXPLICIT_TEMPLATE_DECLARATION(2) @@ -42,6 +46,8 @@ EXPLICIT_TEMPLATE_DECLARATION(7) EXPLICIT_TEMPLATE_DECLARATION(8) EXPLICIT_TEMPLATE_DECLARATION(9) +#endif + #undef EXPLICIT_TEMPLATE_DECLARATION_TYPE_RANK #undef EXPLICIT_TEMPLATE_DECLARATION diff --git a/src/atlas/array/gridtools/GridToolsArray.cc b/src/atlas/array/gridtools/GridToolsArray.cc index 55f858b11..8fbc4d5f8 100644 --- a/src/atlas/array/gridtools/GridToolsArray.cc +++ b/src/atlas/array/gridtools/GridToolsArray.cc @@ -162,49 +162,63 @@ class ArrayT_impl { case 1: return construct(shape[0]); case 2: { - if (layout[0] == 0 && layout[1] == 1) + if (layout[0] == 0 && layout[1] == 1) { return construct_with_layout<::gridtools::layout_map<0, 1>>(shape[0], shape[1]); - if (layout[0] == 1 && layout[1] == 0) + } + if (layout[0] == 1 && layout[1] == 0) { return construct_with_layout<::gridtools::layout_map<1, 0>>(shape[0], shape[1]); + } } case 3: { - if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2) + if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2) { return construct_with_layout<::gridtools::layout_map<0, 1, 2>>(shape[0], shape[1], shape[2]); - if (layout[0] == 0 && layout[1] == 2 && layout[2] == 1) + } + if (layout[0] == 0 && layout[1] == 2 && layout[2] == 1) { return construct_with_layout<::gridtools::layout_map<0, 2, 1>>(shape[0], shape[1], shape[2]); - if (layout[0] == 1 && layout[1] == 0 && layout[2] == 2) + } + if (layout[0] == 1 && layout[1] == 0 && layout[2] == 2) { return construct_with_layout<::gridtools::layout_map<1, 0, 2>>(shape[0], shape[1], shape[2]); - if (layout[0] == 1 && layout[1] == 2 && layout[2] == 0) + } + if (layout[0] == 1 && layout[1] == 2 && layout[2] == 0) { return construct_with_layout<::gridtools::layout_map<1, 2, 0>>(shape[0], shape[1], shape[2]); - if (layout[0] == 2 && layout[1] == 0 && layout[2] == 1) + } + if (layout[0] == 2 && layout[1] == 0 && layout[2] == 1) { return construct_with_layout<::gridtools::layout_map<2, 0, 1>>(shape[0], shape[1], shape[2]); - if (layout[0] == 2 && layout[1] == 1 && layout[2] == 0) + } + if (layout[0] == 2 && layout[1] == 1 && layout[2] == 0) { return construct_with_layout<::gridtools::layout_map<2, 1, 0>>(shape[0], shape[1], shape[2]); + } } case 4: { - if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3) + if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3) { return construct_with_layout<::gridtools::layout_map<0, 1, 2, 3>>(shape[0], shape[1], shape[2], shape[3]); - if (layout[0] == 3 && layout[1] == 2 && layout[2] == 1 && layout[3] == 0) + } + if (layout[0] == 3 && layout[1] == 2 && layout[2] == 1 && layout[3] == 0) { return construct_with_layout<::gridtools::layout_map<3, 2, 1, 0>>(shape[0], shape[1], shape[2], shape[3]); + } } case 5: { - if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3 && layout[4] == 4) + if (layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3 && layout[4] == 4) { return construct_with_layout<::gridtools::layout_map<0, 1, 2, 3, 4>>(shape[0], shape[1], shape[2], shape[3], shape[4]); - if (layout[0] == 4 && layout[1] == 3 && layout[2] == 2 && layout[3] == 1 && layout[4] == 0) + } + if (layout[0] == 4 && layout[1] == 3 && layout[2] == 2 && layout[3] == 1 && layout[4] == 0) { return construct_with_layout<::gridtools::layout_map<4, 3, 2, 1, 0>>(shape[0], shape[1], shape[2], shape[3], shape[4]); + } } default: { std::stringstream err; - if (shape.size() > 5) + if (shape.size() > 5) { err << "shape not recognized"; + } else { err << "Layout < "; - for (size_t j = 0; j < layout.size(); ++j) + for (size_t j = 0; j < layout.size(); ++j) { err << layout[j] << " "; + } err << "> not implemented in Atlas."; } throw_Exception(err.str(), Here()); @@ -229,8 +243,9 @@ class ArrayT_impl { throw_Exception(err.str(), Here()); } - if (array_.valid()) + if (array_.valid()) { array_.syncHostDevice(); + } Array* resized = Array::create(ArrayShape{(idx_t)c...}); @@ -397,7 +412,7 @@ Array* Array::create(DataType datatype, ArraySpec&& spec) { //------------------------------------------------------------------------------ -Array::~Array() {} +Array::~Array() = default; //------------------------------------------------------------------------------ @@ -429,8 +444,9 @@ void ArrayT::insert(idx_t idx1, idx_t size1) { // if( hostNeedsUpdate() ) { // updateHost(); //} - if (not hasDefaultLayout()) + if (not hasDefaultLayout()) { ATLAS_NOTIMPLEMENTED; + } ArrayShape nshape = shape(); if (idx1 > nshape[0]) { diff --git a/src/atlas/array/gridtools/GridToolsArrayView.cc b/src/atlas/array/gridtools/GridToolsArrayView.cc index d77895313..e42dff5c3 100644 --- a/src/atlas/array/gridtools/GridToolsArrayView.cc +++ b/src/atlas/array/gridtools/GridToolsArrayView.cc @@ -12,6 +12,7 @@ #include #include "atlas/array/gridtools/GridToolsArrayHelpers.h" #include "atlas/array/helpers/ArrayAssigner.h" +#include "atlas/array/helpers/ArrayCopier.h" #include "atlas/array/helpers/ArrayInitializer.h" #include "atlas/array/helpers/ArrayWriter.h" #include "atlas/runtime/Exception.h" @@ -34,7 +35,7 @@ struct host_device_array { data_[i++] = v; } } - ATLAS_HOST_DEVICE ~host_device_array() {} + ATLAS_HOST_DEVICE ~host_device_array() = default; ATLAS_HOST_DEVICE const T* data() const { return data_; } @@ -127,6 +128,16 @@ ENABLE_IF_NON_CONST void ArrayView::assign(const std::initializer_l helpers::array_assigner::apply(*this, list); } +//------------------------------------------------------------------------------------------------------ + + +template +ENABLE_IF_NON_CONST void ArrayView::assign(const ArrayView& other) { + helpers::array_copier::apply(other, *this); +} + +//------------------------------------------------------------------------------------------------------ + template void ArrayView::dump(std::ostream& os) const { os << "size: " << size() << " , values: "; @@ -144,28 +155,32 @@ void ArrayView::dump(std::ostream& os) const { // Explicit instantiation namespace atlas { namespace array { -#define EXPLICIT_TEMPLATE_INSTANTIATION(Rank) \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - \ - template void ArrayView::assign(int const&); \ - template void ArrayView::assign(long const&); \ - template void ArrayView::assign(float const&); \ - template void ArrayView::assign(double const&); \ - template void ArrayView::assign(long unsigned const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); +#define EXPLICIT_TEMPLATE_INSTANTIATION(Rank) \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + \ + template void ArrayView::assign(int const&); \ + template void ArrayView::assign(long const&); \ + template void ArrayView::assign(float const&); \ + template void ArrayView::assign(double const&); \ + template void ArrayView::assign(long unsigned const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); // For each Rank in [1..9] diff --git a/src/atlas/array/gridtools/GridToolsArrayView.h b/src/atlas/array/gridtools/GridToolsArrayView.h index f7877908e..be0b25e2e 100644 --- a/src/atlas/array/gridtools/GridToolsArrayView.h +++ b/src/atlas/array/gridtools/GridToolsArrayView.h @@ -144,6 +144,9 @@ class ArrayView { ENABLE_IF_NON_CONST void assign(const std::initializer_list& list); + ENABLE_IF_NON_CONST + void assign(const ArrayView& other); + const idx_t* strides() const { return strides_; } const idx_t* shape() const { return shape_; } diff --git a/src/atlas/array/gridtools/GridToolsIndexView.h b/src/atlas/array/gridtools/GridToolsIndexView.h index 06f3af099..8dcba7d7e 100644 --- a/src/atlas/array/gridtools/GridToolsIndexView.h +++ b/src/atlas/array/gridtools/GridToolsIndexView.h @@ -58,6 +58,18 @@ class FortranIndex { return *this; } + ATLAS_HOST_DEVICE + FortranIndex& operator+=(Value v) { + *(idx_) += v; + return *this; + } + + ATLAS_HOST_DEVICE + FortranIndex& operator-=(Value v) { + *(idx_) -= v; + return *this; + } + // implicit conversion ATLAS_HOST_DEVICE operator Value() const { return get(); } diff --git a/src/atlas/array/gridtools/GridToolsMakeView.cc b/src/atlas/array/gridtools/GridToolsMakeView.cc index 93cf93653..b6e5cfd7f 100644 --- a/src/atlas/array/gridtools/GridToolsMakeView.cc +++ b/src/atlas/array/gridtools/GridToolsMakeView.cc @@ -135,9 +135,9 @@ ArrayView make_view(const Array& array) { template IndexView make_host_indexview(Array& array) { - using value_t = typename std::remove_const::type; - typedef gridtools::storage_traits::storage_info_t<0, Rank> storage_info_ty; - typedef gridtools::storage_traits::data_store_t 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())); @@ -146,9 +146,9 @@ IndexView make_host_indexview(Array& array) { template IndexView make_host_indexview(const Array& array) { - using value_t = typename std::remove_const::type; - typedef gridtools::storage_traits::storage_info_t<0, Rank> storage_info_ty; - typedef gridtools::storage_traits::data_store_t 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())); diff --git a/src/atlas/array/helpers/ArrayCopier.h b/src/atlas/array/helpers/ArrayCopier.h new file mode 100644 index 000000000..d77f41f53 --- /dev/null +++ b/src/atlas/array/helpers/ArrayCopier.h @@ -0,0 +1,78 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/array.h" + +//------------------------------------------------------------------------------ + +namespace atlas { +namespace array { +namespace helpers { + +//------------------------------------------------------------------------------ + +/// Helper to copy from source array to target array. +template +struct array_copier; + +//------------------------------------------------------------------------------ + +// Recursive function to copy array elements across all dimensions. +template +struct array_copier_impl { + template + static void apply(const SourceView& sourceArr, TargetView& targetArr, const std::array& shape, + DimIndex... idxs) { + for (idx_t i = 0; i < shape[Dim]; ++i) { + array_copier_impl::apply(sourceArr, targetArr, shape, idxs..., i); + } + } +}; + +// End of recursion when Dim == Rank +template +struct array_copier_impl { + template + static void apply(const SourceView& sourceArr, TargetView& targetArr, const std::array& shape, + DimIndex... idxs) { + targetArr(idxs...) = sourceArr(idxs...); + } +}; + +//------------------------------------------------------------------------------ + +template +struct array_copier { + // Copy from source array to target array. + static void apply(const ArrayView& sourceArr, ArrayView& targetArr) { + array_copier_impl::apply(sourceArr, targetArr, shape(sourceArr, targetArr)); + } + // Copy from const source array to target array. + static void apply(const ArrayView& sourceArr, ArrayView& targetArr) { + array_copier_impl::apply(sourceArr, targetArr, shape(sourceArr, targetArr)); + } + +private: + // Make an array shape that is compatible with sourceArr and targetArr. + // This is useful for arrays from fields with different sized halos. + template + static std::array shape(const SourceView& sourceArr, const TargetView& targetArr) { + auto arrShape = std::array{}; + for (unsigned int Dim = 0; Dim < Rank; ++Dim) { + arrShape[Dim] = std::min(sourceArr.shape(Dim), targetArr.shape(Dim)); + } + return arrShape; + } +}; + +//------------------------------------------------------------------------------ + +} // namespace helpers +} // namespace array +} // namespace atlas diff --git a/src/atlas/array/native/NativeArrayView.cc b/src/atlas/array/native/NativeArrayView.cc index add1f21e1..2d2e84b28 100644 --- a/src/atlas/array/native/NativeArrayView.cc +++ b/src/atlas/array/native/NativeArrayView.cc @@ -13,6 +13,7 @@ #include "atlas/array/ArrayView.h" #include "atlas/array/helpers/ArrayAssigner.h" +#include "atlas/array/helpers/ArrayCopier.h" #include "atlas/array/helpers/ArrayWriter.h" //------------------------------------------------------------------------------------------------------ @@ -41,6 +42,14 @@ ENABLE_IF_NON_CONST void ArrayView::assign(const std::initializer_l //------------------------------------------------------------------------------------------------------ + +template +ENABLE_IF_NON_CONST void ArrayView::assign(const ArrayView& other) { + helpers::array_copier::apply(other, *this); +} + +//------------------------------------------------------------------------------------------------------ + template void ArrayView::dump(std::ostream& os) const { os << "size: " << size() << " , values: "; @@ -58,25 +67,29 @@ void ArrayView::dump(std::ostream& os) const { // Explicit instantiation namespace atlas { namespace array { -#define EXPLICIT_TEMPLATE_INSTANTIATION(Rank) \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template class ArrayView; \ - template void ArrayView::assign(int const&); \ - template void ArrayView::assign(long const&); \ - template void ArrayView::assign(float const&); \ - template void ArrayView::assign(double const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); \ - template void ArrayView::assign(std::initializer_list const&); +#define EXPLICIT_TEMPLATE_INSTANTIATION(Rank) \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template class ArrayView; \ + template void ArrayView::assign(int const&); \ + template void ArrayView::assign(long const&); \ + template void ArrayView::assign(float const&); \ + template void ArrayView::assign(double const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(std::initializer_list const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); \ + template void ArrayView::assign(ArrayView const&); // For each NDims in [1..9] EXPLICIT_TEMPLATE_INSTANTIATION(1) diff --git a/src/atlas/array/native/NativeArrayView.h b/src/atlas/array/native/NativeArrayView.h index c1e9eeb48..07c436024 100644 --- a/src/atlas/array/native/NativeArrayView.h +++ b/src/atlas/array/native/NativeArrayView.h @@ -267,6 +267,9 @@ class ArrayView { ENABLE_IF_NON_CONST void assign(const std::initializer_list& list); + ENABLE_IF_NON_CONST + void assign(const ArrayView& other); + void dump(std::ostream& os) const; /// @brief Obtain a slice from this view: view.slice( Range, Range, ... ) diff --git a/src/atlas/array/native/NativeIndexView.h b/src/atlas/array/native/NativeIndexView.h index 56454f323..7b2b44abf 100644 --- a/src/atlas/array/native/NativeIndexView.h +++ b/src/atlas/array/native/NativeIndexView.h @@ -79,6 +79,16 @@ class FortranIndex { return *this; } + FortranIndex& operator+=(Value v) { + *(idx_) += v; + return *this; + } + + FortranIndex& operator-=(Value v) { + *(idx_) -= v; + return *this; + } + // implicit conversion operator Value() const { return get(); } diff --git a/src/atlas/functionspace.h b/src/atlas/functionspace.h index 01de76bc5..72f095d0e 100644 --- a/src/atlas/functionspace.h +++ b/src/atlas/functionspace.h @@ -13,6 +13,7 @@ #pragma once #include "atlas/functionspace/CellColumns.h" +#include "atlas/functionspace/CubedSphereColumns.h" #include "atlas/functionspace/EdgeColumns.h" #include "atlas/functionspace/FunctionSpace.h" #include "atlas/functionspace/NodeColumns.h" diff --git a/src/atlas/functionspace/CellColumns.cc b/src/atlas/functionspace/CellColumns.cc index 6d39e0483..760bc04b5 100644 --- a/src/atlas/functionspace/CellColumns.cc +++ b/src/atlas/functionspace/CellColumns.cc @@ -272,10 +272,10 @@ CellColumns::CellColumns(const Mesh& mesh, const eckit::Configuration& config): mesh::actions::build_cells_parallel_fields(mesh_); mesh::actions::build_periodic_boundaries(mesh_); - if (halo_.size() > 0) { - mesh::actions::build_halo(mesh_, halo_.size()); - nb_cells_ = get_nb_cells_from_metadata(); - } + + mesh::actions::build_halo(mesh_, halo_.size()); + nb_cells_ = get_nb_cells_from_metadata(); + if (!nb_cells_) { nb_cells_ = mesh.cells().size(); } @@ -575,6 +575,10 @@ Field CellColumns::global_index() const { return mesh_.cells().global_index(); } +Field CellColumns::ghost() const { + return mesh_.cells().field("ghost"); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ diff --git a/src/atlas/functionspace/CellColumns.h b/src/atlas/functionspace/CellColumns.h index 3b904fdd1..1f84c6ea5 100644 --- a/src/atlas/functionspace/CellColumns.h +++ b/src/atlas/functionspace/CellColumns.h @@ -93,6 +93,8 @@ class CellColumns : public functionspace::FunctionSpaceImpl { Field global_index() const override; + Field ghost() const override; + private: // methods idx_t config_size(const eckit::Configuration& config) const; array::DataType config_datatype(const eckit::Configuration&) const; diff --git a/src/atlas/functionspace/CubedSphereColumns.cc b/src/atlas/functionspace/CubedSphereColumns.cc index 649d28481..c2a880b91 100644 --- a/src/atlas/functionspace/CubedSphereColumns.cc +++ b/src/atlas/functionspace/CubedSphereColumns.cc @@ -65,7 +65,7 @@ class CubedSphereStructureCache : public util::Cachesize()); util::ObjectHandle value = Base::get_or_create(key(mesh_impl), creator); return value; } @@ -78,8 +78,8 @@ class CubedSphereStructureCache : public util::Cache(mesh), getGhost(mesh)); + static value_type* create(const Mesh& mesh, idx_t size) { + value_type* value = new value_type(getTij(mesh), getGhost(mesh), size); return value; } }; @@ -121,13 +121,8 @@ idx_t CubedSphereColumns::invalid_index() const { } template -idx_t CubedSphereColumns::nb_elems() const { - return cubedSphereColumnsHandle_.get()->nb_elems(); -} - -template -idx_t CubedSphereColumns::nb_owned_elems() const { - return cubedSphereColumnsHandle_.get()->nb_owned_elems(); +idx_t CubedSphereColumns::sizeOwned() const { + return cubedSphereColumnsHandle_.get()->sizeOwned(); } template @@ -160,11 +155,6 @@ Field CubedSphereColumns::tij() const { return cubedSphereColumnsHandle_.get()->tij(); } -template -Field CubedSphereColumns::ghost() const { - return cubedSphereColumnsHandle_.get()->ghost(); -} - // Explicit instantiation of template classes. template class CubedSphereColumns; template class CubedSphereColumns; diff --git a/src/atlas/functionspace/CubedSphereColumns.h b/src/atlas/functionspace/CubedSphereColumns.h index eb1058d92..916a6c88d 100644 --- a/src/atlas/functionspace/CubedSphereColumns.h +++ b/src/atlas/functionspace/CubedSphereColumns.h @@ -38,11 +38,8 @@ class CubedSphereColumns : public BaseFunctionSpace { /// Invalid index. idx_t invalid_index() const; - /// Get total number of elements. - idx_t nb_elems() const; - /// Get number of owned elements. - idx_t nb_owned_elems() const; + idx_t sizeOwned() const; /// i lower bound for tile t (including halo) idx_t i_begin(idx_t t) const; @@ -60,16 +57,12 @@ class CubedSphereColumns : public BaseFunctionSpace { /// Return tij field. Field tij() const; - /// Return ghost field. - Field ghost() const; - private: class For { public: For(const CubedSphereColumns& functionSpace, const util::Config& config = util::NoConfig()): functionSpace_{functionSpace}, - indexMax_{config.getBool("include_halo", false) ? functionSpace.nb_elems() - : functionSpace.nb_owned_elems()}, + indexMax_{config.getBool("include_halo", false) ? functionSpace.size() : functionSpace.sizeOwned()}, levels_{config.getInt("levels", functionSpace_.levels())}, tijView_(array::make_view(functionSpace_.tij())) {} @@ -78,7 +71,7 @@ class CubedSphereColumns : public BaseFunctionSpace { using EnableFunctor = typename std::enable_if>::value>::type*; - // Functor: void f( index, t, i, j, k) + // Functor: void f(index, t, i, j, k) template = nullptr> void operator()(const Functor& f) const { using namespace meshgenerator::detail::cubedsphere; @@ -94,7 +87,7 @@ class CubedSphereColumns : public BaseFunctionSpace { } } - // Functor: void f( index, t, i, j) + // Functor: void f(index, t, i, j) template = nullptr> void operator()(const Functor& f) const { using namespace meshgenerator::detail::cubedsphere; @@ -108,7 +101,7 @@ class CubedSphereColumns : public BaseFunctionSpace { } } - // Functor: void f( index, k) + // Functor: void f(index, k) template = nullptr> void operator()(const Functor& f) const { using namespace meshgenerator::detail::cubedsphere; @@ -121,7 +114,7 @@ class CubedSphereColumns : public BaseFunctionSpace { } } - // Functor: void f( index ) + // Functor: void f(index ) template = nullptr> void operator()(const Functor& f) const { using namespace meshgenerator::detail::cubedsphere; diff --git a/src/atlas/functionspace/detail/CubedSphereStructure.cc b/src/atlas/functionspace/detail/CubedSphereStructure.cc index 0f1898c94..612825851 100644 --- a/src/atlas/functionspace/detail/CubedSphereStructure.cc +++ b/src/atlas/functionspace/detail/CubedSphereStructure.cc @@ -32,7 +32,8 @@ CubedSphereStructure::BoundingBox::BoundingBox() { jEnd = std::numeric_limits::min(); } -CubedSphereStructure::CubedSphereStructure(const Field& tij, const Field& ghost): tij_(tij), ghost_(ghost) { +CubedSphereStructure::CubedSphereStructure(const Field& tij, const Field& ghost, idx_t size): + tij_(tij), ghost_(ghost), nElems_(size) { ATLAS_TRACE(); Log::debug() << "CubedSphereStructure bounds checking is set to " + std::to_string(checkBounds) << std::endl; @@ -40,8 +41,6 @@ CubedSphereStructure::CubedSphereStructure(const Field& tij, const Field& ghost) const auto tijView_ = array::make_view(tij_); const auto ghostView_ = array::make_view(ghost_); - nElems_ = tijView_.shape(0); - // loop over tij and find min and max ij bounds. for (idx_t index = 0; index < nElems_; ++index) { const size_t t = static_cast(tijView_(index, Coordinates::T)); @@ -76,11 +75,11 @@ CubedSphereStructure::CubedSphereStructure(const Field& tij, const Field& ghost) } } -idx_t CubedSphereStructure::nb_elems() const { +idx_t CubedSphereStructure::size() const { return nElems_; } -idx_t CubedSphereStructure::nb_owned_elems() const { +idx_t CubedSphereStructure::sizeOwned() const { return nOwnedElems_; } diff --git a/src/atlas/functionspace/detail/CubedSphereStructure.h b/src/atlas/functionspace/detail/CubedSphereStructure.h index 81803b466..aaa8354fc 100644 --- a/src/atlas/functionspace/detail/CubedSphereStructure.h +++ b/src/atlas/functionspace/detail/CubedSphereStructure.h @@ -27,16 +27,16 @@ namespace detail { class CubedSphereStructure : public util::Object { public: CubedSphereStructure() = default; - CubedSphereStructure(const Field& tij, const Field& ghost); + CubedSphereStructure(const Field& tij, const Field& ghost, idx_t size); /// Invalid index. static constexpr idx_t invalid_index() { return -1; } /// Number of elements. - idx_t nb_elems() const; + idx_t size() const; /// Number of owned elements. - idx_t nb_owned_elems() const; + idx_t sizeOwned() const; /// i lower bound for tile t (including halo) idx_t i_begin(idx_t) const; diff --git a/src/atlas/grid/StructuredGrid.h b/src/atlas/grid/StructuredGrid.h index 45c80a8cf..ab7749ffb 100644 --- a/src/atlas/grid/StructuredGrid.h +++ b/src/atlas/grid/StructuredGrid.h @@ -145,6 +145,7 @@ class ReducedGrid : public StructuredGrid { /// @brief Specialization of StructuredGrid, where all rows have the same number of grid points class RegularGrid : public StructuredGrid { public: + using StructuredGrid::dx; using StructuredGrid::StructuredGrid; using StructuredGrid::x; using StructuredGrid::xy; @@ -155,6 +156,8 @@ class RegularGrid : public StructuredGrid { idx_t nx() const { return nxmax(); } + inline double dx() const { return dx(0); } + inline double x(idx_t i) const { return x(i, 0); } PointXY xy(idx_t i, idx_t j) const { return PointXY(x(i), y(j)); } diff --git a/src/atlas/grid/detail/grid/GridBuilder.cc b/src/atlas/grid/detail/grid/GridBuilder.cc index dce738251..06166506d 100644 --- a/src/atlas/grid/detail/grid/GridBuilder.cc +++ b/src/atlas/grid/detail/grid/GridBuilder.cc @@ -124,6 +124,7 @@ void force_link_CubedSphere(); void force_link_Gaussian(); void force_link_LonLat(); void force_link_Regional(); +void force_link_Regional_var_resolution(); } // namespace grid } // namespace detail @@ -132,6 +133,7 @@ const GridBuilder::Registry& GridBuilder::nameRegistry() { detail::grid::force_link_Gaussian(); detail::grid::force_link_LonLat(); detail::grid::force_link_Regional(); + detail::grid::force_link_Regional_var_resolution(); return *named_grids; } diff --git a/src/atlas/grid/detail/grid/RegionalVariableResolution.cc b/src/atlas/grid/detail/grid/RegionalVariableResolution.cc new file mode 100644 index 000000000..edc9b3fd3 --- /dev/null +++ b/src/atlas/grid/detail/grid/RegionalVariableResolution.cc @@ -0,0 +1,134 @@ +/** +* (C) Crown copyright 2021, 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 "RegionalVariableResolution.h" + +#include "eckit/types/FloatCompare.h" + +#include "atlas/grid/StructuredGrid.h" +#include "atlas/grid/detail/grid/GridBuilder.h" +#include "atlas/projection/detail/ProjectionImpl.h" +#include "atlas/runtime/Exception.h" +#include "atlas/runtime/Log.h" +#include "atlas/util/NormaliseLongitude.h" + +using atlas::grid::LinearSpacing; +using XSpace = atlas::StructuredGrid::XSpace; +using YSpace = atlas::StructuredGrid::YSpace; + +namespace atlas { +namespace grid { +namespace { // anonymous + +static class regional_var_resolution : public GridBuilder { +public: + regional_var_resolution(): GridBuilder("regional_variable_resolution") {} + + void print(std::ostream&) const override { + // os << std::left << std::setw(20) << "O" << "Octahedral Gaussian + // grid"; + } + + + const Grid::Implementation* create(const std::string& /*name*/, const Grid::Config&) const override { + throw_NotImplemented("There are no named regional_var_resolution grids implemented.", Here()); + return nullptr; + } + + //create return pointer, data type to return + const Grid::Implementation* create(const Grid::Config& config) const override { + // read projection subconfiguration + double inner_xmin = 0; + double inner_xmax = 0; + double inner_ymin = 0; + double inner_ymax = 0; + double outer_xmin = 0; + double outer_xmax = 0; + double outer_ymin = 0; + double outer_ymax = 0; + double delta_inner = 0.; + Projection projection; + { + util::Config config_all, config_proj, config_inner, config_outer; + util::Config config_pr, configwx, configwy; + + config_all.set("type", "variable_resolution"); + + if (config.get("projection", config_proj)) { + config_all.set(config_proj); + + if (config_proj.getString("type") == "lonlat") { + config_all.set("type", "variable_resolution"); + } + else if (config_proj.getString("type") == "rotated_lonlat") { + config_all.set("type", "rotated_variable_resolution"); + } + else { + throw_Exception("Only \"lonlat\" or \"rotated_lonlat\" projection is supported"); + } + } + + config.get("inner", config_inner); + + config.get("outer", config_outer); + + // merge of multiple configs use | + config.get("progression", config_pr); + config_all.set("progression", config_pr); + + config_all.set("inner", config_inner); + config_all.set("outer", config_outer); + projection = Projection(config_all); + } + + //< Add different parts in the grid something like + //< int inner_xmin = config.getInt("inner.xmin"); + + //compute number of points in the regular grid + util::Config config_grid; + + config.get("inner.xmin", inner_xmin); + config.get("inner.xend", inner_xmax); + config.get("inner.ymin", inner_ymin); + config.get("inner.yend", inner_ymax); + config.get("outer.xmin", outer_xmin); + config.get("outer.xend", outer_xmax); + config.get("outer.ymin", outer_ymin); + config.get("outer.yend", outer_ymax); + config.get("inner.dx", delta_inner); + + constexpr float epstest = std::numeric_limits::epsilon(); + int nx_reg = ((outer_xmax - outer_xmin + epstest) / delta_inner) + 1; + int ny_reg = ((outer_ymax - outer_ymin + epstest) / delta_inner) + 1; + + YSpace yspace = LinearSpacing{outer_ymin, outer_ymax, ny_reg}; + XSpace xspace = LinearSpacing{outer_xmin, outer_xmax, nx_reg}; + + //< RegularGrid is a type of structuredGrid + auto domain_ = RectangularDomain{{outer_xmin, outer_xmax}, {outer_ymin, outer_ymax}}; + //< allocate memory to make class, create an object using new "constructor" + return new StructuredGrid::grid_t(xspace, yspace, projection, domain_); + } + + void force_link() {} + +} regional_var_resolution_; + +} // namespace + +namespace detail { +namespace grid { + +void force_link_Regional_var_resolution() { + regional_var_resolution_.force_link(); +} + +} // namespace grid +} // namespace detail + +} // namespace grid +} // namespace atlas diff --git a/src/atlas/grid/detail/grid/RegionalVariableResolution.h b/src/atlas/grid/detail/grid/RegionalVariableResolution.h new file mode 100644 index 000000000..50203d5ec --- /dev/null +++ b/src/atlas/grid/detail/grid/RegionalVariableResolution.h @@ -0,0 +1,8 @@ +/** +* (C) Crown copyright 2021, 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/grid.h" diff --git a/src/atlas/grid/detail/partitioner/CubedSpherePartitioner.h b/src/atlas/grid/detail/partitioner/CubedSpherePartitioner.h index d53cddc42..603fb07b9 100644 --- a/src/atlas/grid/detail/partitioner/CubedSpherePartitioner.h +++ b/src/atlas/grid/detail/partitioner/CubedSpherePartitioner.h @@ -10,6 +10,7 @@ #pragma once +#include #include #include "atlas/grid/detail/partitioner/Partitioner.h" diff --git a/src/atlas/grid/detail/partitioner/EqualRegionsPartitioner.cc b/src/atlas/grid/detail/partitioner/EqualRegionsPartitioner.cc index 66feea6b0..92b551577 100644 --- a/src/atlas/grid/detail/partitioner/EqualRegionsPartitioner.cc +++ b/src/atlas/grid/detail/partitioner/EqualRegionsPartitioner.cc @@ -269,6 +269,10 @@ void cap_colats(int N, int n_collars, const double& c_polar, int n_regions[], do c_caps[n_collars + 1] = M_PI; } +// Disable optimisation for Cray (See ATLAS-327) +#ifdef _CRAYC +#pragma _CRI noopt +#endif void eq_caps(int N, std::vector& n_regions, std::vector& s_cap) { // // eq_regions uses the zonal equal area sphere partitioning algorithm to @@ -291,14 +295,14 @@ void eq_caps(int N, std::vector& n_regions, std::vector& s_cap) { // Given N, determine c_polar // the colatitude of the North polar spherical cap. // - double c_polar = polar_colat(N); + const double c_polar = polar_colat(N); // // Given N, determine the ideal angle for spherical collars. // Based on N, this ideal angle, and c_polar, // determine n_collars, the number of collars between the polar caps. // - int n_collars = num_collars(N, c_polar, ideal_collar_angle(N)); + const int n_collars = num_collars(N, c_polar, ideal_collar_angle(N)); // int n_regions_ns=n_collars+2; // @@ -337,6 +341,10 @@ void eq_caps(int N, std::vector& n_regions, std::vector& s_cap) { } // int n_regions_ew=maxval(n_regions(:)); } +// Reenable optimisation for Cray (See ATLAS-327) +#ifdef _CRAYC +#pragma _CRI opt +#endif void eq_regions(int N, double xmin[], double xmax[], double ymin[], double ymax[]) { // EQ_REGIONS Recursive zonal equal area (EQ) partition of sphere diff --git a/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc b/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc index 3a52809e5..5dbc571fd 100644 --- a/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc +++ b/src/atlas/grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.cc @@ -10,6 +10,8 @@ #include "atlas/grid/detail/partitioner/MatchingMeshPartitionerLonLatPolygon.h" +#include +#include #include #include "eckit/config/Resource.h" @@ -19,6 +21,7 @@ #include "atlas/grid/Iterator.h" #include "atlas/mesh/Nodes.h" #include "atlas/parallel/mpi/mpi.h" +#include "atlas/parallel/omp/fill.h" #include "atlas/runtime/Exception.h" #include "atlas/runtime/Log.h" #include "atlas/util/CoordinateEnums.h" @@ -44,36 +47,71 @@ void MatchingMeshPartitionerLonLatPolygon::partition(const Grid& grid, int parti Log::debug() << "MatchingMeshPartitionerLonLatPolygon::partition" << std::endl; - // FIXME: THIS IS A HACK! the coordinates include North/South Pole (first/last - // partitions only) - bool includesNorthPole = (mpi_rank == 0); - bool includesSouthPole = (mpi_rank == mpi_size - 1); - const util::PolygonXY poly{prePartitionedMesh_.polygon(0)}; + + double west = poly.coordinatesMin().x(); + double east = poly.coordinatesMax().x(); + comm.allReduceInPlace(west, eckit::mpi::Operation::MIN); + comm.allReduceInPlace(east, eckit::mpi::Operation::MAX); + Projection projection = prePartitionedMesh_.projection(); + omp::fill(partitioning, partitioning + grid.size(), -1); - { - eckit::ProgressTimer timer("Partitioning", grid.size(), "point", double(10), atlas::Log::trace()); + auto compute = [&](double west) { size_t i = 0; for (PointLonLat P : grid.lonlat()) { - ++timer; - projection.lonlat2xy(P); - const bool atThePole = (includesNorthPole && P[LAT] >= poly.coordinatesMax()[LAT]) || - (includesSouthPole && P[LAT] < poly.coordinatesMin()[LAT]); - - partitioning[i++] = atThePole || poly.contains(P) ? mpi_rank : -1; + if (partitioning[i] < 0) { + projection.lonlat2xy(P); + P.normalise(west); + partitioning[i] = poly.contains(P) ? mpi_rank : -1; + } + ++i; } - } - - // Synchronize partitioning, do a sanity check - comm.allReduceInPlace(partitioning, grid.size(), eckit::mpi::Operation::MAX); - const int min = *std::min_element(partitioning, partitioning + grid.size()); + // Synchronize partitioning + comm.allReduceInPlace(partitioning, grid.size(), eckit::mpi::Operation::MAX); + + return *std::min_element(partitioning, partitioning + grid.size()); + }; + + int min = compute(east - 360.); + constexpr double eps = 1.e-10; + bool second_try = [&]() { + if (min < 0 && east - west > 360. + eps) { + min = compute(west - eps); + return true; + } + return false; + }(); if (min < 0) { - throw_Exception( - "Could not find partition for target node (source " - "mesh does not contain all target grid points)", - Here()); + size_t i = 0; + size_t max_failures = grid.size(); + std::vector failed_index; + std::vector failed_lonlat; + failed_index.reserve(max_failures); + failed_lonlat.reserve(max_failures); + for (PointLonLat P : grid.lonlat()) { + if (partitioning[i] < 0) { + failed_index.emplace_back(i); + failed_lonlat.emplace_back(P); + } + ++i; + } + size_t nb_failures = failed_index.size(); + std::stringstream err; + err.precision(20); + err << "Could not find partition of " << nb_failures + << " target grid points (source mesh does not contain all target grid points)\n" + << "Tried first normalizing coordinates with west=" << east - 360.; + if (second_try) { + err << "Tried second time normalizing coordinates with west=" << west - eps << "\n"; + } + err << "Failed target grid points with global index:\n"; + for (size_t n = 0; n < nb_failures; ++n) { + err << " - " << std::setw(10) << std::left << failed_index[n] + 1 << " {lon,lat} : " << failed_lonlat[n] + << "\n"; + } + throw_Exception(err.str(), Here()); } } diff --git a/src/atlas/interpolation/Vector2D.h b/src/atlas/interpolation/Vector2D.h index 9841d6016..14432bcd1 100644 --- a/src/atlas/interpolation/Vector2D.h +++ b/src/atlas/interpolation/Vector2D.h @@ -40,7 +40,7 @@ typedef Eigen::Vector2d Vector2D; #else class Vector2D { -private: +public: Vector2D(const double* d) { xy_[0] = d[0]; xy_[1] = d[1]; @@ -51,7 +51,6 @@ class Vector2D { xy_[1] = y; } -public: Vector2D() { // Warning, data_ is uninitialised } @@ -84,9 +83,14 @@ class Vector2D { return s; } -private: + double* data() { return xy_; } + + const double* data() const { return xy_; } + double x() const { return xy_[0]; } double y() const { return xy_[1]; } + +private: double xy_[2]; }; diff --git a/src/atlas/interpolation/element/Quad2D.cc b/src/atlas/interpolation/element/Quad2D.cc new file mode 100644 index 000000000..1e626128e --- /dev/null +++ b/src/atlas/interpolation/element/Quad2D.cc @@ -0,0 +1,191 @@ +/* + * (C) Crown Copyright 2021 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/interpolation/Vector2D.h" +#include "atlas/interpolation/element/Quad2D.h" +#include "atlas/interpolation/element/Triag2D.h" +#include "atlas/runtime/Log.h" + +namespace atlas { +namespace interpolation { +namespace element { + +//---------------------------------------------------------------------------------------------------------------------- + +method::Intersect Quad2D::intersects(const PointXY& r, double edgeEpsilon, double epsilon) const { + method::Intersect isect; // intersection is false + + /* Split quadrilateral into two triangles, points are labelled counter-clockwise. + * v01-----------v11 + * / " . `. + * / " . `. + * / " .`. + * v00-------------------v10 + * + */ + + Triag2D T013(v00, v10, v01); + isect = T013.intersects(r, edgeEpsilon, epsilon); + if (isect) { + return isect; + } + + Triag2D T231(v11, v01, v10); + isect = T231.intersects(r, edgeEpsilon, epsilon); + if (isect) { + isect.u = 1 - isect.u; + isect.v = 1 - isect.v; + return isect; + } + + return isect.fail(); +} + +method::Intersect Quad2D::localRemap(const PointXY& p, double edgeEpsilon, double epsilon) const { + method::Intersect isect; + + // work out if point is within the polygon + if (!inQuadrilateral({p.x(), p.y()})) { + return isect.fail(); + } + + auto solve_weight = [epsilon](const double a, const double b, const double c, double& wght) -> bool { + if (std::abs(a) > epsilon) { + // if a is non-zero, we need to solve a quadratic equation for the weight + // ax**2 + bx + x = 0 + double det = b * b - 4. * a * c; + if (det >= 0.) { + double inv_two_a = 1. / (2. * a); + double sqrt_det = std::sqrt(det); + double root_a = (-b + sqrt_det) * inv_two_a; + if ((root_a > -epsilon) && (root_a < (1. + epsilon))) { + wght = root_a; + return true; + } + double root_b = (-b - sqrt_det) * inv_two_a; + if ((root_b > -epsilon) && (root_b < (1. + epsilon))) { + wght = root_b; + return true; + } + } + return false; + } + if (std::abs(b) > epsilon) { + // solve ax + b = 0 + wght = -c / b; + return true; + } + return false; + }; + + // solve for u and v where: + // w1 = ( 1 - u ) * ( 1 - v ) + // w2 = u * ( 1 - v ) + // w3 = u * v + // w4 = ( 1 - u ) * v + + Vector2D ray(p.x(), p.y()); + Vector2D vA = v00 - ray; + Vector2D vB = v10 - v00; + Vector2D vC = v01 - v00; + Vector2D vD = v00 - v10 - v01 + v11; + + // solve for v + double a = cross2d(vC, vD); + double b = cross2d(vC, vB) + cross2d(vA, vD); + double c = cross2d(vA, vB); + + if (!solve_weight(a, b, c, isect.v)) { + return isect.fail(); + } + + // solve for u + a = cross2d(vB, vD); + b = cross2d(vB, vC) + cross2d(vA, vD); + c = cross2d(vA, vC); + + if (!solve_weight(a, b, c, isect.u)) { + return isect.fail(); + } + + return isect.success(); +} + +bool Quad2D::validate() const { + // normal for sub-triangle T231 + + Vector2D E23 = v01 - v11; + Vector2D E21 = v10 - v11; + + double N231 = cross2d(E23, E21); + + // normal for sub-triangle T013 + + Vector2D E01 = v10 - v00; + Vector2D E03 = v01 - v00; + + double N013 = cross2d(E01, E03); + + // normal for sub-triangle T120 + + Vector2D E12 = -E21; + Vector2D E10 = -E01; + + double N120 = cross2d(E12, E10); + + // normal for sub-triangle T302 + + Vector2D E30 = -E03; + Vector2D E32 = -E23; + + double N302 = cross2d(E30, E32); + + // all normals must point same way + + double dot02 = N231 * N013; + double dot23 = N013 * N120; + double dot31 = N120 * N302; + double dot10 = N302 * N231; + + // all normals must point same way + bool is_inside = ((dot02 >= 0. && dot23 >= 0. && dot31 >= 0. && dot10 >= 0.) || + (dot02 <= 0. && dot23 <= 0. && dot31 <= 0. && dot10 <= 0.)); + return is_inside; +} + +double Quad2D::area() const { + return std::abs(0.5 * cross2d((v01 - v00), (v11 - v00))) + std::abs(0.5 * cross2d((v10 - v11), (v01 - v11))); +} + +bool Quad2D::inQuadrilateral(const Vector2D& p) const { + // point p must be on the inside of all quad edges to be inside the quad. + return cross2d(p - v00, p - v10) >= 0. && cross2d(p - v10, p - v11) >= 0. && cross2d(p - v11, p - v01) >= 0. && + cross2d(p - v01, p - v00) >= 0; +} + +void Quad2D::print(std::ostream& s) const { + auto printVector2D = [&s](const Vector2D& v) { s << "[" << v[0] << "," << v[1] << "]"; }; + s << "Quad2D["; + printVector2D(v00); + s << ", "; + printVector2D(v10); + s << ", "; + printVector2D(v11); + s << ", "; + printVector2D(v01); + s << "]"; +} + + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace element +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/element/Quad2D.h b/src/atlas/interpolation/element/Quad2D.h new file mode 100644 index 000000000..46c44daca --- /dev/null +++ b/src/atlas/interpolation/element/Quad2D.h @@ -0,0 +1,69 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/interpolation/Vector2D.h" +#include "atlas/interpolation/method/Intersect.h" +#include "atlas/util/Point.h" + +namespace atlas { +namespace interpolation { +namespace method { +struct Ray; +} +namespace element { + +//---------------------------------------------------------------------------------------------------------------------- + +class Quad2D { +public: + Quad2D(const double* x0, const double* x1, const double* x2, const double* x3): + v00(x0), v10(x1), v11(x2), v01(x3) {} + + Quad2D(const PointXY& x0, const PointXY& x1, const PointXY& x2, const PointXY& x3): + Quad2D(x0.data(), x1.data(), x2.data(), x3.data()) {} + + Quad2D(const Vector2D& x0, const Vector2D& x1, const Vector2D& x2, const Vector2D& x3): + Quad2D(x0.data(), x1.data(), x2.data(), x3.data()) {} + + method::Intersect intersects(const PointXY& r, double edgeEpsilon = 5 * std::numeric_limits::epsilon(), + double epsilon = 5 * std::numeric_limits::epsilon()) const; + + method::Intersect localRemap(const PointXY& r, double edgeEpsilon = 5 * std::numeric_limits::epsilon(), + double epsilon = 5 * std::numeric_limits::epsilon()) const; + + bool validate() const; + + double area() const; + + void print(std::ostream&) const; + + friend std::ostream& operator<<(std::ostream& s, const Quad2D& p) { + p.print(s); + return s; + } + +private: // members + Vector2D v00; // aka v0 + Vector2D v10; // aka v1 + Vector2D v11; // aka v2 + Vector2D v01; // aka v3 + + static double cross2d(const Vector2D& a, const Vector2D& b) { return a.x() * b.y() - a.y() * b.x(); } + + bool inQuadrilateral(const Vector2D& p) const; +}; + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace element +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/element/Triag2D.cc b/src/atlas/interpolation/element/Triag2D.cc new file mode 100644 index 000000000..870ba3262 --- /dev/null +++ b/src/atlas/interpolation/element/Triag2D.cc @@ -0,0 +1,124 @@ +/* + * (C) Crown Copyright 2021 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/interpolation/Vector2D.h" +#include "atlas/interpolation/element/Triag2D.h" +#include "atlas/runtime/Log.h" + +namespace atlas { +namespace interpolation { +namespace element { + +//---------------------------------------------------------------------------------------------------------------------- + +method::Intersect Triag2D::intersects(const PointXY& r, double edgeEpsilon, double epsilon) const { + method::Intersect isect; // intersection is false + + /* + * ,v11 + * e2 , ^ `. + * , ^ r `. + * , ^ `. + * v00-------------------v10 + * e1 + */ + + Vector2D rvec{r.data()}; + + if (!inTriangle(rvec)) { + return isect.fail(); + } + + Vector2D e1{v10 - v00}; + Vector2D e2{v11 - v00}; + Vector2D pvec{rvec - v00}; + + // solve u e1 + v e2 = pvec for u and v + float invDet = 1. / (e1.x() * e2.y() - e2.x() * e1.y()); + isect.u = (pvec.x() * e2.y() - e2.x() * pvec.y()) * invDet; + isect.v = (e1.x() * pvec.y() - pvec.x() * e1.y()) * invDet; + + // clamp values between 0 and 1 + isect.u = std::max(0.0, std::min(isect.u, 1.0)); + isect.v = std::max(0.0, std::min(isect.v, 1.0)); + + return isect.success(); +} + +bool Triag2D::validate() const { + // normal for sub-triangle T012 + + Vector2D E01 = v10 - v00; + Vector2D E02 = v11 - v00; + + double N012 = cross2d(E01, E02); + + // normal for sub-triangle T120 + + Vector2D E12 = v11 - v10; + Vector2D E10 = v00 - v10; + + double N120 = cross2d(E12, E10); + + // normal for sub-triangle T201 + + Vector2D E20 = -E02; + Vector2D E21 = -E12; + + double N201 = cross2d(E20, E21); + + // all normals must point same way + + double dot1 = N120 * N012; + double dot2 = N201 * N120; + double dot3 = N201 * N012; + + // all normals must point same way + bool is_inside = ((dot1 >= 0. && dot2 >= 0. && dot3 >= 0.) || (dot1 <= 0. && dot2 <= 0. && dot3 <= 0.)); + return is_inside; +} + +double Triag2D::area() const { + return std::abs(0.5 * cross2d((v10 - v00), (v11 - v00))); +} + +bool Triag2D::inTriangle(const Vector2D& p) const { + // point p must be on the inside of all triangle edges to be inside the triangle. + double zst1 = cross2d(p - v00, p - v10); + if (zst1 >= 0.0) { + double zst2 = cross2d(p - v10, p - v11); + if (zst2 >= 0.0) { + double zst3 = cross2d(p - v11, p - v00); + if (zst3 >= 0.0) { + return true; + } + } + } + + return false; +} + +void Triag2D::print(std::ostream& s) const { + auto printVector2D = [&s](const Vector2D& v) { s << "[" << v[0] << "," << v[1] << "]"; }; + s << "Triag2D["; + printVector2D(v00); + s << ", "; + printVector2D(v11); + s << ", "; + printVector2D(v10); + s << "]"; +} + + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace element +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/element/Triag2D.h b/src/atlas/interpolation/element/Triag2D.h new file mode 100644 index 000000000..9d76f2532 --- /dev/null +++ b/src/atlas/interpolation/element/Triag2D.h @@ -0,0 +1,72 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/interpolation/Vector2D.h" +#include "atlas/interpolation/method/Intersect.h" +#include "atlas/runtime/Exception.h" +#include "atlas/util/Point.h" + +namespace atlas { +namespace interpolation { +namespace element { + +static constexpr double BAD_WEIGHT_VALUE = -1.; + +//---------------------------------------------------------------------------------------------------------------------- + +class Triag2D { +public: + Triag2D(const double* x0, const double* x1, const double* x2): v00(x0), v10(x1), v11(x2) {} + + Triag2D(const PointXY& x0, const PointXY& x1, const PointXY& x2): Triag2D(x0.data(), x1.data(), x2.data()) {} + + Triag2D(const Vector2D& x0, const Vector2D& x1, const Vector2D& x2): Triag2D(x0.data(), x1.data(), x2.data()) {} + + method::Intersect intersects(const PointXY& r, double edgeEpsilon = 5 * std::numeric_limits::epsilon(), + double epsilon = 5 * std::numeric_limits::epsilon()) const; + + bool validate() const; + + double area() const; + + void print(std::ostream&) const; + + friend std::ostream& operator<<(std::ostream& s, const Triag2D& p) { + p.print(s); + return s; + } + + const Vector2D& p(int i) { + if (i == 0) + return v00; + if (i == 1) + return v10; + if (i == 2) + return v11; + throw_OutOfRange("Triag2D::p(i)", i, 3, Here()); + } + +private: // members + Vector2D v00; // aka v0 + Vector2D v10; // aka v1 + Vector2D v11; // aka v2 + + static double cross2d(const Vector2D& a, const Vector2D& b) { return a.x() * b.y() - a.y() * b.x(); } + + bool inTriangle(const Vector2D& p) const; +}; + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace element +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/method/MethodFactory.cc b/src/atlas/interpolation/method/MethodFactory.cc index 7c27e8b0e..8e3fd8970 100644 --- a/src/atlas/interpolation/method/MethodFactory.cc +++ b/src/atlas/interpolation/method/MethodFactory.cc @@ -11,6 +11,7 @@ #include "MethodFactory.h" // for static linking +#include "bil/BilinearRemapping.h" #include "fe/FiniteElement.h" #include "knn/GridBoxAverage.h" #include "knn/GridBoxMaximum.h" @@ -31,6 +32,7 @@ namespace { void force_link() { static struct Link { Link() { + MethodBuilder(); MethodBuilder(); MethodBuilder(); MethodBuilder(); diff --git a/src/atlas/interpolation/method/PointIndex2.cc b/src/atlas/interpolation/method/PointIndex2.cc new file mode 100644 index 000000000..d4bf06b7c --- /dev/null +++ b/src/atlas/interpolation/method/PointIndex2.cc @@ -0,0 +1,60 @@ +/* + * (C) Crown Copyright 2021 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 "eckit/config/Resource.h" + +#include "atlas/array/ArrayView.h" +#include "atlas/array/MakeView.h" +#include "atlas/interpolation/method/PointIndex2.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/runtime/Trace.h" +#include "atlas/util/Topology.h" + +namespace atlas { +namespace interpolation { +namespace method { + +ElemIndex2* create_element2D_kdtree(const Mesh& mesh, const Field& field_centres) { + ATLAS_TRACE(); + const array::ArrayView centres = array::make_view(field_centres); + const array::ArrayView flags = array::make_view(mesh.cells().flags()); + auto include_element = [&](unsigned int e) { + using util::Topology; + return not Topology::view(flags(e)).check(Topology::INVALID); + }; + + static bool fastBuildKDTrees = eckit::Resource("$ATLAS_FAST_BUILD_KDTREES", true); + + ElemIndex2* tree = new ElemIndex2(); + const size_t nb_cells = centres.shape(0); + + if (fastBuildKDTrees) { + std::vector p; + p.reserve(nb_cells); + + for (unsigned int j = 0; j < nb_cells; ++j) { + if (include_element(j)) { + p.emplace_back(ElemIndex2::Point(centres(j, LON), centres(j, LAT)), ElemIndex2::Payload(j)); + } + } + + tree->build(p.begin(), p.end()); + } + else { + for (unsigned int j = 0; j < nb_cells; ++j) { + if (include_element(j)) { + tree->insert( + ElemIndex2::Value(ElemIndex2::Point(centres(j, LON), centres(j, LAT)), ElemIndex2::Payload(j))); + } + } + } + return tree; +} + +} // namespace method +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/method/PointIndex2.h b/src/atlas/interpolation/method/PointIndex2.h new file mode 100644 index 000000000..603e7c80a --- /dev/null +++ b/src/atlas/interpolation/method/PointIndex2.h @@ -0,0 +1,73 @@ +/* + * (C) Crown Copyright 2021 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 "eckit/container/KDMapped.h" +#include "eckit/container/KDMemory.h" +#include "eckit/container/KDTree.h" +#include "eckit/geometry/Point2.h" + +#include "atlas/field/Field.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/util/CoordinateEnums.h" + +namespace atlas { +namespace interpolation { +namespace method { + +//---------------------------------------------------------------------------------------------------------------------- + +template +class Point2KdTree : public eckit::KDTreeMemory { +public: + typedef eckit::KDTreeMemory Tree; + + typedef typename Tree::Alloc Alloc; + typedef typename Tree::NodeInfo NodeInfo; + typedef typename Tree::NodeList NodeList; + typedef typename Tree::PayloadType Payload; + typedef typename Tree::Point Point; + typedef typename Tree::Value Value; + + Point2KdTree(): eckit::KDTreeMemory() {} + Point2KdTree(Alloc& alloc): Tree(alloc) {} + + using Tree::findInSphere; + using Tree::kNearestNeighbours; + using Tree::nearestNeighbour; + + using Tree::findInSphereBruteForce; + using Tree::kNearestNeighboursBruteForce; + using Tree::nearestNeighbourBruteForce; +}; + +//---------------------------------------------------------------------------------------------------------------------- + +struct PointIndex2TreeTrait { + typedef eckit::geometry::Point2 Point; + typedef size_t Payload; +}; + +typedef Point2KdTree PointIndex2; + +//---------------------------------------------------------------------------------------------------------------------- + +struct ElemIndex2TreeTrait { + typedef eckit::geometry::Point2 Point; + typedef size_t Payload; +}; + +typedef Point2KdTree ElemIndex2; + +ElemIndex2* create_element2D_kdtree(const Mesh& mesh, const Field& field_centres); + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace method +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/method/bil/BilinearRemapping.cc b/src/atlas/interpolation/method/bil/BilinearRemapping.cc new file mode 100644 index 000000000..571f1de8d --- /dev/null +++ b/src/atlas/interpolation/method/bil/BilinearRemapping.cc @@ -0,0 +1,400 @@ +/* + * (C) Crown Copyright 2021 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 + +#include "BilinearRemapping.h" + +#include "eckit/log/Plural.h" +#include "eckit/log/ProgressTimer.h" +#include "eckit/log/Seconds.h" + +#include "atlas/functionspace/NodeColumns.h" +#include "atlas/functionspace/PointCloud.h" +#include "atlas/grid.h" +#include "atlas/interpolation/element/Quad2D.h" +#include "atlas/interpolation/element/Triag2D.h" +#include "atlas/interpolation/method/MethodFactory.h" +#include "atlas/interpolation/method/Ray.h" +#include "atlas/mesh/ElementType.h" +#include "atlas/mesh/Nodes.h" +#include "atlas/mesh/actions/Build2DCellCentres.h" +#include "atlas/meshgenerator.h" +#include "atlas/parallel/GatherScatter.h" +#include "atlas/parallel/mpi/Buffer.h" +#include "atlas/parallel/mpi/mpi.h" +#include "atlas/runtime/Exception.h" +#include "atlas/runtime/Log.h" +#include "atlas/runtime/Trace.h" +#include "atlas/util/CoordinateEnums.h" +#include "atlas/util/Earth.h" +#include "atlas/util/Point.h" + + +namespace atlas { +namespace interpolation { +namespace method { + +namespace { + +MethodBuilder __builder("bilinear-remapping"); + +// epsilon used to scale edge tolerance when projecting ray to intesect element +static const double parametricEpsilon = 1e-15; + +} // namespace + + +void BilinearRemapping::do_setup(const Grid& source, const Grid& target, const Cache& cache) { + allow_halo_exchange_ = false; + // no halo_exchange because we don't have any halo with delaunay or 3d structured meshgenerator + + if (interpolation::MatrixCache(cache)) { + matrix_cache_ = cache; + matrix_ = &matrix_cache_.matrix(); + ATLAS_ASSERT(matrix_cache_.matrix().rows() == target.size()); + ATLAS_ASSERT(matrix_cache_.matrix().cols() == source.size()); + return; + } + if (mpi::size() > 1) { + ATLAS_NOTIMPLEMENTED; + } + auto make_nodecolumns = [](const Grid& grid) { + Mesh mesh; + if (StructuredGrid{grid}) { + mesh = MeshGenerator("structured", util::Config("3d", true)).generate(grid); + } + else { + mesh = MeshGenerator("delaunay").generate(grid); + } + return functionspace::NodeColumns(mesh); + }; + + do_setup(make_nodecolumns(source), functionspace::PointCloud{target}); +} + +void BilinearRemapping::do_setup(const FunctionSpace& source, const FunctionSpace& target) { + ATLAS_TRACE("atlas::interpolation::method::BilinearRemapping::do_setup()"); + + source_ = source; + target_ = target; + + ATLAS_TRACE_SCOPE("Setup target") { + if (functionspace::NodeColumns tgt = target) { + Mesh meshTarget = tgt.mesh(); + + target_ghost_ = meshTarget.nodes().ghost(); + target_lonlat_ = meshTarget.nodes().lonlat(); + } + else if (functionspace::PointCloud tgt = target) { + const idx_t N = tgt.size(); + target_ghost_ = tgt.ghost(); + target_lonlat_ = tgt.lonlat(); + } + else { + ATLAS_NOTIMPLEMENTED; + } + } + + setup(source); +} + +struct Stencil { + enum + { + max_stencil_size = 4 + }; +}; + +void BilinearRemapping::print(std::ostream& out) const { + functionspace::NodeColumns src(source_); + functionspace::NodeColumns tgt(target_); + out << "atlas::interpolation::method::BilinearRemapping{" << std::endl; + out << "max_fraction_elems_to_try: " << max_fraction_elems_to_try_; + out << ", treat_failure_as_missing_value: " << treat_failure_as_missing_value_; + if (not tgt) { + out << "}" << std::endl; + return; + } + out << ", NodeColumns to NodeColumns stencil weights: " << std::endl; + auto gidx_src = array::make_view(src.nodes().global_index()); + + ATLAS_ASSERT(tgt.nodes().size() == idx_t(matrix_->rows())); + + + auto field_stencil_points_loc = tgt.createField(option::variables(Stencil::max_stencil_size)); + auto field_stencil_weights_loc = tgt.createField(option::variables(Stencil::max_stencil_size)); + auto field_stencil_size_loc = tgt.createField(); + + auto stencil_points_loc = array::make_view(field_stencil_points_loc); + auto stencil_weights_loc = array::make_view(field_stencil_weights_loc); + auto stencil_size_loc = array::make_view(field_stencil_size_loc); + stencil_size_loc.assign(0); + + for (Matrix::const_iterator it = matrix_->begin(); it != matrix_->end(); ++it) { + idx_t p = idx_t(it.row()); + idx_t& i = stencil_size_loc(p); + stencil_points_loc(p, i) = gidx_src(it.col()); + stencil_weights_loc(p, i) = *it; + ++i; + } + + + gidx_t global_size = tgt.gather().glb_dof(); + + auto field_stencil_points_glb = + tgt.createField(option::variables(Stencil::max_stencil_size) | option::global(0)); + auto field_stencil_weights_glb = + tgt.createField(option::variables(Stencil::max_stencil_size) | option::global(0)); + auto field_stencil_size_glb = tgt.createField(option::global(0)); + + + auto stencil_points_glb = array::make_view(field_stencil_points_glb); + auto stencil_weights_glb = array::make_view(field_stencil_weights_glb); + auto stencil_size_glb = array::make_view(field_stencil_size_glb); + + tgt.gather().gather(stencil_size_loc, stencil_size_glb); + tgt.gather().gather(stencil_points_loc, stencil_points_glb); + tgt.gather().gather(stencil_weights_loc, stencil_weights_glb); + + int precision = std::numeric_limits::max_digits10; + for (idx_t i = 0; i < global_size; ++i) { + out << std::setw(10) << i + 1 << " : "; + for (idx_t j = 0; j < stencil_size_glb(i); ++j) { + out << std::setw(10) << stencil_points_glb(i, j); + } + for (idx_t j = stencil_size_glb(i); j < Stencil::max_stencil_size; ++j) { + out << " "; + } + for (idx_t j = 0; j < stencil_size_glb(i); ++j) { + out << std::setw(precision + 5) << std::left << std::setprecision(precision) << stencil_weights_glb(i, j); + } + out << std::endl; + } + out << "}" << std::endl; +} + +void BilinearRemapping::setup(const FunctionSpace& source) { + const functionspace::NodeColumns src = source; + ATLAS_ASSERT(src); + + Mesh meshSource = src.mesh(); + + + auto trace_setup_source = atlas::Trace{Here(), "Setup source"}; + + // 2D point coordinates + auto source_lonlat = array::make_view(src.lonlat()); + + // generate barycenters of each triangle & insert them on a kd-tree + util::Config config; + config.set("name", "centre "); + config.set("flatten_virtual_elements", false); + Field cell_centres = mesh::actions::Build2DCellCentres(config)(meshSource); + + std::unique_ptr eTree(create_element2D_kdtree(meshSource, cell_centres)); + + trace_setup_source.stop(); + + ilonlat_.reset(new array::ArrayView(array::make_view(meshSource.nodes().lonlat()))); + olonlat_.reset(new array::ArrayView(array::make_view(target_lonlat_))); + igidx_.reset(new array::ArrayView(array::make_view(src.nodes().global_index()))); + connectivity_ = &meshSource.cells().node_connectivity(); + const mesh::Nodes& i_nodes = meshSource.nodes(); + + + idx_t inp_npts = i_nodes.size(); + idx_t out_npts = olonlat_->shape(0); + + array::ArrayView out_ghosts = array::make_view(target_ghost_); + + idx_t Nelements = meshSource.cells().size(); + + // weights -- one per vertex of element, triangles (3) or quads (4) + + Triplets weights_triplets; // structure to fill-in sparse matrix + weights_triplets.reserve(out_npts * 4); // preallocate space as if all elements where quads + + // search nearest k cell centres + + const idx_t maxNbElemsToTry = std::max(8, idx_t(Nelements * max_fraction_elems_to_try_)); + idx_t max_neighbours = 0; + + std::vector failures; + + ATLAS_TRACE_SCOPE("Computing interpolation matrix") { + eckit::ProgressTimer progress("Computing interpolation weights", out_npts, "point", double(5), Log::debug()); + for (idx_t ip = 0; ip < out_npts; ++ip, ++progress) { + if (out_ghosts(ip)) { + continue; + } + + double lon{(*olonlat_)(ip, 0)}; + while (lon > 360.0) { + lon -= 360.0; + } + while (lon < 0.0) { + lon += 360.0; + } + PointXY p{lon, (*olonlat_)(ip, 1)}; // lookup point + + idx_t kpts = 1; + bool success = false; + std::ostringstream failures_log; + + while (!success && kpts <= maxNbElemsToTry) { + max_neighbours = std::max(kpts, max_neighbours); + + ElemIndex2::NodeList cs = eTree->kNearestNeighbours(p, kpts); + Triplets triplets = projectPointToElements(ip, cs, failures_log); + + if (triplets.size()) { + std::copy(triplets.begin(), triplets.end(), std::back_inserter(weights_triplets)); + success = true; + } + kpts *= 2; + } + + if (!success) { + failures.push_back(ip); + if (not treat_failure_as_missing_value_) { + Log::debug() << "------------------------------------------------------" + "---------------------\n"; + Log::debug() << "Failed to project point (lon,lat)=" << p << '\n'; + Log::debug() << failures_log.str(); + } + } + } + } + Log::debug() << "Maximum neighbours searched was " << eckit::Plural(max_neighbours, "element") << std::endl; + + if (failures.size()) { + if (treat_failure_as_missing_value_) { + missing_.resize(failures.size()); + std::copy(std::begin(failures), std::end(failures), missing_.begin()); + } + else { + // If this fails, consider lowering atlas::grid::parametricEpsilon + std::ostringstream msg; + msg << "Rank " << mpi::rank() << " failed to project points:\n"; + for (std::vector::const_iterator i = failures.begin(); i != failures.end(); ++i) { + const PointLonLat pll{(*olonlat_)(*i, (size_t)0), (*olonlat_)(*i, (size_t)1)}; // lookup point + msg << "\t(lon,lat) = " << pll << "\n"; + } + + Log::error() << msg.str() << std::endl; + throw_Exception(msg.str()); + } + } + + // fill sparse matrix and return + Matrix A(out_npts, inp_npts, weights_triplets); + matrix_shared_->swap(A); +} + +Method::Triplets BilinearRemapping::projectPointToElements(size_t ip, const ElemIndex2::NodeList& elems, + std::ostream& /* failures_log */) const { + ATLAS_ASSERT(elems.begin() != elems.end()); + + const size_t inp_points = ilonlat_->shape(0); + std::array idx; + std::array w; + + Triplets triplets; + triplets.reserve(4); + + double lon{(*olonlat_)(ip, 0)}; + while (lon > 360.0) { + lon -= 360.0; + } + while (lon < 0.0) { + lon += 360.0; + } + PointXY ob_loc{lon, (*olonlat_)(ip, 1)}; // lookup point + + for (ElemIndex2::NodeList::const_iterator itc = elems.begin(); itc != elems.end(); ++itc) { + const idx_t elem_id = idx_t((*itc).value().payload()); + ATLAS_ASSERT(elem_id < connectivity_->rows()); + + const idx_t nb_cols = connectivity_->cols(elem_id); + ATLAS_ASSERT(nb_cols == 3 || nb_cols == 4); + + for (idx_t i = 0; i < nb_cols; ++i) { + idx[i] = (*connectivity_)(elem_id, i); + ATLAS_ASSERT(idx[i] < inp_points); + } + + if (nb_cols == 3) { + /* triangle */ + element::Triag2D triag(PointXY{(*ilonlat_)(idx[0], 0), (*ilonlat_)(idx[0], 1)}, + PointXY{(*ilonlat_)(idx[1], 0), (*ilonlat_)(idx[1], 1)}, + PointXY{(*ilonlat_)(idx[2], 0), (*ilonlat_)(idx[2], 1)}); + + // pick an epsilon based on a characteristic length (sqrt(area)) + // (this scales linearly so it better compares with linear weights u,v,w) + const double edgeEpsilon = parametricEpsilon * std::sqrt(triag.area()); + ATLAS_ASSERT(edgeEpsilon >= 0); + + Intersect is = triag.intersects(ob_loc, edgeEpsilon); + + if (is) { + // weights are the linear Lagrange function evaluated at u,v (aka + // barycentric coordinates) + w[0] = 1. - is.u - is.v; + w[1] = is.u; + w[2] = is.v; + + for (size_t i = 0; i < 3; ++i) { + triplets.emplace_back(ip, idx[i], w[i]); + } + + break; // stop looking for elements + } + } + else { + /* quadrilateral */ + element::Quad2D quad(PointXY{(*ilonlat_)(idx[0], 0), (*ilonlat_)(idx[0], 1)}, + PointXY{(*ilonlat_)(idx[1], 0), (*ilonlat_)(idx[1], 1)}, + PointXY{(*ilonlat_)(idx[2], 0), (*ilonlat_)(idx[2], 1)}, + PointXY{(*ilonlat_)(idx[3], 0), (*ilonlat_)(idx[3], 1)}); + + // pick an epsilon based on a characteristic length (sqrt(area)) + // (this scales linearly so it better compares with linear weights u,v,w) + const double edgeEpsilon = parametricEpsilon * std::sqrt(quad.area()); + ATLAS_ASSERT(edgeEpsilon >= 0); + + Intersect is = quad.localRemap(ob_loc, edgeEpsilon); + + if (is) { + //std::cout << "intersection found for p: "; + //std::cout << ob_loc << " and " << quad << std::endl; + // weights are the bilinear Lagrange function evaluated at u,v + w[0] = (1. - is.u) * (1. - is.v); + w[1] = is.u * (1. - is.v); + w[2] = is.u * is.v; + w[3] = (1. - is.u) * is.v; + + for (size_t i = 0; i < 4; ++i) { + triplets.emplace_back(ip, idx[i], w[i]); + } + break; // stop looking for elements + } + } + + } // loop over nearest elements + + if (!triplets.empty()) { + normalise(triplets); + } + return triplets; +} + +} // namespace method +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/method/bil/BilinearRemapping.h b/src/atlas/interpolation/method/bil/BilinearRemapping.h new file mode 100644 index 000000000..5bb0baba3 --- /dev/null +++ b/src/atlas/interpolation/method/bil/BilinearRemapping.h @@ -0,0 +1,86 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/interpolation/method/Method.h" + +#include + +#include "eckit/config/Configuration.h" +#include "eckit/memory/NonCopyable.h" + +#include "atlas/array/ArrayView.h" +#include "atlas/functionspace/FunctionSpace.h" +#include "atlas/interpolation/method/PointIndex2.h" +#include "atlas/mesh/Elements.h" + +namespace atlas { +namespace interpolation { +namespace method { + +class BilinearRemapping : public Method { +public: + BilinearRemapping(const Config& config): Method(config) { + config.get("max_fraction_elems_to_try", max_fraction_elems_to_try_); + } + + virtual ~BilinearRemapping() override {} + + virtual void print(std::ostream&) const override; + +protected: + /** + * @brief Create an interpolant sparse matrix relating two (pre-partitioned) + * meshes, + * using elements as per the Finite Element Method and ray-tracing to + * calculate + * source mesh elements intersections (and interpolation weights) with target + * grid + * node-containing rays + * @param meshSource mesh containing source elements + * @param meshTarget mesh containing target points + */ + void setup(const FunctionSpace& source); + + /** + * Find in which element the point is contained by projecting (ray-tracing) + * the + * point to the nearest element(s), returning the (normalized) interpolation + * weights + */ + Triplets projectPointToElements(size_t ip, const ElemIndex2::NodeList& elems, std::ostream& failures_log) const; + + virtual const FunctionSpace& source() const override { return source_; } + virtual const FunctionSpace& target() const override { return target_; } + +private: + using Method::do_setup; + virtual void do_setup(const FunctionSpace& source, const FunctionSpace& target) override; + + virtual void do_setup(const Grid& source, const Grid& target, const Cache&) override; + +protected: + mesh::MultiBlockConnectivity* connectivity_; + std::unique_ptr> ilonlat_; + std::unique_ptr> olonlat_; + std::unique_ptr> igidx_; + + Field target_lonlat_; + Field target_xyz_; + Field target_ghost_; + + FunctionSpace source_; + FunctionSpace target_; + + bool treat_failure_as_missing_value_{true}; + double max_fraction_elems_to_try_{0.2}; +}; + +} // namespace method +} // namespace interpolation +} // namespace atlas diff --git a/src/atlas/interpolation/method/fe/FiniteElement.cc b/src/atlas/interpolation/method/fe/FiniteElement.cc index d7e9d9b51..aaf9fbe68 100644 --- a/src/atlas/interpolation/method/fe/FiniteElement.cc +++ b/src/atlas/interpolation/method/fe/FiniteElement.cc @@ -132,20 +132,25 @@ struct Stencil { void FiniteElement::print(std::ostream& out) const { functionspace::NodeColumns src(source_); functionspace::NodeColumns tgt(target_); + out << "atlas::interpolation::method::FiniteElement{" << std::endl; + out << "max_fraction_elems_to_try: " << max_fraction_elems_to_try_; + out << ", treat_failure_as_missing_value: " << treat_failure_as_missing_value_; if (not tgt) { - ATLAS_NOTIMPLEMENTED; + out << "}" << std::endl; + return; } + out << ", NodeColumns to NodeColumns stencil weights: " << std::endl; auto gidx_src = array::make_view(src.nodes().global_index()); ATLAS_ASSERT(tgt.nodes().size() == idx_t(matrix_->rows())); auto field_stencil_points_loc = tgt.createField(option::variables(Stencil::max_stencil_size)); - auto field_stencil_weigths_loc = tgt.createField(option::variables(Stencil::max_stencil_size)); + auto field_stencil_weights_loc = tgt.createField(option::variables(Stencil::max_stencil_size)); auto field_stencil_size_loc = tgt.createField(); auto stencil_points_loc = array::make_view(field_stencil_points_loc); - auto stencil_weights_loc = array::make_view(field_stencil_weigths_loc); + auto stencil_weights_loc = array::make_view(field_stencil_weights_loc); auto stencil_size_loc = array::make_view(field_stencil_size_loc); stencil_size_loc.assign(0); @@ -175,23 +180,21 @@ void FiniteElement::print(std::ostream& out) const { tgt.gather().gather(stencil_points_loc, stencil_points_glb); tgt.gather().gather(stencil_weights_loc, stencil_weights_glb); - if (mpi::rank() == 0) { - int precision = std::numeric_limits::max_digits10; - for (idx_t i = 0; i < global_size; ++i) { - out << std::setw(10) << i + 1 << " : "; - for (idx_t j = 0; j < stencil_size_glb(i); ++j) { - out << std::setw(10) << stencil_points_glb(i, j); - } - for (idx_t j = stencil_size_glb(i); j < Stencil::max_stencil_size; ++j) { - out << " "; - } - for (idx_t j = 0; j < stencil_size_glb(i); ++j) { - out << std::setw(precision + 5) << std::left << std::setprecision(precision) - << stencil_weights_glb(i, j); - } - out << std::endl; + int precision = std::numeric_limits::max_digits10; + for (idx_t i = 0; i < global_size; ++i) { + out << std::setw(10) << i + 1 << " : "; + for (idx_t j = 0; j < stencil_size_glb(i); ++j) { + out << std::setw(10) << stencil_points_glb(i, j); + } + for (idx_t j = stencil_size_glb(i); j < Stencil::max_stencil_size; ++j) { + out << " "; + } + for (idx_t j = 0; j < stencil_size_glb(i); ++j) { + out << std::setw(precision + 5) << std::left << std::setprecision(precision) << stencil_weights_glb(i, j); } + out << std::endl; } + out << "}" << std::endl; } void FiniteElement::setup(const FunctionSpace& source) { @@ -231,8 +234,7 @@ void FiniteElement::setup(const FunctionSpace& source) { array::ArrayView out_lonlat = array::make_view(target_lonlat_); - idx_t Nelements = meshSource.cells().size(); - const double maxFractionElemsToTry = 0.2; + idx_t Nelements = meshSource.cells().size(); // weights -- one per vertex of element, triangles (3) or quads (4) @@ -241,7 +243,7 @@ void FiniteElement::setup(const FunctionSpace& source) { // search nearest k cell centres - const idx_t maxNbElemsToTry = std::max(8, idx_t(Nelements * maxFractionElemsToTry)); + const idx_t maxNbElemsToTry = std::max(8, idx_t(Nelements * max_fraction_elems_to_try_)); idx_t max_neighbours = 0; std::vector failures; diff --git a/src/atlas/interpolation/method/fe/FiniteElement.h b/src/atlas/interpolation/method/fe/FiniteElement.h index 5e16b0d73..0b7d99458 100644 --- a/src/atlas/interpolation/method/fe/FiniteElement.h +++ b/src/atlas/interpolation/method/fe/FiniteElement.h @@ -28,7 +28,9 @@ namespace method { class FiniteElement : public Method { public: - FiniteElement(const Config& config): Method(config) {} + FiniteElement(const Config& config): Method(config) { + config.get("max_fraction_elems_to_try", max_fraction_elems_to_try_); + } virtual ~FiniteElement() override {} @@ -79,6 +81,7 @@ class FiniteElement : public Method { FunctionSpace target_; bool treat_failure_as_missing_value_{true}; + double max_fraction_elems_to_try_{0.2}; }; } // namespace method diff --git a/src/atlas/interpolation/method/knn/GridBox.h b/src/atlas/interpolation/method/knn/GridBox.h index 871abc5ff..0d4c95b75 100644 --- a/src/atlas/interpolation/method/knn/GridBox.h +++ b/src/atlas/interpolation/method/knn/GridBox.h @@ -111,7 +111,6 @@ class GridBox { struct GridBoxes : std::vector { GridBoxes(const Grid&, bool gaussianWeightedLatitudes = true); GridBoxes(); - using std::vector::vector; double getLongestGridBoxDiagonal() const; }; diff --git a/src/atlas/io/types/array/ArrayMetadata.cc b/src/atlas/io/types/array/ArrayMetadata.cc index 90f96eeaf..971d95040 100644 --- a/src/atlas/io/types/array/ArrayMetadata.cc +++ b/src/atlas/io/types/array/ArrayMetadata.cc @@ -11,6 +11,7 @@ #include "ArrayMetadata.h" #include +#include #include #include "atlas/runtime/Exception.h" diff --git a/src/atlas/library/config.h b/src/atlas/library/config.h index 34e9c9949..4b43fe9c8 100644 --- a/src/atlas/library/config.h +++ b/src/atlas/library/config.h @@ -45,4 +45,14 @@ using idx_t = long; /// @typedef uidx_t /// Integer type for unique indices typedef gidx_t uidx_t; + +#define ATLAS_ECKIT_VERSION_AT_LEAST(x, y, z) (ATLAS_ECKIT_VERSION_INT >= x * 10000 + y * 100 + z) + +#if ATLAS_ECKIT_VERSION_AT_LEAST(1, 19, 0) +#define ATLAS_ECKIT_HAVE_ECKIT_585 1 +#else +#define ATLAS_ECKIT_HAVE_ECKIT_585 0 +#endif + + } // namespace atlas diff --git a/src/atlas/linalg/Introspection.h b/src/atlas/linalg/Introspection.h index 355c2f8d1..a0e37afbb 100644 --- a/src/atlas/linalg/Introspection.h +++ b/src/atlas/linalg/Introspection.h @@ -12,14 +12,11 @@ #include -#include "eckit/linalg/LinearAlgebra.h" #include "eckit/linalg/Matrix.h" -#include "eckit/linalg/SparseMatrix.h" #include "eckit/linalg/Vector.h" +#include "eckit/linalg/types.h" -#include "atlas/array.h" -#include "atlas/parallel/omp/omp.h" -#include "atlas/runtime/Exception.h" +#include "atlas/library/config.h" namespace atlas { namespace linalg { diff --git a/src/atlas/linalg/dense/Backend.cc b/src/atlas/linalg/dense/Backend.cc index 6dda8f0c0..f72a43611 100644 --- a/src/atlas/linalg/dense/Backend.cc +++ b/src/atlas/linalg/dense/Backend.cc @@ -12,7 +12,13 @@ #include +#include "atlas/library/config.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraDense.h" +#else #include "eckit/linalg/LinearAlgebra.h" +#endif + #include "eckit/utils/Tokenizer.h" #include "atlas/library.h" @@ -66,7 +72,11 @@ struct backends { // // However to be identical in behaviour for TransLocal with atlas 0.26.0 and earlier before // any other codes can be adapted in short notice: +#if ATLAS_ECKIT_HAVE_ECKIT_585 + if (eckit::linalg::LinearAlgebraDense::hasBackend("mkl")) { +#else if (eckit::linalg::LinearAlgebra::hasBackend("mkl")) { +#endif current_backend_ = "mkl"; } else { @@ -110,11 +120,19 @@ bool Backend::available() const { std::string t = type(); if (t == backend::eckit_linalg::type()) { if (has("backend")) { +#if ATLAS_ECKIT_HAVE_ECKIT_585 + return eckit::linalg::LinearAlgebraDense::hasBackend(getString("backend")); +#else return eckit::linalg::LinearAlgebra::hasBackend(getString("backend")); +#endif } return true; } +#if ATLAS_ECKIT_HAVE_ECKIT_585 + return eckit::linalg::LinearAlgebraDense::hasBackend(t); +#else return eckit::linalg::LinearAlgebra::hasBackend(t); +#endif } } // namespace dense diff --git a/src/atlas/linalg/dense/MatrixMultiply.h b/src/atlas/linalg/dense/MatrixMultiply.h index 8194cd585..77f4167f0 100644 --- a/src/atlas/linalg/dense/MatrixMultiply.h +++ b/src/atlas/linalg/dense/MatrixMultiply.h @@ -16,6 +16,7 @@ #include "atlas/linalg/Indexing.h" #include "atlas/linalg/View.h" #include "atlas/linalg/dense/Backend.h" +#include "atlas/runtime/Exception.h" #include "atlas/util/Config.h" namespace atlas { diff --git a/src/atlas/linalg/dense/MatrixMultiply.tcc b/src/atlas/linalg/dense/MatrixMultiply.tcc index 23dffe372..a24096a56 100644 --- a/src/atlas/linalg/dense/MatrixMultiply.tcc +++ b/src/atlas/linalg/dense/MatrixMultiply.tcc @@ -12,14 +12,18 @@ #include "MatrixMultiply.h" -#include - #include "atlas/linalg/Indexing.h" #include "atlas/linalg/Introspection.h" #include "atlas/linalg/View.h" #include "atlas/linalg/dense/Backend.h" #include "atlas/runtime/Exception.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraDense.h" +#else +#include "eckit/linalg/LinearAlgebra.h" +#endif + namespace atlas { namespace linalg { @@ -46,16 +50,14 @@ void matrix_multiply( const Mat& A, const Mat& B, Mat& C, const eckit::Configura } else { if( type == "openmp" ) { - // with ECKIT-578 it will be guaranteed that openmp backend is always available. - if( eckit::linalg::LinearAlgebra::hasBackend("openmp") ) { - type = "openmp"; - } - else { - type = "generic"; - } + type = "generic"; dense::MatrixMultiplyHelper::apply( A, B, C, util::Config("backend",type) ); } +#if ATLAS_ECKIT_HAVE_ECKIT_585 + else if( eckit::linalg::LinearAlgebraDense::hasBackend(type) ) { +#else else if( eckit::linalg::LinearAlgebra::hasBackend(type) ) { +#endif dense::MatrixMultiplyHelper::apply( A, B, C, util::Config("backend",type) ); } else { diff --git a/src/atlas/linalg/dense/MatrixMultiply_EckitLinalg.cc b/src/atlas/linalg/dense/MatrixMultiply_EckitLinalg.cc index b972dbaeb..86885e5b5 100644 --- a/src/atlas/linalg/dense/MatrixMultiply_EckitLinalg.cc +++ b/src/atlas/linalg/dense/MatrixMultiply_EckitLinalg.cc @@ -20,7 +20,13 @@ #include "MatrixMultiply_EckitLinalg.h" +#include "atlas/library/config.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraDense.h" +#else #include "eckit/linalg/LinearAlgebra.h" +#endif + #include "eckit/linalg/Matrix.h" #include "eckit/linalg/Vector.h" @@ -31,6 +37,17 @@ namespace linalg { namespace dense { namespace { +#if ATLAS_ECKIT_HAVE_ECKIT_585 +const eckit::linalg::LinearAlgebraDense& eckit_linalg_backend(const Configuration& config) { + std::string backend = "default"; + config.get("backend", backend); + if (backend == "default") { + return eckit::linalg::LinearAlgebraDense::backend(); + } + ATLAS_ASSERT(eckit::linalg::LinearAlgebraDense::hasBackend(backend)); + return eckit::linalg::LinearAlgebraDense::getBackend(backend); +} +#else const eckit::linalg::LinearAlgebra& eckit_linalg_backend(const Configuration& config) { std::string backend = "default"; config.get("backend", backend); @@ -40,6 +57,7 @@ const eckit::linalg::LinearAlgebra& eckit_linalg_backend(const Configuration& co ATLAS_ASSERT(eckit::linalg::LinearAlgebra::hasBackend(backend)); return eckit::linalg::LinearAlgebra::getBackend(backend); } +#endif } // namespace diff --git a/src/atlas/linalg/sparse/Backend.cc b/src/atlas/linalg/sparse/Backend.cc index 1ce1e55ae..b9084884a 100644 --- a/src/atlas/linalg/sparse/Backend.cc +++ b/src/atlas/linalg/sparse/Backend.cc @@ -12,7 +12,12 @@ #include +#include "atlas/library/config.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraSparse.h" +#else #include "eckit/linalg/LinearAlgebra.h" +#endif #include "eckit/utils/Tokenizer.h" #include "atlas/library.h" @@ -96,11 +101,19 @@ bool Backend::available() const { } if (t == backend::eckit_linalg::type()) { if (has("backend")) { +#if ATLAS_ECKIT_HAVE_ECKIT_585 + return eckit::linalg::LinearAlgebraSparse::hasBackend(getString("backend")); +#else return eckit::linalg::LinearAlgebra::hasBackend(getString("backend")); +#endif } return true; } +#if ATLAS_ECKIT_HAVE_ECKIT_585 + return eckit::linalg::LinearAlgebraSparse::hasBackend(t); +#else return eckit::linalg::LinearAlgebra::hasBackend(t); +#endif } diff --git a/src/atlas/linalg/sparse/SparseMatrixMultiply.h b/src/atlas/linalg/sparse/SparseMatrixMultiply.h index 899b200d6..d1776975b 100644 --- a/src/atlas/linalg/sparse/SparseMatrixMultiply.h +++ b/src/atlas/linalg/sparse/SparseMatrixMultiply.h @@ -16,6 +16,7 @@ #include "atlas/linalg/Indexing.h" #include "atlas/linalg/View.h" #include "atlas/linalg/sparse/Backend.h" +#include "atlas/runtime/Exception.h" #include "atlas/util/Config.h" namespace atlas { diff --git a/src/atlas/linalg/sparse/SparseMatrixMultiply.tcc b/src/atlas/linalg/sparse/SparseMatrixMultiply.tcc index 0e1ca4506..b4e1ed9cd 100644 --- a/src/atlas/linalg/sparse/SparseMatrixMultiply.tcc +++ b/src/atlas/linalg/sparse/SparseMatrixMultiply.tcc @@ -12,14 +12,19 @@ #include "SparseMatrixMultiply.h" -#include - #include "atlas/linalg/Indexing.h" #include "atlas/linalg/Introspection.h" #include "atlas/linalg/View.h" #include "atlas/linalg/sparse/Backend.h" #include "atlas/runtime/Exception.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraSparse.h" +#else +#include "eckit/linalg/LinearAlgebra.h" +#endif + + namespace atlas { namespace linalg { @@ -75,7 +80,11 @@ void sparse_matrix_multiply( const Matrix& matrix, const SourceView& src, Target else if ( type == sparse::backend::eckit_linalg::type() ) { sparse::dispatch_sparse_matrix_multiply( matrix, src, tgt, indexing, config ); } +#if ATLAS_ECKIT_HAVE_ECKIT_585 + else if( eckit::linalg::LinearAlgebraSparse::hasBackend(type) ) { +#else else if( eckit::linalg::LinearAlgebra::hasBackend(type) ) { +#endif sparse::dispatch_sparse_matrix_multiply( matrix, src, tgt, indexing, util::Config("backend",type) ); } else { diff --git a/src/atlas/linalg/sparse/SparseMatrixMultiply_EckitLinalg.cc b/src/atlas/linalg/sparse/SparseMatrixMultiply_EckitLinalg.cc index 72c0b4ecb..72d55c1a9 100644 --- a/src/atlas/linalg/sparse/SparseMatrixMultiply_EckitLinalg.cc +++ b/src/atlas/linalg/sparse/SparseMatrixMultiply_EckitLinalg.cc @@ -20,7 +20,14 @@ #include "SparseMatrixMultiply_EckitLinalg.h" +#include "atlas/library/config.h" + +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraSparse.h" +#else #include "eckit/linalg/LinearAlgebra.h" +#endif + #include "eckit/linalg/Matrix.h" #include "eckit/linalg/Vector.h" @@ -31,6 +38,19 @@ namespace linalg { namespace sparse { namespace { + +#if ATLAS_ECKIT_HAVE_ECKIT_585 +const eckit::linalg::LinearAlgebraSparse& eckit_linalg_backend(const Configuration& config) { + std::string backend = "default"; + config.get("backend", backend); + + if (backend == "default") { + return eckit::linalg::LinearAlgebraSparse::backend(); + } + ATLAS_ASSERT(eckit::linalg::LinearAlgebraSparse::hasBackend(backend)); + return eckit::linalg::LinearAlgebraSparse::getBackend(backend); +} +#else const eckit::linalg::LinearAlgebra& eckit_linalg_backend(const Configuration& config) { std::string backend = "default"; config.get("backend", backend); @@ -40,6 +60,7 @@ const eckit::linalg::LinearAlgebra& eckit_linalg_backend(const Configuration& co ATLAS_ASSERT(eckit::linalg::LinearAlgebra::hasBackend(backend)); return eckit::linalg::LinearAlgebra::getBackend(backend); } +#endif } // namespace diff --git a/src/atlas/mesh/actions/Build2DCellCentres.cc b/src/atlas/mesh/actions/Build2DCellCentres.cc new file mode 100644 index 000000000..d0076f82b --- /dev/null +++ b/src/atlas/mesh/actions/Build2DCellCentres.cc @@ -0,0 +1,122 @@ +/* + * (C) Crown Copyright 2021 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 "eckit/types/FloatCompare.h" + +#include "atlas/array/MakeView.h" +#include "atlas/field/Field.h" +#include "atlas/mesh/HybridElements.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/Nodes.h" +#include "atlas/mesh/actions/Build2DCellCentres.h" +#include "atlas/runtime/Trace.h" +#include "atlas/util/CoordinateEnums.h" + +namespace atlas { +namespace mesh { +namespace actions { + +//---------------------------------------------------------------------------------------------------------------------- + +Build2DCellCentres::Build2DCellCentres(const std::string& field_name, bool force_recompute): + field_name_(field_name), force_recompute_(force_recompute), flatten_virtual_elements_(true) {} + +Build2DCellCentres::Build2DCellCentres(eckit::Configuration& config): + field_name_(config.getString("name", "centre")), + force_recompute_(config.getBool("force_recompute", false)), + flatten_virtual_elements_(config.getBool("flatten_virtual_elements", true)) {} + +Field& Build2DCellCentres::operator()(Mesh& mesh) const { + bool recompute = force_recompute_; + if (!mesh.cells().has_field(field_name_)) { + mesh.cells().add(Field(field_name_, array::make_datatype(), array::make_shape(mesh.cells().size(), 2))); + recompute = true; + } + if (recompute) { + ATLAS_TRACE("Build2DCellCentres"); + mesh::Nodes& nodes = mesh.nodes(); + array::ArrayView coords = array::make_view(nodes.field("lonlat")); + + idx_t firstVirtualPoint = std::numeric_limits::max(); + if (nodes.metadata().has("NbRealPts")) { + firstVirtualPoint = nodes.metadata().get("NbRealPts"); + } + + idx_t nb_cells = mesh.cells().size(); + std::vector lons(nodes.size()); + for (idx_t e = 0; e < nodes.size(); ++e) { + lons[e] = coords(e, LON); + //while (lons[e] > 360.0) { lons[e] -= 360.0; } + //while (lons[e] < 0.0) { lons[e] += 360.0; } + } + auto centroids = array::make_view(mesh.cells().field(field_name_)); + const mesh::HybridElements::Connectivity& cell_node_connectivity = mesh.cells().node_connectivity(); + + for (idx_t e = 0; e < nb_cells; ++e) { + centroids(e, LON) = 0.; + centroids(e, LAT) = 0.; + + const idx_t nb_cell_nodes = cell_node_connectivity.cols(e); + + // check for degenerate elements (less than three unique nodes) + // NOTE: this is not a proper check but it is very robust + eckit::types::CompareApproximatelyEqual approx(1.e-9); + + idx_t nb_equal_nodes = 0; + for (idx_t ni = 0; ni < nb_cell_nodes - 1; ++ni) { + idx_t i = cell_node_connectivity(e, ni); + Point2 Pi(lons[i], coords(i, LAT)); + for (idx_t nj = ni + 1; nj < nb_cell_nodes; ++nj) { + idx_t j = cell_node_connectivity(e, nj); + Point2 Pj(lons[j], coords(j, LAT)); + if (approx(Pi[LON], Pj[LON]) && approx(Pi[LAT], Pj[LAT])) { + ++nb_equal_nodes; + } + } + } + + idx_t nb_unique_nodes = idx_t(nb_cell_nodes) - nb_equal_nodes; + if (nb_unique_nodes < 3) { + continue; + } + + if (flatten_virtual_elements_) { + // calculate centroid by averaging coordinates (uses only "real" nodes) + idx_t nb_real_nodes = 0; + for (idx_t n = 0; n < nb_cell_nodes; ++n) { + const idx_t i = cell_node_connectivity(e, n); + if (i < firstVirtualPoint) { + ++nb_real_nodes; + centroids(e, LON) += lons[i]; + centroids(e, LAT) += coords(i, LAT); + } + } + + if (nb_real_nodes > 1) { + const double average_coefficient = 1. / static_cast(nb_real_nodes); + centroids(e, LON) *= average_coefficient; + centroids(e, LAT) *= average_coefficient; + } + } + else { + const double average_coefficient = 1. / static_cast(nb_cell_nodes); + for (idx_t n = 0; n < nb_cell_nodes; ++n) { + const idx_t i = cell_node_connectivity(e, n); + centroids(e, LON) += lons[i] * average_coefficient; + centroids(e, LAT) += coords(i, LAT) * average_coefficient; + } + } + } + } + return mesh.cells().field(field_name_); +} + +} // namespace actions +} // namespace mesh +} // namespace atlas diff --git a/src/atlas/mesh/actions/Build2DCellCentres.h b/src/atlas/mesh/actions/Build2DCellCentres.h new file mode 100644 index 000000000..6428c8a2b --- /dev/null +++ b/src/atlas/mesh/actions/Build2DCellCentres.h @@ -0,0 +1,39 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/util/Config.h" + +namespace atlas { + +class Mesh; +class Field; + +namespace mesh { +namespace actions { + +/// Generates the cell centres on each cell +class Build2DCellCentres { +public: + Build2DCellCentres(const std::string& field_name = "centre", bool force_recompute = false); + Build2DCellCentres(eckit::Configuration&); + + /// @note Correct only for Linear Triangles and Quadrilaterals + Field& operator()(Mesh&) const; + +private: + std::string field_name_; + bool force_recompute_; + bool flatten_virtual_elements_; +}; + +} // namespace actions +} // namespace mesh +} // namespace atlas diff --git a/src/atlas/mesh/actions/BuildEdges.cc b/src/atlas/mesh/actions/BuildEdges.cc index 198efe062..ac773bb01 100644 --- a/src/atlas/mesh/actions/BuildEdges.cc +++ b/src/atlas/mesh/actions/BuildEdges.cc @@ -289,89 +289,6 @@ class AccumulatePoleEdges { } }; -void accumulate_pole_edges(mesh::Nodes& nodes, std::vector& pole_edge_nodes, idx_t& nb_pole_edges) { - enum - { - NORTH = 0, - SOUTH = 1 - }; - - const auto xy = array::make_view(nodes.xy()); - const auto flags = array::make_view(nodes.flags()); - const auto part = array::make_view(nodes.partition()); - const idx_t nb_nodes = nodes.size(); - - double min[2], max[2]; - min[XX] = std::numeric_limits::max(); - min[YY] = std::numeric_limits::max(); - max[XX] = -std::numeric_limits::max(); - max[YY] = -std::numeric_limits::max(); - for (idx_t node = 0; node < nb_nodes; ++node) { - min[XX] = std::min(min[XX], xy(node, XX)); - min[YY] = std::min(min[YY], xy(node, YY)); - max[XX] = std::max(max[XX], xy(node, XX)); - max[YY] = std::max(max[YY], xy(node, YY)); - } - - ATLAS_TRACE_MPI(ALLREDUCE) { - mpi::comm().allReduceInPlace(min, 2, eckit::mpi::min()); - mpi::comm().allReduceInPlace(max, 2, eckit::mpi::max()); - } - - double tol = 1e-6; - - // Collect all nodes closest to poles - std::vector> pole_nodes(2); - for (idx_t node = 0; node < nb_nodes; ++node) { - if (std::abs(xy(node, YY) - max[YY]) < tol) { - pole_nodes[NORTH].insert(node); - } - else if (std::abs(xy(node, YY) - min[YY]) < tol) { - pole_nodes[SOUTH].insert(node); - } - } - - // Sanity check - { - for (idx_t NS = 0; NS < 2; ++NS) { - int npart = -1; - for (std::set::iterator it = pole_nodes[NS].begin(); it != pole_nodes[NS].end(); ++it) { - int node = *it; - if (npart == -1) { - npart = part(node); - } - else if (part(node) != npart) { - // Not implemented yet, when pole-lattitude is split. - std::stringstream msg; - msg << "Split pole-latitude is not supported yet... node " << node << "[p" << part(node) - << "] should belong to part " << npart; - throw_NotImplemented(msg.str(), Here()); - } - } - } - } - - // Create connections over the poles and store in pole_edge_nodes - nb_pole_edges = 0; - for (idx_t NS = 0; NS < 2; ++NS) { - for (std::set::iterator it = pole_nodes[NS].begin(); it != pole_nodes[NS].end(); ++it) { - int node = *it; - if (!Topology::check(flags(node), Topology::PERIODIC | Topology::GHOST)) { - int x2 = microdeg(xy(node, XX) + 180.); - for (std::set::iterator itr = pole_nodes[NS].begin(); itr != pole_nodes[NS].end(); ++itr) { - int other_node = *itr; - if (microdeg(xy(other_node, XX)) == x2) { - if (!Topology::check(flags(other_node), Topology::PERIODIC)) { - pole_edge_nodes.push_back(node); - pole_edge_nodes.push_back(other_node); - ++nb_pole_edges; - } - } - } - } - } - } -} struct ComputeUniquePoleEdgeIndex { // Already assumes that the edges cross the pole @@ -456,11 +373,6 @@ void build_edges(Mesh& mesh, const eckit::Configuration& config) { accumulate_facets_ordered_by_halo(mesh.cells(), mesh.nodes(), edge_nodes_data, edge_to_elem_data, nb_edges, nb_inner_edges, missing_value, edge_halo_offsets); - std::shared_ptr pole_edge_accumulator; - if (pole_edges) { - pole_edge_accumulator = std::make_shared(nodes); - } - std::vector sorted_edge_nodes_data; std::vector sorted_edge_to_elem_data; @@ -551,6 +463,8 @@ void build_edges(Mesh& mesh, const eckit::Configuration& config) { edge_to_elem_data.data() + edge_halo_offsets[halo] * 2); if (pole_edges) { + auto pole_edge_accumulator = std::make_shared(nodes); + idx_t nb_pole_edges; std::vector pole_edge_nodes; diff --git a/src/atlas/mesh/actions/BuildHalo.cc b/src/atlas/mesh/actions/BuildHalo.cc index 831612306..8d3b6c769 100644 --- a/src/atlas/mesh/actions/BuildHalo.cc +++ b/src/atlas/mesh/actions/BuildHalo.cc @@ -1030,9 +1030,62 @@ class BuildHaloHelper { } } + std::vector> gather_nb_elements() { + idx_t nb_types = mesh.cells().nb_types(); + idx_t mpi_size = mpi::comm().size(); + std::vector elems_per_type(nb_types); + for (idx_t t = 0; t < nb_types; ++t) { + elems_per_type[t] = mesh.cells().elements(t).size(); + } + atlas::mpi::Buffer recv(mpi_size); + ATLAS_TRACE_MPI(ALLGATHER) { mpi::comm().allGatherv(elems_per_type.begin(), elems_per_type.end(), recv); } + + std::vector> result(nb_types, std::vector(mpi_size)); + for (idx_t p = 0; p < mpi_size; ++p) { + auto recv_p = recv[p]; + for (idx_t t = 0; t < nb_types; ++t) { + result[t][p] = recv_p[t]; + } + } + return result; + }; + + void add_buffers(Buffers& buf) { add_nodes(buf); + + auto nb_elements_pre = gather_nb_elements(); + add_elements(buf); + + auto nb_elements_post = gather_nb_elements(); + + // Renumber remote_index as the number of elements per type might have grown + { + auto nb_partitions = mpi::comm().size(); + auto nb_types = mesh.cells().nb_types(); + + std::vector> accumulated_diff(nb_types, std::vector(nb_partitions)); + for (idx_t t = 1; t < nb_types; ++t) { + for (idx_t p = 0; p < nb_partitions; ++p) { + accumulated_diff[t][p] = + accumulated_diff[t - 1][p] + nb_elements_post[t - 1][p] - nb_elements_pre[t - 1][p]; + } + } + + for (idx_t t = 0; t < nb_types; ++t) { + auto& elements = mesh.cells().elements(t); + auto partition = elements.view(mesh.cells().partition()); + auto ridx = elements.indexview(mesh.cells().remote_index()); + + auto& accumulated_diff_t = accumulated_diff[t]; + + for (idx_t elem = 0; elem < elements.size(); ++elem) { + ridx(elem) += accumulated_diff_t[partition(elem)]; + } + } + } + update(); } }; diff --git a/src/atlas/mesh/actions/BuildParallelFields.cc b/src/atlas/mesh/actions/BuildParallelFields.cc index 10d67a3aa..bb7fc9f96 100644 --- a/src/atlas/mesh/actions/BuildParallelFields.cc +++ b/src/atlas/mesh/actions/BuildParallelFields.cc @@ -10,6 +10,7 @@ #include +#include #include #include @@ -297,31 +298,39 @@ Field& build_nodes_remote_idx(mesh::Nodes& nodes) { for (idx_t jnode = 0; jnode < nb_recv_nodes; ++jnode) { uid_t uid = recv_node[jnode * varsize + 0]; int inode = recv_node[jnode * varsize + 1]; - if (lookup.count(uid)) { - send_found[proc[jpart]].push_back(inode); - send_found[proc[jpart]].push_back(lookup[uid]); - } - else { - std::stringstream msg; - msg << "[" << mpi::rank() << "] " - << "Node requested by rank [" << jpart << "] with uid [" << uid - << "] that should be owned is not found"; - throw_Exception(msg.str(), Here()); - } + send_found[proc[jpart]].push_back(inode); + send_found[proc[jpart]].push_back(lookup.count(uid) ? lookup[uid] : -1); } } ATLAS_TRACE_MPI(ALLTOALL) { mpi::comm().allToAll(send_found, recv_found); } + std::stringstream errstream; + size_t failed{0}; + const auto gidx = array::make_view(nodes.global_index()); for (idx_t jpart = 0; jpart < nparts; ++jpart) { const std::vector& recv_node = recv_found[proc[jpart]]; const idx_t nb_recv_nodes = recv_node.size() / 2; // array::ArrayView recv_node( recv_found[ proc[jpart] ].data(), // array::make_shape(recv_found[ proc[jpart] ].size()/2,2) ); for (idx_t jnode = 0; jnode < nb_recv_nodes; ++jnode) { - ridx(recv_node[jnode * 2 + 0]) = recv_node[jnode * 2 + 1]; + idx_t inode = recv_node[jnode * 2 + 0]; + idx_t ridx_inode = recv_node[jnode * 2 + 1]; + if (ridx_inode >= 0) { + ridx(recv_node[jnode * 2 + 0]) = ridx_inode; + } + else { + ++failed; + errstream << "\n[" << mpi::rank() << "] " + << "Node with global index " << gidx(inode) << " not found on part [" << part(inode) << "]"; + } } } + + if (failed) { + throw_AssertionFailed(errstream.str(), Here()); + } + return nodes.remote_index(); } @@ -981,8 +990,6 @@ Field& build_cells_remote_idx(mesh::Cells& cells, const mesh::Nodes& nodes) { idx_t mypart = static_cast(mpi::rank()); idx_t nparts = static_cast(mpi::size()); - UniqueLonLat compute_uid(nodes); - // This piece should be somewhere central ... could be NPROMA ? // ----------> std::vector proc(nparts); @@ -992,10 +999,29 @@ Field& build_cells_remote_idx(mesh::Cells& cells, const mesh::Nodes& nodes) { // <--------- auto ridx = array::make_indexview(cells.remote_index()); - auto part = array::make_view(cells.partition()); + const auto part = array::make_view(cells.partition()); + const auto gidx = array::make_view(cells.global_index()); + const auto flags = array::make_view(cells.flags()); const auto& element_nodes = cells.node_connectivity(); idx_t nb_cells = cells.size(); + const PeriodicTransform transform_periodic_east(-360.); + const PeriodicTransform transform_periodic_west(+360.); + const UniqueLonLat compute_uid_lonlat(nodes); + auto compute_uid = [&](idx_t jcell) { + constexpr int PERIODIC = util::Topology::PERIODIC; + constexpr int EAST = util::Topology::EAST; + constexpr int WEST = util::Topology::WEST; + const auto flags_view = util::Bitflags::view(flags(jcell)); + if (flags_view.check(PERIODIC | EAST)) { + return compute_uid_lonlat(element_nodes.row(jcell), transform_periodic_east); + } + if (flags_view.check(PERIODIC | WEST)) { + return compute_uid_lonlat(element_nodes.row(jcell), transform_periodic_west); + } + return compute_uid_lonlat(element_nodes.row(jcell)); + }; + idx_t varsize = 2; std::vector> send_needed(mpi::size()); @@ -1003,7 +1029,7 @@ Field& build_cells_remote_idx(mesh::Cells& cells, const mesh::Nodes& nodes) { int sendcnt = 0; std::map lookup; for (idx_t jcell = 0; jcell < nb_cells; ++jcell) { - uid_t uid = compute_uid(element_nodes.row(jcell)); + uid_t uid = compute_uid(jcell); if (idx_t(part(jcell)) == mypart) { lookup[uid] = jcell; @@ -1034,38 +1060,39 @@ Field& build_cells_remote_idx(mesh::Cells& cells, const mesh::Nodes& nodes) { for (idx_t jpart = 0; jpart < nparts; ++jpart) { const std::vector& recv_cell = recv_needed[proc[jpart]]; const idx_t nb_recv_cells = idx_t(recv_cell.size()) / varsize; - // array::ArrayView recv_node( make_view( Array::wrap(shape, - // recv_needed[ proc[jpart] ].data()) ), - // array::make_shape(recv_needed[ proc[jpart] ].size()/varsize,varsize) - // ); for (idx_t jcell = 0; jcell < nb_recv_cells; ++jcell) { uid_t uid = recv_cell[jcell * varsize + 0]; int icell = recv_cell[jcell * varsize + 1]; - if (lookup.count(uid)) { - send_found[proc[jpart]].push_back(icell); - send_found[proc[jpart]].push_back(lookup[uid]); - } - else { - std::stringstream msg; - msg << "[" << mpi::rank() << "] " - << "Node requested by rank [" << jpart << "] with uid [" << uid - << "] that should be owned is not found"; - throw_Exception(msg.str(), Here()); - } + send_found[proc[jpart]].push_back(icell); + send_found[proc[jpart]].push_back(lookup.count(uid) ? lookup[uid] : -1); } } ATLAS_TRACE_MPI(ALLTOALL) { mpi::comm().allToAll(send_found, recv_found); } + std::stringstream errstream; + size_t failed{0}; for (idx_t jpart = 0; jpart < nparts; ++jpart) { const std::vector& recv_cell = recv_found[proc[jpart]]; const idx_t nb_recv_cells = recv_cell.size() / 2; // array::ArrayView recv_node( recv_found[ proc[jpart] ].data(), // array::make_shape(recv_found[ proc[jpart] ].size()/2,2) ); for (idx_t jcell = 0; jcell < nb_recv_cells; ++jcell) { - ridx(recv_cell[jcell * 2 + 0]) = recv_cell[jcell * 2 + 1]; + idx_t icell = recv_cell[jcell * 2 + 0]; + idx_t ridx_icell = recv_cell[jcell * 2 + 1]; + if (ridx_icell >= 0) { + ridx(icell) = ridx_icell; + } + else { + ++failed; + errstream << "\n[" << mpi::rank() << "] " + << "Cell " << gidx(icell) << " not found on part [" << part(icell) << "]"; + } } } + if (failed) { + throw_AssertionFailed(errstream.str(), Here()); + } return cells.remote_index(); } diff --git a/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.cc b/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.cc new file mode 100644 index 000000000..b6d3eea46 --- /dev/null +++ b/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.cc @@ -0,0 +1,371 @@ +/* + * (C) Crown Copyright 2021 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 +#include +#include +#include + +#include "eckit/utils/Hash.h" + +#include "atlas/functionspace/CubedSphereColumns.h" +#include "atlas/grid/CubedSphereGrid.h" +#include "atlas/grid/Distribution.h" +#include "atlas/grid/Partitioner.h" +#include "atlas/library/config.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/mesh/Nodes.h" +#include "atlas/meshgenerator/MeshGenerator.h" +#include "atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.h" +#include "atlas/meshgenerator/detail/MeshGeneratorFactory.h" +#include "atlas/meshgenerator/detail/cubedsphere/CubedSphereUtility.h" +#include "atlas/parallel/mpi/mpi.h" + +#define DEBUG_OUTPUT 0 +#define DEBUG_OUTPUT_DETAIL 0 + +namespace atlas { +namespace meshgenerator { + +// ----------------------------------------------------------------------------- + +CubedSphereDualMeshGenerator::CubedSphereDualMeshGenerator(const eckit::Parametrisation& p) { + configure_defaults(); + + // Get number of partitions. + size_t nb_parts; + if (p.get("nb_parts", nb_parts)) { + options.set("nb_parts", nb_parts); + } + + // Get this partition. + int part; + if (p.get("part", part)) { + options.set("part", part); + } + + // Get halo size. + int halo; + if (p.get("halo", halo)) { + options.set("halo", halo); + } + + // Get partitioner. + std::string partitioner; + if (p.get("partitioner", partitioner)) { + options.set("partitioner", partitioner); + } +} + +// ----------------------------------------------------------------------------- + + +void CubedSphereDualMeshGenerator::configure_defaults() { + // This option sets number of partitions. + options.set("nb_parts", mpi::size()); + + // This option sets the part that will be generated. + options.set("part", mpi::rank()); + + // This options sets the number of halo elements around each partition. + options.set("halo", 0); + + // This options sets the default partitioner. + options.set("partitioner", "cubedsphere"); +} + +// ----------------------------------------------------------------------------- + +void CubedSphereDualMeshGenerator::generate(const Grid& grid, Mesh& mesh) const { + // Get partitioner type and number of partitions from config. + const idx_t nParts = static_cast(options.get("nb_parts")); + const std::string partType = options.get("partitioner"); + + auto partConfig = util::Config{}; + partConfig.set("type", partType); + partConfig.set("partitions", nParts); + + // Use lonlat instead of xy for non cubedsphere partitioner. + if (partType != "cubedsphere") { + partConfig.set("coordinates", "lonlat"); + } + + // Set distribution. + const auto partitioner = grid::Partitioner(partConfig); + const auto distribution = grid::Distribution(grid, partitioner); + + generate(grid, distribution, mesh); +} + +// ----------------------------------------------------------------------------- + +void CubedSphereDualMeshGenerator::generate(const Grid& grid, const grid::Distribution& distribution, + Mesh& mesh) const { + // Check for correct grid and need for mesh + ATLAS_ASSERT(!mesh.generated()); + + // Cast grid to cubed sphere grid. + const auto csGrid = CubedSphereGrid(grid); + + // Check for successful cast. + if (!csGrid) { + throw_Exception("CubedSphereDualMeshGenerator can only work with a cubedsphere grid.", Here()); + } + + // Check for correct grid stagger. + if (csGrid.stagger() != "C") { + throw_Exception("CubedSphereDualMeshGenerator will only work with a cell-centroid grid.", Here()); + } + + // Enforce compatible halo size. + if (options.get("halo") != 0) { + throw_Exception( + "Halo size CubedSphereDualMeshGenerator is currently " + "limited to 0.", + Here()); + } + + // Clone some grid properties. + setGrid(mesh, csGrid, distribution); + + generate_mesh(csGrid, distribution, mesh); +} + +// ----------------------------------------------------------------------------- + +namespace { + +// Helper function to copy fields. +template +void copyField(const Field& sourceField, Field& targetField) { + // Assign source field values to target field. + array::make_view(targetField).assign(array::make_view(sourceField)); +} + +} // namespace + +void CubedSphereDualMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, const grid::Distribution& distribution, + Mesh& mesh) const { + ATLAS_TRACE("CubedSphereDualMeshGenerator::generate"); + + using namespace detail::cubedsphere; + + const idx_t N = csGrid.N(); + const idx_t nHalo = options.get("halo"); + + //-------------------------------------------------------------------------- + // Create a cubed-sphere mesh. + //-------------------------------------------------------------------------- + + // Generate cubed sphere mesh. + auto csOptions = options; + csOptions.set("halo", nHalo + 1); + const auto csMesh = MeshGenerator("cubedsphere", csOptions).generate(csGrid, distribution); + + // Generate fucntionspaces cubed sphere mesh. + const auto csCellsFunctionSpace = functionspace::CubedSphereCellColumns(csMesh); + const auto csNodesFunctionSpace = functionspace::CubedSphereNodeColumns(csMesh); + const auto& csCells = csCellsFunctionSpace.cells(); + const auto& csNodes = csNodesFunctionSpace.nodes(); + + //-------------------------------------------------------------------------- + // Set dual mesh nodes (easy part). + //-------------------------------------------------------------------------- + + auto& nodes = mesh.nodes(); + nodes.resize(csCellsFunctionSpace.size()); + + nodes.add(Field("tij", array::make_datatype(), array::make_shape(nodes.size(), 3))); + + // Copy mesh fields to dual mesh. + copyField(csCells.global_index(), nodes.global_index()); + copyField(csCells.remote_index(), nodes.remote_index()); + copyField(csCells.partition(), nodes.partition()); + copyField(csCells.halo(), nodes.halo()); + copyField(csCells.flags(), nodes.flags()); + copyField(csCells.field("ghost"), nodes.ghost()); + copyField(csCells.field("xy"), nodes.xy()); + copyField(csCells.field("lonlat"), nodes.lonlat()); + copyField(csCells.field("tij"), nodes.field("tij")); + + // Need to decrement halo by one. + auto nodesHalo = array::make_view(nodes.halo()); + + for (idx_t idx = 0; idx < nodes.size(); ++idx) { + nodesHalo(idx) = std::max(0, nodesHalo(idx) - 1); + } + + //-------------------------------------------------------------------------- + // Set dual mesh cells (not so easy part). + //-------------------------------------------------------------------------- + + // Make views to cubed sphere nodes. + const auto csNodesHalo = array::make_view(csNodes.halo()); + const auto csNodesTij = array::make_view(csNodes.field("tij")); + + // Figure out element types. + enum struct ElemType + { + tri, + quad + }; + const auto getType = [&](idx_t idx) -> ElemType { + // Nodes of csMesh are cells of dual mesh. + const idx_t i = csNodesTij(idx, Coordinates::I); + const idx_t j = csNodesTij(idx, Coordinates::J); + auto cornerCell = (i == 0 && j == 0) || (i == N && j == 0) || (i == N && j == N) || (i == 0 && j == N); + return cornerCell ? ElemType::tri : ElemType::quad; + }; + + // Set first element type. ( first = type, second = count ) + auto typeCounts = std::vector >{std::make_pair(getType(0), 1)}; + + // Count the number of consecutive triangles and quadtrilaterals in dual mesh. + // This is an attempt to keep dual mesh cells in the same order as mesh nodes. + // Otherwise, the halo exchange bookkeeping is invalidated. + for (idx_t idx = 1; idx < csNodes.size(); ++idx) { + // Exclude outer ring of cubed sphere mesh halo. + if (csNodesHalo(idx) == nHalo + 1) { + break; + } + + // Get the element type. + const auto elemType = getType(idx); + + // Increment counter if this elemType is the same as last one + if (elemType == typeCounts.back().first) { + ++typeCounts.back().second; + } + // Otherwise add a new counter. + else { + typeCounts.emplace_back(elemType, 1); + } + } + + // Add cells to mesh. + auto& cells = mesh.cells(); + idx_t nCells{}; + // Loop through type counters. + for (const auto& typeCount : typeCounts) { + // Select element type. + switch (typeCount.first) { + // Add a batch of triangles. + case ElemType::tri: { + cells.add(new mesh::temporary::Triangle(), typeCount.second); + break; + } + // Add a batch quadrilaterals. + case ElemType::quad: { + cells.add(new mesh::temporary::Quadrilateral(), typeCount.second); + break; + } + } + // Increment the total number of cells. + nCells += typeCount.second; + } + + // Add extra fields to cells. + cells.add(Field("tij", array::make_datatype(), array::make_shape(cells.size(), 3))); + + cells.add(Field("xy", array::make_datatype(), array::make_shape(cells.size(), 2))); + + cells.add(Field("lonlat", array::make_datatype(), array::make_shape(cells.size(), 2))); + + cells.add(Field("ghost", array::make_datatype(), array::make_shape(cells.size()))); + + // Copy dual cells fields from nodes. + copyField(csNodes.global_index(), cells.global_index()); + copyField(csNodes.remote_index(), cells.remote_index()); + copyField(csNodes.partition(), cells.partition()); + copyField(csNodes.ghost(), cells.field("ghost")); + copyField(csNodes.halo(), cells.halo()); + copyField(csNodes.flags(), cells.flags()); + copyField(csNodes.field("tij"), cells.field("tij")); + copyField(csNodes.xy(), cells.field("xy")); + copyField(csNodes.lonlat(), cells.field("lonlat")); + + // Get node connectivity. + auto& nodeConnectivity = cells.node_connectivity(); + + // Loop over dual mesh cells and set connectivity. + for (idx_t idx = 0; idx < nCells; ++idx) { + const idx_t t = csNodesTij(idx, Coordinates::T); + const idx_t i = csNodesTij(idx, Coordinates::I); + const idx_t j = csNodesTij(idx, Coordinates::J); + + + // Declare vector of surrounding nodes. + auto cellNodes = std::vector{}; + + // Get number of nodes per element. + const auto nNodes = cells.node_connectivity().row(idx).size(); + // Element is a triangle. + if (nNodes == 3) { + // Bottom-left corner. + if (i == 0 && j == 0) { + cellNodes = {csCellsFunctionSpace.index(t, 0, -1), csCellsFunctionSpace.index(t, 0, 0), + csCellsFunctionSpace.index(t, -1, 0)}; + } + // Bottom-right corner. + else if (i == N && j == 0) { + cellNodes = {csCellsFunctionSpace.index(t, N - 1, -1), csCellsFunctionSpace.index(t, N, 0), + csCellsFunctionSpace.index(t, N - 1, 0)}; + } + // Top-right corner. + else if (i == N && j == N) { + cellNodes = {csCellsFunctionSpace.index(t, N - 1, N - 1), csCellsFunctionSpace.index(t, N, N - 1), + csCellsFunctionSpace.index(t, N - 1, N)}; + } + // Top-left corner. + else { + cellNodes = {csCellsFunctionSpace.index(t, -1, N - 1), csCellsFunctionSpace.index(t, 0, N - 1), + csCellsFunctionSpace.index(t, 0, N)}; + } + } + // Element is a quadtrilateral. + else if (nNodes == 4) { + cellNodes = {csCellsFunctionSpace.index(t, i - 1, j - 1), csCellsFunctionSpace.index(t, i, j - 1), + csCellsFunctionSpace.index(t, i, j), csCellsFunctionSpace.index(t, i - 1, j)}; + } + // Couldn't determine element type. + else { + ATLAS_THROW_EXCEPTION("Could not determine element type for cell " + std::to_string(idx) + ".";); + } + + nodeConnectivity.set(idx, cellNodes.data()); + } + + // Set metadata. + mesh.metadata().set("halo", nHalo); + mesh.metadata().set("halo_locked", true); + mesh.nodes().metadata().set("parallel", true); + mesh.cells().metadata().set("parallel", true); + + return; +} + + +// ----------------------------------------------------------------------------- + +void CubedSphereDualMeshGenerator::hash(eckit::Hash& h) const { + h.add("CubedSphereDualMeshGenerator"); + options.hash(h); +} + +// ----------------------------------------------------------------------------- + +namespace { +static MeshGeneratorBuilder CubedSphereDualMeshGenerator( + CubedSphereDualMeshGenerator::static_type()); +} + +// ----------------------------------------------------------------------------- + +} // namespace meshgenerator +} // namespace atlas diff --git a/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.h b/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.h new file mode 100644 index 000000000..8a1f6cda9 --- /dev/null +++ b/src/atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.h @@ -0,0 +1,66 @@ +/* + * (C) Crown Copyright 2021 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 "atlas/array.h" +#include "atlas/meshgenerator/MeshGenerator.h" +#include "atlas/meshgenerator/detail/MeshGeneratorImpl.h" +#include "atlas/util/Config.h" +#include "atlas/util/Metadata.h" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace eckit { +class Parametrisation; +} + +namespace atlas { +class CubedSphereGrid; +class Mesh; +template +class vector; +} // namespace atlas + +namespace atlas { +namespace grid { +class Distribution; +} // namespace grid +} // namespace atlas +#endif + +namespace atlas { +namespace meshgenerator { + +//-------------------------------------------------------------------------------------------------- + +class CubedSphereDualMeshGenerator : public MeshGenerator::Implementation { +public: + CubedSphereDualMeshGenerator(const eckit::Parametrisation& = util::NoConfig()); + + virtual void generate(const Grid&, const grid::Distribution&, Mesh&) const override; + virtual void generate(const Grid&, Mesh&) const override; + + using MeshGenerator::Implementation::generate; + + static std::string static_type() { return "cubedsphere_dual"; } + std::string type() const override { return static_type(); } + +private: + virtual void hash(eckit::Hash&) const override; + + void configure_defaults(); + + void generate_mesh(const CubedSphereGrid&, const grid::Distribution&, Mesh&) const; + +private: + util::Metadata options; +}; + +//-------------------------------------------------------------------------------------------------- + +} // namespace meshgenerator +} // namespace atlas diff --git a/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.cc b/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.cc index 14e589722..6dad6bbc7 100644 --- a/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.cc @@ -49,19 +49,19 @@ CubedSphereMeshGenerator::CubedSphereMeshGenerator(const eckit::Parametrisation& configure_defaults(); // Get number of partitions. - size_t nb_parts; + int nb_parts; if (p.get("nb_parts", nb_parts)) { options.set("nb_parts", nb_parts); } // Get this partition. - size_t part; + int part; if (p.get("part", part)) { options.set("part", part); } // Get halo size. - idx_t halo; + int halo; if (p.get("halo", halo)) { options.set("halo", halo); } @@ -84,7 +84,7 @@ void CubedSphereMeshGenerator::configure_defaults() { options.set("part", mpi::rank()); // This options sets the number of halo elements around each partition. - options.set("halo", idx_t{1}); + options.set("halo", 1); // This options sets the default partitioner. options.set("partitioner", "cubedsphere"); @@ -231,7 +231,7 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons LocalElem* localPtr{}; // Pointer to local element. gidx_t globalIdx{undefinedGlobalIdx}; // Global index. idx_t remoteIdx{undefinedIdx}; // Remote index. - idx_t part{undefinedIdx}; // Partition. + int part{undefinedIdx}; // Partition. }; // Struct to store additional information of local cells/nodes. @@ -240,7 +240,7 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons struct LocalElem { GlobalElem* globalPtr{}; // Pointer to global element. ElemType type{ElemType::UNDEFINED}; // Cell/node Type. - idx_t halo{undefinedIdx}; // Halo level. + int halo{undefinedIdx}; // Halo level. idx_t t{}; // t, i and j. idx_t i{}; idx_t j{}; @@ -258,7 +258,7 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons const idx_t N = csGrid.N(); // Get size of halo. - idx_t nHalo = 0; + int nHalo = 0; options.get("halo", nHalo); // Unique non-halo nodes and cells. @@ -278,8 +278,8 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons const auto jacobian = NeighbourJacobian(csGrid); // Get partition information. - const size_t nParts = options.get("nb_parts"); - const size_t thisPart = options.get("part"); + const int nParts = options.get("nb_parts"); + const int thisPart = options.get("part"); // Define an index counter. const auto idxSum = [](const std::vector& idxCounts) -> idx_t { @@ -367,7 +367,7 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons auto globalCells = std::vector(static_cast(nCellsArray)); // Initialise bounding box. - auto cellBounds = std::vector(static_cast(6)); + auto cellBounds = std::vector(6); // Set tij grid iterator. auto tijIt = csGrid.tij().begin(); @@ -830,10 +830,13 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons Field lonLatField = cells.add(Field("lonlat", make_datatype(), make_shape(cells.size(), 2))); lonLatField.set_variables(2); + Field ghostField = cells.add(Field("ghost", make_datatype(), make_shape(cells.size()))); + // Set field views. auto cellsGlobalIdx = array::make_view(cells.global_index()); auto cellsRemoteIdx = array::make_indexview(cells.remote_index()); auto cellsPart = array::make_view(cells.partition()); + auto cellsGhost = array::make_view(ghostField); auto cellsHalo = array::make_view(cells.halo()); auto cellsFlags = array::make_view(cells.flags()); auto cellsTij = array::make_view(tijField); @@ -894,6 +897,9 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons // Set halo. cellsHalo(cellLocalIdx) = localCell.halo; + // Set ghost. + cellsGhost(cellLocalIdx) = cellsHalo(cellLocalIdx) > 0; + // Set flags. Topology::reset(cellsFlags(cellLocalIdx)); switch (localCell.type) { @@ -921,16 +927,48 @@ void CubedSphereMeshGenerator::generate_mesh(const CubedSphereGrid& csGrid, cons // Done. That was rather a lot of bookkeeping! // --------------------------------------------------------------------------- + set_metadata(mesh); + + return; +} + +// ----------------------------------------------------------------------------- + +void CubedSphereMeshGenerator::set_metadata(Mesh& mesh) const { + const auto nHalo = options.get("halo"); + + // Set basic halo metadata. mesh.metadata().set("halo", nHalo); mesh.metadata().set("halo_locked", true); - mesh.nodes().metadata().set("parallel", true); - mesh.nodes().metadata().set("nb_owned", nodeLocalIdxCount[thisPart]); - mesh.cells().metadata().set("parallel", true); - mesh.cells().metadata().set("nb_owned", cellLocalIdxCount[thisPart]); - return; + // Loop over nodes and count number of halo elements. + auto nNodes = std::vector(nHalo + 1, 0); + const auto nodeHalo = array::make_view(mesh.nodes().halo()); + for (idx_t i = 0; i < mesh.nodes().size(); ++i) { + ++nNodes[static_cast(nodeHalo(i))]; + } + std::partial_sum(nNodes.begin(), nNodes.end(), nNodes.begin()); + + // Set node halo metadata. + for (size_t i = 0; i < nNodes.size(); ++i) { + const auto str = "nb_nodes_including_halo[" + std::to_string(i) + "]"; + mesh.metadata().set(str, nNodes[i]); + } + // Loop over cells and count number of halo elements. + auto nCells = std::vector(nHalo + 1, 0); + const auto cellHalo = array::make_view(mesh.cells().halo()); + for (idx_t i = 0; i < mesh.cells().size(); ++i) { + ++nCells[static_cast(cellHalo(i))]; + } + std::partial_sum(nCells.begin(), nCells.end(), nCells.begin()); + + // Set cell halo metadata. + for (size_t i = 0; i < nCells.size(); ++i) { + const auto str = "nb_cells_including_halo[0][" + std::to_string(i) + "]"; + mesh.metadata().set(str, nCells[i]); + } } // ----------------------------------------------------------------------------- diff --git a/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.h b/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.h index 9bfa0e40d..a350ea847 100644 --- a/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.h +++ b/src/atlas/meshgenerator/detail/CubedSphereMeshGenerator.h @@ -56,6 +56,8 @@ class CubedSphereMeshGenerator : public MeshGenerator::Implementation { void generate_mesh(const CubedSphereGrid&, const grid::Distribution&, Mesh&) const; + void set_metadata(Mesh&) const; + private: util::Metadata options; }; diff --git a/src/atlas/meshgenerator/detail/HealpixMeshGenerator.cc b/src/atlas/meshgenerator/detail/HealpixMeshGenerator.cc index 63ee66136..0e082b2eb 100644 --- a/src/atlas/meshgenerator/detail/HealpixMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/HealpixMeshGenerator.cc @@ -369,6 +369,8 @@ void HealpixMeshGenerator::hash(eckit::Hash& h) const { } void HealpixMeshGenerator::generate(const Grid& grid, const grid::Distribution& distribution, Mesh& mesh) const { + ATLAS_TRACE(); + Log::debug() << "HealpixMeshGenerator generating mesh from " << grid.name() << std::endl; ATLAS_ASSERT(HealpixGrid(grid), "Grid could not be cast to a HealpixGrid"); ATLAS_ASSERT(!mesh.generated()); @@ -586,23 +588,24 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: ++ncells; + const int glb2loc_ghost_offset = -nnodes_SB + iy_max + 12 * ns * ns + 17; // mark upper corner iil = up_idx(ix, iy, ns); - iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : -nnodes_SB + iy_max + 12 * ns * ns + 17); + iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : glb2loc_ghost_offset); if (!is_node_SB[iil]) { ++nnodes; is_node_SB[iil] = true; } // mark lower corner iil = down_idx(ix, iy, ns); - iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : -nnodes_SB + iy_max + 12 * ns * ns + 17); + iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : glb2loc_ghost_offset); if (!is_node_SB[iil]) { ++nnodes; is_node_SB[iil] = true; } // mark right corner iil = right_idx(ix, iy, ns); - iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : -nnodes_SB + iy_max + 12 * ns * ns + 17); + iil -= (iil < 12 * ns * ns + 16 ? parts_sidx : glb2loc_ghost_offset); if (!is_node_SB[iil]) { ++nnodes; is_node_SB[iil] = true; @@ -616,8 +619,8 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: ii_ghost = nnodes_SB - (iy_max - iy_min + 1); for (ii = 0; ii < iy_max - iy_min + 1; ii++) { if (!is_node_SB[ii_ghost + ii]) { - is_node_SB[ii_ghost + ii] = true; - ++nnodes; + // is_node_SB[ii_ghost + ii] = true; + // ++nnodes; } } @@ -643,6 +646,7 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: auto remote_idx = array::make_indexview(nodes.remote_index()); auto part = array::make_view(nodes.partition()); auto ghost = array::make_view(nodes.ghost()); + auto halo = array::make_view(nodes.halo()); auto flags = array::make_view(nodes.flags()); // define cells and associated properties @@ -739,6 +743,7 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: part(inode) = parts_SB[iil]; ghost(inode) = is_ghost_SB[iil]; + halo(inode) = 0; if (ghost(inode)) { Topology::set(flags(inode), Topology::GHOST); @@ -747,6 +752,9 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: else { remote_idx(inode) = -1; } + if (Topology::check(flags(inode), Topology::BC | Topology::EAST)) { + part(inode) = mypart; // To be fixed later + } local_idx_SB[iil] = inode; #if DEBUG_OUTPUT_DETAIL @@ -790,8 +798,8 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: cells_part(jcell) = mypart; #if DEBUG_OUTPUT_DETAIL std::cout << "[" << mypart << "] : " - << "New quad " << jcell << ": " << quad_nodes[0] << "," << quad_nodes[1] << "," - << quad_nodes[2] << "," << quad_nodes[3] << std::endl; + << "New quad " << jcell << ": " << glb_idx(quad_nodes[0]) << "," << glb_idx(quad_nodes[1]) + << "," << glb_idx(quad_nodes[2]) << "," << glb_idx(quad_nodes[3]) << std::endl; #endif ++jcell; } @@ -811,8 +819,9 @@ void HealpixMeshGenerator::generate_mesh(const StructuredGrid& grid, const grid: int* cell_nodes; for (jcell = 0; jcell < ncells; jcell++) { std::cout << "[" << mypart << "] : " - << " cell " << jcell << ": " << node_connectivity(jcell, 0) << "," << node_connectivity(jcell, 1) - << "," << node_connectivity(jcell, 2) << "," << node_connectivity(jcell, 3) << std::endl; + << " cell " << jcell << ": " << glb_idx(node_connectivity(jcell, 0)) << "," + << glb_idx(node_connectivity(jcell, 1)) << "," << glb_idx(node_connectivity(jcell, 2)) << "," + << glb_idx(node_connectivity(jcell, 3)) << std::endl; } #endif diff --git a/src/atlas/meshgenerator/detail/MeshGeneratorFactory.cc b/src/atlas/meshgenerator/detail/MeshGeneratorFactory.cc index b74fd7bdf..dfc453637 100644 --- a/src/atlas/meshgenerator/detail/MeshGeneratorFactory.cc +++ b/src/atlas/meshgenerator/detail/MeshGeneratorFactory.cc @@ -10,6 +10,7 @@ #include +#include "atlas/meshgenerator/detail/CubedSphereDualMeshGenerator.h" #include "atlas/meshgenerator/detail/CubedSphereMeshGenerator.h" #include "atlas/meshgenerator/detail/DelaunayMeshGenerator.h" #include "atlas/meshgenerator/detail/HealpixMeshGenerator.h" @@ -28,6 +29,7 @@ void force_link() { static struct Link { Link() { MeshGeneratorBuilder(); + MeshGeneratorBuilder(); MeshGeneratorBuilder(); MeshGeneratorBuilder(); MeshGeneratorBuilder(); diff --git a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc index 2d21bfa4e..a32d43a9e 100644 --- a/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc +++ b/src/atlas/meshgenerator/detail/StructuredMeshGenerator.cc @@ -218,6 +218,7 @@ void StructuredMeshGenerator::hash(eckit::Hash& h) const { void StructuredMeshGenerator::generate(const Grid& grid, const grid::Distribution& distribution, Mesh& mesh) const { ATLAS_TRACE(); + Log::debug() << "StructuredMeshGenerator generating mesh from " << grid.name() << std::endl; const StructuredGrid rg = StructuredGrid(grid); if (!rg) { diff --git a/src/atlas/projection/detail/ProjectionFactory.cc b/src/atlas/projection/detail/ProjectionFactory.cc index 18d572534..226649639 100644 --- a/src/atlas/projection/detail/ProjectionFactory.cc +++ b/src/atlas/projection/detail/ProjectionFactory.cc @@ -21,6 +21,7 @@ #include "atlas/projection/detail/LonLatProjection.h" #include "atlas/projection/detail/MercatorProjection.h" #include "atlas/projection/detail/SchmidtProjection.h" +#include "atlas/projection/detail/VariableResolutionProjection.h" namespace atlas { namespace projection { @@ -38,6 +39,8 @@ void force_link() { ProjectionBuilder(); ProjectionBuilder(); ProjectionBuilder(); + ProjectionBuilder(); + ProjectionBuilder(); ProjectionBuilder(); ProjectionBuilder(); } diff --git a/src/atlas/projection/detail/ProjectionImpl.cc b/src/atlas/projection/detail/ProjectionImpl.cc index a4434d4b1..9b8264ac7 100644 --- a/src/atlas/projection/detail/ProjectionImpl.cc +++ b/src/atlas/projection/detail/ProjectionImpl.cc @@ -10,6 +10,7 @@ #include #include +#include #include #include diff --git a/src/atlas/projection/detail/ProjectionImpl.h b/src/atlas/projection/detail/ProjectionImpl.h index 655134b11..fcb586269 100644 --- a/src/atlas/projection/detail/ProjectionImpl.h +++ b/src/atlas/projection/detail/ProjectionImpl.h @@ -10,9 +10,12 @@ #pragma once +#include +#include #include #include +#include "atlas/runtime/Exception.h" #include "atlas/util/Factory.h" #include "atlas/util/NormaliseLongitude.h" #include "atlas/util/Object.h" @@ -41,97 +44,74 @@ class ProjectionImpl : public util::Object { using Spec = atlas::util::Config; class Jacobian : public std::array, 2> { public: - static Jacobian identity() { - Jacobian id; - id[0] = {1.0, 0.0}; - id[1] = {0.0, 1.0}; - return id; - } + using std::array, 2>::array; - Jacobian inverse() const { - const Jacobian& jac = *this; - Jacobian inv; - double det = jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0]; - inv[0][0] = +jac[1][1] / det; - inv[0][1] = -jac[0][1] / det; - inv[1][0] = -jac[1][0] / det; - inv[1][1] = +jac[0][0] / det; - return inv; - }; + static Jacobian identity() { return Jacobian{1., 0., 0., 1.}; } - Jacobian transpose() const { - Jacobian tra = *this; - std::swap(tra[0][1], tra[1][0]); - return tra; - }; + Jacobian() = default; - Jacobian operator-(const Jacobian& jac2) const { - const Jacobian& jac1 = *this; - Jacobian jac; - jac[0][0] = jac1[0][0] - jac2[0][0]; - jac[0][1] = jac1[0][1] - jac2[0][1]; - jac[1][0] = jac1[1][0] - jac2[1][0]; - jac[1][1] = jac1[1][1] - jac2[1][1]; - return jac; + Jacobian(double j00, double j01, double j10, double j11) { + (*this)[0][0] = j00; + (*this)[0][1] = j01; + (*this)[1][0] = j10; + (*this)[1][1] = j11; } - Jacobian operator+(const Jacobian& jac2) const { - const Jacobian& jac1 = *this; - Jacobian jac; - jac[0][0] = jac1[0][0] + jac2[0][0]; - jac[0][1] = jac1[0][1] + jac2[0][1]; - jac[1][0] = jac1[1][0] + jac2[1][0]; - jac[1][1] = jac1[1][1] + jac2[1][1]; - return jac; - } + Jacobian(std::initializer_list> list): + Jacobian{*(list.begin()->begin()), *(list.begin()->begin() + 1), *((list.begin() + 1)->begin()), + *((list.begin() + 1)->begin() + 1)} {} - double norm() const { - const Jacobian& jac = *this; - return sqrt(jac[0][0] * jac[0][0] + jac[0][1] * jac[0][1] + jac[1][0] * jac[1][0] + jac[1][1] * jac[1][1]); + Jacobian operator-(const Jacobian& jac) const { + return Jacobian{(*this)[0][0] - jac[0][0], (*this)[0][1] - jac[0][1], (*this)[1][0] - jac[1][0], + (*this)[1][1] - jac[1][1]}; } - Jacobian operator*(const Jacobian& jac2) const { - const Jacobian& jac1 = *this; - Jacobian jac; - jac[0][0] = jac1[0][0] * jac2[0][0] + jac1[0][1] * jac2[1][0]; - jac[0][1] = jac1[0][0] * jac2[0][1] + jac1[0][1] * jac2[1][1]; - jac[1][0] = jac1[1][0] * jac2[0][0] + jac1[1][1] * jac2[1][0]; - jac[1][1] = jac1[1][0] * jac2[0][1] + jac1[1][1] * jac2[1][1]; - return jac; + Jacobian operator+(const Jacobian& jac) const { + return Jacobian{(*this)[0][0] + jac[0][0], (*this)[0][1] + jac[0][1], (*this)[1][0] + jac[1][0], + (*this)[1][1] + jac[1][1]}; } - double dx_dlon() const { - const Jacobian& jac = *this; - return jac[JDX][JDLON]; - } - double dy_dlon() const { - const Jacobian& jac = *this; - return jac[JDY][JDLON]; - } - double dx_dlat() const { - const Jacobian& jac = *this; - return jac[JDX][JDLAT]; + Jacobian operator*(double a) const { + return Jacobian{(*this)[0][0] * a, (*this)[0][1] * a, (*this)[1][0] * a, (*this)[1][1] * a}; } - double dy_dlat() const { - const Jacobian& jac = *this; - return jac[JDY][JDLAT]; + + Jacobian operator*(const Jacobian& jac) const { + return Jacobian{(*this)[0][0] * jac[0][0] + (*this)[0][1] * jac[1][0], + (*this)[0][0] * jac[0][1] + (*this)[0][1] * jac[1][1], + (*this)[1][0] * jac[0][0] + (*this)[1][1] * jac[1][0], + (*this)[1][0] * jac[0][1] + (*this)[1][1] * jac[1][1]}; } - double dlon_dx() const { - const Jacobian& jac = *this; - return jac[JDLON][JDX]; + Point2 operator*(const Point2& x) const { + return Point2{(*this)[0][0] * x[0] + (*this)[0][1] * x[1], (*this)[1][0] * x[0] + (*this)[1][1] * x[1]}; } - double dlon_dy() const { - const Jacobian& jac = *this; - return jac[JDLON][JDY]; + + double norm() const { + return std::sqrt((*this)[0][0] * (*this)[0][0] + (*this)[0][1] * (*this)[0][1] + + (*this)[1][0] * (*this)[1][0] + (*this)[1][1] * (*this)[1][1]); } - double dlat_dx() const { - const Jacobian& jac = *this; - return jac[JDLAT][JDX]; + + double determinant() const { return (*this)[0][0] * (*this)[1][1] - (*this)[0][1] * (*this)[1][0]; } + + Jacobian inverse() const { + return Jacobian{(*this)[1][1], -(*this)[0][1], -(*this)[1][0], (*this)[0][0]} * (1. / determinant()); } - double dlat_dy() const { - const Jacobian& jac = *this; - return jac[JDLAT][JDY]; + + Jacobian transpose() const { return Jacobian{(*this)[1][1], (*this)[0][1], (*this)[1][0], (*this)[0][0]}; } + + double dx_dlon() const { return (*this)[JDX][JDLON]; } + double dy_dlon() const { return (*this)[JDY][JDLON]; } + double dx_dlat() const { return (*this)[JDX][JDLAT]; } + double dy_dlat() const { return (*this)[JDY][JDLAT]; } + + double dlon_dx() const { return (*this)[JDLON][JDX]; } + double dlon_dy() const { return (*this)[JDLON][JDY]; } + double dlat_dx() const { return (*this)[JDLAT][JDX]; } + double dlat_dy() const { return (*this)[JDLAT][JDY]; } + + friend std::ostream& operator<<(std::ostream& os, const Jacobian& jac) { + os << jac[0][0] << " " << jac[0][1] << "\n" << jac[1][0] << " " << jac[1][1]; + return os; } private: @@ -147,6 +127,7 @@ class ProjectionImpl : public util::Object { }; }; + public: static const ProjectionImpl* create(const eckit::Parametrisation& p); static const ProjectionImpl* create(const std::string& type, const eckit::Parametrisation& p); diff --git a/src/atlas/projection/detail/SchmidtProjection.cc b/src/atlas/projection/detail/SchmidtProjection.cc index 593aee191..16a6cb7aa 100644 --- a/src/atlas/projection/detail/SchmidtProjection.cc +++ b/src/atlas/projection/detail/SchmidtProjection.cc @@ -7,7 +7,6 @@ * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ - #include #include "eckit/config/Parametrisation.h" @@ -33,6 +32,14 @@ namespace atlas { namespace projection { namespace detail { +PointLonLat rotation_north_pole(const Rotated& rotation) { + return rotation.northPole(); +} + +PointLonLat rotation_north_pole(const NotRotated& rotation) { + return PointLonLat{0., 90.}; +} + // constructor template SchmidtProjectionT::SchmidtProjectionT(const eckit::Parametrisation& params): @@ -40,6 +47,12 @@ SchmidtProjectionT::SchmidtProjectionT(const eckit::Parametrisation& p if (!params.get("stretching_factor", c_)) { throw_Exception("stretching_factor missing in Params", Here()); } + ATLAS_ASSERT(c_ != 0.); + + north0_ = {0.0, 0.0, 1.0}; + + atlas::util::UnitSphere::convertSphericalToCartesian(rotation_north_pole(rotation_), north1_); + north1_ = PointXYZ::normalize(north1_); } // constructor @@ -75,36 +88,34 @@ ProjectionImpl::Jacobian SchmidtProjectionT::jacobian(const PointLonLa lonlat2xy(xy); - PointXYZ xyz, north1, north0(0.0, 0.0, 1.0); - atlas::util::UnitSphere::convertSphericalToCartesian(rotation_.northPole(), north1); + PointXYZ xyz; atlas::util::UnitSphere::convertSphericalToCartesian(lonlat, xyz); - north1 = PointXYZ::normalize(north1); + + double zomc2 = 1.0 - 1.0 / (c_ * c_); + double zopc2 = 1.0 + 1.0 / (c_ * c_); + + double zcosy = std::cos(D2R(xy[1])); + double zsiny = std::sin(D2R(xy[1])); + double zcoslat = std::cos(D2R(lonlat.lat())); + + double zfactor = std::sqrt((zopc2 + zsiny * zomc2) * (zopc2 + zsiny * zomc2) / (zopc2 * zopc2 - zomc2 * zomc2)); // Base vectors in unrotated frame - auto u0 = PointXYZ::normalize(PointXYZ::cross(north0, xyz)); + auto u0 = PointXYZ::normalize(PointXYZ::cross(north0_, xyz)); auto v0 = PointXYZ::normalize(PointXYZ::cross(xyz, u0)); // Base vectors in rotated frame - auto u1 = PointXYZ::normalize(PointXYZ::cross(north1, xyz)); + auto u1 = PointXYZ::normalize(PointXYZ::cross(north1_, xyz)); auto v1 = PointXYZ::normalize(PointXYZ::cross(xyz, u1)); double u0u1 = PointXYZ::dot(u0, u1); double v0u1 = PointXYZ::dot(v0, u1); + double u0v1 = PointXYZ::dot(u0, v1); double v0v1 = PointXYZ::dot(v0, v1); Jacobian jac; - - double zomc2 = 1.0 - 1.0 / (c_ * c_); - double zopc2 = 1.0 + 1.0 / (c_ * c_); - - double zcosy = cos(D2R(xy[1])), zsiny = sin(D2R(xy[1])); - double zcoslat = cos(D2R(lonlat.lat())); - - double zfactor = sqrt((zopc2 + zsiny * zomc2) * (zopc2 + zsiny * zomc2) / (zopc2 * zopc2 - zomc2 * zomc2)); - - jac[0] = {zcoslat * u0u1 * zfactor / zcosy, v0u1 * zfactor / zcosy}; jac[1] = {zcoslat * u0v1 * zfactor, v0v1 * zfactor}; diff --git a/src/atlas/projection/detail/SchmidtProjection.h b/src/atlas/projection/detail/SchmidtProjection.h index 92200e430..24509e795 100644 --- a/src/atlas/projection/detail/SchmidtProjection.h +++ b/src/atlas/projection/detail/SchmidtProjection.h @@ -49,6 +49,8 @@ class SchmidtProjectionT final : public ProjectionImpl { private: double c_; // stretching factor Rotation rotation_; + PointXYZ north0_; + PointXYZ north1_; }; using SchmidtProjection = SchmidtProjectionT; diff --git a/src/atlas/projection/detail/VariableResolutionProjection.cc b/src/atlas/projection/detail/VariableResolutionProjection.cc new file mode 100644 index 000000000..712b7f266 --- /dev/null +++ b/src/atlas/projection/detail/VariableResolutionProjection.cc @@ -0,0 +1,573 @@ +/** +* (C) Crown copyright 2021, 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 + +#include "eckit/config/Parametrisation.h" +#include "eckit/utils/Hash.h" + +#include "atlas/functionspace/StructuredColumns.h" +#include "atlas/grid.h" +#include "atlas/grid/Grid.h" +#include "atlas/projection/detail/ProjectionFactory.h" +#include "atlas/projection/detail/VariableResolutionProjection.h" +#include "atlas/runtime/Exception.h" +#include "atlas/util/Config.h" +#include "atlas/util/Constants.h" +#include "eckit/testing/Test.h" + +/** +* Projection for LAM stretching +* +* The theory used in this code can be found under: +* Lateral boundary conditions for limited area models +* Terry Davies 2014 +* https://doi.org/10.1002/qj.2127 +* +* Some equations are under: +* The benefits of the Met Office variable resolution NWP model for forecasting convection +* Tang et al. 2013 +* https://doi.org/10.1002/met.1300 +* +* Generally: From a original regular grid the point in the middle of the grid are unchanged, +* based on the boundary of the new internal regular grid. +* The grid length is then stretched at a constant stretching (or inflation) factor r, +* that is Δsi+1 = rΔsi until all the points are mapped in the new grid. +* The domain is then extended to a further uniform coarse resolution region (rim) in the outer domain, +* +* The origin of the xy-system is at (lon,lat) = (0,0) +* +*/ + +#ifdef __NVCOMPILER +#define PREVENT_OPT volatile +#else +#define PREVENT_OPT +#endif + +namespace atlas { +namespace projection { +namespace detail { + +static double new_ratio(int n_stretched, double var_ratio) { + /** + * compute ratio, + * change stretching factor so that high and low grids + * retain original sizes + */ + ///< correction used to change from double to integer + constexpr float epstest = std::numeric_limits::epsilon(); + + ///< number of variable (stretched) grid points in one side + PREVENT_OPT int var_ints = (n_stretched + epstest) / 2.; + double var_ints_f = n_stretched / 2.; + double logr = std::log(var_ratio); + double log_ratio = (var_ints_f - 0.5) * logr; + + return std::exp(log_ratio / var_ints); +}; + + +///< specification parameters +template +typename VariableResolutionProjectionT::Spec VariableResolutionProjectionT::spec() const { + Spec proj_st; + proj_st.set("type", static_type()); + proj_st.set("outer.dx", delta_outer); ///< resolution of the external regular grid (rim) + proj_st.set("inner.dx", delta_inner); ///< resolution of the regional model (regular grid) + proj_st.set("progression", var_ratio_); ///< power used for the stretching + proj_st.set("inner.xmin", x_reg_start_); ///< xstart of the internal regional grid + proj_st.set("inner.ymin", y_reg_start_); ///< ystart of the internal regional grid + proj_st.set("inner.xend", x_reg_end_); ///< xend of the regular part of stretched internal grid + proj_st.set("inner.yend", y_reg_end_); ///< yend of the regular part of stretched internal grid + proj_st.set("outer.xmin", startx_); ///< original domain startx + proj_st.set("outer.xend", endx_); ///< original domain endx + proj_st.set("outer.ymin", starty_); ///< original domain starty + proj_st.set("outer.yend", endy_); ///< original domain endy + proj_st.set("rim_widthx", rim_widthx_); ///< xsize of the rim + proj_st.set("rim_widthy", rim_widthy_); ///< ysize of the rim + rotation_.spec(proj_st); + return proj_st; +} + + +///< constructors +template +VariableResolutionProjectionT::VariableResolutionProjectionT(const eckit::Parametrisation& proj_st): + ProjectionImpl(), rotation_(proj_st) { + proj_st.get("outer.dx", delta_outer = 0.); ///< resolution of the external regular grid (rim) + proj_st.get("inner.dx", delta_inner = 0.); ///< resolution of the regional model (regular grid) + proj_st.get("progression", var_ratio_ = 0.); ///< power used for the stretching + proj_st.get("inner.xmin", x_reg_start_ = 0.); ///< xstart of the internal regional grid + proj_st.get("inner.ymin", y_reg_start_ = 0.); ///< ystart of the internal regional grid + proj_st.get("inner.xend", x_reg_end_ = 0.); ///< xend of the regular part of stretched internal grid + proj_st.get("inner.yend", y_reg_end_ = 0.); ///< yend of the regular part of stretched internal grid + proj_st.get("outer.xmin", startx_ = 0.); ///< original domain startx + proj_st.get("outer.xend", endx_ = 0.); ///< original domain endx + proj_st.get("outer.ymin", starty_ = 0.); ///< original domain starty + proj_st.get("outer.yend", endy_ = 0.); ///< original domain endy + + // To get the rim_widthx_ we can specify either: + // - outer.nx + // - outer.xwidth + // - outer.width + // The value for rim_widthy_ is copied from rim_widthx_ and can be overwritten with + // - outer.ny + // - outer.ywidth + if (proj_st.has("outer.nx")) { + long outer_nx; + proj_st.get("outer.nx", outer_nx); + rim_widthx_ = delta_outer * outer_nx; + rim_widthy_ = rim_widthx_; + } + else if (proj_st.has("outer.width")) { + proj_st.get("outer.width", rim_widthx_); + rim_widthy_ = rim_widthx_; + } + else if (proj_st.has("outer.xwidth")) { + proj_st.get("outer.xwidth", rim_widthx_); + rim_widthy_ = rim_widthx_; + } + if (proj_st.has("outer.ny")) { + long outer_ny; + proj_st.get("outer.ny", outer_ny); + rim_widthy_ = delta_outer * outer_ny; + } + else if (proj_st.has("outer.ywidth")) { + proj_st.get("outer.ywidth", rim_widthy_); + } + + constexpr float epsilon = std::numeric_limits::epsilon(); ///< value used to check if the values are equal + constexpr float epstest = + std::numeric_limits::epsilon(); ///< correction used to change from double to intege + + ///< original domain size includes the points for the rim + deltax_all = (endx_ - startx_); + deltay_all = (endy_ - starty_); + + + nx_stretched = 0; + ny_stretched = 0; + nx_rim = 0; + ny_rim = 0; + + if (var_ratio_ == 1) { + lam_hires_size = deltax_all; + phi_hires_size = deltay_all; + lambda_start = x_reg_start_; + phi_start = y_reg_start_; + } + else { + lam_hires_size = x_reg_end_ - x_reg_start_; + phi_hires_size = y_reg_end_ - y_reg_start_; + + /** + * check for start of the new regular LAM grid x and y + * in the middle of the previous regular grid + */ + + ///< distance end of the grid and internal regular grid + add_xf_ = (deltax_all + epstest - lam_hires_size) / 2.; + add_yf_ = (deltay_all + epstest - phi_hires_size) / 2.; + /** + * Compute the number of points for different part of the grid + * internal regular grid high resolution + * stretched grid + * external regular grid low resolution + * +1 otherwise I have the intervals not the points + */ + nx_rim = rim_widthx_ / delta_outer; + ny_rim = rim_widthy_ / delta_outer; + nx_stretched = ((deltax_all + epstest - lam_hires_size) / delta_inner) - nx_rim; + ny_stretched = ((deltay_all + epstest - phi_hires_size) / delta_inner) - ny_rim; + lambda_start = x_reg_start_; + phi_start = y_reg_start_; + /** + * check if stretched grid is in the middle + * of the previous regular grid + */ + check_x = startx_ + add_xf_ - lambda_start; + check_y = starty_ + add_yf_ - phi_start; + check_st = nx_stretched - ny_stretched; + checkvalue(epsilon, check_x); + checkvalue(epsilon, check_y); + checkvalue(epsilon, check_st); + } + + int nx_ = (deltax_all + epstest) / delta_inner + 1; + int ny_ = (deltay_all + epstest) / delta_inner + 1; + + int nx_inner = (lam_hires_size + epstest) / delta_inner + 1; + int ny_inner = (phi_hires_size + epstest) / delta_inner + 1; + + ATLAS_ASSERT((nx_ - 1) - nx_rim - (nx_inner - 1) == nx_stretched); + ATLAS_ASSERT((ny_ - 1) - ny_rim - (ny_inner - 1) == ny_stretched); + + new_ratio_[0] = var_ratio_; + new_ratio_[1] = var_ratio_; + if (var_ratio_ != 1) { + new_ratio_[0] = new_ratio(nx_stretched, var_ratio_); + new_ratio_[1] = new_ratio(ny_stretched, var_ratio_); + } +} + + +template +void VariableResolutionProjectionT::checkvalue(const double& epsilon, const double& value_check) const { + //err_message = "USER defined limits not in the middle of the area " + str; + if (value_check > epsilon || value_check < (-1. * epsilon)) { + std::string err_message; + std::string str = std::to_string(value_check); + throw eckit::BadValue("USER defined limits not in the middle of the area " + str, Here()); + } +} + +/** + * General stretch from a point in regular grid to the + * a correspective point in a variable grid + */ + +template +double VariableResolutionProjectionT::general_stretch(const double crd, const bool L_long, + const int n_stretched) const { + constexpr float epstest = + std::numeric_limits::epsilon(); ///< correction used to change from double to integer + constexpr double epsrem = + 0.1 * std::numeric_limits::epsilon() / + std::numeric_limits::epsilon(); ///< correction used to part the find a part of an integer + + double inner_size; ///< number of new internal regular grid in double + double inner_start; ///< start of the regular grid + double inner_end; ///< end of the regular grid + double point = crd; ///< starting point + + auto normalised = [L_long](double p) { + if (L_long) { + p = (p < 180) ? p + 360.0 : p; + } + return p; + }; + + /* + * regular grids + */ + if (var_ratio_ == 1) { + return normalised(point); + } + + /* + * SECTION 1 + * INTERNAL REGULAR GRID the point is mapped to the same point + */ + + if (L_long) { + inner_start = x_reg_start_; + inner_size = lam_hires_size; + } + else { + inner_start = y_reg_start_; + inner_size = phi_hires_size; + } + inner_end = inner_start + inner_size; + + if ((point >= inner_start) && (point <= inner_end)) { + return normalised(point); + } + + /* SECTION 2 + * Start variable res 'STRETCHED' + * The point is mapped in a stretched grid related to + * distance from the regular grid and stretch internal grid: delta_dist + */ + + double distance_to_inner = 0.; ///< distance from point to reg. grid + double delta_add = 0.; ///< additional part in stretch different from internal high resolution + int n_high = 0; ///< number of points, from point to reg. grid + int n_high_st = 0; ///< number of stretched points, from point to reg grid + int n_high_rim = 0; ///< number of rim points, from point to reg grid + double p_rem = 0.; ///< remaining part in stretch if distance_to_inner not multiple of delta_high_ + double p_rem_low = 0.; ///< remaining part in rim if distance_to_inner not multiple of delta_high_ + double new_ratio = new_ratio_[L_long ? 1 : 0]; + + if (point < inner_start) { + distance_to_inner = inner_start - point; + } + else if (point > inner_end) { + distance_to_inner = point - inner_end; + } + + /* + * number of high resolution points intervals, that are not + * in the internal regular grid on one side, + * this work just for the part of the stretch not the rim + */ + + ///< always the lowest integer + n_high = (distance_to_inner + epstest) / delta_inner; + + ///< only for the stretched part take out the rim part + if (n_high > n_stretched / 2.) { + n_high_st = (n_stretched / 2.); + n_high_rim = n_high - n_high_st; + p_rem = 0; + p_rem_low = std::fmod((distance_to_inner + epsrem), delta_inner); + } + else { + n_high_st = n_high; + n_high_rim = 0; + ///< part remaining, use modulo + p_rem = std::fmod((distance_to_inner + epsrem), delta_inner); + p_rem_low = 0.; + } + + ///< computation of the new stretched delta integer part + double delta = delta_inner; + ///< initialization if is not using the cycle (first interval) + double delta_last = delta; + double deltacheck = 0; + /* + * using difference in delta for stretch + * The point stretched is not in the regular grid and took out the points for the rim + */ + for (int i = 0; i < n_high_st; i += 1) { + delta_last = delta * new_ratio; + delta_add = delta_last - delta_inner; + delta = delta_last; + deltacheck += delta_add; + } + ///< recomputation of point for every interval + if (point > inner_start) { + /* + * after the end of the internal high resolution grid + * no delta_add in the last points as they are rim + */ + point += deltacheck; + } + else { + /* + * before the begin of the internal high resolution grid + * no delta_add in the first points as they are rim + */ + point -= deltacheck; + } + + ///< SECTION 3 last part of stretch adding the remaing non integer part with the same ratio as in the stretching + double delta_r = p_rem * std::pow(new_ratio, (n_high_st + 1)); + double delta_addr = delta_r - p_rem; + + if (point > inner_start) { + point += delta_addr; + } + else { + point -= delta_addr; + } + ///< SECTION 4 rim area + if (n_high > n_stretched / 2.) { + double delta_l_h_ = 0; + for (int i = 0; i < n_high_rim; i += 1) { + delta_l_h_ += (delta_outer - delta_inner); + } + if (point > inner_start) { + point += (delta_l_h_ + p_rem_low * (delta_outer - delta_inner)); + } + else { + point -= (delta_l_h_ + p_rem_low * (delta_outer - delta_inner)); + } + } + + return normalised(point); +} + + +/** + * Inverse of general stretch from a point in variable grid to the + * a correspective point in a regular grid + */ + +template +double VariableResolutionProjectionT::general_stretch_inv(const double crd, const bool L_long, + const int n_stretched) const { + constexpr float epstest = + std::numeric_limits::epsilon(); ///< correction used to change from double to integer + + double inner_size; ///< number of new internal regular grid in double + double inner_start; ///< start of the regular grid + double inner_end; ///< end of the regular grid + double point_st = crd; ///< input point in the variational grid + double point_reg = 0.; ///< point in the regular grid + + + auto normalised = [L_long](double p) { + if (L_long) { + p = (p < 180) ? p + 360.0 : p; + } + return p; + }; + + point_st = normalised(point_st); + + /* + * regular grids + */ + if (var_ratio_ == 1) { + point_reg = point_st; + return normalised(point_reg); + } + + /* + * SECTION 1 + * INTERNAL REGULAR GRID the point is mapped to the same point + */ + + + if (L_long) { + inner_start = x_reg_start_; + inner_size = lam_hires_size; + } + else { + inner_start = y_reg_start_; + inner_size = phi_hires_size; + } + + inner_end = inner_start + inner_size; + + //< points in the internal regular grid + if ((point_st >= inner_start - epstest) && (point_st <= inner_end + epstest)) { + point_reg = point_st; + return normalised(point_reg); + } + + + /* SECTION 2 + * Start variable res 'STRETCHED' + * The point is mapped in a regular grid + * from stretch internal grid: delta_dist + * 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, + * this work just for the part of the stretch not the rim + */ + + ///< computation of the new stretched delta integer part + double delta = delta_inner; + ///< initialization if is not using the cycle (first interval) + double delta_last = delta; + double deltacheck = 0; + /* + * using difference in delta for stretch + * The point stretched is not in the regular grid and took out the points for the rim + */ + + double point_var = 0.; + for (int i = 1; i < n_stretched / 2.; i += 1) { + delta_last = delta * new_ratio; + delta_add = delta_last - delta_inner; + delta = delta_last; + deltacheck += delta_add; + if (point_st > inner_start) { + point_reg = inner_end + (delta_inner * i); + point_var = point_reg + deltacheck; + if ((point_st <= point_var + epstest) && (point_st >= point_var - epstest)) { + return normalised(point_reg); + } + } + else { + point_reg = inner_start - (delta_inner * i); + point_var = point_reg - deltacheck; + if ((point_st <= point_var + epstest) && (point_st >= point_var - epstest)) { + return normalised(point_reg); + } + } + } + + ///< SECTION 3_inv rim area + int n_rim; ///< number of rim points + if (point_st > point_var) { + n_rim = (point_st - point_var) / delta_outer; + point_reg = inner_end + (delta_inner * (n_stretched / 2 + n_rim)); + return normalised(point_reg); + } + else { + if (point_st < point_var) { + n_rim = (point_var - point_st) / delta_outer; + point_reg = inner_start - (delta_inner * (n_stretched / 2 + n_rim)); + return normalised(point_reg); + } + } + return normalised(point_reg); +} + + +///< xy unstretched, only unrotation +template +void VariableResolutionProjectionT::lonlat2xy(double crd[]) const { + ///< unrotate + rotation_.unrotate(crd); + //< normalization + crd[0] = (crd[0] < 0) ? crd[0] + 360.0 : crd[0]; + ///< PUT the unstretch + crd[0] = general_stretch_inv(crd[0], true, nx_stretched); + crd[1] = general_stretch_inv(crd[1], false, ny_stretched); +} + + +///< From unstretched to stretched +template +void VariableResolutionProjectionT::xy2lonlat(double crd[]) const { + /** PUT here the stretch for a point that come input + * give number of power as input,work it out using as from the start of regular grid. + * atlas::PointXY unstretchedXY = crd; + * atlas::PointXY stretchedXY = stretch_LAM_gen(unstretchedXY); + * stretch_LAM_gen(crd[]); + */ + + crd[0] = general_stretch(crd[0], true, nx_stretched); + crd[1] = general_stretch(crd[1], false, ny_stretched); + + rotation_.rotate(crd); +} + +template +ProjectionImpl::Jacobian VariableResolutionProjectionT::jacobian(const PointLonLat&) const { + throw_NotImplemented("VariableResolution::jacobian", Here()); +} + +template +void VariableResolutionProjectionT::hash(eckit::Hash& hsh) const { + hsh.add(static_type()); + rotation_.hash(hsh); + //hsh.add( radius_ ); +} + +template class VariableResolutionProjectionT; +template class VariableResolutionProjectionT; + +namespace { +static ProjectionBuilder register_1(VariableResolutionProjection::static_type()); +static ProjectionBuilder register_2( + RotatedVariableResolutionProjection::static_type()); +} // namespace + +} // namespace detail +} // namespace projection +} // namespace atlas diff --git a/src/atlas/projection/detail/VariableResolutionProjection.h b/src/atlas/projection/detail/VariableResolutionProjection.h new file mode 100644 index 000000000..784088329 --- /dev/null +++ b/src/atlas/projection/detail/VariableResolutionProjection.h @@ -0,0 +1,97 @@ +/** +* (C) Crown copyright 2021, 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 "atlas/domain.h" +#include "atlas/projection/detail/ProjectionImpl.h" +#include "atlas/util/NormaliseLongitude.h" + + +namespace atlas { +namespace projection { +namespace detail { + + +template +class VariableResolutionProjectionT final : public ProjectionImpl { +public: + /// new_ratio_; + + void setup(const eckit::Parametrisation& p); + +private: + Rotation rotation_; +}; + +using VariableResolutionProjection = VariableResolutionProjectionT; +using RotatedVariableResolutionProjection = VariableResolutionProjectionT; + +} // namespace detail +} // namespace projection +} // namespace atlas diff --git a/src/atlas/runtime/Exception.cc b/src/atlas/runtime/Exception.cc index 015f90e24..85a4c8a8f 100644 --- a/src/atlas/runtime/Exception.cc +++ b/src/atlas/runtime/Exception.cc @@ -8,6 +8,8 @@ * nor does it submit to any jurisdiction. */ +#include + #include "eckit/exception/Exceptions.h" #include "atlas/runtime/Exception.h" @@ -23,17 +25,23 @@ void throw_NotImplemented(const std::string& msg, const eckit::CodeLocation& loc } void throw_AssertionFailed(const std::string& msg) { - throw eckit::AssertionFailed(msg); + throw_AssertionFailed(msg, eckit::CodeLocation{}); } void throw_AssertionFailed(const std::string& msg, const eckit::CodeLocation& loc) { - throw eckit::AssertionFailed(msg, loc); + if (loc) { + eckit::handle_assert(msg, loc); + } + else { + eckit::handle_assert(msg, eckit::CodeLocation{"unspecified file", 0, "unspecified function"}); + } + std::abort(); // should never reach here, but makes sure we will never return from this } void throw_AssertionFailed(const std::string& code, const std::string& msg, const eckit::CodeLocation& loc) { std::ostringstream ss; ss << " [[ " << code << " ]]\n" << msg; - throw eckit::AssertionFailed(ss.str(), loc); + throw_AssertionFailed(ss.str(), loc); } void throw_Exception(const std::string& msg) { @@ -67,4 +75,17 @@ void throw_OutOfRange(const std::string& varname, idx_t index, idx_t size, const } +void throw_OutOfRange(idx_t index, idx_t size) { + std::ostringstream ss; + ss << "OutOfRange: Tried to access index " << index << " but maximum allowed index is " << size - 1; + throw eckit::Exception(ss.str()); +} + +void throw_OutOfRange(idx_t index, idx_t size, const eckit::CodeLocation& loc) { + std::ostringstream ss; + ss << "OutOfRange: Tried to access index " << index << " but maximum allowed index is " << size - 1; + throw eckit::Exception(ss.str(), loc); +} + + } // namespace atlas diff --git a/src/atlas/runtime/Exception.h b/src/atlas/runtime/Exception.h index 98b6b08f7..c1d2dc288 100644 --- a/src/atlas/runtime/Exception.h +++ b/src/atlas/runtime/Exception.h @@ -34,6 +34,8 @@ namespace atlas { [[noreturn]] void throw_OutOfRange(const std::string& varname, idx_t index, idx_t size); [[noreturn]] void throw_OutOfRange(const std::string& varname, idx_t index, idx_t size, const eckit::CodeLocation&); +[[noreturn]] void throw_OutOfRange(idx_t index, idx_t size); +[[noreturn]] void throw_OutOfRange(idx_t index, idx_t size, const eckit::CodeLocation&); namespace detail { inline void Assert(bool success, const char* code, const char* file, int line, const char* func) { @@ -47,6 +49,7 @@ inline void Assert(bool success, const char* code, const std::string& msg, const throw_AssertionFailed(code, msg, eckit::CodeLocation(file, line, func)); } } + } // namespace detail } // namespace atlas @@ -54,9 +57,11 @@ inline void Assert(bool success, const char* code, const std::string& msg, const #define ATLAS_NOTIMPLEMENTED ::atlas::throw_NotImplemented(Here()) -#define ATLAS_ASSERT_NOMSG(code) ::atlas::detail::Assert(bool(code), #code, __FILE__, __LINE__, __func__) -#define ATLAS_ASSERT_MSG(code, msg) \ - ::atlas::detail::Assert(bool(code), #code, std::string(msg), __FILE__, __LINE__, __func__) + +#define ATLAS_ASSERT_NOMSG(a) \ + static_cast(0), (a) ? (void)0 : ::atlas::detail::Assert(bool(a), #a, __FILE__, __LINE__, __func__) +#define ATLAS_ASSERT_MSG(a, m) \ + static_cast(0), (a) ? (void)0 : ::atlas::detail::Assert(bool(a), #a, m, __FILE__, __LINE__, __func__) #define ATLAS_ASSERT(...) __ATLAS_SPLICE(__ATLAS_ASSERT_, __ATLAS_NARG(__VA_ARGS__))(__VA_ARGS__) #define __ATLAS_ASSERT_1 ATLAS_ASSERT_NOMSG diff --git a/src/atlas/util/ConvexSphericalPolygon.cc b/src/atlas/util/ConvexSphericalPolygon.cc new file mode 100644 index 000000000..b81f65540 --- /dev/null +++ b/src/atlas/util/ConvexSphericalPolygon.cc @@ -0,0 +1,621 @@ +/* + * (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/geometry/Sphere.h" +#include "eckit/types/FloatCompare.h" + +#include "atlas/runtime/Exception.h" +#include "atlas/runtime/Log.h" +#include "atlas/util/ConvexSphericalPolygon.h" +#include "atlas/util/CoordinateEnums.h" +#include "atlas/util/NormaliseLongitude.h" + +#define DEBUG_OUTPUT 0 +#define DEBUG_OUTPUT_DETAIL 0 + +namespace atlas { +namespace util { + +using GreatCircleSegment = ConvexSphericalPolygon::GreatCircleSegment; + +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]; + double dy = p2[1] - p1[1]; + double dz = p2[2] - p1[2]; + return dx * dx + dy * dy + dz * dz; +} + +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 PointXYZ& v1, const PointXYZ& v2, const double& tol) { + //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; +} + +void lonlat2xyz(const PointLonLat& lonlat, PointXYZ& xyz) { + eckit::geometry::Sphere::convertSphericalToCartesian(1., lonlat, xyz); +} + +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])); +} + +template +struct StackVector { +private: + using Wrapped = std::array; + +public: + using reference = typename Wrapped::reference; + StackVector() = default; + StackVector(size_t size): size_(size) {} +#if ATLAS_VECTOR_BOUNDS_CHECKING + reference operator[](size_t i) { + if (i >= size_) { + throw_OutOfRange(i, size_); + } + return wrapped_[i]; + } +#else + reference operator[](size_t i) { return wrapped_[i]; } +#endif + void push_back(const T& value) { + wrapped_[size_++] = value; + ATLAS_ASSERT(size_ < ConvexSphericalPolygon::MAX_SIZE); + } + size_t size() const { return size_; } + +private: + 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; +}; + +} // namespace + +//------------------------------------------------------------------------------------------------------ + +ConvexSphericalPolygon::ConvexSphericalPolygon(const PointLonLat points[], size_t size): size_{size} { + ATLAS_ASSERT(size_ > 2, "Polygon must have at least 3 points"); + ATLAS_ASSERT(size_ < MAX_SIZE, "Number of polygon points exceeds compile time MAX_SIZE"); + lonlat2xyz(points[0], sph_coords_[0]); + 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)) { + 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)) { + } + else { + ++isp; + } + size_ = isp; + valid_ = size_ > 2; + if (valid_) { + ATLAS_ASSERT(validate()); + } +} + +ConvexSphericalPolygon::ConvexSphericalPolygon(const PointXYZ points[], size_t size): size_{size} { + ATLAS_ASSERT(size_ > 2, "Polygon must have at least 3 points"); + ATLAS_ASSERT(size_ < MAX_SIZE, "Number of polygon points exceeds compile time MAX_SIZE"); + sph_coords_[0] = points[0]; + 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)) { + 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)) { + } + else { + ++isp; + } + size_ = isp; + valid_ = size_ > 2; + if (valid_) { + ATLAS_ASSERT(validate()); + } +} + + +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()); + + area_ = triangles.area(); + computed_area_ = true; + + // Compute centroid based on triangles rather than on vertices of polygon + centroid_ = PointXYZ{0., 0., 0.}; + if (area_ > 0.) { + for (auto& triangle : triangles) { + for (size_t i = 0; i < 3; ++i) { + centroid_[i] += triangle.centroid[i] * triangle.area; + } + } + centroid_ /= PointXYZ::norm(centroid_); + } + computed_centroid_ = true; +} + +bool ConvexSphericalPolygon::validate() { + if (valid_) { + for (int i = 0; i < size(); i++) { + int ni = next(i); + 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); + } + } + return valid_; +} + +bool ConvexSphericalPolygon::equals(const ConvexSphericalPolygon& plg, const double deg_prec) const { + if ((not plg.valid_) || (not valid_) || size_ != plg.size()) { + Log::info() << " ConvexSphericalPolygon::equals == not compatible\n"; + return false; + } + int offset = 0; + const double le = 2. * std::sin(M_PI * deg_prec / 360.); + const double le2 = le * le; + for (; offset < size_; ++offset) { + if (distance2(plg.sph_coords_[0], sph_coords_[offset]) < le2) { + break; + } + } + if (offset == size_) { + Log::info() << "ConvexSphericalPolygon::equals == no point equal\n"; + return false; + } + + for (int j = 0; j < size_; j++) { + 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"; + return false; + } + } + return true; +} + +// note: unit sphere! +// I. Todhunter (1886), Paragr. 99 +ConvexSphericalPolygon::SubTriangles ConvexSphericalPolygon::triangulate(const double cell_radius) 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; + } + } + triangles.size() = itri; + return triangles; +} + + +double ConvexSphericalPolygon::SubTriangles::area() const { + double area = 0.; + for (auto& triangle : *this) { + area += triangle.area; + } + 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) { + ATLAS_ASSERT(valid_); + ATLAS_ASSERT(distance2(great_circle.first(), great_circle.second()) > TOL2); + + 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; + for (int i = 0; i < size_; i++) { + vertex_in[i] = great_circle.inLeftHemisphere(sph_coords_[i], -EPS); + 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(); + } + 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); + } + } + 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); + } + for (int i = f1 + 1; i <= f2; i++) { + if (vertex_in[i]) { + keep_vertex(i); + } + } + 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); + } + } + } + + // Update polygon + { + size_ = clipped_sph_coords.size(); + if (size_ < 3) { + invalidate_this_polygon(); + } + else { + for (size_t i = 0; i < size_; i++) { + sph_coords_[i] = clipped_sph_coords[i]; + } + } + } +} + +// 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; + 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)); + if (not intersection.valid_) { + return intersection; + } + } + } + intersection.simplify(); + return intersection; +} + +void ConvexSphericalPolygon::print(std::ostream& out) const { + out << "{"; + for (size_t i = 0; i < size(); ++i) { + if (i > 0) { + out << ","; + } + PointLonLat ip_ll; + xyz2lonlat(sph_coords_[i], ip_ll); + out << ip_ll; + } + out << "}"; +} + +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; + } + 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)); + } + } + 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 { + const auto& s = *this; + PointXYZ sp = PointXYZ::cross(s.cross(), p.cross()); + + double sp_norm = PointXYZ::norm(sp); + if (sp_norm > EPS) { + sp /= sp_norm; + if (p.contains(sp)) { + return sp; + } + sp *= -1.; + if (p.contains(sp)) { + return sp; + } + return PointXYZ(0, 0, 0); + } + else { + return PointXYZ(1, 1, 1); + } +} + + +//------------------------------------------------------------------------------------------------------ + +} // namespace util +} // namespace atlas diff --git a/src/atlas/util/ConvexSphericalPolygon.h b/src/atlas/util/ConvexSphericalPolygon.h new file mode 100644 index 000000000..9a5783fa0 --- /dev/null +++ b/src/atlas/util/ConvexSphericalPolygon.h @@ -0,0 +1,177 @@ +/* + * (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 "eckit/deprecated.h" + +#include "atlas/util/Point.h" +#include "atlas/util/Polygon.h" + +namespace atlas { +namespace util { + +//------------------------------------------------------------------------------------------------------ + +class ConvexSphericalPolygon { +public: + static constexpr int MAX_GRIDCELL_EDGES = 4; + static constexpr int MAX_SIZE = 2 * MAX_GRIDCELL_EDGES + 1; + +public: + 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); + } + + const PointXYZ& first() const { return p1_; } + + const PointXYZ& second() const { return p2_; } + + const PointXYZ& cross() const { return cross_; } + + private: + const PointXYZ p1_; + const PointXYZ p2_; + const PointXYZ cross_; + }; + +public: + ConvexSphericalPolygon() = default; + + template + using contains_PointLonLat = std::is_same::type, PointLonLat>; + + template ::value, void>::type* = nullptr> + ConvexSphericalPolygon(const Points& points): ConvexSphericalPolygon(points.data(), points.size()) {} + + ConvexSphericalPolygon(const PointLonLat points[], size_t size); + + ConvexSphericalPolygon(const PointXYZ& p1, const PointXYZ& p2, const PointXYZ& p3): + ConvexSphericalPolygon(std::array{p1, p2, p3}.data(), 3) {} + + ConvexSphericalPolygon(const PointXYZ points[], size_t size); + + operator bool() const { return valid_; } + + size_t size() const { return size_; } + + double area() const { + if (not computed_area_) { + compute_centroid(); + } + return area_; + } + + const PointXYZ& centroid() const { + if (not computed_centroid_) { + compute_centroid(); + } + return centroid_; + } + + double radius() const { + if (not computed_radius_) { + radius_ = compute_radius(); + computed_radius_ = true; + } + return radius_; + } + + ConvexSphericalPolygon intersect(const ConvexSphericalPolygon& pol) const; + + /* + * @brief check if two spherical polygons area equal + * @param[in] P given point in (x,y,z) coordinates + * @return true if equal vertices + */ + bool equals(const ConvexSphericalPolygon& plg, const double deg_prec = 1e-10) const; + + void print(std::ostream&) const; + + friend std::ostream& operator<<(std::ostream& out, const ConvexSphericalPolygon& p) { + p.print(out); + return out; + } + + const PointXYZ& operator[](idx_t n) const { return sph_coords_[n]; } + + int next(const int index) const { return (index == size_ - 1) ? 0 : index + 1; }; + +private: + struct SubTriangle { + PointXYZ centroid; + double area; + }; + struct SubTriangles : public std::array { + public: + size_t size() const { return size_; } + size_t& size() { return size_; } + double area() const; + std::array::const_iterator end() const { return data() + size_; } + + private: + size_t size_{0}; + }; + + void compute_centroid() const; + + double compute_radius() const; + + SubTriangles triangulate(const double radius) const; + + void clip(const GreatCircleSegment&); + + /* + * @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_; + mutable double area_{0}; + mutable double radius_{0}; + size_t size_{0}; + bool valid_{false}; + mutable bool computed_centroid_{false}; + mutable bool computed_radius_{false}; + mutable bool computed_area_{false}; +}; + +//------------------------------------------------------------------------------------------------------ + +} // namespace util +} // namespace atlas diff --git a/src/atlas/util/Point.h b/src/atlas/util/Point.h index 2f8443463..91994a221 100644 --- a/src/atlas/util/Point.h +++ b/src/atlas/util/Point.h @@ -95,6 +95,20 @@ class PointXYZ : public eckit::geometry::Point3 { x_[1] = y; x_[2] = z; } + + PointXYZ& operator*=(double m) { + x_[0] *= m; + x_[1] *= m; + x_[2] *= m; + return *this; + } + + PointXYZ& operator/=(double m) { + x_[0] /= m; + x_[1] /= m; + x_[2] /= m; + return *this; + } }; /// @brief Point in longitude-latitude coordinate system. diff --git a/src/atlas/util/Polygon.cc b/src/atlas/util/Polygon.cc index 2bfa0b84f..40a88cb63 100644 --- a/src/atlas/util/Polygon.cc +++ b/src/atlas/util/Polygon.cc @@ -152,7 +152,7 @@ PolygonCoordinates::PolygonCoordinates(const Polygon& poly, const atlas::Field& if ((coordinates_.size() >= 2) && removeAlignedPoints) { const Point2& B = coordinates_.back(); const Point2& C = coordinates_[coordinates_.size() - 2]; - if (eckit::types::is_approximately_equal(0., cross_product_analog(A, B, C))) { + if (eckit::types::is_approximately_equal(0., cross_product_analog(A, B, C), 1.e-10)) { coordinates_.back() = A; ++nb_removed_points_due_to_alignment; continue; @@ -208,7 +208,7 @@ PolygonCoordinates::PolygonCoordinates(const PointContainer& points, bool remove if ((coordinates_.size() >= 2) && removeAlignedPoints) { const Point2& B = coordinates_.back(); const Point2& C = coordinates_[coordinates_.size() - 2]; - if (eckit::types::is_approximately_equal(0., cross_product_analog(A, B, C))) { + if (eckit::types::is_approximately_equal(0., cross_product_analog(A, B, C), 1.e-10)) { coordinates_.back() = A; ++nb_removed_points_due_to_alignment; continue; @@ -257,6 +257,11 @@ void PolygonCoordinates::print(std::ostream& out) const { out << "]"; } +std::ostream& operator<<(std::ostream& out, const PolygonCoordinates& pc) { + pc.print(out); + return out; +} + Polygon::edge_set_t ExplicitPartitionPolygon::compute_edges(idx_t points_size) { util::Polygon::edge_set_t edges; auto add_edge = [&](idx_t p1, idx_t p2) { diff --git a/src/atlas/util/Polygon.h b/src/atlas/util/Polygon.h index 8ff6f1134..55d7a4043 100644 --- a/src/atlas/util/Polygon.h +++ b/src/atlas/util/Polygon.h @@ -184,10 +184,17 @@ class PolygonCoordinates { const Point2& coordinatesMin() const; const Point2& centroid() const; + template + const Point2& operator[](Index i) const { + return coordinates_[i]; + } + idx_t size() const { return coordinates_.size(); } void print(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& out, const PolygonCoordinates& pc); + protected: // -- Members diff --git a/src/atlas/util/PolygonXY.cc b/src/atlas/util/PolygonXY.cc index d79e4b044..71735becb 100644 --- a/src/atlas/util/PolygonXY.cc +++ b/src/atlas/util/PolygonXY.cc @@ -28,7 +28,7 @@ namespace util { namespace { double cross_product_analog(const Point2& A, const Point2& B, const Point2& C) { - return (A[LON] - C[LON]) * (B[LAT] - C[LAT]) - (A[LAT] - C[LAT]) * (B[LON] - C[LON]); + return (A.x() - C.x()) * (B.y() - C.y()) - (A.y() - C.y()) * (B.x() - C.x()); } @@ -90,7 +90,7 @@ PolygonXY::PolygonXY(const PartitionPolygon& partition_polygon): PolygonCoordina centroid_ = compute_centroid(coordinates_); inner_radius_squared_ = compute_inner_radius_squared(coordinates_, centroid_); - ATLAS_ASSERT(contains(centroid_)); + ATLAS_ASSERT(PolygonXY::contains(centroid_)); } } @@ -101,15 +101,16 @@ bool PolygonXY::contains(const Point2& P) const { return dx * dx + dy * dy; }; + constexpr double eps = 1.e-10; // check first bounding box - if (coordinatesMax_[LAT] < P[LAT] || P[LAT] < coordinatesMin_[LAT] || coordinatesMax_[LON] < P[LON] || - P[LON] < coordinatesMin_[LON]) { + if (coordinatesMax_.y() + eps < P.y() || P.y() < coordinatesMin_.y() - eps || coordinatesMax_.x() + eps < P.x() || + P.x() < coordinatesMin_.x() - eps) { return false; } if (inner_radius_squared_ == 0) { // check inner bounding box - if (inner_coordinatesMin_[LON] <= P[LON] && P[LON] <= inner_coordinatesMax_[LON] && - inner_coordinatesMin_[LAT] <= P[LAT] && P[LAT] <= inner_coordinatesMax_[LAT]) { + if (inner_coordinatesMin_.x() <= P.x() && P.x() <= inner_coordinatesMax_.x() && + inner_coordinatesMin_.y() <= P.y() && P.y() <= inner_coordinatesMax_.y()) { return true; } } @@ -132,11 +133,15 @@ bool PolygonXY::contains(const Point2& P) const { // intersecting either: // - "up" on upward crossing & P left of edge, or // - "down" on downward crossing & P right of edge - const bool APB = (A[LAT] <= P[LAT] && P[LAT] <= B[LAT]); - const bool BPA = (B[LAT] <= P[LAT] && P[LAT] <= A[LAT]); + const bool APB = (A.y() <= P.y() && P.y() <= B.y()); + const bool BPA = (B.y() <= P.y() && P.y() <= A.y()); if (APB != BPA) { const double side = cross_product_analog(P, A, B); + bool on_edge = std::abs(side) < eps; + if (on_edge) { + return true; + } if (APB && side > 0) { ++wn; } diff --git a/src/atlas/util/PolygonXY.h b/src/atlas/util/PolygonXY.h index ca2489acd..403d913cc 100644 --- a/src/atlas/util/PolygonXY.h +++ b/src/atlas/util/PolygonXY.h @@ -37,10 +37,10 @@ class PolygonXY : public PolygonCoordinates { bool contains(const Point2& Pxy) const override; private: - PointLonLat centroid_; + Point2 centroid_; double inner_radius_squared_{0}; - PointLonLat inner_coordinatesMin_; - PointLonLat inner_coordinatesMax_; + Point2 inner_coordinatesMin_; + Point2 inner_coordinatesMax_; }; diff --git a/src/atlas/util/detail/KDTree.h b/src/atlas/util/detail/KDTree.h index cee7c53bb..086edebd1 100644 --- a/src/atlas/util/detail/KDTree.h +++ b/src/atlas/util/detail/KDTree.h @@ -72,7 +72,6 @@ class KDTreeBase : public Object { class ValueList : public std::vector { public: - using std::vector::vector; PayloadList payloads() const { PayloadList list; list.reserve(this->size()); @@ -310,7 +309,7 @@ class KDTree_eckit : public KDTreeBase { } idx_t size() const override { -#if ATLAS_ECKIT_VERSION_INT >= 11302 // v1.13.2 +#if ATLAS_ECKIT_VERSION_AT_LEAST(1, 13, 2) return static_cast(tree_->size()); #else // Assume ECKIT-515 not implemented. diff --git a/src/atlas/util/function/VortexRollup.cc b/src/atlas/util/function/VortexRollup.cc new file mode 100644 index 000000000..a4aa591ac --- /dev/null +++ b/src/atlas/util/function/VortexRollup.cc @@ -0,0 +1,43 @@ +/* + * (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/util/Constants.h" +#include "atlas/util/Earth.h" + +#include "atlas/util/function/VortexRollup.h" + +namespace atlas { + +namespace util { + +namespace function { + +double vortex_rollup(double lon, double lat, double t) { + lon *= Constants::degreesToRadians(); + lat *= Constants::degreesToRadians(); + + auto sqr = [](const double x) { return x * x; }; + auto sech = [](const double x) { return 1. / std::cosh(x); }; + constexpr double two_pi = 2. * M_PI; + const double lambda_prime = std::atan2(-std::cos(lon - two_pi * t), std::tan(lat)); + const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - two_pi * t))); + double omega = 0.; + double a = Earth::radius(); + if (rho != 0.) { + omega = 0.5 * 3 * std::sqrt(3) * a * two_pi * sqr(sech(rho)) * std::tanh(rho) / rho; + } + return -std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); +} + +} // namespace function + +} // namespace util + +} // namespace atlas diff --git a/src/atlas/util/function/VortexRollup.h b/src/atlas/util/function/VortexRollup.h new file mode 100644 index 000000000..46f6138f4 --- /dev/null +++ b/src/atlas/util/function/VortexRollup.h @@ -0,0 +1,39 @@ +/* + * (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 + +namespace atlas { + +namespace util { + +namespace function { + +/// \brief An analytic function that provides a vortex rollup on a 2D sphere +/// +/// \detailed The formula is found in +/// "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" +/// by Peter Bosler, James Kent, Robert Krasny, Christiane Jablonowski, JCP 2015 +/// as the tracer density in Test Case 1. +/// The longitude (lon) and latitude (lat) are assumed to be in degrees, +/// The time parameter associated with the vortex rollup is set by (t). +/// +/// The time it takes for the counter-rotating vortices along +/// the equator to return to its original position takes +/// time t = 1.0; +/// +double vortex_rollup(double lon, double lat, double t); + +} // namespace function + +} // namespace util + +} // namespace atlas diff --git a/src/sandbox/interpolation/atlas-parallel-interpolation.cc b/src/sandbox/interpolation/atlas-parallel-interpolation.cc index 7b3aa4527..5a96dcbd2 100644 --- a/src/sandbox/interpolation/atlas-parallel-interpolation.cc +++ b/src/sandbox/interpolation/atlas-parallel-interpolation.cc @@ -10,41 +10,21 @@ #include #include + +#include "eckit/config/Resource.h" +#include "eckit/log/Plural.h" + +#include "PartitionedMesh.h" #include "atlas/field.h" #include "atlas/functionspace.h" #include "atlas/interpolation.h" +#include "atlas/linalg/sparse/Backend.h" #include "atlas/runtime/AtlasTool.h" #include "atlas/runtime/Log.h" -#include "eckit/config/Resource.h" -#include "eckit/linalg/LinearAlgebra.h" -#include "eckit/log/Plural.h" - -#include "PartitionedMesh.h" +#include "atlas/util/function/VortexRollup.h" using namespace atlas; -auto vortex_rollup = [](double lon, double lat, double t) { - // lon and lat in radians! - - // Formula found in "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" - // by Peter Bosler, James Kent, Robert Krasny, CHristiane Jablonowski, JCP 2015 - - auto sqr = [](const double x) { return x * x; }; - auto sech = [](const double x) { return 1. / std::cosh(x); }; - const double T = 1.; - const double Omega = 2. * M_PI / T; - t *= T; - const double lambda_prime = std::atan2(-std::cos(lon - Omega * t), std::tan(lat)); - const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - Omega * t))); - double omega = 0.; - double a = util::Earth::radius(); - if (rho != 0.) { - omega = 0.5 * 3 * std::sqrt(3) * a * Omega * sqr(sech(rho)) * std::tanh(rho) / rho; - } - double q = 1. - std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); - return q; -}; - class AtlasParallelInterpolation : public AtlasTool { int execute(const AtlasTool::Args& args) override; std::string briefDescription() override { return "Demonstration of parallel interpolation"; } @@ -122,7 +102,7 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { if (args.get("backend", option)) { - eckit::linalg::LinearAlgebra::backend(option); + linalg::sparse::current_backend(option); } // Generate and partition source & target mesh @@ -243,7 +223,7 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { // src_scalar_1( j ) = -std::tanh( y / 10. * std::cos( 50. / std::sqrt( x * x + y * y ) ) - // x / 10. * std::sin( 50. / std::sqrt( x * x + y * y ) ) ); - src_scalar_1(j) = vortex_rollup(lon, lat, 1.); + src_scalar_1(j) = util::function::vortex_rollup(lonlat(j, 0), lonlat(j, 1), 1.); } } diff --git a/src/sandbox/interpolation/atlas-parallel-structured-interpolation.cc b/src/sandbox/interpolation/atlas-parallel-structured-interpolation.cc index da06f7097..2be8b3b07 100644 --- a/src/sandbox/interpolation/atlas-parallel-structured-interpolation.cc +++ b/src/sandbox/interpolation/atlas-parallel-structured-interpolation.cc @@ -19,6 +19,7 @@ #include "atlas/runtime/AtlasTool.h" #include "atlas/runtime/Log.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" using namespace atlas; @@ -87,32 +88,6 @@ static Config processed_config(const eckit::Configuration& _config) { return config; } - -double vortex_rollup(double lon, double lat, double t) { - // lon and lat in degrees! - - // Formula found in "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" - // by Peter Bosler, James Kent, Robert Krasny, Christiane Jablonowski, JCP 2015 - - lon *= M_PI / 180.; - lat *= M_PI / 180.; - - auto sqr = [](const double x) { return x * x; }; - auto sech = [](const double x) { return 1. / std::cosh(x); }; - const double T = 1.; - const double Omega = 2. * M_PI / T; - t *= T; - const double lambda_prime = std::atan2(-std::cos(lon - Omega * t), std::tan(lat)); - const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - Omega * t))); - double omega = 0.; - double a = util::Earth::radius(); - if (rho != 0.) { - omega = 0.5 * 3 * std::sqrt(3) * a * Omega * sqr(sech(rho)) * std::tanh(rho) / rho; - } - double q = 1. - std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); - return q; -}; - int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { ATLAS_TRACE("AtlasParallelInterpolation::execute"); auto source_gridname = args.getString("source", "O32"); @@ -180,7 +155,7 @@ int AtlasParallelInterpolation::execute(const AtlasTool::Args& args) { else { idx_t k = args.getInt("vortex-rollup", 0); atlas_omp_parallel_for(idx_t n = 0; n < size; ++n) { - source(n) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); + source(n) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); } } } diff --git a/src/tests/AtlasTestEnvironment.h b/src/tests/AtlasTestEnvironment.h index 528810c46..d656f9feb 100644 --- a/src/tests/AtlasTestEnvironment.h +++ b/src/tests/AtlasTestEnvironment.h @@ -106,8 +106,8 @@ Test& current_test() { #undef CASE #define CASE(description) \ void UNIQUE_NAME2(test_, __LINE__)(std::string&, int&, int); \ - static eckit::testing::TestRegister UNIQUE_NAME2(test_registration_, __LINE__)(description, \ - &UNIQUE_NAME2(test_, __LINE__)); \ + static const eckit::testing::TestRegister UNIQUE_NAME2(test_registration_, __LINE__)( \ + description, &UNIQUE_NAME2(test_, __LINE__)); \ void UNIQUE_NAME2(traced_test_, __LINE__)(std::string & _test_subsection, int& _num_subsections, int _subsection); \ void UNIQUE_NAME2(test_, __LINE__)(std::string & _test_subsection, int& _num_subsections, int _subsection) { \ Test UNIQUE_NAME2(testobj_, __LINE__)(description, Here()); \ diff --git a/src/tests/acceptance_tests/atest_mgrids.cc b/src/tests/acceptance_tests/atest_mgrids.cc index b4a452704..e5022f6ad 100644 --- a/src/tests/acceptance_tests/atest_mgrids.cc +++ b/src/tests/acceptance_tests/atest_mgrids.cc @@ -29,11 +29,10 @@ #include "atlas/util/Config.h" #include "atlas/util/Constants.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" using namespace atlas; -double vortex_rollup(double lon, double lat, double t, double mean); - //------------------------------------------------------------------------------ class Program : public AtlasTool { @@ -100,7 +99,7 @@ int Program::execute(const Args& args) { double meanA = 1.; for (idx_t n = 0; n < fsA.size(); ++n) { - A(n) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 1., meanA); + A(n) = meanA + util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 1.); } fieldA.set_dirty(); fieldA.haloExchange(); @@ -204,33 +203,6 @@ int Program::execute(const Args& args) { //------------------------------------------------------------------------------ -double vortex_rollup(double lon, double lat, double t, double mean) { - // lon and lat in degrees! - - // Formula found in "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" - // by Peter Bosler, James Kent, Robert Krasny, CHristiane Jablonowski, JCP 2015 - - lon *= util::Constants::degreesToRadians(); - lat *= util::Constants::degreesToRadians(); - - auto sqr = [](const double x) { return x * x; }; - auto sech = [](const double x) { return 1. / std::cosh(x); }; - const double T = 1.; - const double Omega = 2. * M_PI / T; - t *= T; - const double lambda_prime = std::atan2(-std::cos(lon - Omega * t), std::tan(lat)); - const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - Omega * t))); - double omega = 0.; - double a = util::Earth::radius(); - if (rho != 0.) { - omega = 0.5 * 3 * std::sqrt(3) * a * Omega * sqr(sech(rho)) * std::tanh(rho) / rho; - } - double q = mean - std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); - return q; -}; - -//------------------------------------------------------------------------------ - int main(int argc, char** argv) { Program tool(argc, argv); return tool.start(); diff --git a/src/tests/field/test_field_missingvalue.cc b/src/tests/field/test_field_missingvalue.cc index 99f4d6c2e..b55e82617 100644 --- a/src/tests/field/test_field_missingvalue.cc +++ b/src/tests/field/test_field_missingvalue.cc @@ -123,13 +123,20 @@ CASE("MissingValue (DataType specialisations)") { config.set("missing_value", n); config.set("missing_value_epsilon", eps); + Log::info().indent(); for (std::string type : {"nan", "equals", "approximately-equals"}) { + Log::info() << "type " << type << std::endl; + Log::info().indent(); auto mv = MissingValue(type + "-real64", config); EXPECT(bool(mv)); EXPECT(mv(type == "nan" ? nan : n)); - EXPECT(mv(n) != mv(nan)); + if (type == "nan") { + EXPECT(mv(n) != mv(nan)); + } EXPECT(mv(n + 1) == false); + Log::info().unindent(); } + Log::info().unindent(); } @@ -142,13 +149,20 @@ CASE("MissingValue (DataType specialisations)") { config.set("missing_value", n); config.set("missing_value_epsilon", eps); + Log::info().indent(); for (std::string type : {"nan", "equals", "approximately-equals"}) { + Log::info() << "type " << type << std::endl; + Log::info().indent(); auto mv = MissingValue(type + "-real32", config); EXPECT(bool(mv)); EXPECT(mv(type == "nan" ? nan : n)); - EXPECT(mv(n) != mv(nan)); + if (type == "nan") { + EXPECT(mv(n) != mv(nan)); + } EXPECT(mv(n + 1) == false); + Log::info().unindent(); } + Log::info().unindent(); } @@ -181,7 +195,7 @@ CASE("MissingValue (DataType specialisations)") { CASE("MissingValue from Field (basic)") { - std::vector values{1., nan, missingValue, missingValue, missingValue + missingValueEps / 2., 6., 7.}; + std::vector values{1., missingValue, missingValue, missingValue + missingValueEps / 2., 6., 7.}; Field field("field", values.data(), array::make_shape(values.size(), 1)); field.metadata().set("missing_value_type", "not defined"); @@ -192,12 +206,15 @@ CASE("MissingValue from Field (basic)") { SECTION("nan") { + std::vector values_with_nan = values; + values_with_nan.insert(values_with_nan.begin() + 1, nan); + // missing value type from user - EXPECT(std::count_if(values.begin(), values.end(), MissingValue("nan", field)) == 1); + EXPECT(std::count_if(values_with_nan.begin(), values_with_nan.end(), MissingValue("nan", field)) == 1); // missing value type from field field.metadata().set("missing_value_type", "nan"); - EXPECT(std::count_if(values.begin(), values.end(), MissingValue(field)) == 1); + EXPECT(std::count_if(values_with_nan.begin(), values_with_nan.end(), MissingValue(field)) == 1); } diff --git a/src/tests/functionspace/test_cellcolumns.cc b/src/tests/functionspace/test_cellcolumns.cc index 93ce068d0..7b63f10e4 100644 --- a/src/tests/functionspace/test_cellcolumns.cc +++ b/src/tests/functionspace/test_cellcolumns.cc @@ -16,20 +16,14 @@ /// within the CellColumns constructor. Meshes without any parallel halo will also succeed as /// the BuildHalo routine is not called. - -#include "eckit/types/Types.h" - #include "atlas/array/ArrayView.h" #include "atlas/array/MakeView.h" #include "atlas/field/Field.h" -#include "atlas/field/FieldSet.h" #include "atlas/functionspace/CellColumns.h" -#include "atlas/grid/Grid.h" +#include "atlas/grid.h" #include "atlas/mesh.h" #include "atlas/meshgenerator.h" #include "atlas/output/Gmsh.h" -#include "atlas/parallel/HaloExchange.h" -#include "atlas/parallel/mpi/mpi.h" #include "tests/AtlasTestEnvironment.h" @@ -42,11 +36,11 @@ namespace test { Mesh generate_mesh() { auto grid = Grid{"O16"}; - auto meshgenerator = MeshGenerator{util::Config("type", "structured")("patch_pole", false)("triangulate", true)}; + auto meshgenerator = MeshGenerator{grid.meshgenerator()}; return meshgenerator.generate(grid); } -void set_field_values(const Mesh& mesh, Field& field) { +void set_partition_field_values(const Mesh& mesh, Field& field) { auto value = array::make_view(field); auto partition = array::make_view(mesh.cells().partition()); auto halo = array::make_view(mesh.cells().halo()); @@ -62,9 +56,10 @@ void set_field_values(const Mesh& mesh, Field& field) { value(j) = partition(j); } } + field.set_dirty(); } -void check_field_values(const Mesh& mesh, Field& field) { +void check_partition_field_values(const Mesh& mesh, Field& field) { auto value = array::make_view(field); auto partition = array::make_view(mesh.cells().partition()); const size_t nb_cells = mesh.cells().size(); @@ -73,6 +68,42 @@ void check_field_values(const Mesh& mesh, Field& field) { } } + +void set_global_index_field_values(const Mesh& mesh, Field& field) { + auto value = array::make_view(field); + auto gidx = array::make_view(mesh.cells().global_index()); + auto halo = array::make_view(mesh.cells().halo()); + + EXPECT(field.shape(0) == mesh.cells().size()); + + const size_t nb_cells = mesh.cells().size(); + for (size_t j = 0; j < nb_cells; ++j) { + if (halo(j)) { + value(j) = -1; + } + else { + value(j) = gidx(j); + } + } + field.set_dirty(); +} + +void check_global_index_field_values(const Mesh& mesh, Field& field) { + auto value = array::make_view(field); + auto gidx = array::make_view(mesh.cells().global_index()); + auto halo = array::make_view(mesh.cells().halo()); + const size_t nb_cells = mesh.cells().size(); + for (size_t j = 0; j < nb_cells; ++j) { + if (halo(j)) { + EXPECT(value(j) != -1); + } + else { + EXPECT(value(j) == gidx(j)); + } + } +} + + //----------------------------------------------------------------------------- CASE("test_functionspace_CellColumns_no_halo") { @@ -81,24 +112,37 @@ CASE("test_functionspace_CellColumns_no_halo") { Field field(fs.createField()); - set_field_values(mesh, field); + set_partition_field_values(mesh, field); fs.haloExchange(field); - check_field_values(mesh, field); + check_partition_field_values(mesh, field); + + set_global_index_field_values(mesh, field); + fs.haloExchange(field); + check_global_index_field_values(mesh, field); + + output::Gmsh output("cellcolumns_halo0.msh"); + output.write(mesh, util::Config("ghost", true)); + output.write(field); } + CASE("test_functionspace_CellColumns_halo_1") { Mesh mesh = generate_mesh(); CellColumns fs(mesh, option::halo(1)); Field field(fs.createField()); - set_field_values(mesh, field); + set_partition_field_values(mesh, field); fs.haloExchange(field); - check_field_values(mesh, field); + check_partition_field_values(mesh, field); + + set_global_index_field_values(mesh, field); + fs.haloExchange(field); + check_global_index_field_values(mesh, field); output::Gmsh output("cellcolumns_halo1.msh"); output.write(mesh, util::Config("ghost", true)); diff --git a/src/tests/functionspace/test_cubedsphere_functionspace.cc b/src/tests/functionspace/test_cubedsphere_functionspace.cc index df4cdc855..3ce49a710 100644 --- a/src/tests/functionspace/test_cubedsphere_functionspace.cc +++ b/src/tests/functionspace/test_cubedsphere_functionspace.cc @@ -18,6 +18,7 @@ #include "atlas/meshgenerator/detail/cubedsphere/CubedSphereUtility.h" #include "atlas/option.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" #include "tests/AtlasTestEnvironment.h" @@ -27,11 +28,6 @@ namespace test { // Allow small differences in the last few bits of a double aprroximately equal to 1 constexpr double epsilon = std::numeric_limits::epsilon() * 16; - -double testFunction(double lon, double lat) { - return std::sin(3 * lon * M_PI / 180) * std::sin(2 * lat * M_PI / 180); -} - template void testFunctionSpace(const functionspace::CubedSphereColumns& functionspace) { // Make field. @@ -63,7 +59,7 @@ void testFunctionSpace(const functionspace::CubedSphereColumns(functionSpaceA.lonlat()); + const auto lonLatFieldB = array::make_view(functionSpaceB.lonlat()); + const auto ghostFieldA = array::make_view(functionSpaceA.ghost()); + const auto ghostFieldB = array::make_view(functionSpaceB.ghost()); + + // Loop over functionspaces. + for (idx_t i = 0; i < functionSpaceA.size(); ++i) { + EXPECT_EQ(lonLatFieldA(i, LON), lonLatFieldB(i, LON)); + EXPECT_EQ(lonLatFieldA(i, LAT), lonLatFieldB(i, LAT)); + EXPECT_EQ(ghostFieldA(i), ghostFieldB(i)); + } + }; + + // Check that primal cells and dual nodes are equivalent. + compareFields(primalCells, dualNodes); + + // Check that dual cells and primal nodes are equivalent. + compareFields(dualCells, primalNodes); +} + +CASE("Variable halo size functionspaces") { + // Create a mesh with a large halo, and a few functionspaces with different + // (smaller) halo sizes. These should create fields with a smaller memory + // footprint. + + // Set grid. + const auto grid = Grid("CS-LFR-C-12"); + + // Set mesh config. + const auto meshConfig = util::Config("partitioner", "equal_regions") | util::Config("halo", 3); + + // Set mesh. + const auto mesh = MeshGenerator("cubedsphere", meshConfig).generate(grid); + + // Set functionspaces. + const auto nodeColumns0 = functionspace::CubedSphereNodeColumns(mesh, util::Config("halo", 0)); + const auto nodeColumns1 = functionspace::CubedSphereNodeColumns(mesh, util::Config("halo", 1)); + const auto nodeColumns2 = functionspace::CubedSphereNodeColumns(mesh, util::Config("halo", 2)); + + const auto cellColumns0 = functionspace::CubedSphereCellColumns(mesh, util::Config("halo", 0)); + const auto cellColumns1 = functionspace::CubedSphereCellColumns(mesh, util::Config("halo", 1)); + const auto cellColumns2 = functionspace::CubedSphereCellColumns(mesh, util::Config("halo", 2)); + + // Check functionspace sizes. + EXPECT(nodeColumns0.size() < nodeColumns1.size()); + EXPECT(nodeColumns1.size() < nodeColumns2.size()); + EXPECT(nodeColumns2.size() < mesh.nodes().size()); + EXPECT(cellColumns0.size() < cellColumns1.size()); + EXPECT(cellColumns1.size() < cellColumns2.size()); + EXPECT(cellColumns2.size() < mesh.cells().size()); + + // Make sure size of owned cell data matches grid. + auto checkSize = [&](idx_t sizeOwned) { + mpi::comm().allReduceInPlace(sizeOwned, eckit::mpi::Operation::SUM); + EXPECT_EQ(sizeOwned, grid.size()); + }; + + checkSize(cellColumns0.sizeOwned()); + checkSize(cellColumns1.sizeOwned()); + checkSize(cellColumns2.sizeOwned()); +} } // namespace test } // namespace atlas diff --git a/src/tests/functionspace/test_stencil.cc b/src/tests/functionspace/test_stencil.cc index 63e76bff0..6ea6ada9f 100644 --- a/src/tests/functionspace/test_stencil.cc +++ b/src/tests/functionspace/test_stencil.cc @@ -9,7 +9,6 @@ */ #include -#include "eckit/linalg/LinearAlgebra.h" #include "eckit/linalg/Vector.h" #include "eckit/types/Types.h" diff --git a/src/tests/interpolation/CMakeLists.txt b/src/tests/interpolation/CMakeLists.txt index 8c9718467..c716e571b 100644 --- a/src/tests/interpolation/CMakeLists.txt +++ b/src/tests/interpolation/CMakeLists.txt @@ -13,12 +13,32 @@ ecbuild_add_test( TARGET atlas_test_Quad3D ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +ecbuild_add_test( TARGET atlas_test_Quad2D + CONDITION eckit_EIGEN_FOUND + SOURCES test_Quad2D.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + +ecbuild_add_test( TARGET atlas_test_Triag2D + CONDITION eckit_EIGEN_FOUND + SOURCES test_Triag2D.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_test( TARGET atlas_test_interpolation_finite_element SOURCES test_interpolation_finite_element.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) +ecbuild_add_test( TARGET atlas_test_interpolation_bilinear_remapping + SOURCES test_interpolation_bilinear_remapping.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + ecbuild_add_test( TARGET atlas_test_interpolation_finite_element_cached SOURCES test_interpolation_finite_element_cached.cc LIBS atlas diff --git a/src/tests/interpolation/test_Quad2D.cc b/src/tests/interpolation/test_Quad2D.cc new file mode 100644 index 000000000..b9e381803 --- /dev/null +++ b/src/tests/interpolation/test_Quad2D.cc @@ -0,0 +1,208 @@ +/* + * (C) Crown Copyright 2021 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 "eckit/types/FloatCompare.h" + +#include "atlas/interpolation/element/Quad2D.h" +#include "atlas/util/Point.h" + +#include "tests/AtlasTestEnvironment.h" + +using atlas::PointXY; +using atlas::interpolation::element::Quad2D; +using atlas::interpolation::method::Intersect; + +namespace atlas { +namespace test { + +//---------------------------------------------------------------------------------------------------------------------- + +const double relative_error = 0.0001; + +CASE("test_quad_area") { + PointXY v0(0., 0.); + PointXY v1(1., 0.); + PointXY v2(1., 1.); + PointXY v3(0., 1.); + + Quad2D quad1(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad1.validate()); + + double area = quad1.area(); + + std::cout << "area " << area << std::endl; + EXPECT_APPROX_EQ(area, 1.0, relative_error); + + PointXY c0(-2., -2.); // 4 + PointXY c1(3., -2.); // 6 + PointXY c2(3., 0.5); // 1.5 + PointXY c3(-2., 0.5); // 1 + + Quad2D quad2(c0.data(), c1.data(), c2.data(), c3.data()); + + EXPECT(quad2.validate()); + + area = quad2.area(); + + std::cout << "area " << area << std::endl; + EXPECT_APPROX_EQ(area, 12.5, relative_error); +} + +CASE("test_quadrilateral_intersection_refquad") { + PointXY v0(0., 0.); + PointXY v1(1., 0.); + PointXY v2(1., 1.); + PointXY v3(0., 1.); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointLonLat orig(0.25, 0.25); + + Intersect isect = quad.intersects(orig); + + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, 0.25, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.25, relative_error); +} + +CASE("test_quadrilateral_remap_refquad") { + PointXY v0(0., 0.); + PointXY v1(1., 0.); + PointXY v2(1., 1.); + PointXY v3(0., 1.); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointXY orig(0.25, 0.25); + + Intersect isect = quad.localRemap(orig); + + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, 0.25, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.25, relative_error); +} + +CASE("test_quadrilateral_intersection_doublequad") { + PointXY v0(0., 0.); + PointXY v1(2., 0.); + PointXY v2(2., 2.); + PointXY v3(0., 2.); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointXY orig(0.5, 0.5); + + Intersect isect = quad.localRemap(orig); + + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, 0.25, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.25, relative_error); +} + +CASE("test_quadrilateral_intersection_rotatedquad") { + PointXY v0(0., -1.); + PointXY v1(1., 0.); + PointXY v2(0., 1.); + PointXY v3(-1., 0.); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointXY orig(0., 0.); + + Intersect isect = quad.localRemap(orig); + + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, 0.5, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.5, relative_error); +} + +CASE("test_quadrilateral_intersection_arbitrary") { + PointXY v0(338.14, 54.6923); + PointXY v1(340.273, 54.6778); + PointXY v2(340.312, 55.9707); + PointXY v3(338.155, 55.9852); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointXY orig(339, 55); + + Intersect isect = quad.localRemap(orig); + + std::cout << isect.u << " " << isect.v << std::endl; + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, 0.400390, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.242483, relative_error); +} + +CASE("test_quadrilateral_intersection_nointersect") { + PointXY v0(0., -1.); + PointXY v1(1., 0.); + PointXY v2(0., 1.); + PointXY v3(-1., 0.); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + PointXY orig(2., 2.); + + Intersect isect = quad.localRemap(orig); + EXPECT(!isect); +} + +CASE("test_quadrilateral_intersection_corners") { + PointXY v0(0.0, -2.0); + PointXY v1(2.5, 0.0); + PointXY v2(0.0, 3.5); + PointXY v3(-1.5, 0.0); + + Quad2D quad(v0.data(), v1.data(), v2.data(), v3.data()); + + EXPECT(quad.validate()); + + std::vector corners; + corners.emplace_back(0.0, -2.0); + corners.emplace_back(2.5, 0.0); + corners.emplace_back(0.0, 3.5); + corners.emplace_back(-1.5, 0.0); + + std::vector> uvs; + uvs.emplace_back(0., 0.); + uvs.emplace_back(1., 0.); + uvs.emplace_back(1., 1.); + uvs.emplace_back(0., 1.); + + for (size_t i = 0; i < 4; ++i) { + PointXY orig = corners[i]; + + Intersect isect = quad.localRemap(orig); + + EXPECT(isect); + EXPECT_APPROX_EQ(isect.u, uvs[i].first, relative_error); + EXPECT_APPROX_EQ(isect.v, uvs[i].second, relative_error); + } +} + +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/interpolation/test_Triag2D.cc b/src/tests/interpolation/test_Triag2D.cc new file mode 100644 index 000000000..375371fd5 --- /dev/null +++ b/src/tests/interpolation/test_Triag2D.cc @@ -0,0 +1,190 @@ +/* + * (C) Crown Copyright 2021 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 "eckit/types/FloatCompare.h" + +#include "atlas/interpolation/element/Triag2D.h" +#include "atlas/util/Point.h" + +#include "tests/AtlasTestEnvironment.h" + +using atlas::PointXY; +using atlas::interpolation::element::Triag2D; +using atlas::interpolation::method::Intersect; + +namespace atlas { +namespace test { + +//---------------------------------------------------------------------------------------------------------------------- + +const double relative_error = 0.0001; + +CASE("test_triag_area") { + PointXY v0(0., 0.); + PointXY v1(1., 0.); + PointXY v2(1., 1.); + + Triag2D triangle1(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle1.validate()); + + double area = triangle1.area(); + + std::cout << "area " << area << std::endl; + EXPECT_APPROX_EQ(area, 0.5, relative_error); + + PointXY c0(-2., -2.); // 4 + PointXY c1(3., -2.); // 6 + PointXY c2(3., 0.5); // 1.5 + + Triag2D triangle2(c0.data(), c1.data(), c2.data()); + + EXPECT(triangle2.validate()); + + area = triangle2.area(); + + std::cout << "area " << area << std::endl; + EXPECT_APPROX_EQ(area, 6.25, relative_error); +} + +CASE("test_intersection_equilateral_triangle") { + PointXY v0(0., 0.); + PointXY v1(1., 0.); + PointXY v2(0.5, std::sqrt(3.0) / 2.0); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + PointXY orig(0.5, std::sqrt(3.0) / 6.0); + + Intersect isect = triangle.intersects(orig); + + EXPECT(isect); + std::cout << "isect.u " << isect.u << std::endl; + std::cout << "isect.v " << isect.v << std::endl; + EXPECT_APPROX_EQ(isect.u, 1. / 3., relative_error); + EXPECT_APPROX_EQ(isect.v, 1. / 3., relative_error); +} + +CASE("test_intersection_right_angled_triangle") { + PointXY v0(0., 0.); + PointXY v1(3., 0.); + PointXY v2(3., 4.); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + // average of the coordinates is the centre + PointXY orig(2.0, (4.0 / 3.0)); + + Intersect isect = triangle.intersects(orig); + + EXPECT(isect); + std::cout << "isect.u " << isect.u << std::endl; + std::cout << "isect.v " << isect.v << std::endl; + EXPECT_APPROX_EQ(isect.u, 1. / 3., relative_error); + EXPECT_APPROX_EQ(isect.v, 1. / 3., relative_error); +} + +CASE("test_intersection_offset_right_angled_triangle") { + PointXY v0(0., 0.); + PointXY v1(3., 0.); + PointXY v2(3., 4.); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + PointXY orig(1.5, 1); + + Intersect isect = triangle.intersects(orig); + + EXPECT(isect); + std::cout << "isect.u " << isect.u << std::endl; + std::cout << "isect.v " << isect.v << std::endl; + EXPECT_APPROX_EQ(isect.u, 0.25, relative_error); + EXPECT_APPROX_EQ(isect.v, 0.25, relative_error); +} + +CASE("test_intersection_rotatedtriangle") { + const double root2 = std::sqrt(2.); + PointXY v0(0., 0.); + PointXY v1(3. / root2, -3. / root2); + PointXY v2(7. / root2, 1. / root2); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + PointXY orig(10. / (3. * root2), -2. / (3. * root2)); + + Intersect isect = triangle.intersects(orig); + + EXPECT(isect); + std::cout << "isect.u " << isect.u << std::endl; + std::cout << "isect.v " << isect.v << std::endl; + EXPECT_APPROX_EQ(isect.u, 1. / 3., relative_error); + EXPECT_APPROX_EQ(isect.v, 1. / 3., relative_error); +} + +CASE("test_intersection_nointersect") { + PointXY v0(0., -1.); + PointXY v1(1., 0.); + PointXY v2(0., 1.); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + PointXY orig(2., 2.); + + Intersect isect = triangle.intersects(orig); + EXPECT(!isect); +} + +CASE("test_intersection_corners") { + PointXY v0(0.0, -2.0); + PointXY v1(2.5, 0.0); + PointXY v2(0.0, 3.5); + + Triag2D triangle(v0.data(), v1.data(), v2.data()); + + EXPECT(triangle.validate()); + + std::vector corners; + corners.emplace_back(0.0, -2.0); + corners.emplace_back(2.5, 0.0); + corners.emplace_back(0.0, 3.5); + + std::vector> uvs; + uvs.emplace_back(0., 0.); + uvs.emplace_back(1., 0.); + uvs.emplace_back(0., 1.); + + for (size_t i = 0; i < 3; ++i) { + PointXY orig = corners[i]; + + Intersect isect = triangle.intersects(orig); + + EXPECT(isect); + std::cout << "isect.u " << isect.u << std::endl; + std::cout << "isect.v " << isect.v << std::endl; + EXPECT_APPROX_EQ(isect.u, uvs[i].first, relative_error); + EXPECT_APPROX_EQ(isect.v, uvs[i].second, relative_error); + } +} + +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/interpolation/test_interpolation_bilinear_remapping.cc b/src/tests/interpolation/test_interpolation_bilinear_remapping.cc new file mode 100644 index 000000000..8f18c9058 --- /dev/null +++ b/src/tests/interpolation/test_interpolation_bilinear_remapping.cc @@ -0,0 +1,139 @@ +/* + * (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 "eckit/types/FloatCompare.h" + +#include "atlas/array.h" +#include "atlas/functionspace.h" +#include "atlas/functionspace/PointCloud.h" +#include "atlas/grid.h" +#include "atlas/interpolation.h" +#include "atlas/mesh.h" +#include "atlas/meshgenerator.h" +#include "atlas/util/CoordinateEnums.h" + +#include "tests/AtlasTestEnvironment.h" + +using namespace eckit; +using namespace atlas::functionspace; +using namespace atlas::util; + +namespace atlas { +namespace test { + +//----------------------------------------------------------------------------- +// + +CASE("test_interpolation_O64_to_points_bilinear_remapping") { + Grid grid("O64"); + Mesh mesh(grid); + NodeColumns fs(mesh); + + // Some points at the equator + PointCloud pointcloud( + {{00., 0.}, {10., 0.}, {20., 0.}, {30., 0.}, {40., 0.}, {50., 0.}, {60., 0.}, {70., 0.}, {80., 0.}, {90., 0.}}); + + auto func = [](double x) -> double { return std::sin(x * M_PI / 180.); }; + + Interpolation interpolation(option::type("bilinear-remapping") | util::Config("max_fraction_elems_to_try", 0.4), fs, + pointcloud); + + SECTION("test maximum nearest neighbour settings") { + std::stringstream test_stream; + interpolation.print(test_stream); + std::string test_string = test_stream.str(); + EXPECT((test_string.find("max_fraction_elems_to_try: 0.4") != std::string::npos)); + } + + SECTION("test interpolation outputs") { + Field field_source = fs.createField(option::name("source")); + Field field_target("target", array::make_datatype(), array::make_shape(pointcloud.size())); + + auto lonlat = array::make_view(fs.nodes().lonlat()); + auto source = array::make_view(field_source); + for (idx_t j = 0; j < fs.nodes().size(); ++j) { + source(j) = func(lonlat(j, LON)); + } + + interpolation.execute(field_source, field_target); + + auto target = array::make_view(field_target); + + auto check = std::vector{func(00.), func(10.), func(20.), func(30.), func(40.), + func(50.), func(60.), func(70.), func(80.), func(90.)}; + + for (idx_t j = 0; j < pointcloud.size(); ++j) { + static double interpolation_tolerance = 1.e-4; + Log::info() << target(j) << " " << check[j] << std::endl; + EXPECT_APPROX_EQ(target(j), check[j], interpolation_tolerance); + } + } +} + +CASE("test_interpolation_N64_to_O32_bilinear_remapping") { + Grid grid_src("N64"); + Mesh mesh_src(grid_src); + NodeColumns fs_src(mesh_src); + + Grid grid_tgt("O32"); + Mesh mesh_tgt(grid_tgt); + NodeColumns fs_tgt(mesh_tgt); + + Interpolation interpolation(option::type("bilinear-remapping") | util::Config("max_fraction_elems_to_try", 0.4), + fs_src, fs_tgt); + + Field field_source = fs_src.createField(option::name("source")); + Field field_target = fs_tgt.createField(option::name("target")); + + const double deg2rad = M_PI / 180., c_lat = 0. * M_PI, c_lon = 1. * M_PI, c_rad = 2. * M_PI / 9.; + auto func = [](double lon, double lat, double t) { return std::cos(lat) * std::sin(lon); }; + + array::ArrayView lonlat = array::make_view(fs_src.nodes().lonlat()); + array::ArrayView source = array::make_view(field_source); + + for (idx_t j = 0; j < lonlat.shape(0); ++j) { + const double lon = deg2rad * lonlat(j, 0); + const double lat = deg2rad * lonlat(j, 1); + source(j) = func(lon, lat, 1.); + } + + interpolation.execute(field_source, field_target); + + array::ArrayView target = array::make_view(field_target); + array::ArrayView lonlat_tgt = array::make_view(fs_tgt.nodes().lonlat()); + std::vector diffs; + ATLAS_ASSERT(target.shape(0) == lonlat_tgt.shape(0)); + for (idx_t j = 0; j < lonlat_tgt.shape(0); ++j) { + const double lon = deg2rad * lonlat_tgt(j, 0); + const double lat = deg2rad * lonlat_tgt(j, 1); + const double check = func(lon, lat, 1.); + diffs.push_back((target(j) - check) * (target(j) - check)); + if (std::abs(target(j) - check) > 0.1) { + Log::info() << "lon, lat:" << lon << ", " << lat << " target, check: " << target(j) << ", " << check + << std::endl; + } + } + static double interpolation_tolerance = 1.e-3; + double std_dev = std::accumulate(diffs.begin(), diffs.end(), decltype(diffs)::value_type(0)) / diffs.size(); + double max_dev = std::sqrt(*std::max_element(diffs.begin(), diffs.end())); + Log::info() << " standard deviation " << std_dev << " max " << max_dev << std::endl; + EXPECT_APPROX_EQ(std_dev, 0.0, 1.e-6); + EXPECT_APPROX_EQ(max_dev, 0.0, interpolation_tolerance); +} +//----------------------------------------------------------------------------- + +} // namespace test +} // namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/interpolation/test_interpolation_cubic_prototype.cc b/src/tests/interpolation/test_interpolation_cubic_prototype.cc index b5d1f94a5..ff1a3bdf7 100644 --- a/src/tests/interpolation/test_interpolation_cubic_prototype.cc +++ b/src/tests/interpolation/test_interpolation_cubic_prototype.cc @@ -11,10 +11,6 @@ #include #include -#include "eckit/linalg/LinearAlgebra.h" -#include "eckit/linalg/Vector.h" -#include "eckit/types/Types.h" - #include "atlas/array.h" #include "atlas/field/Field.h" #include "atlas/functionspace/NodeColumns.h" @@ -30,6 +26,16 @@ #include "atlas/util/CoordinateEnums.h" #include "atlas/util/MicroDeg.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraSparse.h" +#else +#include "eckit/linalg/LinearAlgebra.h" +#endif +#include "eckit/linalg/SparseMatrix.h" +#include "eckit/linalg/Vector.h" +#include "eckit/types/Types.h" + + #include "CubicInterpolationPrototype.h" #include "tests/AtlasTestEnvironment.h" @@ -233,7 +239,11 @@ CASE("test horizontal cubic interpolation triplets") { std::vector tgt(departure_points.size()); eckit::linalg::Vector v_src(const_cast(f.data()), f.size()); eckit::linalg::Vector v_tgt(tgt.data(), tgt.size()); +#if ATLAS_ECKIT_HAVE_ECKIT_585 + eckit::linalg::LinearAlgebraSparse::backend().spmv(matrix, v_src, v_tgt); +#else eckit::linalg::LinearAlgebra::backend().spmv(matrix, v_src, v_tgt); +#endif Log::info() << "output = " << tgt << std::endl; } diff --git a/src/tests/interpolation/test_interpolation_finite_element.cc b/src/tests/interpolation/test_interpolation_finite_element.cc index ed81c45b4..a8dea6fb7 100644 --- a/src/tests/interpolation/test_interpolation_finite_element.cc +++ b/src/tests/interpolation/test_interpolation_finite_element.cc @@ -43,28 +43,38 @@ CASE("test_interpolation_finite_element") { auto func = [](double x) -> double { return std::sin(x * M_PI / 180.); }; - Interpolation interpolation(option::type("finite-element"), fs, pointcloud); + Interpolation interpolation(option::type("finite-element") | util::Config("max_fraction_elems_to_try", 0.4), fs, + pointcloud); + + SECTION("test maximum nearest neighbour settings") { + std::stringstream test_stream; + interpolation.print(test_stream); + std::string test_string = test_stream.str(); + EXPECT((test_string.find("max_fraction_elems_to_try: 0.4") != std::string::npos)); + } - Field field_source = fs.createField(option::name("source")); - Field field_target("target", array::make_datatype(), array::make_shape(pointcloud.size())); + SECTION("test interpolation outputs") { + Field field_source = fs.createField(option::name("source")); + Field field_target("target", array::make_datatype(), array::make_shape(pointcloud.size())); - auto lonlat = array::make_view(fs.nodes().lonlat()); - auto source = array::make_view(field_source); - for (idx_t j = 0; j < fs.nodes().size(); ++j) { - source(j) = func(lonlat(j, LON)); - } + auto lonlat = array::make_view(fs.nodes().lonlat()); + auto source = array::make_view(field_source); + for (idx_t j = 0; j < fs.nodes().size(); ++j) { + source(j) = func(lonlat(j, LON)); + } - interpolation.execute(field_source, field_target); + interpolation.execute(field_source, field_target); - auto target = array::make_view(field_target); + auto target = array::make_view(field_target); - auto check = std::vector{func(00.), func(10.), func(20.), func(30.), func(40.), - func(50.), func(60.), func(70.), func(80.), func(90.)}; + auto check = std::vector{func(00.), func(10.), func(20.), func(30.), func(40.), + func(50.), func(60.), func(70.), func(80.), func(90.)}; - for (idx_t j = 0; j < pointcloud.size(); ++j) { - static double interpolation_tolerance = 1.e-4; - Log::info() << target(j) << " " << check[j] << std::endl; - EXPECT(eckit::types::is_approximately_equal(target(j), check[j], interpolation_tolerance)); + for (idx_t j = 0; j < pointcloud.size(); ++j) { + static double interpolation_tolerance = 1.e-4; + Log::info() << target(j) << " " << check[j] << std::endl; + EXPECT(eckit::types::is_approximately_equal(target(j), check[j], interpolation_tolerance)); + } } } diff --git a/src/tests/interpolation/test_interpolation_structured2D.cc b/src/tests/interpolation/test_interpolation_structured2D.cc index 266122c95..be4c13ad0 100644 --- a/src/tests/interpolation/test_interpolation_structured2D.cc +++ b/src/tests/interpolation/test_interpolation_structured2D.cc @@ -21,6 +21,7 @@ #include "atlas/meshgenerator.h" #include "atlas/output/Gmsh.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" #include "tests/AtlasTestEnvironment.h" @@ -116,31 +117,6 @@ FunctionSpace output_functionspace(const Grid& grid) { return NodeColumns{output_mesh}; } -double vortex_rollup(double lon, double lat, double t) { - // lon and lat in degrees! - - // Formula found in "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" - // by Peter Bosler, James Kent, Robert Krasny, CHristiane Jablonowski, JCP 2015 - - lon *= M_PI / 180.; - lat *= M_PI / 180.; - - auto sqr = [](const double x) { return x * x; }; - auto sech = [](const double x) { return 1. / std::cosh(x); }; - const double T = 1.; - const double Omega = 2. * M_PI / T; - t *= T; - const double lambda_prime = std::atan2(-std::cos(lon - Omega * t), std::tan(lat)); - const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - Omega * t))); - double omega = 0.; - double a = util::Earth::radius(); - if (rho != 0.) { - omega = 0.5 * 3 * std::sqrt(3) * a * Omega * sqr(sech(rho)) * std::tanh(rho) / rho; - } - double q = 1. - std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); - return q; -}; - CASE("which scheme?") { Log::info() << scheme().getString("type") << std::endl; } @@ -162,7 +138,7 @@ CASE("test_interpolation_structured using functionspace API") { auto lonlat = array::make_view(input_fs.xy()); auto source = array::make_view(field_source); for (idx_t n = 0; n < input_fs.size(); ++n) { - source(n) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 1.); + source(n) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 1.); } EXPECT(field_source.dirty()); @@ -202,7 +178,7 @@ CASE("test_interpolation_structured using grid API") { idx_t n{0}; for (auto p : input_grid.lonlat()) { - src_data[n++] = vortex_rollup(p.lon(), p.lat(), 1.); + src_data[n++] = util::function::vortex_rollup(p.lon(), p.lat(), 1.); } // Wrap memory in atlas Fields and interpolate @@ -259,7 +235,7 @@ CASE("test_interpolation_structured using fs API multiple levels") { auto source = array::make_view(field_source); for (idx_t n = 0; n < input_fs.size(); ++n) { for (idx_t k = 0; k < 3; ++k) { - source(n, k) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); + source(n, k) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); } } interpolation.execute(field_source, field_target); @@ -281,7 +257,7 @@ struct AdjointTolerance { template <> const double AdjointTolerance::value = 2.e-14; template <> -const float AdjointTolerance::value = 4.e-6; +const float AdjointTolerance::value = 2.e-5; template @@ -307,7 +283,7 @@ void test_interpolation_structured_using_fs_API_for_fieldset() { auto source = array::make_view(field_source); for (idx_t n = 0; n < input_fs.size(); ++n) { for (idx_t k = 0; k < 3; ++k) { - source(n, k) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); + source(n, k) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); } } } @@ -483,7 +459,7 @@ CASE("ATLAS-315: Target grid with domain West of 0 degrees Lon") { auto source = array::make_view(field_src); constexpr double k = 1; for (idx_t n = 0; n < source.size(); ++n) { - source(n) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); + source(n) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); } interpolation.execute(field_src, field_tgt); diff --git a/src/tests/interpolation/test_interpolation_structured2D_to_unstructured.cc b/src/tests/interpolation/test_interpolation_structured2D_to_unstructured.cc index 25d0704dc..65ac5e42b 100644 --- a/src/tests/interpolation/test_interpolation_structured2D_to_unstructured.cc +++ b/src/tests/interpolation/test_interpolation_structured2D_to_unstructured.cc @@ -21,6 +21,7 @@ #include "atlas/meshgenerator.h" #include "atlas/output/Gmsh.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" #include "tests/AtlasTestEnvironment.h" @@ -37,32 +38,6 @@ std::string input_gridname(const std::string& default_grid) { return eckit::Resource("--input-grid", default_grid); } -double vortex_rollup(double lon, double lat, double t) { - // lon and lat in degrees! - - // Formula found in "A Lagrangian Particle Method with Remeshing for Tracer Transport on the Sphere" - // by Peter Bosler, James Kent, Robert Krasny, CHristiane Jablonowski, JCP 2015 - - lon *= M_PI / 180.; - lat *= M_PI / 180.; - - auto sqr = [](const double x) { return x * x; }; - auto sech = [](const double x) { return 1. / std::cosh(x); }; - const double T = 1.; - const double Omega = 2. * M_PI / T; - t *= T; - const double lambda_prime = std::atan2(-std::cos(lon - Omega * t), std::tan(lat)); - const double rho = 3. * std::sqrt(1. - sqr(std::cos(lat)) * sqr(std::sin(lon - Omega * t))); - double omega = 0.; - double a = util::Earth::radius(); - if (rho != 0.) { - omega = 0.5 * 3 * std::sqrt(3) * a * Omega * sqr(sech(rho)) * std::tanh(rho) / rho; - } - double q = 1. - std::tanh(0.2 * rho * std::sin(lambda_prime - omega / a * t)); - return q; -}; - - FunctionSpace output_functionspace_match() { std::vector points; if (mpi::size() == 2) { @@ -124,7 +99,7 @@ FieldSet create_source_fields(StructuredColumns& fs, idx_t nb_fields, idx_t nb_l auto source = array::make_view(field_source); for (idx_t n = 0; n < fs.size(); ++n) { for (idx_t k = 0; k < nb_levels; ++k) { - source(n, k) = vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); + source(n, k) = util::function::vortex_rollup(lonlat(n, LON), lonlat(n, LAT), 0.5 + double(k) / 2); } }; } diff --git a/src/tests/linalg/test_linalg_dense.cc b/src/tests/linalg/test_linalg_dense.cc index 687880798..a085fe5a9 100644 --- a/src/tests/linalg/test_linalg_dense.cc +++ b/src/tests/linalg/test_linalg_dense.cc @@ -11,6 +11,13 @@ #include #include +#include "atlas/library/config.h" +#if ATLAS_ECKIT_HAVE_ECKIT_585 +#include "eckit/linalg/LinearAlgebraDense.h" +#else +#include "eckit/linalg/LinearAlgebra.h" +#endif + #include "eckit/linalg/Matrix.h" #include "eckit/linalg/Vector.h" @@ -67,7 +74,11 @@ void expect_equal(const T1& v, const T2& r) { CASE("test configuration via resource") { if (atlas::Library::instance().linalgDenseBackend().empty()) { +#if ATLAS_ECKIT_HAVE_ECKIT_585 + if (eckit::linalg::LinearAlgebraDense::hasBackend("mkl")) { +#else if (eckit::linalg::LinearAlgebra::hasBackend("mkl")) { +#endif EXPECT_EQ(dense::current_backend().type(), "mkl"); } else { diff --git a/src/tests/linalg/test_linalg_sparse.cc b/src/tests/linalg/test_linalg_sparse.cc index 181341c5d..ee1b10302 100644 --- a/src/tests/linalg/test_linalg_sparse.cc +++ b/src/tests/linalg/test_linalg_sparse.cc @@ -14,6 +14,7 @@ #include "eckit/linalg/Matrix.h" #include "eckit/linalg/Vector.h" +#include "atlas/array.h" #include "atlas/linalg/sparse.h" #include "tests/AtlasTestEnvironment.h" diff --git a/src/tests/mesh/CMakeLists.txt b/src/tests/mesh/CMakeLists.txt index 6137d111d..b25d98b6a 100644 --- a/src/tests/mesh/CMakeLists.txt +++ b/src/tests/mesh/CMakeLists.txt @@ -139,3 +139,4 @@ atlas_add_cuda_test( LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) + diff --git a/src/tests/mesh/test_cubedsphere_meshgen.cc b/src/tests/mesh/test_cubedsphere_meshgen.cc index 135c8fb9c..fef7770ad 100644 --- a/src/tests/mesh/test_cubedsphere_meshgen.cc +++ b/src/tests/mesh/test_cubedsphere_meshgen.cc @@ -9,17 +9,20 @@ #include "atlas/field/FieldSet.h" #include "atlas/functionspace/CellColumns.h" #include "atlas/functionspace/NodeColumns.h" +#include "atlas/functionspace/StructuredColumns.h" #include "atlas/grid.h" #include "atlas/grid/CubedSphereGrid.h" #include "atlas/grid/Partitioner.h" #include "atlas/grid/Tiles.h" #include "atlas/grid/detail/partitioner/CubedSpherePartitioner.h" +#include "atlas/interpolation.h" #include "atlas/mesh.h" #include "atlas/meshgenerator.h" #include "atlas/meshgenerator/detail/cubedsphere/CubedSphereUtility.h" #include "atlas/option.h" #include "atlas/output/Gmsh.h" #include "atlas/util/CoordinateEnums.h" +#include "atlas/util/function/VortexRollup.h" #include "tests/AtlasTestEnvironment.h" #include "eckit/mpi/Operation.h" @@ -117,12 +120,6 @@ CASE("cubedsphere_mesh_jacobian_test") { } } - -double testFunction(double lon, double lat) { - return std::sin(3 * lon * M_PI / 180) * std::sin(2 * lat * M_PI / 180); -} - - void testHaloExchange(const std::string& gridStr, const std::string& partitionerStr, idx_t halo, bool output = true) { // Set grid. const auto grid = Grid(gridStr); @@ -162,7 +159,7 @@ void testHaloExchange(const std::string& gridStr, const std::string& partitioner break; } - testView1(i) = testFunction(lonLatView(i, LON), lonLatView(i, LAT)); + testView1(i) = 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), 1.0); ++testFuncCallCount; } @@ -176,7 +173,7 @@ void testHaloExchange(const std::string& gridStr, const std::string& partitioner // Check all values after halo exchange. double maxError = 0; for (idx_t i = 0; i < nodeColumns.size(); ++i) { - const double testVal = testFunction(lonLatView(i, LON), lonLatView(i, LAT)); + const double testVal = 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), 1.0); maxError = std::max(maxError, std::abs(testView1(i) - testVal)); } @@ -207,7 +204,7 @@ void testHaloExchange(const std::string& gridStr, const std::string& partitioner break; } - testView2(i) = testFunction(lonLatView(i, LON), lonLatView(i, LAT)); + testView2(i) = 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), 1.0); ++testFuncCallCount; } @@ -222,7 +219,7 @@ void testHaloExchange(const std::string& gridStr, const std::string& partitioner maxError = 0; for (idx_t i = 0; i < cellColumns.size(); ++i) { // Test field and test function should be the same. - const double testVal = testFunction(lonLatView(i, LON), lonLatView(i, LAT)); + const double testVal = 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), 1.0); maxError = std::max(maxError, std::abs(testView2(i) - testVal)); } @@ -306,6 +303,148 @@ CASE("cubedsphere_mesh_test") { } } +CASE("cubedsphere_dual_mesh_test") { + SECTION("NodeColumns") { + // This test generates a test field on a structured gird, then interpolates + // it to a cubed-sphere dual mesh field. + // The following functionality is tested: + // * Generation of a cubed-sphere dual mesh. + // * Partitioning of cubed-sphere dual mesh with a matching mesh partitioner. + // * Interpolation from a StructuredColumns source to a cubed sphere dual mesh (NodeColumns) target. + + // Set number of levels for test field. + const idx_t nLevels = 5; + + // Make a grid, mesh and functionspace for structured source grid. + + const auto sourceGrid = Grid("O96"); + const auto sourceDistribution = grid::Distribution(sourceGrid, grid::Partitioner("equal_bands")); + const auto sourceMesh = MeshGenerator("structured").generate(sourceGrid, sourceDistribution); + const auto sourceFunctionSpace = functionspace::StructuredColumns( + sourceGrid, sourceDistribution, util::Config("halo", 1) | util::Config("periodic_points", true)); + + // Make and set a source field. + auto sourceField = sourceFunctionSpace.createField(util::Config("name", "source field") | + util::Config("levels", nLevels)); + auto sourceView = array::make_view(sourceField); + const auto sourceLonLatView = array::make_view(sourceFunctionSpace.lonlat()); + + for (idx_t i = 0; i < sourceView.shape(0); ++i) { + for (idx_t j = 0; j < sourceView.shape(1); ++j) { + sourceView(i, j) = + 1.0 + util::function::vortex_rollup(sourceLonLatView(i, LON), sourceLonLatView(i, LAT), + static_cast(j) / (nLevels - 1)); + } + } + + // Set matching mesh partitioner. + const auto targetPartitioner = grid::MatchingPartitioner(sourceMesh); + + // Set target grid, mesh and functionspace. + const auto targetGrid = Grid("CS-LFR-C-48"); + const auto targetMesh = MeshGenerator("cubedsphere_dual").generate(targetGrid, targetPartitioner); + const auto targetFunctionSpace = functionspace::NodeColumns(targetMesh); + auto targetField = + targetFunctionSpace.createField(util::Config("name", "targetField") | util::Config("levels", 5)); + + // Perform interpolation. + auto scheme = util::Config("type", "structured-linear2D") | util::Config("halo", 1); + auto interp = Interpolation(scheme, sourceFunctionSpace, targetFunctionSpace); + interp.execute(sourceField, targetField); + targetField.haloExchange(); + + // Test accuracy of interpolation. + auto targetView = array::make_view(targetField); + const auto targetLonLatView = array::make_view(targetFunctionSpace.lonlat()); + double maxError = 0.; + for (idx_t i = 0; i < targetView.shape(0); ++i) { + for (idx_t j = 0; j < targetView.shape(1); ++j) { + double referenceVal = + 1.0 + util::function::vortex_rollup(targetLonLatView(i, LON), targetLonLatView(i, LAT), + static_cast(j) / (nLevels - 1)); + + double relativeError = std::abs((targetView(i, j) - referenceVal) / referenceVal); + maxError = std::max(maxError, relativeError); + } + } + mpi::comm().allReduceInPlace(maxError, eckit::mpi::Operation::Code::MAX); + Log::info() << "Max interpolation error = " + std::to_string(maxError) << std::endl; + + // Maximum interpolation error should be less than 1%. + EXPECT(maxError < 0.01); + + // gmsh output. + const auto gmshConfigXy = + util::Config("coordinates", "xy") | util::Config("ghost", true) | util::Config("info", true); + const auto gmshConfigXyz = + util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); + auto gmshXy = output::Gmsh("dual_nodes_xy.msh", gmshConfigXy); + auto gmshXyz = output::Gmsh("dual_nodes_xyz.msh", gmshConfigXyz); + gmshXy.write(targetMesh); + gmshXyz.write(targetMesh); + gmshXy.write(targetField, targetFunctionSpace); + gmshXyz.write(targetField, targetFunctionSpace); + } + + SECTION("CellColumns") { + // Simple test to check halo exchange and output mesh. + + // Set number of levels for test field. + const idx_t nLevels = 5; + + // Set grid, mesh and functionspace. + const auto grid = Grid("CS-LFR-C-48"); + const auto mesh = + MeshGenerator("cubedsphere_dual", util::Config("partitioner", "equal_regions")).generate(grid); + const auto functionSpace = functionspace::CellColumns(mesh); + auto field = + functionSpace.createField(util::Config("name", "targetField") | util::Config("levels", nLevels)); + + // Make views. + auto fieldView = array::make_view(field); + const auto lonLatView = array::make_view(functionSpace.lonlat()); + const auto remoteIdxView = array::make_indexview(functionSpace.cells().remote_index()); + const auto partView = array::make_view(functionSpace.cells().partition()); + + // Set test field on owned cells. + for (idx_t i = 0; i < fieldView.shape(0); ++i) { + // Break once we've got to ghost cells (cells mirrored from ghost nodes). + if (partView(i) != mpi::rank() || remoteIdxView(i) != i) { + break; + } + + for (idx_t j = 0; j < fieldView.shape(1); ++j) { + fieldView(i, j) = 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), + static_cast(j) / (nLevels - 1)); + } + } + + // Perform halo exchange. + field.haloExchange(); + + // Check test field on *all* cells. + for (idx_t i = 0; i < fieldView.shape(0); ++i) { + for (idx_t j = 0; j < fieldView.shape(1); ++j) { + EXPECT_APPROX_EQ(fieldView(i, j), + 1.0 + util::function::vortex_rollup(lonLatView(i, LON), lonLatView(i, LAT), + static_cast(j) / (nLevels - 1))); + } + } + + // gmsh output. + const auto gmshConfigXy = + util::Config("coordinates", "xy") | util::Config("ghost", true) | util::Config("info", true); + const auto gmshConfigXyz = + util::Config("coordinates", "xyz") | util::Config("ghost", true) | util::Config("info", true); + auto gmshXy = output::Gmsh("dual_cells_xy.msh", gmshConfigXy); + auto gmshXyz = output::Gmsh("dual_cells_xyz.msh", gmshConfigXyz); + gmshXy.write(mesh); + gmshXyz.write(mesh); + gmshXy.write(FieldSet{field}, functionSpace); + gmshXyz.write(FieldSet{field}, functionSpace); + } +} + } // namespace test } // namespace atlas diff --git a/src/tests/mesh/test_healpixmeshgen.cc b/src/tests/mesh/test_healpixmeshgen.cc index 695593880..c83be6aaa 100644 --- a/src/tests/mesh/test_healpixmeshgen.cc +++ b/src/tests/mesh/test_healpixmeshgen.cc @@ -14,6 +14,7 @@ #include "atlas/meshgenerator.h" #include "atlas/meshgenerator/detail/HealpixMeshGenerator.h" #include "atlas/output/Gmsh.h" +#include "atlas/util/PolygonXY.h" #include "tests/AtlasTestEnvironment.h" @@ -131,6 +132,33 @@ CASE("construction by integer") { EXPECT(HealpixGrid(3) == Grid("H3")); } +//----------------------------------------------------------------------------- + +CASE("matching mesh partitioner") { + auto grid = Grid{"H8"}; + auto mesh = HealpixMeshGenerator{}.generate(grid); + auto match = MatchingPartitioner{mesh}; + auto& polygons = mesh.polygons(); + + static bool do_once = [&]() { + for (idx_t i = 0; i < polygons.size(); ++i) { + auto poly = util::PolygonXY{polygons[i]}; + Log::info() << "polygon[" << i << "]:\n"; + for (idx_t j = 0; j < poly.size(); ++j) { + Log::info() << " " << std::setw(5) << std::left << j << std::setprecision(16) << poly[j] << std::endl; + } + } + return true; + }(); + + + SECTION("H8 -> O64") { EXPECT_NO_THROW(match.partition(Grid{"O64"})); } + SECTION("H8 -> L32x17") { EXPECT_NO_THROW(match.partition(Grid{"L32x17"})); } + SECTION("H8 -> S32x17") { EXPECT_NO_THROW(match.partition(Grid{"S32x17"})); } + SECTION("H8 -> F32") { EXPECT_NO_THROW(match.partition(Grid{"F32"})); } + SECTION("H8 -> L64x33 (west=-180)") { EXPECT_NO_THROW(match.partition(Grid{"L64x33", GlobalDomain(-180.)})); } +} + } // namespace test } // namespace atlas diff --git a/src/tests/mesh/test_mesh_build_edges.cc b/src/tests/mesh/test_mesh_build_edges.cc index 2055af1ca..97da39bae 100644 --- a/src/tests/mesh/test_mesh_build_edges.cc +++ b/src/tests/mesh/test_mesh_build_edges.cc @@ -247,7 +247,7 @@ CASE("test_build_edges") { // Accumulate facets of cells ( edges in 2D ) mesh::actions::build_edges(mesh, option::pole_edges(false)); - idx_t edge_nodes_check[] = { + std::vector edge_nodes_check{ 0, 21, 21, 22, 22, 1, 1, 0, 22, 23, 23, 2, 2, 1, 3, 25, 25, 26, 26, 4, 4, 3, 26, 27, 27, 5, 5, 4, 27, 28, 28, 6, 6, 5, 28, 29, 29, 7, 7, 6, 8, 31, 31, 32, 32, 9, 9, 8, 32, 33, 33, 10, 10, 9, 33, 34, 34, 11, 11, 10, 34, 35, 35, 12, 12, 11, 13, 37, 37, 38, 38, 14, 14, 13, 38, 39, 39, 15, 15, 14, 39, @@ -266,206 +266,208 @@ CASE("test_build_edges") { const mesh::HybridElements::Connectivity& edge_node_connectivity = mesh.edges().node_connectivity(); EXPECT(mesh.projection().units() == "degrees"); const util::UniqueLonLat compute_uid(mesh); + EXPECT_EQ(mesh.edges().size(), edge_nodes_check.size() / 2); for (idx_t jedge = 0; jedge < mesh.edges().size(); ++jedge) { if (compute_uid(edge_nodes_check[2 * jedge + 0]) < compute_uid(edge_nodes_check[2 * jedge + 1])) { - EXPECT(edge_nodes_check[2 * jedge + 0] == edge_node_connectivity(jedge, 0)); - EXPECT(edge_nodes_check[2 * jedge + 1] == edge_node_connectivity(jedge, 1)); + EXPECT_EQ(edge_nodes_check[2 * jedge + 0], edge_node_connectivity(jedge, 0)); + EXPECT_EQ(edge_nodes_check[2 * jedge + 1], edge_node_connectivity(jedge, 1)); } else { - EXPECT(edge_nodes_check[2 * jedge + 0] == edge_node_connectivity(jedge, 1)); - EXPECT(edge_nodes_check[2 * jedge + 1] == edge_node_connectivity(jedge, 0)); + EXPECT_EQ(edge_nodes_check[2 * jedge + 0], edge_node_connectivity(jedge, 1)); + EXPECT_EQ(edge_nodes_check[2 * jedge + 1], edge_node_connectivity(jedge, 0)); } } } - idx_t edge_to_cell_check[] = {0, missing_value, - 0, 16, - 0, 1, - 0, missing_value, - 1, 17, - 1, 56, - 1, missing_value, - 2, 58, - 2, 20, - 2, 3, - 2, missing_value, - 3, 21, - 3, 4, - 3, missing_value, - 4, 22, - 4, 5, - 4, missing_value, - 5, 23, - 5, 59, - 5, missing_value, - 6, 61, - 6, 26, - 6, 7, - 6, missing_value, - 7, 27, - 7, 8, - 7, missing_value, - 8, 28, - 8, 9, - 8, missing_value, - 9, 29, - 9, 62, - 9, missing_value, - 10, 64, - 10, 32, - 10, 11, - 10, missing_value, - 11, 33, - 11, 12, - 11, missing_value, - 12, 34, - 12, 13, - 12, missing_value, - 13, 35, - 13, 65, - 13, missing_value, - 14, 67, - 14, 38, - 14, 15, - 14, missing_value, - 15, 39, - 15, missing_value, - 15, missing_value, - 16, missing_value, - 16, 40, - 16, 17, - 17, 41, - 17, 18, - 18, 68, - 18, 19, - 18, 56, - 19, 70, - 19, 20, - 19, 58, - 20, 42, - 20, 21, - 21, 43, - 21, 22, - 22, 44, - 22, 23, - 23, 45, - 23, 24, - 24, 71, - 24, 25, - 24, 59, - 25, 73, - 25, 26, - 25, 61, - 26, 46, - 26, 27, - 27, 47, - 27, 28, - 28, 48, - 28, 29, - 29, 49, - 29, 30, - 30, 74, - 30, 31, - 30, 62, - 31, 76, - 31, 32, - 31, 64, - 32, 50, - 32, 33, - 33, 51, - 33, 34, - 34, 52, - 34, 35, - 35, 53, - 35, 36, - 36, 77, - 36, 37, - 36, 65, - 37, 79, - 37, 38, - 37, 67, - 38, 54, - 38, 39, - 39, 55, - 39, missing_value, - 40, missing_value, - 40, missing_value, - 40, 41, - 41, missing_value, - 41, 68, - 42, 70, - 42, missing_value, - 42, 43, - 43, missing_value, - 43, 44, - 44, missing_value, - 44, 45, - 45, missing_value, - 45, 71, - 46, 73, - 46, missing_value, - 46, 47, - 47, missing_value, - 47, 48, - 48, missing_value, - 48, 49, - 49, missing_value, - 49, 74, - 50, 76, - 50, missing_value, - 50, 51, - 51, missing_value, - 51, 52, - 52, missing_value, - 52, 53, - 53, missing_value, - 53, 77, - 54, 79, - 54, missing_value, - 54, 55, - 55, missing_value, - 55, missing_value, - 56, 57, - 57, 58, - 57, missing_value, - 59, 60, - 60, 61, - 60, missing_value, - 62, 63, - 63, 64, - 63, missing_value, - 65, 66, - 66, 67, - 66, missing_value, - 68, 69, - 69, missing_value, - 69, 70, - 71, 72, - 72, missing_value, - 72, 73, - 74, 75, - 75, missing_value, - 75, 76, - 77, 78, - 78, missing_value, - 78, 79}; + std::vector edge_to_cell_check{0, missing_value, + 0, 16, + 0, 1, + 0, missing_value, + 1, 17, + 1, 56, + 1, missing_value, + 2, 58, + 2, 20, + 2, 3, + 2, missing_value, + 3, 21, + 3, 4, + 3, missing_value, + 4, 22, + 4, 5, + 4, missing_value, + 5, 23, + 5, 59, + 5, missing_value, + 6, 61, + 6, 26, + 6, 7, + 6, missing_value, + 7, 27, + 7, 8, + 7, missing_value, + 8, 28, + 8, 9, + 8, missing_value, + 9, 29, + 9, 62, + 9, missing_value, + 10, 64, + 10, 32, + 10, 11, + 10, missing_value, + 11, 33, + 11, 12, + 11, missing_value, + 12, 34, + 12, 13, + 12, missing_value, + 13, 35, + 13, 65, + 13, missing_value, + 14, 67, + 14, 38, + 14, 15, + 14, missing_value, + 15, 39, + 15, missing_value, + 15, missing_value, + 16, missing_value, + 16, 40, + 16, 17, + 17, 41, + 17, 18, + 18, 68, + 18, 19, + 18, 56, + 19, 70, + 19, 20, + 19, 58, + 20, 42, + 20, 21, + 21, 43, + 21, 22, + 22, 44, + 22, 23, + 23, 45, + 23, 24, + 24, 71, + 24, 25, + 24, 59, + 25, 73, + 25, 26, + 25, 61, + 26, 46, + 26, 27, + 27, 47, + 27, 28, + 28, 48, + 28, 29, + 29, 49, + 29, 30, + 30, 74, + 30, 31, + 30, 62, + 31, 76, + 31, 32, + 31, 64, + 32, 50, + 32, 33, + 33, 51, + 33, 34, + 34, 52, + 34, 35, + 35, 53, + 35, 36, + 36, 77, + 36, 37, + 36, 65, + 37, 79, + 37, 38, + 37, 67, + 38, 54, + 38, 39, + 39, 55, + 39, missing_value, + 40, missing_value, + 40, missing_value, + 40, 41, + 41, missing_value, + 41, 68, + 42, 70, + 42, missing_value, + 42, 43, + 43, missing_value, + 43, 44, + 44, missing_value, + 44, 45, + 45, missing_value, + 45, 71, + 46, 73, + 46, missing_value, + 46, 47, + 47, missing_value, + 47, 48, + 48, missing_value, + 48, 49, + 49, missing_value, + 49, 74, + 50, 76, + 50, missing_value, + 50, 51, + 51, missing_value, + 51, 52, + 52, missing_value, + 52, 53, + 53, missing_value, + 53, 77, + 54, 79, + 54, missing_value, + 54, 55, + 55, missing_value, + 55, missing_value, + 56, 57, + 57, 58, + 57, missing_value, + 59, 60, + 60, 61, + 60, missing_value, + 62, 63, + 63, 64, + 63, missing_value, + 65, 66, + 66, 67, + 66, missing_value, + 68, 69, + 69, missing_value, + 69, 70, + 71, 72, + 72, missing_value, + 72, 73, + 74, 75, + 75, missing_value, + 75, 76, + 77, 78, + 78, missing_value, + 78, 79}; { const mesh::HybridElements::Connectivity& cell_node_connectivity = mesh.cells().node_connectivity(); const mesh::HybridElements::Connectivity& edge_cell_connectivity = mesh.edges().cell_connectivity(); const util::UniqueLonLat compute_uid(mesh); + EXPECT_EQ(mesh.edges().size(), edge_to_cell_check.size() / 2); for (idx_t jedge = 0; jedge < mesh.edges().size(); ++jedge) { idx_t e1 = edge_to_cell_check[2 * jedge + 0]; idx_t e2 = edge_to_cell_check[2 * jedge + 1]; if (e2 == edge_cell_connectivity.missing_value() || compute_uid(cell_node_connectivity.row(e1)) < compute_uid(cell_node_connectivity.row(e2))) { - EXPECT(edge_to_cell_check[2 * jedge + 0] == edge_cell_connectivity(jedge, 0)); - EXPECT(edge_to_cell_check[2 * jedge + 1] == edge_cell_connectivity(jedge, 1)); + EXPECT_EQ(edge_to_cell_check[2 * jedge + 0], edge_cell_connectivity(jedge, 0)); + EXPECT_EQ(edge_to_cell_check[2 * jedge + 1], edge_cell_connectivity(jedge, 1)); } else { std::cout << "jedge " << jedge << std::endl; - EXPECT(edge_to_cell_check[2 * jedge + 0] == edge_cell_connectivity(jedge, 1)); - EXPECT(edge_to_cell_check[2 * jedge + 1] == edge_cell_connectivity(jedge, 0)); + EXPECT_EQ(edge_to_cell_check[2 * jedge + 0], edge_cell_connectivity(jedge, 1)); + EXPECT_EQ(edge_to_cell_check[2 * jedge + 1], edge_cell_connectivity(jedge, 0)); } } } @@ -473,9 +475,9 @@ CASE("test_build_edges") { { const MultiBlockConnectivity& elem_edge_connectivity = mesh.cells().edge_connectivity(); for (idx_t jelem = 0; jelem < mesh.cells().size(); ++jelem) { - std::cout << jelem << " : "; + std::cout << std::setw(3) << jelem << " : "; for (idx_t jedge = 0; jedge < elem_edge_connectivity.cols(jelem); ++jedge) { - std::cout << elem_edge_connectivity(jelem, jedge) << " "; + std::cout << std::setw(3) << elem_edge_connectivity(jelem, jedge) << " "; } std::cout << std::endl; } @@ -494,13 +496,13 @@ CASE("test_build_edges_triangles_only") { const MultiBlockConnectivity& elem_edge_connectivity = mesh.cells().edge_connectivity(); const MultiBlockConnectivity& elem_node_connectivity = mesh.cells().node_connectivity(); for (idx_t jelem = 0; jelem < mesh.cells().size(); ++jelem) { - std::cout << jelem << " : edges ( "; + std::cout << "elem" << std::setw(3) << jelem << " : edges ( "; for (idx_t jedge = 0; jedge < elem_edge_connectivity.cols(jelem); ++jedge) { - std::cout << elem_edge_connectivity(jelem, jedge) << " "; + std::cout << std::setw(3) << elem_edge_connectivity(jelem, jedge) << " "; } - std::cout << ") | nodes ( "; + std::cout << ") | nodes ( "; for (idx_t jnode = 0; jnode < elem_node_connectivity.cols(jelem); ++jnode) { - std::cout << elem_node_connectivity(jelem, jnode) << " "; + std::cout << std::setw(3) << elem_node_connectivity(jelem, jnode) << " "; } std::cout << ")" << std::endl; } diff --git a/src/tests/projection/CMakeLists.txt b/src/tests/projection/CMakeLists.txt index c16950cbb..b44a1ef90 100644 --- a/src/tests/projection/CMakeLists.txt +++ b/src/tests/projection/CMakeLists.txt @@ -10,6 +10,7 @@ foreach(test test_bounding_box test_projection_LAEA test_projection_cubed_sphere + test_projection_variable_resolution test_rotation ) ecbuild_add_test( TARGET atlas_${test} SOURCES ${test}.cc LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) diff --git a/src/tests/projection/test_jacobian.cc b/src/tests/projection/test_jacobian.cc index 9206998ec..3abfa774e 100644 --- a/src/tests/projection/test_jacobian.cc +++ b/src/tests/projection/test_jacobian.cc @@ -163,6 +163,61 @@ CASE("test_lonlat") { doTaylorTest(StructuredGrid("N16"), 1e-9); } +CASE("test_constructors") { + Projection::Jacobian a = {}; + Projection::Jacobian b = {1., 2., 3., 4.}; + Projection::Jacobian c = {{5., 6.}, {7., 8.}}; + + Log::info() << "a =" << std::endl; + Log::info() << a << std::endl; + Log::info() << "b =" << std::endl; + Log::info() << b << std::endl; + Log::info() << "c =" << std::endl; + Log::info() << c << std::endl; + + EXPECT_EQ(a[0][0], 0.); + EXPECT_EQ(a[0][1], 0.); + EXPECT_EQ(a[1][0], 0.); + EXPECT_EQ(a[1][1], 0.); + + EXPECT_EQ(b[0][0], 1.); + EXPECT_EQ(b[0][1], 2.); + EXPECT_EQ(b[1][0], 3.); + EXPECT_EQ(b[1][1], 4.); + + EXPECT_EQ(c[0][0], 5.); + EXPECT_EQ(c[0][1], 6.); + EXPECT_EQ(c[1][0], 7.); + EXPECT_EQ(c[1][1], 8.); +} + +CASE("test_multiplication") { + const Projection::Jacobian A = {{0., -1.}, {1., 0.}}; + const Point2 x = {2., 1.}; + + const auto Ax = A * x; + const auto AinvAx = A.inverse() * Ax; + const auto Atimes2 = A * 2.; + + Log::info() << "A =" << std::endl; + Log::info() << A << std::endl; + Log::info() << "2A =" << std::endl; + Log::info() << Atimes2 << std::endl; + Log::info() << "x =" << std::endl; + Log::info() << x << std::endl; + Log::info() << "Ax =" << std::endl; + Log::info() << Ax << std::endl; + Log::info() << "A^-1 Ax =" << std::endl; + Log::info() << AinvAx << std::endl; + + EXPECT_EQ(Atimes2[0][0], 0.); + EXPECT_EQ(Atimes2[0][1], -2.); + EXPECT_EQ(Atimes2[1][0], 2.); + EXPECT_EQ(Atimes2[1][1], 0.); + EXPECT_EQ(Ax, Point2(-1., 2.)); + EXPECT_EQ(AinvAx, x); +} + //----------------------------------------------------------------------------- diff --git a/src/tests/projection/test_projection_variable_resolution.cc b/src/tests/projection/test_projection_variable_resolution.cc new file mode 100644 index 000000000..38d49df1b --- /dev/null +++ b/src/tests/projection/test_projection_variable_resolution.cc @@ -0,0 +1,614 @@ +/** +* (C) Crown copyright 2021, 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/grid.h" +#include "atlas/mesh/Mesh.h" +#include "atlas/option.h" +#include "atlas/output/Gmsh.h" +#include "atlas/projection.h" +#include "atlas/util/Config.h" +#include "atlas/util/Point.h" +#include "atlas/util/Rotation.h" +#include "tests/AtlasTestEnvironment.h" + +using namespace atlas::util; +using namespace atlas::grid; +using atlas::util::Rotation; + +namespace { + +///< use vectors of double for testing + +const std::vector lon_LAM_str{ + 346.9838795150958504, 347.9838795150958504, 348.9838795150958504, 349.8677242998691668, 350.6659835640296592, + 351.3869448275862055, 352.0380931034482614, 352.6892413793103174, 353.3403896551723733, 353.9915379310344861, + 354.642686206896542, 355.2938344827585979, 355.9449827586206538, 356.5961310344827666, 357.2472793103448225, + 357.8984275862068785, 358.5495758620689344, 359.2007241379310472, 359.8518724137931031, 360.503020689655159, + 361.1541689655172149, 361.8053172413793277, 362.4564655172413836, 363.1076137931034395, 363.7587620689654955, + 364.4099103448276082, 365.0610586206896642, 365.7122068965517201, 366.363355172413776, 367.0843164359702087, + 367.8825757001305305, 368.7664204849036764, 369.7664204849036764, 370.7664204849036764}; + +const std::vector lat_LAM_str{ + -9.41181948490414122, -8.41181948490414122, -7.41181948490414122, -6.527974700130756425, -5.729715435970231141, + -5.00875417241366172, -4.357605896551548952, -3.706457620689436183, -3.055309344827323415, -2.404161068965210646, + -1.753012793103097877, -1.101864517240985109, -0.4507162413788723399, 0.2004320344832404288, 0.8515803103453531975, + 1.502728586207465966, 2.153876862069578735, 2.805025137931691503, 3.456173413793804272, 4.107321689655917041, + 4.758469965518029809, 5.409618241380142578, 6.060766517242255347, 6.711914793104368115, 7.363063068966480884, + 8.014211344828593653, 8.665359620690706421, 9.386320884247275842, 10.18458014840780024, 11.06842493318118414, + 12.06842493318118414, 13.06842493318118414}; + + +const std::vector lon_LAM_reg = { + 348.13120344827576, 348.78235172413787, 349.4334999999999809, 350.0846482758620368, 350.7357965517240928, + 351.3869448275862055, 352.0380931034482614, 352.6892413793103174, 353.3403896551723733, 353.9915379310344861, + 354.642686206896542, 355.2938344827585979, 355.9449827586206538, 356.5961310344827666, 357.2472793103448225, + 357.8984275862068785, 358.5495758620689344, 359.2007241379310472, 359.8518724137931031, 360.503020689655159, + 361.1541689655172149, 361.8053172413793277, 362.4564655172413836, 363.1076137931034395, 363.7587620689654955, + 364.4099103448276082, 365.0610586206896642, 365.7122068965517201, 366.363355172413776, 367.0145034482758888, + 367.6656517241379447, 368.3168000000000006, 368.9679482758621, 369.6190965517242}; + +const std::vector lat_LAM_reg = {-8.264495551724226, -7.613347275862113, -6.962199, + -6.311050724137887258, -5.659902448275774489, -5.00875417241366172, + -4.357605896551548952, -3.706457620689436183, -3.055309344827323415, + -2.404161068965210646, -1.753012793103097877, -1.101864517240985109, + -0.4507162413788723399, 0.2004320344832404288, 0.8515803103453531975, + 1.502728586207465966, 2.153876862069578735, 2.805025137931691503, + 3.456173413793804272, 4.107321689655917041, 4.758469965518029809, + 5.409618241380142578, 6.060766517242255347, 6.711914793104368115, + 7.363063068966480884, 8.014211344828593653, 8.665359620690706421, + 9.31650789655281919, 9.967656172414931959, 10.61880444827704473, + 11.269952724139157, 11.92110100000127}; + +const int nx = 34; // These number are here hardcoded, but there is a test using x/yrange_outer and delta_inner +const int ny = 32; +const double xrange_outer[2] = {348.13120344827576, 369.6190965517242}; +const double yrange_outer[2] = {-8.264495551724226, 11.92110100000127}; +const double xrange_inner[2] = {351.386944827586319, 366.363355172413776}; +const double yrange_inner[2] = {-5.008754172413662, 8.665359620690706}; + +const double rim_width = 4.; +const double delta_outer = 1.; +const double delta_inner = 0.6511482758621128; + +///< correction used +constexpr float epstest = std::numeric_limits::epsilon(); + + +auto make_var_ratio_projection = [](double var_ratio) { + Config conf; + conf.set("type", "variable_resolution"); + conf.set("outer.dx", delta_outer); ///< resolution of the external regular grid (rim) + conf.set("inner.dx", delta_inner); ///< resolution of the regional model (regular grid) + conf.set("progression", var_ratio); ///< power used for the stretching + conf.set("inner.xmin", xrange_inner[0]); ///< xstart of the internal regional grid + conf.set("inner.ymin", yrange_inner[0]); ///< ystart of the internal regional grid + conf.set("inner.xend", xrange_inner[1]); ///< xend of the regular part of stretched internal grid + conf.set("inner.yend", yrange_inner[1]); ///< yend of the regular part of stretched internal grid + conf.set("outer.xmin", xrange_outer[0]); ///< original domain startx + conf.set("outer.xend", xrange_outer[1]); ///< original domain endx + conf.set("outer.ymin", yrange_outer[0]); ///< original domain starty + conf.set("outer.yend", yrange_outer[1]); ///< original domain endy + conf.set("outer.width", rim_width); + return atlas::Projection(conf); +}; + +auto make_var_ratio_projection_rot = [](double var_ratio, std::vector north_pole) { + Config conf; + conf.set("type", "rotated_variable_resolution"); + conf.set("outer.dx", delta_outer); ///< resolution of the external regular grid (rim) + conf.set("inner.dx", delta_inner); ///< resolution of the regional model (regular grid) + conf.set("progression", var_ratio); ///< power used for the stretching + conf.set("inner.xmin", xrange_inner[0]); ///< xstart of the internal regional grid + conf.set("inner.ymin", yrange_inner[0]); ///< ystart of the internal regional grid + conf.set("inner.xend", xrange_inner[1]); ///< xend of the regular part of stretched internal grid + conf.set("inner.yend", yrange_inner[1]); ///< yend of the regular part of stretched internal grid + conf.set("outer.xmin", xrange_outer[0]); ///< original domain startx + conf.set("outer.xend", xrange_outer[1]); ///< original domain endx + conf.set("outer.ymin", yrange_outer[0]); ///< original domain starty + conf.set("outer.yend", yrange_outer[1]); ///< original domain endy + conf.set("outer.width", rim_width); + conf.set("north_pole", north_pole); + return atlas::Projection(conf); +}; + + +auto not_equal = [](double a, double b) { return std::abs(b - a) > 1.e-5; }; + +static double new_ratio(int n_stretched, double var_ratio) { + /** + * compute ratio, + * change stretching factor so that high and low grids + * retain original sizes + */ + + + ///< number of variable (stretched) grid points in one side + double var_ints_f = n_stretched * 1.; + double logr = std::log(var_ratio); + double log_ratio = (var_ints_f - 0.5) * logr; + + return std::exp(log_ratio / n_stretched); +}; + +/** + * n_stretched and n_rim. Outside the regular grid, only one side, + * number of grid points in the stretched grid and in the rim region +*/ +auto create_stretched_grid = [](const int& n_points_, const int& n_stretched_, const double& var_ratio_, + const int& n_rim, double lamphi, const bool& L_long) { + double new_ratio_{}; + double range_outer[2]{}; + + auto normalised = [L_long](double p) { + if (L_long) { + p = (p < 180) ? p + 360.0 : p; + } + return p; + }; + + lamphi = normalised(lamphi); + + if (L_long) { + range_outer[0] = xrange_outer[0]; + range_outer[1] = xrange_outer[1]; + } + else { + range_outer[0] = yrange_outer[0]; + range_outer[1] = yrange_outer[1]; + } + + if (var_ratio_ > 1.) { + //< compute number of points in the original regular grid + // THIS IS TO NORMALIZE + int n_idx_ = ((lamphi + epstest - range_outer[0]) / delta_inner) + 1; + // first point in the internal regular grid + int nstart_int = n_rim + n_stretched_ + 1; + // last point in the internal regular grid + int nend_int = n_points_ - (n_rim + n_stretched_); + + + double delta = delta_inner; + double delta_last = delta_inner; + double delta_add{}; + double delta_tot{}; + + new_ratio_ = new_ratio(n_stretched_, var_ratio_); + + //< stretched area + //< n_idx_ start from 1 + if (((n_idx_ < nstart_int) && (n_idx_ > n_rim)) || ((n_idx_ > nend_int) && (n_idx_ < n_points_ - n_rim + 1))) { + //< number of stretched points for the loop + int n_st{}; + if (n_idx_ < nstart_int) { + n_st = nstart_int - n_idx_; + } + else { + n_st = n_idx_ - nend_int; + } + + delta_tot = 0.; + for (int ix = 0; ix < n_st; ix += 1) { + delta_last = delta * new_ratio_; + delta_add = delta_last - delta_inner; + delta = delta_last; + delta_tot += delta_add; + } + + if (n_idx_ < nstart_int) { + lamphi -= delta_tot; + } + else { + lamphi += delta_tot; + } + } + + //< rim region + if (((n_idx_ < n_rim + 1)) || (n_idx_ > n_points_ - n_rim)) { + delta_tot = 0.; + //compute total stretched + for (int ix = 0; ix < n_stretched_; ix += 1) { + delta_last = delta * new_ratio_; + delta_add = delta_last - delta_inner; + delta = delta_last; + delta_tot += delta_add; + } + + + double drim_size = ((rim_width / 2.) / n_rim) - delta_inner; + int ndrim{}; + + if (n_idx_ < nstart_int) { + ndrim = nstart_int - n_stretched_ - n_idx_; + lamphi = lamphi - delta_tot - (ndrim * drim_size); + } + else { + ndrim = n_idx_ - (n_points_ - n_rim); + lamphi = lamphi + delta_tot + ndrim * drim_size; + } + } + } + + //< return the new point in the stretched grid + return normalised(lamphi); +}; + +}; // namespace + + +namespace atlas { +namespace test { + + +CASE("Understanding of the above data") { + const std::vector& v = lon_LAM_str; + std::vector delta(v.size() - 1); + for (size_t i = 0; i < delta.size(); ++i) { + delta[i] = v[i + 1] - v[i]; + } + Log::info() << "delta = " << delta << std::endl; + // Outputs: + // delta = [1,1,0.883845,0.798259,0.720961,0.651148,0.651148,0.651148,0.651148, + // 0.651148,0.651148,0.651148,0.651148,0.651148,0.651148,0.651148,0.651148, + // 0.651148,0.651148,0.651148,0.651148,0.651148,0.651148,0.651148,0.651148, + // 0.651148,0.651148,0.651148,0.720961,0.798259,0.883845,1,1] + + std::vector delta_half(delta.begin() + delta.size() / 2, delta.end()); + Log::info() << "delta_half = " << delta_half << std::endl; + // Outputs: + // delta_half = [0.651148,0.651148,0.651148,0.651148,0.651148,0.651148,0.651148, + // 0.651148,0.651148,0.651148,0.651148,0.651148,0.720961,0.798259, + // 0.883845,1,1] + std::vector progression(delta_half.size() - 1); + for (size_t i = 0; i < progression.size(); ++i) { + progression[i] = delta_half[i + 1] / delta_half[i]; + } + Log::info() << "progression = " << progression << std::endl; + // Outputs: + // progression = [1,1,1,1,1,1,1,1,1,1,1,1.10722,1.10722,1.10722,1.13142,1] + + int nx = 34; + int nx_var = 6; + int nx_outer = 4; + double var_ratio = 1.13; + + int inner_i_begin = -1; + int inner_i_end = -1; + for (int i = 0; i < nx; ++i) { + if (std::abs(v[i] - xrange_inner[0]) < 1.e-10) { + inner_i_begin = i; + } + if (std::abs(v[i] - xrange_inner[1]) < 1.e-10) { + inner_i_end = i + 1; + } + } + + double inner_size = ((nx - 1) - nx_var - nx_outer) * delta_inner; + ///< number of regular internal grid points, integer + int nx_inner = (inner_size + epstest) / delta_inner + 1; + + EXPECT_EQ(nx_inner, inner_i_end - inner_i_begin); + EXPECT_EQ(nx_inner, inner_i_end - inner_i_begin); + EXPECT_EQ(inner_i_begin, nx_outer / 2 + nx_var / 2); + EXPECT_EQ(inner_i_end, nx - nx_outer / 2 - nx_var / 2); + for (int i = 0; i < nx_outer / 2; ++i) { + EXPECT_APPROX_EQ(v[i + 1] - v[i], delta_outer, 1.e-10); + } + for (int i = nx_outer / 2; i < inner_i_begin; ++i) { + double d = v[i + 1] - v[i]; + EXPECT(not_equal(d, delta_inner)); + EXPECT(not_equal(d, delta_outer)); + } + for (int i = inner_i_begin; i < inner_i_end - 1; ++i) { + EXPECT_APPROX_EQ(v[i + 1] - v[i], delta_inner, 1.e-10); + } + for (int i = inner_i_end - 1; i < nx - nx_outer / 2 - 1; ++i) { + double d = v[i + 1] - v[i]; + EXPECT(not_equal(d, delta_inner)); + EXPECT(not_equal(d, delta_outer)); + } + for (int i = nx - nx_outer / 2 - 1; i < nx - 1; ++i) { + EXPECT_APPROX_EQ(v[i + 1] - v[i], delta_outer, 1.e-10); + } + + double var_ints_f = ((nx - 1) - nx_outer - (nx_inner - 1)) / 2.; + double logr = std::log(var_ratio); + double log_ratio = (var_ints_f - 0.5) * logr; + double new_ratio = std::exp(log_ratio / std::floor(var_ints_f)); + + EXPECT_APPROX_EQ(new_ratio, 1.10722, 1.e-5); +} + +//< create stretched grid from regular grid + +CASE("var_ratio_create = 1.13") { + ///< definition of grid + constexpr float epstest = std::numeric_limits::epsilon(); + int nx_reg = ((xrange_outer[1] - xrange_outer[0] + epstest) / delta_inner) + 1; + int ny_reg = ((yrange_outer[1] - yrange_outer[0] + epstest) / delta_inner) + 1; + + + auto grid_st = + RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx_reg}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny_reg}, make_var_ratio_projection(1.13)}; + + + ///< check over regular grid points stretched using new atlas object and check using look-up table + for (idx_t j = 0; j < grid_st.ny(); ++j) { + for (idx_t i = 0; i < grid_st.nx(); ++i) { + auto ll = grid_st.lonlat(i, j); + EXPECT_APPROX_EQ(ll.lon(), lon_LAM_str[i], 1.e-10); + EXPECT_APPROX_EQ(ll.lat(), lat_LAM_str[j], 1.e-10); + } + } + + + ///< check over regular grid points stretched using new atlas object and check using function above + for (idx_t j = 0; j < grid_st.ny(); ++j) { + for (idx_t i = 0; i < grid_st.nx(); ++i) { + auto ll_st_lon = create_stretched_grid(nx_reg, 3, 1.13, 2, lon_LAM_reg[i], true); + auto ll_st_lat = create_stretched_grid(ny_reg, 3, 1.13, 2, lat_LAM_reg[j], false); + EXPECT_APPROX_EQ(ll_st_lon, lon_LAM_str[i], 1.e-10); + EXPECT_APPROX_EQ(ll_st_lat, lat_LAM_str[j], 1.e-10); + } + } + + ///< check internal regular grid + idx_t ymid = grid_st.ny() / 2; + idx_t xmid = grid_st.nx() / 2; + auto expect_equal_dlon = [&](int i, double dlon) { + EXPECT_APPROX_EQ(grid_st.lonlat(i + 1, ymid).lon() - grid_st.lonlat(i, ymid).lon(), dlon, 1.e-10); + }; + auto expect_equal_dlat = [&](int j, double dlat) { + EXPECT_APPROX_EQ(grid_st.lonlat(xmid, j + 1).lat() - grid_st.lonlat(xmid, j).lat(), dlat, 1.e-10); + }; + expect_equal_dlon(0, delta_outer); + expect_equal_dlon(xmid, delta_inner); + expect_equal_dlat(0, delta_outer); + expect_equal_dlat(ymid, delta_inner); + + //< Check that the spacing in xy coordinates matches "delta_inner" + for (int i = 0; i < grid_st.nx() - 1; ++i) { + EXPECT_APPROX_EQ(grid_st.xy(i + 1, ymid).x() - grid_st.xy(i, ymid).x(), delta_inner, 1.e-10); + } + for (int j = 0; j < grid_st.ny() - 1; ++j) { + EXPECT_APPROX_EQ(grid_st.xy(xmid, j + 1).y() - grid_st.xy(xmid, j).y(), delta_inner, 1.e-10); + } +} + + +CASE("var_ratio = 1.13") { + ///< definition of grid + auto grid = RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny}, make_var_ratio_projection(1.13)}; + + + ///< check over regular grid points stretched using new atlas object and check using look-up table + for (idx_t j = 0; j < grid.ny(); ++j) { + for (idx_t i = 0; i < grid.nx(); ++i) { + auto ll = grid.lonlat(i, j); + EXPECT_APPROX_EQ(ll.lon(), lon_LAM_str[i], 1.e-10); + EXPECT_APPROX_EQ(ll.lat(), lat_LAM_str[j], 1.e-10); + } + } + + idx_t ymid = grid.ny() / 2; + idx_t xmid = grid.nx() / 2; + auto expect_equal_dlon = [&](int i, double dlon) { + EXPECT_APPROX_EQ(grid.lonlat(i + 1, ymid).lon() - grid.lonlat(i, ymid).lon(), dlon, 1.e-10); + }; + auto expect_equal_dlat = [&](int j, double dlat) { + EXPECT_APPROX_EQ(grid.lonlat(xmid, j + 1).lat() - grid.lonlat(xmid, j).lat(), dlat, 1.e-10); + }; + expect_equal_dlon(0, delta_outer); + expect_equal_dlon(xmid, delta_inner); + expect_equal_dlat(0, delta_outer); + expect_equal_dlat(ymid, delta_inner); + + //< Check that the spacing in xy coordinates matches "delta_inner" + for (int i = 0; i < grid.nx() - 1; ++i) { + EXPECT_APPROX_EQ(grid.xy(i + 1, ymid).x() - grid.xy(i, ymid).x(), delta_inner, 1.e-10); + } + for (int j = 0; j < grid.ny() - 1; ++j) { + EXPECT_APPROX_EQ(grid.xy(xmid, j + 1).y() - grid.xy(xmid, j).y(), delta_inner, 1.e-10); + } + + + ///< Set meshes + ///< define mesh to write in file + auto mesh = Mesh{grid}; + + /** + * Write mesh in gmsh object. + * output under: + * /atlas/src/tests/grid/ + */ + + output::Gmsh{"stretch_mesh_lonlat.msh", Config("coordinates", "lonlat")("info", true)}.write(mesh); + output::Gmsh{"stretch_mesh_xy.msh", Config("coordinates", "xy")("info", true)}.write(mesh); + + + /** Create additional regular grid with same delta_hi + * for additional .msh file with lon lat in approximately the same range + * as the stretched one. + */ + + int nx_new = 37; + int ny_new = 35; + + ///< 37 the exact range would have 36.52 points + double alpha = ((delta_inner * nx_new) - (xrange_outer[1] - xrange_outer[0])) / 2.; + double startx_n = xrange_outer[0] - alpha; + double endx_n = xrange_outer[1] + alpha; + double starty_n = yrange_outer[0] - alpha; + double endy_n = yrange_outer[1] + alpha; + + + ///< create regular grid + auto grid_reg_approx = + RegularGrid{LinearSpacing{startx_n, endx_n, nx_new}, LinearSpacing{starty_n, endy_n, ny_new}}; + + + /** + * Write mesh in gmsh object. + * output under: + * /atlas/src/tests/grid/ + */ + output::Gmsh("grid_reg_approx_lonlat.msh", Config("coordinates", "lonlat")("info", true)) + .write(Mesh{grid_reg_approx}); +} + +CASE("var_ratio = 1.0") { + ///< TEST of var_ratio = 1.0 configuration + ///< definition of stretched grid + auto grid = RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny}, make_var_ratio_projection(1.0)}; + + ///< Check if bounding box is correct + { + auto bb = grid.lonlatBoundingBox(); + EXPECT(RectangularLonLatDomain(bb)); + const double tolerance = 1.e-6; + EXPECT_APPROX_EQ(bb.west(), xrange_outer[0], tolerance); + EXPECT_APPROX_EQ(bb.east(), xrange_outer[1], tolerance); + EXPECT_APPROX_EQ(bb.south(), yrange_outer[0], tolerance); + EXPECT_APPROX_EQ(bb.north(), yrange_outer[1], tolerance); + for (PointLonLat p : grid.lonlat()) { + EXPECT(bb.contains(p)); + } + } + + ///< check over regular grid points stretched using new atlas object and check using look-up table + for (idx_t j = 0; j < grid.ny(); ++j) { + for (idx_t i = 0; i < grid.nx(); ++i) { + auto ll = grid.lonlat(i, j); + EXPECT_APPROX_EQ(ll.lon(), lon_LAM_reg[i], 1.e-10); + EXPECT_APPROX_EQ(ll.lat(), lat_LAM_reg[j], 1.e-10); + } + } + + auto mesh = Mesh{grid}; + output::Gmsh{"reg_mesh_lonlat.msh", Config("coordinates", "lonlat")("info", true)}.write(mesh); + output::Gmsh{"reg_mesh_xy.msh", Config("coordinates", "xy")("info", true)}.write(mesh); +} + + +CASE("var_ratio_rot = 1.13") { + ///< check over regular grid points stretched using new atlas object and check using look-up table + ///< in this case add a rotation + Config config; + std::vector north_pole = {-176., 40.}; + config.set("north_pole", north_pole); + + ///< definition of grid I have to rotate this + auto proj_st = make_var_ratio_projection_rot(1.13, north_pole); + auto grid = RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny}, proj_st}; + + Rotation rotation(config); + + for (idx_t j = 0; j < grid.ny(); ++j) { + for (idx_t i = 0; i < grid.nx(); ++i) { + ////atlas/src/tests/grid/ + */ + + output::Gmsh{"stretch_mesh_lonlat_rot.msh", Config("coordinates", "lonlat")("info", true)}.write(mesh); + output::Gmsh{"stretch_mesh_xy_rot.msh", Config("coordinates", "xy")("info", true)}.write(mesh); + + + /** Create additional regular grid with same delta_hi + * for additional .msh file with lon lat in approximately the same range + * as the stretched one. + */ + + int nx_new = 37; + int ny_new = 35; + + ///< 37 the exact range would have 36.52 points + double alpha = ((delta_inner * nx_new) - (xrange_outer[1] - xrange_outer[0])) / 2.; + double startx_n = xrange_outer[0] - alpha; + double endx_n = xrange_outer[1] + alpha; + double starty_n = yrange_outer[0] - alpha; + double endy_n = yrange_outer[1] + alpha; + + /// + ///< create regular grid + auto grid_reg_approx = + RegularGrid{LinearSpacing{startx_n, endx_n, nx_new}, LinearSpacing{starty_n, endy_n, ny_new}}; + + /** + * Write mesh in gmsh object. + * output under: + * /atlas/src/tests/grid/ + */ + output::Gmsh("grid_regrot_approx_lonlat.msh", Config("coordinates", "lonlat")("info", true)) + .write(Mesh{grid_reg_approx}); +} + +CASE("var_ratio_rot_inv = 1.13") { + ///< check over regular grid points stretched using new atlas object and check using look-up table + ///< in this case add a rotation + Config config; + ///< definition of grid I have to rotate this + auto proj_st_nr = make_var_ratio_projection(1.13); + auto grid_nr = RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny}, proj_st_nr}; + + ///< definition of grid I have to rotate this + auto proj_st = make_var_ratio_projection_rot(1.13, {-176., 40.}); + auto grid = RegularGrid{grid::LinearSpacing{xrange_outer[0], xrange_outer[1], nx}, + grid::LinearSpacing{yrange_outer[0], yrange_outer[1], ny}, proj_st}; + + + ///< Test the inverse + for (idx_t j = 0; j < grid.ny(); ++j) { + for (idx_t i = 0; i < grid.nx(); ++i) { + /// { CASE("Structured grid") { - auto grid = atlas::Grid("L48x37"); + auto grid = atlas::Grid("L24x19"); // Set mesh config. const auto sourceMeshConfig = util::Config("partitioner", "equal_regions"); @@ -464,7 +464,7 @@ CASE("Structured grid") { } CASE("Cubed sphere grid") { - auto grid = atlas::Grid("CS-LFR-C-16"); + auto grid = atlas::Grid("CS-LFR-C-8"); // Set mesh config. const auto sourceMeshConfig = util::Config("partitioner", "equal_regions") | util::Config("halo", "2"); @@ -496,6 +496,39 @@ CASE("Cubed sphere grid") { } } +CASE("Cubed sphere dual grid") { + auto grid = atlas::Grid("CS-LFR-C-8"); + + // Set mesh config. + const auto sourceMeshConfig = util::Config("partitioner", "equal_regions") | util::Config("halo", "0"); + const auto targetMeshConfig = util::Config("partitioner", "cubedsphere") | util::Config("halo", "0"); + + auto sourceMesh = MeshGenerator("cubedsphere_dual", sourceMeshConfig).generate(grid); + auto targetMesh = MeshGenerator("cubedsphere_dual", targetMeshConfig).generate(grid); + + SECTION("CubedSphereDualNodeColumns") { + const auto sourceFunctionSpace = functionspace::CubedSphereNodeColumns(sourceMesh); + const auto targetFunctionSpace = functionspace::CubedSphereNodeColumns(targetMesh); + + auto test = TestRedistributionPoints2(sourceFunctionSpace, targetFunctionSpace); + + test.execute(); + + test.outputFields("CubedSphereDual_NodeColumns"); + } + + SECTION("CubedSphereDualCellColumns") { + const auto sourceFunctionSpace = functionspace::CubedSphereCellColumns(sourceMesh); + const auto targetFunctionSpace = functionspace::CubedSphereCellColumns(targetMesh); + + auto test = TestRedistributionElems(sourceFunctionSpace, targetFunctionSpace); + + test.execute(); + + test.outputFields("CubedSphereDual_CellColumns"); + } +} + } // namespace test } // namespace atlas diff --git a/src/tests/util/CMakeLists.txt b/src/tests/util/CMakeLists.txt index 10014cfb2..6cf229e8e 100644 --- a/src/tests/util/CMakeLists.txt +++ b/src/tests/util/CMakeLists.txt @@ -73,3 +73,10 @@ ecbuild_add_test( TARGET atlas_test_kdtree LIBS atlas ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} ) + +ecbuild_add_test( TARGET atlas_test_convexsphericalpolygon + SOURCES test_convexsphericalpolygon.cc + LIBS atlas + ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT} +) + diff --git a/src/tests/util/test_convexsphericalpolygon.cc b/src/tests/util/test_convexsphericalpolygon.cc new file mode 100644 index 000000000..56bdf5663 --- /dev/null +++ b/src/tests/util/test_convexsphericalpolygon.cc @@ -0,0 +1,298 @@ +#include +#include +#include +#include + +#include "atlas/util/ConvexSphericalPolygon.h" +#include "atlas/util/Geometry.h" +#include "atlas/util/Point.h" + +#include "tests/AtlasTestEnvironment.h" + +namespace atlas { +namespace test { + +using ConvexSphericalPolygon = util::ConvexSphericalPolygon; + +util::ConvexSphericalPolygon make_polygon(const std::initializer_list& list) { + return util::ConvexSphericalPolygon{std::vector(list)}; +} + +template +util::ConvexSphericalPolygon make_polygon(Ts&&... ts) { + std::array arr{ts...}; + return util::ConvexSphericalPolygon{arr}; +} + + +util::ConvexSphericalPolygon make_polygon() { + return util::ConvexSphericalPolygon{}; +} + +CASE("test default constructor") { + ConvexSphericalPolygon p; + EXPECT(bool(p) == false); +} + +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; + EXPECT_APPROX_EQ(std::abs(plg1.area() - plg2.area() - plg3.area()), 0, 1e-15); +} + +CASE("test_spherical_polygon_intersection") { + constexpr int nplg_f = 2; + constexpr int nplg_g = 17; + constexpr int nplg_i = nplg_f * nplg_g; + 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 + }; + std::array plg_i = { + make_polygon(), //0 + make_polygon(), + make_polygon(), //2 + make_polygon({{0, 60}, {40, 60}, {40, 70}, {10, 70.8}}), + make_polygon({{0, 70}, {0, 60}, {40, 60}, {30, 70.8}}), //4 + make_polygon({{0, 60}, {40, 60}, {34.6, 70.5}, {5.3, 70.5}}), + make_polygon({{7.5, 60.9}, {32.5, 60.9}, {20, 70}}), //6 + make_polygon({{0, 70}, {0, 60}, {40, 60}, {40, 70}}), + make_polygon({{0, 65.5}, {6.9, 70.6}, {0, 70}}), //8 + make_polygon({{0, 70}, {0, 60}, {40, 60}, {40, 70}}), + make_polygon({{0, 65}, {10, 61.1}, {40, 60}, {20, 65}}), //10 + make_polygon({{0, 60}, {40, 60}, {20, 65}}), + make_polygon({{12.6, 61.3}, {27.4, 61.3}, {30, 63}, {20, 65}, {10, 63}}), //12 + make_polygon({{0, 70}, {1.9, 60.3}, {32.9, 60.9}, {40, 70}}), + make_polygon(), //14 + make_polygon({{13.7, 61.4}, {26.3, 61.4}, {30, 70.8}, {10, 70.8}}), + make_polygon({{0, 65}, {0, 60}, {16.8, 61.5}, {40, 65}, {40, 70}, {15, 71.1}}), //16 + make_polygon({{0, 60}, {0, 50}, {40, 60}}), + make_polygon({{0, 60}, {0, 50}, {20, 60}}), //18 + make_polygon({{10, 60}, {10, 50}, {30, 60}}), + make_polygon({{0, 60}, {40, 60}, {40, 80}}), //20 + make_polygon({{0, 80}, {0, 60}, {40, 60}}), + make_polygon({{0, 60}, {40, 60}, {20, 80}}), //22 + make_polygon({{0, 50}, {40, 50}, {20, 70}}), + make_polygon({{0, 90}, {0, 60}, {40, 60}}), //24 + make_polygon({{0, 65.5}, {40, 79.2}, {40, 80.8}, {0, 80.8}}), + make_polygon({{0, 80}, {0, 50}, {40, 50}, {40, 80}}), //26 + make_polygon({{0, 65}, {20, 55}, {40, 60}, {20, 65}}), + make_polygon({{0, 60}, {20, 55}, {40, 60}, {20, 65}}), //28 + make_polygon({{10, 63}, {20, 55}, {30, 63}, {20, 65}}), + make_polygon({{0, 70}, {5, 5}, {10, 0}, {20, 0}, {40, 70}, {20, 75}}), //30 + 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}})}; + 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; + } + } + } +} + +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 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}; + + 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 I = s1.intersect(s2); + EXPECT_APPROX_EQ(std::abs(PointXYZ::norm(I) - 1.), 0., EPS); + EXPECT_APPROX_EQ(PointXYZ::norm(I - Isol), 0., EPS); + + // test "contains" + EXPECT(s1.contains(Isol) && s2.contains(Isol)); + EXPECT(s1.contains(I) && s2.contains(I)); +} + +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; + const double dlon = 90. / m; + std::vector csp(n * m); + std::vector tgt_area(n * m); + ATLAS_TRACE_SCOPE("1") + 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 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; + + ATLAS_TRACE_SCOPE("3") + 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]; + } + } + + // normalize weights + double norm_fac = csp0.area() / accumulated_tarea; + ATLAS_TRACE_SCOPE("4") + 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]; + } + } + + 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("edge cases") { + Log::info().precision(20); + SECTION("H128->H256 problem polygon intersection") { + 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({{43.30188679, 50.48004426}, + {43.73831776, 50.0914568}, + {44.1509434, 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"; + } + + SECTION("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}}); + 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"; + } +} + +//----------------------------------------------------------------------------- + +} // end namespace test +} // end namespace atlas + +int main(int argc, char** argv) { + return atlas::test::run(argc, argv); +} diff --git a/src/tests/util/test_kdtree.cc b/src/tests/util/test_kdtree.cc index 37e28e50a..782a0f7b6 100644 --- a/src/tests/util/test_kdtree.cc +++ b/src/tests/util/test_kdtree.cc @@ -153,7 +153,7 @@ static const IndexKDTree& search() { } // namespace //------------------------------------------------------------------------------------------------ -static bool ECKIT_515_implemented = (ATLAS_ECKIT_VERSION_INT >= 11302); // version 1.13.2 +static bool ECKIT_515_implemented = ATLAS_ECKIT_VERSION_AT_LEAST(1, 13, 2); // --> implements eckit::KDTree::size() and eckit::KDTree::empty() CASE("test kdtree") {