diff --git a/.gitignore b/.gitignore index 0c9f941a97..cafb205f72 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ src/physics/silhs src/chemistry/geoschem/geoschem_src src/physics/pumas src/physics/pumas-frozen +src/physics/rrtmgp/data +src/physics/rrtmgp/ext src/dynamics/fv3/atmos_cubed_sphere libraries/FMS libraries/mct diff --git a/Externals_CAM.cfg b/Externals_CAM.cfg index 3b9952f6e9..debf4c613e 100644 --- a/Externals_CAM.cfg +++ b/Externals_CAM.cfg @@ -86,5 +86,19 @@ repo_url = https://github.com/ESCOMP/HEMCO_CESM.git required = True externals = Externals_HCO.cfg +[rte-rrtmgp] +local_path = src/physics/rrtmgp/ext +protocol = git +repo_url = https://github.com/earth-system-radiation/rte-rrtmgp.git +tag = v1.7 +required = True + +[rrtmgp-data] +local_path = src/physics/rrtmgp/data +protocol = git +repo_url = https://github.com/earth-system-radiation/rrtmgp-data.git +tag = v1.8 +required = True + [externals_description] schema_version = 1.0.0 diff --git a/bld/build-namelist b/bld/build-namelist index 5ebe186a76..0c56fff055 100755 --- a/bld/build-namelist +++ b/bld/build-namelist @@ -694,6 +694,23 @@ my $rad_pkg = $cfg->get('rad'); if ($rad_pkg eq 'camrt') { add_default($nl, 'absems_data'); } +elsif ($rad_pkg =~ m/rrtmgp/) { + # Dataset for gas optics are checked out of an external repo into + # the source code directory. The paths to this data are relative + # to the root directory of the cam component. + my $cam_dir = $cfg->get('cam_dir'); + + add_default($nl, 'rrtmgp_coefs_lw_file'); + my $rel_path = $nl->get_value('rrtmgp_coefs_lw_file'); + my $abs_path = quote_string(set_abs_filepath($rel_path, $cam_dir)); + # Overwrite the relative pathname with the absolute pathname in the namelist object + $nl->set_variable_value('radiation_nl', 'rrtmgp_coefs_lw_file', $abs_path); + + add_default($nl, 'rrtmgp_coefs_sw_file'); + $rel_path = $nl->get_value('rrtmgp_coefs_sw_file'); + my $abs_path = quote_string(set_abs_filepath($rel_path, $cam_dir)); + $nl->set_variable_value('radiation_nl', 'rrtmgp_coefs_sw_file', $abs_path); +} # Solar irradiance @@ -705,15 +722,20 @@ if (defined $nl->get_value('solar_const') and } -if ($rad_pkg eq 'rrtmg' or $chem =~ /waccm/) { +if ($rad_pkg =~ /rrtmg/ or $chem =~ /waccm/) { if (defined $nl->get_value('solar_const')) { - die "$ProgName - ERROR: Specifying solar_const with RRTMG or WACCM is not allowed.\n" + die "$ProgName - ERROR: Specifying solar_const with RRTMG/RRTMGP or WACCM is not allowed.\n" } # use solar data file as the default for rrtmg and waccm_ma add_default($nl, 'solar_irrad_data_file'); - add_default($nl, 'solar_htng_spctrl_scl', 'val'=>'.true.'); + + # This option only used by camrt and rrtmg radiation schemes. + # The solar spectral scaling is done internal to RRTMGP code. + if ($rad_pkg ne 'rrtmgp') { + add_default($nl, 'solar_htng_spctrl_scl', 'val'=>'.true.'); + } } elsif (!$simple_phys) { @@ -843,7 +865,6 @@ if ($chem_rad_passive or $aqua_mode) { # The aerosol optics depend on which radiative transfer model is used due to differing # wavelength bands used. -my $rrtmg = $rad_pkg eq 'rrtmg' ? 1 : 0; # @aero_names contains the names of the entities (bulk aerosols and modes) # that are externally mixed in aerosol optics calculation. These entities are all @@ -1121,7 +1142,7 @@ if ($aer_model eq 'mam' ) { } if ($rad_prog_sslt) { - if ($rrtmg) { + if ($rad_pkg =~ /rrtmg/) { push(@aero_names, "SSLT01", "SSLT02", "SSLT03", "SSLT04"); push(@aerosources, "A:", "A:", "A:", "A:" ); } else { @@ -1129,7 +1150,7 @@ if ($aer_model eq 'mam' ) { push(@aerosources, "N:", "N:"); } } elsif ($moz_aero_data =~ /$TRUE/io ) { - if ($rrtmg) { + if ($rad_pkg =~ /rrtmg/) { push(@aero_names, "sslt1", "sslt2", "sslt3", "sslt4"); push(@aerosources, "N:", "N:", "N:", "N:" ); } else { @@ -1224,7 +1245,7 @@ if ($carma eq 'bc_strat') { } } -if ($rrtmg) { +if ($rad_pkg eq 'rrtmg') { # CARMA Microphysics - RRTMG Only # @@ -1644,11 +1665,25 @@ if ($rad_pkg ne 'none') { } # Cloud optics -if ($rrtmg) { +if ($rad_pkg =~ m/rrtmg/) { # matches both rrtmg and rrtmgp add_default($nl, 'liqcldoptics'); add_default($nl, 'icecldoptics'); add_default($nl, 'liqopticsfile'); add_default($nl, 'iceopticsfile'); + + # rrtmgp only implemented with mitchell and gammadist cloud optics + if ($rad_pkg =~ m/rrtmgp/) { + my $liqcldoptics = $nl->get_value('liqcldoptics'); + if ($liqcldoptics !~ m/gammadist/) { + die "$ProgName - ERROR: RRTMGP only implemented with gammadist liquid cloud optics\n" . + "liqcldoptics = $liqcldoptics\n"; + } + my $icecldoptics = $nl->get_value('icecldoptics'); + if ($icecldoptics !~ m/mitchell/) { + die "$ProgName - ERROR: RRTMGP only implemented with mitchell ice cloud optics\n" . + "icecldoptics = $icecldoptics\n"; + } + } } # Volcanic Aerosol Mass climatology dataset diff --git a/bld/config_files/definition.xml b/bld/config_files/definition.xml index 7f3ed23873..03f2f10405 100644 --- a/bld/config_files/definition.xml +++ b/bld/config_files/definition.xml @@ -91,9 +91,9 @@ on Mapes and Neale (2011): 0 => no, 1 => yes PBL package: uw (University of Washington), hb (Holtslag and Boville), hbr (Holtslag, Boville, and Rasch), clubb_sgs, spcam_sam1om, spcam_m2005, none. - + Radiative transfer calculation: -camrt (CAM3 and CAM4 RT package), rrtmg (RRTMG package from AER). +camrt (CAM3 and CAM4 RT package), rrtmg (RRTMG package from AER), rrtmgp (updated version). CARMA sectional microphysics: diff --git a/bld/configure b/bld/configure index 7ec8b13833..974c30dc5e 100755 --- a/bld/configure +++ b/bld/configure @@ -103,7 +103,7 @@ OPTIONS -prog_species Comma-separate list of prognostic mozart species packages. Currently available: DST,SSLT,SO4,GHG,OC,BC,CARBON16 -psubcols Maximum number of sub-columns in a run - set to 1 if not using sub-columns (default) - -rad Specify the radiation package [rrtmg | camrt] + -rad Specify the radiation package [rrtmg | rrtmgp | rrtmgp_gpu | camrt] -silhs Switch on SILHS. -spcam_clubb_sgs Turn on the SPCAM version of CLUBB -spcam_nx SPCAM x-grid. - defaults to 4 (note the CRM requires spcam_nx to be greater than or equal to 4) @@ -1069,8 +1069,16 @@ elsif ($phys_pkg =~ m/^cam[56]$|^cam_dev$|^spcam_m2005$/) { $rad_pkg = 'rrtmg'; } # Allow the user to override the default via the commandline. +my $use_rrtmgp_gpu = 0; if (defined $opts{'rad'}) { $rad_pkg = lc($opts{'rad'}); + # If the radiation package is set to rrtmgp_gpu then will add the gpu code version + # (openmp and openacc) to the Filepath file, but strip off the "_gpu" when setting + # the radiation package name in the config_cache file. + if ($rad_pkg eq 'rrtmgp_gpu') { + $use_rrtmgp_gpu = 1; + $rad_pkg = 'rrtmgp'; + } } # consistency checks... @@ -1083,13 +1091,18 @@ if ($rad_pkg eq 'camrt') { " with aerosol package $chem_pkg\n"; } } -elsif ($rad_pkg eq 'rrtmg') { +elsif ($rad_pkg =~ m/rrtmg/) { # The rrtmg package doesn't work with the CAM3 prescribed aerosols if ($phys_pkg eq 'cam3') { die "configure ERROR: radiation package: $rad_pkg is not compatible\n". " with physics package $phys_pkg\n"; } + + # RRTMGP not currently working with CARMA + if ($rad_pkg eq 'rrtmgp' and $carma_pkg ne 'none') { + die "configure ERROR: The CARMA microphysics package does not currently work with RRTMGP\n"; + } } $cfg_ref->set('rad', $rad_pkg); @@ -1125,7 +1138,7 @@ if ($phys_pkg eq 'spcam_sam1mom') { } if ($phys_pkg eq 'spcam_m2005') { - if ($rad_pkg ne 'rrtmg') { + if ($rad_pkg !~ m/rrtmg/) { die "configure ERROR: radiation package: $rad_pkg is not compatible\n". " with m2005 -- it should be rrtmg\n"; } @@ -2231,6 +2244,19 @@ sub write_filepath elsif ($rad eq 'camrt') { print $fh "$camsrcdir/src/physics/camrt\n"; } + elsif ($rad eq 'rrtmgp') { + print $fh "$camsrcdir/src/physics/rrtmgp\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/extensions\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/gas-optics\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rrtmgp-frontend\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rte-frontend\n"; + if ($use_rrtmgp_gpu) { + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rrtmgp-kernels/accel\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rte-kernels/accel\n"; + } + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rrtmgp-kernels\n"; + print $fh "$camsrcdir/src/physics/rrtmgp/ext/rte-kernels\n"; + } if ($clubb_sgs) { print $fh "$camsrcdir/src/physics/clubb/src/CLUBB_core\n"; diff --git a/bld/namelist_files/namelist_defaults_cam.xml b/bld/namelist_files/namelist_defaults_cam.xml index d2e028a388..5afa8a0155 100644 --- a/bld/namelist_files/namelist_defaults_cam.xml +++ b/bld/namelist_files/namelist_defaults_cam.xml @@ -459,6 +459,36 @@ atm/cam/physprops/ssam_rrtmg_c080918.nc atm/cam/physprops/sscm_rrtmg_c080918.nc + +atm/cam/physprops/sulfate_rrtmg_c080918.nc +atm/cam/physprops/sulfate_rrtmg_c080918.nc +atm/cam/physprops/dust1_rrtmg_c080918.nc +atm/cam/physprops/dust1_rrtmg_c080918.nc +atm/cam/physprops/dust2_rrtmg_c080918.nc +atm/cam/physprops/dust2_rrtmg_c080918.nc +atm/cam/physprops/dust3_rrtmg_c080918.nc +atm/cam/physprops/dust3_rrtmg_c080918.nc +atm/cam/physprops/dust4_rrtmg_c080918.nc +atm/cam/physprops/dust4_rrtmg_c080918.nc +atm/cam/physprops/bcpho_rrtmg_c080918.nc +atm/cam/physprops/bcpho_rrtmg_c080918.nc +atm/cam/physprops/bcphi_rrtmg_c080918.nc +atm/cam/physprops/bcphi_rrtmg_c080918.nc +atm/cam/physprops/ocpho_rrtmg_c080918.nc +atm/cam/physprops/ocpho_rrtmg_c080918.nc +atm/cam/physprops/ocphi_rrtmg_c080918.nc +atm/cam/physprops/ocphi_rrtmg_c080918.nc +atm/cam/physprops/seasalt1_rrtmg_c080918.nc +atm/cam/physprops/seasalt1_rrtmg_c080918.nc +atm/cam/physprops/seasalt2_rrtmg_c080918.nc +atm/cam/physprops/seasalt2_rrtmg_c080918.nc +atm/cam/physprops/seasalt3_rrtmg_c080918.nc +atm/cam/physprops/seasalt3_rrtmg_c080918.nc +atm/cam/physprops/seasalt4_rrtmg_c080918.nc +atm/cam/physprops/seasalt4_rrtmg_c080918.nc +atm/cam/physprops/ssam_rrtmg_c080918.nc +atm/cam/physprops/sscm_rrtmg_c080918.nc + @@ -471,6 +501,16 @@ atm/cam/physprops/dust_aeronet_rrtmg_c141106.nc atm/cam/physprops/sulfate_rrtmg_c080918.nc + +atm/cam/physprops/sulfate_rrtmg_c080918.nc +atm/cam/physprops/ocpho_rrtmg_c101112.nc +atm/cam/physprops/ocpho_rrtmg_c130709.nc +atm/cam/physprops/ocphi_rrtmg_c100508.nc +atm/cam/physprops/bcpho_rrtmg_c100508.nc +atm/cam/physprops/ssam_rrtmg_c100508.nc +atm/cam/physprops/dust_aeronet_rrtmg_c141106.nc +atm/cam/physprops/sulfate_rrtmg_c080918.nc + atm/cam/physprops/volc_camRT_byradius_sigma1.6_c130724.nc atm/cam/physprops/sulfuricacid_cam3_c080918.nc @@ -479,6 +519,12 @@ atm/cam/physprops/volc_camRRTMG_byradius_sigma1.6_mode2_c210211.nc atm/cam/physprops/volc_camRRTMG_byradius_sigma1.2_mode3_c210211.nc + +atm/cam/physprops/volc_camRRTMG_byradius_sigma1.6_c130724.nc +atm/cam/physprops/volc_camRRTMG_byradius_sigma1.6_mode1_c210211.nc +atm/cam/physprops/volc_camRRTMG_byradius_sigma1.6_mode2_c210211.nc +atm/cam/physprops/volc_camRRTMG_byradius_sigma1.2_mode3_c210211.nc + atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_c141106.nc atm/cam/physprops/mam4_mode2_rrtmg_aitkendust_c141106.nc @@ -509,6 +555,36 @@ atm/cam/physprops/mam7_mode6_rrtmg_c120904.nc atm/cam/physprops/mam7_mode7_rrtmg_c120904.nc + +atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_c141106.nc +atm/cam/physprops/mam4_mode2_rrtmg_aitkendust_c141106.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_c141106.nc + +atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_sig1.6_dgnh.48_c140304.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_sig1.2_dgnl.40_c150219.nc + +atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_c141106.nc +atm/cam/physprops/mam4_mode2_rrtmg_aitkendust_c141106.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_c141106.nc +atm/cam/physprops/mam4_mode4_rrtmg_c130628.nc + +atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_sig1.6_dgnh.48_c140304.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_sig1.2_dgnl.40_c150219.nc + +atm/cam/physprops/mam4_mode1_rrtmg_aeronetdust_sig1.6_dgnh.48_c140304.nc +atm/cam/physprops/mam4_mode2_rrtmg_aitkendust_c141106.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_c141106.nc +atm/cam/physprops/mam4_mode4_rrtmg_c130628.nc +atm/cam/physprops/mam4_mode3_rrtmg_aeronetdust_sig1.2_dgnl.40_c150219.nc + +atm/cam/physprops/mam7_mode1_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode2_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode3_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode4_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode5_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode6_rrtmg_c120904.nc +atm/cam/physprops/mam7_mode7_rrtmg_c120904.nc + atm/cam/physprops/water_refindex_rrtmg_c080910.nc .false. @@ -523,6 +599,15 @@ atm/cam/physprops/iceoptics_c080917.nc atm/cam/physprops/F_nwvl200_mu20_lam50_res64_t298_c080428.nc + +gammadist +mitchell +atm/cam/physprops/iceoptics_c080917.nc +atm/cam/physprops/F_nwvl200_mu20_lam50_res64_t298_c080428.nc + +src/physics/rrtmgp/data/rrtmgp-gas-lw-g128.nc +src/physics/rrtmgp/data/rrtmgp-gas-sw-g112.nc + atm/cam/rad/abs_ems_factors_fastvx.c030508.nc @@ -1908,7 +1993,7 @@ OFF -atm/cam/chem/trop_mam/atmsrf_ne3np4.pg3_c221214.nc +atm/cam/chem/trop_mam/atmsrf_ne3np4.pg3_221214.nc atm/cam/chem/trop_mam/atmsrf_ne5np4_110920.nc atm/cam/chem/trop_mam/atmsrf_ne5pg3_201105.nc atm/cam/chem/trop_mam/atmsrf_ne16np4_110920.nc diff --git a/bld/namelist_files/namelist_definition.xml b/bld/namelist_files/namelist_definition.xml index 14da5403a3..9d7c323b59 100644 --- a/bld/namelist_files/namelist_definition.xml +++ b/bld/namelist_files/namelist_definition.xml @@ -5467,7 +5467,7 @@ Default: Unused + group="phys_ctl_nl" valid_values="rrtmgp,rrtmg,camrt" > Type of radiation scheme employed. Default: set by build-namelist @@ -5657,6 +5657,25 @@ Switch to turn on Fixed Dynamical Heating in the offline radiation tool (PORT). Default: false + + + +Relative pathname for LW gas optics coefficients for RRTMGP. This data is +part of the RRTMGP source, thus this pathname is relative to the root source +code directory for the CAM component. +Default: set by build-namelist. + + + +Relative pathname for SW gas optics coefficients for RRTMGP. This data is +part of the RRTMGP source, thus this pathname is relative to the root source +code directory for the CAM component. +Default: set by build-namelist. + + + + + + + +atm/cam/solar/SOLAR_SPECTRAL_Lean_1610-2008_annual_c090324.nc +18500101 +FIXED + + +284.7e-6 +791.6e-9 +275.68e-9 +12.48e-12 +0.0 + + +atm/cam/ozone +ozone_1.9x2.5_L26_1850clim_c090420.nc +O3 +CYCLICAL +1850 + + +CYCLICAL +atm/cam/chem/trop_mozart_aero/emis/aerocom_mam3_dms_surf_2000_c090129.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so2_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_soag_1.5_surf_1850_c100217.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_bc_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_oc_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so4_a1_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so4_a2_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_num_a1_surf_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_num_a2_surf_1850_c090726.nc + + +CYCLICAL +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so2_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so4_a1_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_so4_a2_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_num_a1_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_num_a2_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_oc_elev_1850_c090726.nc +atm/cam/chem/trop_mozart_aero/emis/ar5_mam3_bc_elev_1850_c090726.nc + + +CYCLICAL +1850 +oxid_1.9x2.5_L26_1850clim_c091123.nc + + +1850 + + diff --git a/cime_config/testdefs/testlist_cam.xml b/cime_config/testdefs/testlist_cam.xml index 4b6647697d..62cd0af626 100644 --- a/cime_config/testdefs/testlist_cam.xml +++ b/cime_config/testdefs/testlist_cam.xml @@ -215,6 +215,15 @@ + + + + + + + + + @@ -1791,6 +1800,15 @@ + + + + + + + + + @@ -1800,6 +1818,15 @@ + + + + + + + + + @@ -2753,6 +2780,15 @@ + + + + + + + + + @@ -2841,6 +2877,15 @@ + + + + + + + + + diff --git a/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/shell_commands b/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/shell_commands new file mode 100644 index 0000000000..106897a2c6 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/shell_commands @@ -0,0 +1,3 @@ +./xmlchange --append CAM_CONFIG_OPTS="-rad rrtmgp" +./xmlchange ROF_NCPL=\$ATM_NCPL +./xmlchange GLC_NCPL=\$ATM_NCPL diff --git a/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/user_nl_cam b/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/user_nl_cam new file mode 100644 index 0000000000..fcbd0d438b --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/user_nl_cam @@ -0,0 +1,15 @@ + offline_driver_infile = '$DIN_LOC_ROOT/atm/cam/port/base_cam6_3mode_1deg.doubleCO2.cam.h1.0001-01-01-00000_c170526.nc' + rad_data_fdh = .true. + empty_htapes = .true. + avgflag_pertape = 'A','I' + fincl1 = 'SOLIN', 'QRS', 'FSNS', 'FSNT','FSNSC', 'FSDSC','FSNR','FLNR', + 'FSNTOA', 'FSUTOA', 'FSNTOAC', 'FSNTC', 'FSDSC', 'FSDS', 'SWCF', + 'QRL', 'FLNS', 'FLDS', 'FLNT', 'LWCF', 'FLUT' ,'FLUTC', 'FLNTC', + 'FLNSC', 'FLDSC' + fincl2 = 'SOLIN', 'QRS', 'FSNS', 'FSNT','FSNSC', 'FSDSC','FSNR','FLNR', + 'FSNTOA', 'FSUTOA', 'FSNTOAC', 'FSNTC', 'FSDSC', 'FSDS', 'SWCF', + 'QRL', 'FLNS', 'FLDS', 'FLNT', 'LWCF', 'FLUT' ,'FLUTC', 'FLNTC', + 'FLNSC', 'FLDSC' + rad_data_output = .false. + mfilt=100,100 + nhtfrq=-120,73 diff --git a/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/shell_commands b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/shell_commands new file mode 100644 index 0000000000..106897a2c6 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/shell_commands @@ -0,0 +1,3 @@ +./xmlchange --append CAM_CONFIG_OPTS="-rad rrtmgp" +./xmlchange ROF_NCPL=\$ATM_NCPL +./xmlchange GLC_NCPL=\$ATM_NCPL diff --git a/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_cam b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_cam new file mode 100644 index 0000000000..8482082dce --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_cam @@ -0,0 +1,4 @@ +mfilt=1,1,1,1,1,1 +ndens=1,1,1,1,1,1 +nhtfrq=9,9,9,9,9,9 +inithist='ENDOFRUN' diff --git a/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_clm b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_clm new file mode 100644 index 0000000000..0d83b5367b --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_clm @@ -0,0 +1,27 @@ +!---------------------------------------------------------------------------------- +! Users should add all user specific namelist changes below in the form of +! namelist_var = new_namelist_value +! +! Include namelist variables for drv_flds_in ONLY if -megan and/or -drydep options +! are set in the CLM_NAMELIST_OPTS env variable. +! +! EXCEPTIONS: +! Set use_cndv by the compset you use and the CLM_BLDNML_OPTS -dynamic_vegetation setting +! Set use_vichydro by the compset you use and the CLM_BLDNML_OPTS -vichydro setting +! Set use_cn by the compset you use and CLM_BLDNML_OPTS -bgc setting +! Set use_crop by the compset you use and CLM_BLDNML_OPTS -crop setting +! Set spinup_state by the CLM_BLDNML_OPTS -bgc_spinup setting +! Set irrigate by the CLM_BLDNML_OPTS -irrig setting +! Set dtime with L_NCPL option +! Set fatmlndfrc with LND_DOMAIN_PATH/LND_DOMAIN_FILE options +! Set finidat with RUN_REFCASE/RUN_REFDATE/RUN_REFTOD options for hybrid or branch cases +! (includes $inst_string for multi-ensemble cases) +! Set glc_grid with CISM_GRID option +! Set glc_smb with GLC_SMB option +! Set maxpatch_glcmec with GLC_NEC option +! Set glc_do_dynglacier with GLC_TWO_WAY_COUPLING env variable +!---------------------------------------------------------------------------------- +hist_nhtfrq = 9 +hist_mfilt = 1 +hist_ndens = 1 + diff --git a/doc/ChangeLog b/doc/ChangeLog index 9d40f7ba63..5ea328d2c8 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,5 +1,208 @@ =============================================================== +Tag name: cam6_3_148 +Originator(s): brianpm, courtneyp, eaton +Date: Wed 21 Feb 2024 +One-line Summary: Provide RRTMGP as a radiation parameterization +Github PR URL: https://github.com/ESCOMP/CAM/pull/909 + +Purpose of changes (include the issue number and title text for each relevant GitHub issue): + +#255 - Provide RRTMGP as a radiation parameterization +https://github.com/ESCOMP/CAM/issues/255 + +Miscellaneous: +. The 1850_cam5.xml use case file was added back to the source code to + facilitate running the F1850 compset with CAM5. That discussion is in + issue #393. + +Describe any changes made to build system: +. '-rad' argument to configure accepts the values 'rrtmgp' and 'rrtmgp_gpu' + to build the RRTMGP code for CPUs or for GPUs. + +Describe any changes made to the namelist: +. add variables rrtmgp_coefs_lw_file and rrtmgp_coefs_sw_file to contain + filepaths for the RRTMGP coefficients files. + +List any changes to the defaults for the boundary datasets: none + +Describe any substantial timing or memory changes: +. performance evaluation of RRTMGP has not yet been done. + +Code reviewed by: nusbaume, cacraigucar, sjsprecious + +List all files eliminated: + +src/physics/rrtmg/cloud_rad_props.F90 +src/physics/rrtmg/ebert_curry.F90 +src/physics/rrtmg/oldcloud.F90 +src/physics/rrtmg/slingo.F90 +. these cloud optics files which can be shared by rrtmg and rrtmgp are + moved to src/physics/cam + +List all files added and what they do: + +bld/namelist_files/use_cases/1850_cam5.xml +. use case file for 1850 cam5 physics + +cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/shell_commands +cime_config/testdefs/testmods_dirs/cam/cam6_port_f09_rrtmgp/user_nl_cam +cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/shell_commands +cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_cam +cime_config/testdefs/testmods_dirs/cam/outfrq9s_rrtmgp/user_nl_clm +. for adding RRTMGP to tests + +src/physics/cam/cloud_rad_props.F90 +src/physics/cam/ebert_curry_ice_optics.F90 +src/physics/cam/oldcloud_optics.F90 +src/physics/cam/slingo_liq_optics.F90 +. these 4 files are shared cloud optics code moved here from + src/physics/rrtmg/ with the following name changes: + - ebert_curry.F90 -> ebert_curry_ice_optics.F90 + - oldcloud.F90 -> oldcloud_optics.F90 + - slingo.F90 -> slingo_liq_optics.F90 +. remove unused code, cleanup unused vars, improve endrun messages +. module names changed to match file names. + +src/physics/rrtmgp/mcica_subcol_gen.F90 +src/physics/rrtmgp/radconstants.F90 +src/physics/rrtmgp/radiation.F90 +src/physics/rrtmgp/rrtmgp_inputs.F90 +. CAM interface code for RRTMGP. + +List all existing files that have been modified, and describe the changes: + +.gitignore +. add directories src/physics/rrtmgp/{data,ext} + +Externals_CAM.cfg +. add external definition for rte-rrtmgp source +. add external definition for rrtmgp data + +bld/build-namelist +. set the correct filepaths for the coefficient datasets which are checked + out in the source code directory tree. +. generalize logic to include both rrtmgp and rrtmg when appropriate +. add error check for old cloud optics no longer supported + +bld/config_files/definition.xml +. add 'rrtmgp' as valid value for 'rad' configure option + +bld/configure +. add rrtmgp and rrtmgp_gpu as valid values for '-rad' argument. +. '-rad rrtmgp_gpu' sets a flag used to add the filepaths for the GPU code + versions to the Filepath file. The '_gpu' suffix is removed before + setting the parameter value for 'rad' in the config_cache.xml file. +. check to disallow CARMA + RRTMGP + +bld/namelist_files/namelist_defaults_cam.xml +. the aersol and cloud optics datasets for RRTMG are being reused for + RRTMGP for now + +bld/namelist_files/namelist_definition.xml +. add 'rrtmgp' as valid value for 'radiation_scheme' +. add variables rrtmgp_coefs_lw_file and rrtmgp_coefs_sw_file to contain + filepaths for the RRTMGP coefficients files. + +cime_config/testdefs/testlist_cam.xml +. add aux_cam tests: + ERP_D_Ln9.ne30pg3_ne30pg3_mg17.FLTHIST.derecho_intel.cam-outfrq9s_rrtmgp + SMS_Ln9.ne30pg3_ne30pg3_mg17.FW2000climo.derecho_intel.cam-outfrq9s_rrtmgp + ERP_D_Ln9.ne5pg3_ne5pg3_mg37.QPC6.izumi_nag.cam-outfrq9s_rrtmgp + SMS_Ld5.f09_f09_mg17.PC6.izumi_gnu.cam-cam6_port_f09_rrtmgp +. add prealpha test: + SMS_D_Ln9.ne30pg3_ne30pg3_mg17.FMTHIST.derecho_intel.cam-outfrq9s_rrtmgp + +src/chemistry/utils/solar_data.F90 +. add solar_htng_spctrl_scl to log file output + +src/physics/cam/aer_rad_props.F90 +. nrh, ot_length now accessed from phys_prop + +src/physics/cam/aerosol_optics_cam.F90 +. ot_length now accessed from phys_prop + +src/physics/cam/phys_prop.F90 +. add the public parameter nrh to this module. Was previously in + radconstants. +. turn off old debug output to log file + +src/physics/cam/physpkg.F90 +. reorder initialization of solar_data and radiation modules to allow + reading the spectral band boundaries from the input data rather than + requiring them to be hardcoded. + +src/physics/cam/rad_constituents.F90 +. access ot_length from phys_prop rather than rad_constituents + +src/physics/cam_dev/physpkg.F90 +. reorder initialization of solar_data and radiation modules to allow + reading the wavenumber band boundaries from the input data rather than + requiring them to be hardcoded. + +src/physics/camrt/radconstants.F90 +. parameters ot_length and nrh moved to phys_props + +src/physics/rrtmg/radconstants.F90 +. parameters ot_length and nrh moved to phys_props + +src/physics/rrtmg/radiation.F90 +. ebert_curry -> ebert_curry_ice_optics + +src/physics/simple/radconstants.F90 +. parameters ot_length and nrh moved to phys_props +. add dummy interface for get_sw_spectral_boundaries + +src/physics/spcam/crm/CLUBB/crmx_mt95.f90 +. removed 3 non-ascii characters (in comments) + +test/system/TR8.sh +. add checks for rrtmgp interface code + +If there were any failures reported from running test_driver.sh on any test +platform, and checkin with these failures has been OK'd by the gatekeeper, +then copy the lines from the td.*.status files for the failed tests to the +appropriate machine below. All failed tests must be justified. + +derecho/intel/aux_cam: + + ERP_Ln9_Vnuopc.C96_C96_mg17.F2000climo.derecho_intel.cam-outfrq9s_mg3 (Overall: PEND) details: + ERP_Ln9_Vnuopc.f09_f09_mg17.FCSD_HCO.derecho_intel.cam-outfrq9s (Overall: FAIL) details: + SMS_Ld1_Vnuopc.f09_f09_mg17.FCHIST_GC.derecho_intel.cam-outfrq1d (Overall: PEND) details: +-- pre-existing failures + + ERP_D_Ln9.ne30pg3_ne30pg3_mg17.FLTHIST.derecho_intel.cam-outfrq9s_rrtmgp (Overall: DIFF) details: + FAIL ERP_D_Ln9.ne30pg3_ne30pg3_mg17.FLTHIST.derecho_intel.cam-outfrq9s_rrtmgp NLCOMP + FAIL ERP_D_Ln9.ne30pg3_ne30pg3_mg17.FLTHIST.derecho_intel.cam-outfrq9s_rrtmgp BASELINE /glade/campaign/cesm/community/amwg/cam_baselines/cam6_3_147: ERROR BFAIL baseline directory '/glade/campaign/cesm/community/amwg/cam_baselines/cam6_3_147/ERP_D_Ln9.ne30pg3_ne30pg3_mg17.FLTHIST.derecho_intel.cam-outfrq9s_rrtmgp' does not exist + SMS_Ln9.ne30pg3_ne30pg3_mg17.FW2000climo.derecho_intel.cam-outfrq9s_rrtmgp (Overall: DIFF) details: + FAIL SMS_Ln9.ne30pg3_ne30pg3_mg17.FW2000climo.derecho_intel.cam-outfrq9s_rrtmgp NLCOMP + FAIL SMS_Ln9.ne30pg3_ne30pg3_mg17.FW2000climo.derecho_intel.cam-outfrq9s_rrtmgp BASELINE /glade/campaign/cesm/community/amwg/cam_baselines/cam6_3_147: ERROR BFAIL baseline directory '/glade/campaign/cesm/community/amwg/cam_baselines/cam6_3_147/SMS_Ln9.ne30pg3_ne30pg3_mg17.FW2000climo.derecho_intel.cam-outfrq9s_rrtmgp' does not exist +-- expected diffs - no baselines for new tests + +izumi/nag/aux_cam: + + DAE_Vnuopc.f45_f45_mg37.FHS94.izumi_nag.cam-dae (Overall: FAIL) details: +-- pre-existing failure + +izumi/gnu/aux_cam: + + ERP_D_Ln9.ne3pg3_ne3pg3_mg37.QPC6.izumi_gnu.cam-outfrq9s_rrtmgp (Overall: DIFF) details: + FAIL ERP_D_Ln9.ne3pg3_ne3pg3_mg37.QPC6.izumi_gnu.cam-outfrq9s_rrtmgp NLCOMP + FAIL ERP_D_Ln9.ne3pg3_ne3pg3_mg37.QPC6.izumi_gnu.cam-outfrq9s_rrtmgp BASELINE /fs/cgd/csm/models/atm/cam/pretag_bl/cam6_3_147_gnu: ERROR BFAIL baseline directory '/fs/cgd/csm/models/atm/cam/pretag_bl/cam6_3_147_gnu/ERP_D_Ln9.ne3pg3_ne3pg3_mg37.QPC6.izumi_gnu.cam-outfrq9s_rrtmgp' does not exist + SMS_Ld5.f09_f09_mg17.PC6.izumi_gnu.cam-cam6_port_f09_rrtmgp (Overall: DIFF) details: + FAIL SMS_Ld5.f09_f09_mg17.PC6.izumi_gnu.cam-cam6_port_f09_rrtmgp NLCOMP + FAIL SMS_Ld5.f09_f09_mg17.PC6.izumi_gnu.cam-cam6_port_f09_rrtmgp BASELINE /fs/cgd/csm/models/atm/cam/pretag_bl/cam6_3_147_gnu: ERROR BFAIL baseline directory '/fs/cgd/csm/models/atm/cam/pretag_bl/cam6_3_147_gnu/SMS_Ld5.f09_f09_mg17.PC6.izumi_gnu.cam-cam6_port_f09_rrtmgp' does not exist +-- expected diffs - no baselines for new tests + +CAM tag used for the baseline comparison tests if different than previous +tag: + +Summarize any changes to answers: None. + New RRTMGP option changes answers only when enabled. + +=============================================================== +=============================================================== + Tag name: cam6_3_147 Originator(s): lizziel, jimmielin, fritzt Date: 2 Feb 2024 diff --git a/src/chemistry/utils/solar_data.F90 b/src/chemistry/utils/solar_data.F90 index da18fbc777..51ad7ad82b 100644 --- a/src/chemistry/utils/solar_data.F90 +++ b/src/chemistry/utils/solar_data.F90 @@ -91,6 +91,7 @@ subroutine solar_data_readnl( nlfile ) write(iulog,*) 'solar_data_readnl: solar_data_type = ',trim(solar_data_type) write(iulog,*) 'solar_data_readnl: solar_data_ymd = ',solar_data_ymd write(iulog,*) 'solar_data_readnl: solar_data_tod = ',solar_data_tod + write(iulog,*) 'solar_data_readnl: solar_htng_spctrl_scl = ',solar_htng_spctrl_scl endif solar_parms_on = solar_parms_data_file.ne.'NONE' diff --git a/src/physics/cam/aer_rad_props.F90 b/src/physics/cam/aer_rad_props.F90 index 3d46fe9ba8..08dced5a93 100644 --- a/src/physics/cam/aer_rad_props.F90 +++ b/src/physics/cam/aer_rad_props.F90 @@ -11,7 +11,8 @@ module aer_rad_props use physics_types, only: physics_state use physics_buffer, only: physics_buffer_desc -use radconstants, only: nrh, nswbands, nlwbands, idx_sw_diag, ot_length +use radconstants, only: nswbands, nlwbands, idx_sw_diag +use phys_prop, only: nrh, ot_length use rad_constituents, only: rad_cnst_get_info, rad_cnst_get_aer_mmr, & rad_cnst_get_aer_props use wv_saturation, only: qsat @@ -129,7 +130,7 @@ subroutine aer_rad_props_sw(list_idx, state, pbuf, nnite, idxnite, & real(r8), intent(out) :: tau (pcols,0:pver,nswbands) ! aerosol extinction optical depth real(r8), intent(out) :: tau_w (pcols,0:pver,nswbands) ! aerosol single scattering albedo * tau - real(r8), intent(out) :: tau_w_g(pcols,0:pver,nswbands) ! aerosol assymetry parameter * tau * w + real(r8), intent(out) :: tau_w_g(pcols,0:pver,nswbands) ! aerosol asymmetry parameter * tau * w real(r8), intent(out) :: tau_w_f(pcols,0:pver,nswbands) ! aerosol forward scattered fraction * tau * w ! Local variables @@ -308,9 +309,6 @@ end subroutine aer_rad_props_sw subroutine aer_rad_props_lw(list_idx, state, pbuf, odap_aer) - use radconstants, only: ot_length - - use physics_buffer, only : pbuf_get_field, pbuf_get_index, physics_buffer_desc ! Purpose: Compute aerosol transmissions needed in absorptivity/ ! emissivity calculations @@ -318,6 +316,8 @@ subroutine aer_rad_props_lw(list_idx, state, pbuf, odap_aer) ! species. If this changes, this routine will need to do something ! similar to the sw with routines like get_hygro_lw_abs + use physics_buffer, only : pbuf_get_field, pbuf_get_index, physics_buffer_desc + ! Arguments integer, intent(in) :: list_idx ! index of the climate or a diagnostic list type(physics_state), intent(in), target :: state diff --git a/src/physics/cam/aerosol_optics_cam.F90 b/src/physics/cam/aerosol_optics_cam.F90 index eb094446c8..a81e1d4701 100644 --- a/src/physics/cam/aerosol_optics_cam.F90 +++ b/src/physics/cam/aerosol_optics_cam.F90 @@ -3,7 +3,8 @@ module aerosol_optics_cam use shr_kind_mod, only: cl => shr_kind_cl use cam_logfile, only: iulog use radconstants, only: nswbands, nlwbands, idx_sw_diag, idx_uv_diag, idx_nir_diag - use radconstants, only: ot_length, get_lw_spectral_boundaries + use radconstants, only: get_lw_spectral_boundaries + use phys_prop, only: ot_length use physics_types,only: physics_state use physics_buffer,only: physics_buffer_desc use ppgrid, only: pcols, pver diff --git a/src/physics/rrtmg/cloud_rad_props.F90 b/src/physics/cam/cloud_rad_props.F90 similarity index 64% rename from src/physics/rrtmg/cloud_rad_props.F90 rename to src/physics/cam/cloud_rad_props.F90 index 2911e0ac21..257138e7b5 100644 --- a/src/physics/rrtmg/cloud_rad_props.F90 +++ b/src/physics/cam/cloud_rad_props.F90 @@ -7,16 +7,21 @@ module cloud_rad_props use ppgrid, only: pcols, pver, pverp use physics_types, only: physics_state use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_get_field, pbuf_old_tim_idx -use radconstants, only: nswbands, nlwbands, idx_sw_diag, ot_length, idx_lw_diag -use cam_abortutils, only: endrun +use constituents, only: cnst_get_ind +use radconstants, only: nswbands, nlwbands, idx_sw_diag use rad_constituents, only: iceopticsfile, liqopticsfile -use oldcloud, only: oldcloud_lw, old_liq_get_rad_props_lw, old_ice_get_rad_props_lw, oldcloud_init +use oldcloud_optics, only: oldcloud_init, oldcloud_lw, & + old_liq_get_rad_props_lw, old_ice_get_rad_props_lw + +use slingo_liq_optics, only: slingo_rad_props_init +use ebert_curry_ice_optics, only: ec_rad_props_init, scalefactor + +use interpolate_data, only: interp_type, lininterp_init, lininterp, & + extrap_method_bndry, lininterp_finish -use ebert_curry, only: scalefactor use cam_logfile, only: iulog +use cam_abortutils, only: endrun -use interpolate_data, only: interp_type, lininterp_init, lininterp, & - extrap_method_bndry, lininterp_finish implicit none private @@ -24,16 +29,15 @@ module cloud_rad_props public :: & cloud_rad_props_init, & - cloud_rad_props_get_sw, & ! return SW optical props of total bulk aerosols - cloud_rad_props_get_lw, & ! return LW optical props of total bulk aerosols + cloud_rad_props_get_lw, & ! return LW optical props for old cloud optics get_ice_optics_sw, & ! return Mitchell SW ice radiative properties - ice_cloud_get_rad_props_lw, & ! Mitchell LW ice rad props - get_liquid_optics_sw, & ! return Conley SW rad props - liquid_cloud_get_rad_props_lw, & ! return Conley LW rad props - grau_cloud_get_rad_props_lw, & - get_grau_optics_sw, & + ice_cloud_get_rad_props_lw, & ! return Mitchell LW ice radiative properties + get_liquid_optics_sw, & ! return Conley SW radiative properties + liquid_cloud_get_rad_props_lw, & ! return Conley LW radiative properties + get_snow_optics_sw, & snow_cloud_get_rad_props_lw, & - get_snow_optics_sw + get_grau_optics_sw, & + grau_cloud_get_rad_props_lw integer :: nmu, nlambda @@ -51,24 +55,23 @@ module cloud_rad_props real(r8), allocatable :: asm_sw_ice(:,:) real(r8), allocatable :: abs_lw_ice(:,:) -! ! indexes into pbuf for optical parameters of MG clouds -! - integer :: i_dei=0 - integer :: i_mu=0 - integer :: i_lambda=0 - integer :: i_iciwp=0 - integer :: i_iclwp=0 - integer :: i_des=0 - integer :: i_icswp=0 - integer :: i_degrau=0 - integer :: i_icgrauwp=0 +integer :: i_dei=0 +integer :: i_mu=0 +integer :: i_lambda=0 +integer :: i_iciwp=0 +integer :: i_iclwp=0 +integer :: i_des=0 +integer :: i_icswp=0 +integer :: i_degrau=0 +integer :: i_icgrauwp=0 ! indexes into constituents for old optics - integer :: & - ixcldice, & ! cloud ice water index - ixcldliq ! cloud liquid water index +integer :: & + ixcldice, & ! cloud ice water index + ixcldliq ! cloud liquid water index +real(r8), parameter :: tiny = 1.e-80_r8 !============================================================================== contains @@ -83,9 +86,6 @@ subroutine cloud_rad_props_init() #if ( defined SPMD ) use mpishorthand #endif - use constituents, only: cnst_get_ind - use slingo, only: slingo_rad_props_init - use ebert_curry, only: ec_rad_props_init, scalefactor character(len=256) :: liquidfile character(len=256) :: icefile @@ -102,6 +102,7 @@ subroutine cloud_rad_props_init() integer :: d_id, ext_sw_ice_id, ssa_sw_ice_id, asm_sw_ice_id, abs_lw_ice_id integer :: err + character(len=*), parameter :: sub = 'cloud_rad_props_init' liquidfile = liqopticsfile icefile = iceopticsfile @@ -125,25 +126,25 @@ subroutine cloud_rad_props_init() call cnst_get_ind('CLDLIQ', ixcldliq) ! read liquid cloud optics - if(masterproc) then - call getfil( trim(liquidfile), locfn, 0) - call handle_ncerr( nf90_open(locfn, NF90_NOWRITE, ncid), 'liquid optics file missing') - write(iulog,*)' reading liquid cloud optics from file ',locfn + if (masterproc) then + call getfil( trim(liquidfile), locfn, 0) + call handle_ncerr( nf90_open(locfn, NF90_NOWRITE, ncid), 'liquid optics file missing') + write(iulog,*)' reading liquid cloud optics from file ',locfn - call handle_ncerr(nf90_inq_dimid( ncid, 'lw_band', dimid), 'getting lw_band dim') - call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nlwbands), 'getting n lw bands') - if (f_nlwbands /= nlwbands) call endrun('number of lw bands does not match') + call handle_ncerr(nf90_inq_dimid( ncid, 'lw_band', dimid), 'getting lw_band dim') + call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nlwbands), 'getting n lw bands') + if (f_nlwbands /= nlwbands) call endrun(sub//': number of lw bands does not match') - call handle_ncerr(nf90_inq_dimid( ncid, 'sw_band', dimid), 'getting sw_band_dim') - call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nswbands), 'getting n sw bands') - if (f_nswbands /= nswbands) call endrun('number of sw bands does not match') + call handle_ncerr(nf90_inq_dimid( ncid, 'sw_band', dimid), 'getting sw_band_dim') + call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nswbands), 'getting n sw bands') + if (f_nswbands /= nswbands) call endrun(sub//': number of sw bands does not match') - call handle_ncerr(nf90_inq_dimid( ncid, 'mu', mudimid), 'getting mu dim') - call handle_ncerr(nf90_inquire_dimension( ncid, mudimid, len=nmu), 'getting n mu samples') + call handle_ncerr(nf90_inq_dimid( ncid, 'mu', mudimid), 'getting mu dim') + call handle_ncerr(nf90_inquire_dimension( ncid, mudimid, len=nmu), 'getting n mu samples') - call handle_ncerr(nf90_inq_dimid( ncid, 'lambda_scale', lambdadimid), 'getting lambda dim') - call handle_ncerr(nf90_inquire_dimension( ncid, lambdadimid, len=nlambda), 'getting n lambda samples') - endif ! if (masterproc) + call handle_ncerr(nf90_inq_dimid( ncid, 'lambda_scale', lambdadimid), 'getting lambda dim') + call handle_ncerr(nf90_inquire_dimension( ncid, lambdadimid, len=nlambda), 'getting n lambda samples') + end if ! if (masterproc) #if ( defined SPMD ) call mpibcast(nmu, 1, mpiint, 0, mpicom, ierr) @@ -157,39 +158,39 @@ subroutine cloud_rad_props_init() allocate(asm_sw_liq(nmu,nlambda,nswbands)) allocate(abs_lw_liq(nmu,nlambda,nlwbands)) - if(masterproc) then - call handle_ncerr( nf90_inq_varid(ncid, 'mu', mu_id),& - 'cloud optics mu get') - call handle_ncerr( nf90_get_var(ncid, mu_id, g_mu),& - 'read cloud optics mu values') + if (masterproc) then + call handle_ncerr( nf90_inq_varid(ncid, 'mu', mu_id),& + 'cloud optics mu get') + call handle_ncerr( nf90_get_var(ncid, mu_id, g_mu),& + 'read cloud optics mu values') - call handle_ncerr( nf90_inq_varid(ncid, 'lambda', lambda_id),& - 'cloud optics lambda get') - call handle_ncerr( nf90_get_var(ncid, lambda_id, g_lambda),& - 'read cloud optics lambda values') + call handle_ncerr( nf90_inq_varid(ncid, 'lambda', lambda_id),& + 'cloud optics lambda get') + call handle_ncerr( nf90_get_var(ncid, lambda_id, g_lambda),& + 'read cloud optics lambda values') - call handle_ncerr( nf90_inq_varid(ncid, 'k_ext_sw', ext_sw_liq_id),& - 'cloud optics ext_sw_liq get') - call handle_ncerr( nf90_get_var(ncid, ext_sw_liq_id, ext_sw_liq),& - 'read cloud optics ext_sw_liq values') + call handle_ncerr( nf90_inq_varid(ncid, 'k_ext_sw', ext_sw_liq_id),& + 'cloud optics ext_sw_liq get') + call handle_ncerr( nf90_get_var(ncid, ext_sw_liq_id, ext_sw_liq),& + 'read cloud optics ext_sw_liq values') - call handle_ncerr( nf90_inq_varid(ncid, 'ssa_sw', ssa_sw_liq_id),& - 'cloud optics ssa_sw_liq get') - call handle_ncerr( nf90_get_var(ncid, ssa_sw_liq_id, ssa_sw_liq),& - 'read cloud optics ssa_sw_liq values') + call handle_ncerr( nf90_inq_varid(ncid, 'ssa_sw', ssa_sw_liq_id),& + 'cloud optics ssa_sw_liq get') + call handle_ncerr( nf90_get_var(ncid, ssa_sw_liq_id, ssa_sw_liq),& + 'read cloud optics ssa_sw_liq values') - call handle_ncerr( nf90_inq_varid(ncid, 'asm_sw', asm_sw_liq_id),& - 'cloud optics asm_sw_liq get') - call handle_ncerr( nf90_get_var(ncid, asm_sw_liq_id, asm_sw_liq),& - 'read cloud optics asm_sw_liq values') + call handle_ncerr( nf90_inq_varid(ncid, 'asm_sw', asm_sw_liq_id),& + 'cloud optics asm_sw_liq get') + call handle_ncerr( nf90_get_var(ncid, asm_sw_liq_id, asm_sw_liq),& + 'read cloud optics asm_sw_liq values') - call handle_ncerr( nf90_inq_varid(ncid, 'k_abs_lw', abs_lw_liq_id),& - 'cloud optics abs_lw_liq get') - call handle_ncerr( nf90_get_var(ncid, abs_lw_liq_id, abs_lw_liq),& - 'read cloud optics abs_lw_liq values') + call handle_ncerr( nf90_inq_varid(ncid, 'k_abs_lw', abs_lw_liq_id),& + 'cloud optics abs_lw_liq get') + call handle_ncerr( nf90_get_var(ncid, abs_lw_liq_id, abs_lw_liq),& + 'read cloud optics abs_lw_liq values') - call handle_ncerr( nf90_close(ncid), 'liquid optics file missing') - endif ! if masterproc + call handle_ncerr( nf90_close(ncid), 'liquid optics file missing') + end if ! if masterproc #if ( defined SPMD ) call mpibcast(g_mu, nmu, mpir8, 0, mpicom, ierr) @@ -199,28 +200,28 @@ subroutine cloud_rad_props_init() call mpibcast(asm_sw_liq, nmu*nlambda*nswbands, mpir8, 0, mpicom, ierr) call mpibcast(abs_lw_liq, nmu*nlambda*nlwbands, mpir8, 0, mpicom, ierr) #endif - ! I forgot to convert kext from m^2/Volume to m^2/Kg + ! Convert kext from m^2/Volume to m^2/Kg ext_sw_liq = ext_sw_liq / 0.9970449e3_r8 abs_lw_liq = abs_lw_liq / 0.9970449e3_r8 ! read ice cloud optics - if(masterproc) then - call getfil( trim(icefile), locfn, 0) - call handle_ncerr( nf90_open(locfn, NF90_NOWRITE, ncid), 'ice optics file missing') - write(iulog,*)' reading ice cloud optics from file ',locfn - - call handle_ncerr(nf90_inq_dimid( ncid, 'lw_band', dimid), 'getting lw_band dim') - call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nlwbands), 'getting n lw bands') - if (f_nlwbands /= nlwbands) call endrun('number of lw bands does not match') - - call handle_ncerr(nf90_inq_dimid( ncid, 'sw_band', dimid), 'getting sw_band_dim') - call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nswbands), 'getting n sw bands') - if (f_nswbands /= nswbands) call endrun('number of sw bands does not match') - - call handle_ncerr(nf90_inq_dimid( ncid, 'd_eff', d_dimid), 'getting deff dim') - call handle_ncerr(nf90_inquire_dimension( ncid, d_dimid, len=n_g_d), 'getting n deff samples') - - endif ! if (masterproc) + if (masterproc) then + call getfil( trim(icefile), locfn, 0) + call handle_ncerr( nf90_open(locfn, NF90_NOWRITE, ncid), 'ice optics file missing') + write(iulog,*)' reading ice cloud optics from file ',locfn + call handle_ncerr(nf90_inq_dimid( ncid, 'lw_band', dimid), 'getting lw_band dim') + call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nlwbands), 'getting n lw bands') + if (f_nlwbands /= nlwbands) then + call endrun(sub//': number of lw bands does not match') + end if + call handle_ncerr(nf90_inq_dimid( ncid, 'sw_band', dimid), 'getting sw_band_dim') + call handle_ncerr(nf90_inquire_dimension( ncid, dimid, len=f_nswbands), 'getting n sw bands') + if (f_nswbands /= nswbands) then + call endrun(sub//': number of sw bands does not match') + end if + call handle_ncerr(nf90_inq_dimid( ncid, 'd_eff', d_dimid), 'getting deff dim') + call handle_ncerr(nf90_inquire_dimension( ncid, d_dimid, len=n_g_d), 'getting n deff samples') + end if ! if (masterproc) #if ( defined SPMD ) call mpibcast(n_g_d, 1, mpiint, 0, mpicom, ierr) @@ -234,175 +235,83 @@ subroutine cloud_rad_props_init() allocate(asm_sw_ice(n_g_d,nswbands)) allocate(abs_lw_ice(n_g_d,nlwbands)) - if(masterproc) then - call handle_ncerr( nf90_inq_varid(ncid, 'd_eff', d_id),& - 'cloud optics deff get') - call handle_ncerr( nf90_get_var(ncid, d_id, g_d_eff),& - 'read cloud optics deff values') - - call handle_ncerr( nf90_inq_varid(ncid, 'sw_ext', ext_sw_ice_id),& - 'cloud optics ext_sw_ice get') - call handle_ncerr(nf90_inquire_variable ( ncid, ext_sw_ice_id, ndims=ndims, dimids=vdimids),& - 'checking dimensions of ext_sw_ice') - call handle_ncerr(nf90_inquire_dimension( ncid, vdimids(1), len=templen),& - 'getting first dimension sw_ext') - !write(iulog,*) 'expected length',n_g_d,'actual len',templen - call handle_ncerr(nf90_inquire_dimension( ncid, vdimids(2), len=templen),& - 'getting first dimension sw_ext') - !write(iulog,*) 'expected length',nswbands,'actual len',templen - call handle_ncerr( nf90_get_var(ncid, ext_sw_ice_id, ext_sw_ice),& - 'read cloud optics ext_sw_ice values') - - call handle_ncerr( nf90_inq_varid(ncid, 'sw_ssa', ssa_sw_ice_id),& - 'cloud optics ssa_sw_ice get') - call handle_ncerr( nf90_get_var(ncid, ssa_sw_ice_id, ssa_sw_ice),& - 'read cloud optics ssa_sw_ice values') - - call handle_ncerr( nf90_inq_varid(ncid, 'sw_asm', asm_sw_ice_id),& - 'cloud optics asm_sw_ice get') - call handle_ncerr( nf90_get_var(ncid, asm_sw_ice_id, asm_sw_ice),& - 'read cloud optics asm_sw_ice values') - - call handle_ncerr( nf90_inq_varid(ncid, 'lw_abs', abs_lw_ice_id),& - 'cloud optics abs_lw_ice get') - call handle_ncerr( nf90_get_var(ncid, abs_lw_ice_id, abs_lw_ice),& - 'read cloud optics abs_lw_ice values') - - call handle_ncerr( nf90_close(ncid), 'ice optics file missing') - - endif ! if masterproc + if (masterproc) then + call handle_ncerr( nf90_inq_varid(ncid, 'd_eff', d_id),& + 'cloud optics deff get') + call handle_ncerr( nf90_get_var(ncid, d_id, g_d_eff),& + 'read cloud optics deff values') + + call handle_ncerr( nf90_inq_varid(ncid, 'sw_ext', ext_sw_ice_id),& + 'cloud optics ext_sw_ice get') + call handle_ncerr(nf90_inquire_variable ( ncid, ext_sw_ice_id, ndims=ndims, dimids=vdimids),& + 'checking dimensions of ext_sw_ice') + call handle_ncerr(nf90_inquire_dimension( ncid, vdimids(1), len=templen),& + 'getting first dimension sw_ext') + call handle_ncerr(nf90_inquire_dimension( ncid, vdimids(2), len=templen),& + 'getting first dimension sw_ext') + call handle_ncerr( nf90_get_var(ncid, ext_sw_ice_id, ext_sw_ice),& + 'read cloud optics ext_sw_ice values') + + call handle_ncerr( nf90_inq_varid(ncid, 'sw_ssa', ssa_sw_ice_id),& + 'cloud optics ssa_sw_ice get') + call handle_ncerr( nf90_get_var(ncid, ssa_sw_ice_id, ssa_sw_ice),& + 'read cloud optics ssa_sw_ice values') + + call handle_ncerr( nf90_inq_varid(ncid, 'sw_asm', asm_sw_ice_id),& + 'cloud optics asm_sw_ice get') + call handle_ncerr( nf90_get_var(ncid, asm_sw_ice_id, asm_sw_ice),& + 'read cloud optics asm_sw_ice values') + + call handle_ncerr( nf90_inq_varid(ncid, 'lw_abs', abs_lw_ice_id),& + 'cloud optics abs_lw_ice get') + call handle_ncerr( nf90_get_var(ncid, abs_lw_ice_id, abs_lw_ice),& + 'read cloud optics abs_lw_ice values') + + call handle_ncerr( nf90_close(ncid), 'ice optics file missing') + end if ! if masterproc + #if ( defined SPMD ) - call mpibcast(g_d_eff, n_g_d, mpir8, 0, mpicom, ierr) - call mpibcast(ext_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) - call mpibcast(ssa_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) - call mpibcast(asm_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) - call mpibcast(abs_lw_ice, n_g_d*nlwbands, mpir8, 0, mpicom, ierr) + call mpibcast(g_d_eff, n_g_d, mpir8, 0, mpicom, ierr) + call mpibcast(ext_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) + call mpibcast(ssa_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) + call mpibcast(asm_sw_ice, n_g_d*nswbands, mpir8, 0, mpicom, ierr) + call mpibcast(abs_lw_ice, n_g_d*nlwbands, mpir8, 0, mpicom, ierr) #endif - return + return end subroutine cloud_rad_props_init !============================================================================== -subroutine cloud_rad_props_get_sw(state, pbuf, & - tau, tau_w, tau_w_g, tau_w_f,& - diagnosticindex, oldliq, oldice) +subroutine cloud_rad_props_get_lw(state, pbuf, cld_abs_od, oldliq, oldice, oldcloud) -! return totaled (across all species) layer tau, omega, g, f -! for all spectral interval for aerosols affecting the climate - - ! Arguments - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - integer, optional, intent(in) :: diagnosticindex ! index (if present) to radiation diagnostic information - - real(r8), intent(out) :: tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8), intent(out) :: tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8), intent(out) :: tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8), intent(out) :: tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - logical, optional, intent(in) :: oldliq,oldice - - ! Local variables - - integer :: ncol - integer :: lchnk - integer :: k, i ! lev and daycolumn indices - integer :: iswband ! sw band indices - - ! optical props for each aerosol - real(r8), pointer :: h_ext(:,:) - real(r8), pointer :: h_ssa(:,:) - real(r8), pointer :: h_asm(:,:) - real(r8), pointer :: n_ext(:) - real(r8), pointer :: n_ssa(:) - real(r8), pointer :: n_asm(:) - - ! rad properties for liquid clouds - real(r8) :: liq_tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8) :: liq_tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8) :: liq_tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8) :: liq_tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - ! rad properties for ice clouds - real(r8) :: ice_tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8) :: ice_tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8) :: ice_tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8) :: ice_tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - !----------------------------------------------------------------------------- - - ncol = state%ncol - lchnk = state%lchnk - - ! initialize to conditions that would cause failure - tau (:,:,:) = -100._r8 - tau_w (:,:,:) = -100._r8 - tau_w_g (:,:,:) = -100._r8 - tau_w_f (:,:,:) = -100._r8 - - ! initialize layers to accumulate od's - tau (:,1:ncol,:) = 0._r8 - tau_w (:,1:ncol,:) = 0._r8 - tau_w_g(:,1:ncol,:) = 0._r8 - tau_w_f(:,1:ncol,:) = 0._r8 - - - call get_liquid_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, liq_tau_w_f) - - call get_ice_optics_sw (state, pbuf, ice_tau, ice_tau_w, ice_tau_w_g, ice_tau_w_f) - - tau (:,1:ncol,:) = liq_tau (:,1:ncol,:) + ice_tau (:,1:ncol,:) - tau_w (:,1:ncol,:) = liq_tau_w (:,1:ncol,:) + ice_tau_w (:,1:ncol,:) - tau_w_g(:,1:ncol,:) = liq_tau_w_g(:,1:ncol,:) + ice_tau_w_g(:,1:ncol,:) - tau_w_f(:,1:ncol,:) = liq_tau_w_f(:,1:ncol,:) + ice_tau_w_f(:,1:ncol,:) - -end subroutine cloud_rad_props_get_sw -!============================================================================== - -subroutine cloud_rad_props_get_lw(state, pbuf, cld_abs_od, diagnosticindex, oldliq, oldice, oldcloud) - -! Purpose: Compute cloud longwave absorption optical depth -! cloud_rad_props_get_lw() is called by radlw() + ! Purpose: Compute cloud longwave absorption optical depth ! Arguments type(physics_state), intent(in) :: state type(physics_buffer_desc),pointer:: pbuf(:) real(r8), intent(out) :: cld_abs_od(nlwbands,pcols,pver) ! [fraction] absorption optical depth, per layer - integer, optional, intent(in) :: diagnosticindex logical, optional, intent(in) :: oldliq ! use old liquid optics logical, optional, intent(in) :: oldice ! use old ice optics logical, optional, intent(in) :: oldcloud ! use old optics for both (b4b) ! Local variables - - integer :: bnd_idx ! LW band index - integer :: i ! column index - integer :: k ! lev index integer :: ncol ! number of columns - integer :: lchnk ! rad properties for liquid clouds real(r8) :: liq_tau_abs_od(nlwbands,pcols,pver) ! liquid cloud absorption optical depth ! rad properties for ice clouds real(r8) :: ice_tau_abs_od(nlwbands,pcols,pver) ! ice cloud absorption optical depth - !----------------------------------------------------------------------------- ncol = state%ncol - lchnk = state%lchnk - ! compute optical depths cld_absod cld_abs_od = 0._r8 if(present(oldcloud))then if(oldcloud) then - ! make diagnostic calls to these first to output ice and liq OD's - !call old_liq_get_rad_props_lw(state, pbuf, liq_tau_abs_od, oldliqwp=.false.) - !call old_ice_get_rad_props_lw(state, pbuf, ice_tau_abs_od, oldicewp=.false.) - ! This affects climate (cld_abs_od) call oldcloud_lw(state,pbuf,cld_abs_od,oldwp=.false.) return endif @@ -434,13 +343,36 @@ end subroutine cloud_rad_props_get_lw !============================================================================== +subroutine get_ice_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) + type(physics_state), intent(in) :: state + type(physics_buffer_desc),pointer :: pbuf(:) + + real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth + real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau + real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w + real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w + + real(r8), pointer :: iciwpth(:,:), dei(:,:) + + ! Get relevant pbuf fields, and interpolate optical properties from + ! the lookup tables. + call pbuf_get_field(pbuf, i_iciwp, iciwpth) + call pbuf_get_field(pbuf, i_dei, dei) + + call interpolate_ice_optics_sw(state%ncol, iciwpth, dei, tau, tau_w, & + tau_w_g, tau_w_f) + +end subroutine get_ice_optics_sw + +!============================================================================== + subroutine get_snow_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) type(physics_state), intent(in) :: state type(physics_buffer_desc),pointer :: pbuf(:) real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w + real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w real(r8), pointer :: icswpth(:,:), des(:,:) @@ -463,12 +395,13 @@ subroutine get_grau_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w + real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w real(r8), pointer :: icgrauwpth(:,:), degrau(:,:) integer :: i,k + character(len=*), parameter :: sub = 'get_grau_optics_sw' ! This does the same thing as get_ice_optics_sw, except with a different ! water path and effective diameter. @@ -489,87 +422,11 @@ subroutine get_grau_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) enddo else - call endrun('ERROR: Get_grau_optics_sw called when graupel properties not supported') + call endrun(sub//': ERROR: Get_grau_optics_sw called when graupel properties not supported') end if end subroutine get_grau_optics_sw -!============================================================================== -! Private methods -!============================================================================== - -subroutine get_ice_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - - real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth - real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w - real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w - - real(r8), pointer :: iciwpth(:,:), dei(:,:) - - ! Get relevant pbuf fields, and interpolate optical properties from - ! the lookup tables. - call pbuf_get_field(pbuf, i_iciwp, iciwpth) - call pbuf_get_field(pbuf, i_dei, dei) - - call interpolate_ice_optics_sw(state%ncol, iciwpth, dei, tau, tau_w, & - tau_w_g, tau_w_f) - -end subroutine get_ice_optics_sw - -!============================================================================== - -subroutine interpolate_ice_optics_sw(ncol, iciwpth, dei, tau, tau_w, & - tau_w_g, tau_w_f) - - integer, intent(in) :: ncol - real(r8), intent(in) :: iciwpth(pcols,pver) - real(r8), intent(in) :: dei(pcols,pver) - - real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth - real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w - real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w - - type(interp_type) :: dei_wgts - - integer :: i, k, swband - real(r8) :: ext(nswbands), ssa(nswbands), asm(nswbands) - - do k = 1,pver - do i = 1,ncol - if( iciwpth(i,k) < 1.e-80_r8 .or. dei(i,k) == 0._r8) then - ! if ice water path is too small, OD := 0 - tau (:,i,k) = 0._r8 - tau_w (:,i,k) = 0._r8 - tau_w_g(:,i,k) = 0._r8 - tau_w_f(:,i,k) = 0._r8 - else - ! for each cell interpolate to find weights in g_d_eff grid. - call lininterp_init(g_d_eff, n_g_d, dei(i:i,k), 1, & - extrap_method_bndry, dei_wgts) - ! interpolate into grid and extract radiative properties - do swband = 1, nswbands - call lininterp(ext_sw_ice(:,swband), n_g_d, & - ext(swband:swband), 1, dei_wgts) - call lininterp(ssa_sw_ice(:,swband), n_g_d, & - ssa(swband:swband), 1, dei_wgts) - call lininterp(asm_sw_ice(:,swband), n_g_d, & - asm(swband:swband), 1, dei_wgts) - end do - tau (:,i,k) = iciwpth(i,k) * ext - tau_w (:,i,k) = tau(:,i,k) * ssa - tau_w_g(:,i,k) = tau_w(:,i,k) * asm - tau_w_f(:,i,k) = tau_w_g(:,i,k) * asm - call lininterp_finish(dei_wgts) - endif - enddo - enddo - -end subroutine interpolate_ice_optics_sw - !============================================================================== subroutine get_liquid_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) @@ -578,14 +435,13 @@ subroutine get_liquid_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f) real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymetry parameter * tau * w + real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w real(r8), pointer, dimension(:,:) :: lamc, pgam, iclwpth real(r8), dimension(pcols,pver) :: kext - integer i,k,swband,lchnk,ncol + integer i,k,swband, ncol - lchnk = state%lchnk ncol = state%ncol @@ -616,14 +472,13 @@ subroutine liquid_cloud_get_rad_props_lw(state, pbuf, abs_od) type(physics_buffer_desc),pointer :: pbuf(:) real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) - integer :: lchnk, ncol + integer :: ncol real(r8), pointer, dimension(:,:) :: lamc, pgam, iclwpth integer lwband, i, k abs_od = 0._r8 - lchnk = state%lchnk ncol = state%ncol call pbuf_get_field(pbuf, i_lambda, lamc) @@ -668,6 +523,7 @@ subroutine grau_cloud_get_rad_props_lw(state, pbuf, abs_od) real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) real(r8), pointer :: icgrauwpth(:,:), degrau(:,:) + character(len=*), parameter :: sub = 'grau_cloud_get_rad_props_lw' ! This does the same thing as ice_cloud_get_rad_props_lw, except with a ! different water path and effective diameter. @@ -677,7 +533,7 @@ subroutine grau_cloud_get_rad_props_lw(state, pbuf, abs_od) call interpolate_ice_optics_lw(state%ncol,icgrauwpth, degrau, abs_od) else - call endrun('ERROR: Grau_cloud_get_rad_props_lw called when graupel & + call endrun(sub//': ERROR: Grau_cloud_get_rad_props_lw called when graupel & &properties not supported') end if @@ -701,6 +557,59 @@ subroutine ice_cloud_get_rad_props_lw(state, pbuf, abs_od) end subroutine ice_cloud_get_rad_props_lw +!============================================================================== +! Private methods +!============================================================================== + +subroutine interpolate_ice_optics_sw(ncol, iciwpth, dei, tau, tau_w, & + tau_w_g, tau_w_f) + + integer, intent(in) :: ncol + real(r8), intent(in) :: iciwpth(pcols,pver) + real(r8), intent(in) :: dei(pcols,pver) + + real(r8),intent(out) :: tau (nswbands,pcols,pver) ! extinction optical depth + real(r8),intent(out) :: tau_w (nswbands,pcols,pver) ! single scattering albedo * tau + real(r8),intent(out) :: tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w + real(r8),intent(out) :: tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w + + type(interp_type) :: dei_wgts + + integer :: i, k, swband + real(r8) :: ext(nswbands), ssa(nswbands), asm(nswbands) + + do k = 1,pver + do i = 1,ncol + if( iciwpth(i,k) < tiny .or. dei(i,k) == 0._r8) then + ! if ice water path is too small, OD := 0 + tau (:,i,k) = 0._r8 + tau_w (:,i,k) = 0._r8 + tau_w_g(:,i,k) = 0._r8 + tau_w_f(:,i,k) = 0._r8 + else + ! for each cell interpolate to find weights in g_d_eff grid. + call lininterp_init(g_d_eff, n_g_d, dei(i:i,k), 1, & + extrap_method_bndry, dei_wgts) + ! interpolate into grid and extract radiative properties + do swband = 1, nswbands + call lininterp(ext_sw_ice(:,swband), n_g_d, & + ext(swband:swband), 1, dei_wgts) + call lininterp(ssa_sw_ice(:,swband), n_g_d, & + ssa(swband:swband), 1, dei_wgts) + call lininterp(asm_sw_ice(:,swband), n_g_d, & + asm(swband:swband), 1, dei_wgts) + end do + tau (:,i,k) = iciwpth(i,k) * ext + tau_w (:,i,k) = tau(:,i,k) * ssa + tau_w_g(:,i,k) = tau_w(:,i,k) * asm + tau_w_f(:,i,k) = tau_w_g(:,i,k) * asm + call lininterp_finish(dei_wgts) + endif + enddo + enddo + +end subroutine interpolate_ice_optics_sw + !============================================================================== subroutine interpolate_ice_optics_lw(ncol, iciwpth, dei, abs_od) @@ -719,7 +628,7 @@ subroutine interpolate_ice_optics_lw(ncol, iciwpth, dei, abs_od) do k = 1,pver do i = 1,ncol ! if ice water path is too small, OD := 0 - if( iciwpth(i,k) < 1.e-80_r8 .or. dei(i,k) == 0._r8) then + if( iciwpth(i,k) < tiny .or. dei(i,k) == 0._r8) then abs_od (:,i,k) = 0._r8 else ! for each cell interpolate to find weights in g_d_eff grid. @@ -752,7 +661,7 @@ subroutine gam_liquid_lw(clwptn, lamc, pgam, abs_od) type(interp_type) :: mu_wgts type(interp_type) :: lambda_wgts - if (clwptn < 1.e-80_r8) then + if (clwptn < tiny) then abs_od = 0._r8 return endif @@ -786,7 +695,7 @@ subroutine gam_liquid_sw(clwptn, lamc, pgam, tau, tau_w, tau_w_g, tau_w_f) type(interp_type) :: mu_wgts type(interp_type) :: lambda_wgts - if (clwptn < 1.e-80_r8) then + if (clwptn < tiny) then tau = 0._r8 tau_w = 0._r8 tau_w_g = 0._r8 diff --git a/src/physics/cam/cospsimulator_intr.F90 b/src/physics/cam/cospsimulator_intr.F90 index 855a8e82d5..6a01415f04 100644 --- a/src/physics/cam/cospsimulator_intr.F90 +++ b/src/physics/cam/cospsimulator_intr.F90 @@ -1107,7 +1107,7 @@ subroutine cospsimulator_intr_init() flag_xyfill=.true., fill_value=R_UNDEF) call addfld ('MODIS_fracliq', (/'cosp_scol','lev '/), 'I','1', 'Fraction of tau from liquid water', & flag_xyfill=.true., fill_value=R_UNDEF) - call addfld ('MODIS_asym', (/'cosp_scol','lev '/), 'I','1', 'Assymetry parameter (MODIS)', & + call addfld ('MODIS_asym', (/'cosp_scol','lev '/), 'I','1', 'Asymmetry parameter (MODIS)', & flag_xyfill=.true., fill_value=R_UNDEF) call addfld ('MODIS_ssa', (/'cosp_scol','lev '/), 'I','1', 'Single-scattering albedo (MODIS)', & flag_xyfill=.true., fill_value=R_UNDEF) @@ -3262,7 +3262,7 @@ subroutine subsample_and_optics(nPoints, nLevels, nColumns, nHydro,overlap, MODIS_snowSize, cospIN%tau_067, MODIS_opticalThicknessLiq, & MODIS_opticalThicknessIce, MODIS_opticalThicknessSnow) - ! Compute assymetry parameter and single scattering albedo + ! Compute asymmetry parameter and single scattering albedo call modis_optics(nPoints, nLevels, nColumns, MODIS_opticalThicknessLiq, & MODIS_waterSize*1.0e6_wp, MODIS_opticalThicknessIce, & MODIS_iceSize*1.0e6_wp, MODIS_opticalThicknessSnow, & diff --git a/src/physics/rrtmg/ebert_curry.F90 b/src/physics/cam/ebert_curry_ice_optics.F90 similarity index 59% rename from src/physics/rrtmg/ebert_curry.F90 rename to src/physics/cam/ebert_curry_ice_optics.F90 index a1e1c031b1..8d9b4985a7 100644 --- a/src/physics/rrtmg/ebert_curry.F90 +++ b/src/physics/cam/ebert_curry_ice_optics.F90 @@ -1,15 +1,14 @@ -module ebert_curry +module ebert_curry_ice_optics -!------------------------------------------------------------------------------------------------ -!------------------------------------------------------------------------------------------------ use shr_kind_mod, only: r8 => shr_kind_r8 -use ppgrid, only: pcols, pver, pverp +use physconst, only: gravit +use ppgrid, only: pcols, pver use physics_types, only: physics_state use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_get_field, pbuf_old_tim_idx -use radconstants, only: nswbands, nlwbands, idx_sw_diag, ot_length, idx_lw_diag, get_sw_spectral_boundaries +use constituents, only: cnst_get_ind +use radconstants, only: nswbands, nlwbands, get_sw_spectral_boundaries use cam_abortutils, only: endrun -use cam_history, only: outfld implicit none private @@ -17,41 +16,21 @@ module ebert_curry public :: & ec_rad_props_init, & - cloud_rad_props_get_sw, & ! return SW optical props of total bulk aerosols - cloud_rad_props_get_lw, & ! return LW optical props of total bulk aerosols ec_ice_optics_sw, & ec_ice_get_rad_props_lw real(r8), public, parameter:: scalefactor = 1._r8 !500._r8/917._r8 -! Minimum cloud amount (as a fraction of the grid-box area) to -! distinguish from clear sky -! - real(r8) cldmin - parameter (cldmin = 1.0e-80_r8) -! -! Decimal precision of cloud amount (0 -> preserve full resolution; -! 10^-n -> preserve n digits of cloud amount) -! - real(r8) cldeps - parameter (cldeps = 0.0_r8) - -! -! indexes into pbuf for optical parameters of MG clouds -! - integer :: dei_idx = 0 - integer :: mu_idx = 0 - integer :: lambda_idx = 0 - integer :: iciwp_idx = 0 - integer :: iclwp_idx = 0 - integer :: cld_idx = 0 - integer :: rei_idx = 0 - -! indexes into constituents for old optics - integer :: & - ixcldice, & ! cloud ice water index - ixcldliq ! cloud liquid water index +! indices into pbuf +integer :: iciwp_idx = 0 +integer :: iclwp_idx = 0 +integer :: cld_idx = 0 +integer :: rei_idx = 0 + +! indices into constituents for old optics +integer :: ixcldice ! cloud ice water index +integer :: ixcldliq ! cloud liquid water index !============================================================================== @@ -60,17 +39,6 @@ module ebert_curry subroutine ec_rad_props_init() -! use cam_history, only: addfld - use netcdf - use spmd_utils, only: masterproc - use ioFileMod, only: getfil - use cam_logfile, only: iulog - use error_messages, only: handle_ncerr -#if ( defined SPMD ) - use mpishorthand -#endif - use constituents, only: cnst_get_ind - integer :: err iciwp_idx = pbuf_get_index('ICIWP',errcode=err) @@ -82,124 +50,18 @@ subroutine ec_rad_props_init() call cnst_get_ind('CLDICE', ixcldice) call cnst_get_ind('CLDLIQ', ixcldliq) - !call addfld ('CLWPTH_OLD',(/ 'lev' /), 'I','Kg/m2','old In Cloud Liquid Water Path', sampling_seq='rad_lwsw') - !call addfld ('KEXT_OLD',(/ 'lev' /),'I','m^2/kg','old extinction') - !call addfld ('CLDOD_OLD',(/ 'lev' /),'I','1','old liquid OD') - !call addfld ('REL_OLD',(/ 'lev' /),'I','1','old liquid effective radius (liquid)') - - !call addfld ('CLWPTH_NEW',(/ 'lev' /), 'I','Kg/m2','In Cloud Liquid Water Path', sampling_seq='rad_lwsw') - !call addfld ('KEXT_NEW',(/ 'lev' /),'I','m^2/kg','extinction') - !call addfld ('CLDOD_NEW',(/ 'lev' /),'I','1','liquid OD') - - !call addfld('CIWPTH_NEW',(/ 'lev' /), 'I','Kg/m2','In Cloud Ice Water Path', sampling_seq='rad_lwsw') - !call addfld('CIWPTH_OLD',(/ 'lev' /), 'I','Kg/m2','In Cloud Ice Water Path (old)', sampling_seq='rad_lwsw') - - return - end subroutine ec_rad_props_init !============================================================================== -subroutine cloud_rad_props_get_sw(state, pbuf, & - tau, tau_w, tau_w_g, tau_w_f,& - diagnosticindex, oldliq, oldice) - -! return totaled (across all species) layer tau, omega, g, f -! for all spectral interval for aerosols affecting the climate - - ! Arguments - type(physics_state), intent(in) :: state - type(physics_buffer_desc), pointer :: pbuf(:) - integer, optional, intent(in) :: diagnosticindex ! index (if present) to radiation diagnostic information - - real(r8), intent(out) :: tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8), intent(out) :: tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8), intent(out) :: tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8), intent(out) :: tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - logical, optional, intent(in) :: oldliq,oldice - - ! Local variables - - integer :: ncol - integer :: lchnk - integer :: k, i ! lev and daycolumn indices - integer :: iswband ! sw band indices - - !----------------------------------------------------------------------------- - - ncol = state%ncol - lchnk = state%lchnk - - ! initialize to conditions that would cause failure - tau (:,:,:) = -100._r8 - tau_w (:,:,:) = -100._r8 - tau_w_g (:,:,:) = -100._r8 - tau_w_f (:,:,:) = -100._r8 - - ! initialize layers to accumulate od's - tau (:,1:ncol,:) = 0._r8 - tau_w (:,1:ncol,:) = 0._r8 - tau_w_g(:,1:ncol,:) = 0._r8 - tau_w_f(:,1:ncol,:) = 0._r8 - - - call ec_ice_optics_sw (state, pbuf, tau, tau_w, tau_w_g, tau_w_f, oldicewp=.true.) -! call outfld ('CI_OD_SW_OLD', ice_tau(idx_sw_diag,:,:), pcols, lchnk) - - -end subroutine cloud_rad_props_get_sw -!============================================================================== - -subroutine cloud_rad_props_get_lw(state, pbuf, cld_abs_od, diagnosticindex, oldliq, oldice, oldcloud) - -! Purpose: Compute cloud longwave absorption optical depth -! cloud_rad_props_get_lw() is called by radlw() - - ! Arguments - type(physics_state), intent(in) :: state - type(physics_buffer_desc), pointer :: pbuf(:) - real(r8), intent(out) :: cld_abs_od(nlwbands,pcols,pver) ! [fraction] absorption optical depth, per layer - integer, optional, intent(in) :: diagnosticindex - logical, optional, intent(in) :: oldliq ! use old liquid optics - logical, optional, intent(in) :: oldice ! use old ice optics - logical, optional, intent(in) :: oldcloud ! use old optics for both (b4b) - - ! Local variables - - integer :: bnd_idx ! LW band index - integer :: i ! column index - integer :: k ! lev index - integer :: ncol ! number of columns - integer :: lchnk - - !----------------------------------------------------------------------------- - - ncol = state%ncol - lchnk = state%lchnk - - ! compute optical depths cld_absod - cld_abs_od = 0._r8 - - call ec_ice_get_rad_props_lw(state, pbuf, cld_abs_od, oldicewp=.true.) - !call outfld('CI_OD_LW_OLD', ice_tau_abs_od(idx_lw_diag ,:,:), pcols, lchnk) - -end subroutine cloud_rad_props_get_lw - -!============================================================================== -! Private methods -!============================================================================== - subroutine ec_ice_optics_sw (state, pbuf, ice_tau, ice_tau_w, ice_tau_w_g, ice_tau_w_f, oldicewp) - use physconst, only: gravit - - type(physics_state), intent(in) :: state + type(physics_state), intent(in) :: state type(physics_buffer_desc), pointer :: pbuf(:) real(r8),intent(out) :: ice_tau (nswbands,pcols,pver) ! extinction optical depth real(r8),intent(out) :: ice_tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: ice_tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w + real(r8),intent(out) :: ice_tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w real(r8),intent(out) :: ice_tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w logical, intent(in) :: oldicewp @@ -312,10 +174,10 @@ subroutine ec_ice_optics_sw (state, pbuf, ice_tau, ice_tau_w, ice_tau_w_g, ice end do ! nswbands end subroutine ec_ice_optics_sw + !============================================================================== subroutine ec_ice_get_rad_props_lw(state, pbuf, abs_od, oldicewp) - use physconst, only: gravit type(physics_state), intent(in) :: state type(physics_buffer_desc),pointer :: pbuf(:) @@ -390,19 +252,13 @@ subroutine ec_ice_get_rad_props_lw(state, pbuf, abs_od, oldicewp) cldtau(i,k) = kabs*cwp(i,k) end do end do -! + do lwband = 1,nlwbands abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) enddo - !if(oldicewp) then - ! call outfld('CIWPTH_OLD',cicewp(:,:)/1000,pcols,lchnk) - !else - ! call outfld('CIWPTH_OLD',iciwpth(:,:),pcols,lchnk) - !endif - !call outfld('CI_OD_LW_OLD',cldtau(:,:),pcols,lchnk) - end subroutine ec_ice_get_rad_props_lw + !============================================================================== -end module ebert_curry +end module ebert_curry_ice_optics diff --git a/src/physics/cam/oldcloud_optics.F90 b/src/physics/cam/oldcloud_optics.F90 new file mode 100644 index 0000000000..bf53856ad6 --- /dev/null +++ b/src/physics/cam/oldcloud_optics.F90 @@ -0,0 +1,324 @@ +module oldcloud_optics + +!------------------------------------------------------------------------------------------------ +!------------------------------------------------------------------------------------------------ + +use shr_kind_mod, only: r8 => shr_kind_r8 +use ppgrid, only: pcols, pver, pverp +use physics_types, only: physics_state +use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_old_tim_idx, pbuf_get_field +use constituents, only: cnst_get_ind +use physconst, only: gravit +use radconstants, only: nlwbands +use ebert_curry_ice_optics, only: scalefactor + +use cam_abortutils, only: endrun + +implicit none +private +save + +public :: & + oldcloud_init, & + oldcloud_lw, & + old_liq_get_rad_props_lw, & + old_ice_get_rad_props_lw + +integer :: nmu, nlambda +real(r8), allocatable :: g_mu(:) ! mu samples on grid +real(r8), allocatable :: g_lambda(:,:) ! lambda scale samples on grid +real(r8), allocatable :: ext_sw_liq(:,:,:) +real(r8), allocatable :: ssa_sw_liq(:,:,:) +real(r8), allocatable :: asm_sw_liq(:,:,:) +real(r8), allocatable :: abs_lw_liq(:,:,:) + +integer :: n_g_d +real(r8), allocatable :: g_d_eff(:) ! radiative effective diameter samples on grid +real(r8), allocatable :: ext_sw_ice(:,:) +real(r8), allocatable :: ssa_sw_ice(:,:) +real(r8), allocatable :: asm_sw_ice(:,:) +real(r8), allocatable :: abs_lw_ice(:,:) + +! Minimum cloud amount (as a fraction of the grid-box area) to +! distinguish from clear sky +real(r8), parameter :: cldmin = 1.0e-80_r8 + +! Decimal precision of cloud amount (0 -> preserve full resolution; +! 10^-n -> preserve n digits of cloud amount) +real(r8), parameter :: cldeps = 0.0_r8 + +! indexes into pbuf +integer :: iciwp_idx = 0 +integer :: iclwp_idx = 0 +integer :: cld_idx = 0 +integer :: rel_idx = 0 +integer :: rei_idx = 0 + +! indexes into constituents for old optics +integer :: & + ixcldice, & ! cloud ice water index + ixcldliq ! cloud liquid water index + + +!============================================================================== +contains +!============================================================================== + +subroutine oldcloud_init() + + + integer :: err + + iciwp_idx = pbuf_get_index('ICIWP',errcode=err) + iclwp_idx = pbuf_get_index('ICLWP',errcode=err) + cld_idx = pbuf_get_index('CLD') + rel_idx = pbuf_get_index('REL') + rei_idx = pbuf_get_index('REI') + + ! old optics + call cnst_get_ind('CLDICE', ixcldice) + call cnst_get_ind('CLDLIQ', ixcldliq) + +end subroutine oldcloud_init + +!============================================================================== + +subroutine oldcloud_lw(state,pbuf,cld_abs_od,oldwp) + + type(physics_state), intent(in) :: state + type(physics_buffer_desc),pointer :: pbuf(:) + real(r8), intent(out) :: cld_abs_od(nlwbands,pcols,pver) ! [fraction] absorption optical depth, per layer + logical,intent(in) :: oldwp ! use old definition of waterpath + + + real(r8) :: gicewp(pcols,pver) + real(r8) :: gliqwp(pcols,pver) + real(r8) :: cicewp(pcols,pver) + real(r8) :: cliqwp(pcols,pver) + real(r8) :: ficemr(pcols,pver) + real(r8) :: cwp(pcols,pver) + real(r8) :: cldtau(pcols,pver) + + real(r8), pointer, dimension(:,:) :: cldn + real(r8), pointer, dimension(:,:) :: rei + integer :: ncol, itim_old, lwband, i, k, lchnk + real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth + + real(r8) :: kabs, kabsi + real(r8), parameter :: kabsl = 0.090361_r8 ! longwave liquid absorption coeff (m**2/g) + + + ncol = state%ncol + lchnk = state%lchnk + + itim_old = pbuf_old_tim_idx() + call pbuf_get_field(pbuf, rei_idx, rei) + call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) + + if (oldwp) then + do k=1,pver + do i = 1,ncol + gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. + gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. + cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. + cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. + ficemr(i,k) = state%q(i,k,ixcldice) / & + max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) + end do + end do + cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) + else + if (iclwp_idx<=0 .or. iciwp_idx<=0) then + call endrun('oldcloud_lw: oldwp must be set to true since ICIWP and/or ICLWP were not found in pbuf') + endif + call pbuf_get_field(pbuf, iclwp_idx, iclwpth) + call pbuf_get_field(pbuf, iciwp_idx, iciwpth) + do k=1,pver + do i = 1,ncol + cwp(i,k) = 1000.0_r8 *iclwpth(i,k) + 1000.0_r8 *iciwpth(i, k) + ficemr(i,k) = 1000.0_r8 * iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) + end do + end do + endif + + do k=1,pver + do i=1,ncol + + !note that optical properties for ice valid only + !in range of 13 > rei > 130 micron (Ebert and Curry 92) + kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) + kabs = kabsl*(1._r8-ficemr(i,k)) + kabsi*ficemr(i,k) + cldtau(i,k) = kabs*cwp(i,k) + end do + end do +! + do lwband = 1,nlwbands + cld_abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) + enddo + +end subroutine oldcloud_lw + +!============================================================================== + +subroutine old_liq_get_rad_props_lw(state, pbuf, abs_od, oldliqwp) + + type(physics_state), intent(in) :: state + type(physics_buffer_desc),pointer :: pbuf(:) + real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) + logical, intent(in) :: oldliqwp + + real(r8) :: gicewp(pcols,pver) + real(r8) :: gliqwp(pcols,pver) + real(r8) :: cicewp(pcols,pver) + real(r8) :: cliqwp(pcols,pver) + real(r8) :: ficemr(pcols,pver) + real(r8) :: cwp(pcols,pver) + real(r8) :: cldtau(pcols,pver) + + real(r8), pointer, dimension(:,:) :: cldn + real(r8), pointer, dimension(:,:) :: rei + integer :: ncol, itim_old, lwband, i, k, lchnk + + real(r8) :: kabs, kabsi + real(r8), parameter :: kabsl = 0.090361_r8 ! longwave liquid absorption coeff (m**2/g) + + real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth + + ncol=state%ncol + lchnk = state%lchnk + + itim_old = pbuf_old_tim_idx() + call pbuf_get_field(pbuf, rei_idx, rei) + call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) + + if (oldliqwp) then + do k=1,pver + do i = 1,ncol + gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. + gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. + cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. + cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. + ficemr(i,k) = state%q(i,k,ixcldice) / & + max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) + end do + end do + cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) + else + if (iclwp_idx<=0 .or. iciwp_idx<=0) then + call endrun('old_liq_get_rad_props_lw: oldliqwp must be set to true since ICIWP and/or ICLWP were not found in pbuf') + endif + call pbuf_get_field(pbuf, iclwp_idx, iclwpth) + call pbuf_get_field(pbuf, iciwp_idx, iciwpth) + do k=1,pver + do i = 1,ncol + cwp(i,k) = 1000.0_r8 *iclwpth(i,k) + 1000.0_r8 *iciwpth(i, k) + ficemr(i,k) = 1000.0_r8 * iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) + end do + end do + endif + + + do k=1,pver + do i=1,ncol + + ! Note from Andrew Conley: + ! Optics for RK no longer supported, This is constructed to get + ! close to bit for bit. Otherwise we could simply use liquid water path + !note that optical properties for ice valid only + !in range of 13 > rei > 130 micron (Ebert and Curry 92) + kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) + kabs = kabsl*(1._r8-ficemr(i,k)) ! + kabsi*ficemr(i,k) + cldtau(i,k) = kabs*cwp(i,k) + end do + end do + + do lwband = 1,nlwbands + abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) + enddo + + +end subroutine old_liq_get_rad_props_lw + +!============================================================================== + +subroutine old_ice_get_rad_props_lw(state, pbuf, abs_od, oldicewp) + + type(physics_state), intent(in) :: state + type(physics_buffer_desc),pointer :: pbuf(:) + real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) + logical, intent(in) :: oldicewp + + real(r8) :: gicewp(pcols,pver) + real(r8) :: gliqwp(pcols,pver) + real(r8) :: cicewp(pcols,pver) + real(r8) :: cliqwp(pcols,pver) + real(r8) :: ficemr(pcols,pver) + real(r8) :: cwp(pcols,pver) + real(r8) :: cldtau(pcols,pver) + + real(r8), pointer, dimension(:,:) :: cldn + real(r8), pointer, dimension(:,:) :: rei + integer :: ncol, itim_old, lwband, i, k, lchnk + + real(r8) :: kabs, kabsi + real(r8), parameter :: kabsl = 0.090361_r8 ! longwave liquid absorption coeff (m**2/g) + + real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth + + + ncol = state%ncol + lchnk = state%lchnk + + itim_old = pbuf_old_tim_idx() + call pbuf_get_field(pbuf, rei_idx, rei) + call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) + + if(oldicewp) then + do k=1,pver + do i = 1,ncol + gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. + gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. + cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. + cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. + ficemr(i,k) = state%q(i,k,ixcldice) / & + max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) + end do + end do + cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) + else + if (iclwp_idx<=0 .or. iciwp_idx<=0) then + call endrun('old_ice_get_rad_props_lw: oldicewp must be set to true since ICIWP and/or ICLWP were not found in pbuf') + endif + call pbuf_get_field(pbuf, iclwp_idx, iclwpth) + call pbuf_get_field(pbuf, iciwp_idx, iciwpth) + do k=1,pver + do i = 1,ncol + cwp(i,k) = 1000.0_r8 *iciwpth(i,k) + 1000.0_r8 *iclwpth(i,k) + ficemr(i,k) = 1000.0_r8*iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) + end do + end do + endif + + do k=1,pver + do i=1,ncol + + ! Note from Andrew Conley: + ! Optics for RK no longer supported, This is constructed to get + ! close to bit for bit. Otherwise we could simply use ice water path + !note that optical properties for ice valid only + !in range of 13 > rei > 130 micron (Ebert and Curry 92) + kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) + kabs = kabsi*ficemr(i,k) ! kabsl*(1._r8-ficemr(i,k)) + kabsi*ficemr(i,k) + cldtau(i,k) = kabs*cwp(i,k) + end do + end do + + do lwband = 1,nlwbands + abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) + enddo + +end subroutine old_ice_get_rad_props_lw + +!============================================================================== + +end module oldcloud_optics diff --git a/src/physics/cam/phys_prop.F90 b/src/physics/cam/phys_prop.F90 index 568427e44e..6c504e8c78 100644 --- a/src/physics/cam/phys_prop.F90 +++ b/src/physics/cam/phys_prop.F90 @@ -11,7 +11,7 @@ module phys_prop use shr_kind_mod, only: r8 => shr_kind_r8 use spmd_utils, only: masterproc -use radconstants, only: nrh, nlwbands, nswbands, idx_sw_diag +use radconstants, only: nlwbands, nswbands, idx_sw_diag use ioFileMod, only: getfil use cam_pio_utils, only: cam_pio_openfile use pio, only: file_desc_t, var_desc_t, pio_get_var, pio_inq_varid, & @@ -26,6 +26,7 @@ module phys_prop save integer, parameter, public :: ot_length = 32 + public :: & physprop_accum_unique_files, &! Make a list of the unique set of files that contain properties ! This is an initialization step that must be done before calling physprop_init @@ -105,6 +106,10 @@ module phys_prop ! array. character(len=256), allocatable :: uniquefilenames(:) +! Number of evenly spaced intervals in rh used in this module and in the aer_rad_props module +! for calculations of aerosol hygroscopic growth. +integer, parameter, public :: nrh = 1000 + !================================================================================================ contains !================================================================================================ @@ -1106,7 +1111,7 @@ subroutine bulk_props_init(physprop, nc_id) type(var_desc_T) :: vid - logical :: debug = .true. + logical :: debug = .false. character(len=*), parameter :: subname = 'bulk_props_init' !------------------------------------------------------------------------------------ @@ -1134,7 +1139,7 @@ subroutine bulk_props_init(physprop, nc_id) ierr = pio_get_var(nc_id, vid, physprop%num_to_mass_aer) ! Output select data to log file - if (debug .and. masterproc) then + if (debug .and. masterproc .and. idx_sw_diag > 0) then if (trim(physprop%aername) == 'SULFATE') then write(iulog, '(2x, a)') '_______ hygroscopic growth in visible band _______' call aer_optics_log_rh('SO4', physprop%sw_hygro_ext(:,idx_sw_diag), & diff --git a/src/physics/cam/physpkg.F90 b/src/physics/cam/physpkg.F90 index c7d5b2524b..a439f84423 100644 --- a/src/physics/cam/physpkg.F90 +++ b/src/physics/cam/physpkg.F90 @@ -855,19 +855,22 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_in, cam_out ) ! low level, so init it early. Must at least do this before radiation. call wv_sat_init + ! solar irradiance data modules + call solar_data_init() + ! CAM3 prescribed aerosols if (cam3_aero_data_on) call cam3_aero_data_init(phys_state) ! Initialize rad constituents and their properties call rad_cnst_init() + + call radiation_init(pbuf2d) + call aer_rad_props_init() ! initialize carma call carma_init() - ! solar irradiance data modules - call solar_data_init() - ! Prognostic chemistry. call chem_init(phys_state,pbuf2d) @@ -906,8 +909,6 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_in, cam_out ) endif endif - call radiation_init(pbuf2d) - call cloud_diagnostics_init() call radheat_init(pref_mid) diff --git a/src/physics/cam/rad_constituents.F90 b/src/physics/cam/rad_constituents.F90 index 2863197669..777af8728e 100644 --- a/src/physics/cam/rad_constituents.F90 +++ b/src/physics/cam/rad_constituents.F90 @@ -17,9 +17,9 @@ module rad_constituents use physics_types, only: physics_state use phys_control, only: use_simple_phys use constituents, only: cnst_get_ind -use radconstants, only: nradgas, rad_gas_index, ot_length +use radconstants, only: nradgas, rad_gas_index use phys_prop, only: physprop_accum_unique_files, physprop_init, & - physprop_get_id + physprop_get_id, ot_length use cam_history, only: addfld, fieldname_len, outfld, horiz_only use physics_buffer, only: physics_buffer_desc, pbuf_get_field, pbuf_get_index diff --git a/src/physics/rrtmg/slingo.F90 b/src/physics/cam/slingo_liq_optics.F90 similarity index 65% rename from src/physics/rrtmg/slingo.F90 rename to src/physics/cam/slingo_liq_optics.F90 index aedb44bcee..781a056b29 100644 --- a/src/physics/rrtmg/slingo.F90 +++ b/src/physics/cam/slingo_liq_optics.F90 @@ -1,4 +1,4 @@ -module slingo +module slingo_liq_optics !------------------------------------------------------------------------------------------------ ! Implements Slingo Optics for MG/RRTMG for liquid clouds and @@ -6,12 +6,12 @@ module slingo !------------------------------------------------------------------------------------------------ use shr_kind_mod, only: r8 => shr_kind_r8 +use physconst, only: gravit use ppgrid, only: pcols, pver, pverp use physics_types, only: physics_state use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_get_field, pbuf_old_tim_idx -use radconstants, only: nswbands, nlwbands, idx_sw_diag, ot_length, idx_lw_diag, get_sw_spectral_boundaries +use radconstants, only: nswbands, nlwbands, get_sw_spectral_boundaries use cam_abortutils, only: endrun -use cam_history, only: outfld implicit none private @@ -19,37 +19,28 @@ module slingo public :: & slingo_rad_props_init, & - cloud_rad_props_get_sw, & ! return SW optical props of total bulk aerosols - cloud_rad_props_get_lw, & ! return LW optical props of total bulk aerosols slingo_liq_get_rad_props_lw, & slingo_liq_optics_sw ! Minimum cloud amount (as a fraction of the grid-box area) to ! distinguish from clear sky -! - real(r8) cldmin - parameter (cldmin = 1.0e-80_r8) -! +real(r8), parameter :: cldmin = 1.0e-80_r8 + ! Decimal precision of cloud amount (0 -> preserve full resolution; ! 10^-n -> preserve n digits of cloud amount) -! - real(r8) cldeps - parameter (cldeps = 0.0_r8) +real(r8), parameter :: cldeps = 0.0_r8 -! ! indexes into pbuf for optical parameters of MG clouds -! - integer :: iclwp_idx = 0 - integer :: iciwp_idx = 0 - integer :: cld_idx = 0 - integer :: rel_idx = 0 - integer :: rei_idx = 0 +integer :: iclwp_idx = 0 +integer :: iciwp_idx = 0 +integer :: cld_idx = 0 +integer :: rel_idx = 0 +integer :: rei_idx = 0 ! indexes into constituents for old optics - integer :: & - ixcldliq, & ! cloud liquid water index - ixcldice ! cloud liquid water index - +integer :: & + ixcldliq, & ! cloud liquid water index + ixcldice ! cloud liquid water index !============================================================================== contains @@ -80,118 +71,18 @@ subroutine slingo_rad_props_init() call cnst_get_ind('CLDLIQ', ixcldliq) call cnst_get_ind('CLDICE', ixcldice) - !call addfld ('CLWPTH_OLD',(/ 'lev' /), 'I','Kg/m2','old In Cloud Liquid Water Path', sampling_seq='rad_lwsw') - !call addfld ('KEXT_OLD',(/ 'lev' /),'I','m^2/kg','old extinction') - !call addfld ('CLDOD_OLD',(/ 'lev' /),'I','1','old liquid OD') - !call addfld ('REL_OLD',(/ 'lev' /),'I','1','old liquid effective radius (liquid)') - - !call addfld ('CLWPTH_NEW',(/ 'lev' /), 'I','Kg/m2','In Cloud Liquid Water Path', sampling_seq='rad_lwsw') - !call addfld ('KEXT_NEW',(/ 'lev' /),'I','m^2/kg','extinction') - !call addfld ('CLDOD_NEW',(/ 'lev' /),'I','1','liquid OD') - - !call addfld('CIWPTH_NEW',(/ 'lev' /), 'I','Kg/m2','In Cloud Ice Water Path', sampling_seq='rad_lwsw') - !call addfld('CIWPTH_OLD',(/ 'lev' /), 'I','Kg/m2','In Cloud Ice Water Path (old)', sampling_seq='rad_lwsw') - - return - end subroutine slingo_rad_props_init !============================================================================== -subroutine cloud_rad_props_get_sw(state, pbuf, & - tau, tau_w, tau_w_g, tau_w_f,& - diagnosticindex) - -! return totaled (across all species) layer tau, omega, g, f -! for all spectral interval for aerosols affecting the climate - - ! Arguments - type(physics_state), intent(in) :: state - type(physics_buffer_desc), pointer :: pbuf(:) - integer, optional, intent(in) :: diagnosticindex ! index (if present) to radiation diagnostic information - - real(r8), intent(out) :: tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8), intent(out) :: tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8), intent(out) :: tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8), intent(out) :: tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - ! Local variables - - integer :: ncol - integer :: lchnk - integer :: k, i ! lev and daycolumn indices - integer :: iswband ! sw band indices - - real(r8) :: liq_tau (nswbands,pcols,pver) ! aerosol extinction optical depth - real(r8) :: liq_tau_w (nswbands,pcols,pver) ! aerosol single scattering albedo * tau - real(r8) :: liq_tau_w_g(nswbands,pcols,pver) ! aerosol assymetry parameter * tau * w - real(r8) :: liq_tau_w_f(nswbands,pcols,pver) ! aerosol forward scattered fraction * tau * w - - - !----------------------------------------------------------------------------- - - ncol = state%ncol - lchnk = state%lchnk - - call slingo_liq_optics_sw(state, pbuf, tau, tau_w, tau_w_g, tau_w_f, oldliqwp=.true. ) - -end subroutine cloud_rad_props_get_sw -!============================================================================== - -subroutine cloud_rad_props_get_lw(state, pbuf, cld_abs_od, diagnosticindex, oldliq, oldice, oldcloud) - -! Purpose: Compute cloud longwave absorption optical depth -! cloud_rad_props_get_lw() is called by radlw() - - ! Arguments - type(physics_state), intent(in) :: state - type(physics_buffer_desc), pointer :: pbuf(:) - real(r8), intent(out) :: cld_abs_od(nlwbands,pcols,pver) ! [fraction] absorption optical depth, per layer - integer, optional, intent(in) :: diagnosticindex - logical, optional, intent(in) :: oldliq ! use old liquid optics - logical, optional, intent(in) :: oldice ! use old ice optics - logical, optional, intent(in) :: oldcloud ! use old optics for both (b4b) - - ! Local variables - - integer :: bnd_idx ! LW band index - integer :: i ! column index - integer :: k ! lev index - integer :: ncol ! number of columns - integer :: lchnk - - ! rad properties for liquid clouds - real(r8) :: liq_tau_abs_od(nlwbands,pcols,pver) ! liquid cloud absorption optical depth - - !----------------------------------------------------------------------------- - - ncol = state%ncol - lchnk = state%lchnk - - ! compute optical depths cld_absod - cld_abs_od = 0._r8 - - call slingo_liq_get_rad_props_lw(state, pbuf, liq_tau_abs_od, oldliqwp=.true.) - - cld_abs_od(:,1:ncol,:) = liq_tau_abs_od(:,1:ncol,:) - -end subroutine cloud_rad_props_get_lw - -!============================================================================== -! Private methods -!============================================================================== - - subroutine slingo_liq_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, liq_tau_w_f, oldliqwp) - use physconst, only: gravit - type(physics_state), intent(in) :: state type(physics_buffer_desc), pointer :: pbuf(:) real(r8),intent(out) :: liq_tau (nswbands,pcols,pver) ! extinction optical depth real(r8),intent(out) :: liq_tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: liq_tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w + real(r8),intent(out) :: liq_tau_w_g(nswbands,pcols,pver) ! asymmetry parameter * tau * w real(r8),intent(out) :: liq_tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w logical, intent(in) :: oldliqwp @@ -202,14 +93,6 @@ subroutine slingo_liq_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, li real(r8), dimension(nswbands) :: wavmin real(r8), dimension(nswbands) :: wavmax - ! Minimum cloud amount (as a fraction of the grid-box area) to - ! distinguish from clear sky - real(r8), parameter :: cldmin = 1.0e-80_r8 - - ! Decimal precision of cloud amount (0 -> preserve full resolution; - ! 10^-n -> preserve n digits of cloud amount) - real(r8), parameter :: cldeps = 0.0_r8 - ! A. Slingo's data for cloud particle radiative properties (from 'A GCM ! Parameterization for the Shortwave Properties of Water Clouds' JAS ! vol. 46 may 1989 pp 1419-1427) @@ -318,16 +201,9 @@ subroutine slingo_liq_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, li end do ! End do k=1,pver end do ! nswbands - !call outfld('CL_OD_SW_OLD',liq_tau(idx_sw_diag,:,:), pcols, lchnk) - !call outfld('REL_OLD',rel(:,:), pcols, lchnk) - !call outfld('CLWPTH_OLD',cliqwp(:,:), pcols, lchnk) - !call outfld('KEXT_OLD',kext(:,:), pcols, lchnk) - - end subroutine slingo_liq_optics_sw subroutine slingo_liq_get_rad_props_lw(state, pbuf, abs_od, oldliqwp) - use physconst, only: gravit type(physics_state), intent(in) :: state type(physics_buffer_desc),pointer :: pbuf(:) @@ -403,7 +279,6 @@ subroutine slingo_liq_get_rad_props_lw(state, pbuf, abs_od, oldliqwp) abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) enddo - end subroutine slingo_liq_get_rad_props_lw -end module slingo +end module slingo_liq_optics diff --git a/src/physics/cam_dev/physpkg.F90 b/src/physics/cam_dev/physpkg.F90 index 1c461c9a1c..46805c150e 100644 --- a/src/physics/cam_dev/physpkg.F90 +++ b/src/physics/cam_dev/physpkg.F90 @@ -839,16 +839,19 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_in, cam_out ) ! low level, so init it early. Must at least do this before radiation. call wv_sat_init + ! solar irradiance data modules + call solar_data_init() + ! Initialize rad constituents and their properties call rad_cnst_init() + + call radiation_init(pbuf2d) + call aer_rad_props_init() ! initialize carma call carma_init() - ! solar irradiance data modules - call solar_data_init() - ! Prognostic chemistry. call chem_init(phys_state,pbuf2d) @@ -884,8 +887,6 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_in, cam_out ) endif endif - call radiation_init(pbuf2d) - call cloud_diagnostics_init() call radheat_init(pref_mid) diff --git a/src/physics/camrt/radconstants.F90 b/src/physics/camrt/radconstants.F90 index 89503fd0f5..c95c8d2154 100644 --- a/src/physics/camrt/radconstants.F90 +++ b/src/physics/camrt/radconstants.F90 @@ -21,9 +21,6 @@ module radconstants public :: radconstants_init public :: rad_gas_index -! optics files specify a type. What length is it? -integer, parameter, public :: ot_length = 32 - ! SHORTWAVE DATA ! number of shorwave spectral intervals @@ -40,20 +37,6 @@ module radconstants integer, parameter, public :: idx_lw_diag = 2 ! index to (H20 window) LW band - -! Number of evenly spaced intervals in rh -! The globality of this mesh may not be necessary -! Perhaps it could be specific to the aerosol -! But it is difficult to see how refined it must be -! for lookup. This value was found to be sufficient -! for Sulfate and probably necessary to resolve the -! high variation near rh = 1. Alternative methods -! were found to be too slow. -! Optimal approach would be for cam to specify size of aerosol -! based on each aerosol's characteristics. Radiation -! should know nothing about hygroscopic growth! -integer, parameter, public :: nrh = 1000 - ! LONGWAVE DATA ! number of lw bands diff --git a/src/physics/camrt/radiation.F90 b/src/physics/camrt/radiation.F90 index 7cd74faa11..7ca7b15daa 100644 --- a/src/physics/camrt/radiation.F90 +++ b/src/physics/camrt/radiation.F90 @@ -877,7 +877,7 @@ subroutine radiation_tend( & ! Aerosol shortwave radiative properties real(r8) :: aer_tau (pcols,0:pver,nswbands) ! aerosol extinction optical depth real(r8) :: aer_tau_w (pcols,0:pver,nswbands) ! aerosol single scattering albedo * tau - real(r8) :: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol assymetry parameter * w * tau + real(r8) :: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol asymmetry parameter * w * tau real(r8) :: aer_tau_w_f(pcols,0:pver,nswbands) ! aerosol forward scattered fraction * w * tau ! Aerosol longwave absorption optical depth diff --git a/src/physics/camrt/radsw.F90 b/src/physics/camrt/radsw.F90 index e0d609a4cc..58138e4a5f 100644 --- a/src/physics/camrt/radsw.F90 +++ b/src/physics/camrt/radsw.F90 @@ -237,7 +237,7 @@ subroutine radcswmx(lchnk ,ncol , & ! real(r8),intent(in) :: E_aer_tau (pcols,0:pver,nswbands) ! aerosol extinction optical depth real(r8),intent(in) :: E_aer_tau_w (pcols,0:pver,nswbands) ! aerosol single scattering albedo * tau - real(r8),intent(in) :: E_aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol assymetry parameter * w * tau + real(r8),intent(in) :: E_aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol asymmetry parameter * w * tau real(r8),intent(in) :: E_aer_tau_w_f(pcols,0:pver,nswbands) ! aerosol forward scattered fraction * w * tau ! @@ -288,7 +288,7 @@ subroutine radcswmx(lchnk ,ncol , & ! real(r8):: aer_tau (pcols,0:pver,nswbands) ! aerosol extinction optical depth real(r8):: aer_tau_w (pcols,0:pver,nswbands) ! aerosol single scattering albedo * tau - real(r8):: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol assymetry parameter * w * tau + real(r8):: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol asymmetry parameter * w * tau real(r8):: aer_tau_w_f(pcols,0:pver,nswbands) ! aerosol forward scattered fraction * w * tau real(r8) :: pmid(pcols,pver) ! Level pressure real(r8) :: pint(pcols,pverp) ! Interface pressure @@ -1994,7 +1994,7 @@ subroutine raddedmx(coszrs ,ndayc ,abh2o , & ! real(r8) trmin ! Minimum total transmission allowed real(r8) wray ! Rayleigh single scatter albedo - real(r8) gray ! Rayleigh asymetry parameter + real(r8) gray ! Rayleigh asymmetry parameter real(r8) fray ! Rayleigh forward scattered fraction parameter (trmin = 1.e-3_r8) diff --git a/src/physics/rrtmg/aer_src/rrtmg_sw_reftra.f90 b/src/physics/rrtmg/aer_src/rrtmg_sw_reftra.f90 index d37f392025..1622e48450 100644 --- a/src/physics/rrtmg/aer_src/rrtmg_sw_reftra.f90 +++ b/src/physics/rrtmg/aer_src/rrtmg_sw_reftra.f90 @@ -43,7 +43,7 @@ subroutine reftra_sw(nlayers, ncol, lrtchk, pgg, prmuz, ptau, pw, & ! lrtchk = .t. for all layers in clear profile ! lrtchk = .t. for cloudy layers in cloud profile ! = .f. for clear layers in cloud profile -! pgg = assymetry factor +! pgg = asymmetry factor ! prmuz = cosine solar zenith angle ! ptau = optical thickness ! pw = single scattering albedo diff --git a/src/physics/rrtmg/oldcloud.F90 b/src/physics/rrtmg/oldcloud.F90 deleted file mode 100644 index 609c6b4668..0000000000 --- a/src/physics/rrtmg/oldcloud.F90 +++ /dev/null @@ -1,643 +0,0 @@ -module oldcloud - -!------------------------------------------------------------------------------------------------ -!------------------------------------------------------------------------------------------------ - -use shr_kind_mod, only: r8 => shr_kind_r8 -use ppgrid, only: pcols, pver, pverp -use physics_types, only: physics_state -use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_old_tim_idx, pbuf_get_field -use radconstants, only: nswbands, nlwbands, idx_sw_diag, ot_length, idx_lw_diag, get_sw_spectral_boundaries -use cam_abortutils, only: endrun -use cam_history, only: outfld -use rad_constituents, only: iceopticsfile, liqopticsfile -use ebert_curry, only: scalefactor - -implicit none -private -save - -public :: & - oldcloud_init, oldcloud_lw, old_liq_get_rad_props_lw, old_ice_get_rad_props_lw - -integer :: nmu, nlambda -real(r8), allocatable :: g_mu(:) ! mu samples on grid -real(r8), allocatable :: g_lambda(:,:) ! lambda scale samples on grid -real(r8), allocatable :: ext_sw_liq(:,:,:) -real(r8), allocatable :: ssa_sw_liq(:,:,:) -real(r8), allocatable :: asm_sw_liq(:,:,:) -real(r8), allocatable :: abs_lw_liq(:,:,:) - -integer :: n_g_d -real(r8), allocatable :: g_d_eff(:) ! radiative effective diameter samples on grid -real(r8), allocatable :: ext_sw_ice(:,:) -real(r8), allocatable :: ssa_sw_ice(:,:) -real(r8), allocatable :: asm_sw_ice(:,:) -real(r8), allocatable :: abs_lw_ice(:,:) - -! Minimum cloud amount (as a fraction of the grid-box area) to -! distinguish from clear sky -! - real(r8) cldmin - parameter (cldmin = 1.0e-80_r8) -! -! Decimal precision of cloud amount (0 -> preserve full resolution; -! 10^-n -> preserve n digits of cloud amount) -! - real(r8) cldeps - parameter (cldeps = 0.0_r8) - -! -! indexes into pbuf for optical parameters of MG clouds -! - integer :: iciwp_idx = 0 - integer :: iclwp_idx = 0 - integer :: cld_idx = 0 - integer :: rel_idx = 0 - integer :: rei_idx = 0 - -! indexes into constituents for old optics - integer :: & - ixcldice, & ! cloud ice water index - ixcldliq ! cloud liquid water index - - -!============================================================================== -contains -!============================================================================== - -subroutine oldcloud_init() - - use constituents, only: cnst_get_ind - - integer :: err - - iciwp_idx = pbuf_get_index('ICIWP',errcode=err) - iclwp_idx = pbuf_get_index('ICLWP',errcode=err) - cld_idx = pbuf_get_index('CLD') - rel_idx = pbuf_get_index('REL') - rei_idx = pbuf_get_index('REI') - - ! old optics - call cnst_get_ind('CLDICE', ixcldice) - call cnst_get_ind('CLDLIQ', ixcldliq) - - return - -end subroutine oldcloud_init - -!============================================================================== -! Private methods -!============================================================================== - -subroutine old_liquid_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, liq_tau_w_f, oldliqwp) - - use physconst, only: gravit - - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - - real(r8),intent(out) :: liq_tau (nswbands,pcols,pver) ! extinction optical depth - real(r8),intent(out) :: liq_tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: liq_tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w - real(r8),intent(out) :: liq_tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w - logical, intent(in) :: oldliqwp - - real(r8), pointer, dimension(:,:) :: rel - real(r8), pointer, dimension(:,:) :: cldn - real(r8), pointer, dimension(:,:) :: tmpptr - real(r8), dimension(pcols,pver) :: cliqwp - real(r8), dimension(nswbands) :: wavmin - real(r8), dimension(nswbands) :: wavmax - - ! Minimum cloud amount (as a fraction of the grid-box area) to - ! distinguish from clear sky - real(r8), parameter :: cldmin = 1.0e-80_r8 - - ! Decimal precision of cloud amount (0 -> preserve full resolution; - ! 10^-n -> preserve n digits of cloud amount) - real(r8), parameter :: cldeps = 0.0_r8 - - ! A. Slingo's data for cloud particle radiative properties (from 'A GCM - ! Parameterization for the Shortwave Properties of Water Clouds' JAS - ! vol. 46 may 1989 pp 1419-1427) - real(r8) :: abarl(4) = & ! A coefficient for extinction optical depth - (/ 2.817e-02_r8, 2.682e-02_r8,2.264e-02_r8,1.281e-02_r8/) - real(r8) :: bbarl(4) = & ! B coefficient for extinction optical depth - (/ 1.305_r8 , 1.346_r8 ,1.454_r8 ,1.641_r8 /) - real(r8) :: cbarl(4) = & ! C coefficient for single scat albedo - (/-5.62e-08_r8 ,-6.94e-06_r8 ,4.64e-04_r8 ,0.201_r8 /) - real(r8) :: dbarl(4) = & ! D coefficient for single scat albedo - (/ 1.63e-07_r8 , 2.35e-05_r8 ,1.24e-03_r8 ,7.56e-03_r8 /) - real(r8) :: ebarl(4) = & ! E coefficient for asymmetry parameter - (/ 0.829_r8 , 0.794_r8 ,0.754_r8 ,0.826_r8 /) - real(r8) :: fbarl(4) = & ! F coefficient for asymmetry parameter - (/ 2.482e-03_r8, 4.226e-03_r8,6.560e-03_r8,4.353e-03_r8/) - - real(r8) :: abarli ! A coefficient for current spectral band - real(r8) :: bbarli ! B coefficient for current spectral band - real(r8) :: cbarli ! C coefficient for current spectral band - real(r8) :: dbarli ! D coefficient for current spectral band - real(r8) :: ebarli ! E coefficient for current spectral band - real(r8) :: fbarli ! F coefficient for current spectral band - - ! Caution... A. Slingo recommends no less than 4.0 micro-meters nor - ! greater than 20 micro-meters - - integer :: ns, i, k, indxsl, Nday - integer :: lchnk, itim_old - real(r8) :: tmp1l, tmp2l, tmp3l, g - real(r8) :: kext(pcols,pver) - real(r8), pointer, dimension(:,:) :: iclwpth - - Nday = state%ncol - lchnk = state%lchnk - - itim_old = pbuf_old_tim_idx() - call pbuf_get_field(pbuf, cld_idx,cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) - call pbuf_get_field(pbuf, rel_idx,rel) - - if (oldliqwp) then - do k=1,pver - do i = 1,Nday - cliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/(gravit*max(0.01_r8,cldn(i,k))) - end do - end do - else - if (iclwp_idx<0) then - call endrun('old_liquid_optics_sw: oldliqwp must be set to true since ICLWP was not found in pbuf') - endif - ! The following is the eventual target specification for in cloud liquid water path. - call pbuf_get_field(pbuf, iclwp_idx, tmpptr) - cliqwp = tmpptr - endif - - call get_sw_spectral_boundaries(wavmin,wavmax,'microns') - - do ns = 1, nswbands - ! Set index for cloud particle properties based on the wavelength, - ! according to A. Slingo (1989) equations 1-3: - ! Use index 1 (0.25 to 0.69 micrometers) for visible - ! Use index 2 (0.69 - 1.19 micrometers) for near-infrared - ! Use index 3 (1.19 to 2.38 micrometers) for near-infrared - ! Use index 4 (2.38 to 4.00 micrometers) for near-infrared - if(wavmax(ns) <= 0.7_r8) then - indxsl = 1 - else if(wavmax(ns) <= 1.25_r8) then - indxsl = 2 - else if(wavmax(ns) <= 2.38_r8) then - indxsl = 3 - else if(wavmin(ns) > 2.38_r8) then - indxsl = 4 - end if - - ! Set cloud extinction optical depth, single scatter albedo, - ! asymmetry parameter, and forward scattered fraction: - abarli = abarl(indxsl) - bbarli = bbarl(indxsl) - cbarli = cbarl(indxsl) - dbarli = dbarl(indxsl) - ebarli = ebarl(indxsl) - fbarli = fbarl(indxsl) - - do k=1,pver - do i=1,Nday - - ! note that optical properties for liquid valid only - ! in range of 4.2 > rel > 16 micron (Slingo 89) - if (cldn(i,k) >= cldmin .and. cldn(i,k) >= cldeps) then - tmp1l = abarli + bbarli/min(max(4.2_r8,rel(i,k)),16._r8) - liq_tau(ns,i,k) = 1000._r8*cliqwp(i,k)*tmp1l - else - liq_tau(ns,i,k) = 0.0_r8 - endif - - tmp2l = 1._r8 - cbarli - dbarli*min(max(4.2_r8,rel(i,k)),16._r8) - tmp3l = fbarli*min(max(4.2_r8,rel(i,k)),16._r8) - ! Do not let single scatter albedo be 1. Delta-eddington solution - ! for non-conservative case has different analytic form from solution - ! for conservative case, and raddedmx is written for non-conservative case. - liq_tau_w(ns,i,k) = liq_tau(ns,i,k) * min(tmp2l,.999999_r8) - g = ebarli + tmp3l - liq_tau_w_g(ns,i,k) = liq_tau_w(ns,i,k) * g - liq_tau_w_f(ns,i,k) = liq_tau_w(ns,i,k) * g * g - - end do ! End do i=1,Nday - end do ! End do k=1,pver - end do ! nswbands - - !call outfld('CL_OD_SW_OLD',liq_tau(idx_sw_diag,:,:), pcols, lchnk) - !call outfld('REL_OLD',rel(:,:), pcols, lchnk) - !call outfld('CLWPTH_OLD',cliqwp(:,:), pcols, lchnk) - !call outfld('KEXT_OLD',kext(:,:), pcols, lchnk) - - -end subroutine old_liquid_optics_sw -!============================================================================== - -subroutine old_ice_optics_sw (state, pbuf, ice_tau, ice_tau_w, ice_tau_w_g, ice_tau_w_f, oldicewp) - - use physconst, only: gravit - - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - - real(r8),intent(out) :: ice_tau (nswbands,pcols,pver) ! extinction optical depth - real(r8),intent(out) :: ice_tau_w (nswbands,pcols,pver) ! single scattering albedo * tau - real(r8),intent(out) :: ice_tau_w_g(nswbands,pcols,pver) ! assymetry parameter * tau * w - real(r8),intent(out) :: ice_tau_w_f(nswbands,pcols,pver) ! forward scattered fraction * tau * w - logical, intent(in) :: oldicewp - - real(r8), pointer, dimension(:,:) :: rei - real(r8), pointer, dimension(:,:) :: cldn - real(r8), pointer, dimension(:,:) :: tmpptr - real(r8), dimension(pcols,pver) :: cicewp - real(r8), dimension(nswbands) :: wavmin - real(r8), dimension(nswbands) :: wavmax - ! - ! ice water coefficients (Ebert and Curry,1992, JGR, 97, 3831-3836) - real(r8) :: abari(4) = & ! a coefficient for extinction optical depth - (/ 3.448e-03_r8, 3.448e-03_r8,3.448e-03_r8,3.448e-03_r8/) - real(r8) :: bbari(4) = & ! b coefficient for extinction optical depth - (/ 2.431_r8 , 2.431_r8 ,2.431_r8 ,2.431_r8 /) - real(r8) :: cbari(4) = & ! c coefficient for single scat albedo - (/ 1.00e-05_r8 , 1.10e-04_r8 ,1.861e-02_r8,.46658_r8 /) - real(r8) :: dbari(4) = & ! d coefficient for single scat albedo - (/ 0.0_r8 , 1.405e-05_r8,8.328e-04_r8,2.05e-05_r8 /) - real(r8) :: ebari(4) = & ! e coefficient for asymmetry parameter - (/ 0.7661_r8 , 0.7730_r8 ,0.794_r8 ,0.9595_r8 /) - real(r8) :: fbari(4) = & ! f coefficient for asymmetry parameter - (/ 5.851e-04_r8, 5.665e-04_r8,7.267e-04_r8,1.076e-04_r8/) - - real(r8) :: abarii ! A coefficient for current spectral band - real(r8) :: bbarii ! B coefficient for current spectral band - real(r8) :: cbarii ! C coefficient for current spectral band - real(r8) :: dbarii ! D coefficient for current spectral band - real(r8) :: ebarii ! E coefficient for current spectral band - real(r8) :: fbarii ! F coefficient for current spectral band - - ! Minimum cloud amount (as a fraction of the grid-box area) to - ! distinguish from clear sky - real(r8), parameter :: cldmin = 1.0e-80_r8 - - ! Decimal precision of cloud amount (0 -> preserve full resolution; - ! 10^-n -> preserve n digits of cloud amount) - real(r8), parameter :: cldeps = 0.0_r8 - - integer :: ns, i, k, indxsl, lchnk, Nday - integer :: itim_old - real(r8) :: tmp1i, tmp2i, tmp3i, g - - Nday = state%ncol - lchnk = state%lchnk - - itim_old = pbuf_old_tim_idx() - call pbuf_get_field(pbuf, cld_idx,cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) - call pbuf_get_field(pbuf, rei_idx,rei) - - if(oldicewp) then - do k=1,pver - do i = 1,Nday - cicewp(i,k) = 1000.0_r8*state%q(i,k,ixcldice)*state%pdel(i,k) /(gravit* max(0.01_r8,cldn(i,k))) - end do - end do - else - if (iciwp_idx<=0) then - call endrun('old_ice_optics_sw: oldicewp must be set to true since ICIWP was not found in pbuf') - endif - call pbuf_get_field(pbuf, iciwp_idx, tmpptr) - cicewp(1:pcols,1:pver) = 1000.0_r8*tmpptr - endif - - call get_sw_spectral_boundaries(wavmin,wavmax,'microns') - - do ns = 1, nswbands - - if(wavmax(ns) <= 0.7_r8) then - indxsl = 1 - else if(wavmax(ns) <= 1.25_r8) then - indxsl = 2 - else if(wavmax(ns) <= 2.38_r8) then - indxsl = 3 - else if(wavmin(ns) > 2.38_r8) then - indxsl = 4 - end if - - abarii = abari(indxsl) - bbarii = bbari(indxsl) - cbarii = cbari(indxsl) - dbarii = dbari(indxsl) - ebarii = ebari(indxsl) - fbarii = fbari(indxsl) - - do k=1,pver - do i=1,Nday - - ! note that optical properties for ice valid only - ! in range of 13 > rei > 130 micron (Ebert and Curry 92) - if (cldn(i,k) >= cldmin .and. cldn(i,k) >= cldeps) then - tmp1i = abarii + bbarii/max(13._r8,min(scalefactor*rei(i,k),130._r8)) - ice_tau(ns,i,k) = cicewp(i,k)*tmp1i - else - ice_tau(ns,i,k) = 0.0_r8 - endif - - tmp2i = 1._r8 - cbarii - dbarii*min(max(13._r8,scalefactor*rei(i,k)),130._r8) - tmp3i = fbarii*min(max(13._r8,scalefactor*rei(i,k)),130._r8) - ! Do not let single scatter albedo be 1. Delta-eddington solution - ! for non-conservative case has different analytic form from solution - ! for conservative case, and raddedmx is written for non-conservative case. - ice_tau_w(ns,i,k) = ice_tau(ns,i,k) * min(tmp2i,.999999_r8) - g = ebarii + tmp3i - ice_tau_w_g(ns,i,k) = ice_tau_w(ns,i,k) * g - ice_tau_w_f(ns,i,k) = ice_tau_w(ns,i,k) * g * g - - end do ! End do i=1,Nday - end do ! End do k=1,pver - end do ! nswbands - -end subroutine old_ice_optics_sw -!============================================================================== - -subroutine oldcloud_lw(state,pbuf,cld_abs_od,oldwp) - use physconst, only: gravit - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - real(r8), intent(out) :: cld_abs_od(nlwbands,pcols,pver) ! [fraction] absorption optical depth, per layer - logical,intent(in) :: oldwp ! use old definition of waterpath - - - real(r8) :: gicewp(pcols,pver) - real(r8) :: gliqwp(pcols,pver) - real(r8) :: cicewp(pcols,pver) - real(r8) :: cliqwp(pcols,pver) - real(r8) :: ficemr(pcols,pver) - real(r8) :: cwp(pcols,pver) - real(r8) :: cldtau(pcols,pver) - - real(r8), pointer, dimension(:,:) :: cldn - real(r8), pointer, dimension(:,:) :: rei - integer :: ncol, itim_old, lwband, i, k, lchnk - real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth - - real(r8) :: kabs, kabsi - real(r8) kabsl ! longwave liquid absorption coeff (m**2/g) - parameter (kabsl = 0.090361_r8) - - - - ncol = state%ncol - lchnk = state%lchnk - - itim_old = pbuf_old_tim_idx() - call pbuf_get_field(pbuf, rei_idx, rei) - call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) - - if (oldwp) then - do k=1,pver - do i = 1,ncol - gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. - gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. - cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. - cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. - ficemr(i,k) = state%q(i,k,ixcldice) / & - max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) - end do - end do - cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) - else - if (iclwp_idx<=0 .or. iciwp_idx<=0) then - call endrun('oldcloud_lw: oldwp must be set to true since ICIWP and/or ICLWP were not found in pbuf') - endif - call pbuf_get_field(pbuf, iclwp_idx, iclwpth) - call pbuf_get_field(pbuf, iciwp_idx, iciwpth) - do k=1,pver - do i = 1,ncol - cwp(i,k) = 1000.0_r8 *iclwpth(i,k) + 1000.0_r8 *iciwpth(i, k) - ficemr(i,k) = 1000.0_r8 * iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) - end do - end do - endif - - do k=1,pver - do i=1,ncol - - !note that optical properties for ice valid only - !in range of 13 > rei > 130 micron (Ebert and Curry 92) - kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) - kabs = kabsl*(1._r8-ficemr(i,k)) + kabsi*ficemr(i,k) - !emis(i,k) = 1._r8 - exp(-1.66_r8*kabs*clwp(i,k)) - cldtau(i,k) = kabs*cwp(i,k) - end do - end do -! - do lwband = 1,nlwbands - cld_abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) - enddo - -end subroutine oldcloud_lw - -!============================================================================== -subroutine old_liq_get_rad_props_lw(state, pbuf, abs_od, oldliqwp) - use physconst, only: gravit - - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) - logical, intent(in) :: oldliqwp - - real(r8) :: gicewp(pcols,pver) - real(r8) :: gliqwp(pcols,pver) - real(r8) :: cicewp(pcols,pver) - real(r8) :: cliqwp(pcols,pver) - real(r8) :: ficemr(pcols,pver) - real(r8) :: cwp(pcols,pver) - real(r8) :: cldtau(pcols,pver) - - real(r8), pointer, dimension(:,:) :: cldn - real(r8), pointer, dimension(:,:) :: rei - integer :: ncol, itim_old, lwband, i, k, lchnk - - real(r8) :: kabs, kabsi - real(r8) kabsl ! longwave liquid absorption coeff (m**2/g) - parameter (kabsl = 0.090361_r8) - - real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth - - ncol=state%ncol - lchnk = state%lchnk - - itim_old = pbuf_old_tim_idx() - call pbuf_get_field(pbuf, rei_idx, rei) - call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) - - if (oldliqwp) then - do k=1,pver - do i = 1,ncol - gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. - gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. - cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. - cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. - ficemr(i,k) = state%q(i,k,ixcldice) / & - max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) - end do - end do - cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) - else - if (iclwp_idx<=0 .or. iciwp_idx<=0) then - call endrun('old_liq_get_rad_props_lw: oldliqwp must be set to true since ICIWP and/or ICLWP were not found in pbuf') - endif - call pbuf_get_field(pbuf, iclwp_idx, iclwpth) - call pbuf_get_field(pbuf, iciwp_idx, iciwpth) - do k=1,pver - do i = 1,ncol - cwp(i,k) = 1000.0_r8 *iclwpth(i,k) + 1000.0_r8 *iciwpth(i, k) - ficemr(i,k) = 1000.0_r8 * iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) - end do - end do - endif - - - do k=1,pver - do i=1,ncol - - ! Note from Andrew Conley: - ! Optics for RK no longer supported, This is constructed to get - ! close to bit for bit. Otherwise we could simply use liquid water path - !note that optical properties for ice valid only - !in range of 13 > rei > 130 micron (Ebert and Curry 92) - kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) - kabs = kabsl*(1._r8-ficemr(i,k)) ! + kabsi*ficemr(i,k) - !emis(i,k) = 1._r8 - exp(-1.66_r8*kabs*clwp(i,k)) - cldtau(i,k) = kabs*cwp(i,k) - end do - end do -! - do lwband = 1,nlwbands - abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) - enddo - - -end subroutine old_liq_get_rad_props_lw -!============================================================================== - -subroutine old_ice_get_rad_props_lw(state, pbuf, abs_od, oldicewp) - use physconst, only: gravit - type(physics_state), intent(in) :: state - type(physics_buffer_desc),pointer :: pbuf(:) - real(r8), intent(out) :: abs_od(nlwbands,pcols,pver) - logical, intent(in) :: oldicewp - - real(r8) :: gicewp(pcols,pver) - real(r8) :: gliqwp(pcols,pver) - real(r8) :: cicewp(pcols,pver) - real(r8) :: cliqwp(pcols,pver) - real(r8) :: ficemr(pcols,pver) - real(r8) :: cwp(pcols,pver) - real(r8) :: cldtau(pcols,pver) - - real(r8), pointer, dimension(:,:) :: cldn - real(r8), pointer, dimension(:,:) :: rei - integer :: ncol, itim_old, lwband, i, k, lchnk - - real(r8) :: kabs, kabsi - - real(r8) kabsl ! longwave liquid absorption coeff (m**2/g) - parameter (kabsl = 0.090361_r8) - - real(r8), pointer, dimension(:,:) :: iclwpth, iciwpth - - - ncol = state%ncol - lchnk = state%lchnk - - itim_old = pbuf_old_tim_idx() - call pbuf_get_field(pbuf, rei_idx, rei) - call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/)) - - if(oldicewp) then - do k=1,pver - do i = 1,ncol - gicewp(i,k) = state%q(i,k,ixcldice)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box ice water path. - gliqwp(i,k) = state%q(i,k,ixcldliq)*state%pdel(i,k)/gravit*1000.0_r8 ! Grid box liquid water path. - cicewp(i,k) = gicewp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud ice water path. - cliqwp(i,k) = gliqwp(i,k) / max(0.01_r8,cldn(i,k)) ! In-cloud liquid water path. - ficemr(i,k) = state%q(i,k,ixcldice) / & - max(1.e-10_r8,(state%q(i,k,ixcldice)+state%q(i,k,ixcldliq))) - end do - end do - cwp(:ncol,:pver) = cicewp(:ncol,:pver) + cliqwp(:ncol,:pver) - else - if (iclwp_idx<=0 .or. iciwp_idx<=0) then - call endrun('old_ice_get_rad_props_lw: oldicewp must be set to true since ICIWP and/or ICLWP were not found in pbuf') - endif - call pbuf_get_field(pbuf, iclwp_idx, iclwpth) - call pbuf_get_field(pbuf, iciwp_idx, iciwpth) - do k=1,pver - do i = 1,ncol - cwp(i,k) = 1000.0_r8 *iciwpth(i,k) + 1000.0_r8 *iclwpth(i,k) - ficemr(i,k) = 1000.0_r8*iciwpth(i,k)/(max(1.e-18_r8,cwp(i,k))) - end do - end do - endif - - do k=1,pver - do i=1,ncol - - ! Note from Andrew Conley: - ! Optics for RK no longer supported, This is constructed to get - ! close to bit for bit. Otherwise we could simply use ice water path - !note that optical properties for ice valid only - !in range of 13 > rei > 130 micron (Ebert and Curry 92) - kabsi = 0.005_r8 + 1._r8/min(max(13._r8,scalefactor*rei(i,k)),130._r8) - kabs = kabsi*ficemr(i,k) ! kabsl*(1._r8-ficemr(i,k)) + kabsi*ficemr(i,k) - !emis(i,k) = 1._r8 - exp(-1.66_r8*kabs*clwp(i,k)) - cldtau(i,k) = kabs*cwp(i,k) - end do - end do -! - do lwband = 1,nlwbands - abs_od(lwband,1:ncol,1:pver)=cldtau(1:ncol,1:pver) - enddo - - !if(oldicewp) then - ! call outfld('CIWPTH_OLD',cicewp(:,:)/1000,pcols,lchnk) - !else - ! call outfld('CIWPTH_OLD',iciwpth(:,:),pcols,lchnk) - !endif - !call outfld('CI_OD_LW_OLD',cldtau(:,:),pcols,lchnk) - -end subroutine old_ice_get_rad_props_lw -!============================================================================== - -subroutine cloud_total_vis_diag_out(lchnk, nnite, idxnite, tau, radsuffix) - - ! output total aerosol optical depth for the visible band - - use cam_history, only: outfld - use cam_history_support, only : fillvalue - - integer, intent(in) :: lchnk - integer, intent(in) :: nnite ! number of night columns - integer, intent(in) :: idxnite(nnite) ! local column indices of night columns - real(r8), intent(in) :: tau(:,:) - character(len=*), intent(in) :: radsuffix ! identifies whether the radiation call - ! is for the climate calc or a diagnostic calc - - ! Local variables - integer :: i - real(r8) :: tmp(pcols) - !----------------------------------------------------------------------------- - - ! compute total aerosol optical depth output where only daylight columns - tmp(:) = sum(tau(:,:), 2) - do i = 1, nnite - tmp(idxnite(i)) = fillvalue - end do - !call outfld('cloudOD_v'//trim(radsuffix), tmp, pcols, lchnk) - -end subroutine cloud_total_vis_diag_out - -!============================================================================== - -end module oldcloud diff --git a/src/physics/rrtmg/radconstants.F90 b/src/physics/rrtmg/radconstants.F90 index f4f8c76b9c..601bcd3cf6 100644 --- a/src/physics/rrtmg/radconstants.F90 +++ b/src/physics/rrtmg/radconstants.F90 @@ -63,19 +63,6 @@ module radconstants integer, parameter, public :: rrtmg_sw_cloudsim_band = 9 ! rrtmg band for .67 micron -! Number of evenly spaced intervals in rh -! The globality of this mesh may not be necessary -! Perhaps it could be specific to the aerosol -! But it is difficult to see how refined it must be -! for lookup. This value was found to be sufficient -! for Sulfate and probably necessary to resolve the -! high variation near rh = 1. Alternative methods -! were found to be too slow. -! Optimal approach would be for cam to specify size of aerosol -! based on each aerosol's characteristics. Radiation -! should know nothing about hygroscopic growth! -integer, parameter, public :: nrh = 1000 - ! LONGWAVE DATA ! These are indices to the band for diagnostic output @@ -123,9 +110,6 @@ module radconstants real(r8), public, parameter :: minmmr(nradgas) & = epsilon(1._r8) -! Length of "optics type" string specified in optics files. -integer, parameter, public :: ot_length = 32 - public :: rad_gas_index public :: get_number_sw_bands, & diff --git a/src/physics/rrtmg/radiation.F90 b/src/physics/rrtmg/radiation.F90 index 4ca347d749..12f8cd7ec6 100644 --- a/src/physics/rrtmg/radiation.F90 +++ b/src/physics/rrtmg/radiation.F90 @@ -728,8 +728,8 @@ subroutine radiation_tend( & ice_cloud_get_rad_props_lw, cloud_rad_props_get_lw, & grau_cloud_get_rad_props_lw, get_grau_optics_sw, & snow_cloud_get_rad_props_lw, get_snow_optics_sw - use slingo, only: slingo_liq_get_rad_props_lw, slingo_liq_optics_sw - use ebert_curry, only: ec_ice_optics_sw, ec_ice_get_rad_props_lw + use slingo_liq_optics, only: slingo_liq_get_rad_props_lw, slingo_liq_optics_sw + use ebert_curry_ice_optics, only: ec_ice_optics_sw, ec_ice_get_rad_props_lw use rad_solar_var, only: get_variability use radsw, only: rad_rrtmg_sw @@ -806,28 +806,28 @@ subroutine radiation_tend( & ! cloud radiative parameters are "in cloud" not "in cell" real(r8) :: ice_tau (nswbands,pcols,pver) ! ice extinction optical depth real(r8) :: ice_tau_w (nswbands,pcols,pver) ! ice single scattering albedo * tau - real(r8) :: ice_tau_w_g(nswbands,pcols,pver) ! ice assymetry parameter * tau * w + real(r8) :: ice_tau_w_g(nswbands,pcols,pver) ! ice asymmetry parameter * tau * w real(r8) :: ice_tau_w_f(nswbands,pcols,pver) ! ice forward scattered fraction * tau * w real(r8) :: ice_lw_abs (nlwbands,pcols,pver) ! ice absorption optics depth (LW) ! cloud radiative parameters are "in cloud" not "in cell" real(r8) :: liq_tau (nswbands,pcols,pver) ! liquid extinction optical depth real(r8) :: liq_tau_w (nswbands,pcols,pver) ! liquid single scattering albedo * tau - real(r8) :: liq_tau_w_g(nswbands,pcols,pver) ! liquid assymetry parameter * tau * w + real(r8) :: liq_tau_w_g(nswbands,pcols,pver) ! liquid asymmetry parameter * tau * w real(r8) :: liq_tau_w_f(nswbands,pcols,pver) ! liquid forward scattered fraction * tau * w real(r8) :: liq_lw_abs (nlwbands,pcols,pver) ! liquid absorption optics depth (LW) ! cloud radiative parameters are "in cloud" not "in cell" real(r8) :: cld_tau (nswbands,pcols,pver) ! cloud extinction optical depth real(r8) :: cld_tau_w (nswbands,pcols,pver) ! cloud single scattering albedo * tau - real(r8) :: cld_tau_w_g(nswbands,pcols,pver) ! cloud assymetry parameter * w * tau + real(r8) :: cld_tau_w_g(nswbands,pcols,pver) ! cloud asymmetry parameter * w * tau real(r8) :: cld_tau_w_f(nswbands,pcols,pver) ! cloud forward scattered fraction * w * tau real(r8) :: cld_lw_abs (nlwbands,pcols,pver) ! cloud absorption optics depth (LW) ! cloud radiative parameters are "in cloud" not "in cell" real(r8) :: snow_tau (nswbands,pcols,pver) ! snow extinction optical depth real(r8) :: snow_tau_w (nswbands,pcols,pver) ! snow single scattering albedo * tau - real(r8) :: snow_tau_w_g(nswbands,pcols,pver) ! snow assymetry parameter * tau * w + real(r8) :: snow_tau_w_g(nswbands,pcols,pver) ! snow asymmetry parameter * tau * w real(r8) :: snow_tau_w_f(nswbands,pcols,pver) ! snow forward scattered fraction * tau * w real(r8) :: snow_lw_abs (nlwbands,pcols,pver)! snow absorption optics depth (LW) @@ -835,7 +835,7 @@ subroutine radiation_tend( & ! cloud radiative parameters are "in cloud" not "in cell" real(r8) :: grau_tau (nswbands,pcols,pver) ! graupel extinction optical depth real(r8) :: grau_tau_w (nswbands,pcols,pver) ! graupel single scattering albedo * tau - real(r8) :: grau_tau_w_g(nswbands,pcols,pver) ! graupel assymetry parameter * tau * w + real(r8) :: grau_tau_w_g(nswbands,pcols,pver) ! graupel asymmetry parameter * tau * w real(r8) :: grau_tau_w_f(nswbands,pcols,pver) ! graupel forward scattered fraction * tau * w real(r8) :: grau_lw_abs (nlwbands,pcols,pver)! graupel absorption optics depth (LW) @@ -843,7 +843,7 @@ subroutine radiation_tend( & real(r8) :: cldfprime(pcols,pver) ! combined cloud fraction (snow plus regular) real(r8) :: c_cld_tau (nswbands,pcols,pver) ! combined cloud extinction optical depth real(r8) :: c_cld_tau_w (nswbands,pcols,pver) ! combined cloud single scattering albedo * tau - real(r8) :: c_cld_tau_w_g(nswbands,pcols,pver) ! combined cloud assymetry parameter * w * tau + real(r8) :: c_cld_tau_w_g(nswbands,pcols,pver) ! combined cloud asymmetry parameter * w * tau real(r8) :: c_cld_tau_w_f(nswbands,pcols,pver) ! combined cloud forward scattered fraction * w * tau real(r8) :: c_cld_lw_abs (nlwbands,pcols,pver) ! combined cloud absorption optics depth (LW) @@ -855,7 +855,7 @@ subroutine radiation_tend( & ! Aerosol radiative properties real(r8) :: aer_tau (pcols,0:pver,nswbands) ! aerosol extinction optical depth real(r8) :: aer_tau_w (pcols,0:pver,nswbands) ! aerosol single scattering albedo * tau - real(r8) :: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol assymetry parameter * w * tau + real(r8) :: aer_tau_w_g(pcols,0:pver,nswbands) ! aerosol asymmetry parameter * w * tau real(r8) :: aer_tau_w_f(pcols,0:pver,nswbands) ! aerosol forward scattered fraction * w * tau real(r8) :: aer_lw_abs (pcols,pver,nlwbands) ! aerosol absorption optics depth (LW) diff --git a/src/physics/rrtmg/radsw.F90 b/src/physics/rrtmg/radsw.F90 index df222557dd..994d56b44e 100644 --- a/src/physics/rrtmg/radsw.F90 +++ b/src/physics/rrtmg/radsw.F90 @@ -255,7 +255,7 @@ subroutine rad_rrtmg_sw(lchnk,ncol ,rrtmg_levs ,r_state , & ! Aerosol radiative property arrays real(r8) :: tauxar(pcols,0:pver) ! aerosol extinction optical depth real(r8) :: wa(pcols,0:pver) ! aerosol single scattering albedo - real(r8) :: ga(pcols,0:pver) ! aerosol assymetry parameter + real(r8) :: ga(pcols,0:pver) ! aerosol asymmetry parameter real(r8) :: fa(pcols,0:pver) ! aerosol forward scattered fraction ! CRM diff --git a/src/physics/rrtmgp/mcica_subcol_gen.F90 b/src/physics/rrtmgp/mcica_subcol_gen.F90 new file mode 100644 index 0000000000..85bea8281c --- /dev/null +++ b/src/physics/rrtmgp/mcica_subcol_gen.F90 @@ -0,0 +1,286 @@ +module mcica_subcol_gen + +!---------------------------------------------------------------------------------------- +! +! Purpose: Create McICA stochastic arrays for cloud optical properties. +! Input cloud optical properties directly: cloud optical depth, single +! scattering albedo and asymmetry parameter. Output will be stochastic +! arrays of these variables. (longwave scattering is not yet available) +! +! Original code: From RRTMG, with the following copyright notice, +! based on Raisanen et al., QJRMS, 2004: +! -------------------------------------------------------------------------- +! | | +! | Copyright 2006-2007, Atmospheric & Environmental Research, Inc. (AER). | +! | This software may be used, copied, or redistributed as long as it is | +! | not sold and this copyright notice is reproduced on each copy made. | +! | This model is provided as is without any express or implied warranties. | +! | (http://www.rtweb.aer.com/) | +! | | +! -------------------------------------------------------------------------- +! This code is a refactored version of code originally in the files +! mcica_subcol_gen_lw.F90 and mcica_subcol_gen_sw.F90 +! +! Uses the KISS random number generator. +! +! Overlap assumption: maximum-random. +! +!---------------------------------------------------------------------------------------- + +use shr_kind_mod, only: r8 => shr_kind_r8 +use ppgrid, only: pcols, pver +use shr_RandNum_mod, only: ShrKissRandGen +use mo_gas_optics_rrtmgp, only: ty_gas_optics_rrtmgp + +implicit none +private +save + +public :: mcica_subcol_lw, mcica_subcol_sw + +!======================================================================================== +contains +!======================================================================================== + +subroutine mcica_subcol_lw( & + kdist, nbnd, ngpt, ncol, nver, & + changeseed, pmid, cldfrac, tauc, taucmcl ) + + ! Arrays use CAM vertical index convention: index increases from top to bottom. + ! This index ordering is assumed in the maximum-random overlap algorithm which starts + ! at the top of a column and marches down, with each layer depending on the state + ! of the layer above it. + ! + ! For GCM mode, changeseed must be offset between LW and SW by at least the + ! number of subcolumns + + ! arguments + class(ty_gas_optics_rrtmgp), intent(in) :: kdist ! spectral information + integer, intent(in) :: nbnd ! number of spectral bands + integer, intent(in) :: ngpt ! number of subcolumns (g-point intervals) + integer, intent(in) :: ncol ! number of columns + integer, intent(in) :: nver ! number of layers + integer, intent(in) :: changeseed ! if the subcolumn generator is called multiple times, + ! permute the seed between each call. + real(r8), intent(in) :: pmid(pcols,pver) ! layer pressures (Pa) + real(r8), intent(in) :: cldfrac(ncol,nver) ! layer cloud fraction + real(r8), intent(in) :: tauc(nbnd,ncol,nver) ! cloud optical depth + real(r8), intent(out) :: taucmcl(ngpt,ncol,nver) ! subcolumn cloud optical depth [mcica] + + ! Local variables + + integer :: i, isubcol, k, n + + real(r8), parameter :: cldmin = 1.0e-80_r8 ! min cloud fraction + real(r8) :: cldf(ncol,pver) ! cloud fraction clipped to cldmin + + type(ShrKissRandGen) :: kiss_gen ! KISS RNG object + integer :: kiss_seed(ncol,4) + real(r8) :: rand_num_1d(ncol,1) ! random number (kissvec) + real(r8) :: rand_num(ncol,nver) ! random number (kissvec) + + real(r8) :: cdf(ngpt,ncol,nver) ! random numbers + logical :: iscloudy(ngpt,ncol,nver) ! flag that says whether a gridbox is cloudy + !------------------------------------------------------------------------------------------ + + ! clip cloud fraction + cldf(:,:) = cldfrac(:ncol,:) + where (cldf(:,:) < cldmin) + cldf(:,:) = 0._r8 + end where + + ! Create a seed that depends on the state of the columns. + ! Use pmid from bottom four layers. + do i = 1, ncol + kiss_seed(i,1) = (pmid(i,pver) - int(pmid(i,pver))) * 1000000000 + kiss_seed(i,2) = (pmid(i,pver-1) - int(pmid(i,pver-1))) * 1000000000 + kiss_seed(i,3) = (pmid(i,pver-2) - int(pmid(i,pver-2))) * 1000000000 + kiss_seed(i,4) = (pmid(i,pver-3) - int(pmid(i,pver-3))) * 1000000000 + end do + + ! create the RNG object + kiss_gen = ShrKissRandGen(kiss_seed) + + ! Advance randum number generator by changeseed values + do i = 1, changeSeed + call kiss_gen%random(rand_num_1d) + end do + + ! Generate random numbers in each subcolumn at every level + do isubcol = 1,ngpt + call kiss_gen%random(rand_num) + cdf(isubcol,:,:) = rand_num(:,:) + enddo + + ! Maximum-Random overlap + ! i) pick a random number for top layer. + ! ii) walk down the column: + ! - if the layer above is cloudy, use the same random number as in the layer above + ! - if the layer above is clear, use a new random number + + do k = 2, nver + do i = 1, ncol + do isubcol = 1, ngpt + if (cdf(isubcol,i,k-1) > 1._r8 - cldf(i,k-1) ) then + cdf(isubcol,i,k) = cdf(isubcol,i,k-1) + else + cdf(isubcol,i,k) = cdf(isubcol,i,k) * (1._r8 - cldf(i,k-1)) + end if + end do + end do + end do + + do k = 1, nver + iscloudy(:,:,k) = (cdf(:,:,k) >= 1._r8 - spread(cldf(:,k), dim=1, nCopies=ngpt) ) + end do + + ! -- generate subcolumns for homogeneous clouds ----- + ! where there is a cloud, set the subcolumn cloud properties; + ! incoming tauc should be in-cloud quantites and not grid-averaged quantities + do k = 1,nver + do i = 1,ncol + do isubcol = 1,ngpt + if (iscloudy(isubcol,i,k) .and. (cldf(i,k) > 0._r8) ) then + n = kdist%convert_gpt2band(isubcol) + taucmcl(isubcol,i,k) = tauc(n,i,k) + else + taucmcl(isubcol,i,k) = 0._r8 + end if + end do + end do + end do + + call kiss_gen%finalize() + +end subroutine mcica_subcol_lw + +!======================================================================================== + +subroutine mcica_subcol_sw( & + kdist, nbnd, ngpt, ncol, nlay, nver, changeseed, & + pmid, cldfrac, tauc, ssac, asmc, & + taucmcl, ssacmcl, asmcmcl) + + ! Arrays use CAM vertical index convention: index increases from top to bottom. + ! This index ordering is assumed in the maximum-random overlap algorithm which starts + ! at the top of a column and marches down, with each layer depending on the state + ! of the layer above it. + ! + ! For GCM mode, changeseed must be offset between LW and SW by at least the + ! number of subcolumns + + ! arguments + class(ty_gas_optics_rrtmgp), intent(in) :: kdist ! spectral information + integer, intent(in) :: nbnd ! number of spectral bands + integer, intent(in) :: ngpt ! number of subcolumns (g-point intervals) + integer, intent(in) :: ncol ! number of columns + integer, intent(in) :: nlay ! number of vertical layers in radiation calc; + ! may include an "extra layer" + integer, intent(in) :: nver ! number of CAM's vertical layers in rad calc + integer, intent(in) :: changeseed ! if the subcolumn generator is called multiple times, + ! permute the seed between each call. + real(r8), intent(in) :: pmid(ncol,nlay) ! layer midpoint pressures (Pa) + real(r8), intent(in) :: cldfrac(ncol,nver) ! layer cloud fraction + real(r8), intent(in) :: tauc(nbnd,ncol,nver) ! cloud optical depth + real(r8), intent(in) :: ssac(nbnd,ncol,nver) ! cloud single scattering albedo (non-delta scaled) + real(r8), intent(in) :: asmc(nbnd,ncol,nver) ! cloud asymmetry parameter (non-delta scaled) + + + real(r8), intent(out) :: taucmcl(ngpt,ncol,nver) ! subcolumn cloud optical depth [mcica] + real(r8), intent(out) :: ssacmcl(ngpt,ncol,nver) ! subcolumn cloud single scattering albedo [mcica] + real(r8), intent(out) :: asmcmcl(ngpt,ncol,nver) ! subcolumn cloud asymmetry parameter [mcica] + + ! Local vars + + integer :: i, isubcol, k, n + + real(r8), parameter :: cldmin = 1.0e-80_r8 ! min cloud fraction + real(r8) :: cldf(ncol,nver) ! cloud fraction clipped to cldmin + + type(ShrKissRandGen) :: kiss_gen ! KISS RNG object + integer :: kiss_seed(ncol,4) + real(r8) :: rand_num_1d(ncol,1) ! random number (kissvec) + real(r8) :: rand_num(ncol,nver) ! random number (kissvec) + + real(r8) :: cdf(ngpt,ncol,nver) ! random numbers + logical :: iscloudy(ngpt,ncol,nver) ! flag that says whether a gridbox is cloudy + !------------------------------------------------------------------------------------------ + + ! clip cloud fraction + cldf(:,:) = cldfrac(:ncol,:) + where (cldf(:,:) < cldmin) + cldf(:,:) = 0._r8 + end where + + ! Create a seed that depends on the state of the columns. + ! Use pmid from bottom four layers. + do i = 1, ncol + kiss_seed(i,1) = (pmid(i,nlay) - int(pmid(i,nlay))) * 1000000000 + kiss_seed(i,2) = (pmid(i,nlay-1) - int(pmid(i,nlay-1))) * 1000000000 + kiss_seed(i,3) = (pmid(i,nlay-2) - int(pmid(i,nlay-2))) * 1000000000 + kiss_seed(i,4) = (pmid(i,nlay-3) - int(pmid(i,nlay-3))) * 1000000000 + end do + + ! create the RNG object + kiss_gen = ShrKissRandGen(kiss_seed) + + ! Advance randum number generator by changeseed values + do i = 1, changeSeed + call kiss_gen%random(rand_num_1d) + end do + + ! Generate random numbers in each subcolumn at every level + do isubcol = 1,ngpt + call kiss_gen%random(rand_num) + cdf(isubcol,:,:) = rand_num(:,:) + enddo + + ! Maximum-Random overlap + ! i) pick a random number for top layer. + ! ii) walk down the column: + ! - if the layer above is cloudy, use the same random number as in the layer above + ! - if the layer above is clear, use a new random number + + do k = 2, nver + do i = 1, ncol + do isubcol = 1, ngpt + if (cdf(isubcol,i,k-1) > 1._r8 - cldf(i,k-1) ) then + cdf(isubcol,i,k) = cdf(isubcol,i,k-1) + else + cdf(isubcol,i,k) = cdf(isubcol,i,k) * (1._r8 - cldf(i,k-1)) + end if + end do + end do + end do + + do k = 1, nver + iscloudy(:,:,k) = (cdf(:,:,k) >= 1._r8 - spread(cldf(:,k), dim=1, nCopies=ngpt) ) + end do + + ! -- generate subcolumns for homogeneous clouds ----- + ! where there is a cloud, set the subcolumn cloud properties; + ! incoming tauc should be in-cloud quantites and not grid-averaged quantities + do k = 1,nver + do i = 1,ncol + do isubcol = 1,ngpt + if (iscloudy(isubcol,i,k) .and. (cldf(i,k) > 0._r8) ) then + n = kdist%convert_gpt2band(isubcol) + taucmcl(isubcol,i,k) = tauc(n,i,k) + ssacmcl(isubcol,i,k) = ssac(n,i,k) + asmcmcl(isubcol,i,k) = asmc(n,i,k) + else + taucmcl(isubcol,i,k) = 0._r8 + ssacmcl(isubcol,i,k) = 1._r8 + asmcmcl(isubcol,i,k) = 0._r8 + end if + end do + end do + end do + + call kiss_gen%finalize() + +end subroutine mcica_subcol_sw + + +end module mcica_subcol_gen + diff --git a/src/physics/rrtmgp/radconstants.F90 b/src/physics/rrtmgp/radconstants.F90 new file mode 100644 index 0000000000..f490b81b7b --- /dev/null +++ b/src/physics/rrtmgp/radconstants.F90 @@ -0,0 +1,295 @@ +module radconstants + +! This module contains constants that are specific to the radiative transfer +! code used in the RRTMGP model. + +use shr_kind_mod, only: r8 => shr_kind_r8 +use mo_gas_optics_rrtmgp, only: ty_gas_optics_rrtmgp +use cam_abortutils, only: endrun + +implicit none +private +save + +! Number of bands in SW and LW. These values must match data in the RRTMGP coefficients datasets. +! But they are needed to allocate space in the physics buffer and need to be available before the +! RRTMGP datasets are read. So they are set as parameters here and checked in the +! set_wavenumber_bands subroutine after the datasets are read. +integer, parameter, public :: nswbands = 14 +integer, parameter, public :: nlwbands = 16 + +! Band limits (set from data in RRTMGP coefficient datasets) +real(r8), target :: wavenumber_low_shortwave(nswbands) +real(r8), target :: wavenumber_high_shortwave(nswbands) +real(r8), target :: wavenumber_low_longwave(nlwbands) +real(r8), target :: wavenumber_high_longwave(nlwbands) + +logical :: wavenumber_boundaries_set = .false. + +integer, public, protected :: nswgpts ! number of SW g-points +integer, public, protected :: nlwgpts ! number of LW g-points + +! These are indices to specific bands for diagnostic output and COSP input. +integer, public, protected :: idx_sw_diag = -1 ! band contains 500-nm wave +integer, public, protected :: idx_nir_diag = -1 ! band contains 1000-nm wave +integer, public, protected :: idx_uv_diag = -1 ! band contains 400-nm wave +integer, public, protected :: idx_lw_diag = -1 ! band contains 1000 cm-1 wave (H20 window) +integer, public, protected :: idx_sw_cloudsim = -1 ! band contains 670-nm wave (for COSP) +integer, public, protected :: idx_lw_cloudsim = -1 ! band contains 10.5 micron wave (for COSP) + +! GASES TREATED BY RADIATION (line spectra) +! These names are recognized by RRTMGP. They are in the coefficients files as +! lower case strings. These upper case names are used by CAM's namelist and +! rad_constituents module. +integer, public, parameter :: gasnamelength = 5 +integer, public, parameter :: nradgas = 8 +character(len=gasnamelength), public, parameter :: gaslist(nradgas) & + = (/'H2O ','O3 ', 'O2 ', 'CO2 ', 'N2O ', 'CH4 ', 'CFC11', 'CFC12'/) + +! what is the minimum mass mixing ratio that can be supported by radiation implementation? +real(r8), public, parameter :: minmmr(nradgas) = epsilon(1._r8) + +public :: & + set_wavenumber_bands, & + get_sw_spectral_boundaries, & + get_lw_spectral_boundaries, & + get_band_index_by_value, & + rad_gas_index + +!========================================================================================= +contains +!========================================================================================= + +subroutine set_wavenumber_bands(kdist_sw, kdist_lw) + + ! Set the low and high limits of the wavenumber grid for sw and lw. + ! Values come from RRTMGP coefficients datasets, and are stored in the + ! kdist objects. + ! + ! Set band indices for bands containing specific wavelengths. + + ! Arguments + type(ty_gas_optics_rrtmgp), intent(in) :: kdist_sw + type(ty_gas_optics_rrtmgp), intent(in) :: kdist_lw + + ! Local variables + integer :: istat + real(r8), allocatable :: values(:,:) + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'set_wavenumber_bands' + !---------------------------------------------------------------------------- + + ! Check that number of sw/lw bands in gas optics files matches the parameters. + if (kdist_sw%get_nband() /= nswbands) then + write(errmsg,'(a,i4,a,i4)') 'number of sw bands in file, ', kdist_sw%get_nband(), & + ", doesn't match parameter nswbands= ", nswbands + call endrun(sub//': ERROR: '//trim(errmsg)) + end if + if (kdist_lw%get_nband() /= nlwbands) then + write(errmsg,'(a,i4,a,i4)') 'number of lw bands in file, ', kdist_lw%get_nband(), & + ", doesn't match parameter nlwbands= ", nlwbands + call endrun(sub//': ERROR: '//trim(errmsg)) + end if + + nswgpts = kdist_sw%get_ngpt() + nlwgpts = kdist_lw%get_ngpt() + + ! SW band bounds in cm^-1 + allocate( values(2,nswbands), stat=istat ) + if (istat/=0) then + call endrun(sub//': ERROR allocating array: values(2,nswbands)') + end if + values = kdist_sw%get_band_lims_wavenumber() + wavenumber_low_shortwave = values(1,:) + wavenumber_high_shortwave = values(2,:) + + ! Indices into specific bands + idx_sw_diag = get_band_index_by_value('sw', 500.0_r8, 'nm') + idx_nir_diag = get_band_index_by_value('sw', 1000.0_r8, 'nm') + idx_uv_diag = get_band_index_by_value('sw', 400._r8, 'nm') + idx_sw_cloudsim = get_band_index_by_value('sw', 0.67_r8, 'micron') + + deallocate(values) + + ! LW band bounds in cm^-1 + allocate( values(2,nlwbands), stat=istat ) + if (istat/=0) then + call endrun(sub//': ERROR allocating array: values(2,nlwbands)') + end if + values = kdist_lw%get_band_lims_wavenumber() + wavenumber_low_longwave = values(1,:) + wavenumber_high_longwave = values(2,:) + + ! Indices into specific bands + idx_lw_diag = get_band_index_by_value('lw', 1000.0_r8, 'cm^-1') + idx_lw_cloudsim = get_band_index_by_value('lw', 10.5_r8, 'micron') + + wavenumber_boundaries_set = .true. + +end subroutine set_wavenumber_bands + +!========================================================================================= + +subroutine get_sw_spectral_boundaries(low_boundaries, high_boundaries, units) + + ! provide spectral boundaries of each shortwave band + + real(r8), intent(out) :: low_boundaries(nswbands), high_boundaries(nswbands) + character(*), intent(in) :: units ! requested units + + character(len=*), parameter :: sub = 'get_sw_spectral_boundaries' + !---------------------------------------------------------------------------- + + if (.not. wavenumber_boundaries_set) then + call endrun(sub//': ERROR, wavenumber boundaries not set. ') + end if + + select case (units) + case ('inv_cm','cm^-1','cm-1') + low_boundaries = wavenumber_low_shortwave + high_boundaries = wavenumber_high_shortwave + case('m','meter','meters') + low_boundaries = 1.e-2_r8/wavenumber_high_shortwave + high_boundaries = 1.e-2_r8/wavenumber_low_shortwave + case('nm','nanometer','nanometers') + low_boundaries = 1.e7_r8/wavenumber_high_shortwave + high_boundaries = 1.e7_r8/wavenumber_low_shortwave + case('um','micrometer','micrometers','micron','microns') + low_boundaries = 1.e4_r8/wavenumber_high_shortwave + high_boundaries = 1.e4_r8/wavenumber_low_shortwave + case('cm','centimeter','centimeters') + low_boundaries = 1._r8/wavenumber_high_shortwave + high_boundaries = 1._r8/wavenumber_low_shortwave + case default + call endrun(sub//': ERROR, requested spectral units not recognized: '//units) + end select + +end subroutine get_sw_spectral_boundaries + +!========================================================================================= + +subroutine get_lw_spectral_boundaries(low_boundaries, high_boundaries, units) + + ! provide spectral boundaries of each longwave band + + real(r8), intent(out) :: low_boundaries(nlwbands), high_boundaries(nlwbands) + character(*), intent(in) :: units ! requested units + + character(len=*), parameter :: sub = 'get_lw_spectral_boundaries' + !---------------------------------------------------------------------------- + + if (.not. wavenumber_boundaries_set) then + call endrun(sub//': ERROR, wavenumber boundaries not set. ') + end if + + select case (units) + case ('inv_cm','cm^-1','cm-1') + low_boundaries = wavenumber_low_longwave + high_boundaries = wavenumber_high_longwave + case('m','meter','meters') + low_boundaries = 1.e-2_r8/wavenumber_high_longwave + high_boundaries = 1.e-2_r8/wavenumber_low_longwave + case('nm','nanometer','nanometers') + low_boundaries = 1.e7_r8/wavenumber_high_longwave + high_boundaries = 1.e7_r8/wavenumber_low_longwave + case('um','micrometer','micrometers','micron','microns') + low_boundaries = 1.e4_r8/wavenumber_high_longwave + high_boundaries = 1.e4_r8/wavenumber_low_longwave + case('cm','centimeter','centimeters') + low_boundaries = 1._r8/wavenumber_high_longwave + high_boundaries = 1._r8/wavenumber_low_longwave + case default + call endrun(sub//': ERROR, requested spectral units not recognized: '//units) + end select + +end subroutine get_lw_spectral_boundaries + +!========================================================================================= + +integer function rad_gas_index(gasname) + + ! return the index in the gaslist array of the specified gasname + + character(len=*),intent(in) :: gasname + integer :: igas + + rad_gas_index = -1 + do igas = 1, nradgas + if (trim(gaslist(igas)).eq.trim(gasname)) then + rad_gas_index = igas + return + endif + enddo + call endrun ("rad_gas_index: can not find gas with name "//gasname) +end function rad_gas_index + +!========================================================================================= + +function get_band_index_by_value(swlw, targetvalue, units) result(ans) + + ! Find band index for requested wavelength/wavenumber. + + character(len=*), intent(in) :: swlw ! sw or lw bands + real(r8), intent(in) :: targetvalue + character(len=*), intent(in) :: units ! units of targetvalue + integer :: ans + + ! local + real(r8), pointer, dimension(:) :: lowboundaries, highboundaries + real(r8) :: tgt + integer :: nbnds, i + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'get_band_index_by_value' + !---------------------------------------------------------------------------- + + select case (swlw) + case ('sw','SW','shortwave') + nbnds = nswbands + lowboundaries => wavenumber_low_shortwave + highboundaries => wavenumber_high_shortwave + case ('lw', 'LW', 'longwave') + nbnds = nlwbands + lowboundaries => wavenumber_low_longwave + highboundaries => wavenumber_high_longwave + case default + call endrun('radconstants.F90: get_band_index_by_value: type of bands not recognized: '//swlw) + end select + + ! band info is in cm^-1 but target value may be other units, + ! so convert targetvalue to cm^-1 + select case (units) + case ('inv_cm','cm^-1','cm-1') + tgt = targetvalue + case('m','meter','meters') + tgt = 1.0_r8 / (targetvalue * 1.e2_r8) + case('nm','nanometer','nanometers') + tgt = 1.0_r8 / (targetvalue * 1.e-7_r8) + case('um','micrometer','micrometers','micron','microns') + tgt = 1.0_r8 / (targetvalue * 1.e-4_r8) + case('cm','centimeter','centimeters') + tgt = 1._r8/targetvalue + case default + call endrun('radconstants.F90: get_band_index_by_value: units not recognized: '//units) + end select + + ! now just loop through the array + ans = 0 + do i = 1,nbnds + if ((tgt > lowboundaries(i)) .and. (tgt <= highboundaries(i))) then + ans = i + exit + end if + end do + + if (ans == 0) then + write(errmsg,'(f10.3,a,a)') targetvalue, ' ', trim(units) + call endrun(sub//': band not found containing wave: '//trim(errmsg)) + end if + +end function get_band_index_by_value + +!========================================================================================= + +end module radconstants diff --git a/src/physics/rrtmgp/radiation.F90 b/src/physics/rrtmgp/radiation.F90 new file mode 100644 index 0000000000..18488bedb7 --- /dev/null +++ b/src/physics/rrtmgp/radiation.F90 @@ -0,0 +1,2491 @@ +module radiation + +!--------------------------------------------------------------------------------- +! +! CAM interface to RRTMGP radiation parameterization. +! +!--------------------------------------------------------------------------------- + +use shr_kind_mod, only: r8=>shr_kind_r8, cl=>shr_kind_cl +use spmd_utils, only: masterproc +use ppgrid, only: pcols, pver, pverp, begchunk, endchunk +use ref_pres, only: pref_edge +use physics_types, only: physics_state, physics_ptend +use phys_control, only: phys_getopts +use physics_buffer, only: physics_buffer_desc, pbuf_add_field, dtype_r8, pbuf_get_index, & + pbuf_set_field, pbuf_get_field, pbuf_old_tim_idx +use camsrfexch, only: cam_out_t, cam_in_t +use physconst, only: cappa, cpair, gravit +use solar_irrad_data, only: sol_tsi + +use time_manager, only: get_nstep, is_first_step, is_first_restart_step, & + get_curr_calday, get_step_size + +use rad_constituents, only: N_DIAG, rad_cnst_get_call_list, rad_cnst_get_gas, rad_cnst_out + +use rrtmgp_inputs, only: rrtmgp_inputs_init + +use radconstants, only: nradgas, gasnamelength, gaslist, nswbands, nlwbands, & + nswgpts, set_wavenumber_bands + +use cloud_rad_props, only: cloud_rad_props_init + +use cospsimulator_intr, only: docosp, cospsimulator_intr_init, & + cospsimulator_intr_run, cosp_nradsteps + +use scamMod, only: scm_crm_mode, single_column, have_cld, cldobs + +use cam_history, only: addfld, add_default, horiz_only, outfld, hist_fld_active + +use radiation_data, only: rad_data_register, rad_data_init + +use ioFileMod, only: getfil +use cam_pio_utils, only: cam_pio_openfile +use pio, only: file_desc_t, var_desc_t, & + pio_int, pio_double, PIO_NOERR, & + pio_seterrorhandling, PIO_BCAST_ERROR, & + pio_inq_dimlen, pio_inq_dimid, pio_inq_varid, & + pio_def_var, pio_put_var, pio_get_var, & + pio_put_att, PIO_NOWRITE, pio_closefile + +use mo_gas_concentrations, only: ty_gas_concs +use mo_gas_optics_rrtmgp, only: ty_gas_optics_rrtmgp +use mo_optical_props, only: ty_optical_props_1scl, ty_optical_props_2str +use mo_source_functions, only: ty_source_func_lw +use mo_fluxes, only: ty_fluxes_broadband +use mo_fluxes_byband, only: ty_fluxes_byband + +use string_utils, only: to_lower +use cam_abortutils, only: endrun, handle_allocate_error +use cam_logfile, only: iulog + + +implicit none +private +save + +public :: & + radiation_readnl, &! read namelist variables + radiation_register, &! registers radiation physics buffer fields + radiation_do, &! query which radiation calcs are done this timestep + radiation_init, &! initialization + radiation_define_restart, &! define variables for restart + radiation_write_restart, &! write variables to restart + radiation_read_restart, &! read variables from restart + radiation_tend, &! compute heating rates and fluxes + rad_out_t ! type for diagnostic outputs + +integer,public, allocatable :: cosp_cnt(:) ! counter for cosp +integer,public :: cosp_cnt_init = 0 !initial value for cosp counter + +real(r8), public, protected :: nextsw_cday ! future radiation calday for surface models + +type rad_out_t + real(r8) :: solin(pcols) ! Solar incident flux + + real(r8) :: qrsc(pcols,pver) + + real(r8) :: fsnsc(pcols) ! Clear sky surface abs solar flux + real(r8) :: fsntc(pcols) ! Clear sky total column abs solar flux + real(r8) :: fsdsc(pcols) ! Clear sky surface downwelling solar flux + + real(r8) :: fsntoa(pcols) ! Net solar flux at TOA + real(r8) :: fsntoac(pcols) ! Clear sky net solar flux at TOA + real(r8) :: fsutoa(pcols) ! upwelling solar flux at TOA + + real(r8) :: fsnirt(pcols) ! Near-IR flux absorbed at toa + real(r8) :: fsnrtc(pcols) ! Clear sky near-IR flux absorbed at toa + real(r8) :: fsnirtsq(pcols) ! Near-IR flux absorbed at toa >= 0.7 microns + + real(r8) :: fsn200(pcols) ! Net SW flux interpolated to 200 mb + real(r8) :: fsn200c(pcols) ! Net clear-sky SW flux interpolated to 200 mb + real(r8) :: fsnr(pcols) ! Net SW flux interpolated to tropopause + + real(r8) :: flux_sw_up(pcols,pverp) ! upward shortwave flux on interfaces + real(r8) :: flux_sw_clr_up(pcols,pverp) ! upward shortwave clearsky flux + real(r8) :: flux_sw_dn(pcols,pverp) ! downward flux + real(r8) :: flux_sw_clr_dn(pcols,pverp) ! downward clearsky flux + + real(r8) :: flux_lw_up(pcols,pverp) ! upward longwave flux on interfaces + real(r8) :: flux_lw_clr_up(pcols,pverp) ! upward longwave clearsky flux + real(r8) :: flux_lw_dn(pcols,pverp) ! downward flux + real(r8) :: flux_lw_clr_dn(pcols,pverp) ! downward clearsky flux + + real(r8) :: qrlc(pcols,pver) + + real(r8) :: flntc(pcols) ! Clear sky lw flux at model top + real(r8) :: flut(pcols) ! Upward flux at top of model + real(r8) :: flutc(pcols) ! Upward Clear Sky flux at top of model + real(r8) :: lwcf(pcols) ! longwave cloud forcing + + real(r8) :: fln200(pcols) ! net longwave flux interpolated to 200 mb + real(r8) :: fln200c(pcols) ! net clearsky longwave flux interpolated to 200 mb + real(r8) :: flnr(pcols) ! net longwave flux interpolated to tropopause + + real(r8) :: flnsc(pcols) ! Clear sky lw flux at srf (up-down) + real(r8) :: fldsc(pcols) ! Clear sky lw flux at srf (down) + + real(r8) :: tot_cld_vistau(pcols,pver) ! gbx water+ice cloud optical depth (only during day, night = fillvalue) + real(r8) :: tot_icld_vistau(pcols,pver) ! in-cld water+ice cloud optical depth (only during day, night = fillvalue) + real(r8) :: liq_icld_vistau(pcols,pver) ! in-cld liq cloud optical depth (only during day, night = fillvalue) + real(r8) :: ice_icld_vistau(pcols,pver) ! in-cld ice cloud optical depth (only during day, night = fillvalue) + real(r8) :: snow_icld_vistau(pcols,pver) ! snow in-cloud visible sw optical depth for output on history files + real(r8) :: grau_icld_vistau(pcols,pver) ! Graupel in-cloud visible sw optical depth for output on history files +end type rad_out_t + +! Control variables set via namelist +character(len=cl) :: coefs_lw_file ! filepath for lw coefficients +character(len=cl) :: coefs_sw_file ! filepath for sw coefficients + +integer :: iradsw = -1 ! freq. of shortwave radiation calc in time steps (positive) + ! or hours (negative). +integer :: iradlw = -1 ! frequency of longwave rad. calc. in time steps (positive) + ! or hours (negative). + +integer :: irad_always = 0 ! Specifies length of time in timesteps (positive) + ! or hours (negative) SW/LW radiation will be + ! run continuously from the start of an + ! initial or restart run +logical :: use_rad_dt_cosz = .false. ! if true, use radiation dt for all cosz calculations +logical :: spectralflux = .false. ! calculate fluxes (up and down) per band. +logical :: graupel_in_rad = .false. ! graupel in radiation code +logical :: use_rad_uniform_angle = .false. ! if true, use the namelist rad_uniform_angle for the coszrs calculation + +! active_calls is set by a rad_constituents method after parsing namelist input +! for the rad_climate and rad_diag_N entries. +logical :: active_calls(0:N_DIAG) + +! Physics buffer indices +integer :: qrs_idx = 0 +integer :: qrl_idx = 0 +integer :: su_idx = 0 +integer :: sd_idx = 0 +integer :: lu_idx = 0 +integer :: ld_idx = 0 +integer :: fsds_idx = 0 +integer :: fsns_idx = 0 +integer :: fsnt_idx = 0 +integer :: flns_idx = 0 +integer :: flnt_idx = 0 +integer :: cld_idx = 0 +integer :: cldfsnow_idx = 0 +integer :: cldfgrau_idx = 0 + +character(len=4) :: diag(0:N_DIAG) =(/' ','_d1 ','_d2 ','_d3 ','_d4 ','_d5 ',& + '_d6 ','_d7 ','_d8 ','_d9 ','_d10'/) + +! averaging time interval for zenith angle +real(r8) :: dt_avg = 0._r8 +real(r8) :: rad_uniform_angle = -99._r8 + +! Number of layers in radiation calculations. +integer :: nlay + +! Number of CAM layers in radiation calculations. Is either equal to nlay, or is +! 1 less than nlay if "extra layer" is used in the radiation calculations. +integer :: nlaycam + +! Indices for copying data between CAM/WACCM and RRTMGP arrays. Since RRTMGP is +! vertical order agnostic we can send data using the top to bottom order used +! in CAM/WACCM. But the number of layers that RRTMGP does computations for +! may not match the number of layers in CAM/WACCM for two reasons: +! 1. If the CAM model top is below 1 Pa, then RRTMGP does calculations for an +! extra layer that is added between 1 Pa and the model top. +! 2. If the WACCM model top is above 1 Pa, then RRMTGP only does calculations +! for those model layers that are below 1 Pa. +integer :: ktopcam ! Index in CAM arrays of top level (layer or interface) at which + ! RRTMGP is active. +integer :: ktoprad ! Index in RRTMGP arrays of the layer or interface corresponding + ! to CAM's top layer or interface. + ! Note: for CAM's top to bottom indexing, the index of a given layer + ! (midpoint) and the upper interface of that layer, are the same. + +! Gas optics objects contain the data read from the coefficients files. +type(ty_gas_optics_rrtmgp) :: kdist_sw +type(ty_gas_optics_rrtmgp) :: kdist_lw + +! lower case version of gaslist for RRTMGP +character(len=gasnamelength) :: gaslist_lc(nradgas) + +type(var_desc_t) :: cospcnt_desc ! cosp +type(var_desc_t) :: nextsw_cday_desc + +!========================================================================================= +contains +!========================================================================================= + +subroutine radiation_readnl(nlfile) + + ! Read radiation_nl namelist group. + + use namelist_utils, only: find_group_name + use spmd_utils, only: mpicom, mstrid=>masterprocid, mpi_integer, mpi_logical, & + mpi_character, mpi_real8 + + character(len=*), intent(in) :: nlfile ! filepath for file containing namelist input + + ! Local variables + integer :: unitn, ierr + integer :: dtime ! timestep size + character(len=32) :: errmsg + character(len=*), parameter :: sub = 'radiation_readnl' + + character(len=cl) :: rrtmgp_coefs_lw_file, rrtmgp_coefs_sw_file + + namelist /radiation_nl/ & + rrtmgp_coefs_lw_file, rrtmgp_coefs_sw_file, iradsw, iradlw, & + irad_always, use_rad_dt_cosz, spectralflux, use_rad_uniform_angle, & + rad_uniform_angle, graupel_in_rad + !----------------------------------------------------------------------------- + + if (masterproc) then + open( newunit=unitn, file=trim(nlfile), status='old' ) + call find_group_name(unitn, 'radiation_nl', status=ierr) + if (ierr == 0) then + read(unitn, radiation_nl, iostat=ierr) + if (ierr /= 0) then + write(errmsg,'(a,i5)') 'iostat =', ierr + call endrun(sub//': ERROR reading namelist: '//trim(errmsg)) + end if + end if + close(unitn) + end if + + ! Broadcast namelist variables + call mpi_bcast(rrtmgp_coefs_lw_file, cl, mpi_character, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: rrtmgp_coefs_lw_file") + call mpi_bcast(rrtmgp_coefs_sw_file, cl, mpi_character, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: rrtmgp_coefs_sw_file") + call mpi_bcast(iradsw, 1, mpi_integer, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: iradsw") + call mpi_bcast(iradlw, 1, mpi_integer, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: iradlw") + call mpi_bcast(irad_always, 1, mpi_integer, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: irad_always") + call mpi_bcast(use_rad_dt_cosz, 1, mpi_logical, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: use_rad_dt_cosz") + call mpi_bcast(spectralflux, 1, mpi_logical, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: spectralflux") + call mpi_bcast(use_rad_uniform_angle, 1, mpi_logical, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: use_rad_uniform_angle") + call mpi_bcast(rad_uniform_angle, 1, mpi_real8, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: rad_uniform_angle") + call mpi_bcast(graupel_in_rad, 1, mpi_logical, mstrid, mpicom, ierr) + if (ierr /= 0) call endrun(sub//": FATAL: mpi_bcast: graupel_in_rad") + + if (use_rad_uniform_angle .and. rad_uniform_angle == -99._r8) then + call endrun(sub//': ERROR - use_rad_uniform_angle is set to .true,' & + //' but rad_uniform_angle is not set ') + end if + + ! Set module data + coefs_lw_file = rrtmgp_coefs_lw_file + coefs_sw_file = rrtmgp_coefs_sw_file + + ! Convert iradsw, iradlw and irad_always from hours to timesteps if necessary + dtime = get_step_size() + if (iradsw < 0) iradsw = nint((-iradsw *3600._r8)/dtime) + if (iradlw < 0) iradlw = nint((-iradlw *3600._r8)/dtime) + if (irad_always < 0) irad_always = nint((-irad_always*3600._r8)/dtime) + + !----------------------------------------------------------------------- + ! Print runtime options to log. + !----------------------------------------------------------------------- + + if (masterproc) then + write(iulog,*) 'RRTMGP radiation scheme parameters:' + write(iulog,10) trim(coefs_lw_file), trim(coefs_sw_file), nlwbands, nswbands, & + iradsw, iradlw, irad_always, use_rad_dt_cosz, spectralflux, graupel_in_rad + end if + +10 format(' LW coefficents file: ', a/, & + ' SW coefficents file: ', a/, & + ' Number of LW bands: ',i5/, & + ' Number of SW bands: ',i5/, & + ' Frequency (timesteps) of Shortwave Radiation calc: ',i5/, & + ' Frequency (timesteps) of Longwave Radiation calc: ',i5/, & + ' SW/LW calc done every timestep for first N steps. N=',i5/, & + ' Use average zenith angle: ',l5/, & + ' Output spectrally resolved fluxes: ',l5/, & + ' Graupel in Radiation Code: ',l5/) + +end subroutine radiation_readnl + +!================================================================================================ + +subroutine radiation_register + + ! Register radiation fields in the physics buffer + + call pbuf_add_field('QRS' , 'global',dtype_r8,(/pcols,pver/), qrs_idx) ! shortwave radiative heating rate + call pbuf_add_field('QRL' , 'global',dtype_r8,(/pcols,pver/), qrl_idx) ! longwave radiative heating rate + + call pbuf_add_field('FSDS' , 'global',dtype_r8,(/pcols/), fsds_idx) ! Surface solar downward flux + call pbuf_add_field('FSNS' , 'global',dtype_r8,(/pcols/), fsns_idx) ! Surface net shortwave flux + call pbuf_add_field('FSNT' , 'global',dtype_r8,(/pcols/), fsnt_idx) ! Top-of-model net shortwave flux + + call pbuf_add_field('FLNS' , 'global',dtype_r8,(/pcols/), flns_idx) ! Surface net longwave flux + call pbuf_add_field('FLNT' , 'global',dtype_r8,(/pcols/), flnt_idx) ! Top-of-model net longwave flux + + ! If the namelist has been configured for preserving the spectral fluxes, then create + ! physics buffer variables to store the results. This data is accessed by CARMA. + if (spectralflux) then + call pbuf_add_field('SU' , 'global',dtype_r8,(/pcols,pverp,nswbands/), su_idx) ! shortwave upward flux (per band) + call pbuf_add_field('SD' , 'global',dtype_r8,(/pcols,pverp,nswbands/), sd_idx) ! shortwave downward flux (per band) + call pbuf_add_field('LU' , 'global',dtype_r8,(/pcols,pverp,nlwbands/), lu_idx) ! longwave upward flux (per band) + call pbuf_add_field('LD' , 'global',dtype_r8,(/pcols,pverp,nlwbands/), ld_idx) ! longwave downward flux (per band) + end if + + ! Register fields for offline radiation driver. + call rad_data_register() + +end subroutine radiation_register + +!================================================================================================ + +function radiation_do(op, timestep) + + ! Return true if the specified operation is done this timestep. + + character(len=*), intent(in) :: op ! name of operation + integer, intent(in), optional:: timestep + logical :: radiation_do ! return value + + ! Local variables + integer :: nstep ! current timestep number + !----------------------------------------------------------------------- + + if (present(timestep)) then + nstep = timestep + else + nstep = get_nstep() + end if + + select case (op) + case ('sw') ! do a shortwave heating calc this timestep? + radiation_do = nstep == 0 .or. iradsw == 1 & + .or. (mod(nstep-1,iradsw) == 0 .and. nstep /= 1) & + .or. nstep <= irad_always + case ('lw') ! do a longwave heating calc this timestep? + radiation_do = nstep == 0 .or. iradlw == 1 & + .or. (mod(nstep-1,iradlw) == 0 .and. nstep /= 1) & + .or. nstep <= irad_always + case default + call endrun('radiation_do: unknown operation:'//op) + end select + +end function radiation_do + +!================================================================================================ + +real(r8) function radiation_nextsw_cday() + + ! If a SW radiation calculation will be done on the next time-step, then return + ! the calendar day of that time-step. Otherwise return -1.0 + + ! Local variables + integer :: nstep ! timestep counter + logical :: dosw ! true => do shosrtwave calc + integer :: offset ! offset for calendar day calculation + integer :: dtime ! integer timestep size + real(r8):: caldayp1 ! calendar day of next time-step + + !----------------------------------------------------------------------- + + radiation_nextsw_cday = -1._r8 + dosw = .false. + nstep = get_nstep() + dtime = get_step_size() + offset = 0 + do while (.not. dosw) + nstep = nstep + 1 + offset = offset + dtime + if (radiation_do('sw', nstep)) then + radiation_nextsw_cday = get_curr_calday(offset=offset) + dosw = .true. + end if + end do + if(radiation_nextsw_cday == -1._r8) then + call endrun('error in radiation_nextsw_cday') + end if + + ! determine if next radiation time-step not equal to next time-step + if (get_nstep() >= 1) then + caldayp1 = get_curr_calday(offset=int(dtime)) + if (caldayp1 /= radiation_nextsw_cday) radiation_nextsw_cday = -1._r8 + end if + +end function radiation_nextsw_cday + +!================================================================================================ + +subroutine radiation_init(pbuf2d) + + ! Initialize the radiation and cloud optics. + ! Add fields to the history buffer. + + ! arguments + type(physics_buffer_desc), pointer :: pbuf2d(:,:) + + ! local variables + character(len=128) :: errmsg + + ! names of gases that are available in the model + ! -- needed for the kdist initialization routines + type(ty_gas_concs) :: available_gases + + integer :: i, icall + integer :: nstep ! current timestep number + logical :: history_amwg ! output the variables used by the AMWG diag package + logical :: history_vdiag ! output the variables used by the AMWG variability diag package + logical :: history_budget ! output tendencies and state variables for CAM4 + ! temperature, water vapor, cloud ice and cloud + ! liquid budgets. + integer :: history_budget_histfile_num ! history file number for budget fields + integer :: ierr, istat + + integer :: dtime + + character(len=*), parameter :: sub = 'radiation_init' + !----------------------------------------------------------------------- + + ! Number of layers in radiation calculation is capped by the number of + ! pressure interfaces below 1 Pa. When the entire model atmosphere is + ! below 1 Pa then an extra layer is added to the top of the model for + ! the purpose of the radiation calculation. + nlay = count( pref_edge(:) > 1._r8 ) ! pascals (0.01 mbar) + + if (nlay == pverp) then + ! Top model interface is below 1 Pa. RRTMGP is active in all model layers plus + ! 1 extra layer between model top and 1 Pa. + ktopcam = 1 + ktoprad = 2 + nlaycam = pver + else + ! nlay < pverp. nlay layers are used in radiation calcs, and they are + ! all CAM layers. + ktopcam = pver - nlay + 1 + ktoprad = 1 + nlaycam = nlay + end if + + ! Create lowercase version of the gaslist for RRTMGP. The ty_gas_concs objects + ! work with CAM's uppercase names, but other objects that get input from the gas + ! concs objects don't work. + do i = 1, nradgas + gaslist_lc(i) = to_lower(gaslist(i)) + end do + + errmsg = available_gases%init(gaslist_lc) + call stop_on_err(errmsg, sub, 'available_gases%init') + + ! Read RRTMGP coefficients files and initialize kdist objects. + call coefs_init(coefs_sw_file, available_gases, kdist_sw) + call coefs_init(coefs_lw_file, available_gases, kdist_lw) + + ! Set the sw/lw band boundaries in radconstants. Also sets + ! indicies of specific bands for diagnostic output and COSP input. + call set_wavenumber_bands(kdist_sw, kdist_lw) + + ! The spectral band boundaries need to be set before this init is called. + call rrtmgp_inputs_init(ktopcam, ktoprad) + + ! initialize output fields for offline driver + call rad_data_init(pbuf2d) + + call cloud_rad_props_init() + + cld_idx = pbuf_get_index('CLD') + cldfsnow_idx = pbuf_get_index('CLDFSNOW', errcode=ierr) + cldfgrau_idx = pbuf_get_index('CLDFGRAU', errcode=ierr) + + if (is_first_step()) then + call pbuf_set_field(pbuf2d, qrl_idx, 0._r8) + end if + + ! Set the radiation timestep for cosz calculations if requested using + ! the adjusted iradsw value from radiation + if (use_rad_dt_cosz) then + dtime = get_step_size() + dt_avg = iradsw*dtime + end if + + ! Surface components to get radiation computed today + if (.not. is_first_restart_step()) then + nextsw_cday = get_curr_calday() + end if + + call phys_getopts(history_amwg_out = history_amwg, & + history_vdiag_out = history_vdiag, & + history_budget_out = history_budget, & + history_budget_histfile_num_out = history_budget_histfile_num) + + ! "irad_always" is number of time steps to execute radiation continuously from + ! start of initial OR restart run + nstep = get_nstep() + if (irad_always > 0) then + irad_always = irad_always + nstep + end if + + if (docosp) call cospsimulator_intr_init() + + allocate(cosp_cnt(begchunk:endchunk), stat=istat) + call handle_allocate_error(istat, sub, 'cosp_cnt') + if (is_first_restart_step()) then + cosp_cnt(begchunk:endchunk) = cosp_cnt_init + else + cosp_cnt(begchunk:endchunk) = 0 + end if + + ! Add fields to history buffer + + call addfld('TOT_CLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Total gbx cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + call addfld('TOT_ICLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Total in-cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + call addfld('LIQ_ICLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Liquid in-cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + call addfld('ICE_ICLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Ice in-cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + + if (cldfsnow_idx > 0) then + call addfld('SNOW_ICLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Snow in-cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + end if + if (cldfgrau_idx > 0 .and. graupel_in_rad) then + call addfld('GRAU_ICLD_VISTAU', (/ 'lev' /), 'A', '1', & + 'Graupel in-cloud extinction visible sw optical depth', & + sampling_seq='rad_lwsw', flag_xyfill=.true.) + endif + + ! get list of active radiation calls + call rad_cnst_get_call_list(active_calls) + + ! Add shortwave radiation fields to history master field list. + + do icall = 0, N_DIAG + + if (active_calls(icall)) then + + call addfld('SOLIN'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Solar insolation', sampling_seq='rad_lwsw') + call addfld('QRS'//diag(icall), (/ 'lev' /), 'A', 'K/s', & + 'Solar heating rate', sampling_seq='rad_lwsw') + call addfld('QRSC'//diag(icall), (/ 'lev' /), 'A', 'K/s', & + 'Clearsky solar heating rate', sampling_seq='rad_lwsw') + call addfld('FSNT'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net solar flux at top of model', sampling_seq='rad_lwsw') + call addfld('FSNTC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net solar flux at top of model', sampling_seq='rad_lwsw') + call addfld('FSNTOA'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net solar flux at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('FSNTOAC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net solar flux at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('SWCF'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Shortwave cloud forcing', sampling_seq='rad_lwsw') + call addfld('FSUTOA'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Upwelling solar flux at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('FSNIRTOA'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net near-infrared flux (Nimbus-7 WFOV) at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('FSNRTOAC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net near-infrared flux (Nimbus-7 WFOV) at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('FSNRTOAS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net near-infrared flux (>= 0.7 microns) at top of atmosphere', sampling_seq='rad_lwsw') + call addfld('FSN200'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net shortwave flux at 200 mb', sampling_seq='rad_lwsw') + call addfld('FSN200C'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net shortwave flux at 200 mb', sampling_seq='rad_lwsw') + call addfld('FSNR'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net solar flux at tropopause', sampling_seq='rad_lwsw') + call addfld('SOLL'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Solar downward near infrared direct to surface', sampling_seq='rad_lwsw') + call addfld('SOLS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Solar downward visible direct to surface', sampling_seq='rad_lwsw') + call addfld('SOLLD'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Solar downward near infrared diffuse to surface', sampling_seq='rad_lwsw') + call addfld('SOLSD'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Solar downward visible diffuse to surface', sampling_seq='rad_lwsw') + call addfld('FSNS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net solar flux at surface', sampling_seq='rad_lwsw') + call addfld('FSNSC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net solar flux at surface', sampling_seq='rad_lwsw') + call addfld('FSDS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Downwelling solar flux at surface', sampling_seq='rad_lwsw') + call addfld('FSDSC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky downwelling solar flux at surface', sampling_seq='rad_lwsw') + + ! Fluxes on CAM grid + call addfld('FUS'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Shortwave upward flux', sampling_seq='rad_lwsw') + call addfld('FDS'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Shortwave downward flux', sampling_seq='rad_lwsw') + call addfld('FUSC'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Shortwave clear-sky upward flux', sampling_seq='rad_lwsw') + call addfld('FDSC'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Shortwave clear-sky downward flux', sampling_seq='rad_lwsw') + + if (history_amwg) then + call add_default('SOLIN'//diag(icall), 1, ' ') + call add_default('QRS'//diag(icall), 1, ' ') + call add_default('FSNT'//diag(icall), 1, ' ') + call add_default('FSNTC'//diag(icall), 1, ' ') + call add_default('FSNTOA'//diag(icall), 1, ' ') + call add_default('FSNTOAC'//diag(icall), 1, ' ') + call add_default('SWCF'//diag(icall), 1, ' ') + call add_default('FSNS'//diag(icall), 1, ' ') + call add_default('FSNSC'//diag(icall), 1, ' ') + call add_default('FSUTOA'//diag(icall), 1, ' ') + call add_default('FSDSC'//diag(icall), 1, ' ') + call add_default('FSDS'//diag(icall), 1, ' ') + endif + + end if + end do + + if (scm_crm_mode) then + call add_default('FUS ', 1, ' ') + call add_default('FUSC ', 1, ' ') + call add_default('FDS ', 1, ' ') + call add_default('FDSC ', 1, ' ') + endif + + ! Add longwave radiation fields to history master field list. + + do icall = 0, N_DIAG + + if (active_calls(icall)) then + call addfld('QRL'//diag(icall), (/ 'lev' /), 'A', 'K/s', & + 'Longwave heating rate', sampling_seq='rad_lwsw') + call addfld('QRLC'//diag(icall), (/ 'lev' /), 'A', 'K/s', & + 'Clearsky longwave heating rate', sampling_seq='rad_lwsw') + call addfld('FLNT'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net longwave flux at top of model', sampling_seq='rad_lwsw') + call addfld('FLNTC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net longwave flux at top of model', sampling_seq='rad_lwsw') + call addfld('FLUT'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Upwelling longwave flux at top of model', sampling_seq='rad_lwsw') + call addfld('FLUTC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky upwelling longwave flux at top of model', sampling_seq='rad_lwsw') + call addfld('LWCF'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Longwave cloud forcing', sampling_seq='rad_lwsw') + call addfld('FLN200'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net longwave flux at 200 mb', sampling_seq='rad_lwsw') + call addfld('FLN200C'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net longwave flux at 200 mb', sampling_seq='rad_lwsw') + call addfld('FLNR'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net longwave flux at tropopause', sampling_seq='rad_lwsw') + call addfld('FLNS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Net longwave flux at surface', sampling_seq='rad_lwsw') + call addfld('FLNSC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky net longwave flux at surface', sampling_seq='rad_lwsw') + call addfld('FLDS'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Downwelling longwave flux at surface', sampling_seq='rad_lwsw') + call addfld('FLDSC'//diag(icall), horiz_only, 'A', 'W/m2', & + 'Clearsky Downwelling longwave flux at surface', sampling_seq='rad_lwsw') + + ! Fluxes on CAM grid + call addfld('FUL'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Longwave upward flux', sampling_seq='rad_lwsw') + call addfld('FDL'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Longwave downward flux', sampling_seq='rad_lwsw') + call addfld('FULC'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Longwave clear-sky upward flux', sampling_seq='rad_lwsw') + call addfld('FDLC'//diag(icall), (/ 'ilev' /), 'I', 'W/m2', & + 'Longwave clear-sky downward flux', sampling_seq='rad_lwsw') + + if (history_amwg) then + call add_default('QRL'//diag(icall), 1, ' ') + call add_default('FLNT'//diag(icall), 1, ' ') + call add_default('FLNTC'//diag(icall), 1, ' ') + call add_default('FLUT'//diag(icall), 1, ' ') + call add_default('FLUTC'//diag(icall), 1, ' ') + call add_default('LWCF'//diag(icall), 1, ' ') + call add_default('FLNS'//diag(icall), 1, ' ') + call add_default('FLNSC'//diag(icall), 1, ' ') + call add_default('FLDS'//diag(icall), 1, ' ') + end if + + end if + end do + + call addfld('EMIS', (/ 'lev' /), 'A', '1', 'Cloud longwave emissivity') + + if (scm_crm_mode) then + call add_default ('FUL ', 1, ' ') + call add_default ('FULC ', 1, ' ') + call add_default ('FDL ', 1, ' ') + call add_default ('FDLC ', 1, ' ') + endif + + ! Heating rate needed for d(theta)/dt computation + call addfld ('HR',(/ 'lev' /), 'A','K/s','Heating rate needed for d(theta)/dt computation') + + if ( history_budget .and. history_budget_histfile_num > 1 ) then + call add_default ('QRL ', history_budget_histfile_num, ' ') + call add_default ('QRS ', history_budget_histfile_num, ' ') + end if + + if (history_vdiag) then + call add_default('FLUT', 2, ' ') + call add_default('FLUT', 3, ' ') + end if + +end subroutine radiation_init + +!=============================================================================== + +subroutine radiation_define_restart(file) + + ! define variables to be written to restart file + + ! arguments + type(file_desc_t), intent(inout) :: file + + ! local variables + integer :: ierr + !---------------------------------------------------------------------------- + + call pio_seterrorhandling(file, PIO_BCAST_ERROR) + + ierr = pio_def_var(file, 'nextsw_cday', pio_double, nextsw_cday_desc) + ierr = pio_put_att(file, nextsw_cday_desc, 'long_name', 'future radiation calday for surface models') + if (docosp) then + ierr = pio_def_var(File, 'cosp_cnt_init', pio_int, cospcnt_desc) + end if + +end subroutine radiation_define_restart + +!=============================================================================== + +subroutine radiation_write_restart(file) + + ! write variables to restart file + + ! arguments + type(file_desc_t), intent(inout) :: file + + ! local variables + integer :: ierr + !---------------------------------------------------------------------------- + ierr = pio_put_var(File, nextsw_cday_desc, (/ nextsw_cday /)) + if (docosp) then + ierr = pio_put_var(File, cospcnt_desc, (/cosp_cnt(begchunk)/)) + end if + +end subroutine radiation_write_restart + +!=============================================================================== + +subroutine radiation_read_restart(file) + + ! read variables from restart file + + ! arguments + type(file_desc_t), intent(inout) :: file + + ! local variables + integer :: ierr + type(var_desc_t) :: vardesc + integer :: err_handling + + !---------------------------------------------------------------------------- + if (docosp) then + call pio_seterrorhandling(File, PIO_BCAST_ERROR, err_handling) + ierr = pio_inq_varid(File, 'cosp_cnt_init', vardesc) + call pio_seterrorhandling(File, err_handling) + if (ierr /= PIO_NOERR) then + cosp_cnt_init = 0 + else + ierr = pio_get_var(File, vardesc, cosp_cnt_init) + end if + end if + + ierr = pio_inq_varid(file, 'nextsw_cday', vardesc) + ierr = pio_get_var(file, vardesc, nextsw_cday) + + +end subroutine radiation_read_restart + +!=============================================================================== + +subroutine radiation_tend( & + state, ptend, pbuf, cam_out, cam_in, net_flx, rd_out) + + !----------------------------------------------------------------------- + ! + ! CAM driver for radiation computation. + ! + !----------------------------------------------------------------------- + + ! Location/Orbital Parameters for cosine zenith angle + use phys_grid, only: get_rlat_all_p, get_rlon_all_p + use cam_control_mod, only: eccen, mvelpp, lambm0, obliqr + use shr_orb_mod, only: shr_orb_decl, shr_orb_cosz + + use rrtmgp_inputs, only: rrtmgp_set_state, rrtmgp_set_gases_lw, rrtmgp_set_cloud_lw, & + rrtmgp_set_aer_lw, rrtmgp_set_gases_sw, rrtmgp_set_cloud_sw, & + rrtmgp_set_aer_sw + + ! RRTMGP drivers for flux calculations. + use mo_rte_lw, only: rte_lw + use mo_rte_sw, only: rte_sw + + use radheat, only: radheat_tend + + use radiation_data, only: rad_data_write + + use interpolate_data, only: vertinterp + use tropopause, only: tropopause_find, TROP_ALG_HYBSTOB, TROP_ALG_CLIMATE + use cospsimulator_intr, only: docosp, cospsimulator_intr_run, cosp_nradsteps + + + ! Arguments + type(physics_state), intent(in), target :: state + type(physics_ptend), intent(out) :: ptend + type(physics_buffer_desc), pointer :: pbuf(:) + type(cam_out_t), intent(inout) :: cam_out + type(cam_in_t), intent(in) :: cam_in + real(r8), intent(out) :: net_flx(pcols) + + type(rad_out_t), target, optional, intent(out) :: rd_out + + + ! Local variables + type(rad_out_t), pointer :: rd ! allow rd_out to be optional by allocating a local object + ! if the argument is not present + logical :: write_output + + integer :: i, k, istat + integer :: lchnk, ncol + logical :: dosw, dolw + integer :: icall ! loop index for climate/diagnostic radiation calls + + real(r8) :: calday ! current calendar day + real(r8) :: delta ! Solar declination angle in radians + real(r8) :: eccf ! Earth orbit eccentricity factor + real(r8) :: clat(pcols) ! current latitudes(radians) + real(r8) :: clon(pcols) ! current longitudes(radians) + real(r8) :: coszrs(pcols) ! Cosine solar zenith angle + + ! Gathered indices of day and night columns + ! chunk_column_index = IdxDay(daylight_column_index) + integer :: Nday ! Number of daylight columns + integer :: Nnite ! Number of night columns + integer :: IdxDay(pcols) ! chunk indices of daylight columns + integer :: IdxNite(pcols) ! chunk indices of night columns + + integer :: itim_old + + real(r8), pointer :: cld(:,:) ! cloud fraction + real(r8), pointer :: cldfsnow(:,:) ! cloud fraction of just "snow clouds" + real(r8), pointer :: cldfgrau(:,:) ! cloud fraction of just "graupel clouds" + real(r8) :: cldfprime(pcols,pver) ! combined cloud fraction + real(r8), pointer :: qrs(:,:) ! shortwave radiative heating rate + real(r8), pointer :: qrl(:,:) ! longwave radiative heating rate + real(r8), pointer :: fsds(:) ! Surface solar down flux + real(r8), pointer :: fsns(:) ! Surface solar absorbed flux + real(r8), pointer :: fsnt(:) ! Net column abs solar flux at model top + real(r8), pointer :: flns(:) ! Srf longwave cooling (up-down) flux + real(r8), pointer :: flnt(:) ! Net outgoing lw flux at model top + + real(r8), pointer, dimension(:,:,:) :: su => NULL() ! shortwave spectral flux up + real(r8), pointer, dimension(:,:,:) :: sd => NULL() ! shortwave spectral flux down + real(r8), pointer, dimension(:,:,:) :: lu => NULL() ! longwave spectral flux up + real(r8), pointer, dimension(:,:,:) :: ld => NULL() ! longwave spectral flux down + + ! tropopause diagnostic + integer :: troplev(pcols) + real(r8) :: p_trop(pcols) + + ! state data passed to radiation calc + real(r8), allocatable :: t_sfc(:) + real(r8), allocatable :: emis_sfc(:,:) + real(r8), allocatable :: t_rad(:,:) + real(r8), allocatable :: pmid_rad(:,:) + real(r8), allocatable :: pint_rad(:,:) + real(r8), allocatable :: t_day(:,:) + real(r8), allocatable :: pmid_day(:,:) + real(r8), allocatable :: pint_day(:,:) + real(r8), allocatable :: coszrs_day(:) + real(r8), allocatable :: alb_dir(:,:) + real(r8), allocatable :: alb_dif(:,:) + + ! in-cloud optical depths for COSP + real(r8) :: cld_tau_cloudsim(pcols,pver) ! liq + ice + real(r8) :: snow_tau_cloudsim(pcols,pver) ! snow + real(r8) :: grau_tau_cloudsim(pcols,pver) ! graupel + real(r8) :: cld_lw_abs_cloudsim(pcols,pver) ! liq + ice + real(r8) :: snow_lw_abs_cloudsim(pcols,pver)! snow + real(r8) :: grau_lw_abs_cloudsim(pcols,pver)! graupel + + ! Set vertical indexing in RRTMGP to be the same as CAM (top to bottom). + logical, parameter :: top_at_1 = .true. + + ! TOA solar flux on RRTMGP g-points + real(r8), allocatable :: toa_flux(:,:) + ! TSI from RRTMGP data (from sum over g-point representation) + real(r8) :: tsi_ref + + ! Planck sources for LW. + type(ty_source_func_lw) :: sources_lw + + ! Gas volume mixing ratios. Use separate objects for LW and SW because SW only does + ! calculations for daylight columns. + ! These objects have a final method which deallocates the internal memory when they + ! go out of scope (i.e., when radiation_tend returns), so no need for explicit deallocation. + type(ty_gas_concs) :: gas_concs_lw + type(ty_gas_concs) :: gas_concs_sw + + ! Atmosphere optics. This object is initialized with gas optics, then is incremented + ! by the aerosol optics for the clear-sky radiative flux calculations, and then + ! incremented again by the cloud optics for the all-sky radiative flux calculations. + type(ty_optical_props_1scl) :: atm_optics_lw + type(ty_optical_props_2str) :: atm_optics_sw + + ! Cloud optical properties objects (McICA sampling of cloud optical properties). + type(ty_optical_props_1scl) :: cloud_lw + type(ty_optical_props_2str) :: cloud_sw + + ! Aerosol optical properties objects. + type(ty_optical_props_1scl) :: aer_lw + type(ty_optical_props_2str) :: aer_sw + + ! Flux objects contain all fluxes computed by RRTMGP. + ! SW allsky fluxes always include spectrally resolved fluxes needed for surface models. + type(ty_fluxes_byband) :: fsw + ! LW allsky fluxes only need spectrally resolved fluxes when spectralflux=.true. + type(ty_fluxes_byband) :: flw + ! Only broadband fluxes needed for clear sky (diagnostics). + type(ty_fluxes_broadband) :: fswc, flwc + + ! Arrays for output diagnostics on CAM grid. + real(r8) :: fns(pcols,pverp) ! net shortwave flux + real(r8) :: fcns(pcols,pverp) ! net clear-sky shortwave flux + real(r8) :: fnl(pcols,pverp) ! net longwave flux + real(r8) :: fcnl(pcols,pverp) ! net clear-sky longwave flux + + ! for COSP + real(r8) :: emis(pcols,pver) ! Cloud longwave emissivity + real(r8) :: gb_snow_tau(pcols,pver) ! grid-box mean snow_tau + real(r8) :: gb_snow_lw(pcols,pver) ! grid-box mean LW snow optical depth + + real(r8) :: ftem(pcols,pver) ! Temporary workspace for outfld variables + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'radiation_tend' + !-------------------------------------------------------------------------------------- + + lchnk = state%lchnk + ncol = state%ncol + + if (present(rd_out)) then + rd => rd_out + write_output = .false. + else + allocate(rd, stat=istat) + call handle_allocate_error(istat, sub, 'rd') + write_output = .true. + end if + + dosw = radiation_do('sw', get_nstep()) ! do shortwave radiation calc this timestep? + dolw = radiation_do('lw', get_nstep()) ! do longwave radiation calc this timestep? + + ! Cosine solar zenith angle for current time step + calday = get_curr_calday() + call get_rlat_all_p(lchnk, ncol, clat) + call get_rlon_all_p(lchnk, ncol, clon) + + call shr_orb_decl(calday, eccen, mvelpp, lambm0, obliqr, & + delta, eccf) + + if (use_rad_uniform_angle) then + do i = 1, ncol + coszrs(i) = shr_orb_cosz(calday, clat(i), clon(i), delta, dt_avg, & + uniform_angle=rad_uniform_angle) + end do + else + do i = 1, ncol + ! if dt_avg /= 0, it triggers using avg coszrs + coszrs(i) = shr_orb_cosz(calday, clat(i), clon(i), delta, dt_avg) + end do + end if + + ! Gather night/day column indices. + Nday = 0 + Nnite = 0 + do i = 1, ncol + if ( coszrs(i) > 0.0_r8 ) then + Nday = Nday + 1 + IdxDay(Nday) = i + else + Nnite = Nnite + 1 + IdxNite(Nnite) = i + end if + end do + + ! Associate pointers to physics buffer fields + itim_old = pbuf_old_tim_idx() + nullify(cldfsnow) + if (cldfsnow_idx > 0) then + call pbuf_get_field(pbuf, cldfsnow_idx, cldfsnow, start=(/1,1,itim_old/), kount=(/pcols,pver,1/) ) + end if + nullify(cldfgrau) + if (cldfgrau_idx > 0 .and. graupel_in_rad) then + call pbuf_get_field(pbuf, cldfgrau_idx, cldfgrau, start=(/1,1,itim_old/), kount=(/pcols,pver,1/) ) + endif + + call pbuf_get_field(pbuf, cld_idx, cld, start=(/1,1,itim_old/), kount=(/pcols,pver,1/) ) + + call pbuf_get_field(pbuf, qrs_idx, qrs) + call pbuf_get_field(pbuf, qrl_idx, qrl) + + call pbuf_get_field(pbuf, fsns_idx, fsns) + call pbuf_get_field(pbuf, fsnt_idx, fsnt) + call pbuf_get_field(pbuf, fsds_idx, fsds) + call pbuf_get_field(pbuf, flns_idx, flns) + call pbuf_get_field(pbuf, flnt_idx, flnt) + + if (spectralflux) then + call pbuf_get_field(pbuf, su_idx, su) + call pbuf_get_field(pbuf, sd_idx, sd) + call pbuf_get_field(pbuf, lu_idx, lu) + call pbuf_get_field(pbuf, ld_idx, ld) + end if + + ! Allocate the flux arrays and init to zero. + call initialize_rrtmgp_fluxes(nday, nlay+1, nswbands, fsw, do_direct=.true.) + call initialize_rrtmgp_fluxes(nday, nlay+1, nswbands, fswc, do_direct=.true.) + call initialize_rrtmgp_fluxes(ncol, nlay+1, nlwbands, flw) + call initialize_rrtmgp_fluxes(ncol, nlay+1, nlwbands, flwc) + + ! For CRM, make cloud equal to input observations: + if (scm_crm_mode .and. have_cld) then + do k = 1, pver + cld(:ncol,k)= cldobs(k) + end do + end if + + ! Find tropopause height if needed for diagnostic output + if (hist_fld_active('FSNR') .or. hist_fld_active('FLNR')) then + call tropopause_find(state, troplev, tropP=p_trop, primary=TROP_ALG_HYBSTOB, & + backup=TROP_ALG_CLIMATE) + end if + + ! Get time of next radiation calculation - albedos will need to be + ! calculated by each surface model at this time + nextsw_cday = radiation_nextsw_cday() + + if (dosw .or. dolw) then + + allocate( & + t_sfc(ncol), emis_sfc(nlwbands,ncol), toa_flux(nday,nswgpts), & + t_rad(ncol,nlay), pmid_rad(ncol,nlay), pint_rad(ncol,nlay+1), & + t_day(nday,nlay), pmid_day(nday,nlay), pint_day(nday,nlay+1), & + coszrs_day(nday), alb_dir(nswbands,nday), alb_dif(nswbands,nday), & + stat=istat) + call handle_allocate_error(istat, sub, 't_sfc,..,alb_dif') + + ! Prepares state variables, daylit columns, albedos for RRTMGP + call rrtmgp_set_state( & + state, cam_in, ncol, nlay, nday, & + idxday, coszrs, kdist_sw, t_sfc, emis_sfc, & + t_rad, pmid_rad, pint_rad, t_day, pmid_day, & + pint_day, coszrs_day, alb_dir, alb_dif) + + ! Output the mass per layer, and total column burdens for gas and aerosol + ! constituents in the climate list. + call rad_cnst_out(0, state, pbuf) + + ! Modified cloud fraction accounts for radiatively active snow and/or graupel + call modified_cloud_fraction(ncol, cld, cldfsnow, cldfgrau, cldfprime) + + !========================! + ! SHORTWAVE calculations ! + !========================! + + if (dosw) then + + ! Set cloud optical properties in cloud_sw object. + call rrtmgp_set_cloud_sw( & + state, pbuf, nlay, nday, idxday, & + nnite, idxnite, pmid_day, cld, cldfsnow, & + cldfgrau, cldfprime, graupel_in_rad, kdist_sw, cloud_sw, & + rd%tot_cld_vistau, rd%tot_icld_vistau, rd%liq_icld_vistau, & + rd%ice_icld_vistau, rd%snow_icld_vistau, rd%grau_icld_vistau, & + cld_tau_cloudsim, snow_tau_cloudsim, grau_tau_cloudsim ) + + if (write_output) then + call radiation_output_cld(lchnk, rd) + end if + + ! If no daylight columns, can't create empty RRTMGP objects + if (nday > 0) then + + ! Initialize object for gas concentrations. + errmsg = gas_concs_sw%init(gaslist_lc) + call stop_on_err(errmsg, sub, 'gas_concs_sw%init') + + ! Initialize object for combined gas + aerosol + cloud optics. + ! Allocates arrays for properties represented on g-points. + errmsg = atm_optics_sw%alloc_2str(nday, nlay, kdist_sw) + call stop_on_err(errmsg, sub, 'atm_optics_sw%alloc_2str') + + ! Initialize object for SW aerosol optics. Allocates arrays + ! for properties represented by band. + errmsg = aer_sw%alloc_2str(nday, nlay, kdist_sw%get_band_lims_wavenumber()) + call stop_on_err(errmsg, sub, 'aer_sw%alloc_2str') + + end if + + ! The climate (icall==0) calculation must occur last. + do icall = N_DIAG, 0, -1 + if (active_calls(icall)) then + + if (nday > 0) then + + ! Set gas volume mixing ratios for this call in gas_concs_sw. + call rrtmgp_set_gases_sw( & + icall, state, pbuf, nlay, nday, & + idxday, gas_concs_sw) + + ! Compute the gas optics (stored in atm_optics_sw). + ! toa_flux is the reference solar source from RRTMGP data. + errmsg = kdist_sw%gas_optics( & + pmid_day, pint_day, t_day, gas_concs_sw, atm_optics_sw, & + toa_flux) + call stop_on_err(errmsg, sub, 'kdist_sw%gas_optics') + + ! Scale the solar source + tsi_ref = sum(toa_flux(1,:)) + toa_flux = toa_flux * sol_tsi * eccf / tsi_ref + + end if + + ! Set SW aerosol optical properties in the aer_sw object. + ! This call made even when no daylight columns because it does some + ! diagnostic aerosol output. + call rrtmgp_set_aer_sw( & + icall, state, pbuf, nday, idxday, nnite, idxnite, aer_sw) + + if (nday > 0) then + + ! Increment the gas optics (in atm_optics_sw) by the aerosol optics in aer_sw. + errmsg = aer_sw%increment(atm_optics_sw) + call stop_on_err(errmsg, sub, 'aer_sw%increment') + + ! Compute clear-sky fluxes. + errmsg = rte_sw(& + atm_optics_sw, top_at_1, coszrs_day, toa_flux, & + alb_dir, alb_dif, fswc) + call stop_on_err(errmsg, sub, 'clear-sky rte_sw') + + ! Increment the aerosol+gas optics (in atm_optics_sw) by the cloud optics in cloud_sw. + errmsg = cloud_sw%increment(atm_optics_sw) + call stop_on_err(errmsg, sub, 'cloud_sw%increment') + + ! Compute all-sky fluxes. + errmsg = rte_sw(& + atm_optics_sw, top_at_1, coszrs_day, toa_flux, & + alb_dir, alb_dif, fsw) + call stop_on_err(errmsg, sub, 'all-sky rte_sw') + + end if + + ! Transform RRTMGP outputs to CAM outputs and compute heating rates. + call set_sw_diags() + + if (write_output) then + call radiation_output_sw(lchnk, ncol, icall, rd, pbuf, cam_out) + end if + + end if ! (active_calls(icall)) + end do ! loop over diagnostic calcs (icall) + + else + ! SW calc not done. pbuf carries Q*dp across timesteps. + ! Convert to Q before calling radheat_tend. + qrs(:ncol,:) = qrs(:ncol,:) / state%pdel(:ncol,:) + end if ! if (dosw) + + !=======================! + ! LONGWAVE calculations ! + !=======================! + + if (dolw) then + + ! Initialize object for Planck sources. + errmsg = sources_lw%alloc(ncol, nlay, kdist_lw) + call stop_on_err(errmsg, sub, 'sources_lw%alloc') + + ! Set cloud optical properties in cloud_lw object. + call rrtmgp_set_cloud_lw( & + state, pbuf, ncol, nlay, nlaycam, & + cld, cldfsnow, cldfgrau, cldfprime, graupel_in_rad, & + kdist_lw, cloud_lw, cld_lw_abs_cloudsim, snow_lw_abs_cloudsim, grau_lw_abs_cloudsim) + + ! Initialize object for gas concentrations + errmsg = gas_concs_lw%init(gaslist_lc) + call stop_on_err(errmsg, sub, 'gas_concs_lw%init') + + ! Initialize object for combined gas + aerosol + cloud optics. + errmsg = atm_optics_lw%alloc_1scl(ncol, nlay, kdist_lw) + call stop_on_err(errmsg, sub, 'atm_optics_lw%alloc_1scl') + + ! Initialize object for LW aerosol optics. + errmsg = aer_lw%alloc_1scl(ncol, nlay, kdist_lw%get_band_lims_wavenumber()) + call stop_on_err(errmsg, sub, 'aer_lw%alloc_1scl') + + ! The climate (icall==0) calculation must occur last. + do icall = N_DIAG, 0, -1 + + if (active_calls(icall)) then + + ! Set gas volume mixing ratios for this call in gas_concs_lw. + call rrtmgp_set_gases_lw(icall, state, pbuf, nlay, gas_concs_lw) + + ! Compute the gas optics and Planck sources. + errmsg = kdist_lw%gas_optics( & + pmid_rad, pint_rad, t_rad, t_sfc, gas_concs_lw, & + atm_optics_lw, sources_lw) + call stop_on_err(errmsg, sub, 'kdist_lw%gas_optics') + + ! Set LW aerosol optical properties in the aer_lw object. + call rrtmgp_set_aer_lw(icall, state, pbuf, aer_lw) + + ! Increment the gas optics by the aerosol optics. + errmsg = aer_lw%increment(atm_optics_lw) + call stop_on_err(errmsg, sub, 'aer_lw%increment') + + ! Compute clear-sky LW fluxes + errmsg = rte_lw(atm_optics_lw, top_at_1, sources_lw, emis_sfc, flwc) + call stop_on_err(errmsg, sub, 'clear-sky rte_lw') + + ! Increment the gas+aerosol optics by the cloud optics. + errmsg = cloud_lw%increment(atm_optics_lw) + call stop_on_err(errmsg, sub, 'cloud_lw%increment') + + ! Compute all-sky LW fluxes + errmsg = rte_lw(atm_optics_lw, top_at_1, sources_lw, emis_sfc, flw) + call stop_on_err(errmsg, sub, 'all-sky rte_lw') + + ! Transform RRTMGP outputs to CAM outputs and compute heating rates. + call set_lw_diags() + + if (write_output) then + call radiation_output_lw(lchnk, ncol, icall, rd, pbuf, cam_out) + end if + + end if ! (active_calls(icall)) + end do ! loop over diagnostic calcs (icall) + + else + ! LW calc not done. pbuf carries Q*dp across timesteps. + ! Convert to Q before calling radheat_tend. + qrl(:ncol,:) = qrl(:ncol,:) / state%pdel(:ncol,:) + end if ! if (dolw) + + deallocate( & + t_sfc, emis_sfc, toa_flux, t_rad, pmid_rad, pint_rad, & + t_day, pmid_day, pint_day, coszrs_day, alb_dir, alb_dif) + + !================! + ! COSP simulator ! + !================! + + if (docosp) then + + emis(:,:) = 0._r8 + emis(:ncol,:) = 1._r8 - exp(-cld_lw_abs_cloudsim(:ncol,:)) + call outfld('EMIS', emis, pcols, lchnk) + + ! compute grid-box mean SW and LW snow optical depth for use by COSP + gb_snow_tau(:,:) = 0._r8 + gb_snow_lw(:,:) = 0._r8 + if (cldfsnow_idx > 0) then + do i = 1, ncol + do k = 1, pver + if (cldfsnow(i,k) > 0._r8) then + + ! Add graupel to snow tau for cosp + if (cldfgrau_idx > 0 .and. graupel_in_rad) then + gb_snow_tau(i,k) = snow_tau_cloudsim(i,k)*cldfsnow(i,k) + & + grau_tau_cloudsim(i,k)*cldfgrau(i,k) + gb_snow_lw(i,k) = snow_lw_abs_cloudsim(i,k)*cldfsnow(i,k) + & + grau_lw_abs_cloudsim(i,k)*cldfgrau(i,k) + else + gb_snow_tau(i,k) = snow_tau_cloudsim(i,k)*cldfsnow(i,k) + gb_snow_lw(i,k) = snow_lw_abs_cloudsim(i,k)*cldfsnow(i,k) + end if + end if + end do + end do + end if + + ! advance counter for this timestep (chunk dimension required for thread safety) + cosp_cnt(lchnk) = cosp_cnt(lchnk) + 1 + + ! if counter is the same as cosp_nradsteps, run cosp and reset counter + if (cosp_nradsteps .eq. cosp_cnt(lchnk)) then + + ! N.B.: For snow optical properties, the GRID-BOX MEAN shortwave and longwave + ! optical depths are passed. + call cospsimulator_intr_run( & + state, pbuf, cam_in, emis, coszrs, & + cld_swtau_in=cld_tau_cloudsim, snow_tau_in=gb_snow_tau, & + snow_emis_in=gb_snow_lw) + cosp_cnt(lchnk) = 0 + end if + end if ! docosp + + else + ! Radiative flux calculations not done. The quantity Q*dp is carried by the + ! physics buffer across timesteps. It must be converted to Q (dry static energy + ! tendency) before being passed to radheat_tend. + qrs(:ncol,:) = qrs(:ncol,:) / state%pdel(:ncol,:) + qrl(:ncol,:) = qrl(:ncol,:) / state%pdel(:ncol,:) + + end if ! if (dosw .or. dolw) then + + ! Output for PORT: Parallel Offline Radiative Transport + call rad_data_write(pbuf, state, cam_in, coszrs) + + ! Compute net radiative heating tendency. Note that the WACCM version + ! of radheat_tend merges upper atmosphere heating rates with those calculated + ! by RRTMGP. + call radheat_tend(state, pbuf, ptend, qrl, qrs, fsns, & + fsnt, flns, flnt, cam_in%asdir, net_flx) + + if (write_output) then + ! Compute heating rate for dtheta/dt + do k = 1, pver + do i = 1, ncol + ftem(i,k) = (qrs(i,k) + qrl(i,k))/cpair * (1.e5_r8/state%pmid(i,k))**cappa + end do + end do + call outfld('HR', ftem, pcols, lchnk) + end if + + ! The radiative heating rates are carried in the physics buffer across timesteps + ! as Q*dp (for energy conservation). + qrs(:ncol,:) = qrs(:ncol,:) * state%pdel(:ncol,:) + qrl(:ncol,:) = qrl(:ncol,:) * state%pdel(:ncol,:) + + if (.not. present(rd_out)) then + deallocate(rd) + end if + call free_optics_sw(atm_optics_sw) + call free_optics_sw(cloud_sw) + call free_optics_sw(aer_sw) + call free_fluxes(fsw) + call free_fluxes(fswc) + + call sources_lw%finalize() + call free_optics_lw(cloud_lw) + call free_optics_lw(aer_lw) + call free_fluxes(flw) + call free_fluxes(flwc) + + !------------------------------------------------------------------------------- + contains + !------------------------------------------------------------------------------- + + subroutine set_sw_diags() + + ! Transform RRTMGP output for CAM and compute heating rates. + ! SW fluxes from RRTMGP are on daylight columns only, so expand to + ! full chunks for output to CAM history. + + integer :: i + real(r8), dimension(size(fsw%bnd_flux_dn,1), & + size(fsw%bnd_flux_dn,2), & + size(fsw%bnd_flux_dn,3)) :: flux_dn_diffuse + !------------------------------------------------------------------------- + + ! Initialize to provide 0.0 values for night columns. + fns = 0._r8 ! net sw flux + fcns = 0._r8 ! net sw clearsky flux + fsds = 0._r8 ! downward sw flux at surface + rd%fsdsc = 0._r8 ! downward sw clearsky flux at surface + rd%fsutoa = 0._r8 ! upward sw flux at TOA + rd%fsntoa = 0._r8 ! net sw at TOA + rd%fsntoac = 0._r8 ! net sw clearsky flux at TOA + rd%solin = 0._r8 ! solar irradiance at TOA + rd%flux_sw_up = 0._r8 + rd%flux_sw_dn = 0._r8 + rd%flux_sw_clr_up = 0._r8 + rd%flux_sw_clr_dn = 0._r8 + + qrs = 0._r8 + fsns = 0._r8 + fsnt = 0._r8 + rd%qrsc = 0._r8 + rd%fsnsc = 0._r8 + rd%fsntc = 0._r8 + + do i = 1, nday + fns(idxday(i),ktopcam:) = fsw%flux_net(i, ktoprad:) + fcns(idxday(i),ktopcam:) = fswc%flux_net(i,ktoprad:) + fsds(idxday(i)) = fsw%flux_dn(i, nlay+1) + rd%fsdsc(idxday(i)) = fswc%flux_dn(i, nlay+1) + rd%fsutoa(idxday(i)) = fsw%flux_up(i, 1) + rd%fsntoa(idxday(i)) = fsw%flux_net(i, 1) + rd%fsntoac(idxday(i)) = fswc%flux_net(i, 1) + rd%solin(idxday(i)) = fswc%flux_dn(i, 1) + rd%flux_sw_up(idxday(i),ktopcam:) = fsw%flux_up(i,ktoprad:) + rd%flux_sw_dn(idxday(i),ktopcam:) = fsw%flux_dn(i,ktoprad:) + rd%flux_sw_clr_up(idxday(i),ktopcam:) = fswc%flux_up(i,ktoprad:) + rd%flux_sw_clr_dn(idxday(i),ktopcam:) = fswc%flux_dn(i,ktoprad:) + end do + + ! Compute heating rate as a dry static energy tendency. + call heating_rate('SW', ncol, fns, qrs) + call heating_rate('SW', ncol, fcns, rd%qrsc) + + fsns(:ncol) = fns(:ncol,pverp) ! net sw flux at surface + fsnt(:ncol) = fns(:ncol,ktopcam) ! net sw flux at top-of-model (w/o extra layer) + rd%fsnsc(:ncol) = fcns(:ncol,pverp) ! net sw clearsky flux at surface + rd%fsntc(:ncol) = fcns(:ncol,ktopcam) ! net sw clearsky flux at top + + cam_out%netsw(:ncol) = fsns(:ncol) + + ! Output fluxes at 200 mb + call vertinterp(ncol, pcols, pverp, state%pint, 20000._r8, fns, rd%fsn200) + call vertinterp(ncol, pcols, pverp, state%pint, 20000._r8, fcns, rd%fsn200c) + if (hist_fld_active('FSNR')) then + do i = 1,ncol + call vertinterp(1, 1, pverp, state%pint(i,:), p_trop(i), fns(i,:), rd%fsnr(i)) + end do + end if + + if (spectralflux) then + su = 0._r8 + sd = 0._r8 + do i = 1, nday + su(idxday(i),ktopcam:,:) = fsw%bnd_flux_up(i,ktoprad:,:) + sd(idxday(i),ktopcam:,:) = fsw%bnd_flux_dn(i,ktoprad:,:) + end do + end if + + ! Export surface fluxes + ! sols(pcols) Direct solar rad on surface (< 0.7) + ! soll(pcols) Direct solar rad on surface (>= 0.7) + ! RRTMGP: Near-IR bands (1-10), 820-16000 cm-1, 0.625-12.195 microns + ! Put half of band 10 in each of the UV/visible and near-IR values, + ! since this band straddles 0.7 microns: + ! UV/visible bands 10-13, 16000-50000 cm-1, 0.200-0.625 micron + + ! reset fluxes + cam_out%sols = 0.0_r8 + cam_out%soll = 0.0_r8 + cam_out%solsd = 0.0_r8 + cam_out%solld = 0.0_r8 + + ! Calculate diffuse flux from total and direct + flux_dn_diffuse = fsw%bnd_flux_dn - fsw%bnd_flux_dn_dir + + do i = 1, nday + cam_out%soll(idxday(i)) = sum(fsw%bnd_flux_dn_dir(i,nlay+1,1:9)) & + + 0.5_r8 * fsw%bnd_flux_dn_dir(i,nlay+1,10) + + cam_out%sols(idxday(i)) = 0.5_r8 * fsw%bnd_flux_dn_dir(i,nlay+1,10) & + + sum(fsw%bnd_flux_dn_dir(i,nlay+1,11:14)) + + cam_out%solld(idxday(i)) = sum(flux_dn_diffuse(i,nlay+1,1:9)) & + + 0.5_r8 * flux_dn_diffuse(i,nlay+1,10) + + cam_out%solsd(idxday(i)) = 0.5_r8 * flux_dn_diffuse(i, nlay+1, 10) & + + sum(flux_dn_diffuse(i,nlay+1,11:14)) + end do + + end subroutine set_sw_diags + + !------------------------------------------------------------------------------- + + subroutine set_lw_diags() + + ! Set CAM LW diagnostics + !---------------------------------------------------------------------------- + + fnl = 0._r8 + fcnl = 0._r8 + + ! RTE-RRTMGP convention for net is (down - up) **CAM assumes (up - down) !! + fnl(:ncol,ktopcam:) = -1._r8 * flw%flux_net( :, ktoprad:) + fcnl(:ncol,ktopcam:) = -1._r8 * flwc%flux_net( :, ktoprad:) + + rd%flux_lw_up(:ncol,ktopcam:) = flw%flux_up( :, ktoprad:) + rd%flux_lw_clr_up(:ncol,ktopcam:) = flwc%flux_up(:, ktoprad:) + rd%flux_lw_dn(:ncol,ktopcam:) = flw%flux_dn( :, ktoprad:) + rd%flux_lw_clr_dn(:ncol,ktopcam:) = flwc%flux_dn(:, ktoprad:) + + call heating_rate('LW', ncol, fnl, qrl) + call heating_rate('LW', ncol, fcnl, rd%qrlc) + + flns(:ncol) = fnl(:ncol, pverp) + flnt(:ncol) = fnl(:ncol, ktopcam) + + rd%flnsc(:ncol) = fcnl(:ncol, pverp) + rd%flntc(:ncol) = fcnl(:ncol, ktopcam) ! net lw flux at top-of-model + + cam_out%flwds(:ncol) = flw%flux_dn(:, nlay+1) + rd%fldsc(:ncol) = flwc%flux_dn(:, nlay+1) + + rd%flut(:ncol) = flw%flux_up(:, ktoprad) + rd%flutc(:ncol) = flwc%flux_up(:, ktoprad) + + ! Output fluxes at 200 mb + call vertinterp(ncol, pcols, pverp, state%pint, 20000._r8, fnl, rd%fln200) + call vertinterp(ncol, pcols, pverp, state%pint, 20000._r8, fcnl, rd%fln200c) + if (hist_fld_active('FLNR')) then + do i = 1,ncol + call vertinterp(1, 1, pverp, state%pint(i,:), p_trop(i), fnl(i,:), rd%flnr(i)) + end do + end if + + if (spectralflux) then + lu = 0._r8 + ld = 0._r8 + lu(:ncol, ktopcam:, :) = flw%bnd_flux_up(:, ktoprad:, :) + ld(:ncol, ktopcam:, :) = flw%bnd_flux_dn(:, ktoprad:, :) + end if + + end subroutine set_lw_diags + + !------------------------------------------------------------------------------- + + subroutine heating_rate(type, ncol, flux_net, hrate) + + ! Compute heating rate as a dry static energy tendency + + ! arguments + character(2), intent(in) :: type ! either LW or SW + integer, intent(in) :: ncol + real(r8), intent(in) :: flux_net(pcols,pverp) ! W/m^2 + real(r8), intent(out) :: hrate(pcols,pver) ! J/kg/s + + ! local vars + integer :: k + + ! Initialize for layers where RRTMGP is not providing fluxes. + hrate = 0.0_r8 + + select case (type) + case ('LW') + + do k = ktopcam, pver + ! (flux divergence as bottom-MINUS-top) * g/dp + hrate(:ncol,k) = (flux_net(:ncol,k+1) - flux_net(:ncol,k)) * & + gravit * state%rpdel(:ncol,k) + end do + + case ('SW') + + do k = ktopcam, pver + ! top - bottom + hrate(:ncol,k) = (flux_net(:ncol,k) - flux_net(:ncol,k+1)) * & + gravit * state%rpdel(:ncol,k) + end do + + end select + + end subroutine heating_rate + + !---------------------------------------------------------------------------- + ! -- end contains statement of radiation_tend -- + !---------------------------------------------------------------------------- +end subroutine radiation_tend + +!=============================================================================== + +subroutine radiation_output_sw(lchnk, ncol, icall, rd, pbuf, cam_out) + + ! Dump shortwave radiation information to history buffer. + + integer , intent(in) :: lchnk + integer, intent(in) :: ncol + integer, intent(in) :: icall + type(rad_out_t), intent(in) :: rd + type(physics_buffer_desc), pointer :: pbuf(:) + type(cam_out_t), intent(in) :: cam_out + + ! local variables + real(r8), pointer :: qrs(:,:) + real(r8), pointer :: fsnt(:) + real(r8), pointer :: fsns(:) + real(r8), pointer :: fsds(:) + + real(r8) :: ftem(pcols) + !---------------------------------------------------------------------------- + + call pbuf_get_field(pbuf, qrs_idx, qrs) + call pbuf_get_field(pbuf, fsnt_idx, fsnt) + call pbuf_get_field(pbuf, fsns_idx, fsns) + call pbuf_get_field(pbuf, fsds_idx, fsds) + + call outfld('SOLIN'//diag(icall), rd%solin, pcols, lchnk) + + ! QRS is output as temperature tendency. + call outfld('QRS'//diag(icall), qrs(:ncol,:)/cpair, ncol, lchnk) + call outfld('QRSC'//diag(icall), rd%qrsc(:ncol,:)/cpair, ncol, lchnk) + + call outfld('FSNT'//diag(icall), fsnt, pcols, lchnk) + call outfld('FSNTC'//diag(icall), rd%fsntc, pcols, lchnk) + call outfld('FSNTOA'//diag(icall), rd%fsntoa, pcols, lchnk) + call outfld('FSNTOAC'//diag(icall), rd%fsntoac, pcols, lchnk) + + ftem(:ncol) = rd%fsntoa(:ncol) - rd%fsntoac(:ncol) + call outfld('SWCF'//diag(icall), ftem, pcols, lchnk) + + call outfld('FSUTOA'//diag(icall), rd%fsutoa, pcols, lchnk) + + call outfld('FSNIRTOA'//diag(icall), rd%fsnirt, pcols, lchnk) + call outfld('FSNRTOAC'//diag(icall), rd%fsnrtc, pcols, lchnk) + call outfld('FSNRTOAS'//diag(icall), rd%fsnirtsq, pcols, lchnk) + + call outfld('FSN200'//diag(icall), rd%fsn200, pcols, lchnk) + call outfld('FSN200C'//diag(icall), rd%fsn200c, pcols, lchnk) + + call outfld('FSNR'//diag(icall), rd%fsnr, pcols, lchnk) + + call outfld('SOLS'//diag(icall), cam_out%sols, pcols, lchnk) + call outfld('SOLL'//diag(icall), cam_out%soll, pcols, lchnk) + call outfld('SOLSD'//diag(icall), cam_out%solsd, pcols, lchnk) + call outfld('SOLLD'//diag(icall), cam_out%solld, pcols, lchnk) + + call outfld('FSNS'//diag(icall), fsns, pcols, lchnk) + call outfld('FSNSC'//diag(icall), rd%fsnsc, pcols, lchnk) + + call outfld('FSDS'//diag(icall), fsds, pcols, lchnk) + call outfld('FSDSC'//diag(icall), rd%fsdsc, pcols, lchnk) + + call outfld('FUS'//diag(icall), rd%flux_sw_up, pcols, lchnk) + call outfld('FUSC'//diag(icall), rd%flux_sw_clr_up, pcols, lchnk) + call outfld('FDS'//diag(icall), rd%flux_sw_dn, pcols, lchnk) + call outfld('FDSC'//diag(icall), rd%flux_sw_clr_dn, pcols, lchnk) + +end subroutine radiation_output_sw + +!=============================================================================== + +subroutine radiation_output_cld(lchnk, rd) + + ! Dump shortwave cloud optics information to history buffer. + + integer , intent(in) :: lchnk + type(rad_out_t), intent(in) :: rd + !---------------------------------------------------------------------------- + + call outfld('TOT_CLD_VISTAU', rd%tot_cld_vistau, pcols, lchnk) + call outfld('TOT_ICLD_VISTAU', rd%tot_icld_vistau, pcols, lchnk) + call outfld('LIQ_ICLD_VISTAU', rd%liq_icld_vistau, pcols, lchnk) + call outfld('ICE_ICLD_VISTAU', rd%ice_icld_vistau, pcols, lchnk) + if (cldfsnow_idx > 0) then + call outfld('SNOW_ICLD_VISTAU', rd%snow_icld_vistau, pcols, lchnk) + endif + if (cldfgrau_idx > 0 .and. graupel_in_rad) then + call outfld('GRAU_ICLD_VISTAU', rd%grau_icld_vistau , pcols, lchnk) + endif + +end subroutine radiation_output_cld + +!=============================================================================== + +subroutine radiation_output_lw(lchnk, ncol, icall, rd, pbuf, cam_out) + + ! Dump longwave radiation information to history buffer + + integer, intent(in) :: lchnk + integer, intent(in) :: ncol + integer, intent(in) :: icall ! icall=0 for climate diagnostics + type(rad_out_t), intent(in) :: rd + type(physics_buffer_desc), pointer :: pbuf(:) + type(cam_out_t), intent(in) :: cam_out + + ! local variables + real(r8), pointer :: qrl(:,:) + real(r8), pointer :: flnt(:) + real(r8), pointer :: flns(:) + + real(r8) :: ftem(pcols) + !---------------------------------------------------------------------------- + + call pbuf_get_field(pbuf, qrl_idx, qrl) + call pbuf_get_field(pbuf, flnt_idx, flnt) + call pbuf_get_field(pbuf, flns_idx, flns) + + call outfld('QRL'//diag(icall), qrl(:ncol,:)/cpair, ncol, lchnk) + call outfld('QRLC'//diag(icall), rd%qrlc(:ncol,:)/cpair, ncol, lchnk) + + call outfld('FLNT'//diag(icall), flnt, pcols, lchnk) + call outfld('FLNTC'//diag(icall), rd%flntc, pcols, lchnk) + + call outfld('FLUT'//diag(icall), rd%flut, pcols, lchnk) + call outfld('FLUTC'//diag(icall), rd%flutc, pcols, lchnk) + + ftem(:ncol) = rd%flutc(:ncol) - rd%flut(:ncol) + call outfld('LWCF'//diag(icall), ftem, pcols, lchnk) + + call outfld('FLN200'//diag(icall), rd%fln200, pcols, lchnk) + call outfld('FLN200C'//diag(icall), rd%fln200c, pcols, lchnk) + + call outfld('FLNR'//diag(icall), rd%flnr, pcols, lchnk) + + call outfld('FLNS'//diag(icall), flns, pcols, lchnk) + call outfld('FLNSC'//diag(icall), rd%flnsc, pcols, lchnk) + + call outfld('FLDS'//diag(icall), cam_out%flwds, pcols, lchnk) + call outfld('FLDSC'//diag(icall), rd%fldsc, pcols, lchnk) + + call outfld('FDL'//diag(icall), rd%flux_lw_dn, pcols, lchnk) + call outfld('FDLC'//diag(icall), rd%flux_lw_clr_dn, pcols, lchnk) + call outfld('FUL'//diag(icall), rd%flux_lw_up, pcols, lchnk) + call outfld('FULC'//diag(icall), rd%flux_lw_clr_up, pcols, lchnk) + +end subroutine radiation_output_lw + +!=============================================================================== + +subroutine coefs_init(coefs_file, available_gases, kdist) + + ! Read data from coefficients file. Initialize the kdist object. + ! available_gases object provides the gas names that CAM provides. + + ! arguments + character(len=*), intent(in) :: coefs_file + class(ty_gas_concs), intent(in) :: available_gases + class(ty_gas_optics_rrtmgp), intent(out) :: kdist + + ! local variables + type(file_desc_t) :: fh ! pio file handle + character(len=cl) :: locfn ! path to file on local storage + + ! File dimensions + integer :: & + absorber, & + atmos_layer, & + bnd, & + pressure, & + temperature, & + absorber_ext, & + pressure_interp, & + mixing_fraction, & + gpt, & + temperature_Planck + + integer :: i + integer :: did, vid + integer :: ierr, istat + + character(32), dimension(:), allocatable :: gas_names + integer, dimension(:,:,:), allocatable :: key_species + integer, dimension(:,:), allocatable :: band2gpt + real(r8), dimension(:,:), allocatable :: band_lims_wavenum + real(r8), dimension(:), allocatable :: press_ref, temp_ref + real(r8) :: press_ref_trop, temp_ref_t, temp_ref_p + real(r8), dimension(:,:,:), allocatable :: vmr_ref + real(r8), dimension(:,:,:,:), allocatable :: kmajor + real(r8), dimension(:,:,:), allocatable :: kminor_lower, kminor_upper + real(r8), dimension(:,:), allocatable :: totplnk + real(r8), dimension(:,:,:,:), allocatable :: planck_frac + real(r8), dimension(:), allocatable :: solar_src_quiet, solar_src_facular, solar_src_sunspot + real(r8) :: tsi_default + real(r8), dimension(:,:,:), allocatable :: rayl_lower, rayl_upper + character(len=32), dimension(:), allocatable :: gas_minor, & + identifier_minor, & + minor_gases_lower, & + minor_gases_upper, & + scaling_gas_lower, & + scaling_gas_upper + integer, dimension(:,:), allocatable :: minor_limits_gpt_lower, & + minor_limits_gpt_upper + ! Send these to RRTMGP as logicals, + ! but they have to be read from the netCDF as integers + logical, dimension(:), allocatable :: minor_scales_with_density_lower, & + minor_scales_with_density_upper + logical, dimension(:), allocatable :: scale_by_complement_lower, & + scale_by_complement_upper + integer, dimension(:), allocatable :: int2log ! use this to convert integer-to-logical. + integer, dimension(:), allocatable :: kminor_start_lower, kminor_start_upper + real(r8), dimension(:,:), allocatable :: optimal_angle_fit + real(r8) :: mg_default, sb_default + + integer :: pairs, & + minorabsorbers, & + minor_absorber_intervals_lower, & + minor_absorber_intervals_upper, & + contributors_lower, & + contributors_upper, & + fit_coeffs + + character(len=128) :: error_msg + character(len=*), parameter :: sub = 'coefs_init' + !---------------------------------------------------------------------------- + + ! Open file + call getfil(coefs_file, locfn, 0) + call cam_pio_openfile(fh, locfn, PIO_NOWRITE) + + call pio_seterrorhandling(fh, PIO_BCAST_ERROR) + + ! Get dimensions + + ierr = pio_inq_dimid(fh, 'absorber', did) + if (ierr /= PIO_NOERR) call endrun(sub//': absorber not found') + ierr = pio_inq_dimlen(fh, did, absorber) + + ierr = pio_inq_dimid(fh, 'atmos_layer', did) + if (ierr /= PIO_NOERR) call endrun(sub//': atmos_layer not found') + ierr = pio_inq_dimlen(fh, did, atmos_layer) + + ierr = pio_inq_dimid(fh, 'bnd', did) + if (ierr /= PIO_NOERR) call endrun(sub//': bnd not found') + ierr = pio_inq_dimlen(fh, did, bnd) + + ierr = pio_inq_dimid(fh, 'pressure', did) + if (ierr /= PIO_NOERR) call endrun(sub//': pressure not found') + ierr = pio_inq_dimlen(fh, did, pressure) + + ierr = pio_inq_dimid(fh, 'temperature', did) + if (ierr /= PIO_NOERR) call endrun(sub//': temperature not found') + ierr = pio_inq_dimlen(fh, did, temperature) + + ierr = pio_inq_dimid(fh, 'absorber_ext', did) + if (ierr /= PIO_NOERR) call endrun(sub//': absorber_ext not found') + ierr = pio_inq_dimlen(fh, did, absorber_ext) + + ierr = pio_inq_dimid(fh, 'pressure_interp', did) + if (ierr /= PIO_NOERR) call endrun(sub//': pressure_interp not found') + ierr = pio_inq_dimlen(fh, did, pressure_interp) + + ierr = pio_inq_dimid(fh, 'mixing_fraction', did) + if (ierr /= PIO_NOERR) call endrun(sub//': mixing_fraction not found') + ierr = pio_inq_dimlen(fh, did, mixing_fraction) + + ierr = pio_inq_dimid(fh, 'gpt', did) + if (ierr /= PIO_NOERR) call endrun(sub//': gpt not found') + ierr = pio_inq_dimlen(fh, did, gpt) + + temperature_Planck = 0 + ierr = pio_inq_dimid(fh, 'temperature_Planck', did) + if (ierr == PIO_NOERR) then + ierr = pio_inq_dimlen(fh, did, temperature_Planck) + end if + ierr = pio_inq_dimid(fh, 'pair', did) + if (ierr /= PIO_NOERR) call endrun(sub//': pair not found') + ierr = pio_inq_dimlen(fh, did, pairs) + ierr = pio_inq_dimid(fh, 'minor_absorber', did) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_absorber not found') + ierr = pio_inq_dimlen(fh, did, minorabsorbers) + ierr = pio_inq_dimid(fh, 'minor_absorber_intervals_lower', did) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_absorber_intervals_lower not found') + ierr = pio_inq_dimlen(fh, did, minor_absorber_intervals_lower) + ierr = pio_inq_dimid(fh, 'minor_absorber_intervals_upper', did) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_absorber_intervals_upper not found') + ierr = pio_inq_dimlen(fh, did, minor_absorber_intervals_upper) + ierr = pio_inq_dimid(fh, 'contributors_lower', did) + if (ierr /= PIO_NOERR) call endrun(sub//': contributors_lower not found') + ierr = pio_inq_dimlen(fh, did, contributors_lower) + ierr = pio_inq_dimid(fh, 'contributors_upper', did) + if (ierr /= PIO_NOERR) call endrun(sub//': contributors_upper not found') + ierr = pio_inq_dimlen(fh, did, contributors_upper) + + ierr = pio_inq_dimid(fh, 'fit_coeffs', did) + if (ierr == PIO_NOERR) then + ierr = pio_inq_dimlen(fh, did, fit_coeffs) + end if + + ! Get variables + + ! names of absorbing gases + allocate(gas_names(absorber), stat=istat) + call handle_allocate_error(istat, sub, 'gas_names') + ierr = pio_inq_varid(fh, 'gas_names', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': gas_names not found') + ierr = pio_get_var(fh, vid, gas_names) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading gas_names') + + ! key species pair for each band + allocate(key_species(2,atmos_layer,bnd), stat=istat) + call handle_allocate_error(istat, sub, 'key_species') + ierr = pio_inq_varid(fh, 'key_species', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': key_species not found') + ierr = pio_get_var(fh, vid, key_species) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading key_species') + + ! beginning and ending gpoint for each band + allocate(band2gpt(2,bnd), stat=istat) + call handle_allocate_error(istat, sub, 'band2gpt') + ierr = pio_inq_varid(fh, 'bnd_limits_gpt', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': bnd_limits_gpt not found') + ierr = pio_get_var(fh, vid, band2gpt) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading bnd_limits_gpt') + + ! beginning and ending wavenumber for each band + allocate(band_lims_wavenum(2,bnd), stat=istat) + call handle_allocate_error(istat, sub, 'band_lims_wavenum') + ierr = pio_inq_varid(fh, 'bnd_limits_wavenumber', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': bnd_limits_wavenumber not found') + ierr = pio_get_var(fh, vid, band_lims_wavenum) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading bnd_limits_wavenumber') + + ! pressures [hPa] for reference atmosphere; press_ref(# reference layers) + allocate(press_ref(pressure), stat=istat) + call handle_allocate_error(istat, sub, 'press_ref') + ierr = pio_inq_varid(fh, 'press_ref', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': press_ref not found') + ierr = pio_get_var(fh, vid, press_ref) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading press_ref') + + ! reference pressure separating the lower and upper atmosphere + ierr = pio_inq_varid(fh, 'press_ref_trop', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': press_ref_trop not found') + ierr = pio_get_var(fh, vid, press_ref_trop) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading press_ref_trop') + + ! temperatures [K] for reference atmosphere; temp_ref(# reference layers) + allocate(temp_ref(temperature), stat=istat) + call handle_allocate_error(istat, sub, 'temp_ref') + ierr = pio_inq_varid(fh, 'temp_ref', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': temp_ref not found') + ierr = pio_get_var(fh, vid, temp_ref) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading temp_ref') + + ! standard spectroscopic reference temperature [K] + ierr = pio_inq_varid(fh, 'absorption_coefficient_ref_T', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': absorption_coefficient_ref_T not found') + ierr = pio_get_var(fh, vid, temp_ref_t) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading absorption_coefficient_ref_T') + + ! standard spectroscopic reference pressure [hPa] + ierr = pio_inq_varid(fh, 'absorption_coefficient_ref_P', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': absorption_coefficient_ref_P not found') + ierr = pio_get_var(fh, vid, temp_ref_p) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading absorption_coefficient_ref_P') + + ! volume mixing ratios for reference atmosphere + allocate(vmr_ref(atmos_layer, absorber_ext, temperature), stat=istat) + call handle_allocate_error(istat, sub, 'vmr_ref') + ierr = pio_inq_varid(fh, 'vmr_ref', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': vmr_ref not found') + ierr = pio_get_var(fh, vid, vmr_ref) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading vmr_ref') + + ! absorption coefficients due to major absorbing gases + allocate(kmajor(gpt,mixing_fraction,pressure_interp,temperature), stat=istat) + call handle_allocate_error(istat, sub, 'kmajor') + ierr = pio_inq_varid(fh, 'kmajor', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': kmajor not found') + ierr = pio_get_var(fh, vid, kmajor) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading kmajor') + + ! absorption coefficients due to minor absorbing gases in lower part of atmosphere + allocate(kminor_lower(contributors_lower, mixing_fraction, temperature), stat=istat) + call handle_allocate_error(istat, sub, 'kminor_lower') + ierr = pio_inq_varid(fh, 'kminor_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': kminor_lower not found') + ierr = pio_get_var(fh, vid, kminor_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading kminor_lower') + + ! absorption coefficients due to minor absorbing gases in upper part of atmosphere + allocate(kminor_upper(contributors_upper, mixing_fraction, temperature), stat=istat) + call handle_allocate_error(istat, sub, 'kminor_upper') + ierr = pio_inq_varid(fh, 'kminor_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': kminor_upper not found') + ierr = pio_get_var(fh, vid, kminor_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading kminor_upper') + + ! integrated Planck function by band + ierr = pio_inq_varid(fh, 'totplnk', vid) + if (ierr == PIO_NOERR) then + allocate(totplnk(temperature_Planck,bnd), stat=istat) + call handle_allocate_error(istat, sub, 'totplnk') + ierr = pio_get_var(fh, vid, totplnk) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading totplnk') + end if + + ! Planck fractions + ierr = pio_inq_varid(fh, 'plank_fraction', vid) + if (ierr == PIO_NOERR) then + allocate(planck_frac(gpt,mixing_fraction,pressure_interp,temperature), stat=istat) + call handle_allocate_error(istat, sub, 'planck_frac') + ierr = pio_get_var(fh, vid, planck_frac) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading plank_fraction') + end if + + ierr = pio_inq_varid(fh, 'optimal_angle_fit', vid) + if (ierr == PIO_NOERR) then + allocate(optimal_angle_fit(fit_coeffs, bnd), stat=istat) + call handle_allocate_error(istat, sub, 'optiman_angle_fit') + ierr = pio_get_var(fh, vid, optimal_angle_fit) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading optimal_angle_fit') + end if + + ierr = pio_inq_varid(fh, 'solar_source_quiet', vid) + if (ierr == PIO_NOERR) then + allocate(solar_src_quiet(gpt), stat=istat) + call handle_allocate_error(istat, sub, 'solar_src_quiet') + ierr = pio_get_var(fh, vid, solar_src_quiet) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading solar_source_quiet') + end if + + ierr = pio_inq_varid(fh, 'solar_source_facular', vid) + if (ierr == PIO_NOERR) then + allocate(solar_src_facular(gpt), stat=istat) + call handle_allocate_error(istat, sub, 'solar_src_facular') + ierr = pio_get_var(fh, vid, solar_src_facular) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading solar_source_facular') + end if + + ierr = pio_inq_varid(fh, 'solar_source_sunspot', vid) + if (ierr == PIO_NOERR) then + allocate(solar_src_sunspot(gpt), stat=istat) + call handle_allocate_error(istat, sub, 'solar_src_sunspot') + ierr = pio_get_var(fh, vid, solar_src_sunspot) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading solar_source_sunspot') + end if + + ierr = pio_inq_varid(fh, 'tsi_default', vid) + if (ierr == PIO_NOERR) then + ierr = pio_get_var(fh, vid, tsi_default) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading tsi_default') + end if + + ierr = pio_inq_varid(fh, 'mg_default', vid) + if (ierr == PIO_NOERR) then + ierr = pio_get_var(fh, vid, mg_default) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading mg_default') + end if + + ierr = pio_inq_varid(fh, 'sb_default', vid) + if (ierr == PIO_NOERR) then + ierr = pio_get_var(fh, vid, sb_default) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading sb_default') + end if + + ! rayleigh scattering contribution in lower part of atmosphere + ierr = pio_inq_varid(fh, 'rayl_lower', vid) + if (ierr == PIO_NOERR) then + allocate(rayl_lower(gpt,mixing_fraction,temperature), stat=istat) + call handle_allocate_error(istat, sub, 'rayl_lower') + ierr = pio_get_var(fh, vid, rayl_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading rayl_lower') + end if + + ! rayleigh scattering contribution in upper part of atmosphere + ierr = pio_inq_varid(fh, 'rayl_upper', vid) + if (ierr == PIO_NOERR) then + allocate(rayl_upper(gpt,mixing_fraction,temperature), stat=istat) + call handle_allocate_error(istat, sub, 'rayl_upper') + ierr = pio_get_var(fh, vid, rayl_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading rayl_upper') + end if + + allocate(gas_minor(minorabsorbers), stat=istat) + call handle_allocate_error(istat, sub, 'gas_minor') + ierr = pio_inq_varid(fh, 'gas_minor', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': gas_minor not found') + ierr = pio_get_var(fh, vid, gas_minor) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading gas_minor') + + allocate(identifier_minor(minorabsorbers), stat=istat) + call handle_allocate_error(istat, sub, 'identifier_minor') + ierr = pio_inq_varid(fh, 'identifier_minor', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': identifier_minor not found') + ierr = pio_get_var(fh, vid, identifier_minor) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading identifier_minor') + + allocate(minor_gases_lower(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'minor_gases_lower') + ierr = pio_inq_varid(fh, 'minor_gases_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_gases_lower not found') + ierr = pio_get_var(fh, vid, minor_gases_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_gases_lower') + + allocate(minor_gases_upper(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'minor_gases_upper') + ierr = pio_inq_varid(fh, 'minor_gases_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_gases_upper not found') + ierr = pio_get_var(fh, vid, minor_gases_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_gases_upper') + + allocate(minor_limits_gpt_lower(pairs,minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'minor_limits_gpt_lower') + ierr = pio_inq_varid(fh, 'minor_limits_gpt_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_limits_gpt_lower not found') + ierr = pio_get_var(fh, vid, minor_limits_gpt_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_limits_gpt_lower') + + allocate(minor_limits_gpt_upper(pairs,minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'minor_limits_gpt_upper') + ierr = pio_inq_varid(fh, 'minor_limits_gpt_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_limits_gpt_upper not found') + ierr = pio_get_var(fh, vid, minor_limits_gpt_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_limits_gpt_upper') + + ! Read as integer and convert to logical + allocate(int2log(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'int2log for lower') + + allocate(minor_scales_with_density_lower(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'minor_scales_with_density_lower') + ierr = pio_inq_varid(fh, 'minor_scales_with_density_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_scales_with_density_lower not found') + ierr = pio_get_var(fh, vid, int2log) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_scales_with_density_lower') + do i = 1,minor_absorber_intervals_lower + if (int2log(i) .eq. 0) then + minor_scales_with_density_lower(i) = .false. + else + minor_scales_with_density_lower(i) = .true. + end if + end do + + allocate(scale_by_complement_lower(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'scale_by_complement_lower') + ierr = pio_inq_varid(fh, 'scale_by_complement_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': scale_by_complement_lower not found') + ierr = pio_get_var(fh, vid, int2log) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading scale_by_complement_lower') + do i = 1,minor_absorber_intervals_lower + if (int2log(i) .eq. 0) then + scale_by_complement_lower(i) = .false. + else + scale_by_complement_lower(i) = .true. + end if + end do + + deallocate(int2log) + + ! Read as integer and convert to logical + allocate(int2log(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'int2log for upper') + + allocate(minor_scales_with_density_upper(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'minor_scales_with_density_upper') + ierr = pio_inq_varid(fh, 'minor_scales_with_density_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': minor_scales_with_density_upper not found') + ierr = pio_get_var(fh, vid, int2log) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading minor_scales_with_density_upper') + do i = 1,minor_absorber_intervals_upper + if (int2log(i) .eq. 0) then + minor_scales_with_density_upper(i) = .false. + else + minor_scales_with_density_upper(i) = .true. + end if + end do + + allocate(scale_by_complement_upper(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'scale_by_complement_upper') + ierr = pio_inq_varid(fh, 'scale_by_complement_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': scale_by_complement_upper not found') + ierr = pio_get_var(fh, vid, int2log) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading scale_by_complement_upper') + do i = 1,minor_absorber_intervals_upper + if (int2log(i) .eq. 0) then + scale_by_complement_upper(i) = .false. + else + scale_by_complement_upper(i) = .true. + end if + end do + + deallocate(int2log) + + allocate(scaling_gas_lower(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'scaling_gas_lower') + ierr = pio_inq_varid(fh, 'scaling_gas_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': scaling_gas_lower not found') + ierr = pio_get_var(fh, vid, scaling_gas_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading scaling_gas_lower') + + allocate(scaling_gas_upper(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'scaling_gas_upper') + ierr = pio_inq_varid(fh, 'scaling_gas_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': scaling_gas_upper not found') + ierr = pio_get_var(fh, vid, scaling_gas_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading scaling_gas_upper') + + allocate(kminor_start_lower(minor_absorber_intervals_lower), stat=istat) + call handle_allocate_error(istat, sub, 'kminor_start_lower') + ierr = pio_inq_varid(fh, 'kminor_start_lower', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': kminor_start_lower not found') + ierr = pio_get_var(fh, vid, kminor_start_lower) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading kminor_start_lower') + + allocate(kminor_start_upper(minor_absorber_intervals_upper), stat=istat) + call handle_allocate_error(istat, sub, 'kminor_start_upper') + ierr = pio_inq_varid(fh, 'kminor_start_upper', vid) + if (ierr /= PIO_NOERR) call endrun(sub//': kminor_start_upper not found') + ierr = pio_get_var(fh, vid, kminor_start_upper) + if (ierr /= PIO_NOERR) call endrun(sub//': error reading kminor_start_upper') + + ! Close file + call pio_closefile(fh) + + ! Initialize the gas optics object with data. The calls are slightly different depending + ! on whether the radiation sources are internal to the atmosphere (longwave) or external (shortwave) + + if (allocated(totplnk) .and. allocated(planck_frac)) then + error_msg = kdist%load( & + available_gases, gas_names, key_species, & + band2gpt, band_lims_wavenum, & + press_ref, press_ref_trop, temp_ref, & + temp_ref_p, temp_ref_t, vmr_ref, & + kmajor, kminor_lower, kminor_upper, & + gas_minor, identifier_minor, & + minor_gases_lower, minor_gases_upper, & + minor_limits_gpt_lower, minor_limits_gpt_upper, & + minor_scales_with_density_lower, & + minor_scales_with_density_upper, & + scaling_gas_lower, scaling_gas_upper, & + scale_by_complement_lower, scale_by_complement_upper, & + kminor_start_lower, kminor_start_upper, & + totplnk, planck_frac, rayl_lower, rayl_upper, & + optimal_angle_fit) + else if (allocated(solar_src_quiet)) then + error_msg = kdist%load( & + available_gases, gas_names, key_species, & + band2gpt, band_lims_wavenum, & + press_ref, press_ref_trop, temp_ref, & + temp_ref_p, temp_ref_t, vmr_ref, & + kmajor, kminor_lower, kminor_upper, & + gas_minor, identifier_minor, & + minor_gases_lower, minor_gases_upper, & + minor_limits_gpt_lower, minor_limits_gpt_upper, & + minor_scales_with_density_lower, & + minor_scales_with_density_upper, & + scaling_gas_lower, scaling_gas_upper, & + scale_by_complement_lower, & + scale_by_complement_upper, & + kminor_start_lower, & + kminor_start_upper, & + solar_src_quiet, solar_src_facular, solar_src_sunspot, & + tsi_default, mg_default, sb_default, & + rayl_lower, rayl_upper) + else + error_msg = 'must supply either totplnk and planck_frac, or solar_src_[*]' + end if + + call stop_on_err(error_msg, sub, 'kdist%load') + + deallocate( & + gas_names, key_species, & + band2gpt, band_lims_wavenum, & + press_ref, temp_ref, vmr_ref, & + kmajor, kminor_lower, kminor_upper, & + gas_minor, identifier_minor, & + minor_gases_lower, minor_gases_upper, & + minor_limits_gpt_lower, & + minor_limits_gpt_upper, & + minor_scales_with_density_lower, & + minor_scales_with_density_upper, & + scale_by_complement_lower, & + scale_by_complement_upper, & + scaling_gas_lower, scaling_gas_upper, & + kminor_start_lower, kminor_start_upper) + + if (allocated(totplnk)) deallocate(totplnk) + if (allocated(planck_frac)) deallocate(planck_frac) + if (allocated(optimal_angle_fit)) deallocate(optimal_angle_fit) + if (allocated(solar_src_quiet)) deallocate(solar_src_quiet) + if (allocated(solar_src_facular)) deallocate(solar_src_facular) + if (allocated(solar_src_sunspot)) deallocate(solar_src_sunspot) + if (allocated(rayl_lower)) deallocate(rayl_lower) + if (allocated(rayl_upper)) deallocate(rayl_upper) + +end subroutine coefs_init + +!========================================================================================= + +subroutine initialize_rrtmgp_fluxes(ncol, nlevels, nbands, fluxes, do_direct) + + ! Allocate flux arrays and set values to zero. + + ! Arguments + integer, intent(in) :: ncol, nlevels, nbands + class(ty_fluxes_broadband), intent(inout) :: fluxes + logical, optional, intent(in) :: do_direct + + ! Local variables + logical :: do_direct_local + integer :: istat + character(len=*), parameter :: sub = 'initialize_rrtmgp_fluxes' + !---------------------------------------------------------------------------- + + if (present(do_direct)) then + do_direct_local = .true. + else + do_direct_local = .false. + end if + + ! Broadband fluxes + allocate(fluxes%flux_up(ncol, nlevels), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%flux_up') + allocate(fluxes%flux_dn(ncol, nlevels), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%flux_dn') + allocate(fluxes%flux_net(ncol, nlevels), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%flux_net') + if (do_direct_local) then + allocate(fluxes%flux_dn_dir(ncol, nlevels), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%flux_dn_dir') + end if + + select type (fluxes) + type is (ty_fluxes_byband) + ! Fluxes by band always needed for SW. Only allocate for LW + ! when spectralflux is true. + if (nbands == nswbands .or. spectralflux) then + allocate(fluxes%bnd_flux_up(ncol, nlevels, nbands), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%bnd_flux_up') + allocate(fluxes%bnd_flux_dn(ncol, nlevels, nbands), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%bnd_flux_dn') + allocate(fluxes%bnd_flux_net(ncol, nlevels, nbands), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%bnd_flux_net') + if (do_direct_local) then + allocate(fluxes%bnd_flux_dn_dir(ncol, nlevels, nbands), stat=istat) + call handle_allocate_error(istat, sub, 'fluxes%bnd_flux_dn_dir') + end if + end if + end select + + ! Initialize + call reset_fluxes(fluxes) + +end subroutine initialize_rrtmgp_fluxes + +!========================================================================================= + +subroutine reset_fluxes(fluxes) + + ! Reset flux arrays to zero. + + class(ty_fluxes_broadband), intent(inout) :: fluxes + !---------------------------------------------------------------------------- + + ! Reset broadband fluxes + fluxes%flux_up(:,:) = 0._r8 + fluxes%flux_dn(:,:) = 0._r8 + fluxes%flux_net(:,:) = 0._r8 + if (associated(fluxes%flux_dn_dir)) fluxes%flux_dn_dir(:,:) = 0._r8 + + select type (fluxes) + type is (ty_fluxes_byband) + ! Reset band-by-band fluxes + if (associated(fluxes%bnd_flux_up)) fluxes%bnd_flux_up(:,:,:) = 0._r8 + if (associated(fluxes%bnd_flux_dn)) fluxes%bnd_flux_dn(:,:,:) = 0._r8 + if (associated(fluxes%bnd_flux_net)) fluxes%bnd_flux_net(:,:,:) = 0._r8 + if (associated(fluxes%bnd_flux_dn_dir)) fluxes%bnd_flux_dn_dir(:,:,:) = 0._r8 + end select + +end subroutine reset_fluxes + +!========================================================================================= + +subroutine free_optics_sw(optics) + + type(ty_optical_props_2str), intent(inout) :: optics + + if (allocated(optics%tau)) deallocate(optics%tau) + if (allocated(optics%ssa)) deallocate(optics%ssa) + if (allocated(optics%g)) deallocate(optics%g) + call optics%finalize() + +end subroutine free_optics_sw + +!========================================================================================= + +subroutine free_optics_lw(optics) + + type(ty_optical_props_1scl), intent(inout) :: optics + + if (allocated(optics%tau)) deallocate(optics%tau) + call optics%finalize() + +end subroutine free_optics_lw + +!========================================================================================= + +subroutine free_fluxes(fluxes) + + class(ty_fluxes_broadband), intent(inout) :: fluxes + + if (associated(fluxes%flux_up)) deallocate(fluxes%flux_up) + if (associated(fluxes%flux_dn)) deallocate(fluxes%flux_dn) + if (associated(fluxes%flux_net)) deallocate(fluxes%flux_net) + if (associated(fluxes%flux_dn_dir)) deallocate(fluxes%flux_dn_dir) + + select type (fluxes) + type is (ty_fluxes_byband) + if (associated(fluxes%bnd_flux_up)) deallocate(fluxes%bnd_flux_up) + if (associated(fluxes%bnd_flux_dn)) deallocate(fluxes%bnd_flux_dn) + if (associated(fluxes%bnd_flux_net)) deallocate(fluxes%bnd_flux_net) + if (associated(fluxes%bnd_flux_dn_dir)) deallocate(fluxes%bnd_flux_dn_dir) + end select + +end subroutine free_fluxes + +!========================================================================================= + +subroutine modified_cloud_fraction(ncol, cld, cldfsnow, cldfgrau, cldfprime) + + ! Compute modified cloud fraction, cldfprime. + ! 1. initialize as cld + ! 2. modify for snow if cldfsnow is available. use max(cld, cldfsnow) + ! 3. modify for graupel if cldfgrau is available and graupel_in_rad is true. + ! use max(cldfprime, cldfgrau) + + ! Arguments + integer, intent(in) :: ncol + real(r8), pointer :: cld(:,:) ! cloud fraction + real(r8), pointer :: cldfsnow(:,:) ! cloud fraction of just "snow clouds" + real(r8), pointer :: cldfgrau(:,:) ! cloud fraction of just "graupel clouds" + real(r8), intent(out) :: cldfprime(:,:) ! modified cloud fraction + + ! Local variables + integer :: i, k + !---------------------------------------------------------------------------- + + if (associated(cldfsnow)) then + do k = 1, pver + do i = 1, ncol + cldfprime(i,k) = max(cld(i,k), cldfsnow(i,k)) + end do + end do + else + cldfprime(:ncol,:) = cld(:ncol,:) + end if + + if (associated(cldfgrau) .and. graupel_in_rad) then + do k = 1, pver + do i = 1, ncol + cldfprime(i,k) = max(cldfprime(i,k), cldfgrau(i,k)) + end do + end do + end if + +end subroutine modified_cloud_fraction + +!========================================================================================= + +subroutine stop_on_err(errmsg, sub, info) + +! call endrun if RRTMGP function returns non-empty error message. + + character(len=*), intent(in) :: errmsg ! return message from RRTMGP function + character(len=*), intent(in) :: sub ! name of calling subroutine + character(len=*), intent(in) :: info ! name of called function + + if (len_trim(errmsg) > 0) then + call endrun(trim(sub)//': ERROR: '//trim(info)//': '//trim(errmsg)) + end if + +end subroutine stop_on_err + +!========================================================================================= + +end module radiation + diff --git a/src/physics/rrtmgp/rrtmgp_inputs.F90 b/src/physics/rrtmgp/rrtmgp_inputs.F90 new file mode 100644 index 0000000000..2f2b125e09 --- /dev/null +++ b/src/physics/rrtmgp/rrtmgp_inputs.F90 @@ -0,0 +1,1003 @@ +module rrtmgp_inputs + +!-------------------------------------------------------------------------------- +! Transform data for inputs from CAM's data structures to those used by +! RRTMGP. Subset the number of model levels if CAM's top exceeds RRTMGP's +! valid domain. Add an extra layer if CAM's top is below 1 Pa. +! The vertical indexing increases from top to bottom of atmosphere in both +! CAM and RRTMGP arrays. +!-------------------------------------------------------------------------------- + +use shr_kind_mod, only: r8=>shr_kind_r8 +use ppgrid, only: pcols, pver, pverp + +use physconst, only: stebol, pi + +use physics_types, only: physics_state +use physics_buffer, only: physics_buffer_desc +use camsrfexch, only: cam_in_t + +use radconstants, only: nradgas, gaslist, nswbands, nlwbands, nswgpts, nlwgpts, & + get_sw_spectral_boundaries, idx_sw_diag, idx_sw_cloudsim, & + idx_lw_cloudsim + +use rad_constituents, only: rad_cnst_get_gas + +use cloud_rad_props, only: get_liquid_optics_sw, liquid_cloud_get_rad_props_lw, & + get_ice_optics_sw, ice_cloud_get_rad_props_lw, & + get_snow_optics_sw, snow_cloud_get_rad_props_lw, & + get_grau_optics_sw, grau_cloud_get_rad_props_lw + +use mcica_subcol_gen, only: mcica_subcol_sw, mcica_subcol_lw + +use aer_rad_props, only: aer_rad_props_sw, aer_rad_props_lw + +use mo_gas_concentrations, only: ty_gas_concs +use mo_gas_optics_rrtmgp, only: ty_gas_optics_rrtmgp +use mo_optical_props, only: ty_optical_props_2str, ty_optical_props_1scl + +use cam_history_support, only: fillvalue +use cam_logfile, only: iulog +use cam_abortutils, only: endrun +use error_messages, only: alloc_err + +implicit none +private +save + +public :: & + rrtmgp_inputs_init, & + rrtmgp_set_state, & + rrtmgp_set_gases_lw, & + rrtmgp_set_gases_sw, & + rrtmgp_set_cloud_lw, & + rrtmgp_set_cloud_sw, & + rrtmgp_set_aer_lw, & + rrtmgp_set_aer_sw + + +! This value is to match the arbitrary small value used in RRTMG to decide +! when a quantity is effectively zero. +real(r8), parameter :: tiny = 1.0e-80_r8 + +! Indices for copying data between cam and rrtmgp arrays +integer :: ktopcam ! Index in CAM arrays of top level (layer or interface) at which + ! RRTMGP is active. +integer :: ktoprad ! Index in RRTMGP arrays of the layer or interface corresponding + ! to CAM's top layer or interface + +! wavenumber (cm^-1) boundaries of shortwave bands +real(r8) :: sw_low_bounds(nswbands), sw_high_bounds(nswbands) + +! Mapping from RRTMG shortwave bands to RRTMGP. Currently needed to continue using +! the SW optics datasets from RRTMG (even thought there is a slight mismatch in the +! band boundaries of the 2 bands that overlap with the LW bands). +integer, parameter, dimension(14) :: rrtmg_to_rrtmgp_swbands = & + [ 14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ] + +!================================================================================================== +contains +!================================================================================================== + +subroutine rrtmgp_inputs_init(ktcam, ktrad) + + ! Note that this routine must be called after the calls to set_wavenumber_bands which set + ! the sw/lw band boundaries in the radconstants module. + + integer, intent(in) :: ktcam + integer, intent(in) :: ktrad + + ktopcam = ktcam + ktoprad = ktrad + + ! Initialize the module data containing the SW band boundaries. + call get_sw_spectral_boundaries(sw_low_bounds, sw_high_bounds, 'cm^-1') + +end subroutine rrtmgp_inputs_init + +!========================================================================================= + +subroutine rrtmgp_set_state( & + state, cam_in, ncol, nlay, nday, & + idxday, coszrs, kdist_sw, t_sfc, emis_sfc, & + t_rad, pmid_rad, pint_rad, t_day, pmid_day, & + pint_day, coszrs_day, alb_dir, alb_dif) + + ! arguments + type(physics_state), intent(in) :: state ! CAM physics state + type(cam_in_t), intent(in) :: cam_in ! CAM import state + integer, intent(in) :: ncol ! # cols in CAM chunk + integer, intent(in) :: nlay ! # layers in rrtmgp grid + integer, intent(in) :: nday ! # daylight columns + integer, intent(in) :: idxday(:) ! chunk indicies of daylight columns + real(r8), intent(in) :: coszrs(:) ! cosine of solar zenith angle + class(ty_gas_optics_rrtmgp), intent(in) :: kdist_sw ! spectral information + + real(r8), intent(out) :: t_sfc(ncol) ! surface temperature [K] + real(r8), intent(out) :: emis_sfc(nlwbands,ncol) ! emissivity at surface [] + real(r8), intent(out) :: t_rad(ncol,nlay) ! layer midpoint temperatures [K] + real(r8), intent(out) :: pmid_rad(ncol,nlay) ! layer midpoint pressures [Pa] + real(r8), intent(out) :: pint_rad(ncol,nlay+1) ! layer interface pressures [Pa] + real(r8), intent(out) :: t_day(nday,nlay) ! layer midpoint temperatures [K] + real(r8), intent(out) :: pmid_day(nday,nlay) ! layer midpoint pressure [Pa] + real(r8), intent(out) :: pint_day(nday,nlay+1) ! layer interface pressures [Pa] + real(r8), intent(out) :: coszrs_day(nday) ! cosine of solar zenith angle + real(r8), intent(out) :: alb_dir(nswbands,nday) ! surface albedo, direct radiation + real(r8), intent(out) :: alb_dif(nswbands,nday) ! surface albedo, diffuse radiation + + ! local variables + integer :: i, k, iband + + real(r8) :: tref_min, tref_max + + character(len=*), parameter :: sub='rrtmgp_set_state' + character(len=512) :: errmsg + !-------------------------------------------------------------------------------- + + t_sfc = sqrt(sqrt(cam_in%lwup(:ncol)/stebol)) ! Surface temp set based on longwave up flux. + + ! Set surface emissivity to 1.0. + ! The land model *does* have its own surface emissivity, but is not spectrally resolved. + ! The LW upward flux is calculated with that land emissivity, and the "radiative temperature" + ! t_sfc is derived from that flux. We assume, therefore, that the emissivity is unity + ! to be consistent with t_sfc. + emis_sfc(:,:) = 1._r8 + + ! Level ordering is the same for both CAM and RRTMGP (top to bottom) + t_rad(:,ktoprad:) = state%t(:ncol,ktopcam:) + pmid_rad(:,ktoprad:) = state%pmid(:ncol,ktopcam:) + pint_rad(:,ktoprad:) = state%pint(:ncol,ktopcam:) + + ! Add extra layer values if needed. + if (nlay == pverp) then + t_rad(:,1) = state%t(:ncol,1) + pmid_rad(:,1) = 0.5_r8 * state%pint(:ncol,1) + ! The top reference pressure from the RRTMGP coefficients datasets is 1.005183574463 Pa + ! Set the top of the extra layer just below that. + pint_rad(:,1) = 1.01_r8 + else + ! nlay < pverp, thus the 1 Pa level is within a CAM layer. Assuming the top interface of + ! this layer is at a pressure < 1 Pa, we need to adjust the top of this layer so that it + ! is within the valid pressure range of RRTMGP (otherwise RRTMGP issues an error). Then + ! set the midpoint pressure halfway between the interfaces. + pint_rad(:,1) = 1.01_r8 + pmid_rad(:,1) = 0.5_r8 * (pint_rad(:,1) + pint_rad(:,2)) + end if + + ! Limit temperatures to be within the limits of RRTMGP validity. + tref_min = kdist_sw%get_temp_min() + tref_max = kdist_sw%get_temp_max() + t_rad = merge(t_rad, tref_min, t_rad > tref_min) + t_rad = merge(t_rad, tref_max, t_rad < tref_max) + + ! Construct arrays containing only daylight columns + do i = 1, nday + t_day(i,:) = t_rad(idxday(i),:) + pmid_day(i,:) = pmid_rad(idxday(i),:) + pint_day(i,:) = pint_rad(idxday(i),:) + coszrs_day(i) = coszrs(idxday(i)) + end do + + ! Assign albedos to the daylight columns (from E3SM implementation) + ! Albedos are imported from the surface models as broadband (visible, and near-IR), + ! and we need to map these to appropriate narrower bands used in RRTMGP. Bands + ! are categorized broadly as "visible/UV" or "infrared" based on wavenumber. + ! Loop over bands, and determine for each band whether it is broadly in the + ! visible or infrared part of the spectrum based on a dividing line of + ! 0.7 micron, or 14286 cm^-1 + do iband = 1,nswbands + if (is_visible(sw_low_bounds(iband)) .and. & + is_visible(sw_high_bounds(iband))) then + + ! Entire band is in the visible + do i = 1, nday + alb_dir(iband,i) = cam_in%asdir(idxday(i)) + alb_dif(iband,i) = cam_in%asdif(idxday(i)) + end do + + else if (.not.is_visible(sw_low_bounds(iband)) .and. & + .not.is_visible(sw_high_bounds(iband))) then + ! Entire band is in the longwave (near-infrared) + do i = 1, nday + alb_dir(iband,i) = cam_in%aldir(idxday(i)) + alb_dif(iband,i) = cam_in%aldif(idxday(i)) + end do + else + ! Band straddles the visible to near-infrared transition, so we take + ! the albedo to be the average of the visible and near-infrared + ! broadband albedos + do i = 1, nday + alb_dir(iband,i) = 0.5_r8 * (cam_in%aldir(idxday(i)) + cam_in%asdir(idxday(i))) + alb_dif(iband,i) = 0.5_r8 * (cam_in%aldif(idxday(i)) + cam_in%asdif(idxday(i))) + end do + end if + end do + + ! Strictly enforce albedo bounds + where (alb_dir < 0) + alb_dir = 0.0_r8 + end where + where (alb_dir > 1) + alb_dir = 1.0_r8 + end where + where (alb_dif < 0) + alb_dif = 0.0_r8 + end where + where (alb_dif > 1) + alb_dif = 1.0_r8 + end where + +end subroutine rrtmgp_set_state + +!========================================================================================= + +pure logical function is_visible(wavenumber) + + ! Wavenumber is in the visible if it is above the visible threshold + ! wavenumber, and in the infrared if it is below the threshold + ! This function doesn't distinquish between visible and UV. + + ! wavenumber in inverse cm (cm^-1) + real(r8), intent(in) :: wavenumber + + ! Set threshold between visible and infrared to 0.7 micron, or 14286 cm^-1 + real(r8), parameter :: visible_wavenumber_threshold = 14286._r8 ! cm^-1 + + if (wavenumber > visible_wavenumber_threshold) then + is_visible = .true. + else + is_visible = .false. + end if + +end function is_visible + +!========================================================================================= + +function get_molar_mass_ratio(gas_name) result(massratio) + + ! return the molar mass ratio of dry air to gas based on gas_name + + character(len=*),intent(in) :: gas_name + real(r8) :: massratio + + ! local variables + real(r8), parameter :: amdw = 1.607793_r8 ! Molecular weight of dry air / water vapor + real(r8), parameter :: amdc = 0.658114_r8 ! Molecular weight of dry air / carbon dioxide + real(r8), parameter :: amdo = 0.603428_r8 ! Molecular weight of dry air / ozone + real(r8), parameter :: amdm = 1.805423_r8 ! Molecular weight of dry air / methane + real(r8), parameter :: amdn = 0.658090_r8 ! Molecular weight of dry air / nitrous oxide + real(r8), parameter :: amdo2 = 0.905140_r8 ! Molecular weight of dry air / oxygen + real(r8), parameter :: amdc1 = 0.210852_r8 ! Molecular weight of dry air / CFC11 + real(r8), parameter :: amdc2 = 0.239546_r8 ! Molecular weight of dry air / CFC12 + + character(len=*), parameter :: sub='get_molar_mass_ratio' + !---------------------------------------------------------------------------- + + select case (trim(gas_name)) + case ('H2O') + massratio = amdw + case ('CO2') + massratio = amdc + case ('O3') + massratio = amdo + case ('CH4') + massratio = amdm + case ('N2O') + massratio = amdn + case ('O2') + massratio = amdo2 + case ('CFC11') + massratio = amdc1 + case ('CFC12') + massratio = amdc2 + case default + call endrun(sub//": Invalid gas: "//trim(gas_name)) + end select + +end function get_molar_mass_ratio + +!========================================================================================= + +subroutine rad_gas_get_vmr(icall, gas_name, state, pbuf, nlay, numactivecols, gas_concs, idxday) + + ! Set volume mixing ratio in gas_concs object. + ! The gas_concs%set_vmr method copies data into internally allocated storage. + + integer, intent(in) :: icall ! index of climate/diagnostic radiation call + character(len=*), intent(in) :: gas_name + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: nlay ! number of layers in radiation calculation + integer, intent(in) :: numactivecols ! number of columns, ncol for LW, nday for SW + + type(ty_gas_concs), intent(inout) :: gas_concs ! the result is VRM inside gas_concs + + integer, optional, intent(in) :: idxday(:) ! indices of daylight columns in a chunk + + ! Local variables + integer :: i, idx(numactivecols) + integer :: istat + real(r8), pointer :: gas_mmr(:,:) + real(r8), allocatable :: gas_vmr(:,:) + real(r8), allocatable :: mmr(:,:) + real(r8) :: massratio + + ! For ozone profile above model + real(r8) :: P_top, P_int, P_mid, alpha, beta, a, b, chi_mid, chi_0, chi_eff + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'rad_gas_get_vmr' + !---------------------------------------------------------------------------- + + ! set the column indices; when idxday is provided (e.g. daylit columns) use them, otherwise just count. + do i = 1, numactivecols + if (present(idxday)) then + idx(i) = idxday(i) + else + idx(i) = i + end if + end do + + ! gas_mmr points to a "chunk" in either the state or pbuf objects. That storage is + ! dimensioned (pcols,pver). + call rad_cnst_get_gas(icall, gas_name, state, pbuf, gas_mmr) + + ! Copy into storage for RRTMGP + allocate(mmr(numactivecols, nlay), stat=istat) + call alloc_err(istat, sub, 'mmr', numactivecols*nlay) + allocate(gas_vmr(numactivecols, nlay), stat=istat) + call alloc_err(istat, sub, 'gas_vmr', numactivecols*nlay) + + do i = 1, numactivecols + mmr(i,ktoprad:) = gas_mmr(idx(i),ktopcam:) + end do + + ! If an extra layer is being used, copy mmr from the top layer of CAM to the extra layer. + if (nlay == pverp) then + mmr(:,1) = mmr(:,2) + end if + + ! special case: H2O is specific humidity, not mixing ratio. Use r = q/(1-q): + if (gas_name == 'H2O') then + mmr = mmr / (1._r8 - mmr) + end if + + ! convert MMR to VMR, multipy by ratio of dry air molar mas to gas molar mass. + massratio = get_molar_mass_ratio(gas_name) + gas_vmr = mmr * massratio + + ! special case: Setting O3 in the extra layer: + ! + ! For the purpose of attenuating solar fluxes above the CAM model top, we assume that ozone + ! mixing decreases linearly in each column from the value in the top layer of CAM to zero at + ! the pressure level set by P_top. P_top has been set to 50 Pa (0.5 hPa) based on model tuning + ! to produce temperatures at the top of CAM that are most consistent with WACCM at similar pressure levels. + + if ((gas_name == 'O3') .and. (nlay == pverp)) then + P_top = 50.0_r8 + do i = 1, numactivecols + P_int = state%pint(idx(i),1) ! pressure (Pa) at upper interface of CAM + P_mid = state%pmid(idx(i),1) ! pressure (Pa) at midpoint of top layer of CAM + alpha = log(P_int/P_top) + beta = log(P_mid/P_int)/log(P_mid/P_top) + + a = ( (1._r8 + alpha) * exp(-alpha) - 1._r8 ) / alpha + b = 1._r8 - exp(-alpha) + + if (alpha .gt. 0) then ! only apply where top level is below 80 km + chi_mid = gas_vmr(i,1) ! molar mixing ratio of O3 at midpoint of top layer + chi_0 = chi_mid / (1._r8 + beta) + chi_eff = chi_0 * (a + b) + gas_vmr(i,1) = chi_eff + end if + end do + end if + + errmsg = gas_concs%set_vmr(gas_name, gas_vmr) + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR, gas_concs%set_vmr: '//trim(errmsg)) + end if + + deallocate(gas_vmr) + deallocate(mmr) + +end subroutine rad_gas_get_vmr + +!================================================================================================== + +subroutine rrtmgp_set_gases_lw(icall, state, pbuf, nlay, gas_concs) + + ! Set gas vmr for the gases in the radconstants module's gaslist. + + ! The memory management for the gas_concs object is internal. The arrays passed to it + ! are copied to the internally allocated memory. Each call to the set_vmr method checks + ! whether the gas already has memory allocated, and if it does that memory is deallocated + ! and new memory is allocated. + + ! arguments + integer, intent(in) :: icall ! index of climate/diagnostic radiation call + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: nlay + type(ty_gas_concs), intent(inout) :: gas_concs + + ! local variables + integer :: i, ncol + character(len=*), parameter :: sub = 'rrtmgp_set_gases_lw' + !-------------------------------------------------------------------------------- + + ncol = state%ncol + do i = 1, nradgas + call rad_gas_get_vmr(icall, gaslist(i), state, pbuf, nlay, ncol, gas_concs) + end do +end subroutine rrtmgp_set_gases_lw + +!================================================================================================== + +subroutine rrtmgp_set_gases_sw( & + icall, state, pbuf, nlay, nday, & + idxday, gas_concs) + + ! Return gas_concs with gas volume mixing ratio on DAYLIT columns. + ! Set all gases in radconstants gaslist. + + ! arguments + integer, intent(in) :: icall ! index of climate/diagnostic radiation call + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: nlay + integer, intent(in) :: nday + integer, intent(in) :: idxday(:) + type(ty_gas_concs), intent(inout) :: gas_concs + + ! local variables + integer :: i + character(len=*), parameter :: sub = 'rrtmgp_set_gases_sw' + !---------------------------------------------------------------------------- + + ! use the optional argument idxday to specify which columns are sunlit + do i = 1,nradgas + call rad_gas_get_vmr(icall, gaslist(i), state, pbuf, nlay, nday, gas_concs, idxday=idxday) + end do + +end subroutine rrtmgp_set_gases_sw + +!================================================================================================== + +subroutine rrtmgp_set_cloud_lw( & + state, pbuf, ncol, nlay, nlaycam, & + cld, cldfsnow, cldfgrau, cldfprime, graupel_in_rad, & + kdist_lw, cloud_lw, cld_lw_abs_cloudsim, snow_lw_abs_cloudsim, grau_lw_abs_cloudsim) + + ! Compute combined cloud optical properties. + ! Create MCICA stochastic arrays for cloud LW optical properties. + ! Initialize optical properties object (cloud_lw) and load with MCICA columns. + + ! arguments + type(physics_state), intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: ncol ! number of columns in CAM chunk + integer, intent(in) :: nlay ! number of layers in radiation calculation (may include "extra layer") + integer, intent(in) :: nlaycam ! number of CAM layers in radiation calculation + real(r8), pointer :: cld(:,:) ! cloud fraction (liq+ice) + real(r8), pointer :: cldfsnow(:,:) ! cloud fraction of just "snow clouds" + real(r8), pointer :: cldfgrau(:,:) ! cloud fraction of just "graupel clouds" + real(r8), intent(in) :: cldfprime(pcols,pver) ! combined cloud fraction + + logical, intent(in) :: graupel_in_rad ! use graupel in radiation code + class(ty_gas_optics_rrtmgp), intent(in) :: kdist_lw + type(ty_optical_props_1scl), intent(out) :: cloud_lw + + ! Diagnostic outputs + real(r8), intent(out) :: cld_lw_abs_cloudsim(pcols,pver) ! in-cloud liq+ice optical depth (for COSP) + real(r8), intent(out) :: snow_lw_abs_cloudsim(pcols,pver)! in-cloud snow optical depth (for COSP) + real(r8), intent(out) :: grau_lw_abs_cloudsim(pcols,pver)! in-cloud Graupel optical depth (for COSP) + + ! Local variables + + integer :: i, k + + ! cloud radiative parameters are "in cloud" not "in cell" + real(r8) :: liq_lw_abs(nlwbands,pcols,pver) ! liquid absorption optics depth (LW) + real(r8) :: ice_lw_abs(nlwbands,pcols,pver) ! ice absorption optics depth (LW) + real(r8) :: cld_lw_abs(nlwbands,pcols,pver) ! cloud absorption optics depth (LW) + real(r8) :: snow_lw_abs(nlwbands,pcols,pver) ! snow absorption optics depth (LW) + real(r8) :: grau_lw_abs(nlwbands,pcols,pver) ! graupel absorption optics depth (LW) + real(r8) :: c_cld_lw_abs(nlwbands,pcols,pver) ! combined cloud absorption optics depth (LW) + + ! Arrays for converting from CAM chunks to RRTMGP inputs. + real(r8) :: cldf(ncol,nlaycam) + real(r8) :: tauc(nlwbands,ncol,nlaycam) + real(r8) :: taucmcl(nlwgpts,ncol,nlaycam) + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'rrtmgp_set_cloud_lw' + !-------------------------------------------------------------------------------- + + ! Combine the cloud optical properties. These calculations are done on CAM "chunks". + + ! gammadist liquid optics + call liquid_cloud_get_rad_props_lw(state, pbuf, liq_lw_abs) + ! Mitchell ice optics + call ice_cloud_get_rad_props_lw(state, pbuf, ice_lw_abs) + + cld_lw_abs(:,:ncol,:) = liq_lw_abs(:,:ncol,:) + ice_lw_abs(:,:ncol,:) + + if (associated(cldfsnow)) then + ! add in snow + call snow_cloud_get_rad_props_lw(state, pbuf, snow_lw_abs) + do i = 1, ncol + do k = 1, pver + if (cldfprime(i,k) > 0._r8) then + c_cld_lw_abs(:,i,k) = ( cldfsnow(i,k)*snow_lw_abs(:,i,k) & + + cld(i,k)*cld_lw_abs(:,i,k) )/cldfprime(i,k) + else + c_cld_lw_abs(:,i,k) = 0._r8 + end if + end do + end do + else + c_cld_lw_abs(:,:ncol,:) = cld_lw_abs(:,:ncol,:) + end if + + ! add in graupel + if (associated(cldfgrau) .and. graupel_in_rad) then + call grau_cloud_get_rad_props_lw(state, pbuf, grau_lw_abs) + do i = 1, ncol + do k = 1, pver + if (cldfprime(i,k) > 0._r8) then + c_cld_lw_abs(:,i,k) = ( cldfgrau(i,k)*grau_lw_abs(:,i,k) & + + cld(i,k)*c_cld_lw_abs(:,i,k) )/cldfprime(i,k) + else + c_cld_lw_abs(:,i,k) = 0._r8 + end if + end do + end do + end if + + ! Cloud optics for COSP + cld_lw_abs_cloudsim = cld_lw_abs(idx_lw_cloudsim,:,:) + snow_lw_abs_cloudsim = snow_lw_abs(idx_lw_cloudsim,:,:) + grau_lw_abs_cloudsim = grau_lw_abs(idx_lw_cloudsim,:,:) + + ! Extract just the layers of CAM where RRTMGP does calculations. + + ! Subset "chunk" data so just the number of CAM layers in the + ! radiation calculation are used by MCICA to produce subcolumns. + cldf = cldfprime(:ncol, ktopcam:) + tauc = c_cld_lw_abs(:, :ncol, ktopcam:) + + ! Enforce tauc >= 0. + tauc = merge(tauc, 0.0_r8, tauc > 0.0_r8) + + ! MCICA uses spectral data (on bands) to construct subcolumns (one per g-point) + call mcica_subcol_lw( & + kdist_lw, nlwbands, nlwgpts, ncol, nlaycam, & + nlwgpts, state%pmid, cldf, tauc, taucmcl ) + + errmsg =cloud_lw%alloc_1scl(ncol, nlay, kdist_lw) + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR: cloud_lw%alloc_1scalar: '//trim(errmsg)) + end if + + ! If there is an extra layer in the radiation then this initialization + ! will provide zero optical depths there. + cloud_lw%tau = 0.0_r8 + + ! Set the properties on g-points. + do i = 1, nlwgpts + cloud_lw%tau(:,ktoprad:,i) = taucmcl(i,:,:) + end do + + ! validate checks that: tau > 0 + errmsg = cloud_lw%validate() + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR: cloud_lw%validate: '//trim(errmsg)) + end if + +end subroutine rrtmgp_set_cloud_lw + +!================================================================================================== + +subroutine rrtmgp_set_cloud_sw( & + state, pbuf, nlay, nday, idxday, & + nnite, idxnite, pmid, cld, cldfsnow, & + cldfgrau, cldfprime, graupel_in_rad, kdist_sw, cloud_sw, & + tot_cld_vistau, tot_icld_vistau, liq_icld_vistau, ice_icld_vistau, snow_icld_vistau, & + grau_icld_vistau, cld_tau_cloudsim, snow_tau_cloudsim, grau_tau_cloudsim) + + ! Compute combined cloud optical properties. + ! Create MCICA stochastic arrays for cloud SW optical properties. + ! Initialize optical properties object (cloud_sw) and load with MCICA columns. + + ! arguments + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: nlay ! number of layers in radiation calculation (may include "extra layer") + integer, intent(in) :: nday ! number of daylight columns + integer, intent(in) :: idxday(pcols) ! indices of daylight columns in the chunk + integer, intent(in) :: nnite ! number of night columns + integer, intent(in) :: idxnite(pcols) ! indices of night columns in the chunk + + real(r8), intent(in) :: pmid(nday,nlay)! pressure at layer midpoints (Pa) used to seed RNG. + + real(r8), pointer :: cld(:,:) ! cloud fraction (liq+ice) + real(r8), pointer :: cldfsnow(:,:) ! cloud fraction of just "snow clouds" + real(r8), pointer :: cldfgrau(:,:) ! cloud fraction of just "graupel clouds" + real(r8), intent(in) :: cldfprime(pcols,pver) ! combined cloud fraction + + logical, intent(in) :: graupel_in_rad ! graupel in radiation code + class(ty_gas_optics_rrtmgp), intent(in) :: kdist_sw ! shortwave gas optics object + type(ty_optical_props_2str), intent(out) :: cloud_sw ! SW cloud optical properties object + + ! Diagnostic outputs + real(r8), intent(out) :: tot_cld_vistau(pcols,pver) ! gbx total cloud optical depth + real(r8), intent(out) :: tot_icld_vistau(pcols,pver) ! in-cld total cloud optical depth + real(r8), intent(out) :: liq_icld_vistau(pcols,pver) ! in-cld liq cloud optical depth + real(r8), intent(out) :: ice_icld_vistau(pcols,pver) ! in-cld ice cloud optical depth + real(r8), intent(out) :: snow_icld_vistau(pcols,pver) ! snow in-cloud visible sw optical depth + real(r8), intent(out) :: grau_icld_vistau(pcols,pver) ! Graupel in-cloud visible sw optical depth + real(r8), intent(out) :: cld_tau_cloudsim(pcols,pver) ! in-cloud liq+ice optical depth (for COSP) + real(r8), intent(out) :: snow_tau_cloudsim(pcols,pver)! in-cloud snow optical depth (for COSP) + real(r8), intent(out) :: grau_tau_cloudsim(pcols,pver)! in-cloud Graupel optical depth (for COSP) + + ! Local variables + + integer :: i, k, ncol + integer :: igpt, nver + integer :: istat + integer, parameter :: changeseed = 1 + + ! cloud radiative parameters are "in cloud" not "in cell" + real(r8) :: ice_tau (nswbands,pcols,pver) ! ice extinction optical depth + real(r8) :: ice_tau_w (nswbands,pcols,pver) ! ice single scattering albedo * tau + real(r8) :: ice_tau_w_g(nswbands,pcols,pver) ! ice asymmetry parameter * tau * w + real(r8) :: liq_tau (nswbands,pcols,pver) ! liquid extinction optical depth + real(r8) :: liq_tau_w (nswbands,pcols,pver) ! liquid single scattering albedo * tau + real(r8) :: liq_tau_w_g(nswbands,pcols,pver) ! liquid asymmetry parameter * tau * w + real(r8) :: cld_tau (nswbands,pcols,pver) ! cloud extinction optical depth + real(r8) :: cld_tau_w (nswbands,pcols,pver) ! cloud single scattering albedo * tau + real(r8) :: cld_tau_w_g(nswbands,pcols,pver) ! cloud asymmetry parameter * w * tau + real(r8) :: snow_tau (nswbands,pcols,pver) ! snow extinction optical depth + real(r8) :: snow_tau_w (nswbands,pcols,pver) ! snow single scattering albedo * tau + real(r8) :: snow_tau_w_g(nswbands,pcols,pver) ! snow asymmetry parameter * tau * w + real(r8) :: grau_tau (nswbands,pcols,pver) ! graupel extinction optical depth + real(r8) :: grau_tau_w (nswbands,pcols,pver) ! graupel single scattering albedo * tau + real(r8) :: grau_tau_w_g(nswbands,pcols,pver) ! graupel asymmetry parameter * tau * w + real(r8) :: c_cld_tau (nswbands,pcols,pver) ! combined cloud extinction optical depth + real(r8) :: c_cld_tau_w (nswbands,pcols,pver) ! combined cloud single scattering albedo * tau + real(r8) :: c_cld_tau_w_g(nswbands,pcols,pver) ! combined cloud asymmetry parameter * w * tau + + ! RRTMGP does not use this property in its 2-stream calculations. + real(r8) :: sw_tau_w_f(nswbands,pcols,pver) ! Forward scattered fraction * tau * w. + + ! Arrays for converting from CAM chunks to RRTMGP inputs. + real(r8), allocatable :: cldf(:,:) + real(r8), allocatable :: tauc(:,:,:) + real(r8), allocatable :: ssac(:,:,:) + real(r8), allocatable :: asmc(:,:,:) + real(r8), allocatable :: taucmcl(:,:,:) + real(r8), allocatable :: ssacmcl(:,:,:) + real(r8), allocatable :: asmcmcl(:,:,:) + real(r8), allocatable :: day_cld_tau(:,:,:) + real(r8), allocatable :: day_cld_tau_w(:,:,:) + real(r8), allocatable :: day_cld_tau_w_g(:,:,:) + + character(len=128) :: errmsg + character(len=*), parameter :: sub = 'rrtmgp_set_cloud_sw' + !-------------------------------------------------------------------------------- + + ncol = state%ncol + + ! Combine the cloud optical properties. These calculations are done on CAM "chunks". + + ! gammadist liquid optics + call get_liquid_optics_sw(state, pbuf, liq_tau, liq_tau_w, liq_tau_w_g, sw_tau_w_f) + ! Mitchell ice optics + call get_ice_optics_sw(state, pbuf, ice_tau, ice_tau_w, ice_tau_w_g, sw_tau_w_f) + + cld_tau(:,:ncol,:) = liq_tau(:,:ncol,:) + ice_tau(:,:ncol,:) + cld_tau_w(:,:ncol,:) = liq_tau_w(:,:ncol,:) + ice_tau_w(:,:ncol,:) + cld_tau_w_g(:,:ncol,:) = liq_tau_w_g(:,:ncol,:) + ice_tau_w_g(:,:ncol,:) + + ! add in snow + if (associated(cldfsnow)) then + call get_snow_optics_sw(state, pbuf, snow_tau, snow_tau_w, snow_tau_w_g, sw_tau_w_f) + do i = 1, ncol + do k = 1, pver + if (cldfprime(i,k) > 0._r8) then + c_cld_tau(:,i,k) = ( cldfsnow(i,k)*snow_tau(:,i,k) & + + cld(i,k)*cld_tau(:,i,k) )/cldfprime(i,k) + c_cld_tau_w(:,i,k) = ( cldfsnow(i,k)*snow_tau_w(:,i,k) & + + cld(i,k)*cld_tau_w(:,i,k) )/cldfprime(i,k) + c_cld_tau_w_g(:,i,k) = ( cldfsnow(i,k)*snow_tau_w_g(:,i,k) & + + cld(i,k)*cld_tau_w_g(:,i,k) )/cldfprime(i,k) + else + c_cld_tau(:,i,k) = 0._r8 + c_cld_tau_w(:,i,k) = 0._r8 + c_cld_tau_w_g(:,i,k) = 0._r8 + end if + end do + end do + else + c_cld_tau(:,:ncol,:) = cld_tau(:,:ncol,:) + c_cld_tau_w(:,:ncol,:) = cld_tau_w(:,:ncol,:) + c_cld_tau_w_g(:,:ncol,:) = cld_tau_w_g(:,:ncol,:) + end if + + ! add in graupel + if (associated(cldfgrau) .and. graupel_in_rad) then + call get_grau_optics_sw(state, pbuf, grau_tau, grau_tau_w, grau_tau_w_g, sw_tau_w_f) + do i = 1, ncol + do k = 1, pver + if (cldfprime(i,k) > 0._r8) then + c_cld_tau(:,i,k) = ( cldfgrau(i,k)*grau_tau(:,i,k) & + + cld(i,k)*c_cld_tau(:,i,k) )/cldfprime(i,k) + c_cld_tau_w(:,i,k) = ( cldfgrau(i,k)*grau_tau_w(:,i,k) & + + cld(i,k)*c_cld_tau_w(:,i,k) )/cldfprime(i,k) + c_cld_tau_w_g(:,i,k) = ( cldfgrau(i,k)*grau_tau_w_g(:,i,k) & + + cld(i,k)*c_cld_tau_w_g(:,i,k) )/cldfprime(i,k) + else + c_cld_tau(:,i,k) = 0._r8 + c_cld_tau_w(:,i,k) = 0._r8 + c_cld_tau_w_g(:,i,k) = 0._r8 + end if + end do + end do + end if + + ! cloud optical properties need to be re-ordered from the RRTMG spectral bands + ! (assumed in the optics datasets) to RRTMGP's + ice_tau(:,:ncol,:) = ice_tau(rrtmg_to_rrtmgp_swbands,:ncol,:) + liq_tau(:,:ncol,:) = liq_tau(rrtmg_to_rrtmgp_swbands,:ncol,:) + c_cld_tau(:,:ncol,:) = c_cld_tau(rrtmg_to_rrtmgp_swbands,:ncol,:) + c_cld_tau_w(:,:ncol,:) = c_cld_tau_w(rrtmg_to_rrtmgp_swbands,:ncol,:) + c_cld_tau_w_g(:,:ncol,:) = c_cld_tau_w_g(rrtmg_to_rrtmgp_swbands,:ncol,:) + if (associated(cldfsnow)) then + snow_tau(:,:ncol,:) = snow_tau(rrtmg_to_rrtmgp_swbands,:ncol,:) + end if + if (associated(cldfgrau) .and. graupel_in_rad) then + grau_tau(:,:ncol,:) = grau_tau(rrtmg_to_rrtmgp_swbands,:ncol,:) + end if + + ! Set arrays for diagnostic output. + ! cloud optical depth fields for the visible band + tot_icld_vistau(:ncol,:) = c_cld_tau(idx_sw_diag,:ncol,:) + liq_icld_vistau(:ncol,:) = liq_tau(idx_sw_diag,:ncol,:) + ice_icld_vistau(:ncol,:) = ice_tau(idx_sw_diag,:ncol,:) + if (associated(cldfsnow)) then + snow_icld_vistau(:ncol,:) = snow_tau(idx_sw_diag,:ncol,:) + endif + if (associated(cldfgrau) .and. graupel_in_rad) then + grau_icld_vistau(:ncol,:) = grau_tau(idx_sw_diag,:ncol,:) + endif + + ! multiply by total cloud fraction to get gridbox value + tot_cld_vistau(:ncol,:) = c_cld_tau(idx_sw_diag,:ncol,:)*cldfprime(:ncol,:) + + ! overwrite night columns with fillvalue + do i = 1, Nnite + tot_cld_vistau(IdxNite(i),:) = fillvalue + tot_icld_vistau(IdxNite(i),:) = fillvalue + liq_icld_vistau(IdxNite(i),:) = fillvalue + ice_icld_vistau(IdxNite(i),:) = fillvalue + if (associated(cldfsnow)) then + snow_icld_vistau(IdxNite(i),:) = fillvalue + end if + if (associated(cldfgrau) .and. graupel_in_rad) then + grau_icld_vistau(IdxNite(i),:) = fillvalue + end if + end do + + ! Cloud optics for COSP + cld_tau_cloudsim = cld_tau(idx_sw_cloudsim,:,:) + snow_tau_cloudsim = snow_tau(idx_sw_cloudsim,:,:) + grau_tau_cloudsim = grau_tau(idx_sw_cloudsim,:,:) + + ! if no daylight columns the cloud_sw object isn't initialized + if (nday > 0) then + + ! number of CAM's layers in radiation calculation. Does not include the "extra layer". + nver = pver - ktopcam + 1 + + allocate( & + cldf(nday,nver), & + day_cld_tau(nswbands,nday,nver), & + day_cld_tau_w(nswbands,nday,nver), & + day_cld_tau_w_g(nswbands,nday,nver), & + tauc(nswbands,nday,nver), taucmcl(nswgpts,nday,nver), & + ssac(nswbands,nday,nver), ssacmcl(nswgpts,nday,nver), & + asmc(nswbands,nday,nver), asmcmcl(nswgpts,nday,nver), stat=istat) + call alloc_err(istat, sub, 'cldf,..,asmcmcl', 9*nswgpts*nday*nver) + + ! Subset "chunk" data so just the daylight columns, and the number of CAM layers in the + ! radiation calculation are used by MCICA to produce subcolumns. + cldf = cldfprime( idxday(1:nday), ktopcam:) + day_cld_tau = c_cld_tau( :, idxday(1:nday), ktopcam:) + day_cld_tau_w = c_cld_tau_w( :, idxday(1:nday), ktopcam:) + day_cld_tau_w_g = c_cld_tau_w_g(:, idxday(1:nday), ktopcam:) + + ! Compute the optical properties needed for the 2-stream calculations. These calculations + ! are the same as the RRTMG version. + + ! set cloud optical depth, clip @ zero + tauc = merge(day_cld_tau, 0.0_r8, day_cld_tau > 0.0_r8) + ! set value of asymmetry + asmc = merge(day_cld_tau_w_g / max(day_cld_tau_w, tiny), 0.0_r8, day_cld_tau_w > 0.0_r8) + ! set value of single scattering albedo + ssac = merge(max(day_cld_tau_w, tiny) / max(tauc, tiny), 1.0_r8 , tauc > 0.0_r8) + ! set asymmetry to zero when tauc = 0 + asmc = merge(asmc, 0.0_r8, tauc > 0.0_r8) + + ! MCICA uses spectral data (on bands) to construct subcolumns (one per g-point) + call mcica_subcol_sw( & + kdist_sw, nswbands, nswgpts, nday, nlay, & + nver, changeseed, pmid, cldf, tauc, & + ssac, asmc, taucmcl, ssacmcl, asmcmcl) + + ! Initialize object for SW cloud optical properties. + errmsg = cloud_sw%alloc_2str(nday, nlay, kdist_sw) + if (len_trim(errmsg) > 0) then + call endrun(trim(sub)//': ERROR: cloud_sw%alloc_2str: '//trim(errmsg)) + end if + + ! If there is an extra layer in the radiation then this initialization + ! will provide the optical properties there. + cloud_sw%tau = 0.0_r8 + cloud_sw%ssa = 1.0_r8 + cloud_sw%g = 0.0_r8 + + ! Set the properties on g-points. + do igpt = 1,nswgpts + cloud_sw%g (:,ktoprad:,igpt) = asmcmcl(igpt,:,:) + cloud_sw%ssa(:,ktoprad:,igpt) = ssacmcl(igpt,:,:) + cloud_sw%tau(:,ktoprad:,igpt) = taucmcl(igpt,:,:) + end do + + ! validate checks that: tau > 0, ssa is in range [0,1], and g is in range [-1,1]. + errmsg = cloud_sw%validate() + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR: cloud_sw%validate: '//trim(errmsg)) + end if + + ! delta scaling adjusts for forward scattering + errmsg = cloud_sw%delta_scale() + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR: cloud_sw%delta_scale: '//trim(errmsg)) + end if + + ! All information is in cloud_sw, now deallocate local vars. + deallocate( & + cldf, tauc, ssac, asmc, & + taucmcl, ssacmcl, asmcmcl,& + day_cld_tau, day_cld_tau_w, day_cld_tau_w_g ) + + end if + +end subroutine rrtmgp_set_cloud_sw + +!================================================================================================== + +subroutine rrtmgp_set_aer_lw(icall, state, pbuf, aer_lw) + + ! Load LW aerosol optical properties into the RRTMGP object. + + ! Arguments + integer, intent(in) :: icall + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + + type(ty_optical_props_1scl), intent(inout) :: aer_lw + + ! Local variables + integer :: ncol + + ! Aerosol LW absorption optical depth + real(r8) :: aer_lw_abs (pcols,pver,nlwbands) + + character(len=*), parameter :: sub = 'rrtmgp_set_aer_lw' + character(len=128) :: errmsg + !-------------------------------------------------------------------------------- + + ncol = state%ncol + + ! Get aerosol longwave optical properties. + call aer_rad_props_lw(icall, state, pbuf, aer_lw_abs) + + ! If there is an extra layer in the radiation then this initialization + ! will provide zero optical depths there. + aer_lw%tau = 0.0_r8 + + aer_lw%tau(:ncol, ktoprad:, :) = aer_lw_abs(:ncol, ktopcam:, :) + + errmsg = aer_lw%validate() + if (len_trim(errmsg) > 0) then + call endrun(sub//': ERROR: aer_lw%validate: '//trim(errmsg)) + end if +end subroutine rrtmgp_set_aer_lw + +!================================================================================================== + +subroutine rrtmgp_set_aer_sw( & + icall, state, pbuf, nday, idxday, nnite, idxnite, aer_sw) + + ! Load SW aerosol optical properties into the RRTMGP object. + + ! Arguments + integer, intent(in) :: icall + type(physics_state), target, intent(in) :: state + type(physics_buffer_desc), pointer :: pbuf(:) + integer, intent(in) :: nday + integer, intent(in) :: idxday(:) + integer, intent(in) :: nnite ! number of night columns + integer, intent(in) :: idxnite(pcols) ! indices of night columns in the chunk + + type(ty_optical_props_2str), intent(inout) :: aer_sw + + ! local variables + integer :: i + + ! The optical arrays dimensioned in the vertical as 0:pver. + ! The index 0 is for the extra layer used in the radiation + ! calculation. The index ktopcam assumes the CAM vertical indices are + ! in the range 1:pver, so using this index correctly ignores vertical + ! index 0. If an "extra" layer is used in the calculations, it is + ! provided and set in the RRTMGP aerosol object aer_sw. + real(r8) :: aer_tau (pcols,0:pver,nswbands) ! extinction optical depth + real(r8) :: aer_tau_w (pcols,0:pver,nswbands) ! single scattering albedo * tau + real(r8) :: aer_tau_w_g(pcols,0:pver,nswbands) ! asymmetry parameter * w * tau + real(r8) :: aer_tau_w_f(pcols,0:pver,nswbands) ! forward scattered fraction * w * tau + ! aer_tau_w_f is not used by RRTMGP. + character(len=*), parameter :: sub = 'rrtmgp_set_aer_sw' + !-------------------------------------------------------------------------------- + + ! Get aerosol shortwave optical properties. + ! Make outfld calls for aerosol optical property diagnostics. + call aer_rad_props_sw( & + icall, state, pbuf, nnite, idxnite, & + aer_tau, aer_tau_w, aer_tau_w_g, aer_tau_w_f) + + ! The aer_sw object is only initialized if nday > 0. + if (nday > 0) then + + ! aerosol optical properties need to be re-ordered from the RRTMG spectral bands + ! (as assumed in the optics datasets) to the RRTMGP band order. + aer_tau(:,:,:) = aer_tau( :,:,rrtmg_to_rrtmgp_swbands) + aer_tau_w(:,:,:) = aer_tau_w( :,:,rrtmg_to_rrtmgp_swbands) + aer_tau_w_g(:,:,:) = aer_tau_w_g(:,:,rrtmg_to_rrtmgp_swbands) + + ! If there is an extra layer in the radiation then this initialization + ! will provide default values. + aer_sw%tau = 0.0_r8 + aer_sw%ssa = 1.0_r8 + aer_sw%g = 0.0_r8 + + ! CAM fields are products tau, tau*ssa, tau*ssa*asy + ! Fields expected by RRTMGP are computed by + ! aer_sw%tau = aer_tau + ! aer_sw%ssa = aer_tau_w / aer_tau + ! aer_sw%g = aer_tau_w_g / aer_taw_w + ! aer_sw arrays have dimensions of (nday,nlay,nswbands) + + do i = 1, nday + ! set aerosol optical depth, clip to zero + aer_sw%tau(i,ktoprad:,:) = max(aer_tau(idxday(i),ktopcam:,:), 0._r8) + ! set value of single scattering albedo + aer_sw%ssa(i,ktoprad:,:) = merge(aer_tau_w(idxday(i),ktopcam:,:)/aer_tau(idxday(i),ktopcam:,:), & + 1._r8, aer_tau(idxday(i),ktopcam:,:) > 0._r8) + ! set value of asymmetry + aer_sw%g(i,ktoprad:,:) = merge(aer_tau_w_g(idxday(i),ktopcam:,:)/aer_tau_w(idxday(i),ktopcam:,:), & + 0._r8, aer_tau_w(idxday(i),ktopcam:,:) > tiny) + end do + + ! impose limits on the components + aer_sw%ssa = min(max(aer_sw%ssa, 0._r8), 1._r8) + aer_sw%g = min(max(aer_sw%g, -1._r8), 1._r8) + + end if + +end subroutine rrtmgp_set_aer_sw + +!================================================================================================== + +end module rrtmgp_inputs diff --git a/src/physics/simple/radconstants.F90 b/src/physics/simple/radconstants.F90 index b69fac1552..4476dc6669 100644 --- a/src/physics/simple/radconstants.F90 +++ b/src/physics/simple/radconstants.F90 @@ -15,11 +15,9 @@ module radconstants integer, parameter, public :: idx_lw_diag = 1 integer, parameter, public :: idx_nir_diag = 1 integer, parameter, public :: idx_uv_diag = 1 -integer, parameter, public :: nrh = 1 -integer, parameter, public :: ot_length = 32 public :: rad_gas_index -public :: get_lw_spectral_boundaries +public :: get_lw_spectral_boundaries, get_sw_spectral_boundaries integer, public, parameter :: gasnamelength = 1 integer, public, parameter :: nradgas = 1 @@ -39,6 +37,7 @@ integer function rad_gas_index(gasname) end function rad_gas_index !------------------------------------------------------------------------------ + subroutine get_lw_spectral_boundaries(low_boundaries, high_boundaries, units) ! stub should not be called @@ -49,4 +48,18 @@ subroutine get_lw_spectral_boundaries(low_boundaries, high_boundaries, units) end subroutine get_lw_spectral_boundaries +!------------------------------------------------------------------------------ + +subroutine get_sw_spectral_boundaries(low_boundaries, high_boundaries, units) + ! stub should not be called + + real(r8), intent(out) :: low_boundaries(nswbands), high_boundaries(nswbands) + character(*), intent(in) :: units ! requested units + + call endrun('get_sw_spectral_boundaries: ERROR: this is a stub') + +end subroutine get_sw_spectral_boundaries + +!------------------------------------------------------------------------------ + end module radconstants diff --git a/src/physics/spcam/crm/CLUBB/crmx_mt95.f90 b/src/physics/spcam/crm/CLUBB/crmx_mt95.f90 index 14d75bc733..7c2ff7d9db 100644 --- a/src/physics/spcam/crm/CLUBB/crmx_mt95.f90 +++ b/src/physics/spcam/crm/CLUBB/crmx_mt95.f90 @@ -1,10 +1,10 @@ ! A C-program for MT19937, with initialization improved 2002/1/26. ! Coded by Takuji Nishimura and Makoto Matsumoto. -! Code converted to Fortran 95 by José Rui Faustino de Sousa +! Code converted to Fortran 95 by Jose Rui Faustino de Sousa ! Date: 2002-02-01 -! Enhanced version by José Rui Faustino de Sousa +! Enhanced version by Jose Rui Faustino de Sousa ! Date: 2003-04-30 ! Interface: @@ -1310,7 +1310,7 @@ subroutine genrand_res53_7d( r ) end subroutine genrand_res53_7d ! These real versions are due to Isaku Wada, 2002/01/09 added - ! Altered by José Sousa genrand_real[1-3] will not return exactely + ! Altered by Jose Sousa genrand_real[1-3] will not return exactely ! the same values but should have the same properties and are faster end module crmx_mt95 diff --git a/test/system/TR8.sh b/test/system/TR8.sh index f56c9bc636..4366d63c84 100755 --- a/test/system/TR8.sh +++ b/test/system/TR8.sh @@ -12,6 +12,8 @@ ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/components/cam/src/physics/camrt rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/components/cam/src/physics/rrtmg -s aer_src rc=`expr $? + $rc` +ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/components/cam/src/physics/rrtmgp -s data,ext +rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/components/cam/src/physics/simple rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/components/cam/src/physics/waccm @@ -27,6 +29,8 @@ ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/src/physics/camrt rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/src/physics/rrtmg -s aer_src rc=`expr $? + $rc` +ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/src/physics/rrtmgp -s data,ext +rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/src/physics/simple rc=`expr $? + $rc` ruby $ADDREALKIND_EXE -r r8 -l 1 -d $CAM_ROOT/src/physics/waccm