Skip to content

Refactor rad_constituents, aerosol_optics_cam for portability; extend abstract aerosol interface#1506

Open
jimmielin wants to merge 14 commits intoESCOMP:cam_developmentfrom
jimmielin:hplin/rad_cnst_refactor_5_optics
Open

Refactor rad_constituents, aerosol_optics_cam for portability; extend abstract aerosol interface#1506
jimmielin wants to merge 14 commits intoESCOMP:cam_developmentfrom
jimmielin:hplin/rad_cnst_refactor_5_optics

Conversation

@jimmielin
Copy link
Copy Markdown
Member

Motivation for changes:

  • Make portable the rad_constituents infrastructure for porting to CAM-SIMA. Previously, the monolithic module combined radiatively active gases and aerosols (which are split in SIMA) and handled namelist read, pbuf and constituent indexing and access (which will be different in SIMA).
  • Expand use of aerosol abstract interface to allow for physics code to swap underlying aerosol representations without changes to how they interface with aerosol; most modules now use the abstract types only.
  • Make portable the aerosol_optics, aerosol_optics_cam infrastructure for porting to CAM-SIMA.

Purpose of changes (include the issue number and title text for each relevant GitHub issue):

Closes #1504, #1505

This pull request is an extensive refactor of rad_constituents.F90 to facilitate bringing into CAM-SIMA.

  • It splits off the previous rad_constituents.F90 into aerosol-related modules; the original file will only handle gases. This is because RRTMGP port to CAM-SIMA split off radiatively active aerosols from gases+aerosols.
  • Isolate CAM-specific data structure retrieval (from pbuf and constituents) to aerosol_mmr_cam.F90, which can be modified to use whichever data structures are in other host models (e.g., the CCPP constituents object in CAM-SIMA will replace both pbuf and state for the purpose of radiatively active aerosol.)
  • Aerosol-related calls are now prefixed with rad_aer_* for clarity.

This pull request also extends the use of the abstract aerosol interface (aerosol_properties and aerosol_state) into other physics and chemistry code, instead of the previous calls to rad_cnst_*.

  • aerosol_properties and aerosol_state are now per-list_idx (climate or diagnostic list) instead of having separate _0list or _rlist functions; separate instantiations of the aerosol properties and state objects per (aero_model, list_idx) pair is available. This allows us to keep track of the objects themselves instead of list_idx. Since most calls are on the climate list (list_idx=0) it will simplify calls and reduce repeating the "0" argument into rad_cnst_*.
  • aerosol_instances_mod owns both aerosol_properties (per model/list) and aerosol_state (per model/list/chunk) as persistent module data. State objects store only pointers to the persistent phys_state(lchnk) and pbuf arrays, and is thread-safe. A per-call create_states/destroy_states factory remains for transient states bound to local copies of physics_state (i.e., microp_aero_run's state1).
  • aerosol_instances_mod and its use in physpkg.F90 most closely mimics the envisioned implementation under CAM-SIMA where aerosol_properties and aerosol_state become inputs to CCPP physics schemes. Under CAM-SIMA the chunk dimension is removed and objects are passed directly — the factory pattern will be retired.

This pull request also extracts the creation of optics objects and per-bin sw/lw property calculations from aerosol_optics_cam to aerosol_optics_core.F90; the latter is fully-portable.

After this refactoring, the following modules will be
fully-portable:

  • aerosol_properties_mod.F90 - abstract base class.
  • aerosol_state_mod.F90 - abstract base class.
  • radiative_aerosol_definitions.F90 - base types for radiatively active aerosol.
  • bulk_aerosol_properties_mod.F90
  • modal_aerosol_properties_mod.F90
  • aerosol_optics_core.F90

almost-portable (need to remove state and pbuf or minor CAM structures):

  • bulk_aerosol_state_mod.F90 - remove state and pbuf, use host-model MMR module (see below)
  • radiative_aerosol.F90 - use host-model MMR module to resolve indices; diags
  • aerosol_instances_mod.F90 - dechunkize, change init_states to remove pbuf, remove create_states factory once unused

host-model specific:

  • aerosol_mmr_cam.F90 - pbuf and state%q indexing to replace with host-model specific code (i.e., CAM-SIMA CCPP constituents)

needing some future work to separate out dependencies - MAM/CARMA changes were made at a best-effort here and need to be for another day:

  • modal_aerosol_state_mod.F90: need to CCPPize modal_aero_wateruptake_dr and modal_aero_calcsize_diag; role of modal_aero_data is to be scoped out (out of scope for this work)
  • carma_aerosol_properties_mod.F90: depends on carma_intr (out of scope for this work)
  • carma_aerosol_state_mod.F90: depends on carma_intr (out of scope for this work)

All changes are bit-for-bit unchanged with CAM (based off cam6_4_156)

List all files eliminated: N/A

List all files added and what they do:

A       src/chemistry/aerosol/aerosol_instances_mod.F90
  - owns persistent aerosol_properties (per model/list) and aerosol_state
    (per model/list/chunk) objects. Provides getters and a per-call factory
    for transient states.

A       src/chemistry/aerosol/radiative_aerosol_definitions.F90
  - split-off structural data and key definitions from rad_constituents.
    purpose of this file is to be the common dependency and not have the gas rad_cnst
    and aerosol rad_aer depend on each other for types and vars like N_DIAG

A       src/chemistry/aerosol/radiative_aerosol.F90
  - split-off aerosol subroutines from rad_constituents.
    this is the "replacement" for rad_constituents in CAM-SIMA

A       src/physics/cam/aerosol_mmr_cam.F90
  - split-off host-model-specific indexing (pbuf, constituents) for radiatively active
    aerosol.

A       src/chemistry/aerosol/aerosol_optics_core.F90
  - split-off logic to create the aerosol_optics_object and per-bin sw/lw optics calculations
    from aerosol_optics_cam.F90 using the abstract aerosol interface.

List all existing files that have been modified, and describe the changes:

M       src/chemistry/aerosol/aerosol_properties_mod.F90
  - add list_idx_ to store which radiation list this properties instance belongs to.
  - add nbins, nspecies, alogsig, dgnum, dgnumhi, dgnumlo, rhcrystal, rhdeliques
  - add num_to_mass_aer, dryrad properties (for BAM)

M       src/chemistry/aerosol/aerosol_state_mod.F90
  - add list_idx_ to store which radiation list this properties instance belongs to.

M       src/chemistry/aerosol/bulk_aerosol_properties_mod.F90
  - implement alogsig (previously a stub)

M       src/physics/cam/rad_constituents.F90
  - remove all code related to aerosol; this module is now gas-only.

M       src/physics/cam/aerosol_optics_cam.F90
  - now uses aerosol_instances_mod to query which aerosol models are active,
    and how many aerosol models are active.
  - now uses aerosol properties and persistent aerosol_state from
    aerosol_instances_mod (replaces per-call create/destroy).
  - moves core calculations to portable aerosol_optics_core.F90.

M       src/physics/cam7/physpkg.F90
M       src/physics/cam/physpkg.F90
  - initialization for radiative_aerosol
  - initialization for aerosol_instances_mod (properties + persistent states)
  - use aerosol_instances_get_state for modal_aer_calcsize, wateruptake
    (replaces per-call create/destroy).

M       src/chemistry/aerosol/bulk_aerosol_state_mod.F90
M       src/chemistry/aerosol/carma_aerosol_properties_mod.F90
M       src/chemistry/aerosol/carma_aerosol_state_mod.F90
M       src/chemistry/aerosol/modal_aerosol_state_mod.F90
M       src/chemistry/aerosol/hygro_aerosol_optics_mod.F90
M       src/chemistry/aerosol/hygrocoreshell_aerosol_optics_mod.F90
M       src/chemistry/aerosol/hygroscopic_aerosol_optics_mod.F90
M       src/chemistry/aerosol/hygrowghtpct_aerosol_optics_mod.F90
M       src/chemistry/aerosol/insoluble_aerosol_optics_mod.F90
M       src/chemistry/aerosol/refractive_aerosol_optics_mod.F90
M       src/chemistry/aerosol/volcrad_aerosol_optics_mod.F90
  - changes to support list_idx change

M       src/chemistry/aerosol/modal_aerosol_properties_mod.F90
  - changes to support list_idx change
  - port mode_size_order from modal_aero_data.
  - add dgnumlo, dgnumhi, dgnum, rhcrystal, rhdeliques

M       src/chemistry/aerosol/aero_wetdep_cam.F90
  - use persistent aerosol_state from aerosol_instances_mod
    (replaces direct modal/carma constructor calls and per-call deallocation).

M       src/chemistry/aerosol/modal_aero_data.F90
M       src/chemistry/carma_aero/carma_aero_gasaerexch.F90
M       src/chemistry/carma_aero/sox_cldaero_mod.F90
M       src/chemistry/carma_aero/aero_model.F90
M       src/chemistry/geoschem/chemistry.F90
M       src/chemistry/modal_aero/aero_model.F90
M       src/chemistry/modal_aero/modal_aero_rename.F90
  - changes to rename rad_cnst_ to rad_aer_ where needed
    (cannot use abstract interface during initialization/register phases.)

M       src/physics/cam/phys_debug.F90
M       src/physics/camrt/radiation.F90
M       src/physics/rrtmg/radiation.F90
M       src/physics/rrtmgp/radiation.F90
M       src/physics/cam/radiation_data.F90
  - changes to rename rad_cnst_ to rad_aer_ where needed
    (these modules will not be ported to CAM-SIMA so were less modified.)

M       src/chemistry/modal_aero/modal_aero_gasaerexch.F90
  - changes to rename rad_cnst to rad_aer_ where needed
    (part of modal_aero model itself so no need for abstract interface.)

M       src/chemistry/mozart/mo_usrrxt.F90
  - changes to rename rad_cnst to rad_aer_ where needed
    (will be superceded by MICM and just dimension setup anyway.)

M       src/physics/cam/microp_aero.F90
  - now use aero_props and aero_state abstract interface.
  - persistent per-chunk aerosol_state moved to aerosol_instances_mod; the
    aerosol_state_object() shim now delegates to aerosol_instances_get_state()
    for backward compatibility with the dycore stepon modules
    (so we do not have to edit the fv3 external)
  - microp_aero_run still uses the per-call factory for state1 (local copy).

M       src/chemistry/modal_aero/dust_model.F90
M       src/chemistry/modal_aero/seasalt_model.F90
M       src/chemistry/mozart/fire_emissions.F90
M       src/chemistry/utils/modal_aero_deposition.F90
  - now use aero_props abstract interface (modal aerosols).

M       src/chemistry/utils/modal_aero_calcsize.F90
M       src/chemistry/utils/modal_aero_wateruptake.F90
  - now use aero_props and aero_state abstract interfaces.

M       src/chemistry/utils/prescribed_aero.F90
  - now uses aerosol_instances_mod::aerosol_instances_is_active to query
    whether the modal aerosol representation is active.

M       src/physics/cam/aer_rad_props.F90
  - now uses aerosol_instances_mod to query which aerosol models are active,
    and how many aerosol models are active.

M       src/physics/cam/aer_vis_diag_mod.F90
  - now use aero_props abstract interface (bulk aerosols).

M       src/physics/cam/ndrop_bam.F90
  - use aero_props and aero_state abstract interfaces (bulk aerosols).

M       src/physics/cam/nucleate_ice_cam.F90
  - use aero_props and aero_state abstract interfaces.

M       src/physics/cam/clubb_intr.F90
M       src/physics/cam/zm_conv_intr.F90
  - remove unused use statements

…hrough 4f plus cleanup

(interactive rebase on 64_156)

rad_cnst/aerosol interface refactor phases 1 to 3

rad_cnst/aerosol interface refactor 1: make one aerosol property obj per list_idx

rad_cnst/aerosol interface refactor 1b: make one aerosol state obj per list_idx

rad_cnst/aerosol interface refactor 1c: remove list_idx argument from aerosol property objects

rad_cnst/aerosol interface refactor 2a: migrate to aerosol_definition_mod.F90

(non-buildable) aer-gas split in rad_cnst

(to test) split non-portable code by feature

(to test) continued refactoring of rad_cnst/abstract aerosol interface.

rad_cnst/aerosol interface refactor 3: gas-aerosol split; module reorg

wip refactor 3c: move indexing-related to aerosol_mmr_cam

aerosol abstract interface refactor 4a: prototypes for moving all access to abstract aerosol interface (aer_rad_props; microp_aero testbed)

abstract aerosol interface refactor 4b: move initialization of state to avoid wrong ordering; revert bam validation logic in mirop

abstract aerosol interface refactor 4c: move runtime to use aerosol_state when possible (excl register and init phases)

abstract aerosol interface refactor 4c bugfix: aquaplanet sets; ODV_ diagnostic

abstract aerosol interface refactor 4d bugfix: nucleate_ice_cam always active even when aerosol count is 0; implement nlogsig for BAM

abstract aerosol interface refactor 4e: cleanup use statements; keyword args for get mmr for clarity; rename init1 init2 for clarity.

abstract aerosol interface refactor 4f: cleanup use statements for radiative_aerosol; radiative_aerosol_definitions

Continue cleanup of remnant rad_constituents; reorder comments, etc.

Fix build for FCARMA (but introduces aerosol_mmr_cam dep into carma_aero/aero_model.F90)

Fix OMP bug; cleanup

Aerosol list renaming for clarity.

Fix build error in var rename

Comment cleanup

Cleanup of dryrad, num_to_mass_aer leftover in modal, sectional_props.

Change to globally held aerosol_state instance (to test threading)

Fix build (remnant issue in microp_aero_final)
Draft optics

some cleanup; move parameter 1.3 to be shared

cleanup aerosol_optics_core

Fix build; cleanup

Make diag arguments non-optional
@jimmielin jimmielin self-assigned this Mar 13, 2026
@jimmielin jimmielin requested a review from fvitt March 13, 2026 15:57
Copy link
Copy Markdown
Collaborator

@fvitt fvitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started a review. You have done a lot of nice work on this. I have a couple of questions on some of this.

@jimmielin jimmielin requested a review from fvitt March 23, 2026 15:23
Copy link
Copy Markdown
Collaborator

@fvitt fvitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more random things.

ncol, pver, top_lev, nswbands, nlwbands, numrh, &
idx_sw_diag, &
relh(:ncol,:), sulfwtpct(:ncol,:), mass(:ncol,:), crefwsw, crefwlw, &
geometric_radius=geometric_radius(:ncol,:), &
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Fortran allows you to pass a null pointer as an actual argument corresponding to an optional pointer dummy argument. I you take advantage of this, the if (associated(geometric_radius)) check could be removed and these 2 calls to aerosol_optics_sw_bin can be combined. This may require some changes in aerosol_optics_core to declare geometric_radius the dummy argument with both pointer and optional attributes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Francis, this is a great idea. I changed to optional, pointer and check for both present and associated in aerosol_optics_core.

I wasn't sure of compiler behavior, so I did some digging. You probably know this so I'm just writing it down for future reference.

The F2008 standard is quite clear on the behavior of passing null() to optional dummy arguments (emphasis mine)

12.5.2.12 Argument presence and restrictions on arguments not present
A dummy argument or an entity that is host associated with a dummy argument is not present if the dummy argument
• does not correspond to an actual argument,
• corresponds to an actual argument that is not present, or
• does not have the ALLOCATABLE or POINTER attribute, and corresponds to an actual argument that
– has the ALLOCATABLE attribute and is not allocated, or
has the POINTER attribute and is disassociated.

The F2003 standard was not very clear on this so I suspect I need a associated() check to go together with the present() check.

I found an example of this that's currently in CAM

if (.not. present(dgnum_m)) then
call endrun('modal_aero_calcsize_diag called for'// &
'diagnostic list but dgnum_m pointer not present')
end if
if (.not. associated(dgnum_m)) then
call endrun('modal_aero_calcsize_diag called for'// &
'diagnostic list but dgnum_m not associated')
end if

so I think the compiler support should be solid if both checks are used, so that's what I adopted.


public :: aerosol_mmr_init ! allocate zero_cols
public :: get_cam_idx
public :: resolve_mode_idx, resolve_bin_idx
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent, can resolve_bin_idx be declared public in a separate line?

Suggested change
public :: resolve_mode_idx, resolve_bin_idx
public :: resolve_mode_idx
public :: resolve_bin_idx

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, moved to separate line. Originally as suggested by Simone I tried to merge them together but opted not to within this PR.


implicit none
private
save
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The save statement at the module level is redundant. This line can be removed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed.

module procedure rad_aer_get_mam_props_by_idx
end interface

! Public routines — aerosol queries (rad_aer_* naming)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dash in line 29 is showing up as some strange characters in xxdiff.

Suggested change
! Public routines aerosol queries (rad_aer_* naming)
! Public routines -- aerosol queries (rad_aer_* naming)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed throughout.

!
! NOTE: A model is "globally active" if the climate list (list 0) has > 0 entries
! for aerosol with that representation, but individual diagnostic lists
! may have zero entries — in that case the corresponding properties slot is left null.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 78 contains strange characters (the "dash").

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed throughout.

! Number of aerosol models active at runtime.
! Note: Multiple aerosol models can be active at once, e.g., using bulk for volcanic aerosol and modal for others.
! When retrieving properties via aerosol_instances_get_props, or creating states from
! aerosol_instances_create_states, ensure that the aerosol model matches what is needed (e.g., aero_props%model_is('MAM') == .true.)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these lines in the comments seem extra long. Maybe limit the line lengths to 80, or so, characters.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, tried to limit to 80 by rearranging the text a bit.

Comment on lines +37 to +38
real(r8),intent(out) :: palb(ncol) ! parameterized single scattering albedo
real(r8),intent(out) :: pasm(ncol) ! parameterized asymmetry factor
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed these comments were possibly swapped so I just updated them in this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants