-
Notifications
You must be signed in to change notification settings - Fork 313
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
base: master
Are you sure you want to change the base?
Changes from 44 commits
f00dcc1
fb2ba21
37fed71
898cead
9079404
743bfa0
40110ba
01c3efa
ea81976
c04e1fd
1abfed3
eb989a4
53b6008
46cd241
57ab73a
56792c2
67d01db
1e1a540
ce71f95
ab88f6a
734ea15
5617195
09d3c2f
f651f99
f214f12
06a6271
0c3ad39
51f644f
f85d84f
673b90a
f2d58ca
0ecd18b
f62bf19
b017013
526385b
541d806
0586a7a
dfe6489
314e66d
87c2183
f1778be
8875fba
752870a
ebcdeda
bfbe350
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1581,6 +1581,7 @@ sub process_namelist_inline_logic { | |
setup_logic_luna($opts, $nl_flags, $definition, $defaults, $nl, $physv); | ||
setup_logic_hillslope($opts, $nl_flags, $definition, $defaults, $nl); | ||
setup_logic_o3_veg_stress_method($opts, $nl_flags, $definition, $defaults, $nl,$physv); | ||
setup_logic_do3_streams($opts, $nl_flags, $definition, $defaults, $nl, $physv); | ||
setup_logic_hydrstress($opts, $nl_flags, $definition, $defaults, $nl); | ||
setup_logic_dynamic_roots($opts, $nl_flags, $definition, $defaults, $nl, $physv); | ||
setup_logic_params_file($opts, $nl_flags, $definition, $defaults, $nl); | ||
|
@@ -1718,6 +1719,11 @@ sub process_namelist_inline_logic { | |
################################## | ||
setup_logic_cropcal_streams($opts, $nl_flags, $definition, $defaults, $nl); | ||
|
||
################################## | ||
# namelist group: dO3_streams # | ||
################################## | ||
setup_logic_do3_streams($opts, $nl_flags, $definition, $defaults, $nl); | ||
|
||
########################################## | ||
# namelist group: soil_moisture_streams # | ||
########################################## | ||
|
@@ -4223,6 +4229,31 @@ sub setup_logic_cropcal_streams { | |
|
||
#------------------------------------------------------------------------------- | ||
|
||
sub setup_logic_do3_streams { | ||
# 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'); | ||
if ( &value_is_true( $nl->get_value('use_do3_streams') ) ) { | ||
if ($opts->{'driver'} ne "nuopc") { | ||
$log->fatal_error("Cannot use do3_streams=.true. with MCT."); | ||
} | ||
Comment on lines
+4238
to
+4240
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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'} ); | ||
Comment on lines
+4241
to
+4246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} else { | ||
if ( defined($nl->get_value('stream_fldfilename_do3'))) { | ||
$log->fatal_error("One of the do3 streams namelist items (stream_fldfilename_do3, " . | ||
" is defined, but use_do3_streams option set to false"); | ||
} | ||
} | ||
} | ||
|
||
#------------------------------------------------------------------------------- | ||
|
||
sub setup_logic_soilwater_movement { | ||
my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; | ||
|
||
|
@@ -4703,7 +4734,7 @@ sub write_output_files { | |
soil_resis_inparm bgc_shared canopyfluxes_inparm aerosol | ||
clmu_inparm clm_soilstate_inparm clm_nitrogen clm_snowhydrology_inparm hillslope_hydrology_inparm hillslope_properties_inparm | ||
cnprecision_inparm clm_glacier_behavior crop_inparm irrigation_inparm | ||
surfacealbedo_inparm water_tracers_inparm tillage_inparm); | ||
surfacealbedo_inparm water_tracers_inparm tillage_inparm do3_streams); | ||
|
||
#@groups = qw(clm_inparm clm_canopyhydrology_inparm clm_soilhydrology_inparm | ||
# finidat_consistency_checks dynpft_consistency_checks); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
<stream_meshfile_do3>share/meshes/fv0.9x1.25_141008_polemod_ESMFmesh.nc</stream_meshfile_do3> | ||
<do3_mapalgo>bilinear</do3_mapalgo> | ||
|
||
<!-- crop calendar streams namelist defaults --> | ||
<stream_year_first_cropcal >1850</stream_year_first_cropcal> | ||
<stream_year_last_cropcal >2100</stream_year_last_cropcal> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# Software Design Documentation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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!!! |
||
|
||
<span style="font-size:larger;"><b>Project Name</b></span> | ||
<!-- Note you can add this to an issue or pull request in github. That is the normal usage of this template. --> | ||
|
||
**Date:**: 09/27/22 | ||
|
||
**Written By**: Adrianna Foster (@adrifoster) | ||
|
||
## Introduction | ||
|
||
--------------------------------------- | ||
|
||
As laid out in CTSM Issue [#270](https://github.com/ESCOMP/CTSM/issues/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). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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 should only apply this downscaling when receiving multiday average ozone. | ||
|
||
We have a gridded file provided by Louisa Emmons for a diurnal ozone variation factor. The data is additionally dimensioned by 'secs' (seconds of day), and depending on the model time of day, can be used as a multiplicative factor for converting multi-day average ozone to sub-daily ozone partial pressure. | ||
|
||
## Solutions | ||
|
||
--------------------------------------- | ||
|
||
Read in the diurnal anomaly file as a streams file, being careful to not assume too much so that other files may be provided in the future. Create a diurnal ozone type that has an `Interp` method to downscale and interpolate multi-day average ozone data to sub-daily based on a factor attribute. Inside the existing `CalcOzoneUptake` routine, if we are using multi-day average ozone, interpolate/downscale the `forc_o3` array using the `Interp` subroutine. | ||
|
||
**Some alternate ideas**: | ||
|
||
1. have the existing `ozone_type` type have this diurnal ozone anomaly type as an attribute that gets initiated only if we are reading in multi-day average ozone. | ||
2. have the diurnal ozone type be fed into relevant `ozone_type` methods as arguments. We would only want to do this if other modules are going to use the diurnal ozone type. This has a possibility, but no clear plans are on the horizon for it. | ||
|
||
Consider going with the first solution for now. | ||
|
||
## Design Considerations | ||
|
||
--------------------------------------- | ||
|
||
### Assumptions and Dependencies | ||
|
||
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. | ||
|
||
|
||
## Design and Architecture | ||
|
||
--------------------------------------- | ||
|
||
### System diagram or flowchart | ||
|
||
#### Diurnal Factor Streams File | ||
|
||
We need to read in the diurnal ozone factor file provided by Louisa Emmons as a streams file. Some modifications are required on the file so that it can be accurately read and parsed by ESMF and to facilitate our chosen implementation strategy: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
1. Add a new 'time' variable (even though the data is not dimensioned by time, except for seconds of day) so that ESMF can parse it. The date is arbitrarily set to 2000-01-01, and the dimensions are set to 'UNLIMITED'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is really helpful that you have laid out these changes and their reasons - thank you! |
||
|
||
2. Shift the 'secs' array up 1800 seconds so that it provides the midpoint of the averaged time interval, rather than the beginning. This conversion is done to facilitate interpolation between the time-of-day dimensions. | ||
|
||
These file modifications can be seen in the Jupyter notebook /glade/u/home/afoster/Diurnal_ozone.ipynb. | ||
|
||
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` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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'** | ||
Comment on lines
+62
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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. | ||
|
||
Because the diurnal anomaly file is not dimensioned by time other than seconds of day, we just need to read it in and advance one time. This is all currently done in the `diurnalOzoneStreamMod`'s `read_O3_stream` subroutine. We set the date to an arbitrary date (the same as on the file, I'm not sure if this is necessary?). | ||
|
||
We also need to grab the `secs` array to do interpolation/downscaling with. | ||
|
||
Both are read from the file and then an instance of `diurnal_ozone_anom_type` is initialized and relevant arrays are set to the ozone factor and seconds data. | ||
|
||
### Diurnal Ozone Anomaly Type | ||
|
||
The module `DiurnalOzoneType` sets up a `diurnal_ozone_anom_type` which has only a few attributes and methods: | ||
|
||
**Attributes**: | ||
|
||
1. `ntimes` - size of time/seconds-of-day dimension (private) | ||
2. `o3_anomaly_grc` - o3 anomaly data, 2d, gridcells x ntimes | ||
3. `time_arr` - time/seconds of day array, 1d, ntimes | ||
|
||
**Methods**: | ||
|
||
1. `Init` - Initializes the anomaly data structures, calls `InitAllocate` | ||
2. `InitAllocate` - allocates arrays and sets them to nan | ||
3. `Interp` - Interpolates/downscales an input multi-day average ozone (`forc_o3`) using the o3 anomaly data and outputs a downscaled `forc_o3_down` array. See below for `Interp` algorithm/pseudo code | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
### Implementation within OzoneMod | ||
|
||
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?* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question about the |
||
|
||
We also add an integer `atm_ozone_freq` as a new attribute, and set it in `Init`. | ||
|
||
Within the `CalcOzoneUptake` method, we check for the `atm_ozone_freq` flag and if it is `atm_ozone_frequency_multiday_average` we call the `Interp` method. | ||
|
||
### Algorithm and Pseudo code for Interp | ||
|
||
|
||
|
||
## Rollout Plan | ||
|
||
--------------------------------------- | ||
Define the roll-out phases and tests you plan to do | ||
|
||
|
||
## Review Sign-off | ||
|
||
--------------------------------------- | ||
|
||
* Reviewer(s): | ||
|
||
* Sign-off Completed on YYYY-MM-DD* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
module DiurnalOzoneType | ||
|
||
!----------------------------------------------------------------------- | ||
! !DESCRIPTION: | ||
! Sets up type for converting multi-day ozone input to sub-daily values using an input | ||
! ozone anomaly file | ||
! | ||
! !USES: | ||
#include "shr_assert.h" | ||
use shr_kind_mod , only : r8 => shr_kind_r8 | ||
use decompMod , only : bounds_type | ||
use clm_varcon , only : spval | ||
use clm_varctl , only : iulog | ||
use abortutils , only : endrun | ||
|
||
implicit none | ||
save | ||
private | ||
|
||
! !PUBLIC TYPES: | ||
type, public :: diurnal_ozone_anom_type | ||
private | ||
! Private data members | ||
integer :: ntimes ! size of time dimension | ||
real(r8), public, allocatable :: o3_anomaly_grc(:,:) ! o3 anomaly data [grc, ntimes] | ||
real(r8), public, allocatable :: time_arr(:) ! time dimension (units = seconds of day) | ||
|
||
contains | ||
! Public routines | ||
procedure, public :: Init | ||
procedure, private :: InitAllocate | ||
procedure, public :: Interp | ||
|
||
end type diurnal_ozone_anom_type | ||
|
||
character(len=*), parameter, private :: sourcefile = & | ||
__FILE__ | ||
|
||
contains | ||
|
||
! ======================================================================== | ||
! Infrastructure routines (initialization, etc.) | ||
! ======================================================================== | ||
|
||
!----------------------------------------------------------------------- | ||
subroutine Init(this, bounds, n) | ||
! | ||
! DESCRIPTION: | ||
! Initialize ozone anomaly data structures | ||
! | ||
! | ||
! ARGUMENTS: | ||
class(diurnal_ozone_anom_type), intent(inout) :: this | ||
type(bounds_type), intent(in) :: bounds | ||
integer, intent(in) :: n | ||
!----------------------------------------------------------------------- | ||
|
||
! set ntimes from input | ||
this%ntimes = n | ||
|
||
! allocate arrays | ||
call this%InitAllocate(bounds) | ||
|
||
end subroutine Init | ||
|
||
!----------------------------------------------------------------------- | ||
subroutine InitAllocate(this, bounds) | ||
! | ||
! !DESCRIPTION: | ||
! Allocate module variables and data structures | ||
! | ||
! !USES: | ||
use shr_infnan_mod, only: nan => shr_infnan_nan, assignment(=) | ||
! | ||
! !ARGUMENTS: | ||
class(diurnal_ozone_anom_type), intent(inout) :: this | ||
type(bounds_type), intent(in) :: bounds | ||
! | ||
! LOCAL VARIABLES: | ||
integer :: begg, endg | ||
!--------------------------------------------------------------------- | ||
|
||
begg = bounds%begg; endg = bounds%endg | ||
|
||
allocate(this%o3_anomaly_grc(begg:endg,1:this%ntimes)); this%o3_anomaly_grc(:,:) = nan | ||
allocate(this%time_arr(1:this%ntimes)); this%time_arr(:) = nan | ||
|
||
end subroutine InitAllocate | ||
|
||
!----------------------------------------------------------------------- | ||
|
||
subroutine Interp(this, bounds, forc_o3, forc_o3_down) | ||
! | ||
! !DESCRIPTION: | ||
! Downscale/interpolate multi-day ozone data to subdaily | ||
! | ||
! !USES: | ||
use clm_time_manager , only : get_curr_date | ||
use clm_varcon , only : secspday | ||
! | ||
! !ARGUMENTS: | ||
class(diurnal_ozone_anom_type), intent(in) :: this | ||
type(bounds_type), intent(in) :: bounds ! bounds type | ||
real(r8), intent(in) :: forc_o3(:) ! ozone partial pressure (mol/mol) | ||
real(r8), intent(out) :: forc_o3_down(:) ! ozone partial pressure, downscaled (mol/mol) | ||
|
||
! | ||
! LOCAL VARIABLES: | ||
integer :: t ! looping index | ||
integer :: yr ! year | ||
integer :: mon ! month | ||
integer :: day ! day of month | ||
integer :: tod ! time of day (seconds past 0Z) | ||
integer :: begg, endg ! bounds | ||
integer :: t_prev ! previous time index | ||
adrifoster marked this conversation as resolved.
Show resolved
Hide resolved
|
||
real(r8) :: tdiff_end | ||
real(r8) :: tdiff_start | ||
real(r8) :: tdiff | ||
!----------------------------------------------------------------------- | ||
|
||
begg = bounds%begg; endg = bounds%endg | ||
|
||
! Get current date/time - we really only need seconds | ||
call get_curr_date(yr, mon, day, tod) | ||
|
||
! find the time interval we are in | ||
do t = 1, this%ntimes | ||
if (real(tod) <= this%time_arr(t)) then | ||
exit | ||
end if | ||
end do | ||
Comment on lines
+127
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
! interpolate, checking for edge cases | ||
if (t == 1) then | ||
! wrap around back | ||
t_prev = this%ntimes | ||
tdiff_end = secspday - this%time_arr(t_prev) + real(tod) | ||
tdiff = this%time_arr(t) + secspday - this%time_arr(t_prev) | ||
else | ||
t_prev = t - 1 | ||
tdiff_end = real(tod) - this%time_arr(t_prev) | ||
tdiff = this%time_arr(t) - this%time_arr(t_prev) | ||
end if | ||
|
||
tdiff_start = this%time_arr(t) - real(tod) | ||
|
||
! interpolate | ||
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) | ||
Comment on lines
+148
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
end subroutine Interp | ||
|
||
!----------------------------------------------------------------------- | ||
|
||
end module DiurnalOzoneType |
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.