Complete CCPPization of BAM/MAM aerosol optics; BAM microp_aero (nucleate_ice; ndrop_bam; inline logic in microp_aero) - #398
Conversation
…sol interface Draft: wire up aerosol optics to rrtmgp Move nswbands, nlwbands to host side namelist wire up aerosol_optics in cam4 suite to cherry-pick: remove constituent diag in prescribe_volcanic_aerosol fix dim diagnostic test; try to move radiative_aerosol_definitions to metadata instead of host-model dep Fix idxnite not in rrtmgp_pre before running aerosol_optics; add ODV diags for bulk aerosol
…r lbound of assumed-shape arrays
Assisted-by: claude-opus:4.6
…ol from pbuf_ N fields
| ! Reset per-species accumulators for this bin/level | ||
| scatdust_d(:ncol) = 0._kind_phys; absdust_d(:ncol) = 0._kind_phys; hygrodust_d(:ncol) = 0._kind_phys | ||
| scatbc_d(:ncol) = 0._kind_phys; absbc_d(:ncol) = 0._kind_phys; hygrobc_d(:ncol) = 0._kind_phys | ||
| scatsulf_d(:ncol) = 0._kind_phys; abssulf_d(:ncol) = 0._kind_phys; hygrosulf_d(:ncol) = 0._kind_phys | ||
| scatpom_d(:ncol) = 0._kind_phys; abspom_d(:ncol) = 0._kind_phys; hygropom_d(:ncol) = 0._kind_phys | ||
| scatsoa_d(:ncol) = 0._kind_phys; abssoa_d(:ncol) = 0._kind_phys; hygrosoa_d(:ncol) = 0._kind_phys | ||
| scatsslt_d(:ncol) = 0._kind_phys; abssslt_d(:ncol) = 0._kind_phys; hygrosslt_d(:ncol) = 0._kind_phys |
There was a problem hiding this comment.
A note that these variables of the code is intentionally hardcoded to match CAM aerosol_optics_cam.F90 and will be refactored in the future in CAM to be namelist-driven.
| ! | ||
| ! Safe to use with use_hetfrz_classnuc=.true. for snapshot-testing flows where | ||
| ! the flag must propagate downstream; a warning is printed at init time. | ||
| module hetfrz_classnuc_stub |
There was a problem hiding this comment.
This stub serves both microp_aero and PUMAS, so whichever goes in first.
| call ndrop_bam_calc( & | ||
| aero_state = aero_state_bam, & | ||
| aero_props = aero_props_bam, & |
There was a problem hiding this comment.
This contraption brought to you because the framework is currently unable to handle abstract classes like this being passed into the schemes; also, we need to resolve the BAM aerosol state/props from all the potentially active states (more than one can be active at a time.)
This pattern of having a CCPP marshal will be predominant in the BAM/MAM work in the future.
| !! \htmlinclude ndrop_bam_diagnostics_init.html | ||
| subroutine ndrop_bam_diagnostics_init(errmsg, errflg) | ||
| use cam_history, only: history_add_field | ||
| use ndrop_bam, only: aername |
There was a problem hiding this comment.
There is no elegant way to get the bulk aerosol names but sourcing it from the portable code is the best I could think of. The other way would be to scan rad_aer_climate from the host radiative_aerosol_definitions which seems more hassle. This is consistent with how CAM does the addflds.
| intent = in | ||
| [ tke ] | ||
| standard_name = specific_turbulent_kinetic_energy_at_interfaces | ||
| standard_name = tke_at_interfaces |
There was a problem hiding this comment.
Name updated to match UW PBL. Missed this in the last PR.
| index(cnst_name, 'NUMLIQ') > 0 .or. & ! micro_pumas_cam if modal/trop_strat carma | ||
| index(cnst_name, 'mass_number_concentration_of_cloud_liquid_wrt_moist_air_and_condensed_water') > 0) then |
There was a problem hiding this comment.
These are one and the same, but before it was NUMLIQ if it was not in the registry; if it is in the registry, it becomes the actual standard name.
I would not spend too much pondering the utility of this subroutine; it will be eliminated after dropmixnuc is CCPPized, and this is supposed to be a custom constituent property, not a logical array.
| ! Unified CCPP scheme for ice nucleation via the nucleati kernel. | ||
| ! Uses the abstract interface to support BAM, MAM, and CARMA. | ||
| ! | ||
| ! This scheme replaces nucleate_ice_cam_calc for CAM-SIMA. | ||
| module nucleate_ice_ccpp |
There was a problem hiding this comment.
Same pattern as ndrop_bam_ccpp.
This module supports all aerosol models (BAM/MAM/CARMA)
…bed schemes; diagnostic names in sima_state_diagnostics) into bulk_aero_3_rebased_on_main
…e interface. The diagnostic-list water uptake in modal_aerosol_state (CAM-SIMA) takes t, pmid, h2ommr, cldn and top_lev; thread them through the scheme and the two per-species diagnostic calls.
In CAM these are pbuf fields (qqcw) and never reach the diffusion constituent loop; CAM-SIMA registers them as constituents, so they must be excluded by name like the interstitial modal species.
Mirrors physics_update's qneg3 label in CAM ('nucleatei').
…he MAM-side prescribed_stratospheric_aerosol.
CAM computes relative humidity for the optics table lookup inline in aerosol_optics_cam; in CAM-SIMA cam4/cam5 it comes from compute_cloud_fraction, which cam7 does not run, so capgen found no provider for relative_humidity. The water vapor constituent is resolved by standard name instead of CAM's fixed constituent index 1.
The cam7 cap declares scheme locals by local name, so nucleate_ice's ni (cloud ice number concentration) collides with the gravity wave drag schemes' ni (interface Brunt-Vaisala frequency) and the cap fails to compile. Local-name collisions across schemes with different standard names are a capgen issue to raise upstream; renaming ours unblocks cam7.
nusbaume
left a comment
There was a problem hiding this comment.
Thanks @jimmielin! It will be great to get our first (!) aerosol model plugged into CAM-SIMA. I have some comments and change requests, but i don't think any of them will be too hard to implement. Of course if something I requested would in-fact cause a problem just let me know. Thanks again!
| index(cnst_name, '_c3') > 0 .or. & ! MAM cloud-borne species. | ||
| index(cnst_name, '_c4') > 0 .or. & ! MAM cloud-borne species. | ||
| index(cnst_name, 'NUMLIQ') > 0 .or. & ! micro_pumas_cam if modal/trop_strat carma | ||
| index(cnst_name, 'mass_number_concentration_of_cloud_liquid_wrt_moist_air_and_condensed_water') > 0) then |
There was a problem hiding this comment.
Just leaving a note that you might need to update the standard name here?
There was a problem hiding this comment.
Yes, will do when PUMAS is merged! Please feel free to leave unresolved
There was a problem hiding this comment.
Updated in bf22c5e to synchronize all standard names with PUMAS round 3
|
|
||
| <suite name="cam7" version="1.0"> | ||
| <!-- | ||
| CAM7 PHYSICS SUITE (-chem none variant) |
There was a problem hiding this comment.
I wonder if we should actually change the name of the SDF itself to something like suite_cam7_bam.xml (or whatever name you think makes sense), to make it clear that this particular SDF will only for prescribed BAM aerosols in CAM7.
Assisted-by: claude-opus:5
…ebased_on_main # Conflicts: # schemes/microp_aero/ccpp/hetfrz_classnuc_stub.F90 # schemes/microp_aero/ccpp/hetfrz_classnuc_stub.meta # schemes/microp_aero/ccpp/hetfrz_classnuc_stub_namelist.xml
Renames applied from the round 3 rename ledgers (waves 2026-07 + 2026-08): - npccn -> tendency_of_mass_number_concentration_of_activated_cloud_condensation_nuclei - naai -> tendency_of_mass_number_concentration_of_activated_ice_nuclei - naai_hom -> tendency_of_mass_number_concentration_of_activated_ice_nuclei_due_to_homogeneous_freezing - numliq -> mass_number_concentration_of_cloud_liquid_water_droplets_in_moist_air_and_condensed_water (also inside the diffusion_solver ndrop-activation constituent-name check) - numice -> mass_number_concentration_of_cloud_ice_water_crystals_in_moist_air_and_condensed_water - use_hetfrz_classnuc flag -> do_heterogeneous_ice_nucleation. Note this name is wrong (canonical is do_heterogeneous_freezing_by_classical_nucleation_theory) but it is baked into the ESCOMP/PUMAS v1.40 submodule, so we follow it for now.
Move the PUMAS block into the macro/micro substepping section directly after the microp_aero schemes (clubb_tend -> microp_aero_run -> microp_driver_tend in cam7 physpkg.F90); it was placed after the vertical diffusion slot upstream. Drop schemes already present in this suite (to_be_ccppized_temporary, cloud_fraction_options, compute_cloud_fraction_two_moment, hetfrz_classnuc_stub) and initialize_constituents, which is test-harness scaffolding: the hydrometeor constituents are declared advected in micro_pumas_ccpp_dimensions_pre.meta and initialized by the host. Keep clubb_stub (sole do_clubb_sgs provider) in the CLUBB macrophysics slot.
Add the microphysics-aerosol coupling chain to suite_cam5 per CAM5 physpkg.F90, where microp_aero_run runs immediately before the microphysics driver: tropopause_find (chemical tropopause), subgrid vertical velocity using the TKE variant (tke_at_interfaces from the UW PBL bretherton_park_diff, previous timestep), scale_subgrid_vertical_velocity, dust_default_radii, nucleate_ice_ccpp, and ndrop_bam_ccpp with their diagnostics. Add prescribed_aerosols and rebin_seasalt at the top of the group to register the bulk aerosol constituents ndrop_bam activates. Without this the merged-in PUMAS block would run with never-computed activation inputs (npccn/naai resolve to registry allocatables).
nusbaume
left a comment
There was a problem hiding this comment.
Thanks for resolving my concerns @jimmielin! I left a few comments un-resolved as requested, and added one last (hopefully trivial) request, but otherwise everything looks great to me. Thanks again!
| character(len=*), parameter :: ccn_long_name(psat) = (/ character(len=59) :: & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_0.02pct', & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_0.05pct', & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_0.1pct', & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_0.2pct', & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_0.5pct', & | ||
| 'cloud_condensation_nuclei_number_concentration_at_S_1.0pct' /) |
There was a problem hiding this comment.
Use square brackets here instead of (/ and /)?
peverwhee
left a comment
There was a problem hiding this comment.
a couple of nitpicks and a couple of questions!
I only reviewed aerosol_optics.F90, .meta, and .xml
| if (aodvis(icol) > aodvis_min) then | ||
| ssavis(icol) = sum(aer_tau_w(icol, :, idx_sw_diag)) / aodvis(icol) | ||
| else | ||
| ssavis(icol) = ssavis_default |
There was a problem hiding this comment.
nitpick, but perhaps it'd be better to set ssavis to ssavis_default before entering this loop?
There was a problem hiding this comment.
Yep, that's much cleaner. done! now initialized to ssavis_default at initialization of the array itself
| !type(aerlist_t), intent(in) :: bulk_aerosol_list(:) | ||
| ! does not work: errors with | ||
| ! parse_source.CCPPError: No ddt_lib or ddt aerlist_t not in ddt_lib |
There was a problem hiding this comment.
do we know why this isn't working? Based on the CAM-SIMA PR, I'd hope that this would work.
There was a problem hiding this comment.
Yes, looks like it's a host-side plumbing issue. Claude took a stab at it in ESCOMP/CAM-SIMA@c42fc3c
If that looks reasonable, I am happy to use it and thread things through as opaque ddts. If you think it'll need some more discussion, I can pick that commit out of the BAM PR into a standalone one and work the threading later. Let me know which is your preference! Thanks!
| ! Register aerosol diagnostic history fields for the climate list and all | ||
| ! active diagnostic lists. | ||
| do i = id_climate, N_DIAG | ||
| if (active_calls(i)) then | ||
| call rad_aer_diag_init(bulk_aerosol_list(i)) | ||
| end if | ||
| end do |
There was a problem hiding this comment.
naive question! why are we registering diagnostic history fields here? Why not on the host side (in src/aerosol/radiative_aerosol.F90)?
There was a problem hiding this comment.
I think I just put it here because I had to put it somewhere. Now moved to host!
| use aerosol_optics_core, only: aerosol_optics_sw_bin, aerosol_optics_lw_bin | ||
|
|
||
| ! host-model dependency for aerosol objects: | ||
| use phys_prop, only: nrh ! # of relative humidity bins for table lookup |
There was a problem hiding this comment.
would it be more ccpp-esque for phys_prop to have metadata? So we could pass nrh in as an argument?
There was a problem hiding this comment.
Maybe! I think the physprop file is shared with CAM and doesn't have the metadata htmlinclude blocks now so adding them now would diverge the file from CAM (and there's also some gymnastics needed to separate the declarations that have metadata vs not having metadata in the module definitions and some reordering...) - I can add an issue! Or if you feel strongly we have to do it now I am also happy to do it
|
|
||
| ! Local variables | ||
| integer :: iaermod, ibin, nbins, iwav, ilev, icol | ||
| integer :: num_aero_models, numrh |
There was a problem hiding this comment.
looks like numrh is unused
| aerostate => aerosol_instances_get_state(iaermod, list_idx=0) | ||
|
|
||
| if (.not. associated(aeroprops)) cycle | ||
| if (.not. associated(aerostate)) cycle |
There was a problem hiding this comment.
is it an error if aeroprops is associated but aerostate is not? or is that overkill?
There was a problem hiding this comment.
They're initialized (or left uninitialized) as a pair in aerosol_instances_mod.F90 so I think this guard is sufficient!
| nbins = aeroprops%nbins() | ||
|
|
||
| ! Sulfate weight percent for hygroscopic_wtp optics | ||
| sulfwtpct(:ncol, :pver) = aerostate%wgtpct(ncol, pver) |
There was a problem hiding this comment.
can this just be:
| sulfwtpct(:ncol, :pver) = aerostate%wgtpct(ncol, pver) | |
| sulfwtpct = aerostate%wgtpct(ncol, pver) |
There was a problem hiding this comment.
Thanks, changed!
| real(kind_phys) :: absorp_bin(ncol, pver, nlwbands) | ||
|
|
||
| ! Volcanic geometric radius (pointer into constituents) | ||
| real(kind_phys), target :: geometric_radius(ncol, pver) |
There was a problem hiding this comment.
is target right here? It looks like create_aerosol_optics_object on the host side is checking whether geometric_radius is associated, and it would seem that it will always be associated if this is a target rather than a pointer?
It doesn't matter that much because create_aerosol_optics_object also has all of this logic in an identical case('volcanic_radius', 'volcanic_radius1', 'volcanic_radius2', & 'volcanic_radius3', 'volcanic_radius5'), as done below. But maybe could be a bug if those ever got out of sync?
There was a problem hiding this comment.
Rabbit hole! In short changed constituents to be target, intent(in), then have a temporary pointer that's default null pointing to it, so it's cleaner now. Thanks for spotting this ugly code!
Tag name:
Originator(s): @jimmielin
Description:
microp_aeroschemes for BAM in CAM-SIMA; shared code with CAM.Companion CAM-SIMA PR: ESCOMP/CAM-SIMA#504
List all namelist files that were added or changed:
List all files eliminated and why: N/A
List all files added and what they do:
List all existing files that have been modified, and describe the changes:
List all automated tests that failed, as well as an explanation for why they weren't fixed:
Is this an answer-changing PR? If so, is it a new physics package, algorithm change, tuning change, etc?
New physics package
CAM regression tests for shared code
SIMA snapshot tests for microp_aero
Eyeballed the results from aerosol optics and looked reasonable.
If yes to the above question, describe how this code was validated with the new/modified features: