Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement a procedure to carry out a regridding from high to low resolution (binning) #191

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
236ed1f
implemented a procedure to evaluate the areas of the grid cell (grid …
mo-lormi May 3, 2024
83beabe
implemented a procedure to carry out a regridding from high to low re…
mo-lormi May 3, 2024
08a2f06
added test for the regridding procedure (high to low resolution)
mo-lormi May 3, 2024
7f3705a
updated build procedure
mo-lormi May 3, 2024
87312a5
update 03.05.24-01
mo-lormi May 3, 2024
3f1fdfd
updated build procedure
mo-lormi May 9, 2024
475e9f0
added check to identify and ignore an empty row
mo-lormi May 9, 2024
dbf3bea
updated setup procedure for the method 'binning'
mo-lormi May 9, 2024
41c8e4c
update 10.05.24-01
mo-lormi May 10, 2024
15f92a4
added flag to control the adjoint operation
mo-lormi May 10, 2024
0f97c05
added tests to battery of tests
mo-lormi May 10, 2024
a05b292
update 10.05.24-02
mo-lormi May 10, 2024
2494b51
merged branch develop into feature/interpolation_binning_01
mo-lormi Jun 7, 2024
2d63679
added halo exchange operation before generating Gmsh data
mo-lormi Jun 7, 2024
25e9b59
removed variable grid_type_
mo-lormi Jun 7, 2024
1631e61
removed unnecessary namespaces
mo-lormi Jun 7, 2024
c9cbaeb
update 07.06.24-01
mo-lormi Jun 7, 2024
4b8e336
update 07.06.24-02
mo-lormi Jun 7, 2024
fb36305
update 07.06.24-03
mo-lormi Jun 7, 2024
c0b8577
update 07.06.24-04
mo-lormi Jun 7, 2024
d766d88
Consistency with SphericalVector: rename 'ancillary_scheme' to 'scheme'
wdeconinck Jun 10, 2024
b09c2a5
Fix intent of halo_exchange and adjoint in Binning method
wdeconinck Jun 10, 2024
d31023e
Remove SphericalVector::adjoint_ and use base class
wdeconinck Jun 10, 2024
9f8aab0
No casting to CubedSphereProjection necessary
wdeconinck Jun 11, 2024
175d12f
Reduce dimensionality of area field
wdeconinck Jun 11, 2024
2df6ac7
redefined the procedure to evaluate the areas of the grid cells
mo-lormi Jun 13, 2024
1e45e6f
updated build procedure
mo-lormi Jun 13, 2024
7aa4ec8
update 14.06.24-01
mo-lormi Jun 14, 2024
9390bdc
Merge branch 'develop' into feature/interpolation_binning_01
wdeconinck Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/atlas/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ interpolation/method/PointIndex3.cc
interpolation/method/PointIndex3.h
interpolation/method/PointIndex2.cc
interpolation/method/PointIndex2.h
interpolation/method/binning/Binning.cc
interpolation/method/binning/Binning.h
interpolation/method/cubedsphere/CubedSphereBilinear.cc
interpolation/method/cubedsphere/CubedSphereBilinear.h
interpolation/method/knn/GridBox.cc
Expand Down
5 changes: 5 additions & 0 deletions src/atlas/grid/CubedSphereGrid.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include <string>

#include "atlas/field.h"
#include "atlas/functionspace/FunctionSpace.h"
#include "atlas/grid/Grid.h"
#include "atlas/grid/Tiles.h"
#include "atlas/grid/detail/grid/CubedSphere.h"
Expand Down Expand Up @@ -297,6 +299,9 @@ class CubedSphereGrid : public Grid {
// Return the size of the cubed sphere grid, where N is the number of grid boxes along the edge of a tile
inline int N() const { return grid_->N(); }

// Return the areas of the cells
inline Field gridCellArea(const FunctionSpace& fspace) const { return grid_->gridCellArea(fspace); };

/// @brief return tiles object.
inline CubedSphereTiles tiles() const { return grid_->tiles(); }

Expand Down
39 changes: 39 additions & 0 deletions src/atlas/grid/detail/grid/CubedSphere.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "CubedSphere.h"

#include <math.h>

#include <algorithm>
#include <iomanip>
#include <limits>
Expand All @@ -16,7 +18,9 @@
#include "eckit/utils/Hash.h"
#include "eckit/utils/Translator.h"

#include "atlas/array.h"
#include "atlas/domain/Domain.h"
#include "atlas/functionspace/NodeColumns.h"
#include "atlas/grid/CubedSphereGrid.h"
#include "atlas/grid/Tiles.h"
#include "atlas/grid/detail/grid/GridBuilder.h"
Expand Down Expand Up @@ -303,6 +307,41 @@ void CubedSphere::xyt2xy(const double xyt[], double xy[]) const {
xy[YY] = normalisedY * 90. + tiles_offsets_ab2xy_[LAT][t];
}

// Provide the areas of the cells
Field CubedSphere::gridCellArea(const FunctionSpace& fspace) const {

constexpr auto degrees2rads = M_PI / 180.;
const auto ncfs = functionspace::NodeColumns(fspace);

const auto csgrid = CubedSphereGrid(ncfs.mesh().grid());

auto lonlat = array::make_view<double, 2>(fspace.lonlat());

// (grid_res * grid_res) = no. of cells on a tile
auto grid_res = csgrid.N();

const auto& proj = csgrid.cubedSphereProjection();

// area of a grid cell (cubed-sphere coord. system)
double gcell_area_cs = M_PI/(2*grid_res) * M_PI/(2*grid_res);

auto gcell_area_field = ncfs.createField<double>(
atlas::option::name("grid_cell_areas") | atlas::option::levels(1));

auto gcell_area_fview = array::make_view<double, 2>(gcell_area_field);

for (size_t i = 0; i < gcell_area_fview.size(); i++) {
PointLonLat loc = PointLonLat(lonlat(i, atlas::LON), lonlat(i, atlas::LAT));
double cos_lat = std::cos(degrees2rads * loc.lat());
double grid_jac_det = 1/proj.jacobian(loc).determinant();
// area of a grid cell (geographic coord. system)
gcell_area_fview(i, 0) = grid_jac_det * gcell_area_cs * cos_lat;
}

return gcell_area_field;
}


// ------------------------------------------

namespace {
Expand Down
9 changes: 7 additions & 2 deletions src/atlas/grid/detail/grid/CubedSphere.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include "eckit/types/Types.h"

#include "atlas/field.h"
#include "atlas/functionspace/FunctionSpace.h"
#include "atlas/grid/Spacing.h"
#include "atlas/grid/Tiles.h"
#include "atlas/grid/detail/grid/Grid.h"
Expand Down Expand Up @@ -241,9 +243,12 @@ class CubedSphere : public Grid {
virtual std::string name() const override;
virtual std::string type() const override;

// Return number of faces on cube
// Return N_, where (N_ * N_) is the number of cells on a tile
inline idx_t N() const { return N_; }

// Return the areas of the faces/cells
Field gridCellArea(const FunctionSpace&) const;
Copy link
Member

@wdeconinck wdeconinck Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to encapsulate as much as possible the grid from objects like Field, FunctionSpace, Mesh,
I would avoid this function.

Possibly you could create a different function

double CubedSphere::gridCellArea(idx_t index);

The grid itself should have all possible information needed to compute just a single cell value.

Then where needed fill a container such as std::vector or atlas::Field or ...
But in this case here, I think you could just compute the cellArea within the loop where you compute the weights, avoiding the entire allocation of a container.

Copy link
Contributor Author

@mo-lormi mo-lormi Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wdeconinck ... good point. However, there is reason why the method CubedSphere::gridCellArea() has been designed and implemented in this way.

So far, at the MO, we have identified at least two workflow steps where this function is needed: one is the evaluation of the area weights matrix within the interpolation/binning procedure (this PR); the other one is the evaluation of the Jc term, a penalty term associated with the gravity wave constraints for the cost function.
Thus, we thought that having a function signature for CubedSphere::gridCellArea() based on the FunctionSpace data type could make it general enough to be used in different contexts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I managed to boil this down to this standalone function, independent of functionspace

// Area around nodes of cubed sphere mesh
Field CubedSphereNodalArea(const Mesh& mesh) {

  constexpr auto deg2rad = M_PI / 180.;
  const auto& proj = mesh.projection();
  auto lonlat = array::make_view<double, 2>(mesh.nodes().lonlat());
  auto gcell_area_field = Field("grid_cell_areas", make_datatype<double>(), array::make_shape(mesh.nodes().size()));  
  auto gcell_area_fview = array::make_view<double, 1>(gcell_area_field);

  double gcell_area_cs = [&] {
      /******* CUBED_SPHERE SPECIFIC *********/
      // (grid_res * grid_res) = no. of cells on a tile
      ATLAS_ASSERT(CubedSphereGrid(mesh.grid()));
      auto grid_res = CubedSphereGrid(mesh.grid()).N();
      // area of a grid cell (cubed-sphere coord. system)
      return M_PI/(2*grid_res) * M_PI/(2*grid_res);
   }();

  for (size_t i = 0; i < gcell_area_fview.size(); i++) {
    PointLonLat loc = PointLonLat(lonlat(i, atlas::LON), lonlat(i, atlas::LAT));
    double cos_lat = std::cos(deg2rad * loc.lat());
    double grid_jac_det = 1/proj.jacobian(loc).determinant();
    // area of a grid cell (geographic coord. system)
    gcell_area_fview(i) = grid_jac_det * gcell_area_cs * cos_lat ;   /****** USE gcell_area_cs here ******/
  }

  return gcell_area_field;
}

Possibly with more thought this could be made even more independent of CubedSphere.

What happens to the Binning procedure if the combination does not happen to be "grid == CubedSphere && functionspace == NodeColumns" ?

Copy link
Contributor Author

@mo-lormi mo-lormi Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wdeconinck ... thanks for this.
Where do you think this function should be stored? In which file, class,...?
If you tell me where I should define the function, I will do a test with it. Thanks ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be a mesh-action, similar to https://github.com/ecmwf/atlas/blob/develop/src/atlas/mesh/actions/BuildCellCentres.h
So putting it parallel to this file, and following its design is a good idea.
You can then "register" this field within the mesh.nodes() and could be reused later.

What happens to the Binning procedure if the combination does not happen to be "grid == CubedSphere && functionspace == NodeColumns" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

functionspace == NodeColumns

The binning procedure works also for "functionspace == StructuredColumns" (grid type: "O").
I implemented a test to check this scenario, see here.


// Access to the tile class
inline atlas::grid::CubedSphereTiles tiles() const { return tiles_; }

Expand Down Expand Up @@ -436,7 +441,7 @@ class CubedSphere : public Grid {
void xyt2xy(const double xyt[], double xy[]) const;

protected:
// Number of faces on tile
// (N_ * N_) = number of cells on a tile
idx_t N_;

// Number of tiles
Expand Down
2 changes: 1 addition & 1 deletion src/atlas/interpolation/method/Method.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ class Method : public util::Object {
interpolation::MatrixCache matrix_cache_;
NonLinear nonLinear_;
std::string linalg_backend_;
bool adjoint_{false};
Matrix matrix_transpose_;

protected:
bool adjoint_{false};
bool allow_halo_exchange_{true};
std::vector<idx_t> missing_;
};
Expand Down
2 changes: 2 additions & 0 deletions src/atlas/interpolation/method/MethodFactory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "MethodFactory.h"

// for static linking
#include "binning/Binning.h"
#include "cubedsphere/CubedSphereBilinear.h"
#include "knn/GridBoxAverage.h"
#include "knn/GridBoxMaximum.h"
Expand Down Expand Up @@ -49,6 +50,7 @@ void force_link() {
MethodBuilder<method::GridBoxMaximum>();
MethodBuilder<method::CubedSphereBilinear>();
MethodBuilder<method::SphericalVector>();
MethodBuilder<method::Binning>();
}
} link;
}
Expand Down
184 changes: 184 additions & 0 deletions src/atlas/interpolation/method/binning/Binning.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* (C) Crown Copyright 2024 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/functionspace/NodeColumns.h"
#include "atlas/functionspace/StructuredColumns.h"
#include "atlas/grid.h"
#include "atlas/interpolation/Interpolation.h"
#include "atlas/interpolation/method/binning/Binning.h"
#include "atlas/interpolation/method/MethodFactory.h"
#include "atlas/runtime/Trace.h"

#include "eckit/config/LocalConfiguration.h"
#include "eckit/linalg/SparseMatrix.h"
#include "eckit/linalg/Triplet.h"
#include "eckit/mpi/Comm.h"


namespace atlas {
namespace interpolation {
namespace method {


namespace {

MethodBuilder<Binning> __builder("binning");

}


Binning::Binning(const Config& config) : Method(config) {
const auto* conf = dynamic_cast<const eckit::LocalConfiguration*>(&config);
ATLAS_ASSERT(conf, "config must be derived from eckit::LocalConfiguration");
interpAncillaryScheme_ = conf->getSubConfiguration("scheme");
// enabling or disabling the adjoint operation
adjoint_ = conf->getBool("adjoint", false);
// enabling or disabling the halo exchange
allow_halo_exchange_ = conf->getBool("halo_exchange", true);
}


void Binning::do_setup(const Grid& source,
const Grid& target,
const Cache&) {
ATLAS_NOTIMPLEMENTED;
}


void Binning::do_setup(const FunctionSpace& source,
const FunctionSpace& target) {
ATLAS_TRACE("atlas::interpolation::method::Binning::do_setup()");

using Index = eckit::linalg::Index;
using Triplet = eckit::linalg::Triplet;
using SMatrix = eckit::linalg::SparseMatrix;

source_ = source;
target_ = target;

if (target_.size() == 0) {
return;
}

// note that the 'source' grid for the low-to-high regridding (interpolation)
// is the 'target' grid for high-to-low regridding (binning) and
// the 'target' grid for the low-to-high regridding (interpolation) is the
// 'source' grid for the for high-to-low regridding (binning)
const auto& fs_source_interp = target_;
const auto& fs_target_interp = source_;

const auto interp = Interpolation(
interpAncillaryScheme_, fs_source_interp, fs_target_interp);
auto smx_interp_cache = interpolation::MatrixCache(interp);

auto smx_interp = smx_interp_cache.matrix();

auto smx_interp_tr = smx_interp.transpose();

const auto rows_tamx = smx_interp_tr.rows();
const auto cols_tamx = smx_interp_tr.cols();

const double* ptr_tamx_data = smx_interp_tr.data();
const Index* ptr_tamx_idxs_col = smx_interp_tr.inner();
const Index* ptr_tamx_o = smx_interp_tr.outer();

// diagonal of 'area weights matrix', W
auto ds_aweights = getAreaWeights(source_);

auto smx_binning_els = std::vector<Triplet>{};
size_t idx_row_next = 0;

for (size_t idx_row = 0; idx_row < rows_tamx; ++idx_row) {
idx_row_next = (idx_row+1);
// start of the indexes associated with the row 'i'
size_t lbound = ptr_tamx_o[idx_row];
// start of the indexes associated with the row 'i+1'
size_t ubound = ptr_tamx_o[idx_row_next];

if (lbound == ubound)
continue;

double sum_row = 0;
for (size_t i = lbound; i < ubound; ++i) {
sum_row += (ptr_tamx_data[i] * ds_aweights.at(ptr_tamx_idxs_col[i]));
}

// normalization factor
double nfactor = 1/sum_row;

for (size_t i = lbound; i < ubound; ++i) {
// evaluating the non-zero elements of the binning matrix
smx_binning_els.emplace_back(
idx_row, ptr_tamx_idxs_col[i],
(nfactor * (ptr_tamx_data[i] * ds_aweights.at(ptr_tamx_idxs_col[i]))));
}
}

// 'binning matrix' (sparse matrix), B = N A^T W
SMatrix smx_binning{rows_tamx, cols_tamx, smx_binning_els};
setMatrix(smx_binning);
}


void Binning::print(std::ostream&) const {
ATLAS_NOTIMPLEMENTED;
}


std::vector<double> Binning::getAreaWeights(const FunctionSpace& fspace) const {
// diagonal of 'area weights matrix', W
std::vector<double> ds_aweights;

bool is_cubed_sphere {false};
if (auto csfs = functionspace::NodeColumns(fspace)) {
if (CubedSphereGrid(csfs.mesh().grid())) {
is_cubed_sphere = true;
}
}

if (is_cubed_sphere) {

const auto csfs = functionspace::NodeColumns(fspace);

const auto csgrid = CubedSphereGrid(csfs.mesh().grid());
// areas of the cells (geographic coord. system)
const auto gcell_areas = csgrid.gridCellArea(fspace);

auto gcell_areas_view = array::make_view<double, 2>(gcell_areas);
auto is_ghost = array::make_view<int, 1>(csfs.ghost());

double total_area {0.};
for (idx_t i = 0; i < csfs.size(); i++) {
if (!is_ghost[i]) {
total_area += gcell_areas_view(i, 0);
}
}
eckit::mpi::comm().allReduceInPlace(total_area, eckit::mpi::Operation::SUM);

double aweight_temp {0.};
for (idx_t i = 0; i < csfs.size(); i++) {
if (!is_ghost[i]) {
aweight_temp = gcell_areas_view(i, 0)/total_area;
ds_aweights.emplace_back(aweight_temp);
}
}

} else {

// area weights (default)
ds_aweights.assign(fspace.size(), 1.);

}

return ds_aweights;
}


} // namespace method
} // namespace interpolation
} // namespace atlas
Loading
Loading