-
Notifications
You must be signed in to change notification settings - Fork 376
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
EAMxx: Adds aerosols heterogeneous freezing calculations in P3 microphysics #6947
base: master
Are you sure you want to change the base?
EAMxx: Adds aerosols heterogeneous freezing calculations in P3 microphysics #6947
Conversation
|
TODO:
|
Qucik comments:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few comments. Mostly: why are lots of unit tests now commented?
@@ -192,6 +192,7 @@ be lost if SCREAM_HACK_XML is not enabled. | |||
<!-- P3 microphysics --> | |||
<p3 inherit="atm_proc_base"> | |||
<do_prescribed_ccn>true</do_prescribed_ccn> | |||
<use_hetfrz_classnuc>true</use_hetfrz_classnuc> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add metadata to new options. In particular, type
and doc
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. We will add the following info:
doc=“Switch to turn on heterogeneous freezing due to prognostic aerosols”
type=”logical”
@@ -44,6 +44,7 @@ void P3Microphysics::set_grids(const std::shared_ptr<const GridsManager> grids_m | |||
infrastructure.kte = m_num_levs-1; | |||
infrastructure.predictNc = m_params.get<bool>("do_predict_nc",true); | |||
infrastructure.prescribedCCN = m_params.get<bool>("do_prescribed_ccn",true); | |||
infrastructure.use_hetfrz_classnuc = m_params.get<bool>("use_hetfrz_classnuc",true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jgfouca, @AaronDonahue, @tcclevenger, asking your opinion as well
I don't know how I feel about having defaults in the source code (regardless of whether their value). We should distinguish CIME runs from standalone.
CIME RUNS:
We already put defaults in namelist_defaults_scream.xml
, so here we should not have to set defaults. Sure, since the value is in m_params (b/c of buildnml), the default is not used. But what if at some point a bug sneaks in, and the input yaml file does not contain the value. What if we start using the hard-coded default without knowing?
STANDALONE RUNS:
We prob don't want to specify ALL options in our input.yaml files, to keep them at a minimum. Perhaps a compromise would be to do this in the atm procs constructors:
P3Microphysics::P3Microphysics (const ekat::Comm& comm, const ekat::ParameterList& params)
: AtmosphereProcess(comm, params)
{
#ifndef SCREAM_CIME_BUILD
// set defaults for parameters, so unit tests can use small-ish input files
m_params.get<bool>("do_predict_nc",true); // use get with default to "set if not set"
...
#endif
}
Then, we remove ALL hard-coded defaults in the rest of the parametrization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bartgol I think most (if not all) of these should be moved to the runtime struct and initialize stuff in that struct. I definitely will have a hard time approving this PR if use_hetfrz_classnuc is added here and not near the do_ice_production flag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will revisit and mimic how the do_ice_production
flag is used.
ninuc_cnt.set(mask, frzdep*1.0e6/rho, Zero); | ||
qinuc_cnt.set(mask, ninuc_cnt*mi0, Zero); | ||
break; | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you really ok doing nothing if Iflag
is neither 1 nor 2? If that should be an error, please add EKAT_KERNEL_ERROR(...)
in the default clause. If the default case should indeed do nothing, then maybe you should have an early return, avoiding pointless calculations before...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. We should add EKAT_KERNEL_ERROR(...)
as in the current logic, its value can only be 1 or 2. If it has some other value, it should error out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're calling this function twice with 1 and and then with 2, why not just do the calculations in one function call and avoid switch case here?
if (use_hetfrz_classnuc){
CNT_couple(hetfrz_immersion_nucleation_tend(k), hetfrz_contact_nucleation_tend(k),
hetfrz_deposition_nucleation_tend(k), rho(k), qc_incld(k), nc_incld(k), 1,
ncheti_cnt, qcheti_cnt, nicnt, qicnt, ninuc_cnt, qinuc_cnt);
CNT_couple(hetfrz_immersion_nucleation_tend(k), hetfrz_contact_nucleation_tend(k),
hetfrz_deposition_nucleation_tend(k), rho(k), qc_incld(k), nc_incld(k), 2,
ncheti_cnt, qcheti_cnt, nicnt, qicnt, ninuc_cnt, qinuc_cnt);
...
In other words, I think the added complexity and branching hinder readability and therefore maintainability, so I would recommend simplifying as much as possible
{ | ||
constexpr Scalar pi = C::Pi; | ||
constexpr Scalar rho_h2o = C::RHO_H2O; | ||
constexpr Scalar qsmall = 1.0e-18; // BAD_CONSTANT! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should merge PRs that use constant marked as "BAD_CONSTANT!" ... If it's bad, let's change it before we merge,or else you know it's never going away...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is Constants<S>::QSMALL
. Does that fit your needs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not "BAD" in the sense that it is wrong. We use the label BAD_CONSTANT
as a reminder that it is not a good practice to have arbitrary values for these constants. We must revisit these and understand their impact.
For now, we intend to keep the same values as EAM for these small constants. In my previous experience, changing these constants can sometimes have a large impact on model results (even model climate!). It is better to evaluate the impact before making any change. I checked QSMALL is 1e-14
. 1e-18
also has been used in P3, but it is used as a bare constant (numeric literal). Should we keep the BAD_CONSTANT
label here or remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add instead a string "TODO: ..." ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we can change it to a TODO.
const auto mask = qc_incld > qsmall; | ||
switch (Iflag) { | ||
case 1: // cloud droplet immersion freezing | ||
ncheti_cnt.set(mask, frzimm*1.0e6/rho /* frzimm input is in [#/cm3] */ , Zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all these "set" calls, how often do you expect the mask to be true/false? If the mask could often be ALL false (not sometimes, often), then you may consider using if statements, to avoid computing the packs for the true case for nothing (e.g., in the 1st line we have to compute frzimm*1e6/rho
regardless of whether we need it or not).
Note: this nano-opt makes sense only if you expect mask to be often false. I assume that's not the case, since qsmall is very small. But I don't know how qc_incld is computed, so maybe it's often 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good question. I am not sure about that. @kaizhangpnl or @AaronDonahue might know if mask
can often be false or not.
@@ -57,7 +57,7 @@ struct UnitWrap::UnitTest<D>::TestNcConservation : public UnitWrap::UnitTest<D>: | |||
nc_selfcollect_tend[s] = cxx_device(vs).nc_selfcollect_tend; | |||
} | |||
|
|||
Functions::nc_conservation(nc, nc_selfcollect_tend, cxx_device(offset).dt, nc_collect_tend, nc2ni_immers_freeze_tend, nc_accret_tend, nc2nr_autoconv_tend); | |||
//Functions::nc_conservation(nc, nc_selfcollect_tend, cxx_device(offset).dt, nc_collect_tend, nc2ni_immers_freeze_tend, nc_accret_tend, nc2nr_autoconv_tend); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this commented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question for all the other unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can answer this for you since my recent experience with this code: tests are commented because they're all broken by the changes in the source tree under physics/p3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @mahf708 and Luca for your reviews. I commented out these tests because the function signatures changed when I added this feature, and they failed. @overfelt is already working on reviving these tests (see my comment above about TODOs). He has revived some of the tests already. We will uncomment all these tests and revive them again before changing the status of this PR from "draft" to normal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few comments. Mostly: why are lots of unit tests now commented?
@@ -243,6 +248,7 @@ ::p3_main_internal( | |||
|
|||
p3_main_part2( | |||
team, nk_pack, runtime_options.max_total_ni, infrastructure.predictNc, infrastructure.prescribedCCN, infrastructure.dt, inv_dt, | |||
runtime_options.use_hetfrz_classnuc, ohetfrz_immersion_nucleation_tend, ohetfrz_contact_nucleation_tend, ohetfrz_deposition_nucleation_tend, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, better... but I would do this differently:
- I wouldn't pass
runtime_options.use_hetfrz_classnuc
directly (note that runtime_options is passed in the call as the last arg). - Is there any chance we could make this name better? Imho, "hetfrz" is pretty hard to guess, even for me, so I recommend making the user-facing (namelist-facing) parameters as self-documenting as possible, and I wouldn't worry about the length of the name. Heterogenous, freezing, and nucleation should all appear in the name fully (I'd consider adding mam as well in the name if desired)
- I would push the variables lower down as well (later args) but this is not as important
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Thanks. I didn't notice the runtime_options
struct is sent as an arg. I will revisit this to fix it. I will change the variable names as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename this to something better-documenting. If you look at all other names, you see none has ambiguous acronyms in the name. When I see "CNT" I actually first think about carbon nanotubes (my training is chemical engineering) ... I don't think this is about those CNTs, but maybe it is? 🤯
Consider "ice_classical_nucleation"
@@ -134,6 +134,7 @@ struct Functions | |||
bool set_cld_frac_l_to_one = false; | |||
bool set_cld_frac_i_to_one = false; | |||
bool set_cld_frac_r_to_one = false; | |||
bool use_hetfrz_classnuc = false; // Set to true to use het frz from MAM4xx-ACI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment doesn't belong here. I wouldn't want a reader to get confused and edit stuff here, especially that this is just setting a default that gets overwritten with a call to the following method (see two lines below). The comment belongs in the xml file, which you already have it written well:
<use_hetfrz_classnuc type="logical" doc="Switch to turn on heterogeneous freezing due to prognostic aerosols">false</use_hetfrz_classnuc>
This struct shouldn't have any comments in it, because it should be 1) pretty self-evident and 2) not really editable.
Btw, the first thing we do in initalize_impl is to call the method two lines below:
void P3Microphysics::initialize_impl (const RunType /* run_type */)
{
// Gather runtime options from file
runtime_options.load_runtime_options_from_file(m_params);
...
Requesting reviews from @hassanbeydoun and @brhillman because I know they're very curious about and interested in this part of the p3 code |
487f9b6
to
9be599e
Compare
add_field<Required>("hetfrz_immersion_nucleation_tend", scalar3d_layout_mid, | ||
frz_unit, grid_name, ps); | ||
|
||
// heterogeneous freezing by contact nucleation [cm^-3 s^-1] | ||
add_field<Required>("hetfrz_contact_nucleation_tend", scalar3d_layout_mid, frz_unit, | ||
grid_name, ps); | ||
|
||
// heterogeneous freezing by deposition nucleation [cm^-3 s^-1] | ||
add_field<Required>("hetfrz_deposition_nucleation_tend", scalar3d_layout_mid, | ||
frz_unit, grid_name, ps); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@singhbalwinder this is the last potential important comment, otherwise, I think the PR is mergeable:
What happens if MAM is inactive? What happens to these variables? Maybe we should make these hidden behind if-else? IF so, make sure to do the same below (around 333):
// Inputs for the heteogeneous freezing
diag_inputs.hetfrz_immersion_nucleation_tend = get_field_in("hetfrz_immersion_nucleation_tend").get_view<const Pack**>();
diag_inputs.hetfrz_contact_nucleation_tend = get_field_in("hetfrz_contact_nucleation_tend").get_view<const Pack**>();
diag_inputs.hetfrz_deposition_nucleation_tend = get_field_in("hetfrz_deposition_nucleation_tend").get_view<const Pack**>();
where in the unused/uneeded case, you can use the buffer.unused or something like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When MAM is inactive, values for these variables will be picked up from the namelist and will stay the same for the entire simulation. The simulation will run as usual with no impact from these variables.
I can hide it behind an if/else if it is not the desired behavior. Is there a way for P3 to know if MAM4 is active or not? Previously, when I discussed this, the design principle did not allow this, as each parameterization should be able to run independently without the knowledge of other parameterizations/processes. Otherwise, it makes the logic complex (e.g., to decide if different combinations of processes are valid or not). If it has changed, please let me know, and I will add if/else blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are defined, then there's no need to worry (I wasn't aware they would be defined; this is a little surprising to me tbh)
For hiding them, I would use the boolean you're adding here (hetfrz). It doesn't matter though, whether these statements are hidden or not, won't make a difference as far as I could tell. I was worried a CIME case would error out if these variables are here, but MAM is inactive. I guess we will pick this up in testing soon enough :)
The heterogeneous freezing calculations from prognostics aerosols are
added to P3 microphysics. Setting
use_hetfrz_classnuc
totrue
will turn on these calculations. Otherwise, P3 will use the default
prescribed aerosol calculations.
[BFB] for EAM and EAMxx