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

Implement diurnal ozone downscaling #2496

Open
wants to merge 45 commits into
base: master
Choose a base branch
from

Conversation

adrifoster
Copy link
Collaborator

Description of changes

Adds the capability to downscale daily or multi-day ozone into sub-daily ozone input (DATM or from CAM).

As laid out in CTSM Issue #270 we want to downscale input ozone partial pressure (mol/mol) temporally if we receive a multi-day average input ozone (usually in DATM mode).

We have the infrastructure laid out for CAM and DATM to inform CTSM which type of input we are receiving (i.e., 'multiday_average' or 'subdaily'). We only apply this downscaling when receiving multi-day average ozone.

Specific notes

A gridded file provided by @lkemmons for a diurnal ozone variation factor is read in via a new ozone streams module. This streams file is then used to downscale the ozone provided by DATM or otherwise.

Inside the existing CalcOzoneUptake routine, if we are using multi-day average ozone, interpolate/downscale the forc_o3 array using the new Interp subroutine.

Right now we are assuming that the units of the input diurnal file are going to be in seconds, and that the file covers the whole day.

There is no need to assume that the dimension will be 1-24 (i.e. on the hour) or that the dimensions be equal intervals.

The file is read in using a new src/share_esmf/diurnalOzoneStreamMod module, with associated updates to bld/CLMBuildNamelist.pm, bld/namelist_files/namelist_defaults_ctsm.xml and bld/namelist_files/namelist_definition_csvm.xml

Right now the new variable use_do3_streams is set in the user_nl_clm file. And the other namelist variables (stream_fldfilename_do3, stream_meshfile_do3, and do3_mapalgo) are inside the lnd_in file under a do3_streams namelist.

I'm not sure setting up the use_do3_streams namelist variable is the best way to go about this, since we essentially want this feature on whenever the ozone frequency is 'multiday_average' @billsacks what do you think?

Right now we are hard-coding the stream_var_name, and stream_lev_dimname. I think this is okay. Otherwise, we would want them to be namelist variables that get read in.

These updates do change answers, namely ozone uptake is decreased (@danicalombardozzi does this make sense?)

ozone forcing for non-diurnal vs. diurnal for the first simulation day:
ozone_forcing_hist

ozone uptake for the same day:
ozone_uptake_hourly_hist

ozone uptake summed up over the year:
ozone_uptake_sum_hist

@danicalombardozzi i'm not sure about the units for these ozone history variables, they are in mol/m2/time step... do we want to convert them to a per second? Or is this already done somewhere?

Contributors other than yourself, if any:
@billsacks @danicalombardozzi @lkemmons

CTSM Issues Fixed (include github issue #): Fixes #270

Are answers expected to change (and if so in what way)? Yes, see above. If turned on will modify ozone uptake. If ozone damage and ozone downscaling are not on, this will have no effect.

Any User Interface Changes (namelist or namelist defaults changes)? Yes, adds new namelist options for do3_streams. We also need to decide if we want to only optionally do this, or automatically do this all the time if we have multi-day ozone.

Testing performed, if any:

See above, will also do system testing

@adrifoster
Copy link
Collaborator Author

TL; DR:

The big thing we still need to decide is how to implement the namelist options and whether we want to automatically downscale ozone any time we get multi-day average, or only optionally do this if

  1. we have multi-day average ozone
  2. the user requests downscaling via "use_do3_streams" in the user_nl_clm file

My vote: just automatically do it. Or optionally turn it off

Copy link
Contributor

@slevis-lmwg slevis-lmwg left a comment

Choose a reason for hiding this comment

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

@adrifoster I looked at your PR and came up with a comment about indentation. If I were nit-picky, I would have pointed out other, aesthetic, indentation issues, but I'm not :-))

Otherwise, this looks great and it seems straightforward enough that I'm comfortable approving without further follow-up.

A question / comment seeking confirmation: I think this is the land model's first example of a stream with an interp function to take data to subdaily, right?

src/biogeophys/OzoneMod.F90 Outdated Show resolved Hide resolved
@adrifoster
Copy link
Collaborator Author

I looked at your PR and came up with a comment about indentation. If I were nit-picky, I would have pointed out other, aesthetic, indentation issues, but I'm not :-))

I'd like to be consistent, could you point out the other nit-picky things?

@adrifoster
Copy link
Collaborator Author

A question / comment seeking confirmation: I think this is the land model's first example of a stream with an interp function to take data to subdaily, right?

Yes I believe that is correct.

@adrifoster
Copy link
Collaborator Author

All tests PASS except for expected fails, and NLCOMP diffs (which is expected because of the namelist changes I made)

11:16 $ ./cs.status.fails -c NLCOMP
0506-190200de_gnu: 61 tests
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Bgc.derecho_gnu.clm-default--clm-NEON-NIWO SHAREDLIB_BUILD time=14 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Bgc.derecho_gnu.clm-default--clm-NEON-NIWO RUN time=44 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Bgc.derecho_gnu.clm-NEON-MOAB--clm-PRISM SHAREDLIB_BUILD time=9 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Bgc.derecho_gnu.clm-NEON-MOAB--clm-PRISM RUN time=92 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Fates.derecho_gnu.clm-FatesPRISM--clm-NEON-FATES-YELL SHAREDLIB_BUILD time=10 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Fates.derecho_gnu.clm-FatesPRISM--clm-NEON-FATES-YELL RUN time=29 (UNEXPECTED: expected FAIL)

 
0506-190200de_int: 130 tests
    FAIL ERP_P256x2_Ld30.f45_f45_mg37.I2000Clm60FatesRs.derecho_intel.clm-mimicsFatesCold RUN time=71 (EXPECTED FAILURE)
    PEND ERP_P256x2_Ld30.f45_f45_mg37.I2000Clm60FatesRs.derecho_intel.clm-mimicsFatesCold COMPARE_base_rest
    FAIL ERP_P64x2_Ly3.f10_f10_mg37.I2000Clm50BgcCrop.derecho_intel.clm-irrig_o3falk_reduceOutput COMPARE_base_rest
    FAIL ERP_P64x2_Ly3.f10_f10_mg37.I2000Clm50BgcCrop.derecho_intel.clm-irrig_o3falk_reduceOutput BASELINE ctsm5.2.003: DIFF
    FAIL ERS_D_Ld15.f45_f45_mg37.I2000Clm50FatesRs.derecho_intel.clm-FatesColdTwoStream COMPARE_base_rest (EXPECTED FAILURE)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Fates.derecho_intel.clm-FatesFireLightningPopDens--clm-NEON-FATES-NIWO SHAREDLIB_BUILD time=10 (UNEXPECTED: expected FAIL)
    PASS SMS_Ld10_D_Mmpi-serial.CLM_USRDAT.I1PtClm60Fates.derecho_intel.clm-FatesFireLightningPopDens--clm-NEON-FATES-NIWO RUN time=37 (UNEXPECTED: expected FAIL)

 
0506-190200de_nvh: 3 tests
    FAIL SMS_D.f10_f10_mg37.I2000Clm60BgcCrop.derecho_nvhpc.clm-crop SHAREDLIB_BUILD time=278 (EXPECTED FAILURE)

Copy link
Contributor

@slevis-lmwg slevis-lmwg left a comment

Choose a reason for hiding this comment

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

@adrifoster I added a few "nit" comments about indentation since you asked.

src/biogeophys/DiurnalOzoneType.F90 Outdated Show resolved Hide resolved
src/biogeophys/OzoneMod.F90 Outdated Show resolved Hide resolved
src/biogeophys/OzoneMod.F90 Outdated Show resolved Hide resolved
Copy link
Member

@billsacks billsacks left a comment

Choose a reason for hiding this comment

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

Thank you very much for your great work on this @adrifoster ! This feels well-designed and well-written, and I appreciate the time you spent working on this feature - both before you went on leave and then the final work you did to finalize it more recently. I especially appreciate the care you took in various places to document things, call out questions you have, etc. I'm sorry it has taken me so long to review it!

I have a number of comments below. I think most of them are pretty small and will hopefully be easy to address. There are maybe a few that might take a bit more thought, such as related to the interpolation algorithm, but nothing that should take any significant rework: the overall structure of this seems great! I'm happy to talk through any of this if it would help.

Comment on lines +4238 to +4240
if ($opts->{'driver'} ne "nuopc") {
$log->fatal_error("Cannot use do3_streams=.true. with MCT.");
}
Copy link
Member

Choose a reason for hiding this comment

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

  • This check can probably be removed now that mct is no longer supported.

Comment on lines +4241 to +4246
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldfilename_do3',
'hgrid'=>"360x720cru" );
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_meshfile_do3',
'hgrid'=>"360x720cru" );
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'do3_mapalgo',
'hgrid'=>$nl_flags->{'res'} );
Copy link
Member

Choose a reason for hiding this comment

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

  • Is hgrid actually needed as an attribute to these add_default calls? It is confusing to me because the hgrid you're specifying doesn't match the grid of the files (based on the files given in namelist_defaults). And since there is only a single option in the namelist_defaults, I'm thinking that you probably don't need to specify hgrid (which I would think would be to choose between multiple options) - but I could be misunderstanding.

# input file for creating diurnal ozone from >daily data
my ($opts, $nl_flags, $definition, $defaults, $nl, $physv) = @_;

add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_do3_streams');
Copy link
Member

Choose a reason for hiding this comment

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

  • As you note in the design document, I'm not sure there's a use case where a user would want to change use_do3_streams. I'd suggest removing that and just basing the logic on atm_ozone_frequency. (You could potentially still avoid adding the stream definition stuff if ozone is off... not sure if it's worth doing that or not.)

@@ -1732,6 +1732,12 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c
<lai_mapalgo hgrid="1x1_asphaltjungleNJ" >nn</lai_mapalgo>
<lai_mapalgo hgrid="5x5_amazon" >nn</lai_mapalgo>

<!-- do3 streams namelist defaults -->
<use_do3_streams>.true.</use_do3_streams>
<stream_fldfilename_do3>/glade/work/afoster/ozone_update/ozone_damage_files/converted/diurnal_factor_O3surface_ssp370_2015-2024_c20220502.nc</stream_fldfilename_do3>
Copy link
Member

Choose a reason for hiding this comment

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

  • This file should be moved into inputdata.

@@ -0,0 +1,116 @@
# Software Design Documentation
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for writing this design doc and adding it here! This is especially helpful when coming back to this a couple of years later like I'm doing now!!!


We add an instance of the `diurnal_ozone_anom_type` as an attribute of the `ozone_base_type`.

Within the `Init` method of the `ozone_base_type`, we read the `drv_flds_in` file to get the `atom_ozone_frequency_val`. If this value is `atm_ozone_frequency_multiday_average` then we initialize and read in the o3 anomaly data by calling the `read_O3_stream` described above. *Do we need an else decision here?*
Copy link
Member

Choose a reason for hiding this comment

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

Good question about the else. I have added a comment on this below, in the relevant part of the code.

Comment on lines +127 to +131
do t = 1, this%ntimes
if (real(tod) <= this%time_arr(t)) then
exit
end if
end do
Copy link
Member

Choose a reason for hiding this comment

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

  • Is it possible to have real(tod) > this%time_arr(this%ntimes)? My sense is that this case isn't currently handled, but I'm not sure if it's possible for it to arise based on the times on the file.

Comment on lines +148 to +150
forc_o3_down(begg:endg) = forc_o3(begg:endg)* &
((this%o3_anomaly_grc(begg:endg, t_prev)*tdiff_start + &
this%o3_anomaly_grc(begg:endg, t_prev)*tdiff_end)/tdiff)
Copy link
Member

Choose a reason for hiding this comment

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

  • Is there a typo here? You use t_prev as the index for both; should one of them be t? I haven't thought carefully about which one should be t other than thinking: in the edge case where tod == this%time_arr(t), then I think we want to use the anomaly from time t exactly, right? I guess one other thing I'm thinking about here as I start to think about this more is that I wonder if the names of the tdiff_start and tdiff_end variables should be swapped: I haven't thought about this carefully, but it seems like tdiff_start gives the difference from the end of the time interval, which seems a bit counter-intuitive... but maybe I'm seeing this wrong.

Comment on lines +386 to +389
call restartvar(ncid=ncid, flag=flag, varname='o3force', xtype=ncd_double, &
dim1name='gridcell', &
long_name='ozone forcing', units='mol mol^-1', &
readvar=readvar, interpinic_flag='interp', data=this%o3force_grid)
Copy link
Member

Choose a reason for hiding this comment

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

  • I haven't looked at this carefully, but I just want to double-check: does this truly need to be on the restart file? Or is it / can it be recalculated at the start of the run?

integer :: p ! patch index
integer :: c ! column index
integer :: g ! gridcell index
real(r8) :: forc_o3_down(bounds%begg:bounds%endg) ! downscaled ozone partial pressure (mol/mol)
Copy link
Member

Choose a reason for hiding this comment

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

  • Is there a reason for this intermediate forc_o3_down as opposed to putting the result directly in the forc_o3_grid variable? If keeping forc_o3_down, I'd suggest changing its name to forc_o3_downscaled (I assume that's what 'down' is short for?) (rationale: 'down' implies to me 'up' vs. 'down', which is a somewhat reasonable interpretation of the name for a forcing, giving a sign convention, so I'd want to make it clear that that is not the meaning here).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Stalled (needs review, blocked etc.)
Development

Successfully merging this pull request may close these issues.

Receive ozone from atmosphere
3 participants