Skip to content

Commit 4adf67a

Browse files
authored
Merge branch 'feature-eamxx-histogram_diagnostic' (PR #7615)
Implementation of online histograms. As added to the documentation. [BFB]
2 parents a5b0a41 + 21a991a commit 4adf67a

File tree

10 files changed

+435
-3
lines changed

10 files changed

+435
-3
lines changed

components/eamxx/docs/user/diags/field_contraction.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
In EAMxx, we can automatically calculate field reductions
44
across the horizontal columns and across the model vertical levels.
55
We call these horizontal and vertical reductions.
6-
We can also automatically calculate zonal averages.
6+
We can also automatically calculate zonal averages and histograms.
77
We have *experimental* support for composing diagnostics; below,
88
the horizontal and vertical reductions can be composed
99
sequentially, but if using dp- or dz-weighted reductions,
@@ -85,6 +85,19 @@ And so on...
8585
| --------- | ------ | ----------- |
8686
| `X_zonal_avg_Y_bins` | Area fraction | Average across the zonal direction |
8787

88+
## Histograms
89+
90+
We currently have a utility to calculate histograms online.
91+
To select the histogram, you need to suffix a field name `X` with
92+
`_histogram_` followed by the values specifying the edges of the bins,
93+
separated by `_`. For example, the histogram specified by
94+
`T_mid_histogram_250_270_290_310` has 5 bins effectively defined by
95+
[$-\infty$, 250), [250, 270), [270, 290), [290, 310), and [310, $\infty$).
96+
97+
| Reduction | Weight | Description |
98+
| --------- | ------ | ----------- |
99+
| `X_histogram_V0_V1_..._VN` | 1 or 0 | Count of field values within each range |
100+
88101
## Example
89102

90103
```yaml
@@ -99,7 +112,7 @@ fields:
99112
# in this example, we use T_mid in units of K
100113
- T_mid_horiz_avg # K
101114
- T_mid_vert_avg_dp_weighted # K
102-
- T_mid_vert_sum_dp_weighted # K * Pa * s / (m * m)
115+
- T_mid_vert_sum_dp_weighted # K * Pa * s / (m * m)
103116
- T_mid_vert_avg_dz_weighted # K
104117
- T_mid_vert_sum_dz_weighted # K * m
105118
- T_mid_vert_avg # K
@@ -110,6 +123,7 @@ fields:
110123
- T_mid_vert_avg_dp_weighted_horiz_avg # K
111124
- T_mid_zonal_avg_180_bins # K
112125
- T_mid_zonal_avg_90_bins # K
126+
- T_mid_histogram_250_270_290_310 # count
113127
output_control:
114128
frequency: 1
115129
frequency_units: nmonths

components/eamxx/src/diagnostics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(DIAGNOSTIC_SRCS
99
field_at_height.cpp
1010
field_at_level.cpp
1111
field_at_pressure_level.cpp
12+
histogram.cpp
1213
horiz_avg.cpp
1314
longwave_cloud_forcing.cpp
1415
number_path.cpp
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#include "diagnostics/histogram.hpp"
2+
3+
#include <ekat_team_policy_utils.hpp>
4+
#include <ekat_string_utils.hpp>
5+
6+
7+
namespace scream {
8+
9+
HistogramDiag::HistogramDiag(const ekat::Comm &comm, const ekat::ParameterList &params)
10+
: AtmosphereDiagnostic(comm, params) {
11+
const auto &field_name = m_params.get<std::string>("field_name");
12+
const std::string bin_config = m_params.get<std::string>("bin_configuration");
13+
m_diag_name = field_name + "_histogram_" + bin_config;
14+
15+
// extract bin values from configuration, append end values, and check
16+
const std::vector<std::string> bin_strings = ekat::split(bin_config, "_");
17+
m_bin_reals.resize(bin_strings.size()+2);
18+
m_bin_reals[0] = std::numeric_limits<Real>::lowest();
19+
for (long unsigned int i=1; i < m_bin_reals.size()-1; i++)
20+
{
21+
m_bin_reals[i] = std::stod(bin_strings[i-1]);
22+
EKAT_REQUIRE_MSG(m_bin_reals[i] > m_bin_reals[i-1],
23+
"Error! HistogramDiag bin values must be monotonically "
24+
"increasing.\n"
25+
" - bin configuration: " + bin_config + "\n");
26+
}
27+
m_bin_reals.back() = std::numeric_limits<Real>::max();
28+
}
29+
30+
void HistogramDiag::set_grids(const std::shared_ptr<const GridsManager> grids_manager) {
31+
const auto &field_name = m_params.get<std::string>("field_name");
32+
const auto &grid_name = m_params.get<std::string>("grid_name");
33+
add_field<Required>(field_name, grid_name);
34+
}
35+
36+
void HistogramDiag::initialize_impl(const RunType /*run_type*/) {
37+
using FieldIdentifier = FieldHeader::identifier_type;
38+
using FieldLayout = FieldIdentifier::layout_type;
39+
using ShortFieldTagsNames::CMP;
40+
const Field &field = get_fields_in().front();
41+
const FieldIdentifier &field_id = field.get_header().get_identifier();
42+
const FieldLayout &field_layout = field_id.get_layout();
43+
EKAT_REQUIRE_MSG(field_layout.rank() >= 1 && field_layout.rank() <= 3,
44+
"Error! Field rank not supported by HistogramDiag.\n"
45+
" - field name: " +
46+
field_id.name() +
47+
"\n"
48+
" - field layout: " +
49+
field_layout.to_string() + "\n");
50+
51+
// allocate histogram field
52+
const int num_bins = m_bin_reals.size()-1;
53+
FieldLayout diagnostic_layout({CMP}, {num_bins}, {"bin"});
54+
FieldIdentifier diagnostic_id(m_diag_name, diagnostic_layout,
55+
FieldIdentifier::Units::nondimensional(),
56+
field_id.get_grid_name());
57+
m_diagnostic_output = Field(diagnostic_id);
58+
m_diagnostic_output.allocate_view();
59+
60+
// allocate field for bin values
61+
FieldLayout bin_values_layout({CMP}, {num_bins+1}, {"bin"});
62+
FieldIdentifier bin_values_id(m_diag_name + "_bin_values", bin_values_layout,
63+
field_id.get_units(), field_id.get_grid_name());
64+
m_bin_values = Field(bin_values_id);
65+
m_bin_values.allocate_view();
66+
67+
// copy bin values into field
68+
auto bin_values_view = m_bin_values.get_view<Real *>();
69+
auto bin_values_view_host = Kokkos::create_mirror_view(bin_values_view);
70+
for (long unsigned int i=0; i < bin_values_layout.dim(0); i++)
71+
bin_values_view_host(i) = m_bin_reals[i];
72+
Kokkos::deep_copy(bin_values_view,bin_values_view_host);
73+
}
74+
75+
void HistogramDiag::compute_diagnostic_impl() {
76+
const auto &field = get_fields_in().front();
77+
auto field_layout = field.get_header().get_identifier().get_layout();
78+
auto histogram_layout = m_diagnostic_output.get_header().get_identifier().get_layout();
79+
const int num_bins = histogram_layout.dim(0);
80+
81+
auto bin_values_view = m_bin_values.get_view<Real *>();
82+
auto histogram_view = m_diagnostic_output.get_view<Real *>();
83+
using KT = ekat::KokkosTypes<DefaultDevice>;
84+
using TeamPolicy = Kokkos::TeamPolicy<Field::device_t::execution_space>;
85+
using TeamMember = typename TeamPolicy::member_type;
86+
using TPF = ekat::TeamPolicyFactory<typename KT::ExeSpace>;
87+
switch (field_layout.rank())
88+
{
89+
case 1: {
90+
const int d1 = field_layout.dim(0);
91+
auto field_view = field.get_view<const Real *>();
92+
TeamPolicy team_policy = TPF::get_default_team_policy(num_bins, d1);
93+
Kokkos::parallel_for("compute_histogram_" + field.name(), team_policy,
94+
KOKKOS_LAMBDA(const TeamMember &tm) {
95+
const int bin_i = tm.league_rank();
96+
const Real bin_lower = bin_values_view(bin_i);
97+
const Real bin_upper = bin_values_view(bin_i+1);
98+
Kokkos::parallel_reduce(Kokkos::TeamVectorRange(tm, d1),
99+
[&](int i, Real &val) {
100+
if ((bin_lower <= field_view(i)) && (field_view(i) < bin_upper))
101+
val += sp(1.0);
102+
},
103+
histogram_view(bin_i));
104+
});
105+
} break;
106+
case 2: {
107+
const int d1 = field_layout.dim(0);
108+
const int d2 = field_layout.dim(1);
109+
auto field_view = field.get_view<const Real **>();
110+
TeamPolicy team_policy = TPF::get_default_team_policy(num_bins, d1*d2);
111+
Kokkos::parallel_for("compute_histogram_" + field.name(), team_policy,
112+
KOKKOS_LAMBDA(const TeamMember &tm) {
113+
const int bin_i = tm.league_rank();
114+
const Real bin_lower = bin_values_view(bin_i);
115+
const Real bin_upper = bin_values_view(bin_i+1);
116+
Kokkos::parallel_reduce(Kokkos::TeamVectorRange(tm, d1*d2),
117+
[&](int ind, Real &val) {
118+
const int i1 = ind / d2;
119+
const int i2 = ind % d2;
120+
if ((bin_lower <= field_view(i1,i2)) && (field_view(i1,i2) < bin_upper))
121+
val += sp(1.0);
122+
},
123+
histogram_view(bin_i));
124+
});
125+
} break;
126+
case 3: {
127+
const int d1 = field_layout.dim(0);
128+
const int d2 = field_layout.dim(1);
129+
const int d3 = field_layout.dim(2);
130+
auto field_view = field.get_view<const Real ***>();
131+
TeamPolicy team_policy = TPF::get_default_team_policy(num_bins, d1*d2*d3);
132+
Kokkos::parallel_for("compute_histogram_" + field.name(), team_policy,
133+
KOKKOS_LAMBDA(const TeamMember &tm) {
134+
const int bin_i = tm.league_rank();
135+
const Real bin_lower = bin_values_view(bin_i);
136+
const Real bin_upper = bin_values_view(bin_i+1);
137+
Kokkos::parallel_reduce(Kokkos::TeamVectorRange(tm, d1*d2*d3),
138+
[&](int ind, Real &val) {
139+
const int i1 = ind / (d2*d3);
140+
const int ind2 = ind % (d2*d3);
141+
const int i2 = ind2 / d3;
142+
const int i3 = ind2 % d3;
143+
if ((bin_lower <= field_view(i1,i2,i3)) && (field_view(i1,i2,i3) < bin_upper))
144+
val += sp(1.0);
145+
},
146+
histogram_view(bin_i));
147+
});
148+
} break;
149+
150+
default:
151+
EKAT_ERROR_MSG("Error! Unsupported field rank for histogram.\n");
152+
}
153+
154+
// TODO: use device-side MPI calls
155+
// TODO: the dev ptr causes problems; revisit this later
156+
// TODO: doing cuda-aware MPI allreduce would be ~10% faster
157+
Kokkos::fence();
158+
m_diagnostic_output.sync_to_host();
159+
m_comm.all_reduce(m_diagnostic_output.template get_internal_view_data<Real, Host>(),
160+
histogram_layout.size(), MPI_SUM);
161+
m_diagnostic_output.sync_to_dev();
162+
}
163+
164+
} // namespace scream
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#ifndef EAMXX_HISTOTGRAM_HPP
2+
#define EAMXX_HISTOGRAM_HPP
3+
4+
#include "share/atm_process/atmosphere_diagnostic.hpp"
5+
6+
namespace scream {
7+
/*
8+
* This diagnostic will calculate a histogram of a field across all dimensions
9+
* producing a one dimensional field, with CMP tag dimension named "bin", that
10+
* indicates how many times a field value in the specified range occurred.
11+
*/
12+
13+
class HistogramDiag : public AtmosphereDiagnostic {
14+
15+
public:
16+
// Constructors
17+
HistogramDiag(const ekat::Comm &comm, const ekat::ParameterList &params);
18+
19+
// The name of the diagnostic
20+
std::string name() const { return m_diag_name; }
21+
22+
// Set the grid
23+
void set_grids(const std::shared_ptr<const GridsManager> grids_manager);
24+
25+
protected:
26+
#ifdef KOKKOS_ENABLE_CUDA
27+
public:
28+
#endif
29+
void initialize_impl(const RunType /*run_type*/);
30+
void compute_diagnostic_impl();
31+
32+
protected:
33+
std::string m_diag_name;
34+
std::vector<Real> m_bin_reals;
35+
Field m_bin_values;
36+
};
37+
38+
} // namespace scream
39+
40+
#endif // EAMXX_HISTOGRAM_HPP

components/eamxx/src/diagnostics/register_diagnostics.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "diagnostics/zonal_avg.hpp"
3131
#include "diagnostics/conditional_sampling.hpp"
3232
#include "diagnostics/binary_ops.hpp"
33+
#include "diagnostics/histogram.hpp"
3334

3435
namespace scream {
3536

@@ -63,6 +64,7 @@ inline void register_diagnostics () {
6364
diag_factory.register_product("ZonalAvgDiag",&create_atmosphere_diagnostic<ZonalAvgDiag>);
6465
diag_factory.register_product("ConditionalSampling",&create_atmosphere_diagnostic<ConditionalSampling>);
6566
diag_factory.register_product("BinaryOpsDiag", &create_atmosphere_diagnostic<BinaryOpsDiag>);
67+
diag_factory.register_product("HistogramDiag",&create_atmosphere_diagnostic<HistogramDiag>);
6668
}
6769

6870
} // namespace scream

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ CreateDiagTest(vert_contract "vert_contract_test.cpp" MPI_RANKS 1 ${SCREAM_TEST_
8282
CreateDiagTest(vert_derivative "vert_derivative_test.cpp")
8383

8484
# Test zonal averaging
85-
CreateDiagTest(zonal_avg zonal_avg_test.cpp MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS})
85+
CreateDiagTest(zonal_avg "zonal_avg_test.cpp" MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS})
8686

8787
# Test conditional sampling
8888
CreateDiagTest(conditional_sampling "conditional_sampling_test.cpp")
8989

9090
# Test binary ops
9191
CreateDiagTest(binary_ops "binary_ops_test.cpp")
92+
93+
# Test histogram
94+
CreateDiagTest(histogram "histogram_test.cpp" MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS})

0 commit comments

Comments
 (0)