Skip to content
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

Cloud chemistry module review #5

Open
wants to merge 3 commits into
base: develop-1-cloud-chemistry
Choose a base branch
from

Conversation

mattldawson
Copy link
Collaborator

Reimplements four cloud chemistry modules with new file and module names so that the code can be reviewed.

closes #1

From issue #1, things to consider in review:

  • missing descriptions for units for variables
  • missing descriptions of functions and function arguments
  • unused variables
  • cryptically named variables
  • dead code blocks
  • potential to break up large code blocks, and introduce reusable/testable functions
  • compare MAM and CARMA code for consistency
  • identify host-model vs aerosol-model portions of the code
  • separate configuration data from algorithms
  • magic numbers

	modified:   .gitmodules
	modified:   src/physics/cam/carma_intr.F90
	modified:   src/physics/carma/base (new commits)
	modified:   src/physics/carma/cam/carma_intr.F90
	modified:   src/physics/carma/cam/carma_precision_mod.F90
mm = bin_idx(m, l)
call rad_cnst_get_bin_props_by_idx(0, m, l,spectype=spectype)
if (trim(spectype) == 'sulfate') then
so4mmr(i,k) = so4mmr(i,k) + qcw(i,k,mm)
Copy link

Choose a reason for hiding this comment

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

This loop can be condensed and re-ordered.

Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure how this would work?

!----------------------------------------------------------------------------------
! Update the mixing ratios
!----------------------------------------------------------------------------------
subroutine sox_cldaero_update( state, &
Copy link

Choose a reason for hiding this comment

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

Could split subroutine into sox_cldaero_tend to compute tendencies and sox_cldaero_update to apply tendencies.


conc_obj%no3c(:,:) = 0._r8

if (mode7) then
Copy link

Choose a reason for hiding this comment

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

Index gymnastics is confusing here.

Copy link
Collaborator

@tilmes tilmes Dec 17, 2024

Choose a reason for hiding this comment

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

This indexing can be removed, similar to what is done in carma_clouds.F90 Line 72 to 102.

Copy link
Collaborator

@K20shores K20shores left a comment

Choose a reason for hiding this comment

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

Reviewing cloud_aqueous_chemistry.F90

logical :: modal_aerosols

call phys_getopts( prog_modal_aero_out=modal_aerosols )
cloud_borne = modal_aerosols .or. carma_do_cloudborne
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does cloud_borne logically mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree, this needs to be described clearly in the source code. Recent conversations with people who have worked on CAM for years revealed that there is a lot of confusion about what this actually means.

!-----------------------------------------------------------------------
integer, parameter :: itermax = 20
real(r8), parameter :: ph0 = 5.0_r8 ! INITIAL PH VALUES
real(r8), parameter :: const0 = 1.e3_r8/6.023e23_r8
Copy link
Collaborator

Choose a reason for hiding this comment

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

1 / avogadro?

Comment on lines +235 to +242
real(r8), parameter :: xa0 = 11._r8
real(r8), parameter :: xb0 = -.1_r8
real(r8), parameter :: xa1 = 1.053_r8
real(r8), parameter :: xb1 = -4.368_r8
real(r8), parameter :: xa2 = 1.016_r8
real(r8), parameter :: xb2 = -2.54_r8
real(r8), parameter :: xa3 = .816e-32_r8
real(r8), parameter :: xb3 = .259_r8
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we maybe have a description of these, where they are from (reference?), units?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe none of these variables are used (grep them and there is only one occurrence -- here). I have no idea why they are here. I think we can remove them. Other people can confirm this.

Comment on lines +244 to +247
real(r8), parameter :: kh0 = 9.e3_r8 ! HO2(g) -> Ho2(a)
real(r8), parameter :: kh1 = 2.05e-5_r8 ! HO2(a) -> H+ + O2-
real(r8), parameter :: kh2 = 8.6e5_r8 ! HO2(a) + ho2(a) -> h2o2(a) + o2
real(r8), parameter :: kh3 = 1.e8_r8 ! HO2(a) + o2- -> h2o2(a) + o2
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we have a reference for these or is this just known in the ether of science? Is it applicable forever and always or only for a certain range of environmental conditions?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure about this. This module is Mary's expertise and she can confirm and add references.

real(r8), parameter :: kh2 = 8.6e5_r8 ! HO2(a) + ho2(a) -> h2o2(a) + o2
real(r8), parameter :: kh3 = 1.e8_r8 ! HO2(a) + o2- -> h2o2(a) + o2
real(r8), parameter :: Ra = 8314._r8/101325._r8 ! universal constant (atm)/(M-K)
real(r8), parameter :: xkw = 1.e-14_r8 ! water acidity
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
real(r8), parameter :: xkw = 1.e-14_r8 ! water acidity
real(r8), parameter :: water_acidity = 1.e-14_r8 ! water acidity

So let's call it that


if ( cloud_borne ) then
r2h2o2 = r1h2o2*xl & ! mole/L(w)/s * L(w)/fm3(a) = mole/fm3(a)/s
/ const0*1.e+6_r8 & ! correct a bug here ????
Copy link
Collaborator

Choose a reason for hiding this comment

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

well did the bug get corrected? Is the correction const0 or the multiplication of const0

Copy link
Collaborator

Choose a reason for hiding this comment

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

I have no idea about this. Someone needs to figure this out... Maybe in our regular Tue meetings.

Comment on lines +709 to +711
if ( .not. cloud_borne) then ! this seems to be specific to aerosols that are not cloud borne
xh2o2(i,k) = xh2o2(i,k) + r2h2o2*dtime ! updated h2o2 by het production
endif
Copy link
Collaborator

Choose a reason for hiding this comment

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

are we sure it's specific? If so, why is it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I remember Simone and Mary talked about this a while ago and nobody knew what happened here. I am not sure if they now have an updated view on this...

Comment on lines +760 to +767
rah2o2 = 8.e4_r8 * EXP( -3650._r8*work1(i) ) &
/ (.1_r8 + xph(i,k))

!------------------------------------------------------------------------
! ... S(IV)+ O3
!------------------------------------------------------------------------
rao3 = 4.39e11_r8 * EXP(-4131._r8/tz) &
+ 2.56e3_r8 * EXP(-996._r8 /tz) /xph(i,k)
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we have a citation for these values?

Comment on lines +796 to +797
pso4 = rah2o2 * 7.4e4_r8*EXP(6621._r8*work1(i)) * h2o2g * patm_x &
* 1.23_r8 *EXP(3120._r8*work1(i)) * so2g * patm_x
Copy link
Collaborator

Choose a reason for hiding this comment

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

also these values

! S(IV) + H2O2 = S(VI)
!............................

IF (XL .ge. 1.e-8_r8) THEN !! WHEN CLOUD IS PRESENTED
Copy link
Collaborator

Choose a reason for hiding this comment

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

presented what? why is 1e-8 the value we care about? Is it empirical? Numerical? If numerical, was it derived when we only had single precision and we could make it smaller now?

Copy link
Collaborator

@dmleung dmleung Dec 19, 2024

Choose a reason for hiding this comment

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

Yea it's a small positive definite. You can use greater than zero instead of great or equal to a positive definite, I suppose. There are a lot of these small numbers in different parts of CAM-Chem. There are 1e-20, 1e-30, and so on.

@@ -0,0 +1,894 @@
!----------------------------------------------------------------------------------
! Cloud aqueous chemistry
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@tilmes @marybarthbrock - Could we get a high-level description of the purpose of this module in science terms?

Copy link
Collaborator

@dmleung dmleung Jan 15, 2025

Choose a reason for hiding this comment

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

I would say the purpose of this module is to calculate the sulfate formation due to various oxidation/condensation pathways (e.g., SO2 oxidized by ozone, SO2 oxidized by H2O2, H2SO4 condensation) of cloud chemistry. In the cloud chemistry subroutine setsox(), gas-phase species will be consumed and cloud-borne aerosol species be produced, and the main outputs are these two arrays: qin() and qcw(). Sulfate production rates (e.g., aqso4) and cloud pH changes (xphlwc) are also calculated and outputted by setsox().

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks good to me

Comment on lines +20 to +21
integer :: id_so2, id_nh3, id_hno3, id_h2o2, id_o3, id_ho2
integer :: id_so4, id_h2so4
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We should state what array(s) these are indices for

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can write something like
"these indices are mainly for two arrays: qin and qcw -- qin is the volume mixing ratio (mol/mol or m3/m3) for chemical tracers (gas and aerosol), and qcw is the volume mixing ratio of cloud-borne aerosols. e.g., qin(i,k,id_so2) where i is grid, k is vertical level, and id_so2 is the index for SO2."
Feel free to rewrite this in a succinct way.

integer :: id_so2, id_nh3, id_hno3, id_h2o2, id_o3, id_ho2
integer :: id_so4, id_h2so4

logical :: has_sox = .true.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this should have a description of what it means and how it is used

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mary or Francis will have a better idea on this


!-----------------------------------------------------------------------
!-----------------------------------------------------------------------
subroutine sox_inti
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
subroutine sox_inti
subroutine initialize_cloud_aqueous_chemistry

!-----------------------------------------------------------------------
subroutine sox_inti
!-----------------------------------------------------------------------
! ... initialize the hetero sox routine
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we should include a brief description of what is being initialized

Comment on lines +52 to +105
if (cloud_borne) then
id_h2so4 = get_spc_ndx( 'H2SO4' )
else
id_so4 = get_spc_ndx( 'SO4' )
endif
id_msa = get_spc_ndx( 'MSA' )

inv_so2 = .false.
id_so2 = get_inv_ndx( 'SO2' )
inv_so2 = id_so2 > 0
if ( .not. inv_so2 ) then
id_so2 = get_spc_ndx( 'SO2' )
endif

inv_NH3 = .false.
id_NH3 = get_inv_ndx( 'NH3' )
inv_NH3 = id_NH3 > 0
if ( .not. inv_NH3 ) then
id_NH3 = get_spc_ndx( 'NH3' )
endif

inv_HNO3 = .false.
id_HNO3 = get_inv_ndx( 'HNO3' )
inv_HNO3 = id_hno3 > 0
if ( .not. inv_HNO3 ) then
id_HNO3 = get_spc_ndx( 'HNO3' )
endif

inv_H2O2 = .false.
id_H2O2 = get_inv_ndx( 'H2O2' )
inv_H2O2 = id_H2O2 > 0
if ( .not. inv_H2O2 ) then
id_H2O2 = get_spc_ndx( 'H2O2' )
endif

inv_HO2 = .false.
id_HO2 = get_inv_ndx( 'HO2' )
inv_HO2 = id_HO2 > 0
if ( .not. inv_HO2 ) then
id_HO2 = get_spc_ndx( 'HO2' )
endif

inv_o3 = get_inv_ndx( 'O3' ) > 0
if (inv_o3) then
id_o3 = get_inv_ndx( 'O3' )
else
id_o3 = get_spc_ndx( 'O3' )
endif
inv_ho2 = get_inv_ndx( 'HO2' ) > 0
if (inv_ho2) then
id_ho2 = get_inv_ndx( 'HO2' )
else
id_ho2 = get_spc_ndx( 'HO2' )
endif
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there is repetitive but inconsistent logic used here (e.g., different names for sulfate, some species checked for get_inv_ndx() and others not). I would suggest a data structure for species-specific data like this, with some common functionality for identifying indices, etc., and standardization of logic across all species for consistency.

Comment on lines +137 to +139
!-----------------------------------------------------------------------
!-----------------------------------------------------------------------
subroutine setsox( state, &
Copy link
Collaborator Author

@mattldawson mattldawson Dec 17, 2024

Choose a reason for hiding this comment

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

@tilmes @marybarthbrock - could we get a brief (2-3 sentence) high-level description of what this function is doing in science terms? (there is a comment inside the function that might be a starting point, but I wasn't sure if this describes the entirety of what this function does)

We should also rename the function to be more descriptive (e.g., solve_cloud_aqueous_chemistry() or similar)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe we can consider using Doxygen style comments

Copy link
Collaborator

@dmleung dmleung Dec 17, 2024

Choose a reason for hiding this comment

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

For me, a very brief description of setsox will be
"This subroutine calculates the formation of sulfate and updates sulfate concentration due to cloud aqueous chemistry (i.e., updating qin / qcw). This subroutine also outputs the production rates (in kg/m2/s) of various reactions of sulfur species with various oxidants (i.e., outputting things like aqso4_h2o2 and aqh2so4)."
Something along the line... Mary and Simone et al. can add onto this...

Comment on lines +186 to +191
#ifdef USE_MAM
use mam_clouds, only : sox_cldaero_update, sox_cldaero_create_obj, sox_cldaero_destroy_obj
#endif
#ifdef USE_CLOUDS
use carma_clouds, only : sox_cldaero_update, sox_cldaero_create_obj, sox_cldaero_destroy_obj
#endif
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How are the CPP conditional blocks being used in conjunction with the abstract aerosol interface? It seems like we might want to make sure we're using a consistent approach to permitting the use of multiple aerosol modules.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

after some discussion, we should look into how USE_CLOUDS and USE_MAM are being set and used


real(r8), intent(out) :: aqso4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqh2so4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqso4_h2o2(:) ! SO4 aqueous phase chemistry due to H2O2 (kg/m2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Units of these production rates should be kg/m2/s instead of kg/m2.

Comment on lines +215 to +217
real(r8), intent(out) :: aqso4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqh2so4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqso4_h2o2(:) ! SO4 aqueous phase chemistry due to H2O2 (kg/m2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
real(r8), intent(out) :: aqso4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqh2so4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqso4_h2o2(:) ! SO4 aqueous phase chemistry due to H2O2 (kg/m2)
real(r8), intent(out) :: aqso4(:,:) ! aqueous phase chemistry (kg/m2/s)
real(r8), intent(out) :: aqh2so4(:,:) ! aqueous phase chemistry (kg/m2/s)
real(r8), intent(out) :: aqso4_h2o2(:) ! SO4 aqueous phase chemistry due to H2O2 (kg/m2/s)

knudsen = 3.0_r8*gasdiffus/(gasspeed*rad_cd)

! following assumes accomodation coefficient = 0.65
! (Adams & Seinfeld, 2002, JGR, and references therein)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it this paper? If so, let's add a full bibliographical citation that includes the DOI

Copy link
Collaborator

@K20shores K20shores left a comment

Choose a reason for hiding this comment

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

cloud_utilities.F90 reivew

Comment on lines +105 to +106
! rad_cd = (drop radius in cm), computed from liquid water and drop number,
! then bounded by 0.5 and 50.0 micrometers
Copy link
Collaborator

Choose a reason for hiding this comment

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

but WHY is it bounded? Because a reference says we have to? Because numerically only values beteween 0.5 - 50 are valid?

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to line 115 they applied the bounds at line 118 etc to avoid occasional unphysical values. I have no idea why that is the case though.

!----------------------------------------------------------------------------------

function cldaero_uptakerate( xl, cldnum, cfact, cldfrc, tfld, press ) result( uptkrate )
use mo_constants, only : pi
Copy link
Collaborator

Choose a reason for hiding this comment

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

We use a constant from another module here, but in cloud_aqueous_chemistry.F90 we defined several constants within the code. We should probably push those constants into mo_constants if they don't exist

! radxnum_cd = (drop radius)*(drop number conc)
! volx34pi_cd = (3/4*pi) * (liquid water volume in cm^3/cm^3)

volx34pi_cd = xl*0.75_r8/pi
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the significance of 3/(4 pi)? WHY

Copy link
Collaborator

Choose a reason for hiding this comment

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

Given xl (liquid water content) they want to estimate the droplet radius.
V = 4*pi/3 * R^3
So
V * 0.75/pi is related to the radius. See line 113 below.

Comment on lines +116 to +124
if (radxnum_cd .le. volx34pi_cd*4.0e4_r8) then
radxnum_cd = volx34pi_cd*4.0e4_r8
rad_cd = 50.0e-4_r8
else if (radxnum_cd .ge. volx34pi_cd*4.0e8_r8) then
radxnum_cd = volx34pi_cd*4.0e8_r8
rad_cd = 0.5e-4_r8
else
rad_cd = radxnum_cd/num_cd
end if
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is 4.0e4_r8 arbitrary? Why this value? what makes radxnum_cd and why is 4.0e4_r8 related to that?

Copy link
Collaborator

@dmleung dmleung Dec 19, 2024

Choose a reason for hiding this comment

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

I think theses are just bounds. They didn't want radxnum_cd to be smaller than volx34pi_cd*4.0e4 and bigger than volx34pi_cd*4.0e8. Again, I don't know why. This is a question for Mary.

Comment on lines +126 to +134
! gasdiffus = h2so4 gas diffusivity from mosaic code (cm^2/s)
! (pmid must be Pa)
gasdiffus = 0.557_r8 * (tfld**1.75_r8) / press

! gasspeed = h2so4 gas mean molecular speed from mosaic code (cm/s)
gasspeed = 1.455e4_r8 * sqrt(tfld/98.0_r8)

! knudsen number
knudsen = 3.0_r8*gasdiffus/(gasspeed*rad_cd)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is from mosaic, do we have a reference for where these values come from and why? Do we know if they are always valid or does mosaic assume something we don't?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I can think of a reference for knudsen number (I am sure some textbooks will have this equation) but Mary probably knows better

!
! first-order uptake rate is
! 4*pi*(drop radius)*(drop number conc)
! *(gas diffusivity)*(fuchs sutugin correction)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
! *(gas diffusivity)*(fuchs sutugin correction)
! *(gas diffusivity)*(fuchs sutugin correction)
! Fuchs Sutugin correction:
! Fuchs, N.A., Sutugin, A.G., 1971. HIGH-DISPERSED AEROSOLS, in: Hidy, G.M., Brock, J.R. (Eds.),
! Topics in Current Aerosol Research, International Reviews in Aerosol Physics and Chemistry. Pergamon, p. 1.
! https://doi.org/10.1016/B978-0-08-016674-2.50006-6

Is this a correct reference? Took me forever to track down

Copy link
Collaborator

@dmleung dmleung Dec 19, 2024

Choose a reason for hiding this comment

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

Yes this is correct. A lot of papers on sulfur chemistry also used Fuchs' formulation. E.g., see
https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2005jd005870

real(r8), intent(out) :: aqso4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqh2so4(:,:) ! aqueous phase chemistry
real(r8), intent(out) :: aqso4_h2o2(:) ! SO4 aqueous phase chemistry due to H2O2 (kg/m2)
real(r8), intent(out) :: aqso4_o3(:) ! SO4 aqueous phase chemistry due to O3 (kg/m2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The unit of this is also kg/m2/s

real(r8), intent(out) :: aqso4_o3(:) ! SO4 aqueous phase chemistry due to O3 (kg/m2)
real(r8), intent(in), optional :: yph_in ! ph value
real(r8), intent(out), optional :: aqso4_h2o2_3d(:, :) ! 3D SO4 aqueous phase chemistry due to H2O2 (kg/m2)
real(r8), intent(out), optional :: aqso4_o3_3d(:, :) ! 3D SO4 aqueous phase chemistry due to O3 (kg/m2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

These two have a unit of kg/m2/s too.

Copy link
Collaborator

@K20shores K20shores left a comment

Choose a reason for hiding this comment

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

carma_clouds.F90

Comment on lines +13 to +15
!st use modal_aero_data, only : ntot_amode, modeptr_accum, lptr_so4_cw_amode, lptr_msa_cw_amode
!st use modal_aero_data, only : numptrcw_amode, lptr_nh4_cw_amode
!st use modal_aero_data, only : cnst_name_cw, specmw_so4_amode
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just delete this since it's commented out?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes I think so


integer :: id_msa, id_h2so4, id_so2, id_h2o2, id_nh3

real(r8), parameter :: small_value = 1.e-20_r8
Copy link
Collaborator

Choose a reason for hiding this comment

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

In other places (the backward euler solver) small is set to 1e-40. I imagine there are other versions of small throughout CAM. Do we happen to know why this particular value of small is more useful for clouds?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it depends on the order of magnitudes of the variables concerned. in the code here the order of magnitudes of chemical tracers (e.g., SO2, H2O2) are typically anywhere between 1e-14 (0.01 ppt) to 1e-5 or (10 ppm) in volume mixing ratios. People then want to disregard the reactions once the concentrations are low enough.
The small_value can definitely change depending on the situation and variable concerned. E.g., the small_value for relative humidity should be 0.1 instead of 1e-20. For solving differential equations, I think it makes sense that people try to make things as accurate as possible by using smaller small_values even for chemical tracers. But I think there's always wiggle room, e.g., setting 1e-30 in the solver probably doesn't hurt at all.

integer :: i,k,mm

! local indexing for bins
!integer, allocatable :: bin_idx(:,:) ! table for local indexing of modal aero number and mmr
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps we can delete commented out code?

Suggested change
!integer, allocatable :: bin_idx(:,:) ! table for local indexing of modal aero number and mmr

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess Mary is the best to answer this. Mary needs to review the code changes...

Comment on lines +307 to +310
!st if (id_nh3>0) then
!st delnh3 = nh3g(i,k) - xnh3(i,k)
!st delnh4 = - delnh3
!st endif
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
!st if (id_nh3>0) then
!st delnh3 = nh3g(i,k) - xnh3(i,k)
!st delnh4 = - delnh3
!st endif

Comment on lines +353 to +357
!st dmsadt_gasuptk_tomsa = dmsadt_gasuptk
!st if (ntot_msa_c == 0) then
!st dmsadt_gasuptk_tomsa = 0.0_r8
!st dmsadt_gasuptk_toso4 = dmsadt_gasuptk
!st end if
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
!st dmsadt_gasuptk_tomsa = dmsadt_gasuptk
!st if (ntot_msa_c == 0) then
!st dmsadt_gasuptk_tomsa = 0.0_r8
!st dmsadt_gasuptk_toso4 = dmsadt_gasuptk
!st end if

!H2SO4 not updated in Pengfei's model
!st TEST with H2SO4 uptake
qin(i,k,id_h2so4) = qin(i,k,id_h2so4) - dso4dt_gasuptk * dtime * cldfrc(i,k)
!qin(i,k,id_h2so4) = MAX( qin(i,k,id_h2so4), small_value )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
!qin(i,k,id_h2so4) = MAX( qin(i,k,id_h2so4), small_value )

!st if (id_msa > 0) qin(i,k,id_msa) = qin(i,k,id_msa) - dmsadt_gasuptk * dtime * cldfrc(i,k)

! so2 -- the first order loss rate for so2 is frso2_c*clwlrat(i,k)
! fwetrem = max( 0.0_r8, (1.0_r8-exp(-min(100._r8,dtime*frso2_c*clwlrat(i,k)))) )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
! fwetrem = max( 0.0_r8, (1.0_r8-exp(-min(100._r8,dtime*frso2_c*clwlrat(i,k)))) )

qin(i,k,id_so2) = MAX( qin(i,k,id_so2), small_value )

! h2o2 -- the first order loss rate for h2o2 is frh2o2_c*clwlrat(i,k)
! fwetrem = max( 0.0_r8, (1.0_r8-exp(-min(100._r8,dtime*frh2o2_c*clwlrat(i,k)))) )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
! fwetrem = max( 0.0_r8, (1.0_r8-exp(-min(100._r8,dtime*frh2o2_c*clwlrat(i,k)))) )

Comment on lines +435 to +439
!st if (id_nh3>0) then
!st dqdt_aq = delnh3/dtime*cldfrc(i,k)
!st dqdt = dqdt_aq
!st qin(i,k,id_nh3) = qin(i,k,id_nh3) + dqdt * dtime
!st endif
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
!st if (id_nh3>0) then
!st dqdt_aq = delnh3/dtime*cldfrc(i,k)
!st dqdt = dqdt_aq
!st qin(i,k,id_nh3) = qin(i,k,id_nh3) + dqdt * dtime
!st endif

integer :: id_so4, id_h2so4

logical :: has_sox = .true.
logical :: inv_so2, inv_nh3, inv_hno3, inv_h2o2, inv_ox, inv_nh4no3, inv_ho2

Choose a reason for hiding this comment

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

What does inv_* stand for?

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to Francis and Simone, inv_* stands for invariant. So, it sounds like in certain compsets people holds these tracers (SO2, NH3, H2O2, etc.) constant. In most cases nowadays I don't think we use invariant tracers, so the code mostly uses id_so2 instead of inv_so2, for instance. But, it's used for some compsets.

Francis is the one to answer this question.

Comment on lines +114 to +115
if (masterproc) then
write(iulog,*) 'sox_inti: has_sox = ',has_sox

Choose a reason for hiding this comment

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

What does masterproc mean? (There is a typo: sox_inti -> sox_init.)
It doesn't appear to provide useful information, and it also duplicates the following logs.

Copy link
Collaborator

@dmleung dmleung Jan 15, 2025

Choose a reason for hiding this comment

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

masterproc comes from utils/spmd_utils.F90 and is a flag called when scientists want to write() something in a log file.
Yes, that's a typo and can be changed to sox_init.

! = ( fact1_hno3 )/(1 + fact2_hno3 *(1 + fact3_hno3/hplus)
! [hno3-] = ehno3/hplus
xk = 2.1e5_r8 *EXP( 8700._r8*work1(i) )
xe = 15.4_r8

Choose a reason for hiding this comment

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

Some variables (line435, line 492 and so on) that are constant should be defined outside the loop.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure about this. Line 435 and line 458 are different, so the existing approach is to declare xe as real(r8) and update xe for different reactions. xe is a constant in line 435 but get updated in line 458 as a function of work1 (which is dependent on temperature tfld).

Comment on lines +821 to +824
else ! ???? bug ????
xso2(i,k)=1.e-20_r8
xh2o2(i,k)=xh2o2(i,k)-xso2(i,k)
endif

Choose a reason for hiding this comment

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

Should we verify if this code is valid?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think these lines mean that if you remove lines 821-824, lines 818-820 sometimes will not work. This is probably because xso2 was too small (smaller than 1.e-20_r8), but I'm not sure why that is the case. This is something that Mary should comment on.

BTW, I think line 822 can be
xso2(i,k)=small_value

Comment on lines +137 to +139
!-----------------------------------------------------------------------
!-----------------------------------------------------------------------
subroutine setsox( state, &

Choose a reason for hiding this comment

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

This function is large and difficult to test. I think it would be beneficial to break it down into smaller functions for each step, or a series of steps to make it more testable.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, I think since the code is being refactored it is up to the software engineers to design how the code looks like, as long as it looks good to Mary (this is her science after all).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am a-okay with breaking the code down into smaller functions.

Comment on lines +875 to +887
call sox_cldaero_update( state, &
pbuf, ncol, lchnk, loffset, dtime, mbar, pdel, press, tfld, cldnum, cldfrc, cfact, cldconc%xlwc, &
xdelso4hp, xh2so4, xso4, xso4_init, nh3g, hno3g, xnh3, xhno3, xnh4c, xno3c, xmsa, xso2, xh2o2, qcw, qin, &
aqso4, aqh2so4, aqso4_h2o2, aqso4_o3, aqso4_h2o2_3d=aqso4_h2o2_3d, aqso4_o3_3d=aqso4_o3_3d )

xphlwc(:,:) = 0._r8
do k = 1, pver
do i = 1, ncol
if (cldfrc(i,k)>=1.e-5_r8 .and. lwc(i,k)>=1.e-8_r8) then
xphlwc(i,k) = -1._r8*log10(xph(i,k)) * lwc(i,k)
endif
end do
end do

Choose a reason for hiding this comment

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

My understanding of this function is that it sets variables to update cldaero by calling sox_cldaero_update(). There are also variables with intent(out) (eg. xphlwc in line 880) that aren't used for updating cldaero. What is the purpose of those variables, and how are they used?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think there are two questions here:

  1. sox_cldaero_update() is a function that calculates and outputs the production rates of sulfate (so4) with cloud chemistry through several pathways (e.g., so4 formed due to o3 oxidation, due to h2o2 oxidation, etc). So, looking at sox_cldaero_update() under carmo_aero/carma_cloud.F90, the only intent(out) variables are aqso4, aqh2so4, aqso4_h2o2, ... which are sulfate production rates (kg/m2/s). sox_cldaero_update() is called here in aerosol/cloud_aqueous_chemistry.F90, so setsox() gets these arrays as intent(out) too (see line 215). And, when setsox() is called in the aerosol interface, we will see that they are outputted by setsox() just to be set as history variables, as follows:

      call outfld( 'AQSO4_H2O2', aqso4_h2o2(:ncol), ncol, lchnk)
      call outfld( 'AQSO4_O3',   aqso4_o3(:ncol),   ncol, lchnk)
      call outfld( 'XPH_LWC',    xphlwc(:ncol,:),   ncol, lchnk )
    
  2. I think lines 875-878 and lines 880-887 are two different things. xphlwc is a separate intent(out) variable that setsox() wants to output when setsox() is called. It is also a history variable in the aerosol interface, as follow:

      call outfld( 'XPH_LWC',    xphlwc(:ncol,:),   ncol, lchnk )
    

=====

I copied the above code for outfld() from the CESM2.2 version. In the older CESM2.2, modal_aero/aero_model.F90 calls setsox(), setsox() calculates xphlwc and calls sox_cldaero_update() that calculates aqso4 etc, and aero_model.F90 gets them and do outfld(), so scientists get the history fields for plotting and analyses. I'm not sure where setsox() is called in this new CAM tag, and Francis and Simone will know better where the aerosol interface is. You can also grep it in this new CAM code.

But basically in one sentence, the purpose of sox_cldaero_update() in CESM2.2 is to output sulfate formation rates to aero_model.F90 so scientists get the history fields.

Comment on lines +425 to +427
fwetrem = 0.0_r8 ! don't include h2o2 wet removal here

dqdt_wr = -fwetrem*xh2o2(i,k)/dtime*cldfrc(i,k)

Choose a reason for hiding this comment

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

If fwetrem is zero, then dqdt_wr should also be zero, which seems to be unnecessary logic. Could you confirm?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed. I'm not sure why h2o2 and so2 wet removals were decided to set as zero. I think Mary should make a decision on whether fwetrem should be non-zero (i.e., using line 424 instead of line 425).

But yes, if we just keep the code as is, I would say this can be deleted or commented out (preferred).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe fwetrem for h2o2 and so2 is set to zero because wet removal (or wet scavenging) is computed elsewhere in CAM-chem (the Neu & Prather scheme, mo_neu_wetdep.F90). Let's confirm this with Francis and Simone. If it's correct, then I suggest removing these lines of code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants