Skip to content

Commit ad5e9e5

Browse files
committed
EAMxx: diagnose vertical derivatives
1 parent d2e0273 commit ad5e9e5

File tree

9 files changed

+423
-0
lines changed

9 files changed

+423
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Vertical derivative diagnostics
2+
3+
We can automatically calculate the vertical derivative of a given field
4+
with respect to a vertical coordinate (pressure or height).
5+
In other words, the vertical derivative is the derivative of the field
6+
along a vertical coordinate.
7+
To automatically get the derivative of a field X, we can request
8+
`dX_dp_vert_derivative` or `dX_dz_vert_derivative`
9+
in the output yaml files.
10+
11+
*WARNING: support for the dz-based derivative is experimental!*
12+
13+
## Brief description of algorithm
14+
15+
For simplicity, the field `X` can only have two dimensions
16+
(column and level). This covers most fields of interest, but
17+
notably does not cover higher-dimensioned fields in radiation and MAM.
18+
We calculate the derivative by weighting two finite differences.
19+
The input field `X` must be defined on the midpoints, and its
20+
difference is defined as the difference between two consecutive
21+
levels, pointing downwards. That is, for a pressure coordinate,
22+
23+
$$
24+
\nabla_{p} X = w \frac{x}{a} + (1-w)\frac{y}{b}
25+
$$
26+
27+
where
28+
29+
- $x = X_{i, k} - X_{i, k-1}$ and $a = p_{i,k} - p_{i,k-1}$
30+
- $y = X_{i, k+1} - X_{i, k}$ and $b = p_{i,k+1} - p_{i,k}$
31+
- $w = b / (a + b)$
32+
33+
and $x = 0$, $w=0$ for $k=0$ (highest level)
34+
and $y = 0$, $w=1$ for $k=K$ (lowest level).
35+
36+
## Example
37+
38+
```yaml
39+
%YAML 1.1
40+
---
41+
filename_prefix: monthly.outputs
42+
averaging_type: average
43+
max_snapshots_per_file: 1
44+
fields:
45+
physics_pg2:
46+
field_names:
47+
# in this example, we use T_mid in units of K
48+
- dT_mid_dp_vert_derivative # K / Pa
49+
- dT_mid_dz_vert_derivative # K / m
50+
output_control:
51+
frequency: 1
52+
frequency_units: nmonths
53+
```

components/eamxx/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nav:
1818
- 'Diagnostics':
1919
- 'Overview': 'user/diags/index.md'
2020
- 'Field contraction diagnostics': 'user/diags/field_contraction.md'
21+
- 'Vertical divergence diagnostics': 'user/diags/vert_derivative.md'
2122
- 'Presentations': 'user/presentations.md'
2223
- 'Developer Guide':
2324
- 'Quick-start Guide': 'developer/dev_quickstart.md'

components/eamxx/src/diagnostics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ set(DIAGNOSTIC_SRCS
2323
water_path.cpp
2424
wind_speed.cpp
2525
vert_contract.cpp
26+
vert_derivative.cpp
2627
zonal_avg.cpp
2728
)
2829

components/eamxx/src/diagnostics/register_diagnostics.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "diagnostics/atm_backtend.hpp"
2727
#include "diagnostics/horiz_avg.hpp"
2828
#include "diagnostics/vert_contract.hpp"
29+
#include "diagnostics/vert_derivative.hpp"
2930
#include "diagnostics/zonal_avg.hpp"
3031

3132
namespace scream {
@@ -56,6 +57,7 @@ inline void register_diagnostics () {
5657
diag_factory.register_product("AtmBackTendDiag",&create_atmosphere_diagnostic<AtmBackTendDiag>);
5758
diag_factory.register_product("HorizAvgDiag",&create_atmosphere_diagnostic<HorizAvgDiag>);
5859
diag_factory.register_product("VertContractDiag",&create_atmosphere_diagnostic<VertContractDiag>);
60+
diag_factory.register_product("VertDerivativeDiag",&create_atmosphere_diagnostic<VertDerivativeDiag>);
5961
diag_factory.register_product("ZonalAvgDiag",&create_atmosphere_diagnostic<ZonalAvgDiag>);
6062
}
6163

components/eamxx/src/diagnostics/tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,8 @@ CreateDiagTest(horiz_avg "horiz_avg_test.cpp")
7878
# Test for vertical contraction
7979
CreateDiagTest(vert_contract "vert_contract_test.cpp")
8080

81+
# Test for vertical derivative
82+
CreateDiagTest(vert_derivative "vert_derivative_test.cpp")
83+
8184
# Test zonal averaging
8285
CreateDiagTest(zonal_avg "zonal_avg_test.cpp" MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS})
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#include "catch2/catch.hpp"
2+
#include "diagnostics/register_diagnostics.hpp"
3+
#include "physics/share/physics_constants.hpp"
4+
#include "share/field/field_utils.hpp"
5+
#include "share/grid/mesh_free_grids_manager.hpp"
6+
#include "share/util/eamxx_setup_random_test.hpp"
7+
#include "share/util/eamxx_universal_constants.hpp"
8+
9+
namespace scream {
10+
11+
std::shared_ptr<GridsManager> create_gm(const ekat::Comm &comm, const int ncols, const int nlevs) {
12+
const int num_global_cols = ncols * comm.size();
13+
14+
using vos_t = std::vector<std::string>;
15+
ekat::ParameterList gm_params;
16+
gm_params.set("grids_names", vos_t{"Point Grid"});
17+
auto &pl = gm_params.sublist("Point Grid");
18+
pl.set<std::string>("type", "point_grid");
19+
pl.set("aliases", vos_t{"Physics"});
20+
pl.set<int>("number_of_global_columns", num_global_cols);
21+
pl.set<int>("number_of_vertical_levels", nlevs);
22+
23+
auto gm = create_mesh_free_grids_manager(comm, gm_params);
24+
gm->build_grids();
25+
26+
return gm;
27+
}
28+
29+
TEST_CASE("vert_derivative") {
30+
using namespace ShortFieldTagsNames;
31+
using namespace ekat::units;
32+
33+
// A world comm
34+
ekat::Comm comm(MPI_COMM_WORLD);
35+
36+
// A time stamp
37+
util::TimeStamp t0({2024, 1, 1}, {0, 0, 0});
38+
39+
// Create a grids manager - single column for these tests
40+
constexpr int nlevs = 150;
41+
const int ngcols = 195 * comm.size();
42+
43+
auto gm = create_gm(comm, ngcols, nlevs);
44+
auto grid = gm->get_grid("Physics");
45+
46+
// Input (randomized) qc
47+
FieldLayout scalar1d_layout{{LEV}, {nlevs}};
48+
FieldLayout scalar2d_layout{{COL, LEV}, {ngcols, nlevs}};
49+
50+
FieldIdentifier fin2_fid("qc", scalar2d_layout, kg / kg, grid->name());
51+
FieldIdentifier pm_fid("p_mid", scalar2d_layout, Pa, grid->name());
52+
FieldIdentifier zm_fid("z_mid", scalar2d_layout, m, grid->name());
53+
54+
Field fin2(fin2_fid);
55+
Field pm(pm_fid);
56+
// Field dz(dz_fid);
57+
58+
fin2.allocate_view();
59+
pm.allocate_view();
60+
// zm.allocate_view();
61+
62+
// Construct random number generator stuff
63+
using RPDF = std::uniform_real_distribution<Real>;
64+
RPDF pdf(sp(0.0), sp(200.0));
65+
auto engine = scream::setup_random_test();
66+
67+
// Construct the diagnostics factory
68+
std::map<std::string, std::shared_ptr<AtmosphereDiagnostic>> diags;
69+
auto &diag_factory = AtmosphereDiagnosticFactory::instance();
70+
register_diagnostics();
71+
72+
ekat::ParameterList params;
73+
// instantiation works because we don't do anything in the constructor
74+
auto bad_diag = diag_factory.create("VertDerivativeDiag", comm, params);
75+
SECTION("bad_diag") {
76+
// this will throw because no field_name was provided
77+
REQUIRE_THROWS(bad_diag->set_grids(gm));
78+
}
79+
80+
fin2.get_header().get_tracking().update_time_stamp(t0);
81+
pm.get_header().get_tracking().update_time_stamp(t0);
82+
// zm.get_header().get_tracking().update_time_stamp(t0);
83+
randomize(fin2, engine, pdf);
84+
randomize(pm, engine, pdf);
85+
// randomize(zm, engine, pdf);
86+
87+
// Create and set up the diagnostic
88+
params.set("grid_name", grid->name());
89+
params.set<std::string>("field_name", "qc");
90+
SECTION("bad_diag_2") {
91+
// this will throw because no derivative_method was provided
92+
auto bad_diag_2 = diag_factory.create("VertDerivativeDiag", comm, params);
93+
REQUIRE_THROWS(bad_diag_2->set_grids(gm));
94+
}
95+
96+
SECTION("bad_diag_3") {
97+
// this will throw because bad derivative_method was provided
98+
params.set<std::string>("derivative_method", "xyz");
99+
auto bad_diag_3 = diag_factory.create("VertDerivativeDiag", comm, params);
100+
REQUIRE_THROWS(bad_diag_3->set_grids(gm));
101+
}
102+
103+
// dp_vert_derivative
104+
params.set<std::string>("derivative_method", "dp");
105+
auto dp_vert_derivative = diag_factory.create("VertDerivativeDiag", comm, params);
106+
107+
// TODO: for some reason the z_mid field keeps getting set to 0
108+
// TODO: as a workaround, just calculate z_mid here (sigh...)
109+
// TODO: commenting out all the zm stuff for now
110+
// // dz_vert_derivative
111+
// params.set<std::string>("derivative_method", "dz");
112+
// auto dz_vert_derivative = diag_factory.create("VertDerivativeDiag", comm, params);
113+
114+
dp_vert_derivative->set_grids(gm);
115+
// dz_vert_derivative->set_grids(gm);
116+
117+
// Fields for manual calculation
118+
FieldIdentifier diag1_fid("qc_vert_derivative_manual", scalar2d_layout, kg / kg, grid->name());
119+
Field diag1_m(diag1_fid);
120+
diag1_m.allocate_view();
121+
122+
SECTION("dp_vert_derivative") {
123+
// calculate dp_vert_div manually
124+
pm.sync_to_host();
125+
fin2.sync_to_host();
126+
diag1_m.sync_to_host();
127+
128+
auto pm_v = pm.get_view<const Real **, Host>();
129+
auto fin2_v = fin2.get_view<const Real **, Host>();
130+
auto diag1_m_v = diag1_m.get_view<Real **, Host>();
131+
132+
for (int icol = 0; icol < ngcols; ++icol) {
133+
// handle boundary point
134+
diag1_m_v(icol, 0) = (fin2_v(icol, 1) - fin2_v(icol, 0)) / (pm_v(icol, 1) - pm_v(icol, 0));
135+
// interior points
136+
for (int ilev = 1; ilev < nlevs - 1; ++ilev) {
137+
auto pa = pm_v(icol, ilev) - pm_v(icol, ilev - 1);
138+
auto fa = fin2_v(icol, ilev) - fin2_v(icol, ilev - 1);
139+
auto pb = pm_v(icol, ilev + 1) - pm_v(icol, ilev);
140+
auto fb = fin2_v(icol, ilev + 1) - fin2_v(icol, ilev);
141+
auto ww = pb / (pa + pb);
142+
diag1_m_v(icol, ilev) = ww * (fa / pa) + (1 - ww) * (fb / pb);
143+
}
144+
// handle boundary point
145+
diag1_m_v(icol, nlevs - 1) = (fin2_v(icol, nlevs - 1) - fin2_v(icol, nlevs - 2)) /
146+
(pm_v(icol, nlevs - 1) - pm_v(icol, nlevs - 2));
147+
}
148+
diag1_m.sync_to_dev();
149+
150+
// Calculate weighted avg through diagnostics
151+
dp_vert_derivative->set_required_field(fin2);
152+
dp_vert_derivative->set_required_field(pm);
153+
dp_vert_derivative->initialize(t0, RunType::Initial);
154+
dp_vert_derivative->compute_diagnostic();
155+
auto dp_vert_derivative_f = dp_vert_derivative->get_diagnostic();
156+
157+
REQUIRE(views_are_equal(dp_vert_derivative_f, diag1_m));
158+
}
159+
160+
// TODO: add SECTION("dz_vert_derivative")
161+
}
162+
163+
} // namespace scream

0 commit comments

Comments
 (0)