Skip to content

Commit e90687f

Browse files
committed
DE-2319: Add NWP emulator Atlas tool and tests (MPI, ci update) using binary data uploaded to Nexus
1 parent b9ff02a commit e90687f

14 files changed

+635
-16
lines changed

.github/ci-hpc-config.yml

+41-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
1-
build:
2-
modules:
3-
- ninja
4-
dependencies:
5-
- ecmwf/ecbuild@develop
6-
- ecmwf/eckit@develop
7-
- ecmwf/fckit@develop
8-
- ecmwf/atlas@develop
9-
- ecmwf/eccodes@develop
10-
dependency_cmake_options:
11-
- ecmwf/atlas:-DENABLE_FORTRAN=ON
12-
parallel: 64
1+
matrix:
2+
- mpi_on
3+
- mpi_off
4+
5+
mpi_on:
6+
build:
7+
modules:
8+
- ninja
9+
- openmpi
10+
modules_package:
11+
- atlas:openmpi
12+
- eckit:openmpi
13+
dependencies:
14+
- ecmwf/ecbuild@develop
15+
- ecmwf/eckit@develop
16+
- ecmwf/fckit@develop
17+
- ecmwf/atlas@develop
18+
- ecmwf/eccodes@develop
19+
dependency_cmake_options:
20+
- ecmwf/atlas:-DENABLE_FORTRAN=ON
21+
parallel: 64
22+
ntasks: 16
23+
env:
24+
- CTEST_PARALLEL_LEVEL=1
25+
- OMPI_MCA_rmaps_base_oversubscribe=1
26+
- ECCODES_SAMPLES_PATH=$ECCODES_DIR/share/eccodes/samples
27+
- ECCODES_DEFINITION_PATH=$ECCODES_DIR/share/eccodes/definitions
28+
29+
mpi_off:
30+
build:
31+
modules:
32+
- ninja
33+
dependencies:
34+
- ecmwf/ecbuild@develop
35+
- ecmwf/eckit@develop
36+
- ecmwf/fckit@develop
37+
- ecmwf/atlas@develop
38+
- ecmwf/eccodes@develop
39+
dependency_cmake_options:
40+
- ecmwf/atlas:-DENABLE_FORTRAN=ON
41+
parallel: 64

src/nwp_emulator/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,10 @@ ecbuild_add_library(
3434
eccodes
3535
eckit
3636
)
37+
38+
ecbuild_add_executable( TARGET nwp_emulator_run.x
39+
SOURCES
40+
nwp_emulator.cc
41+
LIBS
42+
plume_nwp_emulator
43+
)

src/nwp_emulator/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ emulator:
6565
step:
6666
area: [71.5, -25, 34.5, 45] # rectangle represented by NW and SE (lat,lon) coordinates
6767
value: 10.0
68+
variation: 1.0
6869
translation: [1.0, 1.0] # degrees of translation of the area per time step (lat, lon)
6970
"2":
7071
sinc:

src/nwp_emulator/nwp_data_provider.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ NWPDataProvider::NWPDataProvider(const DataSourceType& sourceType, const eckit::
3030
eckit::mpi::comm().abort(1);
3131
}
3232

33-
// Some logging for sanity, TODO: use atlas log once the emulator tool is setup
34-
std::cout << "Process " << rank << " has grid name " << dataReader->getGridName() << std::endl;
35-
std::cout << "Process " << rank << " has params " << dataReader->getParams() << std::endl;
33+
// Some logging for sanity
34+
eckit::Log::info() << "Process " << rank << " has grid name " << dataReader->getGridName() << std::endl;
35+
eckit::Log::info() << "Process " << rank << " has params " << dataReader->getParams() << std::endl;
3636

3737
gridName_ = dataReader->getGridName();
3838
// Parse parameters into a dictionary structure

src/nwp_emulator/nwp_emulator.cc

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* (C) Copyright 2025- ECMWF.
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+
* In applying this licence, ECMWF does not waive the privileges and immunities
8+
* granted to it by virtue of its status as an intergovernmental organisation nor
9+
* does it submit to any jurisdiction.
10+
*/
11+
#include <iostream>
12+
#include <stdlib.h>
13+
14+
#include "eckit/config/YAMLConfiguration.h"
15+
#include "eckit/filesystem/PathName.h"
16+
#include "eckit/mpi/Comm.h"
17+
18+
#include "atlas/library.h"
19+
#include "atlas/option/Options.h"
20+
#include "atlas/parallel/mpi/mpi.h"
21+
#include "atlas/runtime/AtlasTool.h"
22+
23+
#include "plume/Manager.h"
24+
#include "plume/data/ModelData.h"
25+
#include "plume/data/ParameterCatalogue.h"
26+
27+
#include "nwp_data_provider.h"
28+
#include "nwp_definitions.h"
29+
30+
using namespace nwp_emulator;
31+
32+
/**
33+
* @class NWPEmulator
34+
* @brief Emulates a model run and makes data available at each time step to facilitate Plume and plugins testing.
35+
*/
36+
class NWPEmulator final : public atlas::AtlasTool {
37+
38+
int execute(const Args& args) override;
39+
std::string briefDescription() override { return "NWP model emulator to facilitate Plume & plugins development"; }
40+
std::string usage() override {
41+
return name() +
42+
" [--grib-src=<path> | --config-src=<path>] [--plume-cfg=<path>] [OPTION]... [--help]\n"
43+
" --plume-cfg is optional, pass it to run Plume, else the emulator will do a dry run\n";
44+
}
45+
46+
int numberOfPositionalArguments() override { return -1; }
47+
int minimumPositionalArguments() override { return 0; }
48+
49+
public:
50+
NWPEmulator(int argc, char** argv) : dataSourceType_(DataSourceType::INVALID), atlas::AtlasTool(argc, argv) {
51+
add_option(new SimpleOption<std::string>("grib-src", "Path to GRIB files source"));
52+
add_option(new SimpleOption<std::string>("config-src", "Path to emulator config file"));
53+
add_option(new SimpleOption<std::string>("plume-cfg", "Path to Plume configuration"));
54+
}
55+
56+
private:
57+
std::string dataSourcePath_;
58+
DataSourceType dataSourceType_;
59+
60+
/// Plume members if needed
61+
std::string plumeConfigPath_;
62+
plume::data::ModelData plumeData_;
63+
64+
/**
65+
* @brief Sets up Plume framework and load plugins compatible with model params.
66+
*
67+
* @param dataProvider The object that provides the data for the Atlas fields offered by the emulated model.
68+
*
69+
* @return true if Plume configuration, negotiation and feeding are successful, false otherwise.
70+
*/
71+
bool setupPlume(NWPDataProvider& dataProvider);
72+
73+
/**
74+
* @brief Update necessary parameters offered to Plume other than Atlas fields, and run the plugins for the step.
75+
*
76+
* @param step The internal model step number.
77+
*/
78+
void runPlume(int step);
79+
};
80+
81+
int NWPEmulator::execute(const Args& args) {
82+
// Emulator configuration
83+
std::string gribSrcArg;
84+
if (args.get("grib-src", gribSrcArg)) {
85+
dataSourcePath_ = gribSrcArg;
86+
dataSourceType_ = DataSourceType::GRIB;
87+
}
88+
std::string configSrcPath;
89+
if (args.get("config-src", configSrcPath)) {
90+
if (dataSourceType_ != DataSourceType::INVALID) {
91+
eckit::Log::error() << "Usage : " << usage() << std::endl;
92+
return 1;
93+
}
94+
dataSourcePath_ = configSrcPath;
95+
dataSourceType_ = DataSourceType::CONFIG;
96+
}
97+
if (dataSourcePath_.empty()) {
98+
eckit::Log::error() << "Usage : " << usage() << std::endl;
99+
return 1;
100+
}
101+
args.get("plume-cfg", plumeConfigPath_);
102+
103+
size_t root = 0;
104+
size_t nprocs = eckit::mpi::comm().size();
105+
size_t rank = eckit::mpi::comm().rank();
106+
107+
NWPDataProvider dataProvider(dataSourceType_, eckit::PathName{dataSourcePath_}, rank, root, nprocs);
108+
109+
// Plume loading if a Plume configuration file has been passed, emulator dry run otherwise
110+
if (!plumeConfigPath_.empty()) {
111+
eckit::Log::info() << "The emulator will run Plume with configuration '" << plumeConfigPath_ << "'"
112+
<< std::endl;
113+
setupPlume(dataProvider);
114+
}
115+
116+
// Run the emulator
117+
while (dataProvider.getStepData()) {
118+
// This is a model step
119+
if (!plumeConfigPath_.empty()) {
120+
runPlume(dataProvider.getStep());
121+
}
122+
eckit::mpi::comm().barrier();
123+
}
124+
125+
// Tear down where appropriate and wait for all processes before finishing
126+
if (!plumeConfigPath_.empty()) {
127+
plume::Manager::teardown();
128+
}
129+
130+
eckit::mpi::comm().barrier();
131+
std::cout << "Process " << rank << " finished..." << std::endl;
132+
eckit::Log::info() << "Emulator run completed..." << std::endl;
133+
return 0;
134+
}
135+
136+
bool NWPEmulator::setupPlume(NWPDataProvider& dataProvider) {
137+
plume::Manager::configure(eckit::YAMLConfiguration(eckit::PathName(plumeConfigPath_)));
138+
139+
plume::Protocol offers; /// Define data offered by Plume
140+
offers.offerInt("NSTEP", "always", "Simulation Step");
141+
auto fields = dataProvider.getModelFieldSet();
142+
for (const auto& field: fields) {
143+
offers.offerAtlasField(field.name(), "on-request", field.name());
144+
}
145+
plume::Manager::negotiate(offers);
146+
plumeData_.createInt("NSTEP", 0); /// Initialise parameters
147+
for (auto& field: fields) {
148+
if (plume::Manager::isParamRequested(field.name())) {
149+
plumeData_.provideAtlasFieldShared(field.name(), field.get());
150+
}
151+
}
152+
153+
plume::Manager::feedPlugins(plumeData_);
154+
return true;
155+
}
156+
157+
void NWPEmulator::runPlume(int step) {
158+
plumeData_.updateInt("NSTEP", step);
159+
plume::Manager::run();
160+
}
161+
162+
int main(int argc, char** argv) {
163+
setenv("ATLAS_LOG_FILE", "true", 1);
164+
NWPEmulator emulator(argc, argv);
165+
return emulator.start();
166+
}

tests/CMakeLists.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@
77
# granted to it by virtue of its status as an intergovernmental organisation nor
88
# does it submit to any jurisdiction.
99

10+
if( NOT DEFINED MPI_SLOTS )
11+
set( MPI_SLOTS 9999 )
12+
endif()
13+
1014
add_subdirectory(core)
11-
add_subdirectory(api)
15+
add_subdirectory(api)
16+
add_subdirectory(nwp_emulator)

tests/nwp_emulator/CMakeLists.txt

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# (C) Copyright 2025- ECMWF.
2+
#
3+
# This software is licensed under the terms of the Apache Licence Version 2.0
4+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
6+
# In applying this licence, ECMWF does not waive the privileges and immunities
7+
# granted to it by virtue of its status as an intergovernmental organisation nor
8+
# does it submit to any jurisdiction.
9+
# Test files repository on Nexus
10+
set(ECBUILD_DOWNLOAD_BASE_URL https://get.ecmwf.int/repository/plume-test-data)
11+
12+
set(test_nwp_emulator_files_dir ${CMAKE_BINARY_DIR}/tests/nwp_emulator/data)
13+
add_custom_target(
14+
make_nwp_emulator_test_data_dir ALL COMMAND ${CMAKE_COMMAND} -E make_directory ${test_nwp_emulator_files_dir}
15+
)
16+
17+
ecbuild_get_test_multidata(
18+
TARGET test_nwp_emulator_files
19+
NAMES
20+
model_data_1.grib
21+
model_data_2.grib
22+
DIRNAME
23+
nwp_emulator
24+
DIRLOCAL
25+
${test_nwp_emulator_files_dir}
26+
NOCHECK
27+
)
28+
29+
ecbuild_add_test(
30+
TARGET plume_test_nwp_grib
31+
SOURCES test_grib_reader.cc
32+
LIBS plume_nwp_emulator
33+
ENVIRONMENT TEST_DATA_DIR=${test_nwp_emulator_files_dir}
34+
MPI 3
35+
CONDITION eckit_HAVE_MPI
36+
TEST_DEPENDS test_nwp_emulator_files
37+
)
38+
39+
ecbuild_add_test(
40+
TARGET plume_test_nwp_config
41+
SOURCES test_config_reader.cc
42+
LIBS plume_nwp_emulator
43+
ENVIRONMENT TEST_DATA_DIR=${CMAKE_CURRENT_SOURCE_DIR}/data/
44+
MPI 3
45+
CONDITION eckit_HAVE_MPI
46+
)
47+
48+
ecbuild_add_library(
49+
TARGET nwp_emulator_test_plugin
50+
SOURCES
51+
nwp_emulator_plugin.h
52+
nwp_emulator_plugin.cc
53+
PRIVATE_LIBS
54+
plume_plugin
55+
)
56+
57+
ecbuild_add_test(
58+
TARGET plume_test_nwp_tool
59+
LIBS simple_plugin nwp_emulator_test_plugin
60+
COMMAND nwp_emulator_run.x
61+
ARGS --config-src=${CMAKE_CURRENT_SOURCE_DIR}/data/valid_config.yml
62+
--plume-cfg=${CMAKE_CURRENT_SOURCE_DIR}/data/plume_config.yml
63+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
no_emulator_key: -1
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"plugins": [
3+
{
4+
"name": "SimplePlugin",
5+
"lib": "simple_plugin",
6+
"plugincore-config": {}
7+
},
8+
{
9+
"name": "NWPEmulatorPlugin",
10+
"lib": "nwp_emulator_test_plugin",
11+
"plugincore-config": {}
12+
}
13+
]
14+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
emulator:
2+
n_steps: 2
3+
grid_identifier: "N80"
4+
vertical_levels: 5
5+
fields:
6+
100u:
7+
levtype: "sfc"
8+
apply:
9+
vortex_rollup:
10+
area: [71.5, -25, 34.5, 45]
11+
time_variation: 1.1
12+
u:
13+
apply:
14+
levels:
15+
"2":
16+
random:
17+
distribution: "uniform"
18+
min: 1.0
19+
max: 2.0
20+
step:
21+
area: [71.5, -25, 34.5, 45]
22+
value: 10.0
23+
variation: 1.0
24+
translation: [1.0, 1.0]
25+
"1,3":
26+
sinc:
27+
modes: 3
28+
min: -1.0
29+
max: 10.0
30+
spread: 10.0
31+
sink: false
32+
"4:":
33+
gaussian:
34+
modes: 2
35+
min: 1.0
36+
max: 2.0
37+
max_stddev: 3.0
38+
sink: true
39+
v: "u"

0 commit comments

Comments
 (0)