Skip to content

Commit 71e618c

Browse files
committed
Merge remote-tracking branch 'JCSDA-internal/atlas/feature/bug_fix_interpolation_01' into feature/smv-interpolator-tests-update
2 parents 95d32a9 + 16409ba commit 71e618c

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

src/tests/interpolation/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ ecbuild_add_test( TARGET atlas_test_interpolation_spherical_vector
173173
ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT}
174174
)
175175

176+
ecbuild_add_test( TARGET atlas_test_interpolation_spherical_vector_to_point_6PE
177+
SOURCES test_interpolation_spherical_vector_to_point_6PE.cc
178+
LIBS atlas
179+
MPI 6
180+
CONDITION eckit_HAVE_MPI AND MPI_SLOTS GREATER_EQUAL 6
181+
ENVIRONMENT ${ATLAS_TEST_ENVIRONMENT}
182+
)
183+
176184
ecbuild_add_test( TARGET atlas_test_interpolation_binning
177185
SOURCES test_interpolation_binning.cc
178186
LIBS atlas
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* (C) Crown Copyright 2026 Met Office
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
*/
7+
8+
9+
#include <cmath>
10+
#include <map>
11+
#include <tuple>
12+
#include <utility>
13+
#include <vector>
14+
15+
#include "atlas/array.h"
16+
#include "atlas/array/helpers/ArrayForEach.h"
17+
#include "atlas/field/Field.h"
18+
#include "atlas/field/FieldSet.h"
19+
#include "atlas/functionspace/NodeColumns.h"
20+
#include "atlas/functionspace/PointCloud.h"
21+
#include "atlas/grid/Grid.h"
22+
#include "atlas/interpolation.h"
23+
#include "atlas/mesh/Mesh.h"
24+
#include "atlas/meshgenerator.h"
25+
#include "atlas/option.h"
26+
#include "atlas/util/Config.h"
27+
#include "atlas/util/Constants.h"
28+
#include "atlas/util/function/VortexRollup.h"
29+
#include "atlas/parallel/mpi/mpi.h"
30+
31+
#include "tests/AtlasTestEnvironment.h"
32+
33+
34+
35+
namespace atlas {
36+
namespace test {
37+
38+
39+
struct FieldSpecsFixture {
40+
static const util::Config& get(const std::string& fixture) {
41+
static const auto fieldSpecs = std::map<std::string_view, util::Config>{
42+
{"source", option::name("sfield") |
43+
option::variables(2) |
44+
option::type("vector")},
45+
{"target", option::name("tfield") |
46+
option::variables(2) |
47+
option::type("vector")}};
48+
return fieldSpecs.at(fixture);
49+
}
50+
};
51+
52+
// function to define the 'source' function space
53+
auto generateFunctionspaceSource() {
54+
const auto csgrid = Grid("CS-LFR-48");
55+
const auto csmesh = MeshGenerator("cubedsphere_dual").generate(csgrid);
56+
57+
return functionspace::NodeColumns(csmesh);
58+
}
59+
60+
// function to define the 'target' function space
61+
auto generateFunctionspaceTarget() {
62+
const mpi::Comm& comm_w = mpi::comm();
63+
std::size_t mpi_rank = comm_w.rank();
64+
65+
// data structure storing a single point
66+
// (in a cubed sphere with 6 partitions, the point near
67+
// the North pole is part of partition #4)
68+
std::vector<PointXY> spoint;
69+
if (mpi_rank == 4) {
70+
spoint.push_back({360., 90.});
71+
}
72+
73+
return functionspace::PointCloud(spoint);
74+
}
75+
76+
std::pair<double, double> vortexHorizontal(double lon, double lat) {
77+
// set hLon and hLat step size.
78+
const double hLon = 0.0001;
79+
const double hLat = 0.0001;
80+
81+
const double u = (
82+
util::function::vortex_rollup(lon, lat + 0.5 * hLat, 0.1) -
83+
util::function::vortex_rollup(lon, lat - 0.5 * hLat, 0.1)) /
84+
hLat;
85+
86+
const double v = -(
87+
util::function::vortex_rollup(lon + 0.5 * hLon, lat, 0.1) -
88+
util::function::vortex_rollup(lon - 0.5 * hLon, lat, 0.1)) /
89+
(hLon * std::cos(lat * util::Constants::degreesToRadians()));
90+
91+
return std::make_pair(u, v);
92+
}
93+
94+
// function to generate a data structure containing the 'source' field
95+
FieldSet generateFieldsSource(FunctionSpace& fsSource) {
96+
const auto fieldSourceSpecs = FieldSpecsFixture::get("source");
97+
FieldSet fieldsSource;
98+
auto fieldSource =
99+
fieldsSource.add(fsSource.createField<double>(fieldSourceSpecs));
100+
auto fieldSourceView = array::make_view<double, 2>(fieldSource);
101+
102+
const auto lonLatSource =
103+
array::make_view<double, 2>(fsSource.lonlat());
104+
105+
array::helpers::ArrayForEach<0>::apply(
106+
std::tie(lonLatSource, fieldSourceView),
107+
[](auto&& lonLat, auto&& columnSource) {
108+
const auto setElems = [&](auto&& elemSource) {
109+
std::tie(elemSource(0), elemSource(1)) =
110+
vortexHorizontal(lonLat(0), lonLat(1));
111+
};
112+
setElems(columnSource);
113+
});
114+
115+
return fieldsSource;
116+
}
117+
118+
// function to generate a data structure containing the 'target' field
119+
FieldSet generateFieldsTarget(FunctionSpace& fs) {
120+
const auto fieldTargetSpecs = FieldSpecsFixture::get("target");
121+
FieldSet fieldsTarget;
122+
fieldsTarget.add(fs.createField<double>(fieldTargetSpecs));
123+
124+
return fieldsTarget;
125+
}
126+
127+
double dotProduct(const array::ArrayView<double, 2>& a,
128+
const array::ArrayView<double, 2>& b) {
129+
auto dotProd = 0.;
130+
array::helpers::arrayForEachDim(
131+
std::make_integer_sequence<int, 2>{}, std::tie(a, b),
132+
[&](const double& aElem, const double& bElem) {
133+
dotProd += aElem * bElem;
134+
});
135+
136+
return dotProd;
137+
}
138+
139+
CASE("source grid: cubed sphere (CS-LFR-48); "
140+
"target grid: point cloud (single point); "
141+
"field distribution: 3D-field, single level, 2D-vector; "
142+
"procedure: vector interpolation; "
143+
"parallelization: MPI (6 PEs)") {
144+
145+
auto fsSource = generateFunctionspaceSource();
146+
auto fsTarget = generateFunctionspaceTarget();
147+
148+
auto fieldsSource = generateFieldsSource(fsSource);
149+
auto fieldSourceView = array::make_view<double, 2>(fieldsSource["sfield"]);
150+
auto fieldsTarget = generateFieldsTarget(fsTarget);
151+
auto fieldTargetView = array::make_view<double, 2>(fieldsTarget["tfield"]);
152+
153+
const auto ansScheme = option::type("cubedsphere-bilinear") |
154+
util::Config("adjoint", true);
155+
const auto scheme = util::Config("type", "spherical-vector") |
156+
util::Config("adjoint", true) |
157+
util::Config("scheme", ansScheme);
158+
159+
Interpolation interp(scheme, fsSource, fsTarget);
160+
161+
interp.execute(fieldsSource, fieldsTarget);
162+
fieldsTarget.haloExchange();
163+
164+
//--
165+
166+
// performing a dot-product test ...
167+
168+
const auto fieldTargetSpecs = FieldSpecsFixture::get("target");
169+
auto adjointTarget = fsTarget.createField<double>(fieldTargetSpecs);
170+
adjointTarget.array().copy(fieldsTarget["tfield"]);
171+
adjointTarget.adjointHaloExchange();
172+
173+
const auto fieldSourceSpecs = FieldSpecsFixture::get("source");
174+
auto adjointSource = fsSource.createField<double>(fieldSourceSpecs);
175+
auto adjointSourceView = array::make_view<double, 2>(adjointSource);
176+
adjointSourceView.assign(0.);
177+
178+
interp.execute_adjoint(adjointSource, adjointTarget);
179+
180+
constexpr auto tinyNum = 1e-13;
181+
const auto targetDotTarget = dotProduct(fieldTargetView, fieldTargetView);
182+
const auto sourceDotAdjointSource = dotProduct(fieldSourceView, adjointSourceView);
183+
184+
const mpi::Comm& comm_w = mpi::comm();
185+
std::size_t mpi_rank = comm_w.rank();
186+
187+
if (mpi_rank == 4) {
188+
const auto dotProdRatio = targetDotTarget / sourceDotAdjointSource;
189+
EXPECT_APPROX_EQ(dotProdRatio, 1., tinyNum);
190+
}
191+
}
192+
193+
194+
} // namespace test
195+
} // namespace atlas
196+
197+
198+
int main(int argc, char** argv) {
199+
return atlas::test::run(argc, argv);
200+
}
201+

0 commit comments

Comments
 (0)